2026-08-23 13:13:13 +02:00
|
|
|
# S1 encrypted backup and isolated recovery
|
|
|
|
|
|
|
|
|
|
This repository can collect only the declared S1 operating-system
|
|
|
|
|
configuration and Debian package-selection evidence. It does not back up
|
|
|
|
|
Kubernetes state, databases, Forgejo, application data, user homes, secrets,
|
|
|
|
|
or private keys. The machine-readable boundary is
|
|
|
|
|
[`spec/s1-backup.yaml`](../spec/s1-backup.yaml).
|
|
|
|
|
|
|
|
|
|
## Safe source gate
|
|
|
|
|
|
|
|
|
|
Run this without root, host access, or an age private key:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
make validate-s1-backup
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The declaration must agree exactly with the public age recipients in
|
|
|
|
|
`.sops.yaml`. Fixture tests prove encrypted atomic publication, failure
|
|
|
|
|
cleanup, receipt integrity, isolated restore, traversal rejection, freshness,
|
|
|
|
|
and exact retention approval. This command never installs or starts a timer.
|
|
|
|
|
|
|
|
|
|
## Backup and status
|
|
|
|
|
|
|
|
|
|
On a host, root can create one bundle:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
sudo tools/cmd/railiance-backup-s1
|
|
|
|
|
make s1-backup-status
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
A final `s1-backup-YYYYMMDDTHHMMSSZ/` directory appears only after both
|
|
|
|
|
artifacts are encrypted and the manifest and receipt are durable. The receipt
|
|
|
|
|
contains hashes, sizes, host identity, source revision, declaration digest,
|
|
|
|
|
and the retention decision, but no decrypted content or secret material.
|
|
|
|
|
Status fails for missing, stale, over-budget, over-retained, or tampered newest
|
|
|
|
|
evidence.
|
|
|
|
|
|
|
|
|
|
Retention is deliberately not an unattended delete. Render the current exact
|
|
|
|
|
candidate set, review it, and pass back the one-use approval derived from the
|
|
|
|
|
candidate receipt hashes:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
make s1-backup-prune-plan
|
|
|
|
|
make s1-backup-prune APPROVE_S1_BACKUP_PRUNE=PRUNE-S1-BACKUPS-...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If the set changes between those commands, pruning fails closed. Candidates
|
|
|
|
|
are atomically moved beneath a quarantine directory inside the declared backup
|
2026-08-23 14:11:00 +02:00
|
|
|
root before removal; paths outside that root are never accepted. Production
|
|
|
|
|
pruning also refuses every candidate that lacks a passing off-site receipt
|
|
|
|
|
bound to the current local manifest.
|
|
|
|
|
|
|
|
|
|
## Governed off-site owner interface
|
|
|
|
|
|
|
|
|
|
The upload contract is source-controlled in `spec/s1-offsite.yaml`. It packages
|
|
|
|
|
the two encrypted artifacts and their metadata into one deterministic tar
|
|
|
|
|
envelope, avoiding a remotely visible partial multi-object backup. Upload uses
|
|
|
|
|
an immutable object name and `If-None-Match: *`; a collision fails without a
|
|
|
|
|
success receipt. Neither the endpoint, response body, nor credential is placed
|
|
|
|
|
in the receipt.
|
|
|
|
|
|
|
|
|
|
The contract is intentionally pending `railiance-platform` acceptance. Render
|
|
|
|
|
the exact review phrase without credentials:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
make s1-offsite-review
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
After the owner records acceptance of that exact digest, a fresh bundle can be
|
|
|
|
|
planned without access to the lane:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
make s1-offsite-plan BUNDLE=/opt/backup/railiance/infra/s1-backup-...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Only the exact resulting envelope approval can enter the contained upload
|
|
|
|
|
path. `warden access` injects the primary token into the child environment; it
|
|
|
|
|
is never accepted in argv, files, State Hub, or command output:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
make s1-offsite-upload \
|
|
|
|
|
BUNDLE=/opt/backup/railiance/infra/s1-backup-... \
|
|
|
|
|
APPROVE_S1_OFFSITE_UPLOAD=UPLOAD-S1-OFFSITE-...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This command remains disabled while
|
|
|
|
|
`spec/s1-offsite-owner-acceptance.yaml` is pending. Owner acceptance must also
|
|
|
|
|
confirm that the secret-shaped default discovered in the owner repository is
|
|
|
|
|
retired or rotated; its value must never be copied into this repository or an
|
|
|
|
|
approval message. The current route still lacks an authoritative workload
|
|
|
|
|
identity, so the reviewed interface is attended and is not attached to the
|
|
|
|
|
systemd timer.
|
2026-08-23 13:13:13 +02:00
|
|
|
|
|
|
|
|
## Inspection and isolated restore
|
|
|
|
|
|
|
|
|
|
Inspection verifies receipt, manifest, declaration, encrypted hashes, sizes,
|
|
|
|
|
and membership without a private key:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
make s1-restore-inspect BUNDLE=/opt/backup/railiance/infra/s1-backup-...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
An attended operator may then extract into an explicit absent or empty staging
|
|
|
|
|
directory outside this repository:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
make s1-restore-isolated \
|
|
|
|
|
BUNDLE=/opt/backup/railiance/infra/s1-backup-... \
|
|
|
|
|
DEST=/tmp/s1-restore-drill \
|
|
|
|
|
IDENTITY=/operator-controlled/path/identity.age
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The command suppresses decryption output, validates every archive member and
|
|
|
|
|
mode before extraction, rejects escaping links and traversal, and writes a
|
|
|
|
|
metadata-only restore receipt. It refuses `/`, `/etc`, `/home`, `/root`,
|
|
|
|
|
`/var`, repository paths, and non-empty destinations. There is no live restore
|
|
|
|
|
mode.
|
|
|
|
|
|
|
|
|
|
## Timer deployment approval interface
|
|
|
|
|
|
|
|
|
|
The timer runs daily at 02:15 UTC with up to 30 minutes of jitter. Its service
|
|
|
|
|
can write only the declared backup root under systemd's filesystem controls.
|
|
|
|
|
Deployment is a host mutation and is separate from bootstrap and verification:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
make s1-backup-deploy \
|
|
|
|
|
HOST=Railiance01 \
|
|
|
|
|
APPROVE_S1_BACKUP_DEPLOY=DEPLOY-RAIL-HO-WP-0012-S1-BACKUP-TIMER
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The Make target also refuses a dirty source tree. The playbook independently
|
|
|
|
|
checks the approval and pins the deployed wrapper to the reviewed 40-character
|
|
|
|
|
source revision. This approval installs and enables the timer only; it does
|
|
|
|
|
not authorize pruning, off-host upload, private-key access, or a restore.
|
|
|
|
|
|
2026-08-23 14:11:00 +02:00
|
|
|
Off-host execution remains pending under `RAIL-HO-WP-0012-T05` and belongs to
|
|
|
|
|
the governed `railiance-backup-offsite-lane`. An attended off-host isolated
|
|
|
|
|
drill remains `RAIL-HO-WP-0012-T06`; neither live action is implied here.
|