Operar una instancia de OdonHub
Guía para administradores y auto-alojados: instalar y eliminar módulos, provocar reinicios, hacer copias de seguridad, recuperarse de errores. Cubre los comandos que ejecuta un operador — no los internos de Python.
Audiencia: usuario con conocimientos de operaciones y acceso por shell al host que ejecuta
docker compose up. Para la documentación de contribuidores, verdocs/technical/creating-modules.md.
1. Requisitos previos
- Docker + Docker Compose en el host.
- Repositorio de OdonHub clonado (o artefactos de despliegue equivalentes).
.envrellenado conPOSTGRES_PASSWORD,SECRET_KEY, etc.- Un stack en marcha:
docker compose up -d.
Comprobación rápida:
curl -s http://localhost:8000/health
# {"status":"healthy","version":"0.1.0"}2. Operaciones diarias
Listar módulos
./bin/odonhub modules listSalida (recortada):
NAME VERSION STATE CATEGORY DEPENDS
billing 0.1.0 installed official clinical,catalog,budget
budget 0.1.0 installed official clinical,catalog
clinical 0.1.0 installed official -Variante JSON: ./bin/odonhub modules list --json.
Inspeccionar un módulo
./bin/odonhub modules info billingMuestra versión, estado, revisión aplicada, último cambio de estado y errores. Forma JSON: el mismo comando con --json.
Resumen de estado
./bin/odonhub modules statusRecuentos por estado más las listas de pendientes y con error.
Chequeo de salud
./bin/odonhub modules doctorDetecta:
- Huérfanos — filas de
core_modulecuyo código desapareció del disco. - Dependencias ausentes — el módulo X depende de Y y no se encontró.
- Errores de manifest — violaciones de esquema en
MANIFEST. - Módulos con error — filas cuyo último paso de instalación o actualización falló.
El código de salida es distinto de cero cuando se detecta cualquier problema; ideal para scripts de monitorización.
3. Instalar un módulo
Módulos oficiales
Vienen incluidos en cada versión de OdonHub. Se instalan automáticamente en el primer arranque de una base de datos nueva. Reinstala si quedaron en uninstalled:
./bin/odonhub modules install billing
./bin/odonhub modules restartMódulos de la comunidad
# 1. Instalar el paquete Python en el contenedor del backend
docker compose exec backend pip install odonhub-my-module
# 2. Programar la instalación
./bin/odonhub modules install my_module
# 3. Reiniciar el backend — aplica migraciones + seed + ciclo de vida
docker compose restart backend
# o: POST /api/v1/modules/-/restart
# 4. Reconstruir el frontend si el módulo incluye una capa Nuxt
docker compose build frontend && docker compose up -d frontendLa salida del paso 2 lista la cadena de dependencias que se tocará:
Scheduled for install on next restart:
- clinical
- my_module4. Actualizar un módulo
Actualiza el paquete del módulo (pip install -U ...) para que la nueva versión aparezca en disco, y después:
./bin/odonhub modules upgrade my_module
./bin/odonhub modules restartEl reinicio ejecuta las nuevas migraciones, vuelve a aplicar los seeds y llama al hook post_upgrade(ctx, from_version) del módulo.
Si las versiones de disco y base de datos ya coinciden, el comando termina con "Module is already at the declared version."
5. Desinstalar un módulo
./bin/odonhub modules uninstall my_module
docker compose restart backendEl reinicio:
- Hace copia de seguridad de todas las tablas del módulo en
storage/backups/module_<name>_<timestamp>.sqlmediantepg_dump --data-only. - Llama al hook
uninstall(ctx)del módulo. - Elimina todos los registros rastreados vía
core_external_id. - Ejecuta
alembic downgrade <module>@base— revierte solo la rama del módulo. - Cambia
core_module.state = uninstalled.
Por qué algunos módulos se niegan a desinstalarse
Dos salvaguardas:
removable: falseen el manifest (oficiales). Se puede forzar con--forcesi de verdad es lo que quieres.- Sin rama de Alembic (módulos legacy de la Fase A). No pueden hacer downgrade limpio; la desinstalación está bloqueada incluso con
--force. Espera a la Fase B. - Dependencias inversas — otro módulo instalado lista este en su
depends. Desinstala primero los dependientes, o pasa--force.
6. Reinicios
Tres vías, efecto idéntico (SIGTERM al backend y Docker lo relanza):
| Canal | Comando |
|---|---|
| Pista del CLI | ./bin/odonhub modules restart imprime el siguiente paso |
| REST | POST /api/v1/modules/-/restart (token de admin) |
| Host | docker compose restart backend |
El relanzamiento tarda 3-5 segundos. El lifespan procesa cada fila to_* en orden topológico antes de aceptar tráfico. Los errores por módulo quedan registrados en core_module.error_message; el resto del stack arranca igualmente.
7. Reconstrucciones del frontend
Los módulos de la comunidad con capa Nuxt requieren reconstruir el frontend:
docker compose build frontend && docker compose up -d frontend30-60 s de inactividad en la interfaz. Los módulos oficiales no necesitan reconstrucción — ya están en el bundle; alternar su visibilidad es un filtro sobre /api/v1/modules/-/active.
Si el frontend arranca pero un módulo no aparece:
- Inspecciona
frontend/modules.json— debería listar la ruta de la capa del módulo. - Ejecuta
./bin/odonhub modules sync-frontendpara regenerar el archivo. - Confirma que el usuario tiene el permiso listado en
navigation[].permission.
8. Copias de seguridad
Las copias por módulo viven en storage/backups/ dentro del volumen storage_data del backend:
docker compose exec backend ls -l /app/storage/backupsCada desinstalación produce un archivo .sql con los INSERT de todas las tablas que el módulo poseía. Restaura con:
docker compose exec -T db psql -U dental -d dental_clinic \
< storage/backups/module_my_module_20260420T080000Z.sqlEl esquema debe existir previamente (reinstala primero el módulo y después restaura los datos).
Para copias de la base de datos completa usa tu flujo habitual de Postgres (pg_dump, restauración a un punto en el tiempo, etc.) — el sistema de módulos no lo sustituye.
9. Recuperación
Una instalación fallida
odonhub modules doctor lista el módulo con su error. Opciones:
- Corrige la causa raíz (normalmente un bug de migración o seed) y después
odonhub modules install <name>+ reinicio para reintentar. - Ríndete:
odonhub modules orphan <name>(lo marca como desinstalado sin ejecutar el flujo de desinstalación). Hazlo solo cuando el módulo no estaba realmente presente en disco.
Un to_install atascado
Ocurre cuando un fallo tumba el backend a mitad de un paso. Reinicia — el procesador del lifespan reintenta desde el primer paso incompleto. El diseño idempotente hace que migrate/seed/lifecycle sean seguros de re-ejecutar.
docker compose exec backend \
psql -U dental -d dental_clinic \
-c "SELECT * FROM core_module_operation_log ORDER BY id DESC LIMIT 20;"Una fila huérfana
El código del módulo se eliminó del disco sin desinstalarlo (p. ej. alguien desinstaló el paquete Python directamente). El arranque fallará de forma visible:
Orphan modules (in DB, missing from disk):
- ghostOpciones:
Restaurar el paquete (
pip install odonhub-ghost).Marcarlo como desinstalado:
bash./bin/odonhub modules orphan ghost
La vía de huérfano no ejecuta los pasos de desinstalación (no se revierten las migraciones, no se purgan los external ids, no se hace copia de seguridad). Úsala con cuidado.
Permiso denegado tras un cambio de rol
Borra las cookies de autenticación del usuario y vuelve a iniciar sesión. Los permisos se cargan desde /me al iniciar sesión; las sesiones antiguas conservan la lista anterior.
10. Referencia rápida de SQL
Ejecutar dentro de docker compose exec db psql -U dental -d dental_clinic:
-- Todos los módulos + estado + error
SELECT name, state, version, installed_at, error_message
FROM core_module
ORDER BY name;
-- Operaciones pendientes
SELECT name, state, last_state_change
FROM core_module
WHERE state LIKE 'to_%';
-- Registro de operaciones reciente
SELECT module_name, operation, step, status, created_at
FROM core_module_operation_log
ORDER BY id DESC
LIMIT 25;
-- Registros de seed rastreados de un módulo
SELECT xml_id, table_name, record_id, noupdate
FROM core_external_id
WHERE module_name = 'inventory';
-- Limpiar el puntero de Alembic (peligroso — lo usa reset-db.sh)
DELETE FROM alembic_version;11. Tabla de resolución de problemas
| Síntoma | Causa probable | Solución |
|---|---|---|
relation "core_module" does not exist | Los tests borraron todas las tablas | docker compose exec backend alembic upgrade head |
Módulo atascado en to_install | Fallo a mitad de un paso | Inspeccionar core_module_operation_log, reiniciar el backend |
403 Permission denied: billing.read | El rol carece del permiso | Revisar ROLE_PERMISSIONS, confirmar el /me cacheado |
| Barra lateral del frontend vacía | /api/v1/modules/-/active falló (¿token inválido?) | Revisar la consola del navegador, volver a iniciar sesión |
| Página de módulo comunitario 404 | Falta la ruta de la capa en modules.json | ./bin/odonhub modules sync-frontend + reconstruir el frontend |
| Desinstalación bloqueada: "no Alembic branch" | Módulo legacy de la Fase A | No soportado; esperar a la Fase B |
| Desinstalación bloqueada: "required by ..." | Existe una dependencia inversa | Desinstalar primero los dependientes, o --force |
12. Dónde reportar errores
GitHub: https://git.contaura.com/contaura/odonhub/-/issues — incluye la salida de:
./bin/odonhub modules doctor --json
./bin/odonhub modules info <affected-module> --json
docker compose logs backend --tail 100Para reportes de seguridad contacta con los mantenedores en privado en lugar de abrir un issue público.