Skip to content

Remove the GitHub Actions auto-deploy workflow (billing deploys via CIRISBridge Ansible) - #9

Merged
emooreatx merged 1 commit into
mainfrom
fix/deploy-config-and-host-pinning
Sep 6, 2026
Merged

Remove the GitHub Actions auto-deploy workflow (billing deploys via CIRISBridge Ansible)#9
emooreatx merged 1 commit into
mainfrom
fix/deploy-config-and-host-pinning

Conversation

@emooreatx

@emooreatx emooreatx commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

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

docker-publish.yml -> ghcr.io/cirisai/cirisbilling -> CIRISBridge Ansible
  (roles/billing, pull: always) -> 108.61.242.236 (US) + 46.224.81.217 (EU)

Verified: both production hosts are running ghcr.io/cirisai/cirisbilling:latest right now. deploy.yml was a second, divergent deploy path pointing at a third machine.

It was broken, and had been for six months

Last successful run none in the retained history
Every run since 2026-02-02 failed
Last triggered 2026-02-18

Nothing depended on it working. That's the clearest evidence it isn't the real deploy path.

It was also a live security exposure

ssh-keyscan 149.28.120.73 >> ~/.ssh/known_hosts   # accepts ANY key
-o StrictHostKeyChecking=no

It accepted whatever machine held that IP. As of 2026-08-28, from an operator workstation:

  • SSH to 149.28.120.73 -> REMOTE HOST IDENTIFICATION HAS CHANGED!
  • http://149.28.120.73:8000/health -> no response

So 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_KEY secret. 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_KEY in this repo. It was configured to authenticate to an address that has since changed identity.

And it hardcoded drifting config

GOOGLE_CLIENT_IDS was frozen at 2 ids; production accepts 4 (source of truth: CIRISBridge Ansible vault google_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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread .github/workflows/deploy.yml Outdated
with:
host: 149.28.120.73
envs: GOOGLE_CLIENT_IDS
host: ${{ vars.DEPLOY_HOST }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread .github/workflows/deploy.yml Outdated
Comment on lines +126 to +129
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread .github/workflows/deploy.yml Outdated
-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" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@emooreatx
emooreatx force-pushed the fix/deploy-config-and-host-pinning branch from f658072 to 5299b35 Compare August 28, 2026 13:34
@emooreatx emooreatx changed the title Stop hardcoding OAuth client ids in deploy.yml; pin the deploy host key Remove the GitHub Actions auto-deploy workflow (billing deploys via CIRISBridge Ansible) Aug 28, 2026
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.
@emooreatx
emooreatx force-pushed the fix/deploy-config-and-host-pinning branch from 5299b35 to ce29247 Compare September 6, 2026 22:11
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@emooreatx
emooreatx merged commit 0714231 into main Sep 6, 2026
3 checks passed
@sonarqubecloud

sonarqubecloud Bot commented Sep 6, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant