backup: RESTORE.md runbook + E2E restore test evidence (Tanda B, point 3)
ci / lint-test-leaks (pull_request) Canceled after 0s
ci / lint-test-leaks (pull_request) Canceled after 0s
RESTORE.md documents the exact from-zero recovery on a fresh VPS: download+verify (sha256 over encrypted artifacts, no private key needed on the pull side), decrypt on the operator Mac, Postgres pg_restore, Gitea unzip+migrate, Caddy/systemd/.env config restore, repo re-clone, backup pipeline reinstall with the SAME age recipient, and a post-restore checklist. E2E test executed on the VPS (no production touched — cayce-ops has no production DB; a probe DB was created and dropped): - manifest.sha256: 3/3 artifacts OK (verified WITHOUT any private key) - age decrypt roundtrip with a throwaway keypair (destroyed afterwards) - pg_restore TOC valid; full restore into a scratch DB: 5000/5000 rows - gitea zip valid, config tar valid (34 entries) - all test material removed: probe DB dropped, throwaway key deleted, recipient file removed, test backup deleted (was encrypted to the destroyed throwaway key)
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# RESTORE.md — Restauración de CayceOps desde backups cifrados
|
||||
|
||||
**Modelo:** la VPS genera backups cifrados (age) en `/var/backups/cayce/YYYY-MM-DD/`;
|
||||
tu Mac los descarga con el usuario `backup-pull` (solo lectura). La clave
|
||||
**privada** de age vive SOLO en tu Mac. Este runbook asume una VPS nueva
|
||||
(Ubuntu + IP apuntada por DNS) y tus backups descifrados a mano.
|
||||
|
||||
---
|
||||
|
||||
## 0. Descargar y descifrar (en TU Mac — nunca en la VPS)
|
||||
|
||||
```bash
|
||||
# pull (clave restringida: solo lectura de /var/backups/cayce)
|
||||
rsync -av --partial backup-pull@15.235.3.239:/ ~/backups/cayce/
|
||||
|
||||
# integridad (sin clave privada; manifest lista los artefactos CIFRADOS)
|
||||
cd ~/backups/cayce/2026-XX-XX && shasum -a 256 -c manifest.sha256
|
||||
# (Linux: sha256sum -c manifest.sha256)
|
||||
|
||||
# descifrar (clave privada age del operador — la que NO está en la VPS)
|
||||
age -d -i ~/keys/cayce-backup.txt -o cayce-ops-db.dump cayce-ops-db.dump.age
|
||||
age -d -i ~/keys/cayce-backup.txt -o gitea-dump.zip gitea-dump.zip.age
|
||||
age -d -i ~/keys/cayce-backup.txt -o config.tar.gz config.tar.gz.age
|
||||
```
|
||||
|
||||
Artefactos:
|
||||
| Archivo | Contenido | Formato |
|
||||
|---|---|---|
|
||||
| `cayce-ops-db.dump` | BD de CayceOps | `pg_dump` custom (restaurar con `pg_restore`) |
|
||||
| `gitea-dump.zip` | repos + sqlite + adjuntos + config Gitea | zip de `gitea dump` |
|
||||
| `config.tar.gz` | `caddy/` (Caddyfile+Caddyfile.d), `gitea/` (app.ini + internal_token), `systemd/` (unidades cayce-*), `cayce-ops/dot-env` | tar.gz |
|
||||
|
||||
---
|
||||
|
||||
## 1. VPS nueva — base del sistema
|
||||
|
||||
```bash
|
||||
apt-get update
|
||||
apt-get install -y postgresql age rsync git
|
||||
# Gitea: misma versión mayor que la VPS origen (verSTEP: gitea --version en el viejo
|
||||
# servidor ANTES de apagarlo, o lee la versión del zip gitea-dump: gitea-dump zip
|
||||
# contiene un archivo "app.ini" con la versión en comentarios... anota la versión
|
||||
# manualmente; el binario se baja de https://dl.gitea.com/gitea/)
|
||||
adduser --system --group --home /var/lib/gitea git
|
||||
mkdir -p /etc/gitea /var/lib/gitea /etc/cayce-backup
|
||||
curl -L "https://dl.gitea.com/gitea/<versión>/gitea-<versión>-linux-amd64" -o /usr/local/bin/gitea
|
||||
chmod +x /usr/local/bin/gitea
|
||||
```
|
||||
|
||||
## 2. Postgres
|
||||
|
||||
```bash
|
||||
systemctl enable --now postgresql
|
||||
sudo -u postgres psql <<SQL
|
||||
CREATE ROLE cayce LOGIN PASSWORD '<password-del-.env-restaurado>';
|
||||
CREATE DATABASE cayce_ops OWNER cayce;
|
||||
SQL
|
||||
|
||||
# restaurar el dump (los ficheros descifrados son legibles solo por ti;
|
||||
# cópiales permisos de lectura para postgres o pásalos por stdin)
|
||||
sudo -u postgres pg_restore -d cayce_ops --no-owner --no-privileges \
|
||||
/ruta/cayce-ops-db.dump
|
||||
|
||||
# verificación
|
||||
sudo -u postgres psql -d cayce_ops -tAc \
|
||||
"SELECT count(*) FROM audit_log;" # debe coincidir con la última ancla
|
||||
```
|
||||
|
||||
## 3. Gitea
|
||||
|
||||
```bash
|
||||
# config restaurada desde config.tar.gz
|
||||
tar -xzf config.tar.gz -C /tmp/restored
|
||||
cp /tmp/restored/gitea/app.ini /etc/gitea/app.ini
|
||||
chown root:git /etc/gitea/app.ini && chmod 660 /etc/gitea/app.ini
|
||||
cp /tmp/restored/gitea/internal_token /etc/gitea/ && chown git:git /etc/gitea/internal_token
|
||||
|
||||
# NOTA sobre ROOT_URL: app.ini restaurado apunta a https://git.cayce-co.xyz/ —
|
||||
# el DNS de ese subdominio debe existir ANTES de iniciar Gitea o las URLs
|
||||
# absolutas (webhooks, notificaciones) quedarán mal.
|
||||
|
||||
# dump de Gitea: contiene data/, repos/ y custom/. Restaurar SOBRE el dump
|
||||
# de Gitea exige la misma versión del binario; descomprimir dentro de WORK_PATH
|
||||
systemctl stop gitea 2>/dev/null || true
|
||||
unzip -o gitea-dump.zip -d /tmp/gitea-restore
|
||||
rsync -a /tmp/gitea-restore/data/ /var/lib/gitea/data/
|
||||
rsync -a /tmp/gitea-restore/repos/ /var/lib/gitea/data/gitea-repositories/ 2>/dev/null || true
|
||||
rsync -a /tmp/gitea-restore/custom/ /var/lib/gitea/custom/ 2>/dev/null || true
|
||||
chown -R git:git /var/lib/gitea
|
||||
sudo -u git /usr/local/bin/gitea migrate -c /etc/gitea/app.ini # ajusta esquema si cambia versión
|
||||
```
|
||||
|
||||
> **Consistencia:** los dumps de Gitea se toman EN CALIENTE (el servicio sigue
|
||||
> corriendo). Riesgo teórico de inconsistencia sqlite/repos si un push ocurre
|
||||
> en el mismo instante. Para un backup garantizado-consistente: `systemctl stop
|
||||
> gitea` antes de `gitea dump` y volver a levantar después (no automatizado a
|
||||
> propósito: no queremos ventanas de downtime diarias).
|
||||
|
||||
## 4. Config del sistema (Caddy, unidades systemd, .env)
|
||||
|
||||
```bash
|
||||
# Caddy
|
||||
apt-get install -y caddy # o el instalador oficial
|
||||
rsync -a /tmp/restored/caddy/Caddyfile /etc/caddy/Caddyfile
|
||||
rsync -a /tmp/restored/caddy/Caddyfile.d/ /etc/caddy/Caddyfile.d/
|
||||
systemctl enable --now caddy # emitirá certificados si el DNS ya apunta aquí
|
||||
|
||||
# unidades systemd de cayce (DESHABILITADAS por diseño — ver riesgos)
|
||||
rsync -a /tmp/restored/systemd/ /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
# NO habilitar cayce-ops@*.timer sin el operador
|
||||
|
||||
# .env de cayce-ops
|
||||
install -d -m 755 /home/ubuntu
|
||||
rsync -a /tmp/restored/cayce-ops/dot-env /home/ubuntu/cayce-ops/.env
|
||||
chown ubuntu:ubuntu /home/ubuntu/cayce-ops/.env && chmod 660 /home/ubuntu/cayce-ops/.env
|
||||
```
|
||||
|
||||
## 5. Código del repo
|
||||
|
||||
```bash
|
||||
sudo -u ubuntu git clone http://127.0.0.1:3100/cayce/cayce-ops.git /home/ubuntu/cayce-ops \
|
||||
|| git clone https://git.cayce-co.xyz/cayce/cayce-ops.git /home/ubuntu/cayce-ops
|
||||
cd /home/ubuntu/cayce-ops && uv sync
|
||||
make check # gate verde antes de considerar la restauración completa
|
||||
```
|
||||
|
||||
## 6. Reinstalar el pipeline de backup en la VPS nueva
|
||||
|
||||
```bash
|
||||
install -m 700 -o root -g root deploy/backup/make-backup.sh /usr/local/bin/cayce-make-backup
|
||||
install -m 644 deploy/systemd/cayce-backup.{service,timer} /etc/systemd/system/
|
||||
systemctl daemon-reload && systemctl enable --now cayce-backup.timer
|
||||
# la MISMA clave pública age → tu Mac sigue pudiendo descifrar:
|
||||
echo 'age1<tu-pubkey>' > /etc/cayce-backup/age-recipient.pub
|
||||
```
|
||||
|
||||
## 7. Checklist de verificación post-restauración
|
||||
|
||||
- [ ] `sha256sum -c manifest.sha256` OK en tu Mac (antes de descifrar)
|
||||
- [ ] `pg_restore --list` del dump sin errores
|
||||
- [ ] `verify_audit_chain(sess)` → `(True, None)` contra la BD restaurada
|
||||
- [ ] Gitea responde por TLS en `git.cayce-co.xyz` y `git rev-parse main` da el commit esperado
|
||||
- [ ] `make check` verde en el repo restaurado
|
||||
- [ ] Primer backup nuevo generado y verificado desde la Mac
|
||||
Reference in New Issue
Block a user