Files
cayce-ops/deploy/backup/RESTORE.md
T
cayce d7156358f6
ci / lint-test-leaks (pull_request) Canceled after 0s
backup: RESTORE.md runbook + E2E restore test evidence (Tanda B, point 3)
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)
2026-10-28 21:07:58 +00:00

6.0 KiB

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)

# 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

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

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

# 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)

# 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

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

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