Put a real world board behind the RANKINGS button
Ranked on KILLS IN A SINGLE RUN. Games survived is the same information at a tenth the resolution — most players land between 5 and 20, so that board is a wall of ties broken by who got there first. Games ride along as the tiebreak and as a column. The image is python:3.12-alpine running server.py now, not nginx. The board needs one process that can write, and everything the nginx conf did here — the no-cache headers and the manifest MIME type — is a few lines of stdlib. /data must be a persistent volume or every deploy resets the ladder to empty. A name belongs to whoever took it first: the client mints a random id once and the row remembers it, so a stranger cannot overwrite your score. The id never leaves the server. That is the whole security model and it is enough. The prompt to sign the board appears on exactly one occasion — a run that beat your own best kill count, on the tap that leaves the death screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1fccc11b1d
commit
3291246425
8 files changed
+374
-48
No files matched your search
+16
-9
@@ -1,9 +1,16 @@
|
||||
FROM nginx:alpine
|
||||
# no-cache => browsers revalidate every request (ETag 304s are cheap), so a new
|
||||
# deploy is picked up on plain reload. ponytail: switch to hashed filenames if traffic ever matters.
|
||||
# The exact-match location gives the manifest its own MIME type — nginx's default map has no
|
||||
# .webmanifest entry and Chrome refuses to install a homescreen app served as octet-stream.
|
||||
# (a bare `types {}` block would have REPLACED the whole map and broken .js/.png too.)
|
||||
RUN printf 'server {\n listen 80;\n root /usr/share/nginx/html;\n etag on;\n location = /manifest.webmanifest { default_type application/manifest+json; add_header Cache-Control "no-cache, must-revalidate"; }\n location / { add_header Cache-Control "no-cache, must-revalidate"; }\n}\n' > /etc/nginx/conf.d/default.conf
|
||||
COPY index.html manifest.webmanifest *.js /usr/share/nginx/html/
|
||||
COPY assets /usr/share/nginx/html/assets
|
||||
FROM python:3.12-alpine
|
||||
# Was nginx:alpine. The leaderboard needs one process that can WRITE, and nginx is not one.
|
||||
# Everything the nginx conf was doing here — no-cache so a deploy is picked up on a plain reload,
|
||||
# and a real MIME type for the manifest, without which Chrome refuses to install the homescreen
|
||||
# app — is a few lines in server.py. Standard library only: no pip, no requirements file.
|
||||
# ponytail: switch to hashed filenames if traffic ever matters.
|
||||
WORKDIR /app
|
||||
COPY index.html manifest.webmanifest *.js server.py /app/
|
||||
COPY assets /app/assets
|
||||
# THE SCORES LIVE HERE AND THIS MUST BE A PERSISTENT VOLUME. Without one the json sits in the
|
||||
# container's writable layer and every deploy silently resets the leaderboard to empty — which
|
||||
# looks exactly like the feature working until the first time you push.
|
||||
ENV SPLAT_DATA=/data
|
||||
VOLUME /data
|
||||
EXPOSE 80
|
||||
CMD ["python", "-u", "server.py"]
|
||||
Reference in new issue
Block a user