# SSH deploy to bun-source services (not Nix): gotchas This repo (`asepharyana/mcpedia`) deploys **bun source** via systemd — NOT via Nix like GMW. The pattern is: CI builds in GitHub Actions → upload artifact → deploy job downloads artifact → SCP tarball to VPS → SSH → git pull + unpack + index + `systemctl restart`. The VPS does NOT build. ## Gotchas (learned during mcpedia deploy setup, 2026-08-20/21) ### 1. SSH host = public IP, NOT Tailscale IP The VPS appears in `~/.ssh/config` as `Host orange → HostName 100.79.111.61`. That IP is a **Tailscale `tailscale0` interface address** in the CGNAT range (`100.64.0.0/10`). GitHub Actions runners are NOT on the Tailscale network, so they get `Connection timed out` (dropped at the network layer, not refused). **Fix:** Use the VPS's real public IP (`45.127.35.244`, discovered via `curl https://api.ipify.org` from the VPS). Port 22 is open in iptables (`ACCEPT tcp dpt:22` from `0.0.0.0/0`). ### 2. SSH key MUST be stored directly from file (not shell variable) Storing the deploy key via a shell variable corrupts it: ```bash # ❌ WRONG — newlines get mangled by the shell → "ssh: no key found" PRIV_KEY=$(cat keyfile) gh secret set SSH_DEPLOY_KEY --body "$PRIV_KEY" # ✅ CORRECT — pipe the file directly so GitHub preserves all bytes cat keyfile | gh secret set SSH_DEPLOY_KEY --repo asepharyana/mcpedia ``` Symptom: `appleboy/ssh-action` fails with `ssh.ParsePrivateKey: ssh: no key found`. ### 3. Use `appleboy/ssh-action@v1` (not `@v1.1.0`) - `@v1.1.0` (very old) has a key-parsing bug that rejects valid keys (`ssh.ParsePrivateKey: ssh: no key found`). - `@v1` (latest) handles OpenSSH ed25519 keys correctly. ### 4. `appleboy/ssh-action` needs explicit `envs` to pass through secret-derived vars The `envs` parameter passes environment variables to the remote script. Include any secret you reference in the script: ```yaml envs: SSH_DEPLOY_HOST # passes SSH_DEPLOY_HOST into the remote script ``` Without it, `echo $SSH_DEPLOY_HOST` on the VPS returns empty even though the action connected. ### 5. Always pass `-o IdentitiesOnly=yes` Without it, SSH offers ALL loaded identities (deploy key + default keys) and the server may reject after "Too many authentication failures". `appleboy/ssh-action` handles this internally via the `key` input, but if you use raw `ssh` add `-o IdentitiesOnly=yes`. ### 6. GitHub `workflow_run` does NOT carry the push commit SHA `workflow_run` events fire after CI completes, but `actions/checkout` checks out the **default branch tip**, not the specific commit. This is fine for deploy (the script does `git pull origin main` anyway), but verify the checkout ref matches what CI built if you rely on it. ### 7. DB migrations: `db:push` prompt is non-interactive-unfriendly in SSH deploy When the schema includes a new column (e.g. adding `extra_fields JSONB`), `drizzle-kit push` in the SSH deploy script needs to be run. Two issues: - **`--strict` mode (config default)**: `drizzle.config.ts` has `strict: true`, which makes `drizzle-kit push` prompt `No, abort / Yes, I want to execute all statements`. In a non-interactive SSH script (no TTY), the prompt never receives input and the command times out → SIGTERM → exit code 124 → deploy fails. `set -e` then kills the whole deploy. - **`bun run