291 lines
8.3 KiB
Markdown
291 lines
8.3 KiB
Markdown
# 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
|
|
|
|
```powershell
|
|
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:
|
|
|
|
```caddyfile
|
|
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:
|
|
|
|
```text
|
|
https://git.whoagland.com/phenom/i-want-to-heal.git
|
|
```
|
|
|
|
TrueNAS paths:
|
|
|
|
```text
|
|
/mnt/usbssds/apps/iwanttoheal/app
|
|
/mnt/usbssds/apps/iwanttoheal/data
|
|
```
|
|
|
|
Create the app directory and clone the repo:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
sudo chown -R truenas_admin:truenas_admin /mnt/usbssds/apps/iwanttoheal
|
|
```
|
|
|
|
Create the persistent data folder:
|
|
|
|
```sh
|
|
mkdir -p /mnt/usbssds/apps/iwanttoheal/data
|
|
```
|
|
|
|
Check that the production server file exists:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
1. Open **Apps**.
|
|
2. Open **Discover**.
|
|
3. Click the three-dot menu.
|
|
4. Choose **Install via YAML**.
|
|
5. Name the app `iwanttoheal`.
|
|
6. Paste this YAML:
|
|
|
|
```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:
|
|
|
|
```sh
|
|
curl http://TRUENAS-IP:4173/api/auth/session
|
|
```
|
|
|
|
Expected response:
|
|
|
|
```json
|
|
{"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:
|
|
|
|
```caddyfile
|
|
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:
|
|
|
|
```sh
|
|
curl https://iwanttoheal.phenomrom.com
|
|
curl https://auth.phenomrom.com/api/auth/session
|
|
```
|
|
|
|
Expected auth response:
|
|
|
|
```json
|
|
{"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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
# 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:
|
|
|
|
```powershell
|
|
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:
|
|
|
|
```powershell
|
|
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.
|