Files
healer-man/DEPLOYMENT.md
T

342 lines
12 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.
For the repeatable Git, TrueNAS, signed-APK, and Obtainium release checklist,
follow [RELEASE_RUNBOOK.md](RELEASE_RUNBOOK.md).
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/runtime-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 `F:\Projects\HealerMan` project to its `main` branch.
The project is already connected to Gitea. Verify the checkout and both remote
URLs from PowerShell with these exact commands:
```powershell
Set-Location F:\Projects\HealerMan
git status --short --branch
git remote -v
git branch --show-current
```
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/runtime-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/runtime-data
- /mnt/usbssds/apps/healer-man/content:/app/content
working_dir: /app
```
Do not remove either persistent mount. `/app/runtime-data` owns
account/cloud-save data; `/app/content` owns downloadable manifests and
immutable content objects. Never mount a volume at `/app/data`, because that
would hide the checkout's required `/app/data/loot/schema.sql`. 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
For a code update that includes a new APK, choose the next version first. The
example below upgrades `0.1.0` to `0.1.1`; replace `2` if it is not the next
unused Android `versionCode`. Then run these commands in **Windows PowerShell**
on the development PC. Stop if any test or build fails:
```powershell
Set-Location F:\Projects\HealerMan
$VersionName = '0.1.1'
$VersionCode = 2
npm version $VersionName --no-git-tag-version
npm run content:test
npm run server:test
npm test
npm run build
npm test -- --run src/avatar
npx vitest run src/game/inputManager.test.ts src/game/inputMath.test.ts
git status --short
git diff --check
git add -A
git status --short
git diff --cached --stat
git diff --cached --check
git commit -m "Release Healer Man $VersionName"
git push origin main
git rev-parse HEAD
```
Before `git commit`, read the staged file list printed by `git status --short`.
Do not commit a `.env` file, password, API token, keystore, APK, database,
`content/`, `dist/`, `dist-android/`, or `.android-public/` output. The final
command prints the exact deployed commit ID.
Next, open the TrueNAS Apps UI and **stop** `healer-man`. Open a TrueNAS shell
and run these exact commands:
```sh
sudo mkdir -p /mnt/usbssds/apps/healer-man/backups
if sudo test -f /mnt/usbssds/apps/healer-man/data/game.db; then
sudo cp -p \
/mnt/usbssds/apps/healer-man/data/game.db \
/mnt/usbssds/apps/healer-man/backups/game-$(date +%Y%m%d-%H%M%S).db
fi
sudo git config --global --add safe.directory \
/mnt/usbssds/apps/healer-man/app
sudo 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
sudo chown -R truenas_admin:truenas_admin \
/mnt/usbssds/apps/healer-man/app
sudo git -C /mnt/usbssds/apps/healer-man/app rev-parse HEAD
```
The TrueNAS commit ID must match the one printed on the development PC. Start
`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.
After the logs say the server is listening on port `4173`, run on TrueNAS:
```sh
curl -fI http://127.0.0.1:4173/
curl -fsS http://127.0.0.1:4173/api/health
curl -fsS http://127.0.0.1:4173/content/manifest.json | head -c 500
```
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, complete the exact update workflow above first. Then
set the permanent signing-key secrets and Gitea token in PowerShell:
```powershell
$env:ANDROID_KEYSTORE_FILE = 'F:\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`.