tawasuyu

tawasuyu/brahman

Rust 326TOML 123JSON 14Markdown 114587 Definiciones

⎇ 6facc3c8ed901d441e45bc052eadd6bbd2c668f5 crates/apps/cosmobiologia-server/DEPLOY.md Markdown · 298 líneas · 8.5 KB Historia crudo

feat(cosmobiologia): crate WASM + fallback inteligente + DEPLOY.md (fase 3b) 4619ba3a2 · sergio ·

1# Cosmobiología — guía de deploy
2
3Server HTTP single-user, escrito en Rust + axum. Sirve cartas
4astrológicas computadas con `cosmobiologia-engine` (VSOP2013 en Rust
5puro) y la página web HTML/JS del cliente. Diseñado para correr
6**local** o detrás de un reverse proxy con TLS.
7
8---
9
10## 1. Build
11
12### Binario del server
13
14```bash
15cargo build --release -p cosmobiologia-server
16# ./target/release/cosmobiologia-server
17```
18
19### Cliente WASM (opcional pero recomendado)
20
21Sin esto, el cliente cae al **SSR**: cada interacción pide al server
22el SVG recompuesto (~12 KB por click). Con WASM, el cliente compone
23localmente — primera carga ~150 KB, después scrubbing instantáneo
24sin round-trip.
25
26```bash
27# Una sola vez:
28cargo install wasm-pack
29
30# Cada vez que cambie cosmobiologia-render o cosmobiologia-web:
31cd crates/modules/cosmobiologia/cosmobiologia-web
32wasm-pack build --release --target web \
33    --out-dir ../../../../apps/cosmobiologia-server/static/wasm
34```
35
36`wasm-pack` produce `cosmobiologia_web.js` +
37`cosmobiologia_web_bg.wasm` en
38`crates/apps/cosmobiologia-server/static/wasm/`. El server los sirve
39en `/static/wasm/*` y el `index.html` los importa con
40`import init, { render_model_to_svg } from
41'/static/wasm/cosmobiologia_web.js'`.
42
43Si el directorio NO existe (build incompleto), el server devuelve
44404 y el cliente cae al SSR automáticamente — sin error visible.
45
46---
47
48## 2. Levantar el server
49
50### Local (single-user, sin reverse proxy)
51
52```bash
53./target/release/cosmobiologia-server \
54    --port 8787 \
55    --bind 127.0.0.1 \
56    --db ~/.local/share/cosmobiologia/charts.db
57```
58
59Abrí `http://127.0.0.1:8787/`. La DB es la misma que usa la app
60desktop — cualquier carta creada en la app aparece en el browser
61y viceversa.
62
63### systemd (server público vía VPS)
64
65```ini
66# /etc/systemd/system/cosmobiologia.service
67[Unit]
68Description=Cosmobiología (server astrológico)
69After=network.target
70
71[Service]
72Type=simple
73User=cosmobio
74Group=cosmobio
75WorkingDirectory=/opt/cosmobiologia
76ExecStart=/opt/cosmobiologia/cosmobiologia-server \
77    --port 8787 \
78    --bind 127.0.0.1 \
79    --db /var/lib/cosmobiologia/charts.db \
80    --static-wasm /opt/cosmobiologia/static/wasm
81Environment=RUST_LOG=cosmobiologia_server=info,tower_http=warn
82Restart=on-failure
83RestartSec=3
84# Sandboxing básico
85ProtectSystem=strict
86ProtectHome=true
87PrivateTmp=true
88ReadWritePaths=/var/lib/cosmobiologia
89NoNewPrivileges=true
90
91[Install]
92WantedBy=multi-user.target
93```
94
95```bash
96sudo useradd -r -s /usr/sbin/nologin cosmobio
97sudo mkdir -p /opt/cosmobiologia/static/wasm /var/lib/cosmobiologia
98sudo cp target/release/cosmobiologia-server /opt/cosmobiologia/
99sudo cp -r crates/apps/cosmobiologia-server/static/wasm/* \
100    /opt/cosmobiologia/static/wasm/
101sudo chown -R cosmobio:cosmobio /opt/cosmobiologia /var/lib/cosmobiologia
102sudo systemctl daemon-reload
103sudo systemctl enable --now cosmobiologia
104sudo systemctl status cosmobiologia
105```
106
107---
108
109## 3. Reverse proxy (HTTPS + DNS bonito)
110
111Con dos subdominios apuntando al host:
112
113| DNS | Función |
114|-----|---------|
115| `cosmobiologia.gioser.net` | página web (HTML + WASM) |
116| `api.cosmobiologia.gioser.net` | endpoints `/api/*` (JSON / SVG) |
117
118Hoy el server sirve los dos roles en el mismo puerto — el split por
119subdominio lo hace el proxy, **sin cambiar nada del Rust**.
120
121### Caddyfile (recomendado — TLS automático con Let's Encrypt)
122
123```Caddyfile
124cosmobiologia.gioser.net {
125    encode gzip zstd
126    # Página web + estáticos + WASM
127    @api path /api/*
128    handle @api {
129        # Si el cliente pega un /api/ directo al subdominio principal,
130        # lo dejamos pasar (más amigable que 404).
131        reverse_proxy 127.0.0.1:8787
132    }
133    handle {
134        reverse_proxy 127.0.0.1:8787
135    }
136}
137
138api.cosmobiologia.gioser.net {
139    encode gzip zstd
140    # Solo los endpoints /api/*; rechaza el resto.
141    @api path /api/*
142    handle @api {
143        reverse_proxy 127.0.0.1:8787
144    }
145    handle {
146        respond "Use cosmobiologia.gioser.net para la página" 404
147    }
148}
149```
150
151### nginx (alternativa)
152
153```nginx
154# /etc/nginx/sites-available/cosmobiologia
155server {
156    server_name cosmobiologia.gioser.net;
157    listen 443 ssl http2;
158    # ssl_certificate / ssl_certificate_key — vía certbot
159    location / {
160        proxy_pass http://127.0.0.1:8787;
161        proxy_set_header Host $host;
162        proxy_set_header X-Real-IP $remote_addr;
163    }
164    gzip on;
165    gzip_types application/javascript application/wasm image/svg+xml application/json text/css text/html;
166}
167
168server {
169    server_name api.cosmobiologia.gioser.net;
170    listen 443 ssl http2;
171    location /api/ {
172        proxy_pass http://127.0.0.1:8787;
173        proxy_set_header Host $host;
174        proxy_set_header X-Real-IP $remote_addr;
175    }
176    location / { return 404; }
177    gzip on;
178    gzip_types application/json image/svg+xml;
179}
180```
181
182### DNS
183
184A records (o AAAA si IPv6) hacia tu VPS:
185
186```
187cosmobiologia.gioser.net.       A  <ip-del-VPS>
188api.cosmobiologia.gioser.net.   A  <ip-del-VPS>
189```
190
191---
192
193## 4. CORS y separación cliente↔API
194
195Hoy el server tiene `CorsLayer::permissive()` — cualquier origen
196puede hacer fetch contra `/api/*`. Eso es OK para:
197
198- **Single-user local**: nadie más alcanza al server.
199- **Demo público single-tenant**: misma DB para todos los visitantes,
200  sin datos sensibles. Los visitantes pueden leer y crear cartas
201  públicamente (es la naturaleza del demo).
202
203**No use CorsLayer::permissive en producción multi-usuario**. Para
204eso hay que:
2051. Agregar auth (sesiones / JWT / API key).
2062. Reemplazar con `CorsLayer::new().allow_origin(["https://cosmobiologia.gioser.net".parse().unwrap()])`.
2073. Volverte el `AllowCredentials::yes()` si vas a usar cookies.
208
209---
210
211## 5. Separación demo público ↔ desktop personal
212
213El path por default de la DB (`~/.local/share/cosmobiologia/charts.db`)
214es **compartido entre el server y la app desktop**. Eso es lo que
215querés en tu máquina local — abrís el browser y ves las mismas
216cartas que tenés en la app gpui.
217
218**Pero NO querés que el server público en
219`cosmobiologia.gioser.net` exponga TUS cartas privadas**. Para el
220demo público:
221
222```bash
223# En tu VPS:
224mkdir -p /var/lib/cosmobiologia
225# Empezás con DB vacía (la app crea las tablas al primer arranque).
226cosmobiologia-server --db /var/lib/cosmobiologia/charts.db
227```
228
229Si querés precargar cartas demo (Einstein, una carta natal pública),
230podés copiarlas desde tu DB local con la app, exportarlas como JSON
231via `/api/charts/:id`, y postearlas al server público con POST
232`/api/charts`. O simplemente abrir el browser, ir a "Nuevo
233contacto" → "Nueva carta…" y cargarlas a mano.
234
235---
236
237## 6. Backup
238
239La DB SQLite es **un solo archivo**. Backup = `cp` (mientras el
240server está parado, o usá `sqlite3 charts.db ".backup
241charts.bak"` con el server corriendo).
242
243```bash
244# Snapshot diario sin parar el server
245sqlite3 /var/lib/cosmobiologia/charts.db ".backup /var/backups/cosmobiologia-$(date +%F).db"
246```
247
248---
249
250## 7. Smoke test post-deploy
251
252```bash
253# Desde tu máquina:
254curl https://cosmobiologia.gioser.net/api/health
255# → {"status":"ok","service":"cosmobiologia-server"}
256
257curl https://cosmobiologia.gioser.net/api/sky | jq .title
258# → "Cielo 2026-05-19 00:55 UTC"
259
260# Abrí la página:
261open https://cosmobiologia.gioser.net/
262# (deberías ver la rueda del cielo + sidebar con "Cielo ahora")
263```
264
265Si el cliente WASM cargó, en la barra inferior verás "WASM".
266Si cayó al SSR, verás "SSR". Ambos modos son funcionales.
267
268---
269
270## 8. Endpoints públicos (referencia)
271
272| Método | Path | Función |
273|--------|------|---------|
274| GET | `/api/health` | healthcheck |
275| GET | `/api/tree` | árbol completo (groups/contacts/charts) |
276| GET | `/api/sky` | RenderModel "Cielo ahora" |
277| GET | `/api/sky.svg` | SVG agnóstico del cielo (server-side) |
278| GET | `/api/charts/:id` | Chart JSON |
279| GET | `/api/charts/:id/render?...` | RenderModel con overlays |
280| GET | `/api/charts/:id/svg?...` | SVG vía engine (svg_export) |
281| GET | `/api/charts/:id/wheel.svg?...` | SVG vía render agnóstico |
282| POST | `/api/charts` | crear carta |
283| PATCH | `/api/charts/:id` | editar label/birth/config |
284| DELETE | `/api/charts/:id` | borrar |
285| POST | `/api/groups` | crear grupo |
286| PATCH | `/api/groups/:id` | renombrar |
287| DELETE | `/api/groups/:id` | borrar |
288| POST | `/api/contacts` | crear contacto |
289| PATCH | `/api/contacts/:id` | renombrar |
290| DELETE | `/api/contacts/:id` | borrar |
291
292Query params del render (`?...`):
293
294- `offset_min=<i64>` — time scrubbing (minutos desde el natal).
295- `transit=1` — activa overlay de tránsito al `now` del server.
296- `prog_age=<f64>` — progresión secundaria a edad N.
297- `sa_age=<f64>` — solar arc a edad N.
298- `pd_age=<f64>` — primary directions GR (Naibod).

DEPLOY.md

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:

DNSFunción
cosmobiologia.gioser.netpágina web (HTML + WASM)
api.cosmobiologia.gioser.netendpoints /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:

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étodoPathFunción
GET/api/healthhealthcheck
GET/api/treeárbol completo (groups/contacts/charts)
GET/api/skyRenderModel "Cielo ahora"
GET/api/sky.svgSVG agnóstico del cielo (server-side)
GET/api/charts/:idChart 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/chartscrear carta
PATCH/api/charts/:ideditar label/birth/config
DELETE/api/charts/:idborrar
POST/api/groupscrear grupo
PATCH/api/groups/:idrenombrar
DELETE/api/groups/:idborrar
POST/api/contactscrear contacto
PATCH/api/contacts/:idrenombrar
DELETE/api/contacts/:idborrar

Query params del render (?...):