mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 22:03:13 -06:00
Document the Node.js upgrade procedure and this build's reinstall requirement
Upgrading the runtime does not go through scripts/upgrade.sh, so nothing reinstalls dependencies — which is precisely when the one remaining native module goes stale. The runbook now covers the version floor imposed by --env-file-if-exists, why the better-sqlite3 pin is exact, and why a version without a matching prebuild can turn Restart=always into a boot loop. Also records that this build changes dependencies in both directions: rolling back past it needs the reinstall too, because earlier builds import sharp at runtime and this one drops it from production dependencies. Kept deployment-neutral — no hostnames, addresses, or environment specifics.
This commit is contained in:
parent
c436a44c89
commit
ba33b26a96
55
CHANGELOG.md
55
CHANGELOG.md
|
|
@ -1,5 +1,60 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
<!-- Rename this heading to the version when cutting the release — scripts/bump-version.sh
|
||||
warns if CHANGELOG.md has no entry matching the new version. -->
|
||||
|
||||
### ⚠️ Upgrading to this build requires reinstalling dependencies
|
||||
|
||||
Both changes below alter `server/package.json`, so **`npm ci --omit=dev` is required, not
|
||||
optional** — in both directions. The runbook's rollback step marks that command "only if
|
||||
dependencies changed"; for this release they did.
|
||||
|
||||
- **Upgrading**: `scripts/upgrade.sh` already runs it. A hand-rolled deploy that skips it leaves a
|
||||
`better-sqlite3` that no longer matches `package.json`.
|
||||
- **Rolling back past this build**: the reinstall is **mandatory**. Earlier builds import `sharp` at
|
||||
runtime to thumbnail images, and this build removes it from the production dependencies — so
|
||||
rolling back the code without reinstalling leaves a server whose image ingest cannot load its
|
||||
decoder. `lib/preflight-deps.js` catches this at boot and repairs it, but do not rely on that as
|
||||
the plan.
|
||||
|
||||
Docker deployments need no action either way: dependencies are installed inside the image.
|
||||
|
||||
No migrations, no configuration changes, no player-side changes.
|
||||
|
||||
### Changed — image processing no longer uses a native module
|
||||
`sharp` is gone. Thumbnailing and image measurement are pure JavaScript (Jimp) with WebAssembly
|
||||
codecs for webp and avif, running on a worker thread. `sharp` remains as a development dependency
|
||||
for test fixtures, so `--omit=dev` excludes it from a deployed install entirely.
|
||||
|
||||
The motivation is that a native module needs a prebuilt binary matching both the platform and the
|
||||
Node ABI; when there isn't one, the failure arrives at load time and reads like database corruption
|
||||
rather than a missing image library. `better-sqlite3` is now the only native module left.
|
||||
|
||||
Format support is unchanged in practice. jpeg, png, gif, tiff and bmp decode natively; webp and avif
|
||||
via WebAssembly. `.heic` still produces no thumbnail — it never did, because the `sharp` builds in
|
||||
use decode AV1 but refuse HEVC.
|
||||
|
||||
Images are decoded on a worker thread rather than in-process. Pure-JavaScript decoding costs about a
|
||||
second for a 12-megapixel photo, which in-process would block the event loop — and the thumbnail
|
||||
backfill walks an entire library at boot, which is exactly how a maintenance task turns into missed
|
||||
heartbeats and players marked offline. Thumbnailing is slower in wall-clock terms than the native
|
||||
library was, and no longer competes with serving requests.
|
||||
|
||||
### Changed — `better-sqlite3` pinned to 12.9.0
|
||||
Preparation for a future Node 22 upgrade, landed separately so the runtime move and the database
|
||||
driver move stay independently reversible.
|
||||
|
||||
The pin is **exact on purpose**. 12.9.0 is the last release publishing prebuilt binaries for both
|
||||
the current and the next Node major; later 12.x releases dropped the older one while still
|
||||
advertising support for it in `engines`. A caret range would resolve to one of those and silently
|
||||
turn installation into a from-source compile. `lib/preflight-deps.js` explains this at the point
|
||||
anyone debugging the resulting failure would be reading.
|
||||
|
||||
Nothing in the query API changed — every major since 9.x was bumped only to drop end-of-life Node
|
||||
and Electron versions.
|
||||
|
||||
## 1.9.34-alpha6
|
||||
|
||||
### Added — a setup guide for single sign-on
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ did not behave.
|
|||
- [Verifying a deploy](#verifying-a-deploy)
|
||||
- [Rolling back](#rolling-back)
|
||||
- [Releases and version numbers](#releases-and-version-numbers)
|
||||
- [Upgrading Node.js](#upgrading-nodejs)
|
||||
- [Traps worth knowing before they bite](#traps-worth-knowing-before-they-bite)
|
||||
|
||||
---
|
||||
|
|
@ -253,12 +254,68 @@ or an image, delete-and-repush is not a fix; cut the next version instead.
|
|||
|
||||
---
|
||||
|
||||
## Upgrading Node.js
|
||||
|
||||
Upgrading the runtime is not like deploying a release: nothing in the app's own upgrade path is
|
||||
involved, so the usual `scripts/upgrade.sh` never runs and nothing reinstalls dependencies. Read
|
||||
this before changing the Node major.
|
||||
|
||||
**Do it as two separate deploys, never one.** Move the app to a release whose dependencies support
|
||||
both the old and new Node major first, confirm it on the runtime you already have, and only then
|
||||
change Node. Each half is then independently reversible. Doing both at once means a failure gives
|
||||
you nothing to bisect and no single step to undo.
|
||||
|
||||
**Check the version floor.** `npm start` uses `node --env-file-if-exists=.env`. That flag reached
|
||||
the Node 22 line only in **22.9.0** — it works on Node 20 because it was separately backported
|
||||
there. On Node 22.0–22.8 the server refuses to start with `node: bad option`. Target 22.9.0 or
|
||||
newer.
|
||||
|
||||
**One native module has to survive the move.** `better-sqlite3` is compiled against a single Node
|
||||
ABI, so changing Node invalidates it. Two things make this survivable:
|
||||
|
||||
- `lib/preflight-deps.js` runs before anything else at boot, detects the mismatch by *opening a
|
||||
database* (a bare `require` succeeds even on a wrong ABI, so it is not a valid check), and repairs
|
||||
it with `npm rebuild better-sqlite3`.
|
||||
- The pinned version ships **prebuilt binaries for both the current and the next Node major**, so
|
||||
that repair downloads a binary instead of compiling one.
|
||||
|
||||
⚠️ **That second point is why the version is pinned exactly rather than with a caret**, and why
|
||||
widening it is risky in a way `package.json` does not show. A version with no prebuild for your Node
|
||||
falls back to a from-source `node-gyp` build — and because preflight rebuilds *synchronously before
|
||||
the server listens*, a compile that outlives `TimeoutStartSec` turns `Restart=always` into a boot
|
||||
loop that never finishes. Before changing that pin, check the project's release assets and confirm a
|
||||
prebuild exists for every Node ABI you intend to run. A build toolchain (`python3`, `make`, `g++`)
|
||||
should still be present as a fallback.
|
||||
|
||||
**Native (git + systemd)**
|
||||
|
||||
1. Back up first — a Node upgrade cannot corrupt the database, but you want the rollback anyway.
|
||||
2. Change the Node major. If Node came from a distribution repository pinned to a major, the repo
|
||||
definition itself must be repointed — upgrading the package alone can never cross majors, and
|
||||
this pin lives in system configuration rather than in this repository.
|
||||
3. `node --version` to confirm.
|
||||
4. Rebuild the native module explicitly (`npm rebuild better-sqlite3` as the service user, in
|
||||
`server/`), or let preflight do it on the next restart. Doing it by hand keeps the logs readable.
|
||||
5. Restart, then verify as in [Verifying a deploy](#verifying-a-deploy). In the logs, confirm
|
||||
preflight reports a successful rebuild rather than exiting.
|
||||
|
||||
**Docker** — nothing to rebuild. Change the base image, build, and deploy the new tag: dependencies
|
||||
are installed inside the image against its own Node, so the ABI can never be stale. Rollback is
|
||||
repinning the previous tag.
|
||||
|
||||
**Afterwards, move CI too.** CI pins its own Node version, and it will happily keep validating a
|
||||
version nobody runs — which is worse than no signal, because it looks like coverage. The Docker base
|
||||
image is a separate pin from the CI one; both need changing or what CI tests and what ships diverge.
|
||||
|
||||
---
|
||||
|
||||
## Traps worth knowing before they bite
|
||||
|
||||
**Native modules are built for one Node ABI.** `better-sqlite3` is compiled against the Node that
|
||||
installed it. Run the app — or its tests — under a different major version and it fails with
|
||||
`NODE_MODULE_VERSION` mismatch, which presents as hundreds of unrelated test failures rather than
|
||||
one clear error. Use the same Node the service runs.
|
||||
one clear error. Use the same Node the service runs. See [Upgrading Node.js](#upgrading-nodejs)
|
||||
before changing it deliberately.
|
||||
|
||||
**SQLite foreign keys are off unless enabled per connection.** A declared `ON DELETE CASCADE` does
|
||||
not fire on its own, so deleting a parent row can leave orphaned children. Check with
|
||||
|
|
|
|||
Loading…
Reference in a new issue