Verify full and essentials recovery and implement bounded retention tooling
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
This commit is contained in:
codex 2026-09-06 11:10:50 +02:00
parent 11322a5256
commit a3ca4b708f
16 changed files with 461 additions and 4 deletions

View file

@ -10,7 +10,7 @@ Backup, 10 GiB quota. Provider selection does not establish asset coverage.
| platform-pg | Scaleway Barman base backups + WAL, `platform-pg/` | Earlier package restore evidence; independent logical Nextcloud copy |
| platform-pg-2 | Scaleway Barman base backups + WAL, `platform-pg/platform-pg-2/` | Earlier package restore evidence; independent logical Nextcloud copy |
| forgejo-db | Scaleway Barman base backups + WAL, `platform-pg/forgejo-db/`; daily 02:35 UTC, 30-day retention | Fresh isolated physical restore passed in 45.08 seconds; full archive coverage remains separate |
| Forgejo repositories/packages/blobs | No reviewed Scaleway archive destination found | Corrected full-archive capture; 5.35 GB encrypted secondary download and isolated Forgejo recovery passed, including all 2,040 package blobs |
| Forgejo repositories/packages/blobs | Full archive at `platform-pg/application-archives/forgejo/`; retrieved full-application recovery passed | Full secondary copy preserved; new 273 MiB essentials copy also passed isolated reduced recovery. Scheduled tier cutover remains pending. |
| net-kingdom-pg / state-hub-db | No native Barman destination observed | Do not infer protection from the shared cells' healthy backup status |
| OpenBao / S1 host configuration | Not evaluated by this database restore | Their own encrypted snapshot/host backup and recovery contracts still apply |

View file

@ -90,3 +90,57 @@ when overlap/headroom cannot be met. Never delete the last verified recovery poi
upload overlap, failed-upload preservation and safe expiration within 10 GiB.
6. Switch scheduled secondary delivery only after these gates pass. Preserve the
current good full Nextcloud copy until a verified replacement permits retirement.
## Verified implementation — 2026-09-06
The actual encrypted essentials artifact is **286,298,520 bytes (273 MiB)**,
versus 5,353,024,293 bytes for the tested full archive: about 94.7% smaller.
Nine distinct copies at this measured size need about 2.40 GiB, before other
services, incoming overlap, growth and provider accounting. Keep the 600 MiB
admission ceiling; current size is not a perpetual promise.
The full archive was multipart-uploaded to the separate existing-policy prefix
`platform-pg/application-archives/forgejo/`, retrieved by its returned object
version, hash-checked, decrypted and restored successfully. Forgejo startup,
two Git clones/fsck and all 2,040 package blob hashes passed. This is a verified
primary archive and recovery; recurring full-archive scheduling is still pending.
The existing bucket contract is versioned with 30-day current/noncurrent lifecycle;
a separate calendar archive-expiration policy has not been activated.
The smaller candidate was uploaded/downloaded through Backup, decrypted and
restored without primary access. Forgejo started with 142 repositories, six users
and 2,050 package metadata records; the package service was explicitly disabled.
Two Git clones/fsck passed. The newer essentials capture and older full archive
are different recovery points; they are not claimed to be an atomic matching pair.
Both disposable recovery environments were removed. The public pinned Forgejo
and PostgreSQL recovery images were successfully pulled independently of both
Scaleway and the production forge. Other five database clusters total about
209 MiB of physical data; compressed export budgets remain to be measured.
Implementation:
- `scripts/capture_forgejo_archive.py --profile essentials` uses native exclusions,
seals the explicit manifest and enforces the size ceiling. Default remains full.
- `scripts/forgejo_essentials_profile.py` also removes generated Actions logs while
preserving unique attachments/LFS/Actions artifacts and rejects unsafe paths.
- `scripts/scaleway_forgejo_archive.py` performs bounded multipart PUT, aborts failed
in-flight multipart uploads, then downloads and verifies bytes. Run with
`uv run --no-project --with boto3==1.40.18 python ...`; credential values stay in
captured memory from the existing governed databases Secret.
- `scripts/backup_retention_plan.py` selects seven daily/two weekly points without
duplicate uploads; protects the explicitly pinned recovered archive and newest
point, ignores unknown/unverified objects and requires 2 GiB upload headroom.
- `scripts/execute_nextcloud_retention.py` previews by default. With `--apply`, the
separate attended owner executor revalidates protected ciphertext, retained
object presence and candidate sizes/ETags before conditional deletion. It uses
a per-user local lock; deploy only one authoritative owner executor. It never
changes workload credentials or deletes full archives. Unknown drill filenames
are intentionally ineligible. No live expiration was executed in this review.
Before scheduled cutover, bind the durable primary caller and dependencies,
produce canonical `forgejo-essentials-YYYYMMDDTHHMMSSZ.zip.age` names and verified
inventory, and bind the separate owner retention execution/fresh quota check.
The current local `forgejo-backup` cron remains unchanged. The activity-core
worker has vendored age/kubectl, which its CNPG resolver adds to PATH; absence
from its default PATH did not establish missing tools. Neither that mount nor
an attended workstation proof establishes a scheduled primary archive job.

View file

@ -0,0 +1,22 @@
{
"schema": "platform.essentials-bootstrap-assessment.v1",
"pinned_recovery_images_public_pull_verified": true,
"image_bytes": [
75323786,
160190278
],
"other_database_physical_bytes": {
"apps-pg-1": 32477276,
"net-kingdom-pg-1": 41300060,
"platform-pg-1": 53373778,
"platform-pg-2-1": 25556393,
"state-hub-db-1": 66697262
},
"package_type_counts": {
"container": 21,
"npm": 2,
"pypi": 2
},
"all_packages_assumed_rebuildable": false,
"note": "Physical database/image sizes, not compressed offsite exports. Other service backup coverage and independent recovery-key custody retain their own acceptance contracts."
}

View file

@ -0,0 +1,26 @@
{
"schema": "platform.forgejo-essentials-capture.v1",
"status": "captured_pending_offsite_restore",
"archive_bytes": 286228448,
"archive_sha256": "940b27f6782ad614fe16ab4f6f31dd431adb60b6dea249662346c573617b3a10",
"repository_heads": 142,
"zip_crc_passed": true,
"budget_bytes": 629145600,
"manifest": {
"complete_application_backup": false,
"created_at": "2026-09-06T06:12:09.581148+00:00",
"kept_members": 70433,
"omitted_prefixes": [
"data/packages/",
"data/repo-archive/",
"data/indexers/",
"data/actions_log/",
"log/"
],
"package_registry_available": false,
"profile": "essentials",
"recovery_contract": "Git and collaboration recovery; packages require full primary or an independent artifact source.",
"schema": "platform.forgejo-backup-profile.v1",
"unique_data_policy": "Keep repositories, database, configuration, attachments, LFS and Actions artifacts when present."
}
}

View file

@ -0,0 +1,27 @@
{
"schema": "platform.forgejo-isolated-restore.v1",
"status": "restored_essentials",
"images": [
"code.forgejo.org/forgejo/forgejo@sha256:e2684fd8707d486329084a695ed91999a4072a798e5409d45c1eb8a2911ff4b9",
"postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70"
],
"source_provider": "Nextcloud",
"offsite_artifact": "wp0029-recovery-forgejo-essentials-20260906.zip.age",
"ciphertext_sha256": "23b5250469a63a3e0097047851cb02036ec4e24928979db7a972a3245f93b60a",
"archive_profile": "essentials",
"stage": "repository_recovery",
"database_import": true,
"database_counts": {
"repositories": 142,
"users": 6,
"package_blobs": 2050
},
"application_health": true,
"repositories_verified": [
"coulomb/key-cape",
"coulomb/adaptive-pricing"
],
"package_registry_available": false,
"primary_storage_accessed": false,
"cleanup": true
}

View file

@ -0,0 +1,25 @@
{
"schema": "platform.real-offsite-recovery.v1",
"status": "offsite_fetched_pending_isolated_restore",
"quota_before": {
"quota_bytes": 10737418240,
"used_bytes": 5385385643,
"available_bytes": 5352032597
},
"artifact": "wp0029-recovery-forgejo-essentials-20260906.zip.age",
"source_backup": "forgejo-essentials-20260906.zip.age",
"kv_version": 3,
"upload_http_status": 201,
"download_http_status": 200,
"ciphertext_bytes": 286298520,
"ciphertext_sha256": "23b5250469a63a3e0097047851cb02036ec4e24928979db7a972a3245f93b60a",
"archive_profile": "essentials",
"decrypted": true,
"plaintext_sha256": "940b27f6782ad614fe16ab4f6f31dd431adb60b6dea249662346c573617b3a10",
"quota_after": {
"quota_bytes": 10737418240,
"used_bytes": 5385385643,
"available_bytes": 5352032597
},
"warden_exit_code": 0
}

View file

@ -0,0 +1,16 @@
{
"schema": "platform.forgejo-primary-archive.v1",
"status": "primary_fetched_pending_application_restore",
"stage": "transfer_verified",
"destination": "s3://railiance-platform-pg-backup/platform-pg/application-archives/forgejo/2026/09/06/060650-6cf2063187f52d6a2b65f79b.zip.age",
"ciphertext_sha256": "00d2a5b099bc0e65b2109287fca3bd9d6eb1c8a4e43623520a6e9f3379904018",
"ciphertext_bytes": 5353024293,
"uploaded_bytes": 5353024293,
"multipart_completed": true,
"version_pinned": true,
"downloaded_bytes": 5353024293,
"download_hash_matches": true,
"decrypted": true,
"plaintext_sha256": "fe53d156461387ea487cddc5d6a01f0b5e3debda6fa494c8b87afc8bc62b55b2",
"warden_exit_code": 0
}

View file

@ -0,0 +1,26 @@
{
"schema": "platform.forgejo-isolated-restore.v1",
"status": "restored",
"images": [
"code.forgejo.org/forgejo/forgejo@sha256:e2684fd8707d486329084a695ed91999a4072a798e5409d45c1eb8a2911ff4b9",
"postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70"
],
"source_provider": "Scaleway",
"offsite_artifact": "s3://railiance-platform-pg-backup/platform-pg/application-archives/forgejo/2026/09/06/060650-6cf2063187f52d6a2b65f79b.zip.age",
"ciphertext_sha256": "00d2a5b099bc0e65b2109287fca3bd9d6eb1c8a4e43623520a6e9f3379904018",
"archive_profile": "full",
"stage": "package_blob_recovery",
"database_import": true,
"database_counts": {
"repositories": 142,
"users": 6,
"package_blobs": 2040
},
"application_health": true,
"repositories_verified": [
"coulomb/key-cape",
"coulomb/adaptive-pricing"
],
"package_blobs_verified": 2040,
"cleanup": true
}

View file

@ -0,0 +1,13 @@
{
"schema": "platform.forgejo-primary-archive.v1",
"status": "primary_fetched_pending_application_restore",
"stage": "transfer_verified",
"destination": "s3://railiance-platform-pg-backup/platform-pg/application-archives/forgejo/2026/09/06/060650-6cf2063187f52d6a2b65f79b.zip.age",
"ciphertext_sha256": "00d2a5b099bc0e65b2109287fca3bd9d6eb1c8a4e43623520a6e9f3379904018",
"ciphertext_bytes": 5353024293,
"uploaded_bytes": 5353024293,
"multipart_completed": true,
"version_pinned": true,
"downloaded_bytes": 5353024293,
"download_hash_matches": true
}