12 KiB
Operación de instancias SKEEN (backups + panel)
Guía de operación comercial de la VPS: cómo funcionan los backups automáticos verificados de cada instancia Odoo y cómo usar el panel/CLI multi-instancia.
Todo vive en infra/ops/:
| Archivo | Para qué |
|---|---|
backup_instancias.sh |
Backup por instancia: dump + config, verificación de integridad, rotación y (opcional) prueba de restauración. |
backup.env |
Configuración central: destinos, credenciales de DB, retención, log. |
admin_instancias.py |
Panel/CLI multi-instancia (read-only por defecto). |
systemd/ |
Unidades y timers (skeen-backup*, backup-instancia@). Se copian a /etc/systemd/system/. |
logrotate/skeen-backup |
Rotación del log; se copia a /etc/logrotate.d/skeen-backup. |
Las instancias no se listan a mano: se descubren desde las unidades
odoo-*.service. Por cada unidad se lee su -c <conf> (de systemctl cat) y de
ahí db_name, http_port, logfile y data_dir. Añadir una instancia nueva
con infra/provision/provision_clinica.py la incluye automáticamente en backups
y panel, sin editar nada de ops.
1. Backups automáticos
Qué se respalda
Por cada instancia, en /root/backups/<slug>/:
- Base de datos:
pg_dump -Fc→<db>_<YYYYmmdd_HHMMSS>.dump.- Se vuelca primero a
/tmp(el usuariopostgresno puede escribir en/root) y se mueve al destino final conchmod 600 root:root. - Se verifica la integridad con
pg_restore --list; si falla, el dump se marca como corrupto, se borra y se reporta como error.
- Se vuelca primero a
- Configuración del host:
<db>_<YYYYmmdd_HHMMSS>.config.tar.gzconodoo.conf, la unidad systemd, el vhost nginx (buscado por slug o por puerto) y los directorios de marca del frontend (/var/www/<slug>-frontend/brand,/var/www/<slug>-frontend/skeen-brand,/var/www/skeen-frontend/skeen-brand). Incluye unMANIFEST.txt. Pesa unos pocos MB por las tipografías de marca.
Comando
/root/skeen-proyecto/infra/ops/backup_instancias.sh # todas las instancias
/root/skeen-proyecto/infra/ops/backup_instancias.sh --instancia skeen
/root/skeen-proyecto/infra/ops/backup_instancias.sh --verificar # + prueba de restauración
/root/skeen-proyecto/infra/ops/backup_instancias.sh --solo-db # sin config
/root/skeen-proyecto/infra/ops/backup_instancias.sh --dry-run # no escribe nada
/root/skeen-proyecto/infra/ops/backup_instancias.sh --help
| Flag | Efecto |
|---|---|
--instancia <slug> |
Solo esa instancia. |
--solo-db / --sin-config |
No respalda la configuración del host. |
--verificar |
Además restaura el dump a una DB temporal y compara conteos (ver abajo). |
--dry-run |
Muestra qué haría (incluida la rotación) sin tocar nada. |
--backup-root <dir> |
Destino alternativo (pruebas / recuperación). |
- Lockfile:
/var/lock/skeen-backup.lock(flock). Si ya hay un backup en curso, la segunda ejecución sale con exit 3 sin hacer nada. - Log:
/var/log/skeen-backup.log(una línea por evento y un resumen final conESTADO GLOBAL: OK/FALLO). Exit code0si todo va bien,1si alguna instancia falla. Se rota por logrotate (semanal, 8 copias) y, además, el script lo trunca si superaLOG_MAX_BYTES(5 MiB).
Programación (systemd)
| Unidad | Cuándo | Qué corre |
|---|---|---|
skeen-backup.timer |
diario ~03:20 (RandomizedDelaySec=1800) |
skeen-backup.service → todas las instancias |
skeen-backup-verify.timer |
domingos ~04:40 | skeen-backup-verify.service → todas con --verificar |
backup-instancia@.timer |
diario ~03:40 (plantilla, no habilitada) | backup-instancia@<slug>.service → una instancia concreta |
Los timers tienen Persistent=true: si la VPS estaba apagada a la hora prevista,
el backup corre al arrancar.
Política de retención
Configurable en backup.env (RETAIN_DAILY, RETAIN_WEEKLY, RETAIN_MONTHLY).
Por defecto: 14 diarios / 8 semanales / 6 mensuales.
Es un esquema abuelo-padre-hijo con buckets exclusivos: un archivo que ya quedó retenido como mensual no vuelve a contar para semanal, y así sucesivamente.
- Mensual (hasta 6): el backup más reciente de cada mes calendario.
- Semanal (hasta 8): el más reciente de cada semana ISO no retenido aún.
- Diario (hasta 14): el más reciente de cada día no retenido aún.
- Todo lo demás se borra.
El backup más reciente nunca se borra (el "último bueno"), y por
construcción tampoco se borra el representante mensual. Cuando se borra un dump,
se borra también su .config.tar.gz gemelo (misma marca de tiempo).
Consecuencia útil: varios backups del mismo día no se acumulan sin límite; se conservan a lo sumo uno por nivel (mensual/semanal/diario).
2. Verificación
Integridad (siempre)
Cada dump se valida con pg_restore --list antes de considerarse bueno. Para
verificar a mano:
for f in /root/backups/*/*.dump; do pg_restore --list "$f" >/dev/null && echo "OK $f" || echo "FALLA $f"; done
Prueba de restauración real (--verificar)
Restaura el dump recién creado a una DB temporal <db>_verify_<fecha>, cuenta
filas de tablas clave (res_partner, skeen_cita, skeen_venta), compara con
la original y borra la temporal:
/root/skeen-proyecto/infra/ops/backup_instancias.sh --instancia clinica2 --verificar
- Requiere espacio libre en el filesystem de PostgreSQL de al menos
VERIFY_MIN_FREE_FACTOR(2) × tamaño de la base; si no lo hay, la omite con un aviso en el log (no falla el backup). - La restauración corre por peer como
postgres(superusuario), de modo que extensiones y objetos se recrean sin problemas; la prueba real es la comparación de conteos.
3. Restaurar un backup (procedimiento)
Las DB y sus tablas son propiedad del rol skeen. Restaura siempre a una DB
nueva primero y solo luego promueve, para no dejar la instancia sin datos si el
dump tuviera un problema.
DUMP=/root/backups/clinica2/clinica2_odoo_20261011_093633.dump
SLUG=clinica2
DB=clinica2_odoo
Paso 1 — Restaurar a una DB nueva y comparar
sudo -u postgres createdb -T template0 -O skeen "${DB}_restore"
sudo -u postgres pg_restore --role=skeen --no-privileges -d "${DB}_restore" "$DUMP"
# comparar conteos (deben coincidir)
sudo -u postgres psql -d "$DB" -Atc "select count(*) from res_partner"
sudo -u postgres psql -d "${DB}_restore" -Atc "select count(*) from res_partner"
Paso 2 — Promover la DB restaurada (con parada del servicio)
systemctl stop odoo-${SLUG}.service
# cortar conexiones y renombrar
sudo -u postgres psql -Atc "select pg_terminate_backend(pid) from pg_stat_activity \
where datname='${DB}' and pid <> pg_backend_pid()"
sudo -u postgres psql -c "ALTER DATABASE ${DB} RENAME TO ${DB}_pre_restore"
sudo -u postgres psql -c "ALTER DATABASE ${DB}_restore RENAME TO ${DB}"
systemctl start odoo-${SLUG}.service
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:<puerto>/web/login
Paso 3 — Limpieza (cuando ya validaste)
sudo -u postgres dropdb "${DB}_pre_restore"
Restauración de emergencia sobre la DB viva (solo si no puedes parar y la DB está corrupta; pierde los datos posteriores al dump):
systemctl stop odoo-${SLUG}.service
sudo -u postgres dropdb "${DB}" # o ALTER DATABASE ... RENAME
sudo -u postgres createdb -T template0 -O skeen "${DB}"
sudo -u postgres pg_restore --role=skeen --no-privileges -d "${DB}" "$DUMP"
systemctl start odoo-${SLUG}.service
El
pg_restore --role=skeenes importante: hace que los objetos queden del rolskeen(el que usa Odoo). Sin--role, quedarían depostgresy el backend podría no tener permisos.
La configuración (
*.config.tar.gz) se restaura descomprimiendo el tar y copiando cada archivo a su ruta (están listados en elMANIFEST.txt).
4. Panel / CLI multi-instancia
admin_instancias.py no requiere dependencias externas y descubre las
instancias igual que el backup. Por defecto es de solo lectura.
cd /root/skeen-proyecto/infra/ops
./admin_instancias.py listar # tabla (subcomando por defecto)
./admin_instancias.py --json listar # JSON para automatización
| Subcomando | Qué muestra |
|---|---|
listar |
slug, unit, estado del servicio, puerto, DB, HTTP en /web/login, nº de módulos skeen_*, último backup (fecha/tamaño), nº de backups, uso de disco de backups y datadir, y si el puerto escucha. |
estado <slug> |
Detalle: conf, DB, puerto, HTTP, log, data_dir, vhost, frontend, versión de cada módulo skeen_*, backups. |
version |
Versión instalada de cada módulo skeen_*, en tabla por instancia. |
uso |
Disco, RAM, carga del host y tabla de procesos Odoo (CPU/RAM/RSS/uptime/conf). |
instalacion <slug> |
Valida que la instancia esté completa: DB, unit activa, conf, vhost, puerto, HTTP, frontend desplegado y marca configurada. Lista lo que falta. |
logs <slug> [-n N] |
Tail del log de Odoo de esa instancia y del log de backups. |
credenciales <slug> [--mostrar] |
Ruta del archivo de credenciales del provisioning. Nunca imprime passwords sin --mostrar. |
backup <slug> [--verificar] --yes |
Lanza el script de backup de esa instancia. |
reiniciar <slug> --yes |
systemctl restart. Comprueba el lock y advierte que interrumpe el servicio. |
--json(global o por subcomando) da salida JSON.- Las acciones que cambian estado (
backup,reiniciar) exigen--yesy avisan qué harán antes de hacerlo.
5. Qué hacer si algo falla
| Síntoma | Diagnóstico | Acción |
|---|---|---|
| Disco lleno | ./admin_instancias.py uso; df -h / |
Bajar retención en backup.env (p. ej. RETAIN_DAILY=7), borrar backups viejos de /root/backups/<slug>/, revisar /var/log/skeen-backup.log.1.gz. La verificación se auto-omite si no hay espacio. |
| Dump corrupto | En el log: dump CORRUPTO (...). Comprobar con pg_restore --list. |
El script ya borra el dump corrupto. Revisar espacio en /tmp y salud de PostgreSQL; volver a lanzar el backup. |
| Backup no se ejecuta | systemctl list-timers | grep skeen; journalctl -u skeen-backup.service -n 50; tail /var/log/skeen-backup.log |
Verificar el timer (enabled) y que el lock no esté tomado por un proceso colgado: fuser /var/lock/skeen-backup.lock. |
| Servicio caído | ./admin_instancias.py estado <slug>; journalctl -u odoo-<slug>.service -n 100 |
Reiniciar con ./admin_instancias.py reiniciar <slug> --yes (o systemctl restart odoo-<slug>). Si la DB quedó corrupta, restaurar (sección 3). |
| Instancia incompleta | ./admin_instancias.py instalacion <slug> |
Completar lo que falte (vhost, frontend, marca) o re-provisionar con infra/provision/provision_clinica.py. |
| Restauración de emergencia | La instancia no arranca y el dump más reciente es válido | Sección 3, "Restauración de emergencia". Antes de tocar nada, copia el directorio de datos actual (/var/lib/odoo*) por si acaso. |
| No llegan backups de una instancia nueva | ./admin_instancias.py listar no la ve |
Comprobar que existe su odoo-<slug>.service y que -c <conf> apunta a un conf con db_name. |
6. Rutas y estado instalado
| Recurso | Ruta |
|---|---|
| Scripts | /root/skeen-proyecto/infra/ops/ |
| Config | /root/skeen-proyecto/infra/ops/backup.env (chmod 600) |
| Backups | /root/backups/<slug>/ (<db>_<fecha>.dump, <db>_<fecha>.config.tar.gz) |
| Log | /var/log/skeen-backup.log (+ rotación logrotate) |
| Lock | /var/lock/skeen-backup.lock |
| Units | /etc/systemd/system/skeen-backup*.{service,timer}, backup-instancia@.{service,timer} |
| Logrotate | /etc/logrotate.d/skeen-backup |
7. Componentes opcionales (Docker)
La VPS también corre Supabase self-hosted y WACRM en Docker. No forman
parte del backup por instancia; se manejan aparte. En backup.env,
SUPABASE_BACKUP y WACRM_BACKUP están documentados y en 0. Los backups
manuales previos de esos componentes viven en /root/backups/*.sql
(supabase_skeen_backup_*.sql, wacrm_conversations_*.sql).