Remove the GitHub Actions auto-deploy workflow (billing deploys via CIRISBridge Ansible) - #9
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f65807275f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| with: | ||
| host: 149.28.120.73 | ||
| envs: GOOGLE_CLIENT_IDS | ||
| host: ${{ vars.DEPLOY_HOST }} |
There was a problem hiding this comment.
Pin the host key for both SSH action connections
The pinned known_hosts file only governs the runner's preceding OpenSSH/rsync command; appleboy/ssh-action establishes its own connection and does not consume that file. The action's v1.0.0 input documentation provides the fingerprint input for the “SHA256 fingerprint of the host public key,” but neither this build step nor the verification step supplies it. Consequently, if a hostname resolves to a different machine between rsync and either action, those connections still accept the replacement host despite this change's host-pinning guarantee; configure fingerprint on both action invocations or use the already-pinned OpenSSH client for them.
Useful? React with 👍 / 👎.
| GOOGLE_CLIENT_ID_PRIMARY=$(printf '%s' "$GOOGLE_CLIENT_IDS" | cut -d, -f1) | ||
| if [ -z "$GOOGLE_CLIENT_ID_PRIMARY" ]; then | ||
| echo "ERROR: GOOGLE_CLIENT_IDS did not reach the deploy host"; exit 1 | ||
| fi |
There was a problem hiding this comment.
Validate the primary client ID before removing production
When the new repository variable is nonempty but its first entry is empty—for example GOOGLE_CLIENT_IDS=,valid-id.apps.googleusercontent.com—the earlier validator accepts it, then this remote script stops and removes the live container before reaching this check. The check exits with no replacement container running, turning a malformed configuration into a production outage; derive and validate the primary ID before the stop/remove operations (and reject empty list elements in the initial validation).
Useful? React with 👍 / 👎.
| -e "DATABASE_URL=$DB_URL" \ | ||
| -e GOOGLE_CLIENT_ID=265882853697-vsrucm66hl39jei1f5f20kb49om8t9pb.apps.googleusercontent.com \ | ||
| -e GOOGLE_CLIENT_IDS=265882853697-vsrucm66hl39jei1f5f20kb49om8t9pb.apps.googleusercontent.com,265882853697-l421ndojcs5nm7lkln53jj29kf7kck91.apps.googleusercontent.com \ | ||
| -e "GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID_PRIMARY" \ |
There was a problem hiding this comment.
Keep the admin OAuth client ID explicit
GOOGLE_CLIENT_IDS is documented in .env.example as the list of all token-validation audiences, whereas GOOGLE_CLIENT_ID is specifically the web application ID used by GoogleOAuthProvider with the client secret and redirect URI. Deriving the latter from list order introduces an undocumented constraint: a syntactically valid list beginning with an Android or desktop client passes validation and health checks, but admin OAuth then sends that client ID with the web client secret and callback and fails. Preserve an explicit primary web-client setting or otherwise validate that the selected primary is the matching web client.
Useful? React with 👍 / 👎.
f658072 to
5299b35
Compare
Billing is deployed by CIRISBridge Ansible, not by this repo. The path that actually reaches production is: docker-publish.yml -> ghcr.io/cirisai/cirisbilling -> bridge Ansible (roles/billing, pull: always) -> 108.61.242.236 + 46.224.81.217 Both live hosts run ghcr.io/cirisai/cirisbilling:latest today. This workflow was a second, divergent deploy path to a different machine and should not exist alongside that one. It was also broken and dangerous: - Every run has FAILED since at least 2026-02-02, and it has not been triggered since 2026-02-18. Nothing depended on it working. - It targeted 149.28.120.73 (the legacy box, listed for decommission), which is neither of the two production billing hosts. - `ssh-keyscan <ip> >> known_hosts` with StrictHostKeyChecking=no meant it would accept whatever machine held that IP. As of 2026-08-28 SSH to that address reports REMOTE HOST IDENTIFICATION HAS CHANGED and its health endpoint no longer answers, so the workflow was one green run away from rsyncing the source tree to a host we may not control, using the DEPLOY_SSH_KEY secret. - It hardcoded a GOOGLE_CLIENT_IDS list that had already drifted from the four ids production actually accepts. Deleting it removes all four problems at once and leaves exactly one deploy path. Image publishing (docker-publish.yml) is untouched. Follow-up for whoever owns the legacy box: if 149.28.120.73 is retired, the DEPLOY_SSH_KEY secret in this repo should be rotated, since it was configured to authenticate to an address that has since changed identity.
5299b35 to
ce29247
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|



Supersedes my earlier attempt to fix this workflow. It shouldn't be fixed — it should not exist. Unblocks #8.
There is already exactly one correct deploy path
Verified: both production hosts are running
ghcr.io/cirisai/cirisbilling:latestright now.deploy.ymlwas a second, divergent deploy path pointing at a third machine.It was broken, and had been for six months
Nothing depended on it working. That's the clearest evidence it isn't the real deploy path.
It was also a live security exposure
It accepted whatever machine held that IP. As of 2026-08-28, from an operator workstation:
149.28.120.73->REMOTE HOST IDENTIFICATION HAS CHANGED!http://149.28.120.73:8000/health-> no responseSo it was one green run away from rsyncing the full source tree to a host we may no longer control, authenticating with the
DEPLOY_SSH_KEYsecret. It failed at some earlier step every time, which is the only reason this stayed theoretical — but that's luck, not design.Recommended follow-up: rotate
DEPLOY_SSH_KEYin this repo. It was configured to authenticate to an address that has since changed identity.And it hardcoded drifting config
GOOGLE_CLIENT_IDSwas frozen at 2 ids; production accepts 4 (source of truth: CIRISBridge Ansible vaultgoogle_client_ids). Deleting the workflow removes the drift rather than relocating it.Correction carried over from #8
I originally warned that merging #8 would drop client ids from production. That was wrong — the bridge nodes get theirs from Ansible + vault (
roles/billing/templates/env.j2:40), not from this workflow, and all four are present on both nodes. Corrected in a comment there.Image publishing is untouched. Nothing else in the repo references
deploy.yml.🤖 Generated with Claude Code