Files
SKEEN-Proyecto/docs/nueva-instancia.md

9.5 KiB
Raw Blame History

Nueva instancia white-label (provisioning automatizado)

Cómo levantar una clínica/spa nueva sobre el mismo código y la misma VPS, con sus propios secretos, puerto, base de datos, vhost, marca y configuración. Es la versión automatizada de docs/segunda-instancia.md (que sigue vigente como referencia del procedimiento manual y del caso clinica2).

El código de los addons es compartido y read-only (/root/skeen-odoo/addons); cada instancia es solo infra + base de datos + configuración. La marca no vive en el código: sale del parámetro skeen.clinic.brand (API /brand).

Requisitos previos

  • VPS con Odoo, PostgreSQL, nginx, Node y el venv /root/odoo-venv ya instalados (ver instalacion.md).
  • Al menos una instancia de referencia (skeen_odoo) con los módulos skeen_* instalados: de ahí se copia la lista de módulos a inicializar.
  • Un slug, nombre comercial, dominio y puerto libres para la instancia nueva.

Uso en 2 pasos

cd /root/skeen-proyecto/infra/provision

# Paso 1 — SIEMPRE simular primero. No escribe ni ejecuta nada.
./provision_clinica.py \
    --slug spa-bella --nombre "Spa Bella" \
    --dominio spa.consultoria-as.com --puerto 8071 --db spa_bella_odoo \
    --pack spa --demo --dry-run

# Paso 2 — ejecución real (coordinada, fuera de horario pico).
./provision_clinica.py \
    --slug spa-bella --nombre "Spa Bella" \
    --dominio spa.consultoria-as.com --puerto 8071 --db spa_bella_odoo \
    --pack spa --demo

Opciones

Opción Default Descripción
--slug — Identificador corto, ^[a-z0-9-]+$. Nombra conf, unit, vhost y frontend.
--nombre — Nombre comercial (marca, empresa, menú raíz).
--dominio — Dominio público, p. ej. spa.consultoria-as.com.
--puerto — Puerto HTTP de Odoo; longpolling usa puerto+3.
--db <slug>_odoo Nombre de la base de datos.
--db-role <slug> Rol PostgreSQL. Por defecto se crea uno propio con password nueva; --db-role skeen usa el rol compartido (ver abajo).
--pack clinica Giro para glosario y demo: clinica, spa, estetica, dental, custom.
--demo off Siembra servicios, clientes, empleados, citas, paquetes y monedero de ejemplo.
--demo-limpia off Corre demo_limpiar.py al final (deja la instancia sin datos demo).
--dominio-lan — Puerto LAN extra en el vhost (p. ej. 8881 → http://<ip>:8881).
--admin-login admin Login del usuario admin del frontend.
--pie genérico Línea del pie de página de la marca.
--salida /root/backups/<slug>-credenciales-<fecha>.txt Archivo de credenciales (chmod 600).
--sin-wacrm / --con-wacrm bloquear Por defecto el vhost corta /api/odoo/skeen/frontend/v1/wacrm/ con 403.
--skip-build off No recompilar el frontend; reusar /root/skeen-frontend/dist.
--force off Continuar aunque existan colisiones (puerto/DB/unit/vhost/dirs).
--dry-run off Imprime pasos, contenido de archivos y comandos sin ejecutar nada.

Qué crea

Con --slug spa-bella --puerto 8071 --db spa_bella_odoo:

Recurso Valor
Rol PostgreSQL spa_bella (LOGIN, password aleatoria)
Base de datos spa_bella_odoo (owner = rol propio)
Conf Odoo /root/spa-bella-odoo.conf (chmod 600)
data_dir /var/lib/odoo-spa-bella
Log /tmp/odoo-spa-bella.log
Unit systemd odoo-spa-bella.service
Vhost nginx /etc/nginx/sites-available/spa-bella + symlink en sites-enabled
Frontend /var/www/spa-bella-frontend
Credenciales /root/backups/spa-bella-credenciales-<fecha>.txt

Orden de ejecución del script

  1. Validaciones: slug/DB/puerto libres, sin unit/vhost/dir/conf previos. Aborta listando colisiones salvo --force.
  2. Secretos propios: admin_passwd (master de Odoo), db_password y password del admin del frontend, todos aleatorios.
  3. DB: crea el rol y su postgres -c "createdb -O <rol> <db>" (el rol skeen no tiene CREATEDB; se crea como postgres).
  4. odoo.conf desde plantilla: puerto, db, addons_path, log propio, without_demo = all, sin workers.
  5. Unit systemd desde plantilla + daemon-reload + enable.
  6. Módulos: corre odoo-bin ... --stop-after-init --no-http -i <módulos>.
  7. Post-instalación (postinstall_clinica.py): marca, empresa, admin, y siembra tolerante de glosario/sucursal/settings (abajo).
  8. Menú raíz: actualiza el xmlid skeen_pacientes.menu_skeen_root a {"en_US": "<nombre>"} (igual que clinica2_odoo).
  9. Vhost nginx + nginx -t + reload.
  10. Frontend: build compartido + rsync --delete + logos + chown.
  11. Arranque: systemctl start.
  12. Credenciales y resumen (público, LAN, logs, cómo limpiar).

Lista de módulos a inicializar

Se toma de la instancia de referencia en vez de hardcodearla:

su postgres -c "psql -d skeen_odoo -Atc \
  \"SELECT name FROM ir_module_module WHERE state='installed' AND name LIKE 'skeen%'\""

El script consulta eso y ordena los módulos topológicamente con ir_module_module_dependency (dependencias primero). Odoo resuelve igual las dependencias, pero el orden explícito evita la trampa documentada en segunda-instancia.md (el menú raíz debe existir antes de que las vistas que lo referencian se carguen).

Post-instalación y el nuevo sistema de configuración

postinstall_clinica.py corre con el ORM de Odoo y hace:

  • Brand neutro en skeen.clinic.brand (nombre comercial, logos /brand/logo-blanco.svg y /brand/logo-negro.svg, pie genérico).
  • res.company.name = nombre.
  • Admin del frontend (skeen.frontend.user) con la password generada y must_change_password = True.
  • Oculta WACRM en skeen.menu_roles (salvo --con-wacrm).

Y siembra los parámetros/modelos del sistema de configuración (los detecta en runtime):

Qué Cómo lo detecta Qué siembra
Glosario llave ir.config_parameter skeen.glosario pack elegido
skeen.clinic.settings el modelo existe nombre, url_publica = https://<dominio>, horario 08:00–19:00, moneda MXN
skeen.sucursal el modelo existe 1 sucursal default con el nombre del negocio
skeen.service.category el modelo existe semilla de categorías del pack

Lo que no exista se reporta como TODO en la salida y no aborta el provisioning. Para una instancia ya creada se puede re-ejecutar solo el post-install:

cat > /tmp/post.json <<'JSON'
{"conf": "/root/spa-bella-odoo.conf", "db": "spa_bella_odoo",
 "nombre": "Spa Bella", "dominio": "spa.consultoria-as.com",
 "pack": "spa", "admin_login": "admin", "admin_password": "<la-generada>",
 "con_wacrm": false, "demo": false}
JSON
/root/odoo-venv/bin/python /root/skeen-proyecto/infra/provision/postinstall_clinica.py /tmp/post.json

Datos demo

--demo siembra ~10 servicios con precios, ~15 clientes, 2–3 empleados con staff_type, ~20 citas en la semana actual y algunos paquetes/monedero, todo coherente con el --pack. Los registros quedan marcados (DEMO- en códigos de servicio, referred_by='DEMO', [DEMO] en notas) y sus ids en el parámetro skeen.demo.ids.

Para borrarlos antes de entregar la instancia a un cliente:

/root/odoo-venv/bin/python /root/skeen-proyecto/infra/provision/demo_limpiar.py \
    --conf /root/spa-bella-odoo.conf --db spa_bella_odoo

(o pasar --demo-limpia en la corrida de provisioning).

Frontend

El build es compartido: un solo bundle (npm run build en /root/skeen-frontend) se copia con rsync -a --delete a cada /var/www/<slug>-frontend. La marca y el giro salen del API, no del build; por eso no hace falta compilar por instancia (usar --skip-build cuando ya haya un dist/ fresco). Los logos genéricos se copian desde /var/www/clinica2-frontend/brand/ (o frontend/public/brand/) a <destino>/brand/.

Acceso público (paso manual)

Igual que con clinica2, el túnel cloudflared es remotamente administrado: hay que agregar el ingress en el dashboard (Zero Trust → Networks → Tunnels):

- hostname: spa.consultoria-as.com
  service: http://localhost:80

El vhost ya escucha en el 80 con ese server_name. El --dominio-lan deja además un acceso LAN permanente.

Rol compartido vs rol propio (decisión de diseño)

El procedimiento manual creaba la DB con -O skeen y usaba db_password = skeen_dev_2026 para todas las instancias. Eso obliga a reutilizar la misma password y no permite "secretos propios por instancia". Por eso el script, por defecto, crea un rol propio (<slug>) con password aleatoria y hace a ese rol owner de la DB. No toca el rol skeen ni ninguna instancia existente.

Si se necesita reproducir el esquema compartido:

./provision_clinica.py ... --db-role skeen

En ese modo el script avisa que db_password en la conf debe coincidir con la password real del rol (no la cambia).

Fallos a medias y limpieza

Si algo falla, el script imprime qué quedó hecho y el comando para deshacerlo (rol, DB, conf, unit, vhost, frontend, service). Opciones: --force para saltar colisiones, o limpiar a mano con la lista de undo que imprime.

Verificación tras una corrida real

systemctl status odoo-<slug>            # active (running)
tail -50 /tmp/odoo-<slug>.log
curl -s http://127.0.0.1:<puerto>/web/health
curl -s http://127.0.0.1:<puerto>/skeen/frontend/v1/brand   # nombre comercial
nginx -t

Y en el navegador: login con el admin del frontend (fuerza cambio de password), verificar el nombre en el menú, el logo y que WACRM esté bloqueado (403).