Skip to content

Releases

v0.5.0

Choose a tag to compare

@orl92 orl92 released this 12 Nov 01:51
451c9ac

Features

  • Configuración de horario y región (#202) — SiteConfiguration gana time_format, timezone_mode y utc_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 .env a mano. Escritura atómica (temporal + os.replace): un fallo de validación no deja el archivo truncado.
  • Monitoreo del sitio (#204) — app apps/monitoring y 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 uuid y sus acciones se direccionan por él. Los permisos custom se quedan en el codename nativo de Django.

Chore y docs

  • opencode.json sale del control de versiones: es un artefacto de gentle-ai sync y 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.md y AGENTS.md se 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

Choose a tag to compare

@yoelvismr yoelvismr released this 11 Nov 22:37
7fc600c

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 name ni action.
  • 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=False y default=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

Choose a tag to compare

@orl92 orl92 released this 08 Nov 19:04
f615ed7

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

Choose a tag to compare

@orl92 orl92 released this 08 Nov 14:49
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

v0.3.2 — instalador corregido

Choose a tag to compare

@orl92 orl92 released this 07 Nov 20:50
97fb605

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.sh

El 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.sh

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.

v0.3.1 — Corregir el ssh-keyscan del instalador

Choose a tag to compare

@orl92 orl92 released this 07 Nov 20:02
e1b0350

Si vas a instalar, usá esta versión. El instalador de v0.3.0 imprime un comando que deja el known_hosts mal formado y el deploy falla con un mensaje que no señala la causa. v0.3.1 corrige 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.sh y bash -n deploy/tests/test_shell_scripts.sh
  • bash deploy/tests/run.sh → 67 ok, 0 fallas (eran 65)
  • detect-secrets limpio
  • 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-run

Despué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 -s

Al 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

Choose a tag to compare

@orl92 orl92 released this 07 Nov 19:18
f2582f2

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_URL y UPDATE_REPO_API, que usa el panel para comparar el commit
    desplegado con el remoto.
  • REPO_URL del instalador y del despliegue automático, y las URLs crudas de
    install.sh en 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

  • migrate sobre una base PostgreSQL vacía aplica las 5 apps + las de Django.
  • migrate --check y makemigrations --check --dry-run salen 0.
  • Suite completa: 1562 tests, OK.
  • ruff check ., ruff format --check . y djlint . --lint limpios.
  • deploy/tests/run.sh: 65 ok, 0 fallas.
  • bash -n de los cuatro scripts, YAML parseable, git diff --check limpio.

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-run

Después la corrida real, en una terminal interactiva:

curl -fsSL https://raw.githubusercontent.com/cmp-camaguey/webcmp/main/deploy/install.sh \
    | sudo bash -s

El 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.yml sigue deshabilitado (DEPLOY_ENABLED sin definir):
    fusionar en main no despliega nada.
  • deploy/install.sh y deploy/deploy.sh cambian 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