156 lines
5.5 KiB
Markdown
156 lines
5.5 KiB
Markdown
# HIDDEN SPECTRE — server edition 👻
|
|
|
|
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 <this-repo-url> /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)
|
|
```
|