Implement governed S1 backup recovery loop
Some checks failed
CI Smoke / source-contract (push) Failing after 9s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02994-7685-7940-bf34-3555b8256018
This commit is contained in:
codex 2026-08-23 13:13:13 +02:00
parent 40e295e3bd
commit 295bf43d54
16 changed files with 1623 additions and 95 deletions

View file

@ -6,6 +6,7 @@ SHELL := /usr/bin/env bash
GITEA ?= gitea.example.com
OWNER ?= coulomb
REPO ?= railiance-infra
SOURCE_REVISION ?= $(shell git rev-parse HEAD 2>/dev/null)
# New-host defaults (can be overridden: make new-host NAME=... TYPE=...)
TYPE ?= cpx11
@ -146,6 +147,36 @@ tf-providers-plan: ## Plan after an upgrade (uses HCLOUD_TOKEN if set)
backup: ## Backup S1 OS config to /opt/backup/railiance/infra/ (age-encrypted, root required)
sudo tools/cmd/railiance-backup-s1
validate-s1-backup: ## Validate S1 backup declaration and recovery implementation without host changes
python3 scripts/s1_backup.py check
python3 -m unittest tests.test_s1_backup_recovery -v
s1-backup-status: ## Check newest local S1 backup integrity, freshness, count, and disk budget
python3 scripts/s1_backup.py status
s1-backup-prune-plan: ## Print retained bundles and their exact one-use deletion approval
python3 scripts/s1_backup.py prune-plan
s1-backup-prune: ## Apply reviewed prune plan: APPROVE_S1_BACKUP_PRUNE=PRUNE-S1-BACKUPS-...
@test -n "$(APPROVE_S1_BACKUP_PRUNE)" || (echo "Run make s1-backup-prune-plan, then pass its exact approval"; exit 1)
python3 scripts/s1_backup.py prune --approval "$(APPROVE_S1_BACKUP_PRUNE)"
s1-restore-inspect: ## Verify encrypted bundle metadata without a private key: BUNDLE=/absolute/path
@test -n "$(BUNDLE)" || (echo "Usage: make s1-restore-inspect BUNDLE=/absolute/path/to/s1-backup-*"; exit 1)
python3 scripts/s1_restore.py "$(BUNDLE)"
s1-restore-isolated: ## Decrypt only into explicit empty staging: BUNDLE=... DEST=/tmp/... IDENTITY=...
@test -n "$(BUNDLE)" && test -n "$(DEST)" && test -n "$(IDENTITY)" || (echo "Usage: make s1-restore-isolated BUNDLE=... DEST=/tmp/... IDENTITY=/path/to/age-identity"; exit 1)
python3 scripts/s1_restore.py "$(BUNDLE)" --extract-to "$(DEST)" --identity "$(IDENTITY)"
s1-backup-deploy: ## Deploy and enable timer: HOST=... APPROVE_S1_BACKUP_DEPLOY=DEPLOY-RAIL-HO-WP-0012-S1-BACKUP-TIMER
@test -n "$(HOST)" || (echo "Usage: make s1-backup-deploy HOST=Railiance01 APPROVE_S1_BACKUP_DEPLOY=DEPLOY-RAIL-HO-WP-0012-S1-BACKUP-TIMER"; exit 1)
@test "$(APPROVE_S1_BACKUP_DEPLOY)" = "DEPLOY-RAIL-HO-WP-0012-S1-BACKUP-TIMER" || (echo "Refusing deployment: exact approval is absent"; exit 1)
@git diff --quiet && git diff --cached --quiet || (echo "Refusing deployment: commit the exact source first"; exit 1)
cd ansible && ansible-playbook playbooks/s1-backup.yaml --limit "$(HOST)" \
-e railiance_backup_deploy_approval="$(APPROVE_S1_BACKUP_DEPLOY)" \
-e railiance_backup_source_revision="$(SOURCE_REVISION)"
# ---- Ansible ----
ansible-bootstrap: ## Run base bootstrap play (users, ssh, ufw, sops-agent, custodian-agent)
cd ansible && ansible-playbook playbooks/bootstrap.yaml $(ANSIBLE_USER_FLAG)