• Onebox v33.0.0
    Release / build-and-release (push) Failing after 11m14s
    Stable

    philkunz released this 2026-09-29 23:59:49 +00:00 | 72 commits to main since this release

    2026-09-29 - 33.0.0

    Breaking Changes

    • The package's Docker manager is the Docker runtime backend now, with no aliases: the export OneboxDockerManager is DockerRuntimeBackend (with the IOneboxRuntimeBackend type beside it), Onebox.docker is Onebox.runtime for every workload effect and Onebox.dockerRuntime and Onebox.corestoreEngineDocker for the Docker-only Corestore conversion steps. DockerImageLoadError is gone: images no longer travel through the runtime (see below).
    • A backup that includes the service's image reads it from the registry the service pulled it from, no longer from the runtime (docker save). The registry must be reachable with the credentials Onebox holds for it and must still serve the image the service runs; an image that was only ever built on the host is refused by name as backup-image-unavailable and must be pushed to a registry first. A restored image runs from Onebox's local registry (onebox-restore/<service>@sha256:…, with the local registry as the service's registry) instead of by its bare image ID.

    Features

    • Route every workload effect through one runtime seam, the first step of running Onebox on Pallet without Docker. IOneboxRuntimeBackend (ts/classes/runtime-backend.ts) states what Onebox needs from a runtime: service and platform workloads, their images, secrets, volumes, published ports, logs, stats and exec, the image and volume artifacts of a backup, the CoreTraffic ingress and managed dcrouter workloads, and the host prerequisites onebox systemd enable installs. Services, platform providers, WorkloadInit approvals, backups and the ops handlers now call only onebox.runtime. DockerRuntimeBackend (the former OneboxDockerManager, ts/classes/docker.ts) implements it on the Docker Engine API and Swarm, with the Docker parts of CoreTraffic (docker-coretraffic.ts), the managed dcrouter (docker-dcrouter.ts), Docker installation and swarm init (docker-host.ts) and the backup's docker save, docker load and docker cp steps (DockerArtifactTools in utils/docker-cli.ts) behind it. The Corestore engine conversion, its target handoffs, its data relocation and its maintenance containers are Docker history and move to CorestoreEngineDockerRuntime in ts_migration/corestore-engine-docker.ts; they and the other Corestore migrations reach Docker through onebox.corestoreEngineDocker and onebox.dockerRuntime. The seam states no Docker shapes: a service workload is created stopped with { start: false }, an interactive session reports { running, exitCode }, and a service runtime's secret files carry numeric owner ids, which the Docker backend reads from Swarm's decimal strings and refuses by name when one is not a canonical decimal. Otherwise nothing changes in behaviour: the same Docker requests, CLI commands, unit file and messages.
    • Administer MariaDB and ClickHouse over their own protocols, from the host, instead of running their command-line clients inside the platform containers: the first slice of taking exec off the paths that need no dump. Provisioning, deprovisioning and the MariaDB health check connect as the admin user through @lossless.org/client/sqldb (ts/classes/platform-services/sql-admin.ts) to the port the workload publishes on the host's loopback (getContainerHostPort), and the ClickHouse health check asks its HTTP /ping there. MariaDB binds no parameter in an account statement, so a user is created with IDENTIFIED BY PASSWORD and the mysql_native_password hash of its password, which is what MariaDB stores for IDENTIFIED BY '<password>'; ClickHouse takes the new password as a query parameter. No password reaches a command line, a process environment or a statement text, and a failure names only the client's error code and the engine's code, never the engine's message. Database dumps and restores still run inside the containers.
    • Back up and restore a service's image through registries instead of the runtime, so a backup no longer needs the runtime to export or import images (ts/classes/backup-image.ts). The backup fetches the image over the registry protocol (oci-registry-client.ts) by the service's canonical digest reference or else its configured reference, keeps the first whose index, manifest or config digest is the image ID the runtime reports, and stores it as an OCI image layout with the host platform's manifest, config and layers, each blob verified against its digest as it streams (oci-image-archive.ts); docker load reads that layout too. A restore reads an OCI image layout, or the docker-archive of a docker save from Docker before 25, which it converts into an OCI image over the same config and layer bytes after checking each layer against the diff ID its config records; it requires the archive to name the recorded image ID, pushes the image into the local registry under onebox-restore/<service> with the tag restored-<12 hex digits> (local-registry-image.ts) and has the runtime pull it by digest. Backups and restores of a service without volumes no longer need the Docker CLI. The runtime's artifact tools carry only volumes now (exportImage, importImage and their docker save/docker load steps are removed).
    • The dashboard runs in the dees-appui shell with path URLs: Overview; Workloads (Services, App Store); Domains & Traffic (Domains, DNS, Proxy); Registry (Registry, Tokens); Access; Settings pinned at the bottom. The server's own paths (/typedrequest, /oidc, /.well-known, /v2, /backups, /mcp) stay the server's. The update notice lives in the bottom bar and sign-out in the profile menu. Old dashboard URLs such as /apps/services open the not-found screen with a link to the Overview.
    • Every page has one header (title, one-line description, actions on the right) and one loading, empty and error pattern; duplicate headings are gone. Views load when they are shown and stop polling, live logs and metrics when they are left.
    • Services and platform services open as routed detail pages (/services/<name>, /platform-services/<type>) on @serve.zone/catalog 34's service detail page; every status reads from the platform's shared status vocabulary.
    • The overview shows compact figures and a "Needs attention" list: Docker down, proxy failed, CoreTraffic takeover failure and last refused route set, failed services and platform services, expired and expiring certificates, and missing gateway permissions.
    • Settings is split into Admin UI, Gateway, Storage, Platform and Updates (/settings/<section>); Access lists users and hosted-app sign-in in tables with "Invite user" in the page header.
    • Bundle the Cloudly App Store template 1.11.0 for Cloudly 33.8.0 through @serve.zone/appstore 32.11.0, moved from 32.10.0 in package.json, pnpm-lock.yaml and the binary's deno.lock, changing no other package's integrity and keeping the lock's registry origin. Onebox reads the App Store from the catalog this package bundles, so onebox appstore upgrade cloudly --version 1.11.0 needs this release. The template pins the Cloudly 33.8.0 image index code.foss.global/serve.zone/cloudly@sha256:06ab9a594aecf496fcc16f19bb19fd19123601e11f9615c0d5dcc9be9e540eda and keeps the 1.10.0 runtime contract and minOneboxVersion 32.4.0; against 1.10.0 only its image and changelog differ. Cloudly 33.8.0 adds no data step and keeps the data version 0.23.0, so from 1.9.0 or 1.10.0 nothing migrates; the template still marks the upgrade migration-required, backup-before-upgrade, breaking and manual-review, because hosts read these flags from the target version alone and every upgrade from 1.8.0 or earlier runs the forward-only data step deployment-machine-user-grants. Deploy Cloudly 33.8.0 before any Pallet that captures workload output reaches a fleet node, since it answers reportRuntimeAssignmentLogs with not-kept; from 1.4.0 and earlier roll out Cloudly 33.8.0 before Coreflow 33.2.0. The App Store upgrade test now checks that 1.11.0 is the latest catalog version and prepares its upgrade from installs of 1.2.1 through 1.10.0, the latter on the Cloudly 33.7.0 image. App Store 32.11.0 still pins @serve.zone/interfaces 32.3.0, so no other runtime package moves.

    Fixes

    • Security: close the local registry's storage server to everyone but the registry. The S3-compatible storage server that holds the registry's blobs, manifests and tags listened on 0.0.0.0:4000 and checked no credentials, so anyone who reached that port could read, overwrite and delete registry data, image layers included, without a /v2/token grant. It now listens on 127.0.0.1:4000 only and refuses every request without a SigV4 signature from a credential RegistryManager generates at each start and passes to its in-process smartregistry, the server's only client; the credential exists only in memory, never on disk or in a log (ts/classes/registry.ts). Registry data stays where it is, so an existing install needs no migration: the next start binds the new address and credential. A test proves the port refuses connections on a non-loopback interface, anonymous reads, writes and deletes, and the old fixed onebox credential, while the registry still stores and serves images (test/registry-storage-access.node.ts).
    • Stop the local registry's storage server again when the registry fails to initialize after starting it. RegistryManager.init() left the storage server running and the registry uninitialized when smartregistry's own initialization failed, for example when the storage server could not give it its bucket, so the storage port stayed bound and every later init() failed with Address already in use. A failed init() now closes the smartregistry instance and stops the storage server it started before it rethrows, so the same registry can initialize again (ts/classes/registry.ts). A test fails the initialization with a regular file in the place of the registry's bucket directory, then checks that the port is free and that the same registry initializes on it once the file is gone (test/registry-init.node.ts).
    • Security: @push.rocks/smartregistry moves from 2.10.1 to 2.11.2, which holds the local registry's OCI tokens to what Onebox issues: it compares a token's HS256 signature in constant time, refuses any other algorithm, a missing or non-numeric exp and a non-numeric nbf, requires iss to be the configured realm and aud to name the configured service, and refuses every OCI token while the OCI token service is disabled. Onebox mints its tokens through smartregistry's createOciToken() with OCI tokens enabled, so they carry exactly these claims and keep validating. The registry now shuts down through SmartRegistry.close(), which 2.11.0 added and which also releases the object storage client that destroy() left open (ts/classes/registry.ts). 2.11 stores through @lossless.org/client instead of @push.rocks/smartbucket and no longer needs @push.rocks/qenv or adm-zip; it brings no package Onebox did not already resolve. The move is in package.json, pnpm-lock.yaml and the binary's deno.lock, changing no other package's integrity and keeping the lock's registry origin.
    • Make Onebox's local registry usable by Docker and other OCI clients. Its Bearer challenge named a token endpoint (/v2/token) nothing served, and the registry tokens administrators create were never checked, so no client could pull from or push to it. /v2/token now issues a signed token, valid 300 seconds, for the requested repository actions the presented HTTP Basic credentials allow (ts/classes/registry-token-service.ts): a stored registry token (compared by SHA-256 in constant time, refused once expired) may push and pull the repositories its scope names, and an internal credential Onebox issues for each of its own runtime's pulls from the local registry may pull every repository and expires after ten minutes; the runtime's registry-auth hook hands that credential for the local registry. Two more defects kept clients out: the registry's OCI base path was empty, so every repository name began with v2/ and matched neither tokens nor tag storage (now /v2), and every response body smartregistry streams, blobs among them, was answered as the text {} (a stream now passes through). Tests drive the endpoint over HTTP against smartregistry and its storage server, and the local Docker daemon pulls from it by digest with an internal credential.
    • Authenticate every pull from a private registry. @apiclient.xyz/docker up to 10.1.1 sent the X-Registry-Auth header as unpadded base64url, which the Docker daemon cannot decode and then pulls anonymously without an error: the credentials Onebox holds for a registry reached the daemon only when their JSON length was a multiple of three, about one pull in three, so a private image otherwise failed with an access error. 10.1.2 pads the header. @apiclient.xyz/docker moves from 10.1.1 to 10.1.2 in package.json, pnpm-lock.yaml and the binary's deno.lock, changing no other package's integrity and keeping the lock's registry origin; regenerating deno.lock for it resolves @push.rocks/smartarchive 5.5.1 (with file-type 22.1.1), which @apiclient.xyz/docker requires as ^5.4.1, where pnpm-lock.yaml keeps 5.4.1. No Onebox code changes with it.
    • A sign-in page that cannot reach Onebox says so ("Onebox is not reachable") with Retry and the technical detail folded away, instead of asking for a trusted hostname and showing the raw exception; every sign-in and hosted-app sign-in error is a sentence.
    • Deletes and the restart-causing platform sign-in toggle ask through the dashboard's own confirmation dialog instead of the browser's.
    • Volumes entered when creating a service become managed volumes at their container path; the form dropped every volume before.

    Maintenance

    • @serve.zone/interfaces 32.15.0 → 32.36.0 and @api.global/typedsocket 8.4.0 → 8.7.0 (exact pins) in package.json, pnpm-lock.yaml and the binary's deno.lock, changing no other package's integrity and keeping the lock's registry origin. They are what Onebox needs to control Pallet locally: interfaces 32.36.0 carries the local controller contract (nodeRuntimeLocalControllerContract), local storage claims and the runtime log, stats, exec and volume archive contracts, and typedsocket 8.7.0 reaches a server over a Unix socket (unixSocketPath), which 8.4.0 cannot. No Onebox code changes with them.
    • The backup import specs are split over three files by concern (backup-import, backup-import-refusals, backup-import-space) with their two host doubles in test/helpers/backup-import.ts. Each double initializes its own encrypted archive, and Argon2id derives the passphrase key at every initialization (about 2–3 s on a loaded host), so the single file needed 56–76 s against its 60-second limit; each file now takes about 20 s.
    • mariadb 3.5.4 (LGPL-2.1-or-later) and @clickhouse/client 1.23.1 (Apache-2.0) are runtime dependencies now, the drivers @lossless.org/client/sqldb loads for MariaDB and ClickHouse, in package.json, pnpm-lock.yaml and the binary's deno.lock. The release binaries' THIRD_PARTY_NOTICES.md reproduces their license files, and those of denque and lru-cache, which mariadb brings, verbatim from Deno's npm cache at the versions the binary's deno.lock resolves (scripts/binary-notices.ts); mariadb's section states that it is LGPL-2.1-or-later, where its corresponding source is (its npm tarball, pinned by the release's lockfiles, and the upstream tag) and how to build Onebox with a modified copy. The release workflow checks the published notices for each of these texts (pnpm run check:binary-notices).
    • Release tooling: @git.zone/tsdeno ^1.12.0 → ^1.14.0 (with @git.zone/tsbundle 3.1.0 through it). The text of @epic-web/invariant, which ships no license file and reaches the binary through argon2, is named as a thirdPartyNotices supplement in .smartconfig.json, pointing at notices/third-party-notices.supplement.md. tsdeno's notices stay disabled until the libraries that embed license-less packages supply their texts; the release keeps assembling THIRD_PARTY_NOTICES.md with scripts/binary-notices.ts.
    • The WorkloadInit approval specs are split over four files by concern (workloadinit-approvals for the lifecycle, -restore, -deploy and -delete) with their shared fixture in test/helpers/workloadinit-approvals.ts. Every one of the 38 cases starts its own database and commits the approval steps as SmartDB transactions, each of which takes about 60–120 ms on this host (its journal is staged, renamed and fsynced file by file), so the single file needed about 50–58 s against its 60-second limit; each file now takes 13–20 s.
    • @design.estate/dees-catalog ^16.17.0 and @serve.zone/catalog ^34.1.0, one copy of each in the lockfile: status badges carry the status dot, and the catalog list views open a row with a single click. Removed dashboard code no route reached (the proxy view's DNS and domain branches, the services quick-deploy and backups sub-views, and an external-registries stub that only logged).
    • Release tooling currency: packageManager moves to pnpm@12.6.0 and @git.zone/tsbundle to ^3.1.0, the version the lockfile already resolved; @git.zone/tsbuild ^5.0.0, @git.zone/tsdeno ^1.14.0, @git.zone/tsrun ^3.0.2, @git.zone/tstest 6.3.2 and @git.zone/tswatch ^4.0.2 are already the current releases. pnpm and @types/node stop at the newest releases at least seven days old: pnpm 12.7.0 and @types/node 26.6.3 are younger, so @types/node stays at 26.6.2. No migration is due: the project has no folder tsconfig.json or tspublish.json (tsbuild 5), the dashboard bundle already writes the third-party notices tsbundle 3 requires, and no script reads process.argv through tsrun (tsrun 3). The release workflow takes pnpm from packageManager through corepack and asserts no pnpm version of its own; the lockfile keeps the npmjs sha512 integrity of every package.
    Downloads