Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 45 additions & 56 deletions projects/ProvinceSystem/DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,16 @@ ProvinceSystem changes reach the website in three steps, all driven by GitHub Ac

Paths below are on the TF server.

All site records use native PostgreSQL after the site's verified cutover; CoreProtect's external SQLite
databases are unchanged. Main (`www`) remains a separate, explicitly requested manual deployment and cutover.
See [Native PostgreSQL operations](docs/ops/postgresql.md) for the reviewed copy, verification and rollback
procedure, including retention of the one-off SQLite import/rollback schema for at least 30 days.
Both sites keep their records in PostgreSQL; CoreProtect's SQLite databases are read separately. See
[PostgreSQL storage](docs/ops/postgresql.md) for configuration, migrations, backups and restores.

## Branch previews

Native preview deployment requires the repository variable `NATIVE_POSTGRES_PREVIEWS_READY=true`. Keep it
disabled until the host scripts, database isolation and Dev migration are ready. Builds can still run while
deployment is held. The host also requires Dev's verified authority marker at
Previews deploy while the repository variable `NATIVE_POSTGRES_PREVIEWS_READY` is `true`. Set it to `false`
to hold preview deployment; builds still run. The host also requires Dev's authority marker at
`/home/tfmc/provincesystem-dev-data/data/.postgres-authority.json`, a backend image labelled
`net.tfminecraft.storage=postgres`, and both images' `org.opencontainers.image.revision` labels matching the
requested commit. A branch without the native backend is refused. After Dev and database isolation pass,
enable readiness for a controlled first Preview workflow run on a reviewed native branch. Verify creation,
redeployment and teardown, including database/role cleanup. Leave readiness enabled only after that lifecycle
passes; disable it again if validation fails.
requested commit. A branch from before the move to PostgreSQL is refused.

Pushing a branch builds its backend and frontend images on GitHub, in parallel, and pushes them to
`ghcr.io/tf-minecraft/provincesystem-preview-{backend,frontend}:<slug>` (public packages). Pushes to `main` build
Expand All @@ -51,9 +45,12 @@ the `ps-preview-router` Caddy container (`127.0.0.1:8090`, behind the `*.tfminec
database and assets.
- **Clone:** the trusted live Dev helper
([`backend/src/database/preview.py`](https://github.com/TF-Minecraft/ProvinceSystem/blob/main/backend/src/database/preview.py))
creates the 61-table site schema using reviewed migrations under the preview owner. A site-data-only custom
dump/restore copies Dev's site records and identity counters; it copies no Precedent or patch note data,
ownership or ACLs. Schema creation and privileged restore use trusted Dev tooling, not branch code.
creates the site schema using reviewed migrations under the preview owner. It lists Dev's public tables,
leaving out Precedent (`precedent_*`), patch notes (`patchnote_*`) and `schema_migrations`, and a data-only
custom dump/restore copies those tables' rows and identity counters. No ownership or ACLs are copied. Schema
creation and privileged restore use trusted Dev tooling; branch code takes no part. The schema follows Dev's
migrations, so once Dev has a new migration, a preview of a branch cut before it fails its migration check
until the branch is rebased onto `main`.
- **Scrub:** before startup, the helper clears sessions, user sessions, Discord OAuth/link state, preview sign-in
tickets, Microsoft link state, and Patreon tokens, OAuth and pending-link state, including creator credentials.
It revokes redemption and war declaration codes and clears redemption-code plaintext, retaining rows needed by
Expand All @@ -78,17 +75,16 @@ the `ps-preview-router` Caddy container (`127.0.0.1:8090`, behind the `*.tfminec
- **Publication:** `up` reports success and sets the expiry only after the public UI and API pass health checks.
Host preview and Dev deployment locks prevent cloning while Dev is being deployed or migrated.

Before enabling native previews, a Dev administrator must explicitly run the isolation setup from the trusted
Dev backend:
Preview isolation needs a one-time administrator setup, run from the trusted Dev backend:

```bash
sudo -n -u tfmc /usr/local/sbin/ps-compose dev exec -T backend python3 -m src.database.preview setup
```

This revokes `PUBLIC CONNECT` on connectable databases in Dev's PostgreSQL cluster, preserving database-owner
access and existing direct grants. Provisioning grants each runtime preview login access only to its own
database and refuses creation if either login can connect elsewhere. Review this cluster-wide change in the
[native operations procedure](docs/ops/postgresql.md) before enabling the workflow variable.
database and refuses creation if either login can connect elsewhere. See
[preview provisioning](docs/ops/postgresql.md#preview-provisioning).

Inspect or stop previews by hand:

Expand All @@ -114,60 +110,53 @@ the owner of both checkouts. Deploys to the same site run one at a time.
| Compose target | `ps-compose main` | `ps-compose dev` |
| Health checks | `127.0.0.1:3000/` and `127.0.0.1:8000/maps/accessible` | `127.0.0.1:13001/` and `127.0.0.1:18001/maps/accessible` |
| Backups | `/home/tfmc/provincesystem-data/deploy-backups/` | `/home/tfmc/provincesystem-dev-data/deploy-backups/` |
| Older commit than deployed | deployed (this is a rollback) | skipped as `already deployed` |
| Older commit than deployed | deployed if it has every migration the database has ([Rolling back www](#rolling-back-www)) | skipped as `already deployed` |

`site-deploy`:

1. Refuses commits not on `origin/main` (exit 4) and, on www, a checkout with uncommitted changes to tracked files
(exit 1). On dev, `git checkout --detach` keeps the expected local edits and refuses if they would be overwritten.
1. Refuses commits not on `origin/main` or from before the move to PostgreSQL (no
`backend/src/database/operations.py`), both with exit 4, and, on www, a checkout with uncommitted changes to
tracked files (exit 1). On dev, `git checkout --detach` keeps the expected local edits and refuses if they would
be overwritten. It then reads `schema_migrations` from the site's `db` and compares it with the target
revision's files under `backend/src/database/migrations/`. A revision missing an applied migration is refused
before anything changes (exit 4, log `<sha> predates database migration NNNN; fix forward`).
2. Selects private `/home/tfmc/.site-images-<site>.json` image tags for the exact requested revision and builds
both services using Docker's cache before stopping the backend for a forward deployment. The overlay is
applied after the site's private Compose override. Both running container image IDs must match those
revision tags before the deployment can pass its health checks, including an already-deployed shortcut.
3. Starts/checks the `db` service and writes a private custom-format PostgreSQL backup as
`postgres.before-deploy-<sha>-<UTC time>.dump`, publishing it by an atomic rename after `pg_dump` succeeds.
Only these PostgreSQL deploy dumps are pruned to the newest 10. While SQLite is still authoritative, it also
takes a final consistent `province.db.before-deploy-<sha>-<UTC time>` backup with SQLite's backup API after
stopping the backend.
4. For native target code, runs
[`src.database.operations`](https://github.com/TF-Minecraft/ProvinceSystem/blob/main/backend/src/database/operations.py)
from the reviewed target image while the backend is stopped. The first cutover archives the original SQLite
database and legacy schema/columns under the data mount's `migration-rollback/`, copies and verifies site
records, then atomically publishes `.postgres-authority.json`. An interrupted committed copy must verify
exactly before adoption; an existing populated target is not erased. Later deploys check the marked database
identity and migration history and apply native migrations without recopying SQLite. Studio provisioning
requires a separate restricted `DATABASE_STUDIO_URL` login for the same database.
`postgres.before-deploy-<sha>-<UTC time>.dump`, publishing it by an atomic rename after `pg_dump` succeeds,
and keeps the newest 10.
4. Runs [`src.database.operations`](https://github.com/TF-Minecraft/ProvinceSystem/blob/main/backend/src/database/operations.py)
`prepare` and then `studio` from the target image while the backend is stopped. `prepare` checks that the
configured database is the one named by the data mount's `.postgres-authority.json` marker, repeats the
migration check, then applies pending migrations. `studio` provisions the Database Studio login, which must be
a separate restricted `DATABASE_STUDIO_URL` login for the same database ([deploy steps](docs/ops/postgresql.md#deploy-steps)).
5. Starts both services without rebuilding, verifies their image IDs, then waits up to 3 minutes for both health checks.
On failure it stops the backend, restores the previous code, rebuilds and checks again, then exits 5. When
returning to legacy code after a cutover, it first reverse-exports and verifies PostgreSQL into SQLite,
including writes accepted during the health-check window. A requested native-to-legacy downgrade likewise
exports with the current native image before checking out and starting old code. If reverse export fails,
keep the backend stopped for operator inspection. If a legacy target accepts SQLite writes and then fails,
the tracked offline transition verifies the unchanged PostgreSQL baseline before importing those accepted
writes back in one transaction. Independently changed PostgreSQL targets are refused. Native code rollback
retains PostgreSQL data and uses retained, checksum-verified definitions of already-applied migrations;
no deploy automatically restores a dump.
On failure it stops the backend and checks the last healthy revision against `schema_migrations` the same
way. If the failed deploy applied a migration that revision predates, the backend stays stopped and the log
records `failed: last healthy revision does not fit the database (see log); backend kept stopped`; deploy a fix
forward. Otherwise it restores the previous code, rebuilds, runs `prepare` and `studio` again and checks
health, then exits 5. If any of those steps fails, the backend stays stopped. No deploy restores a dump.
6. After a healthy www deploy that changes `backend/src/patchnotes/`, runs `sudo tfmc-patchnotes-update <sha>` so the
[patch note jobs](docs/ops/patch-notes-jobs.md) use the same code. A failure there is logged and does not fail the
deploy.

Each run appends a line to `/home/tfmc/site-deploy.log`. The private
`/home/tfmc/.site-deployed-<site>` record tracks the last healthy revision separately from Git HEAD,
so a retry resumes an interrupted build/copy instead of reporting an undeployed checkout as successful.
so a retry resumes an interrupted build or migration instead of reporting an undeployed checkout as successful.
Revision-tagged images carry site/revision labels. Weekly image pruning waits for both deployment locks and
removes unused site images only after 30 days; images referenced by containers remain protected.

Keep original SQLite copies, consistent cutover backups, legacy archives and the one-off import/rollback schema
for the full rollback window of **at least 30 days**. These are outside the newest-10 PostgreSQL dump pruning;
the deployment script does not automatically delete them. Follow the
[copy and rollback procedure](docs/ops/postgresql.md) before changing authority or removing retained material.
`province.db` files and the `migration-rollback/` folder left from before the move to PostgreSQL can stay on the
host as plain backups. No deploy or tool reads them.

### Rolling back www

Run **Deploy** with site `www` and an older commit on `main` only when explicitly requested. A native code rollback
keeps current PostgreSQL data. A downgrade to legacy code uses the stopped-backend reverse export above so
post-cutover writes survive. A full PostgreSQL restore is a separate manual operation and also affects Precedent
and patch notes; use the [native rollback procedure](docs/ops/postgresql.md) to choose the appropriate recovery.
Run **Deploy** with site `www` and an older commit on `main` only when explicitly requested. Migrations only go
forward: `site-deploy` refuses a commit that lacks a migration the database already has (exit 4), and the log
names the version. Once www has a newer migration, fix forward with a new commit on `main`. A full PostgreSQL
restore is a separate manual operation that also affects Precedent and patch notes; see [backups and restores](docs/ops/postgresql.md#backups-and-restores).

### Deploying by hand

Expand Down Expand Up @@ -210,18 +199,18 @@ and waits until it is healthy. Each site's password is `POSTGRES_PASSWORD` in it
`DATABASE_URL=postgresql://provincesystem:<password>@db:5432/provincesystem`. Dev's compose file builds `DATABASE_URL`
from the same password, and its override repeats it because its `environment: !override` replaces the list.
`DATABASE_STUDIO_URL` must address that same database using a separate restricted login; it must not reuse the
owner credentials. Initial restriction of `PUBLIC` large-object constructor privileges is an explicit,
reviewed administrator step in [Native PostgreSQL operations](docs/ops/postgresql.md); preserve owner access
and review existing direct grants. Deploys refresh the Studio grants after migrations and refuse unsafe roles.
owner credentials. The deploy's `studio` step restricts `PUBLIC` large-object constructor privileges while
keeping owner access and direct grants, refreshes the Studio grants after migrations and refuses unsafe roles
([deploy steps](docs/ops/postgresql.md#deploy-steps)).
PostgreSQL reads `POSTGRES_PASSWORD` only when it creates the volume; to change the password later, run
`ALTER USER provincesystem PASSWORD '…'` in `psql` and update both files.

```bash
sudo -u tfmc ps-compose main exec db psql -U provincesystem provincesystem # a shell
```

Do not treat copying the asset mounts as a database import. Use the
[native operations guide](docs/ops/postgresql.md) for copies, restores, validation and rollback.
Copying the asset mounts does not copy site records. See [PostgreSQL storage](docs/ops/postgresql.md) for
backups and restores.

## Keys and secrets

Expand Down
12 changes: 6 additions & 6 deletions projects/ProvinceSystem/STAGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ All site records live in the stack's PostgreSQL `db` service (`pgvector/pgvector
`tfmc-staging_db` named volume. The backend waits for the database health check and uses `DATABASE_URL`.
The clone's `backend/src/data`, `input`, `defines` and `output` directories are mounted for assets and generated
files; copying these directories does not copy site records. CoreProtect remains an external SQLite source.
Use [Native PostgreSQL operations](docs/ops/postgresql.md) for reviewed data copies, migrations and rollback.
See [PostgreSQL storage](docs/ops/postgresql.md) for migrations, backups and restores.

**Product docs:** [docs/README.md](docs/README.md) · **Flows:** [docs/flows/journeys.md](docs/flows/journeys.md) · **Dev flags:** [docs/ops/dev-config.md](docs/ops/dev-config.md)

Expand All @@ -26,8 +26,7 @@ do not run `git reset --hard` there. If a manual deployment is necessary, use
`sudo -u tfmc site-deploy deploy dev <full sha>` on the TF server.

Do not run the local staging scripts on the TF server: their Compose project name, `tfmc-staging`, is also used
by managed Dev. Main (`www`) deploys only through an explicitly requested **Actions → Deploy → Run workflow**;
local staging does not cut it over.
by managed Dev. Main (`www`) deploys only through an explicitly requested **Actions → Deploy → Run workflow**.

## Start a separate local stack

Expand Down Expand Up @@ -64,8 +63,9 @@ Stop later:
```

Stopping removes containers and the stack network, but retains the database volume and mounted assets.
Removing the volume deletes the local site records. For a copy of another site's data, follow the
[native copy procedure](docs/ops/postgresql.md), which handles database records separately from assets.
Removing the volume deletes the local site records. To load another site's records, restore one of its
PostgreSQL deploy dumps into this stack's database with `pg_restore`
([backups and restores](docs/ops/postgresql.md#backups-and-restores)); copy asset directories separately.

## Same-host integrations

Expand Down Expand Up @@ -110,7 +110,7 @@ configuration for a shared or longer-lived stack. `POSTGRES_PASSWORD` comes from
it when initializing the volume, so changing the variable alone does not rotate an existing database password.
Do not copy host Dev administrator credentials into local or branch-preview containers. If testing Database
Studio, provision a separate restricted login and set `DATABASE_STUDIO_URL` for the same local database as
described in the [native guide](docs/ops/postgresql.md).
described in the [PostgreSQL guide](docs/ops/postgresql.md).

## Auth hardening

Expand Down
2 changes: 1 addition & 1 deletion projects/ProvinceSystem/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,5 @@ Schema assets: [assets/map-export-schema.json](assets/map-export-schema.json) (S

- Deployment and QA checklists: [STAGING.md](../STAGING.md)
- Preview and deployment workflow: [DEPLOY.md](../DEPLOY.md)
- Database configuration, migrations and rollback: [PostgreSQL](ops/postgresql.md)
- Database configuration, migrations, backups and restores: [PostgreSQL](ops/postgresql.md)
- Update reference: [UPDATE.md](../UPDATE.md)
8 changes: 5 additions & 3 deletions projects/ProvinceSystem/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ App Router `loading.tsx` boundaries cover route navigation, with outlines suited
| Map | `map_routes`, `data_routes`, `file_routes`, `regen_routes` |
| Skins | codes, redeem, upload, staff review, plugin approved |
| Drinks | redeem, submit, catalog, staff review |
| Characters | session, create, roster, wardrobe, `rpc_player_meta` |
| Characters | session, create, roster, wardrobe, web entitlements (`PUT /characters/plugin/rpc-player-meta`) |
| Profile | dashboard, skin/drink start, code sign-in (`POST /profile/redeem`), code wardrobe (`GET /profile/wardrobe`) |
| Identity | Discord link, guild grace |
| Diagnostic | Server ping, resource pack copy, speed test, player reports |
Expand Down Expand Up @@ -154,14 +154,16 @@ Map and cosmetics share Docker and process space but not business logic.

`DATABASE_URL` points to the site's Compose `db` service and persistent volume.
Startup applies checked, ordered migrations from `backend/src/database/migrations/`.
Configuration, backups and rollback: [PostgreSQL storage](ops/postgresql.md).
Configuration, migrations, backups and restores: [PostgreSQL storage](ops/postgresql.md).

| Table family | Purpose |
|--------------|---------|
| `codes`, `submissions` | Skins workflow |
| `drink_submissions`, `drink_textures`, `drink_catalog` | Drinks workflow |
| `discord_links`, `discord_link_codes` | Identity |
| Character sessions, roster, wardrobe, wills and catalog | Characters API |
| `web_entitlements` | Per-realm rank entitlements from TFMCWeb: character limit, name colours, wardrobe slots, skin kinds, drink perks, permission flags |
| `character_account_meta` | Per-account data from RPCharacters' roster push: 18+ answer, account creation time, kit cooldowns |
| `users`, `user_sessions`, OAuth states | Website accounts and sign-in |
| Patreon and LuckPerms tables | Entitlement state and plugin outboxes |
| Chronicle and Ledger index tables | Map history indexes |
Expand All @@ -174,7 +176,7 @@ backend/src/output/{map}/ # generated map assets
```

Map assets remain under `backend/src/output/{map}/…`. PNGs stay on disk for serving and pack export.
CoreProtect is a separate read-only SQLite source; retained `province.db` copies are offline rollback resources.
CoreProtect is a separate read-only SQLite source.

## Auth model

Expand Down
Loading