From d9a9da5b5ea486c85f37d52d9e64cfcea75d7d92 Mon Sep 17 00:00:00 2001 From: Paul Grogan Date: Mon, 5 Oct 2026 09:53:44 -0700 Subject: [PATCH 1/6] update to bootstrap-5, jquery-4, chart.js 4, add trajectory charts, add docker-compose with traefik --- .dockerignore | 1 + .env.example | 16 ++++ README.md | 34 ++++++- docker-compose.yml | 39 ++++++++ hunt.js | 8 +- package.json | 16 +--- public/admin.html | 81 ++++++++++------- public/index.html | 111 +++++++++++------------ public/javascripts/admin.js | 166 +++++++++++++++++++++++++++++++---- public/javascripts/client.js | 15 ++-- public/stylesheets/style.css | 4 + 11 files changed, 356 insertions(+), 135 deletions(-) create mode 100644 .env.example create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore index c7e7df1..e9c25bb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,4 @@ node_modules public/scripts npm-debug.log* +.env diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..57f9811 --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +# Copy to .env and fill in before running `docker compose up -d`. + +# Public domain name for the game; its DNS record must point at this server. +HUNT_DOMAIN=hunt.example.com + +# Email address Let's Encrypt uses for certificate expiry notices. +ACME_EMAIL=admin@example.com + +# Password for the administrator interface (/admin.html). +HUNT_ADMIN_PASSWORD=change-me + +# Pass code players enter to join the game. +HUNT_USER_PASSCODE=change-me + +# Optional: use the Let's Encrypt staging server while testing to avoid rate limits. +# ACME_CA_SERVER=https://acme-staging-v02.api.letsencrypt.org/directory diff --git a/README.md b/README.md index a38f0d6..efb4388 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,41 @@ Multi-player stag hunting game. This application demonstrates a multi-player stag hunting game. Players choose between hunting stag or hare and are matched with either: 1) random number generator, 2) random but hidden player, or 3) random and identified player. -Note: the default pass code for users is `attila` and the default administrator password is `admin`. +Note: the default pass code for users is `attila` and the default administrator password is `admin`. Override them with the `HUNT_USER_PASSCODE` and `HUNT_ADMIN_PASSWORD` environment variables (required for the Docker Compose deployment below). ## Installation -This package can be used in two ways: either as a docker image or as a standalone application. +This package can be used in three ways: as a public deployment with Docker Compose, as a docker image, or as a standalone application. + +### Deployment with Docker Compose + +The included `docker-compose.yml` runs the application behind a [Traefik](https://traefik.io/) reverse proxy that obtains and renews HTTPS certificates from [Let's Encrypt](https://letsencrypt.org/). It requires a server with [Docker](https://www.docker.com/) (including Compose), ports 80 and 443 open to the internet, and a DNS record for your domain pointing at the server. + +Configuration is read from a `.env` file in this directory. Copy the template and edit it: +```shell +cp .env.example .env +``` + +| Variable | Required | Description | +|---|---|---| +| `HUNT_DOMAIN` | yes | Public domain name for the game, e.g. `hunt.example.com`. | +| `ACME_EMAIL` | yes | Email address Let's Encrypt uses for certificate expiry notices. | +| `HUNT_ADMIN_PASSWORD` | yes | Password for the administrator interface. | +| `HUNT_USER_PASSCODE` | yes | Pass code players enter to join the game. | +| `ACME_CA_SERVER` | no | Certificate authority URL. Set to `https://acme-staging-v02.api.letsencrypt.org/directory` while testing to avoid Let's Encrypt rate limits; remove it (and the `letsencrypt` volume) to switch to real certificates. | + +The `.env` file holds secrets: it is excluded from git and from the Docker image, so keep it only on the server. + +Start the application (Compose stops with an error naming any required variable that is missing): +```shell +docker compose up -d --build +``` +The application is then available at: + + * `https:///`: hunter interface + * `https:///admin.html`: administrator interface + +HTTP requests are redirected to HTTPS. Certificates are stored in the `letsencrypt` volume, so they persist across restarts. To apply changes to `.env` or the code, run `docker compose up -d --build` again; to stop the application, run `docker compose down`. ### Docker Image diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..93f13a5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +# Production deployment: the hunt app behind a Traefik reverse proxy with +# Let's Encrypt certificates. Configure with a .env file (see .env.example). +services: + traefik: + image: traefik:v3.7 + command: + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --entrypoints.web.address=:80 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --entrypoints.websecure.address=:443 + - --certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL:?set ACME_EMAIL in .env} + - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json + - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web + - --certificatesresolvers.letsencrypt.acme.caserver=${ACME_CA_SERVER:-https://acme-v02.api.letsencrypt.org/directory} + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - letsencrypt:/letsencrypt + restart: unless-stopped + + hunt: + build: . + environment: + HUNT_ADMIN_PASSWORD: ${HUNT_ADMIN_PASSWORD:?set HUNT_ADMIN_PASSWORD in .env} + HUNT_USER_PASSCODE: ${HUNT_USER_PASSCODE:?set HUNT_USER_PASSCODE in .env} + labels: + - traefik.enable=true + - traefik.http.routers.hunt.rule=Host(`${HUNT_DOMAIN:?set HUNT_DOMAIN in .env}`) + - traefik.http.routers.hunt.entrypoints=websecure + - traefik.http.routers.hunt.tls.certresolver=letsencrypt + - traefik.http.services.hunt.loadbalancer.server.port=3000 + restart: unless-stopped + +volumes: + letsencrypt: diff --git a/hunt.js b/hunt.js index 751e02f..5f7a962 100644 --- a/hunt.js +++ b/hunt.js @@ -16,6 +16,10 @@ module.exports = function(io) { var probCollab = 0.5; + // credentials are escaped the same way as login input so they compare equal + var userPasscode = validator.escape(process.env.HUNT_USER_PASSCODE || 'attila'); + var adminPassword = validator.escape(process.env.HUNT_ADMIN_PASSWORD || 'admin'); + function removeUser(userName) { // remove the user as anyone's partner Object.keys(users).forEach((i) => { @@ -76,7 +80,7 @@ module.exports = function(io) { var userInput = validator.escape(data.user + ''); var passcodeInput = validator.escape(data.passcode + ''); - if(passcodeInput !== 'attila') { + if(passcodeInput !== userPasscode) { socket.emit('login-auth', {'user': userInput, 'success': false, 'message': 'Incorrect pass code'}); } else { if(addUser(userInput, socket)) { @@ -97,7 +101,7 @@ module.exports = function(io) { return; } var passwordInput = validator.escape(data.password + ''); - if(passwordInput !== 'admin') { + if(passwordInput !== adminPassword) { socket.emit('login-auth', {'success': false, 'message': 'Incorrect password'}); } else if(admin !== null) { socket.emit('login-auth', {'success': false, 'message': 'Already logged in'}); diff --git a/package.json b/package.json index 227852b..1bd299e 100644 --- a/package.json +++ b/package.json @@ -29,27 +29,17 @@ "target": "public/scripts/", "packages": { "bootstrap": { - "version": "^4.6.2", - "src": "dist/", - "namespaced": true - }, - "popper.js": { - "version": "^1.16.1", + "version": "^5.3.8", "src": "dist/", "namespaced": true }, "jquery": { - "version": "^3.7.1", + "version": "^4.0.0", "src": "dist/", "namespaced": true }, - "open-iconic": { - "version": "^1.1.1", - "src": "font/", - "namespaced": true - }, "chart.js": { - "version": "^2.9.4", + "version": "^4.5.1", "src": "dist/", "namespaced": true }, diff --git a/public/admin.html b/public/admin.html index cc2d487..bfa51dc 100644 --- a/public/admin.html +++ b/public/admin.html @@ -10,32 +10,34 @@ -