Files
SKEEN-Proyecto/docs/producto/operacion-instancia.md

12 KiB
Raw Blame History

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 usuario postgres no puede escribir en /root) y se mueve al destino final con chmod 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.
  • Configuración del host: <db>_<YYYYmmdd_HHMMSS>.config.tar.gz con odoo.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 un MANIFEST.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 con ESTADO GLOBAL: OK/FALLO). Exit code 0 si todo va bien, 1 si alguna instancia falla. Se rota por logrotate (semanal, 8 copias) y, además, el script lo trunca si supera LOG_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.

  1. Mensual (hasta 6): el backup más reciente de cada mes calendario.
  2. Semanal (hasta 8): el más reciente de cada semana ISO no retenido aún.
  3. Diario (hasta 14): el más reciente de cada día no retenido aún.
  4. 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=skeen es importante: hace que los objetos queden del rol skeen (el que usa Odoo). Sin --role, quedarían de postgres y 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 el MANIFEST.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 --yes y 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).