summaryrefslogtreecommitdiffstats
path: root/webd/README.md
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-08 03:29:35 +0100
committersalmonik <salmonik@tuta.io>2026-11-08 03:29:35 +0100
commit058432130628d00764663ecf819a9adc6793768d (patch)
treed3f49f111fbfaa48647cca2600adcf9017af2d30 /webd/README.md
parentweb: serve latest.php through PHP and refuse stray PHP files (diff)
downloadzagan-bet-058432130628d00764663ecf819a9adc6793768d.tar.gz
zagan-bet-058432130628d00764663ecf819a9adc6793768d.zip
v5.0.0: protocol v5 and the browser frontend
Protocol v5 replaces newline lines with bounded [u32 len][text] frames and REQ/RES/EVT envelopes. Requests carry correlation ids and the server replays the last 256 replies per connection, so a gateway retry cannot double-debit and a late reply cannot clobber a newer request. HELLO/PONG carry server time; crash rounds announce started_at and push coalesced 10 Hz ticks. Crash now commits sha256(seed) before a round and reveals the seed when it ends, so clients recompute the crash point. Adds the webd/ gateway: one blocking thread, one ClientCore and one pinned upstream TLS connection per session, an HttpOnly session cookie, takeover, and a 60-second grace window. The browser sends Command JSON and renders View JSON; it never speaks the game protocol or stores credentials. Adds the vanilla TypeScript browser app at /play/ (login, lobby, all four games, history, notices, crash verification), the nginx /play and /ws routes, and the headless DOM smoke test. webd and the app stay out of the TUI/GUI/server build graph. The client/server version moves to 5.0.0 in lockstep; v4 peers are refused at HELLO.
Diffstat (limited to 'webd/README.md')
-rw-r--r--webd/README.md118
1 files changed, 118 insertions, 0 deletions
diff --git a/webd/README.md b/webd/README.md
new file mode 100644
index 0000000..ac95fdb
--- /dev/null
+++ b/webd/README.md
@@ -0,0 +1,118 @@
1# `webd` — browser gateway
2
3The browser cannot open the game's TCP/TLS stream, and the rules must not be
4reimplemented in JavaScript. `webd` is a server-side frontend: one blocking
5thread per browser session, one `ClientCore`, and one ordinary client TLS
6connection to the game server. nginx terminates TLS and proxies `/ws` here.
7
8It is a workspace member like `gui/`. Its dependencies never enter the TUI,
9GUI or server build graph.
10
11## Build
12
13```sh
14cargo build -p zagan-bet-webd --release
15```
16
17## Configuration
18
19| Variable | Default | Meaning |
20| --- | --- | --- |
21| `ZAGAN_WEB_BIND` | `127.0.0.1:8890` | Loopback address nginx proxies to |
22| `ZAGAN_WEB_ORIGIN` | `https://zagan.2137697.xyz` | Exact `Origin` allowed |
23| `ZAGAN_UPSTREAM` | `127.0.0.1:11525` | Game server `host:port` |
24| `ZAGAN_UPSTREAM_FINGERPRINT` | **required** | SPKI SHA-256, 64 lowercase hex |
25| `ZAGAN_GRACE_SECONDS` | `60` | Session survives this long without a socket |
26| `ZAGAN_WEB_COOKIE_SECURE` | on for `https://` origin | `Secure` on the cookie |
27
28The fingerprint is the same one the desktop client shows:
29
30```sh
31openssl x509 -in cert.pem -pubkey -noout \
32 | openssl pkey -pubin -outform DER \
33 | sha256sum
34```
35
36There is no trust-on-first-use and no unsafe mode. A mismatch refuses to
37connect and logs both fingerprints. Run the gateway on the same host as the
38game server (or pin the remote key and accept the network hop).
39
40## systemd
41
42Install `zagan-bet-webd` under `/usr/local/bin/`, then:
43
44```ini
45# /etc/systemd/system/zagan-bet-webd.service
46[Unit]
47Description=zagan-bet browser gateway
48After=network-online.target
49Wants=network-online.target
50
51[Service]
52Type=simple
53User=zagan
54Group=zagan
55EnvironmentFile=/etc/zagan-bet-webd.env
56ExecStart=/usr/local/bin/zagan-bet-webd
57Restart=on-failure
58RestartSec=2
59NoNewPrivileges=true
60PrivateTmp=true
61ProtectSystem=strict
62ProtectHome=true
63
64[Install]
65WantedBy=multi-user.target
66```
67
68```sh
69sudo install -m 0755 target/release/zagan-bet-webd /usr/local/bin/
70sudo install -d -m 0755 /etc
71sudo tee /etc/zagan-bet-webd.env >/dev/null <<'ENV'
72ZAGAN_UPSTREAM=127.0.0.1:11525
73ZAGAN_UPSTREAM_FINGERPRINT=<64 hex>
74ZAGAN_WEB_ORIGIN=https://zagan.2137697.xyz
75ZAGAN_WEB_BIND=127.0.0.1:8890
76ENV
77sudo systemctl daemon-reload
78sudo systemctl enable --now zagan-bet-webd
79```
80
81nginx needs:
82
83```nginx
84location = /ws {
85 proxy_pass http://127.0.0.1:8890;
86 proxy_http_version 1.1;
87 proxy_set_header Upgrade $http_upgrade;
88 proxy_set_header Connection "upgrade";
89 proxy_set_header Host $host;
90 proxy_read_timeout 3600s;
91 proxy_send_timeout 3600s;
92}
93```
94
95## Behaviour
96
97- The WebSocket handshake sets an HttpOnly, SameSite=Strict session cookie.
98 There is no credential storage in the browser or gateway.
99- A browser close starts the grace window. Upstream stays connected, crash
100 ticks are still applied to the core, and a reconnect with the same cookie
101 gets the current `View`. Expiry closes upstream; the server forfeits a live
102 hand, exactly like a desktop disconnect.
103- A second socket with the same cookie takes over: the old tab receives
104 `{"type":"replaced"}` and is closed.
105- A gateway restart loses live sessions, so their rounds are forfeited. There
106 is no protocol-level resume.
107- The browser sends `{"type":"command","command":...}` and receives
108 `view`/`notice`/`error`/`replaced`/`hello`/`pong` JSON. It never sees the
109 game wire or a certificate.
110
111## Security notes
112
113- Bind loopback. Do not expose the gateway directly.
114- Keep `ZAGAN_WEB_ORIGIN` exact. The cookie is SameSite=Strict, and the
115 `Origin` check is a second lock on cross-site WebSocket attempts.
116- The gateway is play-money only in v1. No payments, KYC or real-money copy.
117- `ZAGAN_UPSTREAM_FINGERPRINT` is the browser's credential path: a wrong or
118 missing pin must fail closed.