Compare commits
No commits in common. "54819fb3f48474bf0142038a9c20a7708a8adc2c" and "63b5d6d4261ec2b25c208200eac6f46c5c2e5a12" have entirely different histories.
54819fb3f4
...
63b5d6d426
6 changed files with 2 additions and 193 deletions
2
Makefile
2
Makefile
|
|
@ -75,8 +75,6 @@ db-logs: ## Tail gitea-db primary logs
|
||||||
forgejo-db-deploy: ## Apply forgejo-db cnpg Cluster + NetworkPolicies on railiance01
|
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-cluster.yaml
|
||||||
$(KUBECTL) apply -f helm/forgejo-db-networkpolicies.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
|
forgejo-db-status: ## Show forgejo-db cnpg cluster health
|
||||||
$(KUBECTL) cnpg status forgejo-db -n databases 2>/dev/null || \
|
$(KUBECTL) cnpg status forgejo-db -n databases 2>/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}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# Apply: KUBECONFIG=~/.kube/config-hosteurope make forgejo-db-deploy
|
# Apply: KUBECONFIG=~/.kube/config-hosteurope make forgejo-db-deploy
|
||||||
# Status: make forgejo-db-status
|
# 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.
|
# Pre-condition: forgejo-db-credentials Secret in databases namespace.
|
||||||
# See helm/forgejo-db-secret.sops.yaml.template
|
# See helm/forgejo-db-secret.sops.yaml.template
|
||||||
|
|
@ -49,22 +49,4 @@ spec:
|
||||||
database: forgejo
|
database: forgejo
|
||||||
owner: forgejo
|
owner: forgejo
|
||||||
secret:
|
secret:
|
||||||
name: forgejo-db-credentials
|
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
|
|
||||||
|
|
@ -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())
|
|
||||||
|
|
@ -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.
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue