The high score table moves out of the browser and into a service, so every player is on one board. It is a Node process with SQLite, no build step and no native modules - node:sqlite ships with the runtime and Node runs the TypeScript directly. Everything in a request is treated as hostile: names forced to a printable subset, every number range-checked, and scores refused if they could not have happened in the days claimed. Bodies capped, submissions rate limited per address. It still cannot prove a score is real, and server/README.md says so plainly rather than implying otherwise. The game degrades properly without it: the board says it cannot reach town, posting happens behind the closing standings, and play is untouched. Deployment is three containers behind the host's nginx - the hub at /, the game at /lemonade/, the scores service at /api/ - so the game keeps its own container and a second game is just another service. Licensing: AGPL-3.0-or-later, Affero because the leaderboard is a network service. NOTICE.md credits Bob Jamison and Charlie Kellner, records that this is clean-room work, and is honest about the one thing it is not: some on-screen wording is quoted from the original and cannot be licensed by us. The street can now get better as well as worse. The summer fair brings the town out and lifts what they will pay; a rival on the next corner takes a share and lingers. Both show in the art and in the crowd. Over 500 seasons a player who reads the briefing survives every time and finishes around $25.80; one who ignores it goes broke 70% of the time.
51 lines
1.8 KiB
Plaintext
51 lines
1.8 KiB
Plaintext
# Host-side nginx for games.jcoffey.dev. TLS, logging and proxying only -
|
|
# nothing is read from disk here, every path goes to a container.
|
|
#
|
|
# Copy into the host's sites-available, adjust the certificate paths, then
|
|
# `nginx -t && systemctl reload nginx`.
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name games.jcoffey.dev;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/games.jcoffey.dev/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/games.jcoffey.dev/privkey.pem;
|
|
|
|
access_log /var/log/nginx/games.jcoffey.dev.access.log;
|
|
error_log /var/log/nginx/games.jcoffey.dev.error.log;
|
|
|
|
# The leaderboard. TRUST_PROXY=1 in the container means it reads the
|
|
# forwarded address, so the rate limit applies per player rather than
|
|
# counting every submission as coming from nginx.
|
|
location /api/ {
|
|
proxy_pass http://127.0.0.1:5184;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
|
|
location /lemonade/ {
|
|
proxy_pass http://127.0.0.1:5185/;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
|
|
# Everything else is the list of games.
|
|
location / {
|
|
proxy_pass http://127.0.0.1:5186;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name games.jcoffey.dev;
|
|
return 301 https://$host$request_uri;
|
|
}
|