224 lines
9.5 KiB
Markdown
224 lines
9.5 KiB
Markdown
# 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).
|