A production-hardening patch: the lazy
require()class that only crashed outside vitest is gone (OIDC login works in production again), both remote build-cache backends can finally produce hits, image retention actually prunes, and the databases pages show live CPU/RAM.
Added
- Live CPU and RAM for managed databases. Running databases show a
live CPU + memory line on the list cards and a Live Resources card in
the detail view — CPU %, memory used against the configured
memLimitMb— polling the stats snapshot every 3 seconds while visible.
Changed
- Redeploying a running service asks first. The Deploy button on a live service opens a confirmation dialog explaining the blue-green behavior (the current version keeps serving until the new build passes the healthcheck, then traffic swaps); idle, stopped or errored services still deploy immediately.
Fixed
- OIDC login works in production again. RS256 signature verification
pulled
node:cryptothrough a lazyrequire()— undefined in the pure-ESM server package — so every OIDC sign-in died withReferenceError: require is not definedin production while the unit suite stayed green (vitest's module runner shimsrequire). The helpers now ride the module's static import, and the suite gains a source-level ESM-purity guard. - The same
require()class killed two more production paths. The iptables egress driver's entire on-disk state layer (rehydrate, persist, delete) threw and was swallowed by best-effort catches — boot forgot every persisted rule, applied rules were never persisted, and detach was a permanent no-op after a restart — andRegistryBuildCache.store()crashed outright on every non-marker digest with no catch to soften it. Both now use staticnode:fs/node:cryptoimports. - Remote build caches can produce hits. The S3 backend's
lookup()demanded a metadata header on HEAD thatstore()cannot send — the digest only lives in the marker body — and the registry backend compared the cached layer digest against HEAD'sDocker-Content-Digest, the digest of the manifest itself, which is never equal. Both now GET the marker/manifest and compare like-for-like, so store→lookup round-trips hit and--cache-fromstops building cold every time. images prune --keep-last Nactually prunes. Retention groups were keyed byrepo:tag, which maps to exactly one image id, so every group was a singleton:keepLast >= 1protected the entire tagged inventory and nothing was ever deleted on real hosts. Groups are now keyed per repository, keeping the N newest versions per repo.- Email template overrides stay per template. The override lookup filtered by workspace only, so once a workspace overrode a second template, every render returned an arbitrary sibling override (a password-reset email rendered with the workspace-invitation text) and deleting one template's override wiped the workspace's others. Lookups are now scoped by workspace AND template name, matching the table's unique key.
- Magic hex secrets are exact-length.
SERVICE_PASSWORD_HEX_25baked a 24-char key andHEX_1an empty secret, because the hex generator computedsize / 2bytes and Node silently truncates fractional sizes. Generation now rounds up and slices to the exact documented length; even sizes are byte-identical. - Manifest formatting keeps string types.
formatManifestYamlemitted plain scalars for any charset-safe string, so header values liketrue,8080or007round-tripped as boolean/number and the schema rejected the manifest the formatter itself produced. Quoting is now self-verifying: a plain scalar ships only if js-yaml loads it back as a string. - Cron summaries refuse wrong monthlies.
0 0 1,15 * *,0 0 */7 * *and0 0 1-15 * *were all summarized as "monthly on the 1st at 00:00" — factually wrong, operator-facing. Multi-day day-of-month expressions now fall through to null so the UI shows the bare expression instead. - PgBouncer status reports the real pool mode. Status grepped a
container env var that is never set, through an invalid Go template,
so
poolModewas always null while the sidecar ran; it now parses the rendered/etc/pgbouncer/pgbouncer.ini. - CLI multi-line pastes.
prompt()/promptHidden()split raw stdin chunks on the first newline, collapsing multi-line pastes into one value and dropping the tail after a hidden prompt's Enter; lines are now framed and fed to successive prompts in FIFO order. - Misc: the SettingsTab privilege tests use the global 30s timeout ceiling instead of a tighter per-file override that tripped on loaded CI runners.