feat(staging): staging environment for beta.sona.fast (#166) - #171
feat(staging): staging environment for beta.sona.fast (#166)#171sparkyfen wants to merge 2 commits into
Conversation
Deploy + cron workflows gated to the canonical repo (inverse of the fork gate), a synthetic seed with a placeholder admin hash + stdin hasher, and a one-time setup runbook. - staging-deploy.yml: push/dispatch, `if: github.repository == 'sona-fast/sona'`, mirrors deploy.yml's step sequence; name fallbacks default to the staging project/DB so a missing repo var can't target production. Forbids pull_request_target (secret exposure to untrusted PR code). - staging-cron.yml: sticker re-sync (daily), artist sync (daily), orphan cleanup (weekly) POSTing beta.sona.fast with Bearer CRON_SECRET. - staging-seed.sql: synthetic artist + placeholder images, setupComplete=true, and a placeholder adminPasswordHash (loading without substitution is a documented silent lockout). - hash-admin-password.ts: stdin -> pbkdf2 hash, reusing reset-password.ts's parity-tested hasher; staging-seed.test.ts proves seed+migrations apply and the hash round-trips through verifyPasswordHash. - docs/staging.md: manual vs scriptable setup, the "Sona beta testers" reusable Access policy created before the first deploy, Pages bindings (#115), and the wizard-reset procedure. Closes #166
| # otherwise) are put so the panel has them at runtime. Unset = logged skip. | ||
| - name: Sync Cloudflare analytics secrets to Pages project | ||
| env: | ||
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} |
There was a problem hiding this comment.
We already have these values set for production so how do we know that they're not gonna clash with staging?
There was a problem hiding this comment.
They can't clash, for two reasons that stack. First, GitHub Actions secrets are per repository: the production values you're thinking of live in each fork's own repo (sparkyfen/sparky.ink and friends), and this workflow only ever reads sona-fast/sona's secrets, which as of right now are completely empty (I checked: zero secrets, zero variables set on canonical). Whatever you add here per the docs feeds staging alone, because the fork-facing workflows (deploy.yml and the crons) are gated off this repo. Second, even if a wrong value slipped in, the runtime targets resolve from canonical's own variables with fallbacks that fail toward staging names, never a production project or database (the adversarial review verified no fallback resolves to prod). For this analytics block specifically: the zone id you'd set is the sona.fast zone, which is exactly what beta.sona.fast needs, and the account-scoped analytics token from the fork rollout is safe to reuse since it lands only on the sona-staging Pages project. I've also added a note to docs/staging.md spelling out the per-repo isolation (commit dfc7483).
| Do these **in order**. The Access policy + app (step 3) must exist **before the | ||
| first deploy** (step 6) so the site is never briefly public. | ||
|
|
||
| ### Manual — needs Sparky's Cloudflare token / dashboard |
There was a problem hiding this comment.
Having my name in these documents feels a little weird, should we just make a generic as if anybody is the owner?
There was a problem hiding this comment.
Agreed, done in dfc7483: the heading now says "the account owner's Cloudflare token / dashboard". That was the only place in the PR that named you (checked the workflows and seed too), so the docs now read correctly for any self-hoster running their own staging.
dfc7483 to
f63b5ef
Compare
What
A staging deployment of the canonical
sona-fast/sonarepo at https://beta.sona.fast, behind Cloudflare Access (invite-only beta testers), for exercising changes against real Cloudflare infra before they reach a fork.Pages project
sona-staging, D1sona-staging-db, R2sona-staging-images.Architecture
.github/workflows/staging-deploy.yml— push tomain+ manual dispatch, gatedif: github.repository == 'sona-fast/sona'(the exact inverse ofdeploy.yml's fork gate, so the two never both fire on one repo). Mirrorsdeploy.yml's step sequence unchanged: check/test/build → create-Pages-project-if-needed → trackedschema_migrationsD1 migrations → optional secret/var syncs (each skips cleanly when unset) →wrangler pages deploy. TheCLOUDFLARE_PAGES_PROJECT/D1_DATABASE_NAMEfallbacks default to the staging names, notdeploy.yml's prodsona/sona-db, so a missing repo var can never point this pipeline at production..github/workflows/staging-cron.yml— sticker re-sync (daily 06:00), artist sync (daily 06:30), orphan cleanup (weekly Mon 07:00), each gated to the canonical repo, POSTingbeta.sona.fastwithBearer CRON_SECRET, mirroring the fork cron curls. Per-schedule job gating;workflow_dispatchruns all three.scripts/staging-seed.sql— synthetic data only (fake artist,/staging/*placeholder URLs that 404 harmlessly,setupComplete=true) plus a placeholderadminPasswordHash.scripts/hash-admin-password.ts— reads a password from stdin, prints thepbkdf2$sha256$...hash, reusingreset-password.ts's already-parity-tested hasher (one PBKDF2 impl for the CLI tooling).docs/staging.md— one-time setup checklist.Review catches honored
From the proposal round:
REPLACE_ME_WITH_pbkdf2_HASHplaceholder; the operator generates their own via the stdin hasher and sed-substitutes at load time.beta.sona.fastandsona-staging.pages.dev, sequenced before the first deploy sosona-stagingis never briefly public.DB→sona-staging-db,IMAGES→sona-staging-images,nodejs_compat, andcompatibility_dateconfig that a CI-first Pages project needs.From this fix round:
name:key removed —staging-cron.ymlhad two top-levelname:keys (invalid YAML that GitHub's validator rejects, silently never scheduling); now a singlename.adminPasswordHashINSERT now documents that loading without the sed substitution locks out admin (wizard skipped, no password verifies), with thenpm run reset-password/ wizard-reset recovery path.pull_request_targetis forbidden in a prominent top-of-file comment (that trigger would run untrusted fork-PR code withCLOUDFLARE_API_TOKEN/SETUP_TOKEN/CRON_SECRETin scope).Attaching
beta.sona.fastto thesona-stagingPages project is exactly the Pages-domain rung of the SONA-6 connect-domains/doctor tooling (in flight onfeat/connect-domains-doctor-11), so this doubles as its first live test bed.Setup
See
docs/staging.md— split into manual (token/dashboard) vs scriptable steps, ordered so the Access app+policy exist before the first deploy.Gates
npx vitest run→ 84 files / 885 tests pass, incl. 3 newstaging-seedtests (migrations+seed apply cleanly to a fresh better-sqlite3 DB; the committed seed carries no real hash; a generated hash round-trips through the app'sverifyPasswordHash).npm run check→ 0 errors (79 warnings, all pre-existing baseline in.sveltefiles not touched here).actionlintis not installed in this environment.Closes #166