`planear.py --revisar` sobre el censo ordena lo que queda, y lo primero no eran los servicios del usuario sino TRES CAPACIDADES que ninguna metrica reclama: el disparador periodico, la hora y la rotacion de logs. Los tres paquetes ya estaban sellados y declarados en `perfil.servidor` desde que ese perfil nacio — lo que faltaba era el eslabon que los ARRANCA, igual que con squid. · `chronyd`: la caja iba **15 s atrasada** y nadie la corregia. Ahora stratum 3 contra los NTP de Hetzner, 0,000005 s de NTP. Sin hora no hay TLS ni firmas, y el sintoma no se parece a la causa. · `crond`: no existia el latido. Verificado con una entrada `* * * * *` en el crontab REAL. · `logrotate`: `/var/log/squid` sin rotar. Diario, con `squid -k rotate` (squid mantiene los ficheros abiertos: un rename a secas lo deja escribiendo en un inode que nadie puede leer). · `scripts/respaldo-gitea.sh` + `17 3 * * *`: cierra el hueco del §6.15 — los 44 repos vivian en UNA copia y el respaldo se hacia a mano. Sube 23+21 repos (1,5 G) y un `gitea.db` de 332 M tomado con `.backup`, consistente con el servidor vivo. El control no es que el script salga 0: es CONTAR los repos de los dos lados. `chrony` y `cronie` NO declaraban `[[service]]` — el paquete llegaba a la imagen y no lo arrancaba nadie. Ahora lo declaran (fuera de `hash_inputs`: los hashes no se movieron) y el perfil los habilita. TRES MEDICIONES que valen mas que el resultado: 1. **La deriva era de gioser, no de la caja**: despues de sincronizar, el HUB quedo 3 s adelantado. Medir «contra el otro» sin un tercero no dice quien esta mal. 2. **El spool de cronie es `/var/spool/cron/<usuario>`**, no `…/crontabs/<usuario>`: `crontab -l` mostraba las entradas y `crontabs/` estaba vacio. Un crontab restaurado en el sitio equivocado es un fichero perfecto que nadie lee. 3. 🧨 **`sqlite3` seguia INERTE en la caja** (`compressBound: symbol not found`): el ultimo de los 23 del §6.4. Le faltaba `zlib-shared`, sellado y declarado en `perfil.base` desde el 11-09 — pero la caja se instalo antes. Sin el no habia snapshot consistente. Un paquete declarado no es un paquete instalado: en una caja viva vale lo que el `upgrade` proyecto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
66 lines
3.4 KiB
Bash
Executable File
66 lines
3.4 KiB
Bash
Executable File
#!/bin/sh
|
|
# respaldo-gitea.sh — el respaldo PERIÓDICO del gitea: la sqlite y los repos.
|
|
#
|
|
# ── POR QUÉ EXISTE (SDD 28 §6.15) ───────────────────────────────────────────────────────────────
|
|
# `respaldo-storagebox.sh` respalda el STORE de artefactos, que es lo reconstruible. **Los datos del
|
|
# gitea —44 repos, 26 de ellos sin copia en ningún otro lado— no los cubría nadie**, y estuvieron en
|
|
# UNA sola copia hasta el 2026-09-14, cuando se hizo a mano. Un respaldo que depende de que alguien
|
|
# se acuerde es un respaldo que no existe: esto es lo mismo, con `crond` disparándolo.
|
|
#
|
|
# ── LAS DOS PARTES, Y POR QUÉ LA SQLITE NO SE COPIA A LO BRUTO ──────────────────────────────────
|
|
# Un `cp` de una sqlite viva puede quedar a medias de una transacción y el fichero resultante abre
|
|
# —parece bueno— y le faltan las últimas escrituras. `.backup` del propio sqlite3 toma una copia
|
|
# CONSISTENTE con el servidor corriendo (1,5 s medidos sobre los 2 G de gioser). Los repos en cambio
|
|
# son ficheros que git sólo AÑADE, así que `rsync -aH` alcanza.
|
|
#
|
|
# ⚠ Al Storage Box se entra por el PUERTO 23: el 22 da un SFTP restringido y contesta
|
|
# `Permission denied (publickey,password)` teniendo la clave buena — se lee como «no tengo acceso».
|
|
#
|
|
# Uso: respaldo-gitea.sh (desde la caja que sirve el gitea)
|
|
# DRY=1 respaldo-gitea.sh (dice qué haría y no sube nada)
|
|
set -eu
|
|
|
|
GITEA_DATA="${GITEA_DATA:-/var/lib/gitea}"
|
|
GITEA_ETC="${GITEA_ETC:-/etc/gitea}"
|
|
SB_USER="${SB_USER:-u647150}"
|
|
SB_HOST="${SB_HOST:-u647150.your-storagebox.de}"
|
|
SB_PORT="${SB_PORT:-23}"
|
|
KEY="${KEY:-/root/.ssh/github5}"
|
|
DESTINO="${DESTINO:-gitea}"
|
|
TMP="${TMP:-/work/respaldo-gitea}"
|
|
|
|
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"; }
|
|
|
|
[ -d "$GITEA_DATA" ] || { log "no existe $GITEA_DATA — ¿esta caja sirve el gitea?"; exit 78; }
|
|
command -v sqlite3 >/dev/null || { log "falta sqlite3: la DB se copiaría a lo bruto y eso NO es consistente"; exit 78; }
|
|
[ -f "$KEY" ] || { log "falta la clave $KEY — sin ella no hay Storage Box"; exit 78; }
|
|
|
|
mkdir -p "$TMP"
|
|
DB="$GITEA_DATA/gitea.db"
|
|
if [ -f "$DB" ]; then
|
|
log "snapshot consistente de la sqlite…"
|
|
rm -f "$TMP/gitea.db"
|
|
sqlite3 "$DB" ".backup '$TMP/gitea.db'"
|
|
# Un `.backup` que deja un fichero de 0 bytes es el modo de fallo que más engaña: sube, ocupa
|
|
# lugar y no restaura nada (`CLAUDE.md` regla 3).
|
|
[ -s "$TMP/gitea.db" ] || { log "el snapshot salió VACÍO — no se sube nada"; exit 1; }
|
|
log " $(du -h "$TMP/gitea.db" | cut -f1)"
|
|
else
|
|
log "no hay $DB (¿DB_TYPE distinto de sqlite3?) — sigo con los repos"
|
|
fi
|
|
|
|
SSH_CMD="ssh -4 -p $SB_PORT -i $KEY -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30"
|
|
RS="rsync -aH --partial -e '$SSH_CMD'"
|
|
[ "${DRY:-0}" = "1" ] && RS="$RS --dry-run" && log "DRY=1: no se sube nada"
|
|
|
|
log "subiendo repos y config…"
|
|
eval rsync -aH --partial -e "'$SSH_CMD'" ${DRY:+--dry-run} \
|
|
--exclude 'gitea.db' --exclude 'gitea.db-wal' --exclude 'gitea.db-shm' \
|
|
"$GITEA_DATA/" "$SB_USER@$SB_HOST:$DESTINO/data/"
|
|
eval rsync -aH --partial -e "'$SSH_CMD'" ${DRY:+--dry-run} \
|
|
"$GITEA_ETC/" "$SB_USER@$SB_HOST:$DESTINO/etc/"
|
|
[ -s "$TMP/gitea.db" ] && eval rsync -aH --partial -e "'$SSH_CMD'" ${DRY:+--dry-run} \
|
|
"$TMP/gitea.db" "$SB_USER@$SB_HOST:$DESTINO/gitea.db"
|
|
|
|
log "listo."
|