--- 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