From b063f3f34b324452f98ab4ae388205c55408133c Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 21 Jul 2026 20:48:28 +0000 Subject: [PATCH] Phase 0: deployment shell --- .dockerignore | 7 + .env.example | 7 + .gitignore | 5 + Dockerfile | 18 + README.md | 77 ++++ docker-compose.yml | 20 ++ lifegame-phase0.zip | Bin 0 -> 15259 bytes package-lock.json | 850 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 16 + public/index.html | 117 ++++++ server/index.js | 63 ++++ 11 files changed, 1180 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 lifegame-phase0.zip create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/index.html create mode 100644 server/index.js diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..498a1d0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +node_modules +data +npm-debug.log +.env +.git +.gitignore +*.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d6bff21 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Copy to .env and adjust. Nothing here is required for Phase 0. +# PORT=3000 +# DATA_DIR=/app/data + +# Arrives in later phases: +# ADMIN_PASSWORD=change-me # Phase 4 — protects the admin page +# PUBLIC_URL=https://game.example.com # Phase 1 — for building invite links diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff4483 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +data/ +.env +*.log +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a109f2a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM node:20-alpine + +WORKDIR /app + +# Install dependencies first (better layer caching). +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev + +# App source. +COPY server ./server +COPY public ./public + +ENV NODE_ENV=production +ENV PORT=3000 +ENV DATA_DIR=/app/data + +EXPOSE 3000 +CMD ["node", "server/index.js"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..46c22df --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# Life Journey — Deployment Shell (Phase 0) + +An empty, deployable shell for the async multiplayer board game. It contains no +game logic yet — its only job is to prove the deployment path works: the server +runs in Docker, is reachable through Nginx Proxy Manager over HTTPS, and +WebSockets survive the reverse proxy. + +Once both checks are green, adding the real game (Phase 1 onward) never touches +the deployment story again. + +## What's here + +``` +lifegame/ +├── docker-compose.yml +├── Dockerfile +├── package.json / package-lock.json +├── server/index.js # health endpoint + WebSocket echo +└── public/index.html # live HTTP + WebSocket status page +``` + +## 1. Put it on the homelab + +Drop the folder in your services directory (e.g. `~/homelab/lifegame/`) and build: + +```bash +cd ~/homelab/lifegame +docker compose up -d --build +docker compose logs -f # expect: "Life Journey (Phase 0) listening on :3000" +``` + +Verify locally on the host first: + +```bash +curl http://localhost:3000/api/health +# {"ok":true,"service":"life-journey","phase":0,...} +``` + +## 2. Point Nginx Proxy Manager at it + +Add a **Proxy Host**: + +- **Domain**: your chosen name, e.g. `game.example.com` +- **Scheme**: `http` +- **Forward Hostname / IP**: + - *Option A (default compose):* the homelab's LAN IP (e.g. `192.168.x.x`) + - *Option B (shared network):* `lifegame` +- **Forward Port**: `3000` +- **Websockets Support**: **ON** ← the easy-to-forget one +- **SSL tab**: request a new Let's Encrypt certificate, Force SSL on + +## 3. Confirm it works + +Open `https://game.example.com`. You should see two rows go green: + +- **Server (HTTP)** — the health endpoint responded +- **WebSocket** — a live socket connected through the proxy + +Click **Send WebSocket ping** and the status should show the echo came back. + +If HTTP is green but WebSocket is red, the proxy isn't upgrading the +connection — go back and toggle **Websockets Support** on the proxy host. + +## Data & backups + +The `lifegame-data` volume (mounted at `/app/data`) is where the SQLite +database and uploaded player tokens will live in later phases. It's empty now, +but include it in the same backup routine as your other self-hosted data. + +## What Phase 1 adds + +- The shared `game.js` rules engine (the pure reducer from the prototype) +- SQLite data model: games, players, board/rule config, tokens +- Rooms + shareable invite links / join codes +- `NetworkTransport` on the client — the one-line swap that makes the game + run across devices +- Persistence & reconnection, since async means tabs close and servers restart diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d180899 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +services: + lifegame: + build: . + container_name: lifegame + restart: unless-stopped + environment: + - PORT=3000 + - NODE_ENV=production + - DATA_DIR=/app/data + volumes: + - lifegame-data:/app/data + networks: + - proxy-net + +networks: + proxy-net: + external: true + +volumes: + lifegame-data: diff --git a/lifegame-phase0.zip b/lifegame-phase0.zip new file mode 100644 index 0000000000000000000000000000000000000000..317093e304d05e82ac475293222e4442c246e138 GIT binary patch literal 15259 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 Pe1&})9ADV}`1Jn)*HRmb literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..41345e3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,850 @@ +{ + "name": "life-journey-server", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "life-journey-server", + "version": "0.0.1", + "dependencies": { + "express": "^4.19.2", + "ws": "^8.18.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..da4f8de --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "life-journey-server", + "version": "0.0.1", + "private": true, + "type": "module", + "description": "Async multiplayer board game — deployment shell (Phase 0)", + "main": "server/index.js", + "scripts": { + "start": "node server/index.js", + "dev": "node --watch server/index.js" + }, + "dependencies": { + "express": "^4.19.2", + "ws": "^8.18.0" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..2ed60c8 --- /dev/null +++ b/public/index.html @@ -0,0 +1,117 @@ + + + + + +Life Journey — Deployment Check + + + + + + +
+

Life Journey

+

Deployment check · Phase 0 — no game here yet

+ +
+ + Server (HTTP) + checking… +
+ +
+ + WebSocket + connecting… +
+ + + +

+ Both dots green means the shell is deployed correctly and your reverse + proxy is passing WebSockets. If the WebSocket dot is red but HTTP is green, + enable Websockets Support on the proxy host in Nginx Proxy Manager. +

+
+ + + + diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..da2f7c1 --- /dev/null +++ b/server/index.js @@ -0,0 +1,63 @@ +/** + * Life Journey — Phase 0 deployment shell. + * + * This does the minimum needed to prove the plumbing works end to end: + * - serves the static frontend + * - answers GET /api/health (is the server reachable?) + * - accepts a WebSocket on /ws (does WS survive the reverse proxy?) + * + * There is deliberately NO game logic here yet. Phase 1 adds the shared + * game.js reducer, a SQLite-backed data model, rooms, and real actions. + * The data directory is created now so the Docker volume mount is validated. + */ + +import http from 'node:http'; +import path from 'node:path'; +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import express from 'express'; +import { WebSocketServer } from 'ws'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PORT = process.env.PORT || 3000; +const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '..', 'data'); + +// Ensure the persisted data directory exists (future: SQLite db + token uploads). +fs.mkdirSync(DATA_DIR, { recursive: true }); + +const app = express(); +app.use(express.json()); + +// --- Health check: lets the frontend (and you) confirm the server is up --- +app.get('/api/health', (_req, res) => { + res.json({ + ok: true, + service: 'life-journey', + phase: 0, + version: '0.0.1', + time: new Date().toISOString(), + }); +}); + +// --- Static frontend --- +const publicDir = path.join(__dirname, '..', 'public'); +app.use(express.static(publicDir)); + +const server = http.createServer(app); + +// --- WebSocket smoke test on /ws --- +// If this connects through your domain, Nginx Proxy Manager is passing the +// Upgrade/Connection headers correctly ("Websockets Support" is ON). Phase 1 +// reuses this exact endpoint to push game state to each device. +const wss = new WebSocketServer({ server, path: '/ws' }); +wss.on('connection', (ws) => { + ws.send(JSON.stringify({ type: 'welcome', msg: 'WebSocket connected' })); + ws.on('message', (raw) => { + ws.send(JSON.stringify({ type: 'echo', received: raw.toString() })); + }); +}); + +server.listen(PORT, () => { + console.log(`Life Journey (Phase 0) listening on :${PORT}`); + console.log(`Data directory: ${DATA_DIR}`); +});