Files
healer-man/RELEASE_RUNBOOK.md
T

607 lines
19 KiB
Markdown

# Healer Man update and release runbook
Use this checklist whenever game changes need to be pushed to Gitea, deployed
to the TrueNAS server, and released as an Obtainium-compatible APK.
The examples below use Android version `0.1.1` and `versionCode` `2`. Replace
both with the next values for the release you are making. Every APK must use a
larger `versionCode` than every APK previously released.
## What requires a new APK?
Use the full procedure in this document when the update changes any of these:
- React, TypeScript, JavaScript, gameplay, UI, shaders, or asset-loading code.
- Capacitor or native Android code, permissions, or configuration.
- The downloadable-content manifest format or compatibility rules.
- Starter content that must work immediately after a fresh offline install.
For an asset-only update, such as a compatible GLB, texture, audio file, or data
file, use the shorter procedure at the end. Those updates can be published by
the server and downloaded by an already-installed APK without an Obtainium
release.
Single-player remains offline-capable. A fresh APK includes the runtime and
starter content. Additional content that a player has downloaded is retained
in Android app-private storage and can be used offline after download.
## Fixed locations
```text
Development checkout:
F:\Projects\HealerMan
Gitea repository:
https://git.whoagland.com/phenom/healer-man
Git push endpoint:
ssh://git@192.168.1.180:30009/phenom/healer-man.git
TrueNAS working checkout:
/mnt/usbssds/apps/healer-man/app
TrueNAS bare Gitea repository:
/mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/healer-man.git
Persistent database:
/mnt/usbssds/apps/healer-man/data/game.db
Published downloadable content:
/mnt/usbssds/apps/healer-man/content
Public game URL:
https://iwanttoheal.phenomrom.com
```
The commands in the Windows sections are PowerShell commands. The commands in
the TrueNAS sections are shell commands entered after signing in to TrueNAS.
## One-time release setup
These items only need to be configured once on the development PC.
### 1. Confirm Git uses LAN SSH for pushes
```powershell
Set-Location F:\Projects\HealerMan
git remote -v
ssh -T -p 30009 git@192.168.1.180
```
The SSH test should say that Gitea authenticated you as `phenom`. The current
repository is configured to fetch over HTTPS and push over LAN SSH.
If the push URL ever needs to be restored, run:
```powershell
git remote set-url --push origin `
ssh://git@192.168.1.180:30009/phenom/healer-man.git
```
### 2. Install the Android build requirements
Install JDK 21 and Android SDK Platform 36. Configure their actual paths, then
verify them:
```powershell
$env:JAVA_HOME = 'C:\path\to\jdk-21'
$env:ANDROID_SDK_ROOT = "$env:LOCALAPPDATA\Android\Sdk"
& "$env:JAVA_HOME\bin\java.exe" -version
Test-Path "$env:ANDROID_SDK_ROOT\platforms\android-36\android.jar"
```
The last command must return `True`. The build wrapper can also use the portable
JDK and Android SDK locations described in `scripts/runAndroidGradle.ps1`.
### 3. Create and protect one permanent signing key
Skip this step if the permanent Healer Man release key already exists.
```powershell
New-Item -ItemType Directory -Force F:\secure
keytool -genkeypair -v `
-keystore F:\secure\healer-man-release.jks `
-alias healer-man `
-keyalg RSA `
-keysize 4096 `
-validity 10000
```
Back up the keystore and its passwords somewhere secure. Do not put them in the
repository. Android will not install a future update over the existing game if
it is signed with a different key.
### 4. Create a Gitea access token
In Gitea, create an access token for `phenom` that can create repository
releases and upload attachments to `phenom/healer-man`. Store it in a password
manager. Never add the token to a file in the repository or distribute it to
players.
Obtainium must be able to read the release without your Gitea credentials.
Either make `phenom/healer-man` publicly readable or publish the APK to a
separate public release-only repository. Gitea release visibility follows the
repository's access rules.
### 5. Confirm the TrueNAS app has the correct data mount
The persistent database must be mounted at `/app/runtime-data`. Do **not** mount
anything at `/app/data`, because `/app/data/loot/schema.sql` is source data that
the build needs. The complete known-good YAML is in the appendix.
## Full release: Git, server, and APK
### Step 1: Open the project and inspect every change
```powershell
Set-Location F:\Projects\HealerMan
git status --short --branch
git diff --stat
git diff
```
Check that every listed file belongs to this update. Do not use `git reset
--hard` or discard unfamiliar changes. Before continuing, make sure the project
contains no passwords, Gitea tokens, `.env` files, keystores, database files, or
other secrets.
### Step 2: Select the Android version
Choose both values before building:
```powershell
$VersionName = '0.1.1'
$VersionCode = 2
```
- `VersionName` is the player-facing semantic version.
- `VersionCode` is Android's integer update counter. It must always increase.
- Record the last used `versionCode` in the Gitea release notes or your release
records so it is never accidentally reused.
Update `package.json` and `package-lock.json` to the same version:
```powershell
npm version $VersionName --no-git-tag-version
```
Do not create a local Git tag with `npm version`; the release script creates the
Gitea tag after the signed APK builds successfully.
### Step 3: Run automated validation
```powershell
npm run content:test
npm run server:test
npm test
npm run build
```
All four commands must succeed.
Apply these additional targeted checks whenever the matching area was edited:
```powershell
# Character, gear, appearance, weapon, or attachment changes:
npm test -- --run src/avatar
# Camera or desktop input changes:
npx vitest run src/game/inputManager.test.ts src/game/inputMath.test.ts
```
Then run the game locally and verify:
```powershell
npm run dev
```
- Check the roster preview and an in-dungeon character. Confirm that bodies,
weapons, and equipped silhouettes render, and record screenshot evidence in
`PLAYTEST_NOTES.md`.
- Hold and drag the right mouse button in the 3D scene. Confirm the camera moves,
releasing the button stops it, no browser capture prompt appears, and
`document.pointerLockElement` remains `null`.
- Exercise the gameplay, UI, and content changed by this release.
- Stop the development server with `Ctrl+C` when finished.
### Step 4: Review and commit the release
First check for whitespace errors and review the final file list:
```powershell
git diff --check
git status --short
git diff --stat
```
Stage the intended release:
```powershell
git add -A
git status --short
git diff --cached --stat
git diff --cached --check
```
Do not continue if the staged list contains a keystore, APK, `.env` file,
database, `content/`, `dist/`, `dist-android/`, or `.android-public/` output.
Unstage any accidental file with `git restore --staged -- <path>` without
deleting the local file.
Commit only after the staged set is correct:
```powershell
git commit -m "Release Healer Man $VersionName"
$ReleaseCommit = git rev-parse HEAD
$ReleaseCommit
```
Keep the printed commit ID so it can be compared with TrueNAS.
### Step 5: Push `main` to Gitea
```powershell
git push origin main
git status --short --branch
git ls-remote origin refs/heads/main
```
The push must finish successfully and the remote `main` hash must equal
`$ReleaseCommit`. Open the repository in Gitea and confirm the new commit is at
the top of `main`.
If SSH authentication fails, test it again:
```powershell
ssh -T -p 30009 git@192.168.1.180
```
Do not switch this large repository back to an HTTPS push just to work around an
error; its large initial pack previously exceeded the HTTPS proxy connection.
### Step 6: Stop and back up the TrueNAS app
In the TrueNAS Apps UI, stop the `healer-man` app. This prevents the running
container from reading files while its checkout changes.
Then sign in to a TrueNAS shell and create a point-in-time SQLite copy:
```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
df -h /mnt/usbssds/apps/healer-man
```
A TrueNAS dataset snapshot is recommended in addition to the file copy,
especially before a database migration.
### Step 7: Pull the release into the TrueNAS working checkout
The safe-directory commands are normally one-time setup, but running them again
is harmless:
```sh
sudo git config --global --add safe.directory \
/mnt/usbssds/apps/healer-man/app
sudo git config --global --add safe.directory \
/mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/healer-man.git
```
Pull from the local Gitea bare repository:
```sh
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 final commit ID must match `$ReleaseCommit` from the development PC. Always
use `sudo` for this pull: `truenas_admin` cannot read the bare Gitea repository
directly.
### Step 8: Start the TrueNAS app and watch startup
Start `healer-man` in the TrueNAS Apps UI, then open its logs. A successful
startup performs these actions in order:
1. Installs the locked dependencies.
2. Initializes `/app/runtime-data/game.db`.
3. Publishes the content manifest and immutable objects to `/app/content`.
4. Generates the loot catalog and builds the browser client.
5. Starts the combined web/API/content server on port `4173`.
The deprecation, funding, and `npm audit` lines are warnings; they do not by
themselves close the server. Continue until the logs show the server listening.
If the logs report this path, the YAML is still wrong:
```text
ENOENT: no such file or directory, open '/app/data/loot/schema.sql'
```
Change the database volume and `DATA_DIR` to `/app/runtime-data` using the YAML
in the appendix, then redeploy the app.
### Step 9: Verify the live server
From the TrueNAS shell:
```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
```
From the development PC or another LAN machine:
```powershell
curl.exe -fI https://iwanttoheal.phenomrom.com/
curl.exe -fsS https://iwanttoheal.phenomrom.com/api/health
```
Also open the public URL in a browser. Confirm the game loads, login works, and
the changed gameplay is present. If this release includes new or changed assets,
confirm the client sees the new content manifest and can retrieve one of those
assets.
### Step 10: Set release secrets for this PowerShell session
Set the permanent keystore path and alias:
```powershell
Set-Location F:\Projects\HealerMan
$env:ANDROID_KEYSTORE_FILE = 'F:\secure\healer-man-release.jks'
$env:ANDROID_KEY_ALIAS = 'healer-man'
```
Read the passwords and token without displaying them or placing them in command
history:
```powershell
$env:ANDROID_KEYSTORE_PASSWORD = [System.Net.NetworkCredential]::new(
'',
(Read-Host 'Keystore password' -AsSecureString)
).Password
$env:ANDROID_KEY_PASSWORD = [System.Net.NetworkCredential]::new(
'',
(Read-Host 'Key password' -AsSecureString)
).Password
$env:GITEA_TOKEN = [System.Net.NetworkCredential]::new(
'',
(Read-Host 'Gitea token' -AsSecureString)
).Password
```
If Java and the Android SDK are not configured permanently, set `JAVA_HOME` and
`ANDROID_SDK_ROOT` in this same shell now.
### Step 11: Build, sign, and publish the APK
Confirm the version variables still contain the intended values:
```powershell
$VersionName
$VersionCode
(Get-Content package.json -Raw | ConvertFrom-Json).version
```
The package version must match `$VersionName`. Then run:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass `
-File scripts/publish-gitea-release.ps1 `
-VersionName $VersionName `
-VersionCode $VersionCode `
-ReleaseNotes "Healer Man $VersionName client update; Android versionCode $VersionCode."
```
The script will:
1. Build the compact Android web bundle and synchronize Capacitor.
2. Build a signed release APK.
3. Name it `healer-man-VERSION-release.apk`.
4. Create a SHA-256 checksum file.
5. Create the Gitea tag and non-draft release.
6. Upload the APK and checksum as release attachments.
Do not interrupt it while Gradle or the upload is running.
### Step 12: Verify the APK and Obtainium release
Check the local artifact:
```powershell
$Apk = "android\app\build\outputs\apk\release\healer-man-$VersionName-release.apk"
Get-Item $Apk | Select-Object FullName, Length, LastWriteTime
Get-FileHash $Apk -Algorithm SHA256
```
Open:
```text
https://git.whoagland.com/phenom/healer-man/releases
```
Confirm the release has both attachments:
```text
healer-man-VERSION-release.apk
healer-man-VERSION-release.apk.sha256
```
On a test Android device:
1. Refresh Healer Man in Obtainium.
2. Confirm it detects the new version.
3. Install the update over the existing app; do not uninstall first.
4. Launch once online and allow changed content to download.
5. Disconnect Wi-Fi/mobile data and confirm single-player still starts and
previously downloaded content remains playable.
If Obtainium needs manual source settings, use the releases page above, select
the HTML source override, and filter APK links with:
```text
healer-man-.*-release\.apk$
```
### Step 13: Clear secrets from the shell
```powershell
Remove-Item Env:ANDROID_KEYSTORE_PASSWORD -ErrorAction SilentlyContinue
Remove-Item Env:ANDROID_KEY_PASSWORD -ErrorAction SilentlyContinue
Remove-Item Env:GITEA_TOKEN -ErrorAction SilentlyContinue
```
The keystore path and alias are not passwords, but they may also be cleared if
desired.
## Short procedure for an asset-only update
Use this only when no APK-owned code, native behavior, starter content, or
content compatibility rules changed.
Do **not** bump the Android version. Run these exact PowerShell commands:
```powershell
Set-Location F:\Projects\HealerMan
npm run content:test
npm run build
git status --short
git diff --check
git add -A
git status --short
git diff --cached --stat
git diff --cached --check
git commit -m "Update Healer Man assets"
git push origin main
git rev-parse HEAD
```
Read the staged file list before committing and stop if a test, build, or Git
command fails. Next, stop `healer-man` in the TrueNAS Apps UI and run this exact
block in a TrueNAS shell:
```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 -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
```
Start `healer-man` in the TrueNAS Apps UI. Startup runs `content:publish`, writes
new immutable objects, and atomically replaces `manifest.json` after the objects
exist. When the logs show the server listening, run:
```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
```
Finally, launch a currently installed APK while online. Confirm it downloads the
changed content, then disconnect the device and test the content offline.
Changing or adding an asset changes the published manifest. Because content is
addressed by its hash, an unchanged asset is reused while a changed file becomes
a new immutable object. Old clients continue using their active verified
manifest until the new download is complete; a partially downloaded update is
not activated.
## Rollback
Do not rewrite or force-push shared `main` history. If a source/server release is
bad, revert it from the development PC:
```powershell
Set-Location F:\Projects\HealerMan
git revert <bad-commit-id>
git push origin main
```
Then stop the TrueNAS app, pull the new revert commit with the Step 7 command,
and start the app again. Restore a database backup only if the failure changed
or damaged persistent data.
An already-installed Android APK cannot be rolled back in place to a lower
`versionCode`. Publish a corrected APK with a new version name and a still-higher
`versionCode`.
## Common failures
- **TrueNAS says the bare repository is not a Git repository:** run the pull
with `sudo`. The normal `truenas_admin` account cannot read Gitea's repository
storage.
- **`publish-content.mjs` is missing:** the TrueNAS checkout is not at the pushed
commit. Compare `git rev-parse HEAD` on both systems and pull again.
- **`/app/data/loot/schema.sql` is missing:** a volume is masking source
`/app/data`. Apply the corrected YAML below.
- **Git HTTPS push fails after uploading a large pack:** keep the configured LAN
SSH push URL and test port `30009` authentication.
- **APK installs as a separate app or refuses to update:** verify the app ID is
still `com.phenomrom.healerman`, use the same signing key, and increase
`versionCode`.
- **Gitea APK upload returns an HTTP size error:** increase the Gitea/reverse
proxy release-upload limit or use Gitea's direct LAN web endpoint with the
script's `-GiteaBaseUrl` option. Do not assume `192.168.1.180:8080` is Gitea;
verify the actual Gitea web port first.
- **Release creation succeeds but attachment upload fails:** do not repeatedly
create the same tag. Remove the incomplete release/tag in Gitea or upload the
attachments to that release, then retry only after its state is understood.
## Appendix: known-good TrueNAS YAML
The TrueNAS app name is `healer-man`:
```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
```
The critical distinction is:
```text
Source loot schema: /app/data/loot/schema.sql
Persistent database: /app/runtime-data/game.db
```
Never add a volume mounted at `/app/data`.