@serve.zone/pallet-bundle
v36.3.14
Published
Install surface of Pallet's sealed control bundle: pinned release download and verification, the service units Pallet's contract states, and supervisors for the pallet-control process protocol.
Readme
@serve.zone/pallet-bundle
The install surface of Pallet's sealed control bundle, released from Pallet's repository with Pallet's own version. An installer — Spark on fleet nodes, Onebox on its own host — uses it to
- download a pinned Pallet release and verify it with tspack,
- verify the extracted bundle: its control record, every executable's role and build provenance, the component manifests and the notice and source inventories,
- write the service units Pallet's "Service unit contract" states, and read them back from the service manager,
- run and supervise
pallet-controlmodes over itsPALLET_CONTROLprocess protocol.
Pallet is the source of truth for all of it. Pallet's own packaging runs this package's verifier over every bundle it seals, so a release never disagrees with the version of this package that carries the same version number.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Install
pnpm install @serve.zone/pallet-bundleThe package runs under Node.js and Deno; it uses node: APIs and the global fetch only.
It reads its own version at runtime from the package.json beside its compiled modules
(version.ts, resolved relative to import.meta.url). Consume it as an npm dependency and do not
bundle it into another file: a bundler that inlines the modules loses that manifest, and
palletBundleVersion and every call that takes a pin (the release client and the verifiers) then fail. deno compile
keeps it: a compiled binary embeds each npm package it
uses with its package.json, whether the dependency comes from a node_modules directory
installed by pnpm or from Deno's own nodeModulesDir: "auto" install.
Pins
A release is selected only by its pin, the values a consumer compiles in:
import type { IPalletBundlePin } from '@serve.zone/pallet-bundle';
const pin: IPalletBundlePin = {
version: '35.3.0',
sourceCommit: '<the 40-hex commit of the v35.3.0 tag>',
manifestSha256: '<SHA-256 of that release\'s tspack-manifest.json>',
};Every entry point refuses a pin whose version differs from this package's own version
(version_mismatch): the inventory and contracts it states belong to that one Pallet version.
Upgrade the package and the pin together.
Download, extract and verify
import { PalletReleaseClient, extractPalletControlBundle } from '@serve.zone/pallet-bundle';
const client = new PalletReleaseClient();
const bundle = await client.run(pin, 'linux-amd64', '/opt/onebox/pallet', (download) =>
extractPalletControlBundle(download, 'linux-amd64', '/opt/onebox/pallet', pin, signal), signal);
// bundle.directory is a fresh private directory below /opt/onebox/pallet; the caller owns it.
console.log(bundle.executables.control.path, bundle.executables.control.sha256);
console.log(bundle.containerdManifestSha256, bundle.provenance.store.projectVersion);PalletReleaseClient.run resolves the release v<version> of serve.zone/pallet on
https://code.foss.global, requires its tag to name the pinned commit, verifies the release metadata
against the pinned manifest digest (tspack verifyMetadata), downloads the manifest, the checksums
and the target's archive into a private directory below the given parent, verifies the archive
(tspack verifyBundle) and hands the directory to the callback. It removes the download directory
when the callback settles. A release that exists without all of its assets answers
release_pending with the missing asset names.
extractPalletControlBundle extracts the target's archive with tspack extractBundle, then runs
verifyPalletControlBundle over it and removes the extraction again if that fails. For a bundle
that is already extracted, call verifyPalletControlBundle(directory, sealedFiles, pin, target)
with the file records of the pinned manifest.
The verifier checks, against the sealed records of the bundle's files:
control-build.json: exactly its keys, Pallet's package name, the pinned version and commit, a clean source, the target and the fixed host paths; every file it declares carries the sealed digest and size, and every executable in the bundle is one it declares;- the executables
pallet-controlstarts by name (pallet-control,pallet-smartdb,pallet-runtime,pallet-guard,pallet-dns,pallet-vpn) and their tsrust provenance: the sealed digest, the owning project and binary, the target, a clean build, and forpallet-runtimethe pinned version and commit; - the clock and containerd manifests, which list every file below their directories with digest, size and mode, and the digests the control record pins for them and for the DNS and VPN notices;
- the notice trees (
native-notices/,notices/smartnftables/) and the clock and containerd notice and corresponding-source inventories: each manifest names exactly the files below it.
Refusals are PalletBundleError values with a fixed code; see TPalletBundleErrorCode.
Service units
import { SystemdUnit, SystemdUnitFile } from '@push.rocks/smartdaemon';
import {
createPalletGuardServiceDefinition, createPalletContainerdServiceDefinition,
createPalletNodeServiceDefinition, palletContainerdServiceSettingsMatch,
} from '@serve.zone/pallet-bundle';
const control = '/opt/onebox/pallet/current/pallet-control';
const guard = createPalletGuardServiceDefinition({ unitName: 'onebox-pallet-guard.service',
executable: control, workingDirectory: '/opt/onebox/pallet' });
const containerd = createPalletContainerdServiceDefinition({ unitName: 'onebox-pallet-containerd.service',
controlExecutable: control, workingDirectory: '/opt/onebox/pallet',
guardUnitName: guard.unitName, nodeUnitName: 'onebox-pallet-node.service' });
const node = createPalletNodeServiceDefinition({ unitName: 'onebox-pallet-node.service',
controlExecutable: control, workingDirectory: '/opt/onebox/pallet',
guardUnitName: guard.unitName, containerdUnitName: containerd.unitName });The definitions are SystemdServiceDefinition values of @push.rocks/smartdaemon; install them
with its SystemdUnitFile. The unit names and the install root are the installer's.
- The guard unit is a oneshot that runs
guard-recoverbeforenetwork-pre.targetandsystemd-networkd.service, which both require it. Install it only after the firstguard-commission. Its executable ispallet-controlor an installer's own executable that verifies the installation before it starts that mode. - The containerd unit runs
containerd-serveto the contract in Pallet's readme ("Pallet's own containerd").palletContainerdServiceSettingsMatchreads the loaded unit's settings, drop-ins included, back against that contract. - The node unit runs
runtime-unit --forwarding-mode <mode>, the node lifetime as a unit's main process, with the host's forwarding mode (forwardingMode, required:docker-sharedwhile Docker runs on the host,exclusiveonce it is removed; see Pallet's readme, "Host forwarding modes"): null stdin, stopped by SIGTERM,KillMode=mixed, restarted on failure by the service manager, after the guard and containerd units.palletNodeServiceSettingsMatch(settings, unitName, { controlExecutable, forwardingMode })reads it back, its command line included: exactly oneExecStart, whose argument vector (smartdaemon ≥ 2.16.0 reads it from the unit's D-Bus property, one string per argument) is the control executable,runtime-unit,--forwarding-modeand the stated mode, so a unit installed for the other mode never matches.palletNodeStopTimeoutSeconds(360) is its leastTimeoutStopSec=.palletNodeStartTimeoutSeconds(600) is how long a start may take before itsreadyline: the start fences what the previous lifetime retained first (derivation in Pallet's readme, "Foreground node process").Type=execdoes not wait forready; an installer that does allows at least that long. - The node unit keeps the node's router network namespace across restarts in its file descriptor
store: it renders
NotifyAccess=all(the native that owns the namespace, a child of the main process, stores it asFDNAME=pallet-router),FileDescriptorStoreMax=1andFileDescriptorStorePreserve=yes, which need systemd 254 or later, andpalletNodeServiceSettingsMatchrequires all three — a node unit a 35.x installer wrote no longer matches and must be installed again. The manager hands the namespace back to the next main process throughLISTEN_FDS; a reboot empties the store, and so does unloading the unit.
import { releasePalletNodeDescriptorStore } from '@serve.zone/pallet-bundle';
// The node unit stopped: on every change of its forwarding mode, and on uninstall.
await releasePalletNodeDescriptorStore(new SystemdUnit({ unitName: node.unitName }));releasePalletNodeDescriptorStore(unit) releases that store (systemctl clean --what=fdstore).
An installer calls it with the node unit stopped — inactive with no pending job, a failed one
after reset-failed — whenever it changes the node's forwarding mode and when it uninstalls the
node, so no namespace of the previous mode or installation is adopted. It resolves once the manager
released the store, also when only the inspection after the release failed; a clean whose outcome
is unknown is decided by inspecting the unit, and a store still held, or a release it cannot
confirm, rejects with the PalletBundleError code store_kept. Its cause is the clean's failure,
or, when the inspection that was to decide that clean failed as well, an AggregateError of both,
the clean's failure first.
Process supervisors
import { PalletControlOneshot, PalletControlLifetime } from '@serve.zone/pallet-bundle';
// A null-stdin oneshot: guard-recover, guard-commission, network-acquire, network-acquire-local, store-provision.
await new PalletControlOneshot({ binaryPath: control, binarySha256: bundle.executables.control.sha256 })
.run('store-provision', signal);
// A supervised lifetime held by a stdin pipe: enrollment-serve, enrollment-provision, runtime-serve.
const lifetime = new PalletControlLifetime({ binaryPath: control, binarySha256, mode: 'enrollment-serve' });
await lifetime.run(signal, async (running) => { /* talk to the enrollment socket */ });
// The node states who owns the host's IPv4 forwarding, always: `docker-shared` while Docker runs on
// the host, `exclusive` once it is removed. The enrollment modes take no forwarding mode.
const node = new PalletControlLifetime({ binaryPath: control, binarySha256, mode: 'runtime-serve', forwardingMode: 'docker-shared' });Both verify the executable's digest before every start, start it with a clean environment and /
as its working directory, read the PALLET_CONTROL lines of the mode's protocol and forward its
standard error line by line, bounded (PalletControlStderr). They never restart a child. A oneshot
succeeds only on ready, stopped, drained pipes and exit status 0. A lifetime runs the operation
while the child is ready; closing its stdin is the graceful stop, and a missed shutdown deadline
signals SIGTERM and then SIGKILL, which fails the lifetime as shutdown_failed. Refusals are
PalletControlProcessError values; unsupported_kernel carries the operator's remedy.
A node that refuses its stated forwarding mode for a fact of the host that persists until an
operator changes it fails with the reason forwarding_mode_refused (palletControlFailureReasons;
PalletControlProcessError code forwarding_mode_refused), before or after ready: persisted IPv4
forwarding or a running Docker under exclusive, no iptables-nft frontend or a host that does not
forward under docker-shared. Restarting the node cannot help; a supervisor ends it instead.
Everything transient stays owner_failed.
A supervisor that keeps the router namespace for runtime-serve itself — as Spark does with the
descriptor its unit's store hands back — passes it as descriptor 3, without close-on-exec, and
appends palletRouterNamespaceDescriptorArgument and palletRouterNamespaceDescriptor
(--router-namespace-descriptor 3) after the forwarding mode; the node stores a namespace it
creates under palletRouterNamespaceDescriptorName (pallet-router). The node verifies the
descriptor against its journal before it adopts it, marks it close-on-exec itself and closes the
inherited number before any child starts. PalletControlLifetime never passes a descriptor.
A lifetime whose child does not report ready within startupTimeoutMs fails as
startup_timeout. For runtime-serve it defaults to the node's start contract,
palletNodeStartTimeoutSeconds × 1000 (600 s), and admits up to an hour for a node that retains
many workloads; the enrollment modes default to 15 s and admit up to 60 s. shutdownTimeoutMs
defaults to 330 s for runtime-serve (up to 600 s) and 15 s for the enrollment modes (up to 60 s).
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license is included in this package as license.md.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at [email protected].
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
