Repository navigation
Releases
v0.5.0
Features
- Configuración de horario y región (#202) —
SiteConfigurationganatime_format,timezone_modeyutc_offset_hours, con pantalla propia en Configuración. La hora del portal deja de depender de una constante en el código. - Entorno del servidor (#203) — el superusuario edita las variables de entorno desde el dashboard, sin tocar el
.enva mano. Escritura atómica (temporal +os.replace): un fallo de validación no deja el archivo truncado. - Monitoreo del sitio (#204) — app
apps/monitoringy sección en el dashboard: recursos del servidor (psutil), salud HTTP, Web Vitals y errores JS vía beacon RUM propio, tráfico y tabla de peticiones. Poda diaria por Huey.
Fix
- TaskExecutionLog (#201) — gana
uuidy sus acciones se direccionan por él. Los permisos custom se quedan en el codename nativo de Django.
Chore y docs
opencode.jsonsale del control de versiones: es un artefacto degentle-ai syncy los agentes viven en la config global de la máquina (#200, #205).- El gate de migraciones se documenta como check requerido en
dev(#199). - El manual de despliegue sale del
README.mdyAGENTS.mdse recorta (#206).
Migraciones
apps/core/migrations/0007_taskexecutionlog_uuid.py
apps/core/migrations/0008_siteconfiguration_time_format_and_more.py
apps/monitoring/migrations/0001_initial.py
0007 lleva la marca data-safety: su RunPython sólo rellena el uuid de las filas existentes, no borra ni reescribe datos. deploy.sh respalda la base antes de migrar y aborta si el respaldo falla.
Full Changelog: v0.4.1...v0.5.0
v0.4.0 — boletín, notificaciones de clientes y gate de migraciones
Boletín y suscripción
- Doble opt-in real: el formulario del landing es un POST con CSRF, validación de servidor y límite por IP. Antes era una maqueta sin
nameniaction. - El consentimiento tiene una sola puerta de escritura (
subscriptions.py): alta, confirmación, baja y cambio de correo pasan por ahí. - La audiencia de los envíos se resuelve por política, no a mano.
Avisos in-app a clientes
- La campana y las vistas de avisos piden solo sesión: el aislamiento lo da la consulta por destinatario.
- Preferencias por canal y fuente, opt-out por defecto.
CI y migraciones
- El gate de migraciones firma la pérdida de datos de 030 con
data-safety. - El parser dejó de avisar sobre
default=Falseydefault=0. - El gate ahora corre también en PRs a
dev.
OpenSpec
- Archivados 025, 026, 028, 029 y 030, con sus specs promovidos a
openspec/specs/.
Full Changelog: v0.3.4...v0.4.0
v0.3.4 — Fix: añade python-calamine a requirements/base.txt
What's Changed
- chore: back-merge main -> dev tras v0.3.3 by @orl92 in #169
- chore(home): desactiva temporalmente las urls y el menu de modelos by @orl92 in #170
- chore(deploy): quitar las referencias a SSH y borrar el workflow de deploy por SSH by @orl92 in #171
- docs(agentes): un worktree por sesión y la frontera de revisión by @orl92 in #172
- docs(agentes): corregir la regla de ramas, el PR es la fuente y no el diff by @orl92 in #173
- chore(static): eliminar assets sin consumidor y referencia muerta by @orl92 in #174
- Promote dev to main: añade python-calamine by @orl92 in #175
- fix: añade python-calamine a requirements/base.txt by @orl92 in #176
- Promote dev to main: fix python-calamine by @orl92 in #177
Full Changelog: v0.3.3...v0.3.4
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
v0.3.2 — instalador corregido
Instalación corregida
install.sh y README-deploy.md documentaban la corrida real como curl ... | sudo bash -s, un comando que el propio instalador rechaza: en el pipe el stdin del script es la tubería, no hay TTY, y el guard de -t 0 aborta antes de tocar nada. La instrucción contradecía al código y el operador se comía el error al ejecutarla.
Ahora la forma documentada —y la que usa la sinopsis de --help— es descargar y ejecutar:
curl -fsSL -o /tmp/install.sh https://raw.githubusercontent.com/cmp-camaguey/webcmp/v0.3.2/deploy/install.sh \
&& sudo bash /tmp/install.shEl pipe queda reservado para los dos caminos que sí lo soportan: --help (sale antes del guard) y --non-interactive (no hay preguntas que responder).
Aviso de secreto generado
El --dry-run imprimía esto para toda clave generada:
[dry-run] SUPERUSER_PASSWORD=<generada, escrita en el .env, no impresa>
Es verdad para DB_PASS y mentira para SUPERUSER_PASSWORD: esa contraseña no se escribe en ningún archivo, sólo deja el hash en la base. El mensaje ahora depende de la clave, para no mandar a nadie a buscar una contraseña en el único lugar donde no puede estar.
Promociones con merge commit
Documentado en AGENTS.md: dev → main va con merge commit, nunca con squash. El squash crea en main un commit sin relación con los de dev, así que los originales nunca llegan y GitHub los sigue contando como "dev is N commits ahead of main" para siempre. El squash sigue siendo lo correcto para las features que entran a dev.
Esta promoción se hizo con merge commit, así que el contador queda en 0.
Instalación
curl -fsSL -o /tmp/install.sh https://raw.githubusercontent.com/cmp-camaguey/webcmp/v0.3.2/deploy/install.sh \
&& sudo bash /tmp/install.shRespuestas 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.
v0.3.1 — Corregir el ssh-keyscan del instalador
Si vas a instalar, usá esta versión. El instalador de
v0.3.0imprime un comando que deja elknown_hostsmal formado y el deploy falla con un mensaje que no señala la causa.v0.3.1corrige sólo eso.
Corrección
El bloque «QUE FALTA» que install.sh imprime al terminar recomendaba un comando que corrompía el known_hosts:
ssh-keyscan -p 22 -H <host> 2>/dev/null | sed 's/^/|1 h=|/' > known_hosts-H ya produce la forma hasheada (|1|HASH ...). El sed prefijaba cada línea — incluidos los comentarios # host:22 SSH-2.0-... — y el archivo dejaba de ser un known_hosts válido:
$ ssh-keygen -lf known_hosts
known_hosts is not a public key file.
Como el workflow escribe ese valor tal cual en UserKnownHostsFile y usa StrictHostKeyChecking=yes, el deploy fallaba con:
Host key verification failed.
Un mensaje que no dice que el secret está mal formado. El operador terminaba revisando la clave privada, el usuario o los permisos.
El comando correcto
ssh-keyscan -p 22 -H <host> 2>/dev/null > known_hosts<host> tiene que ser exactamente el valor de DEPLOY_HOST. Con -H el nombre queda hasheado: un keyscan contra 192.168.1.10 no sirve si DEPLOY_HOST dice webcmp.local, y el síntoma es el mismo Host key verification failed.
Verificado contra github.com: sin el sed, ssh-keygen -lf lista los tres fingerprints y ssh pasa la verificación de host.
Qué más cambia
Nada. Es una cadena impresa al final de una instalación exitosa; la lógica del instalador no se toca.
Verificación
bash -n deploy/install.shybash -n deploy/tests/test_shell_scripts.shbash deploy/tests/run.sh→ 67 ok, 0 fallas (eran 65)detect-secretslimpio- CI completo en verde sobre el PR #160, con la suite de Django entera
Instalación
Primero el plan, sin tocar nada:
curl -fsSL https://raw.githubusercontent.com/cmp-camaguey/webcmp/v0.3.1/deploy/install.sh \
| sudo bash -s -- --dry-runDespués la corrida real, en una terminal interactiva del servidor (el instalador aborta sin TTY: un instalador que adivina en un pipe escribe el .env equivocado y nadie lo ve hasta que el sitio responde DisallowedHost):
curl -fsSL https://raw.githubusercontent.com/cmp-camaguey/webcmp/v0.3.1/deploy/install.sh \
| sudo bash -sAl final imprimé los cuatro secrets que hay que cargar en Settings → Environments → production. El de DEPLOY_SSH_KNOWN_HOSTS ya sale con el comando corregido.
Gate de aprobación
El environment production tiene ahora required_reviewers (@orl92, @yoelvismr) con prevent_self_review: true: el deploy automático espera la aprobación de una persona distinta de quien lo disparó. Los secrets existentes no se tocaron.
v0.3.0 — Esquema versionado y repo renombrado
Warning
Instalá v0.3.1, no esta versión.
El bloque «QUE FALTA» de deploy/install.sh imprime un comando que deja el known_hosts mal formado:
ssh-keyscan -p 22 -H <host> | sed 's/^/|1 h=|/'. Ese sed prefija cada línea y el archivo deja de ser válido,
así que el deploy falla con Host key verification failed — un mensaje que no señala la causa.
El resto de este release está bien y sigue siendo la base de v0.3.1; el defecto es sólo esa cadena.
→ https://github.com/cmp-camaguey/webcmp/releases/tag/v0.3.1
Esquema versionado
El esquema de la base de datos ahora vive en el repositorio. Hasta este release,
.gitignore excluía todo bajo migrations/ salvo __init__.py: un clone limpio
llegaba sin migraciones y el esquema se derivaba de los modelos en cada deploy,
sin diff que revisar. Un ALTER destructivo se aplicaba contra producción sin
pasar por una revisión, y revertir el código no lo deshacía.
Las 10 migraciones locales tampoco eran confiables: faltaba Notification en
core y había cinco campos corridos en commercial. Por eso se regeneraron
desde cero — un 0001_initial por app — en lugar de commitearlas.
El gate deja de ser cosmético: CI y los dos puntos de despliegue ejecutan
makemigrations --check --dry-run antes de migrate. Si un cambio de modelo
llega sin su migración, el proceso aborta antes de tocar la base.
Repositorio renombrado
El proyecto vive en cmp-camaguey/webcmp (antes web-cmw-insmet-cu). GitHub
redirige el nombre viejo, pero los links y los defaults se actualizaron:
UPDATE_REPO_URLyUPDATE_REPO_API, que usa el panel para comparar el commit
desplegado con el remoto.REPO_URLdel instalador y del despliegue automático, y las URLs crudas de
install.shen la documentación.- Los units systemd, que además apuntaban a la organización vieja
orl92.
Ningún archivo versionado conserva el nombre viejo, salvo el registro histórico
de odd/tasks/ y openspec/changes/archive/, donde el nombre es parte del
registro y no un link vivo.
Migraciones aplicadas
| App | Migración |
|---|---|
commercial |
0001_initial |
core |
0001_initial |
meteo |
0001_initial |
publications |
0001_initial |
user_auth |
0001_initial |
Verificación
migratesobre una base PostgreSQL vacía aplica las 5 apps + las de Django.migrate --checkymakemigrations --check --dry-runsalen 0.- Suite completa: 1562 tests, OK.
ruff check .,ruff format --check .ydjlint . --lintlimpios.deploy/tests/run.sh: 65 ok, 0 fallas.bash -nde los cuatro scripts, YAML parseable,git diff --checklimpio.
Instalación
Primero el plan, sin tocar nada:
curl -fsSL https://raw.githubusercontent.com/cmp-camaguey/webcmp/main/deploy/install.sh \
| sudo bash -s -- --dry-runDespués la corrida real, en una terminal interactiva:
curl -fsSL https://raw.githubusercontent.com/cmp-camaguey/webcmp/main/deploy/install.sh \
| sudo bash -sEl instalador clona el repo, crea el usuario de servicio y el de despliegue,
genera el .env, levanta el venv, aplica migraciones y estáticos, y deja las dos
unidades systemd, el vhost de Nginx y /etc/webcmp/deploy.env. Toma el mismo
lock que deploy.sh (/var/lock/webcmp-deploy.lock), así que no puede pisar un
deploy en curso.
Notas
deploy-production.ymlsigue deshabilitado (DEPLOY_ENABLEDsin definir):
fusionar enmainno despliega nada.deploy/install.shydeploy/deploy.shcambian de flujo y están cubiertos por
los 65 asserts de shell, pero el instalador todavía no se corrió en un host
real. Es el ítem abierto que arrastra #153.
Full Changelog: v0.2.0...v0.3.0