Release 1.4.35; build the one-file Full Setup on macOS too

- Version 1.4.35, published to the Gitea package registry and verified: the app
  downloads it, checks size and sha256, and lands a byte-identical installer.
- tools/build-full-installer.cjs picked node_modules/7zip-bin/win/x64/7za.exe
  unconditionally, so the one-file Full Setup could only be built on Windows.
  7za only packs the payload — the SFX stub and its config are concatenated
  byte-for-byte — so the host's own 7za yields the same Windows .exe. Also names
  the three payload files instead of passing a "*" that each 7za build expands
  its own way, and that would otherwise sweep the output archive into itself.
- BUILD-AND-DEPLOY.md / UPDATES.md rewritten for the laptop workflow and the
  Gitea update source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 13:25:40 -04:00
co-authored by Claude Opus 5
parent 838a87dbf3
commit bb72c4fb0c
4 changed files with 88 additions and 69 deletions
+3
View File
@@ -127,6 +127,9 @@ never enters the repo), build the regular installer, then:
node tools/build-full-installer.cjs node tools/build-full-installer.cjs
``` ```
This builds on the laptop too: 7-Zip only packs the payload, and the Windows SFX stub is concatenated
onto it byte-for-byte, so the host platform makes no difference to the result.
Machines that already have the model keep using the regular installer. Machines that already have the model keep using the regular installer.
## 6. Git ## 6. Git
+69 -65
View File
@@ -1,58 +1,30 @@
# Updating the Bible App # Updating the Bible App
The app updates itself from **a folder you control** — a network share, a folder on this PC, a USB The app updates itself from **a source you control** — the church's own Gitea server, a network
drive, or a small web server. No GitHub, no app store, no internet required (a network share is share, a folder on this PC, or any small web server. No GitHub, no app store, nothing leaves the
enough). building.
--- ---
## One-time setup (per installed machine) ## One-time setup (per installed machine)
1. Open the app → **menu (☰) → Settings → Updates** 1. Open the app → **menu (☰) → Settings → Updates**
2. Put your update location in **Update folder or address**, for example: 2. Put the update source in **Update folder or address**. The church's is:
- `\\WATERS-SERVER\builds\bible-app` (network share — recommended)
- `D:\BibleAppBuilds` (folder on this PC) ```
- `http://server.local/bible-app` (web server) http://chronicles-ab.waterschurch.org:3000/api/packages/christian/generic/bible-app/latest
3. Leave **“Check for updates automatically when the app starts”** ticked. ```
Anything else the app accepts also works: `\\WATERS-SERVER\builds\bible-app` (network share),
`D:\BibleAppBuilds` (folder on this PC), `http://server.local/bible-app` (web server).
3. Leave **"Check for updates automatically when the app starts"** ticked.
4. Click **Save**. 4. Click **Save**.
That's it. From now on the stage PC never needs a hand-carried .exe. That's it. From now on the stage PC never needs a hand-carried .exe.
--- > **Checking is automatic; installing is not.** With that box ticked the app asks the server at
> startup and remembers the answer, which Settings → Updates then shows. It never installs on its
## Publishing a new version (on the dev machine) > own — installing closes and reopens the app, so it stays a deliberate tap.
```bash
# 1. bump the version in package.json (e.g. 1.4.0 -> 1.4.1)
# 2. build + publish in one step:
npm run release
```
That produces the regular ~300 MB installer and publishes it — this is what existing machines
download. To also make the one-file installer for a **new** machine (app + AI model in a single
2.2 GB .exe), run this afterwards:
```bash
node tools/build-full-installer.cjs
```
It lands in `dist-installer/Bible App Full Setup <version>.exe`. You only need it when setting up a
PC that has never had the app.
`npm run release` builds the installer and copies it, plus a `latest.json` manifest, into the update
folder. To publish somewhere specific, or to add release notes the operator will see:
```bash
npm run publish-update -- "\\WATERS-SERVER\builds\bible-app" "Faster laser, new popup cards"
```
Publish destination is chosen in this order:
1. the path you pass on the command line
2. the `BIBLEAPP_UPDATE_DIR` environment variable
3. the folder configured in the app on this machine
4. `dist-installer/publish` (fallback)
The script keeps the 3 most recent installers in that folder and deletes older ones.
--- ---
@@ -60,7 +32,7 @@ The script keeps the 3 most recent installers in that folder and deletes older o
1. **Settings → Updates → Check for Updates** 1. **Settings → Updates → Check for Updates**
2. If a new version exists it shows the version number and notes → **Download & Install** 2. If a new version exists it shows the version number and notes → **Download & Install**
3. A progress bar runs while the installer copies (~1.4 GB, fast over a LAN) 3. A progress bar runs while the installer downloads (~750 MB, a couple of minutes over the LAN)
4. The app closes, the installer runs, and the app reopens 4. The app closes, the installer runs, and the app reopens
**Everything is kept:** notes, graphics, announcements, maps, annotations, service profiles, **Everything is kept:** notes, graphics, announcements, maps, annotations, service profiles,
@@ -71,27 +43,62 @@ folder, which the installer never touches.
--- ---
## Publishing a new version (on the laptop)
```bash
npm run release -- "Faster laser, new popup cards"
git commit -am "Release <version>" && git push
```
That bumps the patch version, builds the Windows installer, uploads it and a `latest.json` manifest
to the update source, and clears out builds older than the previous one. The Windows installer builds
on the Mac — see BUILD-AND-DEPLOY.md.
To publish an existing build, or to publish somewhere other than the default:
```bash
npm run publish-update # newest build -> default target
npm run publish-update -- "\\WATERS-SERVER\builds\bible-app" "Notes"
```
Publish destination is chosen in this order:
1. the path or URL you pass on the command line
2. the `BIBLEAPP_UPDATE_DIR` environment variable
3. `bibleApp.updateTarget` in `package.json` (the church's Gitea registry)
4. the source configured in the app on this machine
5. `dist-installer/publish` (fallback)
Uploading to Gitea needs a token — `GITEA_TOKEN`, or the macOS keychain item `gitea-mcp` that the
git tooling already uses. Downloading does not: the published installer is readable without a
login, which is what lets the TV fetch it. The source repository stays private.
---
## What `latest.json` looks like ## What `latest.json` looks like
The publish script writes it for you; you only need this if you're placing a build by hand: The publish script writes it for you; you only need this if you're placing a build by hand:
```json ```json
{ {
"version": "1.4.1", "version": "1.4.35",
"file": "Bible App Setup 1.4.1.exe", "file": "Bible App Setup 1.4.35.exe",
"size": 784268993,
"sha256": "7a628d27…",
"notes": "What changed in this version", "notes": "What changed in this version",
"date": "2026-08-12" "date": "2026-09-24"
} }
``` ```
Both the `.exe` and `latest.json` must sit in the same folder. Version numbers are compared Both the `.exe` and `latest.json` must sit in the same place. Version numbers are compared
numerically, so `1.4.10` correctly beats `1.4.9`. numerically, so `1.4.10` correctly beats `1.4.9` — and the app only ever offers a HIGHER version,
so re-publishing the same version number is invisible to it.
--- ---
## Setting up a brand-new machine — one file ## Setting up a brand-new machine — one file
Use **`Bible App Full Setup <version>.exe`** (about 2.2 GB). It is a single self-extracting file Use **`Bible App Full Setup <version>.exe`** (about 2.7 GB). It is a single self-extracting file
containing the app *and* the offline AI model. Copy it to the new PC any way you like (USB stick, containing the app *and* the offline AI model. Copy it to the new PC any way you like (USB stick,
network, cloud) and double-click it. network, cloud) and double-click it.
@@ -105,15 +112,15 @@ survives every future update. Settings → Updates will show *"AI model installe
this PC"*. this PC"*.
> Why two different installers? A Windows installer cannot exceed 2 GB, and the app plus the model is > Why two different installers? A Windows installer cannot exceed 2 GB, and the app plus the model is
> over that. The regular `Bible App Setup <version>.exe` (~300 MB) is what the in-app updater > over that. The regular `Bible App Setup <version>.exe` (~750 MB) is what the in-app updater
> downloads, which keeps ordinary updates fast. The **Full** one exists for new machines. > downloads. The **Full** one exists for new machines.
**Always check the file after copying it.** An interrupted copy or a cloud placeholder leaves a 0 KB **Always check the file after copying it.** An interrupted copy or a cloud placeholder leaves a 0 KB
file, and Windows then says *"This app can't run on your PC"* — which looks like an app problem but file, and Windows then says *"This app can't run on your PC"* — which looks like an app problem but
isn't: isn't:
```bash ```powershell
Get-FileHash "Bible App Full Setup 1.4.1.exe" -Algorithm SHA256 Get-FileHash "Bible App Full Setup 1.4.35.exe" -Algorithm SHA256
``` ```
Compare that to the `.sha256` file next to the installer. If they match, install it. If not (or the Compare that to the `.sha256` file next to the installer. If they match, install it. If not (or the
@@ -124,21 +131,18 @@ anything, and refuses a damaged download instead of installing it.
### Keeping several machines in step ### Keeping several machines in step
Point every machine at the same update location so nobody hand-carries files again: Point every machine at the same update source and nobody hand-carries files again. The Gitea URL
above works from anywhere on the church network; a network share or a web address works too.
- **Same building:** a network share, e.g. `\\WATERS-SERVER\builds\bible-app`
- **Different site:** any web address the app can reach (`http://...`), or a synced cloud folder
that lands on that PC's disk
## If something goes wrong ## If something goes wrong
| Symptom | Fix | | Symptom | Fix |
|---|---| |---|---|
| “Could not read update info” | The folder path is wrong, or `latest.json` isn't in it. Check Settings → Updates. | | "Could not read update info" | The address is wrong, or `latest.json` isn't there. Check Settings → Updates, and that the machine can reach the Gitea server. |
| “Installer not found” | `latest.json`'s `file` name doesn't match the actual .exe in the folder. | | "Installer not found" | `latest.json`'s `file` name doesn't match the actual .exe. Re-run `npm run publish-update`. |
| Network share not reachable | Confirm the stage PC can open that share in File Explorer. | | Network share not reachable | Confirm the stage PC can open that share in File Explorer. |
| Update seems stuck | The installer is large; watch the progress bar. It resumes fine if you retry. | | Update seems stuck | The installer is large; watch the progress bar. It resumes fine if you retry. |
| “came across empty (0 bytes)” / “is incomplete” | The copy into the update folder didn't finish. Re-copy the installer there and try again. | | "came across empty (0 bytes)" / "is incomplete" | The transfer didn't finish. Publish again and retry. |
| “was damaged in transfer (checksum mismatch)” | The file arrived corrupted. Publish or copy it again. | | "was damaged in transfer (checksum mismatch)" | The file arrived corrupted. Publish or copy it again. |
| Windows: “This app can't run on your PC” | The .exe you're double-clicking is 0 KB or corrupt — check its size, verify the hash, and re-copy. | | Windows: "This app can't run on your PC" | The .exe is 0 KB, corrupt, or the wrong architecture — check its size and verify the hash. |
| Need to roll back | Run an older installer from the update folder by hand — it installs over the top. | | Need to roll back | `git checkout v<older>` and `npm run release -- --no-bump "Rolling back"`, or run the previous installer (still in the update source) by hand — it installs over the top. |
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "bible-app", "name": "bible-app",
"version": "1.4.34", "version": "1.4.35",
"description": "Minimalist Bible App for Portrait-Oriented Touchscreen TV", "description": "Minimalist Bible App for Portrait-Oriented Touchscreen TV",
"type": "module", "type": "module",
"main": "main.cjs", "main": "main.cjs",
+15 -3
View File
@@ -31,7 +31,19 @@ const installerName = `Bible App Setup ${version}.exe`;
const installerPath = path.join(DIST, installerName); const installerPath = path.join(DIST, installerName);
const modelPath = path.join(ROOT, 'vendor', 'llama', 'model.gguf'); const modelPath = path.join(ROOT, 'vendor', 'llama', 'model.gguf');
const sfxModule = path.join(ROOT, 'tools', 'sfx', '7zSD.sfx'); const sfxModule = path.join(ROOT, 'tools', 'sfx', '7zSD.sfx');
const sevenZip = path.join(ROOT, 'node_modules', '7zip-bin', 'win', 'x64', '7za.exe'); /*
* 7za only packs the payload archive; the SFX stub and its config are plain byte concatenation, so
* the HOST's own 7za produces exactly the same Windows .exe. This used to be hard-coded to
* win/x64/7za.exe, which made the one-file installer buildable on Windows only — now the laptop can
* build it too.
*/
const sevenZip = (() => {
const base = path.join(ROOT, 'node_modules', '7zip-bin');
const arch = ['arm64', 'ia32', 'x64'].includes(process.arch) ? process.arch : 'x64';
if (process.platform === 'win32') return path.join(base, 'win', arch, '7za.exe');
if (process.platform === 'darwin') return path.join(base, 'mac', arch === 'arm64' ? 'arm64' : 'x64', '7za');
return path.join(base, 'linux', arch, '7za');
})();
const outName = `Bible App Full Setup ${version}.exe`; const outName = `Bible App Full Setup ${version}.exe`;
const outPath = path.join(DIST, outName); const outPath = path.join(DIST, outName);
@@ -45,7 +57,7 @@ function need(p, what) {
need(installerPath, 'installer'); need(installerPath, 'installer');
need(modelPath, 'AI model (vendor/llama/model.gguf)'); need(modelPath, 'AI model (vendor/llama/model.gguf)');
need(sfxModule, 'SFX module (tools/sfx/7zSD.sfx)'); need(sfxModule, 'SFX module (tools/sfx/7zSD.sfx)');
need(sevenZip, '7za.exe'); need(sevenZip, `7za for ${process.platform}/${process.arch}`);
const mb = (n) => (n / 1048576).toFixed(1); const mb = (n) => (n / 1048576).toFixed(1);
console.log(`Building one-file installer for ${version}`); console.log(`Building one-file installer for ${version}`);
@@ -90,7 +102,7 @@ fs.writeFileSync(path.join(stage, 'install.cmd'), [
// LZMA would burn many minutes for ~0% gain. // LZMA would burn many minutes for ~0% gain.
const bundle = path.join(stage, 'payload.7z'); const bundle = path.join(stage, 'payload.7z');
console.log(' packing : building archive (store mode)…'); console.log(' packing : building archive (store mode)…');
execFileSync(sevenZip, ['a', '-t7z', '-mx0', '-bso0', '-bsp0', bundle, path.join(stage, '*')], { stdio: 'inherit' }); execFileSync(sevenZip, ['a', '-t7z', '-mx0', '-bso0', '-bsp0', bundle, installerName, 'model.gguf', 'install.cmd'], { cwd: stage, stdio: 'inherit' });
/* /*
* ExecuteFile, NOT RunProgram. RunProgram launches via CreateProcess, which cannot start a .cmd at * ExecuteFile, NOT RunProgram. RunProgram launches via CreateProcess, which cannot start a .cmd at