Document the credentials clobber trap and the smb CLI deploy path

The v1.1.0 deploy wiped the GitHub App credentials because they had been added with credentials:edit on the server: credentials.yml.enc is git-tracked and the hook's checkout -f replaces it. Both the release skill and the App runbook now say to edit locally and commit the .enc. Also replace the env presence-check advice with plain printenv, since the compound check produced a false positive, and document deploying via the smb CLI (key id_11).

Seto Elkahfi committed Jul 6, 2026 at 07:27 UTC 6b1b166ffd3c2497eae8abc56f81676b639b8297
2 files changed +44 -21
.agents/skills/release/SKILL.md
+29 -13
index 354dbd5..d6ed9db 100644 --- a/.agents/skills/release/SKILL.md +++ b/.agents/skills/release/SKILL.md @@ -111,16 +111,23 @@ git diff origin/main --stat -- config/initializers/ # new boot-time requirement ssh smb1-deploy 'sudo -u git bash -lc "printenv | grep -oE \"^SOME_NEW_VAR[A-Z_]*\""' ``` -Notes that save time: +Notes that save time (each learned from a real incident): - The `git` user's `~/.profile` is the env source; the post-receive hook starts with `. ~/.profile`, so vars set there do reach `db:prepare`, Puma, - and `bin/jobs`. Rails credentials are the alternative for multi-line - secrets (private keys) — see the runbooks under `docs/`. -- Beware shell false positives when checking remotely: `printenv | grep -c X` - can match `SUDO_COMMAND` (your own command line), and `pgrep -f X` over ssh - matches its own invocation. Anchor patterns (`^X=`) and exclude the - grep/ssh process before trusting a count. + and `bin/jobs`. +- **Rails credentials must be edited locally and committed, never on the + server.** `config/credentials.yml.enc` is git-tracked and the deploy does + `git checkout -f main` — a `credentials:edit` in the server work tree is + silently wiped by the next deploy (this deleted the GitHub App credentials + once). Edit with the local `config/master.key`, commit the `.enc`, and let + the deploy distribute it. +- The only trustworthy remote presence check is `printenv VAR` in the login + shell: `ssh ... 'sudo -u git bash -lc "printenv GITHUB_WEBHOOK_SECRET + >/dev/null && echo present || echo absent"'`. Compound `[ -n "$VAR" ]` + checks through the ssh/sudo/bash quoting layers have produced false + "set" readings, `printenv | grep -c X` matches `SUDO_COMMAND` (your own + command line), and `pgrep -f X` over ssh matches its own invocation. ### Production database state @@ -146,15 +153,24 @@ git push origin main ## 4. Deploy +The operator deploys with the interactive `smb` CLI from the repo root on +`main` — it logs in, detects the Rails app, and pushes with the right key +(`~/.ssh/id_11@smbcloud`), ending in "Deployment complete": + ```bash -git push smbcloud main # the bare-repo post-receive hook does the rest +smb ``` -Remember the hook's sharp edges (deployment skill has the full list): no -`set -e`, migration output goes to the pushing terminal — **read the push -output**, it is the only record of `db:prepare`'s success — and `bin/jobs -start` stacks a new Solid Queue supervisor on every deploy unless the hook -has been amended with a `pkill -f solid_queue || true` line first. +A plain `git push smbcloud main` does the same thing but only if that key is +what ssh offers; the `Host api-1.smbcloud.xyz` entry in `~/.ssh/config` is +not configured, so without the smb CLI expect `Permission denied (publickey)`. + +Either way it lands on the bare-repo post-receive hook, which has sharp +edges (deployment skill has the full list): no `set -e`, migration output +goes to the pushing terminal — **read the push output**, it is the only +record of `db:prepare`'s success — and `bin/jobs start` stacks a new Solid +Queue supervisor on every deploy unless the hook has been amended with a +`pkill -f solid_queue || true` line first. ## 5. Post-deploy verification
docs/github-app-reviews.md
+15 -8
index b1f5a93..56dcee5 100644 --- a/docs/github-app-reviews.md +++ b/docs/github-app-reviews.md @@ -44,9 +44,9 @@ secret (`bin/rails secret | head -c 48`), and **Generate a private key** ## Configuration -Preferred in production: Rails credentials (`bin/rails credentials:edit`), -because the private key is multi-line and prod env vars live in the `git` -user's `~/.profile`, which is single-line only: +Preferred in production: Rails credentials, because the private key is +multi-line and prod env vars live in the `git` user's `~/.profile`, which is +single-line only: ```yaml github_app: @@ -58,11 +58,18 @@ github_app: -----END RSA PRIVATE KEY----- ``` -This block is already set in production credentials (verify without booting -the app: `bin/rails runner 'c = Rails.application.credentials.github_app; puts -c.app_id, c.webhook_secret.length'`). When pasting the key, indent every PEM -line to match `private_key: |`; on the server `bin/rails credentials:edit` -needs an editor that waits, e.g. `EDITOR=nano bin/rails credentials:edit`. +**Edit credentials locally and commit — never on the server.** +`config/credentials.yml.enc` is git-tracked, and the deploy hook's +`git checkout -f main` overwrites the server copy, silently deleting any +server-side edit on the next deploy (this happened: the App credentials were +added on the server on 2026-07-05 and wiped by the v1.1.0 deploy the next +day). Run `EDITOR=nano bin/rails credentials:edit` in a local checkout that +has `config/master.key`, paste the block, commit the `.enc`, and deploy. + +When pasting the key, indent every PEM line to match `private_key: |`. +Verify after deploy without booting the app: +`bin/rails runner 'puts GithubAppService.configured?'` on the server — and +expect `POST /github/webhooks` to return 401 (not 503) to an unsigned probe. Note the `webhook_secret` here is separate from `stripe.webhook_secret`; they share a key name but live under different top-level parents.