fix(deploy): il gioco non si collegava — /ws riscritto da try_files e tsx fuori dal PATH #1

Merged
a.tartaglia merged 2 commits from a.balena/sparatutto:fix/produzione-ws-e-avvio into main 2026-08-29 20:30:19 +00:00
Contributor

Il deploy Coolify di sparatutto.lascuolaopensource.org rispondeva 502, e una volta tolto di mezzo il 502 il gioco comunque non si collegava. Sono tre guasti sovrapposti: uno era nell'infrastruttura (già sistemato, fuori da questa PR), due sono qui.

1. /ws non arrivava mai al server di gioco

Nel Caddyfile il proxy del WebSocket e il fallback SPA stavano piatti nello stesso blocco:

@ws path /ws
reverse_proxy @ws 127.0.0.1:8080

file_server
try_files {path} /index.html

Caddy però non esegue le direttive nell'ordine in cui le scrivi: le riordina, e in quell'ordine try_files sta fra le manipolazioni della richiesta, molto prima di reverse_proxy. Siccome /ws non è un file dentro client/dist, try_files lo riscriveva in /index.html; a quel punto il matcher path /ws non faceva più match e il proxy non scattava mai.

Verificato con un handshake vero contro il container in produzione:

$ curl -i -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
       -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
       -H 'Sec-WebSocket-Version: 13' http://<container>/ws
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8      <-- la home, non un upgrade

Ora i due percorsi stanno in due handle distinti, mutuamente esclusivi e valutati nell'ordine scritto — che è poi la stessa ricetta già scritta in docs/HOSTING.md:174. Il matcher resta /ws esatto come prima: cambia l'ordine, non cosa viene intercettato.

2. Il server di gioco non è mai partito — tsx non è nel PATH

Nei log del container, dal primissimo avvio:

/usr/local/bin/avvia-produzione.sh: line 14: tsx: not found

Il binario c'è ed è sano (node_modules/.bin/tsx -> ../tsx/dist/cli.mjs, tsx v4.23.1), ma /app/node_modules/.bin non è nel PATH di node:22-alpine: invocato nudo non viene trovato. Lo script ora si mette quella cartella nel PATH.

Il guasto è rimasto invisibile per via di una corsa nel guardiano: era la subshell del server, morendo, a dover ammazzare Caddy con una pkill, ma tsx moriva prima che Caddy fosse partito e la pkill non trovava nessuno. Caddy nasceva subito dopo, il wait non tornava più e il container restava Up — Coolify lo dava per sano mentre metà app era morta, e il sito serviva la home statica di un gioco a cui era impossibile connettersi. La ronda passa quindi al guscio principale: le subshell lasciano solo scritto chi è caduto e con che codice, il guscio aspetta quel certificato di morte e ammazza il superstite, che a quel punto esiste di sicuro.

Come sono state verificate

Non a occhio: container usa-e-getta creato dalla stessa immagine di produzione, con dentro solo questi due file sostituiti, attaccato alla rete coolify e interrogato dal proxy.

--- log ---
SPARA server v3 — 9 armi, mappe: RIG-7 | CORSAIR | BARI | ...
in ascolto su ws://0.0.0.0:8080
--- processi ---
node /app/node_modules/.bin/tsx server/src/index.ts
caddy run --config /etc/caddy/Caddyfile
--- GET / ---
200 text/html; charset=utf-8
--- upgrade WebSocket su /ws ---
HTTP/1.1 101 Switching Protocols
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

E il guardiano in entrambi i versi: ammazzando il server il container esce 143 scrivendo SPARA: e' caduto server (uscita 143), spengo il container.; ammazzando Caddy esce dichiarando l'altro caduto. Con restart: unless-stopped (la policy che Coolify mette al container) un avvio a metà ora si vede subito invece di mascherarsi. Container di prova rimosso a fine verifica.

3. Il 502 — non è in questa PR, era nel proxy

Per completezza, la causa del gateway error: sul proxy Caddy dell'host Coolify c'erano due definizioni dello stesso dominio, la label generata da Coolify (reverse_proxy 10.0.1.10:80, giusta) e uno snippet scritto a mano in /data/coolify/proxy/caddy/dynamic/sparatutto.caddy che puntava a reverse_proxy sparatutto:80. In questi casi caddy-docker-proxy scarta il blocco delle label ([ERROR] Removing invalid block: ambiguous site definition) e tiene lo snippet — il cui upstream sparatutto in Docker non risolve, perché l'unico alias di rete del container è <uuid>-<timestamp>. Da qui il 502, con il container invece perfettamente vivo.

Lo snippet è stato spostato in /data/coolify/proxy/caddy/sparatutto.caddy.disabled-20260829 e il dominio è tornato a rispondere 200. Non va reintrodotto: le label di Coolify si aggiornano da sole a ogni deploy, lo snippet no.


Dopo il merge serve un redeploy da Coolify perché i due file stanno dentro l'immagine.

Il deploy Coolify di `sparatutto.lascuolaopensource.org` rispondeva **502**, e una volta tolto di mezzo il 502 il gioco comunque non si collegava. Sono tre guasti sovrapposti: uno era nell'infrastruttura (già sistemato, fuori da questa PR), due sono qui. ## 1. `/ws` non arrivava mai al server di gioco Nel `Caddyfile` il proxy del WebSocket e il fallback SPA stavano piatti nello stesso blocco: ``` @ws path /ws reverse_proxy @ws 127.0.0.1:8080 file_server try_files {path} /index.html ``` Caddy però non esegue le direttive nell'ordine in cui le scrivi: le riordina, e in quell'ordine `try_files` sta fra le manipolazioni della richiesta, **molto prima** di `reverse_proxy`. Siccome `/ws` non è un file dentro `client/dist`, `try_files` lo riscriveva in `/index.html`; a quel punto il matcher `path /ws` non faceva più match e il proxy non scattava mai. Verificato con un handshake vero contro il container in produzione: ``` $ curl -i -H 'Connection: Upgrade' -H 'Upgrade: websocket' \ -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \ -H 'Sec-WebSocket-Version: 13' http://<container>/ws HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 <-- la home, non un upgrade ``` Ora i due percorsi stanno in due `handle` distinti, mutuamente esclusivi e valutati nell'ordine scritto — che è poi la stessa ricetta già scritta in `docs/HOSTING.md:174`. Il matcher resta `/ws` esatto come prima: cambia l'ordine, non cosa viene intercettato. ## 2. Il server di gioco non è mai partito — `tsx` non è nel PATH Nei log del container, dal primissimo avvio: ``` /usr/local/bin/avvia-produzione.sh: line 14: tsx: not found ``` Il binario c'è ed è sano (`node_modules/.bin/tsx -> ../tsx/dist/cli.mjs`, tsx v4.23.1), ma `/app/node_modules/.bin` non è nel PATH di `node:22-alpine`: invocato nudo non viene trovato. Lo script ora si mette quella cartella nel PATH. Il guasto è rimasto **invisibile** per via di una corsa nel guardiano: era la subshell del server, morendo, a dover ammazzare Caddy con una `pkill`, ma `tsx` moriva prima che Caddy fosse partito e la `pkill` non trovava nessuno. Caddy nasceva subito dopo, il `wait` non tornava più e il container restava `Up` — Coolify lo dava per sano mentre metà app era morta, e il sito serviva la home statica di un gioco a cui era impossibile connettersi. La ronda passa quindi al guscio principale: le subshell lasciano solo scritto chi è caduto e con che codice, il guscio aspetta quel certificato di morte e ammazza il superstite, che a quel punto esiste di sicuro. ## Come sono state verificate Non a occhio: container usa-e-getta creato **dalla stessa immagine di produzione**, con dentro solo questi due file sostituiti, attaccato alla rete `coolify` e interrogato dal proxy. ``` --- log --- SPARA server v3 — 9 armi, mappe: RIG-7 | CORSAIR | BARI | ... in ascolto su ws://0.0.0.0:8080 --- processi --- node /app/node_modules/.bin/tsx server/src/index.ts caddy run --config /etc/caddy/Caddyfile --- GET / --- 200 text/html; charset=utf-8 --- upgrade WebSocket su /ws --- HTTP/1.1 101 Switching Protocols Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ``` E il guardiano in entrambi i versi: ammazzando il server il container esce `143` scrivendo `SPARA: e' caduto server (uscita 143), spengo il container.`; ammazzando Caddy esce dichiarando l'altro caduto. Con `restart: unless-stopped` (la policy che Coolify mette al container) un avvio a metà ora si vede subito invece di mascherarsi. Container di prova rimosso a fine verifica. ## 3. Il 502 — non è in questa PR, era nel proxy Per completezza, la causa del *gateway error*: sul proxy Caddy dell'host Coolify c'erano **due definizioni dello stesso dominio**, la label generata da Coolify (`reverse_proxy 10.0.1.10:80`, giusta) e uno snippet scritto a mano in `/data/coolify/proxy/caddy/dynamic/sparatutto.caddy` che puntava a `reverse_proxy sparatutto:80`. In questi casi `caddy-docker-proxy` **scarta il blocco delle label** (`[ERROR] Removing invalid block: ambiguous site definition`) e tiene lo snippet — il cui upstream `sparatutto` in Docker non risolve, perché l'unico alias di rete del container è `<uuid>-<timestamp>`. Da qui il 502, con il container invece perfettamente vivo. Lo snippet è stato spostato in `/data/coolify/proxy/caddy/sparatutto.caddy.disabled-20260829` e il dominio è tornato a rispondere **200**. Non va reintrodotto: le label di Coolify si aggiornano da sole a ogni deploy, lo snippet no. --- Dopo il merge serve un redeploy da Coolify perché i due file stanno dentro l'immagine.
Nel Caddyfile del container il proxy del WebSocket e il fallback SPA stavano
piatti nello stesso blocco. Caddy pero' non esegue le direttive nell'ordine in
cui sono scritte: le riordina, e in quell'ordine `try_files` sta fra le
manipolazioni della richiesta, molto prima di `reverse_proxy`.

Risultato: `/ws` non e' un file dentro client/dist, quindi try_files lo
riscriveva in `/index.html`; a quel punto il matcher `path /ws` non faceva piu'
match e il reverse_proxy non scattava mai. Un handshake WebSocket verso
`/ws` tornava `200 text/html` con dentro la home invece di `101 Switching
Protocols`, e il gioco non si collegava.

I due percorsi ora stanno in due `handle` distinti — mutuamente esclusivi e
valutati nell'ordine scritto — che e' poi la stessa ricetta gia' scritta in
docs/HOSTING.md. Il matcher resta `/ws` esatto come prima: cambia l'ordine, non
cosa viene intercettato.

Verificato su un container usa-e-getta costruito dalla stessa immagine di
produzione: `GET /` -> 200 text/html, upgrade su `/ws` -> 101 Switching
Protocols.

Co-Authored-By: Claude Opus 5 <[email protected]>
In produzione il container e' sempre partito a meta'. Nei log, dal primo
avvio: `avvia-produzione.sh: line 14: tsx: not found`. Il binario c'e' ed e'
sano, ma sta in /app/node_modules/.bin, che non e' nel PATH dell'immagine
node:22-alpine; invocato nudo non viene trovato. Lo script ora si mette quella
cartella nel PATH.

Il guasto e' rimasto invisibile per via di una corsa nel guardiano: era la
subshell del server, morendo, a dover ammazzare Caddy con una pkill, ma tsx
moriva prima che Caddy fosse partito e la pkill non trovava nessuno. Caddy
nasceva subito dopo, il `wait` non tornava piu' e il container restava Up —
Coolify lo dava per sano mentre meta' app era morta, e il sito serviva la home
statica di un gioco a cui era impossibile connettersi.

La ronda passa quindi al guscio principale: le subshell si limitano a lasciare
scritto chi e' caduto e con che codice, il guscio aspetta quel certificato di
morte e solo allora ammazza il superstite — che a quel punto esiste di sicuro.

Verificato su un container usa-e-getta dalla stessa immagine di produzione: il
server parte ("in ascolto su ws://0.0.0.0:8080"); ammazzando il server il
container esce 143, ammazzando Caddy esce dichiarando il caduto. Con
`restart: unless-stopped` un avvio a meta' ora si vede subito invece di
mascherarsi.

Co-Authored-By: Claude Opus 5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
a.tartaglia/sparatutto!1
No description provided.