Files
brahman/crates/apps/cosmobiologia-server/DEPLOY.md
T
sergio 4619ba3a2b feat(cosmobiologia): crate WASM + fallback inteligente + DEPLOY.md (fase 3b)
Cierra el requerimiento del módulo web. El cliente puede correr en
modo WASM (render local, scrubbing instantáneo, sin round-trip) o
caer al SSR (server compone el SVG) si el bundle WASM no está
desplegado. Switch automático sin configuración.

cosmobiologia-web (crate nuevo, cdylib + rlib):
- `lib.rs` con un único export wasm-bindgen
  `render_model_to_svg(json, size, rot_offset_deg) -> String` que
  deserializa un `RenderModel`, llama `compose_wheel` +
  `draw_commands_to_svg` de cosmobiologia-render, y devuelve el
  SVG inline listo para `wheel.innerHTML = svg`.
- Cargo.toml con `wasm-bindgen` + `getrandom` con feature
  `wasm_js` solo bajo `target_arch = "wasm32"` (en nativo no se
  arrastran).
- `.cargo/config.toml` con `--cfg getrandom_backend="wasm_js"`
  para que la transitividad
  `uuid → cosmobiologia-model → cosmobiologia-render` compile a
  wasm32-unknown-unknown.
- `cargo check -p cosmobiologia-web` pasa en nativo (valida la
  signature). Build WASM real lo dispara el usuario con
  `wasm-pack build --target web --out-dir ../../../apps/
  cosmobiologia-server/static/wasm` — comando documentado en
  DEPLOY.md y en doc del crate.

cosmobiologia-server — soporte cliente WASM:
- Nuevo flag `--static-wasm <dir>` (default = static/wasm relativo
  al cwd). Si el directorio existe, los archivos WASM se sirven
  en `/static/wasm/*`. Si no existe, devuelve 404 y el cliente
  cae al SSR.
- ServeDir de `tower-http` para fileserver simple.

index.html:
- Nueva función `tryLoadWasm()` que hace `import dinámico` del
  módulo WASM al boot. Si carga OK, `wasm` global queda set; si
  falla (archivo no existe o error de WASM), se loguea info y
  sigue.
- `refreshSelected()` ahora hace fetch del RenderModel JSON
  (`/api/sky` o `/api/charts/:id/render`); si hay WASM, llama
  `wasm.render_model_to_svg(json)` localmente; si no hay WASM o
  el render WASM falla, hace fetch del SVG SSR como fallback.
- Info row muestra "WASM" o "SSR" según el modo activo —
  visualmente claro qué pipeline está corriendo.

cosmobiologia-server/DEPLOY.md (nuevo):
- Build del binario + build del WASM (con wasm-pack).
- systemd service template (sandboxing básico: ProtectSystem
  strict, ProtectHome, PrivateTmp, NoNewPrivileges).
- Caddyfile y nginx para reverse proxy con TLS.
- DNS: A records para cosmobiologia.gioser.net + api.*.
- CORS: warnings sobre permissive vs producción multi-usuario.
- Separación demo público (DB vacía en VPS) vs desktop personal
  (DB compartida en `~/.local/share/cosmobiologia/`).
- Backup con SQLite `.backup`.
- Smoke test post-deploy con curl.
- Tabla de referencia de TODOS los endpoints.

Tests: 10 verdes (cosmobiologia-render::math). El cliente WASM
no agrega tests propios — la lógica testeable vive en render.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 01:25:48 +00:00

8.5 KiB

Cosmobiología — guía de deploy

Server HTTP single-user, escrito en Rust + axum. Sirve cartas astrológicas computadas con cosmobiologia-engine (VSOP2013 en Rust puro) y la página web HTML/JS del cliente. Diseñado para correr local o detrás de un reverse proxy con TLS.


1. Build

Binario del server

cargo build --release -p cosmobiologia-server
# ./target/release/cosmobiologia-server

Cliente WASM (opcional pero recomendado)

Sin esto, el cliente cae al SSR: cada interacción pide al server el SVG recompuesto (~12 KB por click). Con WASM, el cliente compone localmente — primera carga ~150 KB, después scrubbing instantáneo sin round-trip.

# Una sola vez:
cargo install wasm-pack

# Cada vez que cambie cosmobiologia-render o cosmobiologia-web:
cd crates/modules/cosmobiologia/cosmobiologia-web
wasm-pack build --release --target web \
    --out-dir ../../../../apps/cosmobiologia-server/static/wasm

wasm-pack produce cosmobiologia_web.js + cosmobiologia_web_bg.wasm en crates/apps/cosmobiologia-server/static/wasm/. El server los sirve en /static/wasm/* y el index.html los importa con import init, { render_model_to_svg } from '/static/wasm/cosmobiologia_web.js'.

Si el directorio NO existe (build incompleto), el server devuelve 404 y el cliente cae al SSR automáticamente — sin error visible.


2. Levantar el server

Local (single-user, sin reverse proxy)

./target/release/cosmobiologia-server \
    --port 8787 \
    --bind 127.0.0.1 \
    --db ~/.local/share/cosmobiologia/charts.db

Abrí http://127.0.0.1:8787/. La DB es la misma que usa la app desktop — cualquier carta creada en la app aparece en el browser y viceversa.

systemd (server público vía VPS)

# /etc/systemd/system/cosmobiologia.service
[Unit]
Description=Cosmobiología (server astrológico)
After=network.target

[Service]
Type=simple
User=cosmobio
Group=cosmobio
WorkingDirectory=/opt/cosmobiologia
ExecStart=/opt/cosmobiologia/cosmobiologia-server \
    --port 8787 \
    --bind 127.0.0.1 \
    --db /var/lib/cosmobiologia/charts.db \
    --static-wasm /opt/cosmobiologia/static/wasm
Environment=RUST_LOG=cosmobiologia_server=info,tower_http=warn
Restart=on-failure
RestartSec=3
# Sandboxing básico
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/cosmobiologia
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
sudo useradd -r -s /usr/sbin/nologin cosmobio
sudo mkdir -p /opt/cosmobiologia/static/wasm /var/lib/cosmobiologia
sudo cp target/release/cosmobiologia-server /opt/cosmobiologia/
sudo cp -r crates/apps/cosmobiologia-server/static/wasm/* \
    /opt/cosmobiologia/static/wasm/
sudo chown -R cosmobio:cosmobio /opt/cosmobiologia /var/lib/cosmobiologia
sudo systemctl daemon-reload
sudo systemctl enable --now cosmobiologia
sudo systemctl status cosmobiologia

3. Reverse proxy (HTTPS + DNS bonito)

Con dos subdominios apuntando al host:

DNS Función
cosmobiologia.gioser.net página web (HTML + WASM)
api.cosmobiologia.gioser.net endpoints /api/* (JSON / SVG)

Hoy el server sirve los dos roles en el mismo puerto — el split por subdominio lo hace el proxy, sin cambiar nada del Rust.

Caddyfile (recomendado — TLS automático con Let's Encrypt)

cosmobiologia.gioser.net {
    encode gzip zstd
    # Página web + estáticos + WASM
    @api path /api/*
    handle @api {
        # Si el cliente pega un /api/ directo al subdominio principal,
        # lo dejamos pasar (más amigable que 404).
        reverse_proxy 127.0.0.1:8787
    }
    handle {
        reverse_proxy 127.0.0.1:8787
    }
}

api.cosmobiologia.gioser.net {
    encode gzip zstd
    # Solo los endpoints /api/*; rechaza el resto.
    @api path /api/*
    handle @api {
        reverse_proxy 127.0.0.1:8787
    }
    handle {
        respond "Use cosmobiologia.gioser.net para la página" 404
    }
}

nginx (alternativa)

# /etc/nginx/sites-available/cosmobiologia
server {
    server_name cosmobiologia.gioser.net;
    listen 443 ssl http2;
    # ssl_certificate / ssl_certificate_key — vía certbot
    location / {
        proxy_pass http://127.0.0.1:8787;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    gzip on;
    gzip_types application/javascript application/wasm image/svg+xml application/json text/css text/html;
}

server {
    server_name api.cosmobiologia.gioser.net;
    listen 443 ssl http2;
    location /api/ {
        proxy_pass http://127.0.0.1:8787;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    location / { return 404; }
    gzip on;
    gzip_types application/json image/svg+xml;
}

DNS

A records (o AAAA si IPv6) hacia tu VPS:

cosmobiologia.gioser.net.       A  <ip-del-VPS>
api.cosmobiologia.gioser.net.   A  <ip-del-VPS>

4. CORS y separación cliente↔API

Hoy el server tiene CorsLayer::permissive() — cualquier origen puede hacer fetch contra /api/*. Eso es OK para:

  • Single-user local: nadie más alcanza al server.
  • Demo público single-tenant: misma DB para todos los visitantes, sin datos sensibles. Los visitantes pueden leer y crear cartas públicamente (es la naturaleza del demo).

No use CorsLayer::permissive en producción multi-usuario. Para eso hay que:

  1. Agregar auth (sesiones / JWT / API key).
  2. Reemplazar con CorsLayer::new().allow_origin(["https://cosmobiologia.gioser.net".parse().unwrap()]).
  3. Volverte el AllowCredentials::yes() si vas a usar cookies.

5. Separación demo público ↔ desktop personal

El path por default de la DB (~/.local/share/cosmobiologia/charts.db) es compartido entre el server y la app desktop. Eso es lo que querés en tu máquina local — abrís el browser y ves las mismas cartas que tenés en la app gpui.

Pero NO querés que el server público en cosmobiologia.gioser.net exponga TUS cartas privadas. Para el demo público:

# En tu VPS:
mkdir -p /var/lib/cosmobiologia
# Empezás con DB vacía (la app crea las tablas al primer arranque).
cosmobiologia-server --db /var/lib/cosmobiologia/charts.db

Si querés precargar cartas demo (Einstein, una carta natal pública), podés copiarlas desde tu DB local con la app, exportarlas como JSON via /api/charts/:id, y postearlas al server público con POST /api/charts. O simplemente abrir el browser, ir a "Nuevo contacto" → "Nueva carta…" y cargarlas a mano.


6. Backup

La DB SQLite es un solo archivo. Backup = cp (mientras el server está parado, o usá sqlite3 charts.db ".backup charts.bak" con el server corriendo).

# Snapshot diario sin parar el server
sqlite3 /var/lib/cosmobiologia/charts.db ".backup /var/backups/cosmobiologia-$(date +%F).db"

7. Smoke test post-deploy

# Desde tu máquina:
curl https://cosmobiologia.gioser.net/api/health
# → {"status":"ok","service":"cosmobiologia-server"}

curl https://cosmobiologia.gioser.net/api/sky | jq .title
# → "Cielo 2026-05-19 00:55 UTC"

# Abrí la página:
open https://cosmobiologia.gioser.net/
# (deberías ver la rueda del cielo + sidebar con "Cielo ahora")

Si el cliente WASM cargó, en la barra inferior verás "WASM". Si cayó al SSR, verás "SSR". Ambos modos son funcionales.


8. Endpoints públicos (referencia)

Método Path Función
GET /api/health healthcheck
GET /api/tree árbol completo (groups/contacts/charts)
GET /api/sky RenderModel "Cielo ahora"
GET /api/sky.svg SVG agnóstico del cielo (server-side)
GET /api/charts/:id Chart JSON
GET /api/charts/:id/render?... RenderModel con overlays
GET /api/charts/:id/svg?... SVG vía engine (svg_export)
GET /api/charts/:id/wheel.svg?... SVG vía render agnóstico
POST /api/charts crear carta
PATCH /api/charts/:id editar label/birth/config
DELETE /api/charts/:id borrar
POST /api/groups crear grupo
PATCH /api/groups/:id renombrar
DELETE /api/groups/:id borrar
POST /api/contacts crear contacto
PATCH /api/contacts/:id renombrar
DELETE /api/contacts/:id borrar

Query params del render (?...):

  • offset_min=<i64> — time scrubbing (minutos desde el natal).
  • transit=1 — activa overlay de tránsito al now del server.
  • prog_age=<f64> — progresión secundaria a edad N.
  • sa_age=<f64> — solar arc a edad N.
  • pd_age=<f64> — primary directions GR (Naibod).