Added
- One Runtime Version Catalog:
RUNTIME_VERSION_CATALOGin@ninedeploy/schemasis now the single source for every version NineDeploy suggests — the Manifest Creator presets, its version picker and the CLI'sstarterManifestall read from it, so a bump is one edit instead of three that can disagree. Each entry carries its upstream support state and EOL date, and the catalog records the date it was last reviewed so staleness is visible rather than silent. Tests assert the table's invariants: no recommended pin may be security-only or end-of-life, and every offered version must satisfy the manifest schema. - Runtime Versions Are Picked, Not Typed — And Old Ones Stay Available: the Manifest Creator's Version field is a picker listing each version with its support status, plus an "Other version…" escape hatch for anything the catalog does not list. Choosing an end-of-life or security-only version is still allowed — reproducing a legacy runtime is a real need — but it now carries an advisory naming the recommended pin instead of passing silently. Nothing here blocks a build, and
runtime.versionstays free-form in the schema. Switching runtime type now drops a pin that is meaningless for the new type rather than carrying, say, Node's24over to Python.
Fixed
- The Manifest's Build Half Was Never Applied — And The Docs Said Otherwise:
.ninedeploydocumentedruntime,phasesandbuildas taking effect at build time, and 0.3.0's notes claimed the pipeline "applies the build sections at build time". It does not.engine/pipeline.tsapplies only the operational sections (routes, alerts, database);applyManifestToBuildConfigandgenerateNixpacksTomlare both written, tested — and called by nothing. The builder invokes the Nixpacks CLI with--install-cmd/--build-cmd/--start-cmdand writes nonixpacks.toml. The docs, the module comments and the Manifest Creator's Runtime section now say this plainly instead of implying a pin that never happens. Wiring it up needs a real Linux/Docker/Nixpacks integration test and is deliberately not in this release. - Every Runtime Pin The Generator Produced Was Broken:
generateNixpacksTomlbuilt nixpkgs attribute names by stripping non-digits, yieldinggo_127andruby_34where nixpkgs usesgo_1_27andruby_3_4, and turning any patch-level pin into nonsense (24.4.1→nodejs_2441). Worse,[phases.setup] nixPkgsreplaces the provider's package list unless it contains the"..."sentinel — so declaring one extra package silently deleted the toolchain, and an unresolvable name is a hardundefined variableNix error late indocker build. Version pins now go only through the provider environment variables Nixpacks actually reads, everynixPkgslist leads with"..."so extras are additive as documented, and the generator returns warnings instead of emitting a pin it knows will be ignored or will fail. - Pins Nixpacks Cannot Honour Are Named, Not Guessed: checked against the v1.41.0 the installer pins,
NIXPACKS_GO_VERSIONandNIXPACKS_PHP_VERSIONdo not exist (those versions come fromgo.modandcomposer.json), Node silently falls back to 18 outside {14,16,18,20,22,24}, Python silently falls back to its default outside 3.7–3.13, Ruby and Rust need an exact patch version, and JDK fails the build outside {8,11,17,19,20,21}. The generator refuses each of these with a specific reason rather than emitting something that breaks or quietly builds the wrong thing. - Manifest Creator Shipped End-Of-Life Runtimes As Its Defaults: every preset pinned Node 20, Python 3.12 and Go 1.22 — versions that lost upstream support on 2026-04-30, 2025-04-02 and 2025-02, respectively. They were hand-written literals duplicated between
apps/webandpackages/sdk, so nobody owned them and they drifted until a deployment tool was recommending unpatched runtimes. Defaults are now Node 24 (Active LTS), Python 3.14, Go 1.27, Ruby 3.4, PHP 8.4, Java 25 and Rust 1.98. - The Pre-Upgrade Snapshot Usually Did Nothing: the installer passed
.data/ninedeploy.dband.data/master.keyto a singletarwith stderr muted.master.keyis written lazily — the first time something is encrypted — so on any instance without stored secrets the file is absent,tarexits non-zero, and the entire snapshot was skipped, database included. The operator saw one vague warning and upgraded with no rollback point, next to a truncated archive left on disk. Each file that exists is now archived (SQLite's-wal/-shmsidecars included so the snapshot is a consistent set), a missingmaster.keyis treated as the normal state it is, tar's actual error is printed instead of muted, and a failed archive is removed rather than left behind. - Failure Messages Claimed A Backup That Did Not Exist: every readiness-gate failure ended with "A pre-update backup is in …/upgrade-backups" regardless of whether the snapshot had run. The message now reports what actually happened.
Installer changes take effect immediately — install.sh is fetched from main, not from the release tarball.