8.3 KiB
Self-Hosting
The game can run on your own Windows PC and use router port forwarding. Account
registration, sessions, characters, progression, inventory, loot history, and
leaderboards are stored in the SQLite database at data/game.db.
That database file is the persistent storage. It remains after the server or PC restarts. If the file is deleted or lost, all accounts and save data are lost, so keep regular backups.
Build and run
npm ci
npm run db:init
npm run build
$env:HOST = "127.0.0.1"
$env:PORT = "4173"
$env:TRUST_PROXY = "1"
npm start
Run npm run db:init after pulling schema changes. It creates missing tables and
seed content without erasing existing player data.
Internet access
Do not expose the Node port directly over unencrypted HTTP. Passwords would travel across the internet without encryption. Put Caddy or another HTTPS reverse proxy in front of the game:
game.example.com {
reverse_proxy 127.0.0.1:4173
}
Point a domain or dynamic-DNS name to your public IP, forward router ports 80 and 443 to the PC running Caddy, and allow those ports through Windows Firewall. Caddy obtains and renews the HTTPS certificate.
Keep the game server bound to 127.0.0.1. Set TRUST_PROXY=1 only when the
server can be reached solely through your local reverse proxy. This lets account
limits use the visitor's public IP instead of the proxy's address.
TrueNAS single-container hosting
TrueNAS SCALE runbook
This is the simplest TrueNAS setup. One container serves the browser game,
auth routes, game API routes, and one SQLite database. Use this when you want
iwanttoheal.phenomrom.com to host the playable browser version and you want
code updates to be a Git pull plus app restart.
Portainer is not required. Use TrueNAS Apps > Discover > Install via YAML.
Repository:
https://git.whoagland.com/phenom/i-want-to-heal.git
TrueNAS paths:
/mnt/usbssds/apps/iwanttoheal/app
/mnt/usbssds/apps/iwanttoheal/data
Create the app directory and clone the repo:
sudo mkdir -p /mnt/usbssds/apps/iwanttoheal
cd /mnt/usbssds/apps/iwanttoheal
sudo git clone https://git.whoagland.com/phenom/i-want-to-heal.git app
Because the clone was run with sudo, give the normal TrueNAS user ownership:
sudo chown -R truenas_admin:truenas_admin /mnt/usbssds/apps/iwanttoheal
Create the persistent data folder:
mkdir -p /mnt/usbssds/apps/iwanttoheal/data
Check that the production server file exists:
ls /mnt/usbssds/apps/iwanttoheal/app/server/production.mjs
If that file is missing, push the latest code to git.whoagland.com from the
development machine, then pull on TrueNAS:
cd /mnt/usbssds/apps/iwanttoheal/app
git pull
If Git fails with chmod ... Operation not permitted, do not use a media or SMB
dataset for the repo. Git needs normal file locking and chmod behavior. Create or
use a dedicated apps dataset and clone under /mnt/usbssds/apps/....
TrueNAS app YAML
In TrueNAS:
- Open Apps.
- Open Discover.
- Click the three-dot menu.
- Choose Install via YAML.
- Name the app
iwanttoheal. - Paste this YAML:
services:
iwanttoheal:
image: node:24-bookworm-slim
working_dir: /app
command: sh -lc "npm ci && npm run db:init && npm run build && npm start"
environment:
HOST: 0.0.0.0
PORT: "4173"
TRUST_PROXY: "1"
COOKIE_SECURE: "1"
CORS_ORIGINS: "http://localhost,https://localhost,capacitor://localhost,https://iwanttoheal.phenomrom.com,https://auth.phenomrom.com"
ports:
- "4173:4173"
volumes:
- /mnt/usbssds/apps/iwanttoheal/app:/app
- /mnt/usbssds/apps/iwanttoheal/data:/app/data
restart: unless-stopped
The app listens inside Docker on port 4173. The database lives at
/mnt/usbssds/apps/iwanttoheal/data/game.db because that host directory is
mounted into the container as /app/data. The startup command installs
dependencies, applies schema migrations, builds the web app, and starts the
production server.
Test the local TrueNAS service:
curl http://TRUENAS-IP:4173/api/auth/session
Expected response:
{"account":null,"profile":null}
Reverse proxy
Point iwanttoheal.phenomrom.com at the TrueNAS app through HTTPS. Do not expose
port 4173 directly to the internet. Put Caddy or another reverse proxy in
front:
iwanttoheal.phenomrom.com {
reverse_proxy TRUENAS-IP:4173
}
auth.phenomrom.com {
reverse_proxy TRUENAS-IP:4173
}
Both hostnames can point at the same container. iwanttoheal.phenomrom.com
serves the browser game. auth.phenomrom.com stays available as an auth URL for
Android or other clients that need a dedicated auth hostname.
DNS should point both hostnames at the public IP or dynamic DNS name that reaches
the reverse proxy. Forward public ports 80 and 443 to the reverse proxy host.
Test the public game and auth URLs:
curl https://iwanttoheal.phenomrom.com
curl https://auth.phenomrom.com/api/auth/session
Expected auth response:
{"account":null,"profile":null}
App build config
For the hosted browser game, no separate auth build setting is needed. The web
app can call same-origin routes like /api/auth/login and /api/profile.
For an Android build that should use the TrueNAS-hosted game API, build with:
npm run android:apk:truenas
If you intentionally want Android auth calls to use auth.phenomrom.com, also
set VITE_AUTH_API_BASE_URL=https://auth.phenomrom.com. Otherwise, leave it
unset and auth uses the same base URL as the game API.
Android runs the bundled web app from a local Capacitor origin, not from
iwanttoheal.phenomrom.com. The hosted server must allow that origin through
CORS, which is why the TrueNAS YAML includes http://localhost,
https://localhost, and capacitor://localhost.
Updating the TrueNAS game app
Push changes from the development machine to git.whoagland.com, then pull them
on TrueNAS:
cd /mnt/usbssds/apps/iwanttoheal/app
git pull
Restart the iwanttoheal app in the TrueNAS Apps UI after pulling. The app
command runs npm ci, npm run db:init, npm run build, and npm start on
startup, so dependency, schema, and browser bundle changes are applied each time
the container restarts.
Normal update workflow:
# development machine
git add .
git commit -m "Update game"
git push origin main
# TrueNAS shell
cd /mnt/usbssds/apps/iwanttoheal/app
git pull
Then restart the TrueNAS app.
Existing auth-only app
If iwanttoheal-auth was already created during earlier testing, the simplest
path is to stop that app and use the single iwanttoheal app above. The single
container serves both domains and avoids two processes sharing one SQLite file.
Account limits
Registration permits one account per public IP by default. Login and API rate limits also apply. To allow a shared household to create more accounts, run a local administrator command:
npm run accounts:ip -- set 203.0.113.42 4 Smith-household
npm run accounts:ip -- list
npm run accounts:ip -- remove 203.0.113.42
set changes the total number of accounts that address may create. remove
returns it to the default one-account limit. These commands must be run on the
host PC and are not exposed through the website.
An IP account limit reduces casual account spam, but it is not DDoS protection. Your router and internet connection can still be overwhelmed before requests reach the game. Keep Windows patched, expose only required ports, and use router or ISP protections where available.
Backups
Create a consistent live backup with:
npm run db:backup
Backups are written to backups/game-<timestamp>.db. Copy them to another disk
or cloud backup location; backups left only on the game PC will not help if its
drive fails. Test restoration occasionally by stopping the server, preserving
the current data/game.db, and placing a backup at that path.
Current scaling boundary
SQLite is a good fit for a small private server running one game process. Do not run multiple Node server instances against this setup.
Combat currently runs in the browser. It is suitable for a friend group, but a modified client could submit false completion metrics. Move combat simulation and run validation to the server before treating rankings as cheat-resistant.