# 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, 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 $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 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.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`.