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:
| 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 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).