Skip to content

v0.3.3 — instalador sin SSH y gate de migraciones

Choose a tag to compare

@orl92 orl92 released this 08 Nov 14:49
· 66 commits to main since this release
a39350d

Instalador sin SSH

El contenedor se entrega a un proveedor que prohíbe SSH entrante. install.sh deja de generar el par /etc/webcmp/deploy_key*, deja de escribir authorized_keys y deja de pedir DEPLOY_SSH_KEY / DEPLOY_SSH_KNOWN_HOSTS.

La actualización real es el botón "Actualizar" del panel: deploy/autodeploy.sh resuelve refs/heads/main por HTTPS y aplica el commit localmente. Se conservan el usuario deploy, el sudoers y /usr/local/sbin/webcmp-deploy porque ese botón los usa.

sshd no se deshabilita solo: el instalador puede estar corriendo sobre esa misma sesión. Queda documentado como paso del operador.

Respaldo antes de migrar

Si migrate --check reporta migraciones pendientes, webcmp-deploy toma un pg_dump --format=custom en /var/backups/webcmp/db/ (root:root 0700/0600) antes de tocar el esquema, conserva DB_BACKUP_KEEP (default 10) e imprime el comando de restauración.

Si el respaldo falla, el deploy aborta. Sin migraciones pendientes no se respalda nada: el pg_dump de una base grande no es gratis.

La restauración va como root, no como el usuario de la base: el dump vive dentro de un directorio 0700, así que el usuario de la aplicación no puede leerlo.

Gate de migraciones en dev -> main

scripts/check_migrations.py analiza el AST de las migraciones del PR y falla si:

  • la migración agrega operaciones destructivas o que el script no puede clasificar;
  • se modifica, borra o renombra una migración ya publicada — esto no tiene escape;
  • el escape auditado # data-safety: reviewed: <motivo> sólo cubre operaciones conocidas. Lo que no se puede leer o clasificar bloquea siempre.

Y el gate ahora se exige de verdad. El job corría, pero el ruleset de main no lo tenía entre sus status checks obligatorios: GitHub lo ignoraba al decidir si el PR se podía mergear. Es la novena comprobación requerida a partir de esta versión. El ruleset no tiene actores de excepción, así que nadie puede saltárselo.

El día local se decide en Python

Los lookups __date compilan a (col AT TIME ZONE 'America/Havana')::date: la zona se resolvía dentro de PostgreSQL, con el tzdata de la imagen de la base. timezone.localdate() la resuelve en Python, con el tzdata del paquete. Cuando ambos discrepan, el mismo instante cae en fechas distintas según el lado.

Ya había roto producción: tzdata 2026.6 decía -04:00 y PostgreSQL 18 decía -05:00, así que las cuatro páginas públicas del tiempo respondían "Nada que mostrar" durante horas.

Se unifica en apps.core.utils.local_day_window(day), que devuelve [inicio, fin) en instantes con zona. La usan los tres sitios que filtran por fecha local: el reporte del tiempo, el conteo active_today del dashboard y el filtro de rango de la auditoría. La base sólo compara timestamptz.

Instalación

curl -fsSL -o /tmp/install.sh https://raw.githubusercontent.com/cmp-camaguey/webcmp/v0.3.3/deploy/install.sh \
  && sudo bash /tmp/install.sh

No uses curl ... | sudo bash: en el pipe no hay TTY y el guard del instalador aborta antes de tocar nada. El pipe sólo está soportado para --help y --non-interactive.

Respuestas obligatorias cuando el TLS lo termina un NPM por delante:

  • USE_WWW → si
  • PROXY_MODE → 2 (o external)

Con external no se preguntan TLS_MODE ni ACME_EMAIL y certbot nunca corre.

Verificación

apps.core + apps.dashboard + apps.home + apps.meteo      827 tests OK
mismo conjunto con PYTHONTZPATH=tzdata 2026.6            827 tests OK
bash deploy/tests/run.sh                                 106 ok, 0 falla(s)
test_check_migrations_script                             21 tests OK
ruff check / ruff format --check / manage.py check       limpio