docs/audit/functional-audit-ops.md. Ingestion does not stop meanwhile: the edge tier keeps
accepting events into JetStream (WEB_EVENTS 72 hours, API_METRICS 7 days), and they are
delivered once the core tier is back.
The path chains pieces that exist and are tested on their own:
- a fresh restore node from OpenTofu (
restore_enabled); restore.yml, which restores the pgBackRest repository to a moment on that node;migrate-export METHOD=dumpfrom the restored server;- new core nodes with an empty, bootstrapped cluster on a new repository generation;
migrate-import,verify-migrationagainst the restored server, then the services.
archive_timeout (60 s, roles/core/defaults/main.yml) of committed transactions
(../reference/availability.md).
Steps
-
Stop what still runs on the core nodes, if anything does, so nothing writes into a
half-dead cluster: on each reachable core node
cd /opt/veristar/core && sudo docker compose stop. -
Create the restore node. In
infra/tofu/envs/<env>/terraform.tfvarssetrestore_enabled = true(andrestore_zoneif zone 3 is affected), thenmake <env>-planandmake <env>-apply. Expected:Plan: 1 to addfor<env>-restore-1(a core-sized server on the private network at.41) and an inventory whoserestoregroup holds it (tests/infra/*.tftest.hclruninventory_with_restorechecks this shape). -
Restore to the moment before the loss (or to the end of the newest backup when the
repository itself is the only survivor):
If someone deleted from the repository too, read it as it was before the deletion by adding
-e '{"restore_extra_args": ["--repo=1", "--repo-target-time=2026-11-20 07:00:00+00"]}'(the bucket is versioned and object-locked, ../deploy/backups.md). Expected:restored to ... on <restore node private address>:5432. -
Export from the restored, standalone server (no services write to it) with an env file
naming the restore node as in ../handover/rebuild.md section 4.1
(
PLATFORM_DB_HOST=<restore node private address>, port 5432, the superuser login from the SOPS file): -
Replace the core nodes:
tofu ... plan -replace=module.node["<env>-core-1"].azurerm_linux_virtual_machine.this(and core-2), as in edge-node-failure.md step 1, thenmake <env>-apply. -
Remove the old cluster from etcd, so the new nodes bootstrap a new cluster instead of
waiting to join a leader that no longer exists. On any edge node:
-
Raise the repository generation. The new cluster has a new system id, which the old
stanza refuses. Add
backup_repo_generation: <old + 1>(it starts at 1) to the environment’sgroup_vars/all.yml; the new cluster then archives to/<env>-g<n>in the same bucket, and the old generation stays readable and locked until its retention ends. Restores from the old generation pass-e backup_repo_generation=<old>. -
Converge:
make <env>-deploy SA_IMAGE_TAG=<release> SA_RELEASE_MANIFEST=<...>.site.ymlbootstraps an empty cluster, creates the stanza of the new generation, switches WAL archiving on and starts the services. -
Import and verify with the services on the core nodes stopped
(
sudo docker compose stop umami controlplane replayer metricswriter jobson both):Expected:verify-migrationexits 0 (row counts of every table, per-tenant daily web and API aggregates, Umami/statsfor a sample of sites and days). Then start the services again (make <env>-deploy ...), and the replayer and metrics writer drain what JetStream held. -
Take a full backup of the new generation at once:
sudo systemctl start sa-pgbackrest-full.serviceon the leader, thenpgbackrest --stanza=sa infoshows it. -
Remove the restore node:
restore_enabled = false, plan and apply.
docs/reference/drills/<date>.md.
Exercised
Not yet exercised: the whole chain on staging (needs the first apply), and steps 3 to 10 on the
local converge. The local point-in-time drill (
tests/drills/pitr.sh) covers step 3 only and has
not been run either (plan phase 16).