410 lines
15 KiB
Markdown
410 lines
15 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`. Production
|
|
browser and Android clients download verified content objects from `/content`;
|
|
browsers retain them in an app-managed cache and Android retains 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 content-source GLB assets,
|
|
so the initial clone and first content publication will take substantially
|
|
longer than later fast-forward updates. Production browser and Android bundles
|
|
stage only their compact required-content subset.
|
|
|
|
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`, `.web-public`, 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 "apt-get update && apt-get install -y --no-install-recommends python3 && rm -rf /var/lib/apt/lists/* && python3 --version && npm ci --include=dev && npm run db:init && npm run content:publish && npm run build && npm start"
|
|
environment:
|
|
BALANCE_LAB_MAX_CONCURRENT: "1"
|
|
BALANCE_LAB_MAX_QUEUED: "8"
|
|
BALANCE_LAB_PYTHON: "python3"
|
|
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
|
|
HEALER_MAN_ENABLE_SHELVED_MODES: "0"
|
|
NODE_OPTIONS: "--max-old-space-size=8192"
|
|
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 compact required-content browser
|
|
bundle, and starts the combined static/API/content server. Existing content
|
|
objects are reused.
|
|
|
|
### Required GM Balance Lab server dependency
|
|
|
|
Balance Lab uses the repository's Python controller to orchestrate the live
|
|
TypeScript combat runtime. The canonical YAML above therefore installs Python
|
|
3 before installing/building the app and explicitly selects `python3` with
|
|
`BALANCE_LAB_PYTHON`.
|
|
|
|
If an existing app reports `Could not start python3`, edit that app's YAML to
|
|
use this command:
|
|
|
|
```yaml
|
|
command: >-
|
|
sh -lc "apt-get update && apt-get install -y --no-install-recommends python3 && rm -rf /var/lib/apt/lists/* && python3 --version && npm ci --include=dev && npm run db:init && npm run content:publish && npm run build && npm start"
|
|
```
|
|
|
|
and add or correct these environment values:
|
|
|
|
```yaml
|
|
BALANCE_LAB_PYTHON: "python3"
|
|
BALANCE_LAB_MAX_CONCURRENT: "1"
|
|
BALANCE_LAB_MAX_QUEUED: "8"
|
|
```
|
|
|
|
The app stores job reports in
|
|
`/app/runtime-data/balance-lab/jobs/<job-id>/`, so the existing persistent
|
|
runtime-data mount also preserves generated reports and the GM app's run
|
|
history across container/server restarts. Each directory includes `job.json`
|
|
metadata used to rebuild the newest 100 history entries; do not exclude those
|
|
files when migrating the runtime-data dataset. An interrupted queued/running
|
|
job returns as failed after restart, while any job with a complete
|
|
`suite-report.json` returns as complete. Keep concurrency at one
|
|
unless the server has enough spare CPU for multiple full combat sweeps. All
|
|
Balance Lab routes independently require a server-authorized GM account.
|
|
|
|
After saving the YAML, restart the TrueNAS app and watch its startup log for a
|
|
line beginning with `Python 3.`. Reopen Balance Lab; the setup panel must show
|
|
**Server runtime ready** before **Run on server** becomes available. The server
|
|
now performs this preflight before queueing, so a missing runtime cannot create
|
|
another doomed job.
|
|
|
|
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, the visual workflow in
|
|
`RELEASE_RUNBOOK.md` is recommended. For a manual release, choose the next
|
|
version first. The example below upgrades `0.1.2` to `0.1.3`; replace `1003` if
|
|
it is not greater than every prior 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
|
|
npm run release:check
|
|
|
|
$VersionName = '0.1.3'
|
|
$VersionCode = 1003
|
|
npm version $VersionName --no-git-tag-version
|
|
$ReleaseVersion = [ordered]@{
|
|
versionName = $VersionName
|
|
versionCode = $VersionCode
|
|
} | ConvertTo-Json
|
|
[IO.File]::WriteAllText(
|
|
(Join-Path $PWD 'release-version.json'),
|
|
"$ReleaseVersion`n",
|
|
[Text.UTF8Encoding]::new($false)
|
|
)
|
|
|
|
npm run content:test
|
|
npm run server:test
|
|
npm test
|
|
npm run content:publish
|
|
npm run build
|
|
npm test -- --run src/avatar
|
|
npx vitest run src/game/inputManager.test.ts src/game/inputMath.test.ts
|
|
|
|
npm run release:check
|
|
git status --short
|
|
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
|
|
```
|
|
|
|
`content:publish` is a fail-closed pre-build release check. It must report all
|
|
54 WoW campaign dungeon packs and refuses to replace the manifest when a pack,
|
|
dependency, or catalog-referenced asset is missing. Its local `content/` output
|
|
is ignored and must not be staged.
|
|
|
|
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/`, `.web-public/`, 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. Browser
|
|
and Android clients discover the small new manifest and download only missing
|
|
or changed files for packs the player installed. 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, starter equipment, basic Human character,
|
|
and Wailing Caverns starter content. Optional character/skin packs, original
|
|
spell media, dungeons, and other large content remain outside the APK.
|
|
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.3 `
|
|
-VersionCode 1003 `
|
|
-ReleaseNotes 'Healer Man client update.'
|
|
```
|
|
|
|
The result contains:
|
|
|
|
```text
|
|
Tag: v0.1.3
|
|
APK: healer-man-0.1.3-release.apk
|
|
Checksum: healer-man-0.1.3-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`.
|