Repository navigation
v0.3.3 — instalador sin SSH y gate de migraciones
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.shNo 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→siPROXY_MODE→2(oexternal)
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