From 15094a3f2d5aea79e1941fdf9b1eb40f93401e26 Mon Sep 17 00:00:00 2001 From: "Eric Meehan -X (ermeehan - ACTALENT SERVICES LLC at Cisco)" Date: Sun, 18 Oct 2026 14:03:00 -0400 Subject: [PATCH] Docker --- .dockerignore | 3 ++ Dockerfile | 22 +++++++++++ README.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++ compose.yaml | 11 ++++++ 4 files changed, 140 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 compose.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2d5393a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +* +!server.py +!static/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a5dc737 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.12-slim + +ENV PYTHONUNBUFFERED=1 \ + BUTTON_HOST=0.0.0.0 \ + BUTTON_PORT=8000 \ + BUTTON_DATA_FILE=/data/data.json \ + BUTTON_MAILING_LIST_FILE=/data/mailing_list.csv + +RUN useradd --system --uid 10001 --no-create-home button \ + && mkdir /data && chown button /data + +WORKDIR /app +COPY server.py ./ +COPY static ./static + +USER button +VOLUME /data +EXPOSE 8000 +# Python as PID 1 ignores SIGTERM; SIGINT raises KeyboardInterrupt for a clean exit. +STOPSIGNAL SIGINT + +CMD ["python", "server.py"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..587d05e --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# The Button + +A web game of patience and nerve. A 24-hour countdown resets every time any player presses the button. If the countdown ever reaches zero, the game is over forever, and the final global stats are published. + +Inspired by Reddit's [The Button](https://en.wikipedia.org/wiki/The_Button_(Reddit)) experiment. + +## How to play + +1. Sign up with a name and password (or log back in). +2. Press the button. The countdown resets to 24 hours. +3. You score points based on the zone the countdown was in when you pressed. The longer you wait, the more you score, but if everyone waits too long, the game ends. + +### Zones + +Higher-value zones are shorter, so scoring big is risky. + +| Zone | Points | Duration (24h timer) | +|--------|-------:|---------------------:| +| Green | 0 | 12h 11m | +| Blue | 2 | 6h 6m | +| Yellow | 4 | 3h 3m | +| Red | 8 | 1h 31m | +| Black | 16 | 46m | +| White | 32 | 23m | + +Every press records the zone, the points, and the exact remaining time in milliseconds. Players can view their own score, presses per zone, and press history at any time. Global stats (leaderboard, presses per zone, closest call, full press log) stay hidden until the game ends. + +Logged-in players can also join the mailing list. + +## Running + +### Docker (recommended) + +```sh +docker compose up -d --build +``` + +Open http://localhost:8000. Game data and the mailing list are stored in the `button-data` volume. + +```sh +docker compose logs -f # view logs +docker compose cp thebutton:/data/mailing_list.csv . # export the mailing list +docker compose down # stop (data is kept) +docker compose down -v # stop and delete all data (new game) +``` + +### Without Docker + +Requires Python 3.9+. No third-party packages. + +```sh +python server.py +``` + +Open http://127.0.0.1:8000. Delete `data.json` to start a new game. + +## Configuration + +Set via environment variables (in Docker, add them under `environment:` in `compose.yaml`). + +| Variable | Default | Description | +|-----------------------------|--------------------------|--------------------------------------| +| `BUTTON_DURATION_SECONDS` | `86400` | Countdown length. Zones scale with it. | +| `BUTTON_HOST` | `127.0.0.1` (`0.0.0.0` in Docker) | Address to listen on. | +| `BUTTON_PORT` | `8000` | Port to listen on. | +| `BUTTON_DATA_FILE` | `data.json` | Game state file. | +| `BUTTON_MAILING_LIST_FILE` | `mailing_list.csv` | Mailing list file. | + +Tip: `BUTTON_DURATION_SECONDS=60` makes for quick testing. + +## API + +| Method | Path | Auth | Description | +|--------|------------------|------|-----------------------------------------------| +| GET | `/api/state` | | Deadline, server time, zones, whether ended | +| POST | `/api/register` | | `{name, password}` → `{name, token}` | +| POST | `/api/login` | | `{name, password}` → `{name, token}` | +| POST | `/api/logout` | ✓ | Revoke the current session token | +| POST | `/api/press` | ✓ | Press the button | +| GET | `/api/me` | ✓ | Your score, zone counts, and press history | +| GET | `/api/stats` | | Global stats (403 until the game ends) | +| POST | `/api/subscribe` | ✓ | `{first_name, email, last_name?, phone?}` | + +Authenticated requests use `Authorization: Bearer `. + +## Security notes + +- Passwords are hashed with PBKDF2-SHA256 (600k iterations); session tokens are stored only as SHA-256 hashes. +- Logins lock for 15 minutes after 5 failed attempts per name. +- The server speaks plain HTTP. **Put it behind a TLS-terminating reverse proxy** (e.g. Caddy or nginx) before exposing it to the internet. +- Registration and mailing-list signup are not rate limited. + +## Project layout + +``` +server.py Game server and API (Python standard library only) +static/ Frontend (HTML, CSS, vanilla JS) +Dockerfile Container image +compose.yaml Docker Compose service and data volume +``` + +## Credits + +Created by [Eric Meehan](https://www.eom.dev/). Built with [GitHub Copilot](https://github.com/features/copilot). Inspired by [Reddit's Original "The Button" Game](https://en.wikipedia.org/wiki/The_Button_(Reddit)). diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..fee5b60 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,11 @@ +services: + thebutton: + build: . + ports: + - "8000:8000" + volumes: + - button-data:/data + restart: unless-stopped + +volumes: + button-data: