From d0ae31c24c3c98ae89eebd67227c0c0d01606ed5 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 11 Apr 2026 20:43:41 -0700 Subject: Add ship-* Claude skills and plan Introduces a skills/ directory with 8 Claude skills that reimagine ship as a set of composable, human-driven deployment tools backed by Claude's reasoning rather than a rigid CLI. Skills: - ship-setup: one-time VPS config, saves host to ~/.config/ship/config.json - ship-status: derives live state from server, no local state file - ship-env: read/write env vars with merge semantics, never overwrites - ship-binary: deploy Go binaries with SQLite backup, correct restart behavior - ship-caddy: manage per-app Caddyfile with validate-before-reload - ship-service: systemd management and log inspection - ship-static: rsync static sites with SPA routing support - ship-deploy: orchestration runbook tying the others together Also adds SKILLS_PLAN.md documenting the architecture and rationale. Co-Authored-By: Claude Sonnet 4.6 --- skills/ship-binary/SKILL.md | 168 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 skills/ship-binary/SKILL.md (limited to 'skills/ship-binary') diff --git a/skills/ship-binary/SKILL.md b/skills/ship-binary/SKILL.md new file mode 100644 index 0000000..16056ff --- /dev/null +++ b/skills/ship-binary/SKILL.md @@ -0,0 +1,168 @@ +--- +name: ship-binary +description: Upload and deploy a pre-built binary to a ship VPS. Handles port allocation, systemd service, env vars, Caddy config, and SQLite backup. Use when deploying a Go binary or other compiled executable. +argument-hint: " [host-nickname]" +--- + +# ship-binary + +Upload a pre-built binary and deploy it as a systemd service with Caddy reverse proxy. + +## Read Config + +```bash +python3 -c " +import json, os +cfg = json.load(open(os.path.expanduser('~/.config/ship/config.json'))) +nick = '' +h = cfg['hosts'].get(nick, cfg['hosts'][cfg['default']]) +print(h['host']) +print(h['domain']) +" +``` + +## Inputs + +- **Binary path** — local path to the compiled binary +- **App name** — short lowercase name, becomes the service name and subdomain (e.g. `foodtracker`) +- **Domain** — defaults to `.` from config, ask if different +- **Env vars** — ask if there are any env vars to set (beyond PORT/SHIP_NAME/SHIP_URL) +- **Host** — use default unless specified + +## Steps + +### 1. Check if app already exists + +```bash +ssh "test -f /etc/ship/ports/ && echo exists || echo new" +``` + +This determines whether to allocate a new port or reuse the existing one. + +### 2. Backup SQLite databases (if app exists) + +Before touching anything, check for SQLite files in the app's data directory: + +```bash +ssh "find /var/lib// -name '*.db' 2>/dev/null" +``` + +If any `.db` files are found, back them up: + +```bash +ssh "sudo mkdir -p /var/lib//backups && sudo cp /var/lib//data/.db /var/lib//backups/-\$(date +%Y%m%d-%H%M%S).db" +``` + +Tell the user what was backed up before proceeding. + +### 3. Allocate or retrieve port + +**New app** — find the highest port in use and add 1: +```bash +ssh "sudo bash -c 'max=9000; for f in /etc/ship/ports/*; do p=\$(cat \$f 2>/dev/null); [ \"\$p\" -gt \"\$max\" ] && max=\$p; done; port=\$((max+1)); echo \$port | tee /etc/ship/ports/; echo \$port'" +``` + +**Existing app** — reuse the existing port: +```bash +ssh "cat /etc/ship/ports/" +``` + +### 4. Upload binary + +```bash +scp :/tmp/ +ssh "sudo mv /tmp/ /usr/local/bin/ && sudo chmod +x /usr/local/bin/" +``` + +### 5. Create work directory and service user + +```bash +ssh "sudo mkdir -p /var/lib//data && sudo useradd -r -s /bin/false 2>/dev/null || true && sudo chown -R : /var/lib/" +``` + +### 6. Write env file + +Build the env file content merging ship-managed vars with any user-provided vars. +If the file already exists, read it first and merge (new values win, old values survive): + +```bash +ssh "sudo cat /etc/ship/env/.env 2>/dev/null" +``` + +Then write merged result: +```bash +ssh "sudo tee /etc/ship/env/.env > /dev/null << 'EOF' +PORT= +SHIP_NAME= +SHIP_URL=https:// + +EOF +sudo chmod 600 /etc/ship/env/.env" +``` + +### 7. Write systemd unit + +```bash +ssh "sudo tee /etc/systemd/system/.service > /dev/null << 'EOF' +[Unit] +Description= +After=network.target + +[Service] +Type=simple +User= +WorkingDirectory=/var/lib/ +EnvironmentFile=/etc/ship/env/.env +ExecStart=/usr/local/bin/ +Restart=always +RestartSec=5s +NoNewPrivileges=true +PrivateTmp=true + +[Install] +WantedBy=multi-user.target +EOF" +``` + +### 8. Start or restart service + +```bash +ssh "sudo systemctl daemon-reload" +``` + +**New app:** +```bash +ssh "sudo systemctl enable --now " +``` + +**Existing app:** +```bash +ssh "sudo systemctl restart " +``` + +### 9. Write Caddy config + +```bash +ssh "sudo tee /etc/caddy/sites-enabled/.caddy > /dev/null << 'EOF' + { + reverse_proxy 127.0.0.1: +} +EOF +sudo systemctl reload caddy" +``` + +### 10. Confirm + +Tell the user: +- App name, URL, and port +- Whether it was a new deploy or update +- Any SQLite backups made +- Any env vars set + +## Notes + +- Always back up SQLite before swapping the binary +- Always merge env vars — never replace the whole file +- Use `systemctl restart` for existing apps, `enable --now` for new ones +- The data directory `/var/lib//data/` persists across deploys +- If the user doesn't specify env vars, ask if they need any before deploying -- cgit v1.2.3