From 0d30ca1bea29ad40abe9846735c6f33b4e944109 Mon Sep 17 00:00:00 2001 From: jessikitty Date: Wed, 3 Jun 2026 10:01:31 +1000 Subject: [PATCH] Flesh out README with architecture, security, and Ubuntu+nginx deploy guide --- README.md | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e422c8..ddd0f3d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,155 @@ -# hidden-spectre-server +# HIDDEN SPECTRE — server edition 👻 -Node/Express backend + admin panel for Hidden Spectre AR ghost hunt. GIF ghost management, JWT auth, QR-based set recognition. Fan-made tribute, not affiliated with the LEGO Group. \ No newline at end of file +An augmented-reality ghost-hunting web app — a fan-made tribute to the AR play concept of the LEGO® Hidden Side™ app. This version adds a **Node/Express backend** with a secure admin panel for managing ghost GIFs and QR-based set recognition. + +> **Fan-made. Not affiliated with, sponsored by, or endorsed by the LEGO Group.** LEGO® and Hidden Side™ are trademarks of the LEGO Group. This project does not use LEGO logos or artwork. + +## What's here + +- **Game** (`public/`) — WebXR world-tracked AR with a camera+gyro fallback. Ghosts roam and you trap them by holding the reticle on them. Ghosts render as **animated GIF billboards** when a set is scanned, or as procedural wisps in free-hunt mode. +- **QR set recognition** — scan a set's QR code; the game asks the API which ghosts that set unlocks and spawns them (weighted by rarity). +- **Admin panel** (`/admin`) — JWT-protected login to upload/enable/disable/delete ghost GIFs and create/manage sets and their QR codes. +- **Backend** (`server/`) — Express 5, SQLite (`better-sqlite3`), bcrypt password hashing, JWT auth, image-only uploads via multer. + +## Architecture + +``` +Browser ──► nginx (HTTPS) ──► Node/Express (localhost:3000) + ├─ / game (static) + ├─ /admin admin UI (static) + ├─ /api/login bcrypt + JWT + ├─ /api/ghosts CRUD + GIF upload (auth) + ├─ /api/sets CRUD + ghost links (auth) + ├─ /api/scan/:code public — game reads roster + └─ /uploads/* served GIFs + └─ SQLite: server/data/spectre.db +``` + +## Security notes + +- Admin writes require a valid JWT (8h expiry). The public game only hits the read-only `/api/scan/:code`. +- Passwords are bcrypt-hashed (cost 12). The first admin is created by the seed script from `.env`. +- `JWT_SECRET` **must** be set or the server refuses to start. +- Uploads are restricted to GIF/PNG/WebP, max 8 MB, stored with random filenames. +- `.env`, the database, and uploaded files are git-ignored. + +--- + +## Deploy on Ubuntu behind nginx + +### 1. Install Node + build tools + +`better-sqlite3` is a native module and needs a compiler toolchain to build: + +```bash +sudo apt update +sudo apt install -y nodejs npm build-essential python3 +# (or install Node 20+ from NodeSource for a newer runtime) +``` + +### 2. Get the code and install + +```bash +git clone /opt/hidden-spectre +cd /opt/hidden-spectre +npm install # compiles better-sqlite3 — needs build-essential +``` + +### 3. Configure secrets + +```bash +cp .env.example .env +# generate a strong secret: +node -e "console.log('JWT_SECRET=' + require('crypto').randomBytes(48).toString('hex'))" >> .env +# then edit .env to set ADMIN_USER / ADMIN_PASS (used once for seeding) +nano .env +``` + +### 4. Create the first admin + +```bash +npm run seed # reads ADMIN_USER / ADMIN_PASS from .env +``` + +### 5. Run it as a service + +Create `/etc/systemd/system/hidden-spectre.service`: + +```ini +[Unit] +Description=Hidden Spectre server +After=network.target + +[Service] +Type=simple +WorkingDirectory=/opt/hidden-spectre +ExecStart=/usr/bin/node server/index.js +Restart=on-failure +Environment=NODE_ENV=production +User=www-data + +[Install] +WantedBy=multi-user.target +``` + +```bash +sudo chown -R www-data:www-data /opt/hidden-spectre +sudo systemctl daemon-reload +sudo systemctl enable --now hidden-spectre +``` + +### 6. Reverse-proxy with nginx + +In your existing HTTPS server block: + +```nginx +server { + listen 443 ssl; + server_name spectre.hideawaygaming.com.au; + + ssl_certificate /etc/letsencrypt/live/spectre.hideawaygaming.com.au/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/spectre.hideawaygaming.com.au/privkey.pem; + + client_max_body_size 10M; # allow GIF uploads + + location / { + proxy_pass http://127.0.0.1:3000; + 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; + } +} +``` + +```bash +sudo nginx -t && sudo systemctl reload nginx +``` + +HTTPS is required — the camera, gyro, and WebXR all refuse to run on an insecure origin. + +## Using it + +1. Go to `https://your-host/admin`, log in, upload a few ghost GIFs. +2. Create a set with a code like `SET-GRAVEYARD` and tick which ghosts it unlocks. +3. Make a QR code whose payload is exactly that code (any QR generator works) and print/display it. +4. On a phone, open `https://your-host/`, tap **Scan a Set**, scan the QR — those ghosts spawn. Or tap **Free hunt** for procedural ghosts with no set. + +## Asset tips for ghost GIFs + +- Transparent background (GIF or, better, animated WebP/PNG for alpha) reads best floating in space. +- Roughly square or portrait framing; the billboard auto-fits the image aspect. +- Keep file size modest (a few hundred KB) so they load fast on mobile data. +- `.obj` 3D minifigure ghosts are a planned future mode; this version is GIF/image billboards. + +## Files + +``` +server/index.js Express app + all routes +server/db.js SQLite schema +server/auth.js JWT issue/verify +server/upload.js multer image upload config +server/seed.js first-admin creation +public/ the game + /admin panel +public/js/qr.js QR scanning (BarcodeDetector + manual fallback) +```