diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 174 |
1 files changed, 56 insertions, 118 deletions
| @@ -1,167 +1,105 @@ | |||
| 1 | # Ship | 1 | # Ship |
| 2 | 2 | ||
| 3 | Ship turns a VPS into a self-hosted git server with a web frontend — similar to running cgit on your own domain. Visiting your base domain in a browser shows a cgit repo index; clicking through shows trees, commit logs, diffs, and blame. Public repos are cloneable over HTTPS. If you host Go code, `go get` works with your domain out of the box. This is the read-only, public-facing side, and it works exactly the way cgit users expect. | 3 | Deploy Go binaries and static sites to a VPS with automatic HTTPS. No agent on the server — just SSH, systemd, and Caddy. |
| 4 | 4 | ||
| 5 | The difference is what happens on the write side. When you `git push` over SSH, Ship doesn't just update the bare repo — it builds and deploys your code. A post-receive hook checks out the repo, runs `docker build`, installs a systemd service and Caddy reverse-proxy config, and restarts the app. Your deployment config (the systemd unit, the Caddyfile) lives in `.ship/` in your repo and is versioned alongside your code. Push to main and it's live; push to any other branch and nothing happens. | 5 | ## How it works |
| 6 | 6 | ||
| 7 | Not every repo needs to be a running service. If there's no Dockerfile, the push is accepted but the deploy step is skipped — the repo just sits there as a browsable, cloneable library. This makes Ship useful for Go modules that only need vanity imports and a public source view, alongside apps that need the full build-and-deploy pipeline. The same base domain serves both. | 7 | Ship is a set of Claude skills. Instead of a rigid CLI that bakes in assumptions, Claude reasons about what to do using a family of narrow, composable skills. The server is the source of truth — no local state file that can go stale. |
| 8 | 8 | ||
| 9 | Ship also supports direct deploys (SCP a binary or rsync a static directory) for cases where git push isn't the right fit. | 9 | ## Skills |
| 10 | 10 | ||
| 11 | Ship is a client-side CLI. All state lives on your laptop at `~/.config/ship/state.json`. The VPS is configured entirely over SSH — no agent or daemon runs on the server. | 11 | | Skill | What it does | |
| 12 | |-------|-------------| | ||
| 13 | | `/ship-setup` | One-time VPS config. Installs Caddy, creates directories, saves host to `~/.config/ship/config.json` | | ||
| 14 | | `/ship-status` | Show all running apps, ports, domains, and disk usage — derived live from server | | ||
| 15 | | `/ship-deploy` | Deploy a binary or static site — orchestrates the skills below | | ||
| 16 | | `/ship-binary` | Upload a pre-built binary, configure systemd + Caddy, back up SQLite | | ||
| 17 | | `/ship-static` | Rsync a dist folder, configure Caddy to serve it | | ||
| 18 | | `/ship-env` | Read/write env vars with merge semantics — never wipes existing vars | | ||
| 19 | | `/ship-caddy` | Manage per-app Caddyfile — validates before reloading | | ||
| 20 | | `/ship-service` | systemd control: start, stop, restart, logs | | ||
| 12 | 21 | ||
| 13 | ## Install | 22 | ## Install |
| 14 | 23 | ||
| 15 | ``` | 24 | Copy the `skills/` directory into `~/.claude/skills/`: |
| 16 | go install github.com/bdw/ship/cmd/ship@latest | ||
| 17 | ``` | ||
| 18 | |||
| 19 | Or build from source: | ||
| 20 | 25 | ||
| 21 | ``` | 26 | ```bash |
| 22 | go build -o ship ./cmd/ship | 27 | cp -r skills/ship-* ~/.claude/skills/ |
| 23 | ``` | 28 | ``` |
| 24 | 29 | ||
| 25 | ## Quick start | 30 | ## Quick start |
| 26 | 31 | ||
| 27 | ### 1. Set up the VPS | 32 | ### 1. Set up your VPS |
| 28 | |||
| 29 | ``` | ||
| 30 | ship host init --host user@your-vps --base-domain example.com | ||
| 31 | ``` | ||
| 32 | |||
| 33 | This installs Caddy, Docker, git, fcgiwrap, and cgit. It creates a `git` user for push access, configures sudoers for deploy hooks, and enables automatic HTTPS. The host becomes the default for subsequent commands. | ||
| 34 | |||
| 35 | ### 2. Deploy | ||
| 36 | |||
| 37 | **Git push (Docker-based app):** | ||
| 38 | |||
| 39 | ``` | ||
| 40 | ship init myapp | ||
| 41 | git add .ship/ Dockerfile | ||
| 42 | git commit -m "initial deploy" | ||
| 43 | git push origin main | ||
| 44 | ``` | ||
| 45 | |||
| 46 | **Git push (static site):** | ||
| 47 | 33 | ||
| 48 | ``` | 34 | ``` |
| 49 | ship init mysite --static | 35 | /ship-setup |
| 50 | git add .ship/ index.html | ||
| 51 | git commit -m "initial deploy" | ||
| 52 | git push origin main | ||
| 53 | ``` | 36 | ``` |
| 54 | 37 | ||
| 55 | **Git push (library / Go module):** | 38 | Claude asks for SSH host, domain, and nickname. Saves to `~/.config/ship/config.json`. Installs Caddy on the server. |
| 56 | 39 | ||
| 57 | ``` | 40 | Or run the script directly: |
| 58 | ship init mylib --public | ||
| 59 | git add . | ||
| 60 | git commit -m "initial" | ||
| 61 | git push origin main | ||
| 62 | ``` | ||
| 63 | |||
| 64 | No Dockerfile, so nothing is deployed — the repo is just browsable and cloneable at `https://example.com/mylib`. | ||
| 65 | 41 | ||
| 66 | **Direct (pre-built binary):** | 42 | ```bash |
| 67 | 43 | bash ~/.claude/skills/ship-setup/setup.sh ubuntu@1.2.3.4 example.com prod --default | |
| 68 | ``` | ||
| 69 | GOOS=linux GOARCH=amd64 go build -o myapp | ||
| 70 | ship --binary ./myapp --domain api.example.com | ||
| 71 | ``` | 44 | ``` |
| 72 | 45 | ||
| 73 | On first deployment, Ship creates a `.ship/` directory in your current working directory containing: | 46 | ### 2. Deploy |
| 74 | - `.ship/service` - systemd unit file | ||
| 75 | - `.ship/Caddyfile` - Caddy reverse proxy config | ||
| 76 | |||
| 77 | These files are uploaded on each deployment. You can edit them locally to customize your deployment (add extra Caddy routes, adjust systemd settings). The systemd service is regenerated when you update resource limits with `--memory`, `--cpu`, or `--args` flags. The Caddyfile is never regenerated, so your custom routes won't be overwritten. | ||
| 78 | |||
| 79 | You can version control `.ship/` or add it to `.gitignore` — it's your choice. | ||
| 80 | |||
| 81 | ## Commands | ||
| 82 | |||
| 83 | ### `ship init <name>` | ||
| 84 | |||
| 85 | Create a bare git repo on the VPS and generate local `.ship/` config files. | ||
| 86 | 47 | ||
| 87 | ``` | 48 | ``` |
| 88 | ship init myapp # Docker-based app | 49 | /ship-deploy |
| 89 | ship init mysite --static # static site | ||
| 90 | ship init myapp --domain custom.example.com # custom domain | ||
| 91 | ship init mylib --public # publicly cloneable (for go get) | ||
| 92 | ``` | 50 | ``` |
| 93 | 51 | ||
| 94 | ### `ship deploy <name>` | 52 | Claude asks what you're deploying (binary or static site), where it is, what to call it, and any env vars. Handles the rest. |
| 95 | |||
| 96 | Manually rebuild and deploy a git-deployed app. | ||
| 97 | |||
| 98 | ### `ship [deploy flags]` | ||
| 99 | 53 | ||
| 100 | Deploy a pre-built binary or static directory directly. | 54 | ### 3. Check status |
| 101 | 55 | ||
| 102 | ``` | 56 | ``` |
| 103 | ship --binary ./myapp --domain api.example.com | 57 | /ship-status |
| 104 | ship --binary ./myapp --domain api.example.com --env DB_HOST=localhost | ||
| 105 | ship --static --dir ./dist --domain example.com | ||
| 106 | ship --name myapi --memory 512M --cpu 50% | ||
| 107 | ``` | 58 | ``` |
| 108 | 59 | ||
| 109 | Flags: `--binary`, `--static`, `--dir`, `--domain`, `--name`, `--env`, `--env-file`, `--args`, `--file`, `--memory`, `--cpu` | 60 | Shows all running apps, ports, URLs, and disk usage — no local state file, reads directly from server. |
| 110 | |||
| 111 | ### `ship list` | ||
| 112 | |||
| 113 | List all deployments on the default host. | ||
| 114 | 61 | ||
| 115 | ### `ship status/logs/restart/remove <name>` | 62 | ## Config |
| 116 | 63 | ||
| 117 | Manage a deployment's systemd service. | 64 | `~/.config/ship/config.json` — created by `/ship-setup`. Supports multiple hosts: |
| 118 | 65 | ||
| 119 | ### `ship env` | 66 | ```json |
| 120 | 67 | { | |
| 121 | ``` | 68 | "default": "prod", |
| 122 | ship env list myapp | 69 | "hosts": { |
| 123 | ship env set myapp KEY=VALUE | 70 | "prod": { |
| 124 | ship env unset myapp KEY | 71 | "host": "ubuntu@1.2.3.4", |
| 72 | "domain": "example.com" | ||
| 73 | }, | ||
| 74 | "staging": { | ||
| 75 | "host": "ubuntu@5.6.7.8", | ||
| 76 | "domain": "staging.example.com" | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 125 | ``` | 80 | ``` |
| 126 | 81 | ||
| 127 | ### `ship host` | 82 | Deploy to a specific host: "deploy foodtracker to staging" |
| 128 | |||
| 129 | ``` | ||
| 130 | ship host init --host user@vps --base-domain example.com | ||
| 131 | ship host status | ||
| 132 | ship host update | ||
| 133 | ship host ssh | ||
| 134 | ``` | ||
| 135 | 83 | ||
| 136 | ### `ship ui` | 84 | ## Server layout |
| 137 | |||
| 138 | Launch a local web UI for viewing deployments. | ||
| 139 | |||
| 140 | ## VPS file layout | ||
| 141 | 85 | ||
| 142 | ``` | 86 | ``` |
| 143 | /srv/git/<name>.git/ # bare git repos | 87 | /usr/local/bin/<name> # binary |
| 144 | /srv/git/<name>.git/hooks/post-receive # auto-deploy hook | 88 | /var/lib/<name>/ # work directory |
| 145 | /var/lib/<name>/src/ # checked-out source (for docker build) | 89 | /var/lib/<name>/data/ # persistent data (SQLite lives here) |
| 146 | /var/lib/<name>/data/ # persistent data volume | 90 | /var/lib/<name>/backups/ # SQLite backups (made before each binary swap) |
| 147 | /var/www/<name>/ # static site files | 91 | /var/www/<name>/ # static site files |
| 148 | /etc/systemd/system/<name>.service # systemd unit | 92 | /etc/systemd/system/<name>.service # systemd unit |
| 149 | /etc/caddy/sites-enabled/<name>.caddy # per-app Caddy config | 93 | /etc/caddy/sites-enabled/<name>.caddy # per-app Caddy config |
| 150 | /etc/caddy/sites-enabled/ship-code.caddy # base domain Caddy config | ||
| 151 | /etc/cgitrc # cgit configuration | ||
| 152 | /etc/ship/env/<name>.env # environment variables | 94 | /etc/ship/env/<name>.env # environment variables |
| 153 | /etc/sudoers.d/ship-git # sudo rules for git user | 95 | /etc/ship/ports/<name> # allocated port number |
| 154 | /opt/ship/vanity/index.html # vanity import template | ||
| 155 | /home/git/.ssh/authorized_keys # SSH keys for git push | ||
| 156 | ``` | 96 | ``` |
| 157 | 97 | ||
| 158 | ## Supported platforms | 98 | ## Requirements |
| 159 | |||
| 160 | VPS: Ubuntu 20.04+ or Debian 11+ | ||
| 161 | |||
| 162 | ## Security | ||
| 163 | 99 | ||
| 164 | See [SECURITY.md](SECURITY.md) for the threat model, mitigations, and known gaps. | 100 | - VPS running Ubuntu 20.04+ or Debian 11+ |
| 101 | - SSH access | ||
| 102 | - Claude Code with skills support | ||
| 165 | 103 | ||
| 166 | ## License | 104 | ## License |
| 167 | 105 | ||
