Files
healer-man/DEPLOYMENT.md
T
2026-08-14 15:56:39 -04:00

282 lines
10 KiB
Markdown

# TrueNAS deployment
Healer Man uses the same local-Gitea deployment pattern as the previous
`i-want-to-heal-mmo` game: clone the Gitea bare repository through the TrueNAS
filesystem, mount the runnable checkout into one Node container, and update it
with a local fast-forward pull plus an app restart.
The public URL remains `https://iwanttoheal.phenomrom.com`. The old game and
Healer Man cannot both own host port `4173`; stop the old app before the final
cutover. Keep its checkout and database until the replacement is verified.
## What the TrueNAS server does
The `healer-man` TrueNAS app is the browser host and online account server. One
Node 24 process serves the production Vite bundle and authenticated `/api`
routes on port `4173`. Its SQLite database stores accounts, hashed credentials,
sessions, and cloud rosters under `/app/data/game.db`.
The source checkout and persistent data are separate mounts. Replacing the
container, rebuilding `dist`, or pulling source must not replace the database.
TrueNAS Gitea stores the source repository and APK release attachments; it is
not the game database.
Large runtime assets are published separately under `/app/content`. Android
clients download verified content objects from `/content` and retain them in
app-private storage for offline play. See `CONTENT_UPDATES.md` for the manifest,
atomic activation, compatibility, and rollback contract.
## Paths
```text
Local Gitea bare repository:
/mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/healer-man.git
Runnable working checkout:
/mnt/usbssds/apps/healer-man/app
Persistent game data:
/mnt/usbssds/apps/healer-man/data
Published downloadable content:
/mnt/usbssds/apps/healer-man/content
Database backups:
/mnt/usbssds/apps/healer-man/backups
Public URL:
https://iwanttoheal.phenomrom.com
Container/host port:
4173
```
The Gitea bare repository is not runnable. Clone it with Git; never copy it
with `cp`. The checkout contains several gigabytes of required GLB assets, so
the initial clone and first build will take substantially longer than later
fast-forward updates.
Do not turn the GLBs into Git LFS pointers without also changing this deployment
procedure. Gitea keeps LFS objects outside the bare repository, so a bare-path
clone alone cannot retrieve them.
## First installation
Create an empty Gitea repository named `phenom/healer-man`, then initialize and
push the local `D:\Projects\HealerMan` project to its `main` branch.
The current project directory is not yet a Git working tree. After creating the
empty Gitea repository, run this once from PowerShell on the development PC:
```powershell
Set-Location D:\Projects\HealerMan
git init -b main
git remote add origin https://git.whoagland.com/phenom/healer-man.git
git add .
git commit -m "Initial Healer Man import"
git push -u origin main
```
If the repository slug differs, change both the remote URL here and every
`healer-man.git` bare-repository path below. Do not commit `.env` files,
keystores, API tokens, `content`, `dist`, `dist-android`, or `.android-public`.
On TrueNAS, confirm the bare repository path. If the first command fails, use
the search command:
```sh
sudo test -d /mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/healer-man.git
sudo find /mnt -type d -name "healer-man.git" -prune -print 2>/dev/null
```
Clone entirely through the local filesystem:
```sh
sudo mkdir -p /mnt/usbssds/apps/healer-man/{app,data,content,backups}
sudo git config --global --add safe.directory \
/mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/healer-man.git
sudo git clone \
/mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/healer-man.git \
/mnt/usbssds/apps/healer-man/app
sudo chown -R truenas_admin:truenas_admin /mnt/usbssds/apps/healer-man
```
Verify the checkout:
```sh
git -C /mnt/usbssds/apps/healer-man/app remote -v
git -C /mnt/usbssds/apps/healer-man/app branch --show-current
ls /mnt/usbssds/apps/healer-man/app/package.json
ls /mnt/usbssds/apps/healer-man/app/server/server.mjs
```
Expected branch: `main`. Expected origin: the local Gitea path above.
## Install as a TrueNAS app
1. Stop the old `iwanttoheal-mmo` app so port `4173` is free.
2. Open **Apps** and then **Discover**.
3. Open the three-dot menu and select **Install via YAML**.
4. Name the new app `healer-man`.
5. Paste the following YAML:
```yaml
services:
healerman:
image: node:24-bookworm-slim
command: >-
sh -lc "npm ci --include=dev && npm run db:init && npm run content:publish && npm run build && npm start"
environment:
CORS_ORIGINS: "https://iwanttoheal.phenomrom.com,capacitor://localhost,http://localhost,https://localhost"
CONTENT_DIR: /app/content
DATA_DIR: /app/data
HOST: 0.0.0.0
NODE_OPTIONS: "--max-old-space-size=4096"
PORT: "4173"
SESSION_TTL_DAYS: "30"
STATIC_DIR: /app/dist
TRUST_PROXY: "true"
VITE_API_BASE_URL: "https://iwanttoheal.phenomrom.com"
init: true
ports:
- "4173:4173"
restart: unless-stopped
volumes:
- /mnt/usbssds/apps/healer-man/app:/app
- /mnt/usbssds/apps/healer-man/data:/app/data
- /mnt/usbssds/apps/healer-man/content:/app/content
working_dir: /app
```
Do not remove either persistent mount. `/app/data` owns account/cloud-save data;
`/app/content` owns downloadable manifests and immutable content objects. The
startup command installs locked dependencies, initializes the schema, publishes
new or changed content objects, builds the full browser bundle, and starts the
combined static/API/content server. Existing content objects are reused.
After startup, test on the LAN:
```sh
curl -I http://TRUENAS-IP:4173
curl http://TRUENAS-IP:4173/api/health
curl http://TRUENAS-IP:4173/content/manifest.json
```
Expected results are HTTP `200` and a JSON response containing
`"service":"healer-man"`. Keep the existing reverse proxy for
`iwanttoheal.phenomrom.com` pointed at `TRUENAS-IP:4173`; no DNS change is
required if that proxy target stays the same.
Verify account creation, sign-out/sign-in, character creation, and a second
login before deleting the old TrueNAS app. The old game's database is not
schema-compatible with Healer Man and must not be copied over the new one.
## Update workflow
Push `main` from the development PC. Then run on TrueNAS:
```sh
git -C /mnt/usbssds/apps/healer-man/app pull --ff-only \
/mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/healer-man.git \
main
```
Restart `healer-man` in the TrueNAS Apps UI afterward. Container startup
publishes immutable content objects and replaces `manifest.json` only after all
objects exist, then rebuilds the browser client and starts the server. Android
devices discover the small new manifest and download only missing or changed
files. Asset-only updates do not require a new APK. A failed fast-forward pull
deliberately leaves the running checkout unchanged; resolve diverged history on
the development machine rather than forcing the TrueNAS checkout.
To publish content manually without restarting the app container, run inside a
Node 24 environment that mounts the checkout and content volume:
```sh
CONTENT_DIR=/mnt/usbssds/apps/healer-man/content \
npm --prefix /mnt/usbssds/apps/healer-man/app run content:publish
```
## Database backup
Create a consistent SQLite backup before migrations or destructive maintenance:
```sh
DATA_DIR=/mnt/usbssds/apps/healer-man/data \
BACKUP_DIR=/mnt/usbssds/apps/healer-man/backups \
npm --prefix /mnt/usbssds/apps/healer-man/app run db:backup
```
Back up the whole `/mnt/usbssds/apps/healer-man/data` dataset with TrueNAS
snapshots as well. The script backup is convenient for point-in-time database
copies; it does not replace dataset-level protection.
## APK releases and Obtainium
Keep APK binaries out of the Git branch. The Android build uses `dist-android`,
which contains the game runtime, UI, equipment, and Wailing Caverns starter
content rather than the full multi-gigabyte browser catalog. Downloaded packs
survive APK updates.
Install JDK 21 and Android SDK Platform 36 on the development PC and set
`JAVA_HOME` plus `ANDROID_SDK_ROOT`. The build wrapper also recognizes the
project's existing portable JDK/SDK layout when present.
For each Android version, push `main` first. Then set the permanent signing-key
secrets and Gitea token in the development shell:
```powershell
$env:ANDROID_KEYSTORE_FILE = 'D:\secure\healer-man-release.jks'
$env:ANDROID_KEYSTORE_PASSWORD = '...'
$env:ANDROID_KEY_ALIAS = 'healer-man'
$env:ANDROID_KEY_PASSWORD = '...'
$env:GITEA_TOKEN = '...'
```
Do not commit those values or the keystore. Build, sign, checksum, create the
Gitea release/tag, and upload both attachments with:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/publish-gitea-release.ps1 `
-VersionName 0.1.1 `
-VersionCode 2 `
-ReleaseNotes 'Healer Man client update.'
```
The result contains:
```text
Tag: v0.1.1
APK: healer-man-0.1.1-release.apk
Checksum: healer-man-0.1.1-release.apk.sha256
```
Obtainium should monitor:
```text
https://git.whoagland.com/phenom/healer-man/releases
```
Generic Gitea is not listed as a dedicated Obtainium source. Add the public
releases URL above; if automatic detection does not select it correctly, set
the source override to **HTML** and filter APK links with:
```text
healer-man-.*-release\.apk$
```
The repository or a separate release-only repository must be publicly readable
for players. Never distribute a privileged Gitea token in the app or Obtainium
configuration.
Use one permanent Android release-signing key and increase `versionCode` for
every release. Losing or changing that key prevents installed copies from
updating in place. If the source repository is private, expose a public
read-only release repository or download page rather than storing a privileged
Gitea token on the game device.
Asset, texture, audio, and compatible data updates use `content:publish` and do
not need an Obtainium release. JavaScript/React gameplay changes, loader or
shader changes, native Android changes, permissions, or manifest-schema changes
require a new signed APK with a higher `versionCode`.