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

224 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
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:
```bash
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:
```bash
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:
```bash
/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):
```yaml
- 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:
```bash
./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
```bash
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).