fix(release): make deployment backup and rollback immutable
This commit is contained in:
@@ -89,9 +89,9 @@ The normal project lifecycle archive path does not need destructive
|
||||
confirmation because it changes only `status=archived` and preserves all
|
||||
data.
|
||||
|
||||
## Destructive apply gate
|
||||
## Recoverable quarantine gate
|
||||
|
||||
First create a fresh backup with a SHA-256 storage inventory on the host:
|
||||
First create a fresh backup with a byte-complete SHA-256 storage snapshot on the host:
|
||||
|
||||
```bash
|
||||
bash scripts/backup_release_state.sh \
|
||||
@@ -112,7 +112,8 @@ bash scripts/verify_release_backup.sh \
|
||||
```
|
||||
|
||||
The Unraid runtime mounts `GEOINTEL_BACKUPS_PATH` read-only at `/app/backups`.
|
||||
Only after reviewing the dry run may an operator execute:
|
||||
Only after reviewing the dry run may an operator move the exact candidates to
|
||||
a protected quarantine:
|
||||
|
||||
```bash
|
||||
docker exec geointel python /app/scripts/cleanup_storage_artifacts.py \
|
||||
@@ -120,14 +121,34 @@ docker exec geointel python /app/scripts/cleanup_storage_artifacts.py \
|
||||
--max-delete <reviewed-candidate-count> \
|
||||
--backup-dir /app/backups/<release-id> \
|
||||
--backup-max-age-hours 24 \
|
||||
--confirm DELETE_STORAGE_ARTIFACTS \
|
||||
--confirm QUARANTINE_STORAGE_ARTIFACTS \
|
||||
--apply
|
||||
```
|
||||
|
||||
The command re-runs the audit immediately before deletion. It refuses the
|
||||
The command re-runs the audit immediately before quarantine. It refuses the
|
||||
operation when the exact token is absent, the candidate count exceeds the
|
||||
operator limit, the backup is stale/incomplete, checksums differ, the storage
|
||||
inventory is not SHA-256, or the path is outside the cleanup allowlist.
|
||||
operator limit, the backup is stale/incomplete, snapshot checksums differ, or
|
||||
the path is outside the cleanup allowlist.
|
||||
Every moved byte is hashed and retained below
|
||||
`operator-evidence/cleanup-quarantine/<operation-id>/files/`; an atomic
|
||||
manifest records its original path, quarantine path, size, checksum and
|
||||
backup identity. Cleanup uses a backup-to-quarantine hard-link state machine:
|
||||
the manifest records `planned`, `linked` and `quarantined` transitions so each
|
||||
crash window can be reconciled without losing the retained inode.
|
||||
|
||||
Restore a reviewed quarantine without overwriting any path:
|
||||
|
||||
```bash
|
||||
docker exec geointel python /app/scripts/restore_storage_quarantine.py \
|
||||
--storage-root /app/storage \
|
||||
--manifest /app/storage/operator-evidence/cleanup-quarantine/<operation-id>/manifest.json \
|
||||
--confirm RESTORE_QUARANTINED_ARTIFACTS
|
||||
```
|
||||
|
||||
Restore validates every retained checksum and never replaces an existing
|
||||
destination. It uses an exclusive hard link and reconciles both-file and
|
||||
one-file interruption states before updating the manifest, so an interrupted
|
||||
restore can be resumed and verified.
|
||||
|
||||
The older demo-export cleanup has the same gate and uses confirmation token
|
||||
`DELETE_DEMO_EXPORTS`.
|
||||
|
||||
Reference in New Issue
Block a user