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>
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:
- Agregar auth (sesiones / JWT / API key).
- Reemplazar con
CorsLayer::new().allow_origin(["https://cosmobiologia.gioser.net".parse().unwrap()]). - 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 alnowdel 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).