diff --git a/Makefile b/Makefile index d23af46..b524cc0 100644 --- a/Makefile +++ b/Makefile @@ -75,8 +75,6 @@ db-logs: ## Tail gitea-db primary logs forgejo-db-deploy: ## Apply forgejo-db cnpg Cluster + NetworkPolicies on railiance01 $(KUBECTL) apply -f helm/forgejo-db-cluster.yaml $(KUBECTL) apply -f helm/forgejo-db-networkpolicies.yaml - $(KUBECTL) apply -f helm/forgejo-db-backup-networkpolicies.yaml - $(KUBECTL) apply -f helm/forgejo-db-backup.yaml forgejo-db-status: ## Show forgejo-db cnpg cluster health $(KUBECTL) cnpg status forgejo-db -n databases 2>/dev/null || \ diff --git a/helm/forgejo-db-backup-networkpolicies.yaml b/helm/forgejo-db-backup-networkpolicies.yaml deleted file mode 100644 index 5f1fc68..0000000 --- a/helm/forgejo-db-backup-networkpolicies.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-backup-egress-forgejo-db - namespace: databases -spec: - podSelector: - matchLabels: - cnpg.io/cluster: forgejo-db - policyTypes: [Egress] - egress: - - ports: - - {port: 443, protocol: TCP} - - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: kube-system - ports: - - {port: 53, protocol: UDP} - - {port: 53, protocol: TCP} diff --git a/helm/forgejo-db-backup.yaml b/helm/forgejo-db-backup.yaml deleted file mode 100644 index 5d14839..0000000 --- a/helm/forgejo-db-backup.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: postgresql.cnpg.io/v1 -kind: ScheduledBackup -metadata: - name: forgejo-db-daily - namespace: databases - labels: - cnpg.io/cluster: forgejo-db -spec: - schedule: "0 35 2 * * *" - backupOwnerReference: self - cluster: - name: forgejo-db - immediate: true - method: barmanObjectStore diff --git a/helm/forgejo-db-cluster.yaml b/helm/forgejo-db-cluster.yaml index 5caade1..db4b474 100644 --- a/helm/forgejo-db-cluster.yaml +++ b/helm/forgejo-db-cluster.yaml @@ -4,7 +4,7 @@ # # Apply: KUBECONFIG=~/.kube/config-hosteurope make forgejo-db-deploy # Status: make forgejo-db-status -# Primary: Scaleway Barman + WAL. Secondary: make forgejo-backup (Nextcloud + age). +# Backup: make forgejo-backup (pg_dump + forgejo dump → Nextcloud; docs/forgejo-backup.md) # # Pre-condition: forgejo-db-credentials Secret in databases namespace. # See helm/forgejo-db-secret.sops.yaml.template @@ -49,22 +49,4 @@ spec: database: forgejo owner: forgejo secret: - name: forgejo-db-credentials - backup: - retentionPolicy: 30d - barmanObjectStore: - destinationPath: s3://railiance-platform-pg-backup/platform-pg/forgejo-db/ - endpointURL: https://s3.nl-ams.scw.cloud - s3Credentials: - accessKeyId: - name: platform-pg-backup-s3 - key: ACCESS_KEY_ID - secretAccessKey: - name: platform-pg-backup-s3 - key: ACCESS_SECRET_KEY - wal: - compression: gzip - maxParallel: 2 - data: - compression: gzip - jobs: 2 + name: forgejo-db-credentials \ No newline at end of file diff --git a/scripts/activate_forgejo_primary_backup.py b/scripts/activate_forgejo_primary_backup.py deleted file mode 100644 index 1e34857..0000000 --- a/scripts/activate_forgejo_primary_backup.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -"""Apply only the reviewed Forgejo backup contract and verify native completion.""" -import argparse -from datetime import datetime,timezone -import json -from pathlib import Path -import subprocess -import time -import yaml -from state_hub_preflight_lane import assert_cluster -ROOT=Path(__file__).resolve().parents[1] - -def run(k,receipt): - def cmd(args,payload=None): - r=subprocess.run(k+args,input=None if payload is None else json.dumps(payload).encode(),capture_output=True,timeout=60) - if r.returncode: raise RuntimeError('backup_activation_command_failed') - return r.stdout - def get(args): return json.loads(cmd(args)) - assert_cluster(k) - desired=yaml.safe_load((ROOT/'helm/forgejo-db-cluster.yaml').read_text())['spec']['backup'] - if desired['barmanObjectStore']['destinationPath']!='s3://railiance-platform-pg-backup/platform-pg/forgejo-db/': raise RuntimeError('wrong_destination') - live=get(['-n','databases','get','cluster','forgejo-db','-o','json']) - if live['spec'].get('backup') not in (None,desired): raise RuntimeError('existing_backup_differs') - ready=any(c['type']=='Ready' and c['status']=='True' for c in live['status']['conditions']) - if not ready: raise RuntimeError('source_not_ready') - for file in ('helm/forgejo-db-backup-networkpolicies.yaml','helm/forgejo-db-backup.yaml'): - cmd(['apply','--dry-run=server','-f',str(ROOT/file)]) - patch=[{'op':'test','path':'/metadata/resourceVersion','value':live['metadata']['resourceVersion']},{'op':'add','path':'/spec/backup','value':desired}] - cmd(['-n','databases','patch','cluster','forgejo-db','--type=json','--dry-run=server','--patch-file=/dev/stdin'],patch) - cmd(['apply','-f',str(ROOT/'helm/forgejo-db-backup-networkpolicies.yaml')]) - cmd(['-n','databases','patch','cluster','forgejo-db','--type=json','--patch-file=/dev/stdin'],patch) - receipt['backup_contract_applied']=True - receipt['started_at']=datetime.now(timezone.utc).isoformat() - cmd(['apply','-f',str(ROOT/'helm/forgejo-db-backup.yaml')]) - for _ in range(120): - rows=get(['-n','databases','get','backups','-o','json'])['items'] - rows=[r for r in rows if r.get('spec',{}).get('cluster',{}).get('name')=='forgejo-db'] - good=[r for r in rows if r.get('status',{}).get('phase')=='completed'] - if good: - chosen=max(good,key=lambda r:r['metadata']['creationTimestamp']) - receipt.update(backup_name=chosen['metadata']['name'],backup_id=chosen['status'].get('backupId'),backup_phase='completed') - break - time.sleep(5) - else: raise RuntimeError('fresh_backup_not_completed') - live=get(['-n','databases','get','cluster','forgejo-db','-o','json']) - checks={c['type']:c['status']=='True' for c in live['status']['conditions']} - receipt.update(production_ready=checks.get('Ready',False),continuous_archiving=checks.get('ContinuousArchiving',False),destination=desired['barmanObjectStore']['destinationPath'],retention='30d') - if not receipt['production_ready'] or not receipt['continuous_archiving']: raise RuntimeError('source_or_archiving_not_ready') - receipt['status']='verified' - -def main(): - p=argparse.ArgumentParser(description=__doc__);p.add_argument('--kubeconfig',required=True);p.add_argument('--receipt',required=True);a=p.parse_args() - receipt={'schema':'platform.forgejo-primary-backup.v1','status':'failed'} - target=Path(a.receipt) - with target.open('x') as f: - try:run(['kubectl','--kubeconfig',a.kubeconfig],receipt) - except Exception:receipt['error']='activation_or_acceptance_failed' - finally:json.dump(receipt,f,indent=2) - return int(receipt['status']!='verified') -if __name__=='__main__':raise SystemExit(main()) diff --git a/workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md b/workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md deleted file mode 100644 index c81e212..0000000 --- a/workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -id: RPF-WP-0038 -type: workplan -title: "Close Forgejo primary backup coverage on Scaleway" -domain: financials -repo: railiance-platform -status: active -owner: codex -created: "2026-09-06" -updated: "2026-09-06" -state_hub_workstream_id: "7beec1a7-aa82-5a36-9a66-6b60008a2455" ---- - -Scaleway is the user-confirmed primary; Nextcloud is independent secondary. -Follow-up to the live coverage gap in RPF-WP-0036-T03. WP-0029 retains the -secondary incident and old-share invalidation. No old backups are deleted. - -## Verify ownership and scoped primary destination - -```task -id: RPF-WP-0038-T01 -status: done -priority: high -state_hub_task_id: "4f5e594a-e731-5298-8ba4-5b1e0cd9e71d" -``` - -This repository owns helm/forgejo-db-cluster.yaml and its deploy target. -Reuse CCR-2026-0012's backup runtime identity and existing databases Secret. -The reviewed bucket policy permits platform-pg/*; choose the separate native -Barman destination platform-pg/forgejo-db/. No bootstrap key, IAM mutation or -namespace expansion is required. Never write full archives into a Barman -server directory. Retention follows the existing 30-day primary contract. - -## Activate native database backup and prove fresh completion - -```task -id: RPF-WP-0038-T02 -status: progress -priority: high -state_hub_task_id: "b6960c2e-1ad3-5c23-bfb6-9f1f16d0f0c2" -``` - -Add exact Scaleway destination, HTTPS/DNS egress, continuous WAL and daily -base backup. Apply only the reviewed backup field with a resource-version -guard, preserving unrelated live settings. Require a completed fresh Backup, -continuous archiving and source readiness; record metadata only. - -## Prove isolated primary database recovery - -```task -id: RPF-WP-0038-T03 -status: todo -priority: high -state_hub_task_id: "b47b5905-83c8-5da8-9951-e8cb7b7459f9" -``` - -Recover a new scratch cluster from the native Scaleway base backup/WAL. -Verify expected Forgejo database and nonempty repository/user/package metadata, -source readiness and scratch cleanup. This is database recovery, not combined -Forgejo blob/application recovery. - -## Establish primary full-archive delivery and application recovery - -```task -id: RPF-WP-0038-T04 -status: wait -priority: high -state_hub_task_id: "a4807df0-ec96-58f1-9cbd-42b1dcde0d6f" -``` - -Choose an independent archive prefix and confirm storage retention/caller -contract, bounded worker credential delivery and multipart transfer/abort. -Then retrieve a primary full archive and execute isolated application recovery. -The existing 5.35 GB encrypted verified archive is available in private staging. -Keep Nextcloud's 10 GiB secondary budget separate. The current helper still -uploads only to Nextcloud until this task's full delivery is implemented and -verified; native database backup alone cannot close the workplan.