CD deploys only to the dev host; the live instance is 6 days behind and manually managed #154
Labels
No labels
area:ai
area:ci-cd
area:notifications
area:observability
area:public-pages
backlog
bug
duplicate
enhancement
help wanted
invalid
question
type:decision
type:feature
type:infra
type:maintenance
type:security
v1.0.1
v1.1.0
v1.2.0
v1.3.0
v2.0.0
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/WeatherBot#154
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
CD works correctly. It just does not deploy the instance that is actually serving alerts. There are two separate WeatherBot deployments on two different Docker hosts, and only one of them receives merges.
Evidence
CD target (receives every merge). From the run 126 deploy job log:
The
previous refbeingb6b22a2confirms the earlier merge landed there too. This side is healthy and current.The live instance (never receives merges). Inspecting the running stack:
/home/ryan/WeatherBot/docker-compose.ymlcreatedStartedAtrestart_countweatherbot-app:latestbuilt0.0.0.0:8054 -> 8000Untouched for six days and never restarted, across at least four successful CD runs on 2026-07-28 (00:58, 01:47, 02:13, 03:14).
They cannot be the same daemon. Both directories yield compose project name
weatherbot, so adocker compose upfrom~/projects/WeatherBotwould have recreated these exact containers. It did not — they still showrestart_count: 0and a 07-22 creation time. Different hosts. The ports differ too: CD health-checks:18082, the live stack publishes:8054.Behavioural confirmation. As of 03:36–03:39, after CD's 03:14 deploy, the live instance still logs the pre-#151 format (
Webex all-clear failed ... Client error '400 Bad Request') with noWebex API rejected the messageline and noGiving up on all-clear. It is running roughly v1.7.0.Impact
The live instance is missing everything merged since 2026-07-22, including all five alert-correctness fixes:
Live symptoms right now:
Processing 2 pending lifted alert recordsevery cycle, andnotifier:webex failure_count=1238climbing by roughly 2 per minute.The real hazard
A green CD run currently implies a production deploy that is not happening. That assumption is easy to make — I made it earlier in this session — and it is most dangerous for exactly the class of fix involved here: an alert-correctness change can look shipped while the instance sending real notifications is unchanged.
Options
cd.yml, gated on aPROD_SSH_KEY/PROD_HOSTpair, mirroring the dev job (including its rollback-on-unhealthy behaviour).cd.ymltargets dev only, and rename the job/workflow so "CD succeeded" cannot be read as "production is live".Option 1 is the obvious one if production should track
main; option 2 is right if production is deliberately pinned to releases. Either way the current state — where the two are silently different and nothing says so — is the thing to fix.Immediate
Production needs a manual deploy to pick up the alert-correctness fixes:
Decision
Production updates only on stable release tags — not on every merge to
main. Option 1 (auto-deploy on merge) is rejected; option 3 (consolidate) is rejected.maincontinues to deploy to dev, unchanged.Mechanism: a Forgejo Actions job on
v*tag push that invokes an Ansible playbook iniac-repo. Watchtower was considered and rejected for this app — see below.Why not Watchtower
It was a reasonable thing to ask, since it is in use on other projects here. Two blockers, one structural and one safety:
docker-compose.ymlusesbuild:with anAPP_VERSIONbuild arg, and the prod deploy isgit pull+docker compose up -d --build. Watchtower updates containers by pulling a newer image for a tag from a registry, so it has nothing to act on until WeatherBot publishes images.What following the
iac-repoconvention actually impliesWorth stating plainly, because it is more than "add a playbook."
The established pattern for an app in
iac-repo—carriage-ansibleis the closest analogue — is registry-image based, not git-checkout based:Its role renders
.envanddocker-compose.ymlfrom templates (secrets from Ansible Vault), does adocker_loginagainst the Forgejo registry, pulls the pinned image, and brings the stack up withdocker_compose_v2under a service user in/opt/.WeatherBot production today is none of those things: a hand-managed git checkout at
/home/ryan/WeatherBot, running asryan(uid 1000), with a hand-written.env, building images locally. Both live on the samedocker-hostthat Ansible already manages for carriage, authentik, forgejo and shelf-life.So converging on the convention means three pieces of work, not one:
v*tags that builds and pushesgit.rhoving.com/rbrooks/weatherbot:<tag>. BakesAPP_VERSIONat build time, which also removes the git checkout from prod entirely — and with it the root-owned-git-objects hazard that the current manual recipe has to work around.weatherbot-ansible/following the carriage layout: templated.env+docker-compose.yml, pinned image, health gate, rollback.⚠️ The migration hazard, stated before anyone starts
Compose derives its project name from the directory. Production currently runs as project
weatherbotout of/home/ryan/WeatherBot, with its data in named volumesweatherbot_postgres_data,weatherbot_radar_cache,weatherbot_matrix_store,weatherbot_signal_cli_data.Deploying the same stack from a new Ansible-managed path (say
/opt/weatherbot-svc) without pinning the project name would create a brand-new parallel stack and orphan every one of those volumes — losing the alert history, the 155k freshness samples, and the history capture that only started on 2026-07-28. It would look like a successful deploy.Mitigation is straightforward but must be explicit: pin
COMPOSE_PROJECT_NAME=weatherbotin the rendered environment and keep the existing named volumes, or perform a deliberate, backed-up volume migration. Either way it is a decision to make up front, not something to discover afterwards.Secrets also have to move. The prod
.envholds the Postgres password,SECRET_KEY, the Authentik client secret, the Discord bot token, and theLIGHTNING_PLACEFILE_URLsubscriber token. These would move intoinventory/group_vars/weatherbot/vault.ymlunderansible-vault, matching how carriage sourcesvault_carriage_secret_key.Sequencing
Because of the above, the safe order is: (1) publish images on tag → (2) write and dry-run the playbook against the dev host → (3) cut production over, with a database backup taken first. Dev is the natural rehearsal for the cutover, and it already receives every merge.
Interim state
Until this lands, production remains manual. Today it was deployed by hand to
12d47ddto pick up #131 and #132. Anyone reading a green CD run should still not conclude production is current — that is precisely the hazard this issue exists to remove, and it is not removed yet.Both PRs are up
Rhoving/iac-repoweatherbot-ansible/role, inventory group, vault stubrbrooks/WeatherBotrelease.yml(build+push+deploy onv*),cd.ymlrenamedTracked on the iac side as Rhoving/iac-repo#219. Merge order matters: iac-repo#220 first — the workflow invokes a play that does not exist until it lands.
What landed against each option on this issue
Option 1 (deploy on merge) and option 3 (consolidate) are rejected. What is implemented is closest to option 2 — production stays deliberate — but automated rather than manual: a
v*tag is the trigger, so production can never silently lag the way it did here, and nobody has to remember a procedure.cd.ymlis also renamed "CD (dev only)" with a header saying so, which was the explicit ask in option 2. The hazard was never that CD was broken, only that its name implied more than it did.Two things deliberately left for a human
The production cutover. The play is written but not run. Production is a git checkout at
/home/ryan/WeatherBotrunning as uid 1000 with hand-written secrets; moving it to/opt/weatherbot-svcunder a service user touches the four named volumes holding all the alert history. The compose project name is pinned toweatherbotin the rendered file specifically so those volumes survive, but the cutover itself should be watched, after apg_dumpand a dev rehearsal. Sequence is on iac-repo#220.One open design question, on WeatherBot#159: the deploy passes
-e weatherbot_image=<tag>, so the committed pin in iac-repo shows the previous version until someone bumps it. That is convenient but leaves git and reality disagreeing, which cuts against how the rest of the fleet works. The alternative — fail the play when the pin and the tag disagree — keeps git authoritative at the cost of a two-repo ordering requirement. Worth a decision before this gets used in anger.Incidentally fixed
Production had no database backup of any kind. Upstream ships an opt-in
db-backupcompose profile, but it bind-mounts./scriptsfrom a git checkout — unusable from a registry image — and was never enabled. iac-repo#220 adds a nightlypg_dumpsystemd timer instead. That matters more than usual here: the play's rollback restores the previous image, not the previous schema, and migrations are forward-only, so a bad migration needs a restore to recover from. Until now there would have been nothing to restore from.Decision revised: CI publishes, iac-repo deploys
Ryan's call, 2026-07-31: the release workflow should only build and push the image. Deployment happens from iac-repo by an operator running the play. Rationale:
PROD_SSH_KEY+ANSIBLE_VAULT_PASSWORDin Forgejo Actions secrets — together root-equivalent ondocker-host, exposed to anything that can push av*tag or compromise a runner. Publish-only leaves CI with apackage:writetoken, whose worst case (a poisoned image) still has to get past the operator.-e weatherbot_image=<tag>pin drift): with deploys operator-run, the committedweatherbot_imagepin in iac-repo is the deployed state. Git stays authoritative, matching the fleet convention.PR #159 updated accordingly (commit
42ae241):deploy-prodjob removed, secrets requirement reduced toREGISTRY_TOKENonly, README documents the three-step release procedure (tag → bump pin → run play). iac-repo#220 is unchanged by this — the role was already invocation-agnostic — and the merge-order constraint between the two PRs is gone.Trade-off accepted knowingly: shipping regains one human step (bump pin + run play), which is the same class of step whose forgetability caused this issue. Mitigations: the tag itself is already a deliberate human act (so the deploy step is adjacent, not separate), the release run emits a
::notice::with the exact commands, and the committed pin makes any lag visible —git logon iac-repo answers "what is production running", which was the unanswerable question that started this.