docs: plan for deploying main over the live release #15

Merged
heimdall merged 10 commits from heimdall/deploy-main-plan into main 2026-11-09 15:51:44 +00:00
Member

Plan only; nothing deployed. Preconditions (root window, verified and restored DB backup, rollback rehearsal), ordered steps, live check (login, vote read, Persona picture), rollback. Notes Bifrost #11 rides the deploy and that Heimdall__AppSecret is new (absent: Discord/Patreon sign-in off).

Unverified in the plan, flagged inside it: the ConceptVotes__* required keys, and the live env contents (read from source, not the host).

🤖 Generated with Claude Code

Plan only; nothing deployed. Preconditions (root window, verified and restored DB backup, rollback rehearsal), ordered steps, live check (login, vote read, Persona picture), rollback. Notes Bifrost #11 rides the deploy and that Heimdall__AppSecret is new (absent: Discord/Patreon sign-in off). Unverified in the plan, flagged inside it: the ConceptVotes__* required keys, and the live env contents (read from source, not the host). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Preconditions, ordered steps, live check and rollback for moving live
67072b2 to main (141 commits, two additive EF migrations, new
Heimdall:AppSecret). Plan only; nothing deployed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Owner

Read-only check of this plan against the live Yggdrasil host, 2026-11-09 ~10:00Z, main at 244811cb. Nothing was changed. Env: key names only, no values read into this comment.

Matches the host

  • /srv/bifrost/app/current -> /srv/bifrost/app/releases/20261010043032-67072b2; REVISION is 67072b28...; bifrost.service active, listening on 127.0.0.1:5080. 67072b2 is an ancestor of main (141 commits).
  • Unit is bifrost.service (User=bifrost, /usr/bin/dotnet /srv/bifrost/app/current/Bifrost.Web.dll); runtime is .NET 8.0.30, main targets net8.0. Same bifrost-watchdog-notice.timer exists; the plan does not mention it.
  • __EFMigrationsHistory ends at 20260705190000_AddPatronSupportProjectAttribution, so exactly the two migrations the plan lists are pending. Both Up bodies are CreateTable/CreateIndex only (the Drop* calls are in Down), so the rollback-by-symlink claim holds for the schema.
  • Rollback target exists and is readable (755; owned by a Windows uid from the tar extract, harmless). bifrost-publish.tar is the Oct 10 tarball, which step 4 overwrites; the old release directory is what rollback uses, as planned.
  • Postgres 16.15, bifrost DB 9.4 MB, 918 GB free. pg_dump/pg_restore are installed. A dump from the Oct 10 deploy exists (bifrost-20261010-043022.dump, root-owned).
  • Live data to compare before/after: UserAccounts=3, Memberships=3, MemberProfiles=3, Motions=0, Votes=0.

Where the host differs from the plan (blockers first)

  1. ConceptVotes__NetworkTagSecret is absent from the live env, and main refuses to start without it. StartupConfigurationValidator throws outside Development/Testing unless it is set and at least 32 characters. The plan marks it "check"; it is a hard requirement. No other ConceptVotes__* key is required: the rest default (IPv4PrefixLength, IPv6PrefixLength, CastPermits, CastWindow, MintPermits, MintWindow, MaxTrackedKeys), and AllowedOrigins is validated only if present. Without the secret, step 5's restart crash-loops (Restart=always, 5 s) and the plan sends you to rollback. Add it to step 3 beside Heimdall__AppSecret, and to Precondition 6.
  2. Heimdall__AppSecret is absent too (as the plan says). Heimdall__AppSlug, BaseUrl, DiscordGuildId, DiscordAllowedRoleIds__0, PatreonTierTitle and PatronSupportIntakeSecret are present. Missing AppSecret is not a startup failure, so Discord/Patreon sign-in silently turns off; the live check catches it.
  3. journalctl -u bifrost.service shows nothing. The unit sends stdout/stderr to /srv/bifrost/log/bifrost.stdout.log and .stderr.log (root-owned, appended by systemd), and the journal has no bifrost entries. Steps 6, the live check's error-rate line and Rollback should tail those files, not journald. The stderr log was last written Oct 18 (an old Npgsql "Failed to connect" crash loop while Postgres was down), so take a line count before the window to have a baseline.
  4. Env values the plan lists as present are mostly fine, Bifrost__Oidc__* is unset (as planned), and DataProtection__KeysPath is set; the key ring /srv/bifrost/data-protection-keys holds one key (Jul 15). Back that directory up too: it is outside /srv/bifrost/env, which Precondition 3 already hints at, but the runbook's env tar does not cover it.
  5. Scratch DB/rollback rehearsal (Precondition 4): feasible on disk, but the bifrost role has no CREATEDB/superuser, so createdb/pg_restore must run as postgres, as runbook section 8 does (sudo -u postgres ...). The plan's createdb bifrost_rehearsal as written needs that prefix. The host has the .NET 10 SDK under /opt/commons/opt/dotnet but /usr/bin/dotnet is runtime 8 only, so dotnet ef database update is not available on the host: use the "one start of the target with ApplyMigrationsOnStartup" route, with a separate env file (scratch connection string, a throwaway DataProtection__KeysPath, port other than 5080), or the rehearsal will touch live keys and bind the live port.
  6. Deploy script: /home/gamecultadmin/deploy-bifrost-alpha.sh is not readable from my account, so I could not compare it to gamecult-ops/scripts/deploy-bifrost-alpha.sh. The repo copy does what the plan says (unpack, REVISION, symlink, daemon-reload, restart). It takes the tarball from /srv/bifrost/bifrost-publish.tar, so the upload in step 4 must land there.
  7. Nginx: bifrost.gamecult.org proxies to 5080 and sets X-Forwarded-Proto; main's forwarded-headers trust is loopback only, which matches. This is what makes Secure/__Host- cookies work behind TLS termination.

Bifrost #11 ordering (__Host- cookies, head c757acf5, mergeable, open)

  • Merge it before the build, as the plan says; a second deploy later costs a second sign-out.
  • Cost of landing on this deploy: every member is signed out once (session cookie renamed .AspNetCore.Cookies -> __Host-bifrost_session); in-flight GitHub/Heimdall attempts fail once; anonymous voters get a new device id. With 3 members this is cheap, now more than later.
  • It combines with the new Heimdall__AppSecret: the live check's "an existing session survives the restart" will fail by design once #11 is in. Change that line to "everyone is signed out once; sign-in then sticks after a second restart".
  • __Host- needs Secure + Path=/ + no Domain; fine through nginx TLS as above. Browser-test over https only; a plain-http check will never keep the cookie.
  • The Persona-picture and vote-read checks are unaffected.

Summary

The plan's shape is right and the migrations and rollback claim hold. Three fixes before a root window: add ConceptVotes__NetworkTagSecret (hard start failure), read /srv/bifrost/log/*.log instead of journald, and use sudo -u postgres plus a separate env for the rehearsal. The live check's session-survival line is wrong once #11 is in.

Read-only check of this plan against the live Yggdrasil host, 2026-11-09 ~10:00Z, main at `244811cb`. Nothing was changed. Env: key names only, no values read into this comment. ## Matches the host - `/srv/bifrost/app/current` -> `/srv/bifrost/app/releases/20261010043032-67072b2`; `REVISION` is `67072b28...`; `bifrost.service` active, listening on `127.0.0.1:5080`. `67072b2` is an ancestor of main (141 commits). - Unit is `bifrost.service` (`User=bifrost`, `/usr/bin/dotnet /srv/bifrost/app/current/Bifrost.Web.dll`); runtime is .NET 8.0.30, main targets `net8.0`. Same `bifrost-watchdog-notice.timer` exists; the plan does not mention it. - `__EFMigrationsHistory` ends at `20260705190000_AddPatronSupportProjectAttribution`, so exactly the two migrations the plan lists are pending. Both `Up` bodies are `CreateTable`/`CreateIndex` only (the `Drop*` calls are in `Down`), so the rollback-by-symlink claim holds for the schema. - Rollback target exists and is readable (755; owned by a Windows uid from the tar extract, harmless). `bifrost-publish.tar` is the Oct 10 tarball, which step 4 overwrites; the old release directory is what rollback uses, as planned. - Postgres 16.15, `bifrost` DB 9.4 MB, 918 GB free. `pg_dump`/`pg_restore` are installed. A dump from the Oct 10 deploy exists (`bifrost-20261010-043022.dump`, root-owned). - Live data to compare before/after: `UserAccounts`=3, `Memberships`=3, `MemberProfiles`=3, `Motions`=0, `Votes`=0. ## Where the host differs from the plan (blockers first) 1. **`ConceptVotes__NetworkTagSecret` is absent from the live env, and main refuses to start without it.** `StartupConfigurationValidator` throws outside Development/Testing unless it is set and at least 32 characters. The plan marks it "check"; it is a hard requirement. No other `ConceptVotes__*` key is required: the rest default (`IPv4PrefixLength`, `IPv6PrefixLength`, `CastPermits`, `CastWindow`, `MintPermits`, `MintWindow`, `MaxTrackedKeys`), and `AllowedOrigins` is validated only if present. Without the secret, step 5's restart crash-loops (`Restart=always`, 5 s) and the plan sends you to rollback. Add it to step 3 beside `Heimdall__AppSecret`, and to Precondition 6. 2. **`Heimdall__AppSecret` is absent too** (as the plan says). `Heimdall__AppSlug`, `BaseUrl`, `DiscordGuildId`, `DiscordAllowedRoleIds__0`, `PatreonTierTitle` and `PatronSupportIntakeSecret` are present. Missing `AppSecret` is not a startup failure, so Discord/Patreon sign-in silently turns off; the live check catches it. 3. **`journalctl -u bifrost.service` shows nothing.** The unit sends stdout/stderr to `/srv/bifrost/log/bifrost.stdout.log` and `.stderr.log` (root-owned, appended by systemd), and the journal has no bifrost entries. Steps 6, the live check's error-rate line and Rollback should tail those files, not journald. The stderr log was last written Oct 18 (an old Npgsql "Failed to connect" crash loop while Postgres was down), so take a line count before the window to have a baseline. 4. **Env values the plan lists as present are mostly fine, `Bifrost__Oidc__*` is unset (as planned)**, and `DataProtection__KeysPath` is set; the key ring `/srv/bifrost/data-protection-keys` holds one key (Jul 15). Back that directory up too: it is outside `/srv/bifrost/env`, which Precondition 3 already hints at, but the runbook's env tar does not cover it. 5. **Scratch DB/rollback rehearsal (Precondition 4):** feasible on disk, but the `bifrost` role has no `CREATEDB`/superuser, so `createdb`/`pg_restore` must run as `postgres`, as runbook section 8 does (`sudo -u postgres ...`). The plan's `createdb bifrost_rehearsal` as written needs that prefix. The host has the .NET 10 SDK under `/opt/commons/opt/dotnet` but `/usr/bin/dotnet` is runtime 8 only, so `dotnet ef database update` is not available on the host: use the "one start of the target with `ApplyMigrationsOnStartup`" route, with a separate env file (scratch connection string, a throwaway `DataProtection__KeysPath`, port other than 5080), or the rehearsal will touch live keys and bind the live port. 6. **Deploy script:** `/home/gamecultadmin/deploy-bifrost-alpha.sh` is not readable from my account, so I could not compare it to `gamecult-ops/scripts/deploy-bifrost-alpha.sh`. The repo copy does what the plan says (unpack, `REVISION`, symlink, `daemon-reload`, restart). It takes the tarball from `/srv/bifrost/bifrost-publish.tar`, so the upload in step 4 must land there. 7. Nginx: `bifrost.gamecult.org` proxies to 5080 and sets `X-Forwarded-Proto`; main's forwarded-headers trust is loopback only, which matches. This is what makes `Secure`/`__Host-` cookies work behind TLS termination. ## Bifrost #11 ordering (`__Host-` cookies, head `c757acf5`, mergeable, open) - Merge it **before** the build, as the plan says; a second deploy later costs a second sign-out. - Cost of landing on this deploy: every member is signed out once (session cookie renamed `.AspNetCore.Cookies` -> `__Host-bifrost_session`); in-flight GitHub/Heimdall attempts fail once; anonymous voters get a new device id. With 3 members this is cheap, now more than later. - It combines with the new `Heimdall__AppSecret`: the live check's "an existing session survives the restart" will **fail by design** once #11 is in. Change that line to "everyone is signed out once; sign-in then sticks after a second restart". - `__Host-` needs `Secure` + `Path=/` + no `Domain`; fine through nginx TLS as above. Browser-test over https only; a plain-http check will never keep the cookie. - The Persona-picture and vote-read checks are unaffected. ## Summary The plan's shape is right and the migrations and rollback claim hold. Three fixes before a root window: add `ConceptVotes__NetworkTagSecret` (hard start failure), read `/srv/bifrost/log/*.log` instead of journald, and use `sudo -u postgres` plus a separate env for the rehearsal. The live check's session-survival line is wrong once #11 is in.
Targets 55b74275 (#11 and #17 in main; #15 docs only). #11 signs everyone out
once. Lists the validator's required keys by name, NetworkTagSecret (32+
chars) as a hard startup failure, put into step 3. Rehearsal through
sudo -u postgres and one start on a separate env file, scratch connection
string, throwaway KeysPath and a port other than 5080; no dotnet ef. Backs up
the data-protection key ring. Uses the real log files with a baseline count.
Notes bifrost-watchdog-notice.timer.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/Bifrost!15
No description provided.