607 lines
19 KiB
Markdown
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`.
|