# Mothership
Process supervisor with HTTP exposure. Launches your fleet, routes traffic, runs WASM plugins.
## Features
- **Fleet Management**: Launch and monitor processes with dependency ordering
- **Named Binds**: Multiple listeners (http, https, ws) routed to ships via TCP or Unix sockets
- **Static File Serving**: Serve static assets with configurable path prefix
- **Response Compression**: Automatic gzip/deflate/br compression
- **Health Checks**: HTTP-based monitoring with circuit breakers
- **Crash Loop Protection**: Exponential backoff for failing ships
- **TUI Dashboard**: Real-time fleet status with logs (`--tui`)
- **WASM Plugins**: Request/response processing with sandboxing
- **Prometheus Metrics**: Ship status, restarts, request counts at `/metrics`
- **Tag Filtering**: `--only web` or `--except workers` for selective launching
- **Base Templates**: Reduce config duplication
- **Graceful Shutdown**: Dependency-aware termination
## Installation
```bash
cargo install mothership
```
Or from source:
```bash
git clone https://github.com/seuros/mothership.git
cd mothership
cargo build --release
```
## Quick Start
```bash
# Initialize a new manifest
mothership init
# Validate configuration
mothership clearance
# Run the fleet
mothership
# Run with TUI dashboard
mothership --tui
```
## Configuration
Create `ship-manifest.toml`:
```toml
# Global configuration
[mothership]
metrics_port = 9090
compression = true # Enable gzip/deflate/br compression
# Static files (served before routing to ships)
[mothership.static_dir]
path = "./public"
prefix = "/static" # Requests to /static/* served from ./public
# Named binds - external listeners
[mothership.bind]
http = "0.0.0.0:80"
https = "0.0.0.0:443"
ws = "0.0.0.0:8080"
# Web ships
[[fleet.web]]
name = "app"
command = "ruby"
args = ["app.rb"]
bind = "tcp://127.0.0.1:3000"
healthcheck = "/health"
routes = [
{ bind = "http", pattern = "/.*" },
{ bind = "https", pattern = "/.*" },
]
[[fleet.web]]
name = "cable"
command = "anycable-go"
bind = "tcp://127.0.0.1:8081"
routes = [
{ bind = "ws", pattern = "/cable" },
]
# Background workers
[[fleet.workers]]
name = "sidekiq"
command = "bundle"
args = ["exec", "sidekiq"]
critical = false # Won't kill fleet on crash
# One-shot jobs
[[fleet.jobs]]
name = "migrate"
command = "rails"
args = ["db:migrate"]
oneshot = true
[[fleet.web]]
name = "main-app"
depends_on = ["migrate"] # Wait for migration
# WASM modules (optional)
[[modules]]
name = "rate_limiter"
wasm = "./modules/rate_limiter.wasm"
routes = ["/api/.*"]
phase = "request"
```
### Ship Configuration
| `name` | string | required | Unique identifier (ASCII only, no spaces) |
| `command` | string | required | Command to execute |
| `args` | string[] | `[]` | Command arguments |
| `bind` | string | - | Internal bind address (`tcp://host:port` or `unix:///path`) |
| `healthcheck` | string | - | Health check endpoint path |
| `routes` | array | `[]` | HTTP routes (see below) |
| `depends_on` | string[] | `[]` | Ships to wait for before starting |
| `env` | table | `{}` | Environment variables |
| `critical` | bool | `true` | Crash kills entire fleet |
| `oneshot` | bool | `false` | Run once and exit |
| `tags` | string[] | `[]` | Tags for filtering (`--only`, `--except`) |
| `comment` | string | - | Description (for documentation) |
### Route Configuration
Routes map mothership binds to ships. Three formats supported:
```toml
# Object format (explicit)
routes = [
{ bind = "http", pattern = "/api/.*" },
{ bind = "ws", pattern = "/cable" },
]
# Shorthand format (bind:pattern)
routes = ["http:/api/.*", "ws:/cable"]
```
### Bind Formats
```toml
# TCP with explicit prefix
bind = "tcp://127.0.0.1:3000"
# TCP without prefix
bind = "0.0.0.0:8080"
# Port only (defaults to 127.0.0.1)
bind = "3000"
# Unix socket
bind = "unix:///tmp/app.sock"
```
### Unix Socket Example (Puma)
Configure Puma in `config/puma.rb`:
```ruby
bind "unix:///tmp/puma.sock"
```
Then in `ship-manifest.toml`:
```toml
[[fleet.web]]
name = "puma"
command = "bundle"
args = ["exec", "puma"]
bind = "unix:///tmp/puma.sock"
healthcheck = "/up"
routes = ["/.*"]
```
Health checks and HTTP proxying both work over Unix sockets.
## Commands
```bash
# Run fleet (default)
mothership
mothership run
mothership run -c /path/to/manifest.toml
# Run with TUI
mothership --tui
# Filter by tags
mothership --only web # Only ships tagged "web"
mothership --only web,api # Ships tagged "web" OR "api"
mothership --except workers # All except "workers"
# Validate manifest
mothership clearance
# Initialize new manifest
mothership init
```
## Base Templates
Reduce duplication with base templates:
```toml
[base.ship]
env = { RAILS_ENV = "production" }
critical = true
tags = ["ruby"]
[base.module]
phase = "request"
tags = ["security"]
# Ships inherit from base.ship
[[fleet.web]]
name = "app"
command = "ruby"
tags = ["web"] # Combined: ["ruby", "web"]
# Inherits: env, critical from base
```
## TUI Dashboard
The TUI shows real-time fleet status:
- **Overview Tab**: Ship status, group, PID, health, routes
- **Logs Tab**: Per-ship stdout/stderr with scroll
- **Modules Tab**: WASM module status
Controls:
- `Tab` - Switch tabs
- `↑/↓` - Navigate ships
- `PgUp/PgDn` - Scroll logs
- `q` - Quit
## WASM Modules
Modules process requests/responses at the proxy layer:
```toml
[[modules]]
name = "auth"
wasm = "./modules/auth.wasm"
routes = ["/admin/.*"]
phase = "request" # or "response"
[[modules]]
name = "cache"
wasm = "./modules/cache.wasm"
routes = ["/api/.*"]
phase = "response"
config = { ttl = "3600" }
```
Modules can:
- Block requests (return custom status/body)
- Modify request path/headers
- Modify response headers
## Static Files & Compression
### Static File Serving
Serve static assets directly from a directory:
```toml
[mothership.static_dir]
path = "./public" # Directory containing static files
prefix = "/static" # URL prefix (default: "/static")
bind = "http" # Optional: limit to specific bind
```
Requests to `/static/css/app.css` serve `./public/css/app.css`.
### Response Compression
Enable automatic compression for all responses:
```toml
[mothership]
compression = true
```
Supports gzip, deflate, and brotli based on client `Accept-Encoding`.
## Environment Variables
Ships inherit all environment variables from the mothership process. Pass secrets at runtime:
```bash
MASTER_KEY=secret DATABASE_URL=postgres://... mothership run
```
Ship-specific env vars override inherited values:
```toml
[[fleet.web]]
name = "app"
command = "ruby"
env = { RAILS_ENV = "production" } # adds to inherited env
```
Shell expansion is supported:
```toml
env = {
DATABASE_URL = "${DATABASE_URL}",
REVISION = "${git rev-parse HEAD}",
}
```
## Metrics
Enable Prometheus metrics:
```toml
[mothership]
metrics_port = 9090
```
Scrape `http://127.0.0.1:9090/metrics`:
```
mothership_ship_status{ship="app",group="web"} 1
mothership_ship_healthy{ship="app",group="web"} 1
mothership_ship_restarts_total{ship="app",group="web"} 0
mothership_requests_total{route="/api"} 1234
mothership_fleet_ships_total 3
```
Also serves `/health` for liveness probes.
## License
MIT