342 lines
12 KiB
Markdown
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`.
|