# 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 "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 compact required-content 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 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 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 ``` 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`.