@serve.zone/pallet-bundle 35.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist_ts_bundle/classes.controlprocess.d.ts +71 -0
  2. package/dist_ts_bundle/classes.controlprocess.js +441 -0
  3. package/dist_ts_bundle/classes.controlstderr.d.ts +36 -0
  4. package/dist_ts_bundle/classes.controlstderr.js +95 -0
  5. package/dist_ts_bundle/classes.releaseclient.d.ts +38 -0
  6. package/dist_ts_bundle/classes.releaseclient.js +285 -0
  7. package/dist_ts_bundle/errors.d.ts +35 -0
  8. package/dist_ts_bundle/errors.js +20 -0
  9. package/dist_ts_bundle/index.d.ts +10 -0
  10. package/dist_ts_bundle/index.js +11 -0
  11. package/dist_ts_bundle/inventory.d.ts +90 -0
  12. package/dist_ts_bundle/inventory.js +78 -0
  13. package/dist_ts_bundle/paths.d.ts +12 -0
  14. package/dist_ts_bundle/paths.js +76 -0
  15. package/dist_ts_bundle/plugins.d.ts +14 -0
  16. package/dist_ts_bundle/plugins.js +19 -0
  17. package/dist_ts_bundle/protocol.d.ts +48 -0
  18. package/dist_ts_bundle/protocol.js +47 -0
  19. package/dist_ts_bundle/units.d.ts +67 -0
  20. package/dist_ts_bundle/units.js +128 -0
  21. package/dist_ts_bundle/verify.d.ts +75 -0
  22. package/dist_ts_bundle/verify.js +263 -0
  23. package/dist_ts_bundle/version.d.ts +7 -0
  24. package/dist_ts_bundle/version.js +17 -0
  25. package/license.md +21 -0
  26. package/package.json +35 -0
  27. package/readme.md +176 -0
  28. package/ts_bundle/classes.controlprocess.ts +453 -0
  29. package/ts_bundle/classes.controlstderr.ts +92 -0
  30. package/ts_bundle/classes.releaseclient.ts +288 -0
  31. package/ts_bundle/errors.ts +40 -0
  32. package/ts_bundle/index.ts +10 -0
  33. package/ts_bundle/inventory.ts +104 -0
  34. package/ts_bundle/paths.ts +74 -0
  35. package/ts_bundle/plugins.ts +25 -0
  36. package/ts_bundle/protocol.ts +56 -0
  37. package/ts_bundle/readme.md +176 -0
  38. package/ts_bundle/units.ts +165 -0
  39. package/ts_bundle/verify.ts +353 -0
  40. package/ts_bundle/version.ts +18 -0
@@ -0,0 +1,176 @@
1
+ # @serve.zone/pallet-bundle
2
+
3
+ The install surface of Pallet's sealed control bundle, released from Pallet's repository with
4
+ Pallet's own version. An installer — Spark on fleet nodes, Onebox on its own host — uses it to
5
+
6
+ - download a pinned Pallet release and verify it with tspack,
7
+ - verify the extracted bundle: its control record, every executable's role and build provenance, the
8
+ component manifests and the notice and source inventories,
9
+ - write the service units Pallet's "Service unit contract" states, and read them back from the
10
+ service manager,
11
+ - run and supervise `pallet-control` modes over its `PALLET_CONTROL` process protocol.
12
+
13
+ Pallet is the source of truth for all of it. Pallet's own packaging runs this package's verifier over
14
+ every bundle it seals, so a release never disagrees with the version of this package that carries
15
+ the same version number.
16
+
17
+ ## Issue Reporting and Security
18
+
19
+ For reporting bugs, issues, or security vulnerabilities, please visit
20
+ [community.foss.global/](https://community.foss.global/). This is the central community hub for all
21
+ issue reporting. Developers who sign and comply with our contribution agreement and go through
22
+ identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull
23
+ Requests directly.
24
+
25
+ ## Install
26
+
27
+ ```sh
28
+ pnpm install @serve.zone/pallet-bundle
29
+ ```
30
+
31
+ The package runs under Node.js and Deno; it uses `node:` APIs and the global `fetch` only.
32
+
33
+ It reads its own version at runtime from the `package.json` beside its compiled modules
34
+ (`version.ts`, resolved relative to `import.meta.url`). Consume it as an npm dependency and do not
35
+ bundle it into another file: a bundler that inlines the modules loses that manifest, and
36
+ `palletBundleVersion` and every call that takes a pin (the release client and the verifiers) then fail. `deno compile`
37
+ keeps it: a compiled binary embeds each npm package it
38
+ uses with its `package.json`, whether the dependency comes from a `node_modules` directory
39
+ installed by pnpm or from Deno's own `nodeModulesDir: "auto"` install.
40
+
41
+ ## Pins
42
+
43
+ A release is selected only by its pin, the values a consumer compiles in:
44
+
45
+ ```typescript
46
+ import type { IPalletBundlePin } from '@serve.zone/pallet-bundle';
47
+
48
+ const pin: IPalletBundlePin = {
49
+ version: '35.3.0',
50
+ sourceCommit: '<the 40-hex commit of the v35.3.0 tag>',
51
+ manifestSha256: '<SHA-256 of that release\'s tspack-manifest.json>',
52
+ };
53
+ ```
54
+
55
+ Every entry point refuses a pin whose `version` differs from this package's own version
56
+ (`version_mismatch`): the inventory and contracts it states belong to that one Pallet version.
57
+ Upgrade the package and the pin together.
58
+
59
+ ## Download, extract and verify
60
+
61
+ ```typescript
62
+ import { PalletReleaseClient, extractPalletControlBundle } from '@serve.zone/pallet-bundle';
63
+
64
+ const client = new PalletReleaseClient();
65
+ const bundle = await client.run(pin, 'linux-amd64', '/opt/onebox/pallet', (download) =>
66
+ extractPalletControlBundle(download, 'linux-amd64', '/opt/onebox/pallet', pin, signal), signal);
67
+ // bundle.directory is a fresh private directory below /opt/onebox/pallet; the caller owns it.
68
+ console.log(bundle.executables.control.path, bundle.executables.control.sha256);
69
+ console.log(bundle.containerdManifestSha256, bundle.provenance.store.projectVersion);
70
+ ```
71
+
72
+ `PalletReleaseClient.run` resolves the release `v<version>` of `serve.zone/pallet` on
73
+ `https://code.foss.global`, requires its tag to name the pinned commit, verifies the release metadata
74
+ against the pinned manifest digest (tspack `verifyMetadata`), downloads the manifest, the checksums
75
+ and the target's archive into a private directory below the given parent, verifies the archive
76
+ (tspack `verifyBundle`) and hands the directory to the callback. It removes the download directory
77
+ when the callback settles. A release that exists without all of its assets answers
78
+ `release_pending` with the missing asset names.
79
+
80
+ `extractPalletControlBundle` extracts the target's archive with tspack `extractBundle`, then runs
81
+ `verifyPalletControlBundle` over it and removes the extraction again if that fails. For a bundle
82
+ that is already extracted, call `verifyPalletControlBundle(directory, sealedFiles, pin, target)`
83
+ with the file records of the pinned manifest.
84
+
85
+ The verifier checks, against the sealed records of the bundle's files:
86
+
87
+ - `control-build.json`: exactly its keys, Pallet's package name, the pinned version and commit, a
88
+ clean source, the target and the fixed host paths; every file it declares carries the sealed
89
+ digest and size, and every executable in the bundle is one it declares;
90
+ - the executables `pallet-control` starts by name (`pallet-control`, `pallet-smartdb`,
91
+ `pallet-runtime`, `pallet-guard`, `pallet-dns`, `pallet-vpn`) and their tsrust provenance: the
92
+ sealed digest, the owning project and binary, the target, a clean build, and for `pallet-runtime`
93
+ the pinned version and commit;
94
+ - the clock and containerd manifests, which list every file below their directories with digest,
95
+ size and mode, and the digests the control record pins for them and for the DNS and VPN notices;
96
+ - the notice trees (`native-notices/`, `notices/smartnftables/`) and the clock and containerd notice
97
+ and corresponding-source inventories: each manifest names exactly the files below it.
98
+
99
+ Refusals are `PalletBundleError` values with a fixed `code`; see `TPalletBundleErrorCode`.
100
+
101
+ ## Service units
102
+
103
+ ```typescript
104
+ import { SystemdUnit, SystemdUnitFile } from '@push.rocks/smartdaemon';
105
+ import {
106
+ createPalletGuardServiceDefinition, createPalletContainerdServiceDefinition,
107
+ createPalletNodeServiceDefinition, palletContainerdServiceSettingsMatch,
108
+ } from '@serve.zone/pallet-bundle';
109
+
110
+ const control = '/opt/onebox/pallet/current/pallet-control';
111
+ const guard = createPalletGuardServiceDefinition({ unitName: 'onebox-pallet-guard.service',
112
+ executable: control, workingDirectory: '/opt/onebox/pallet' });
113
+ const containerd = createPalletContainerdServiceDefinition({ unitName: 'onebox-pallet-containerd.service',
114
+ controlExecutable: control, workingDirectory: '/opt/onebox/pallet',
115
+ guardUnitName: guard.unitName, nodeUnitName: 'onebox-pallet-node.service' });
116
+ const node = createPalletNodeServiceDefinition({ unitName: 'onebox-pallet-node.service',
117
+ controlExecutable: control, workingDirectory: '/opt/onebox/pallet',
118
+ guardUnitName: guard.unitName, containerdUnitName: containerd.unitName });
119
+ ```
120
+
121
+ The definitions are `SystemdServiceDefinition` values of `@push.rocks/smartdaemon`; install them
122
+ with its `SystemdUnitFile`. The unit names and the install root are the installer's.
123
+
124
+ - The guard unit is a oneshot that runs `guard-recover` before `network-pre.target` and
125
+ `systemd-networkd.service`, which both require it. Install it only after the first
126
+ `guard-commission`. Its executable is `pallet-control` or an installer's own executable that
127
+ verifies the installation before it starts that mode.
128
+ - The containerd unit runs `containerd-serve` to the contract in Pallet's readme ("Pallet's own
129
+ containerd"). `palletContainerdServiceSettingsMatch` reads the loaded unit's settings, drop-ins
130
+ included, back against that contract.
131
+ - The node unit runs `runtime-unit`, the node lifetime as a unit's main process: null stdin, stopped
132
+ by SIGTERM, `KillMode=mixed`, restarted on failure by the service manager, after the guard and
133
+ containerd units. `palletNodeServiceSettingsMatch` reads it back.
134
+
135
+ ## Process supervisors
136
+
137
+ ```typescript
138
+ import { PalletControlOneshot, PalletControlLifetime } from '@serve.zone/pallet-bundle';
139
+
140
+ // A null-stdin oneshot: guard-recover, guard-commission, network-acquire, network-acquire-local, store-provision.
141
+ await new PalletControlOneshot({ binaryPath: control, binarySha256: bundle.executables.control.sha256 })
142
+ .run('store-provision', signal);
143
+
144
+ // A supervised lifetime held by a stdin pipe: enrollment-serve, enrollment-provision, runtime-serve.
145
+ const lifetime = new PalletControlLifetime({ binaryPath: control, binarySha256, mode: 'enrollment-serve' });
146
+ await lifetime.run(signal, async (running) => { /* talk to the enrollment socket */ });
147
+ ```
148
+
149
+ Both verify the executable's digest before every start, start it with a clean environment and `/`
150
+ as its working directory, read the `PALLET_CONTROL` lines of the mode's protocol and forward its
151
+ standard error line by line, bounded (`PalletControlStderr`). They never restart a child. A oneshot
152
+ succeeds only on `ready`, `stopped`, drained pipes and exit status 0. A lifetime runs the operation
153
+ while the child is `ready`; closing its stdin is the graceful stop, and a missed shutdown deadline
154
+ signals SIGTERM and then SIGKILL, which fails the lifetime as `shutdown_failed`. Refusals are
155
+ `PalletControlProcessError` values; `unsupported_kernel` carries the operator's remedy.
156
+
157
+ ## License and Legal Information
158
+
159
+ This repository contains open-source code licensed under the MIT License. A copy of the license is included in this package as `license.md`.
160
+
161
+ **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.
162
+
163
+ ### Trademarks
164
+
165
+ 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.
166
+
167
+ 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.
168
+
169
+ ### Company Information
170
+
171
+ Task Venture Capital GmbH<br>
172
+ Registered at District Court Bremen HRB 35230 HB, Germany
173
+
174
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
175
+
176
+ 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.
@@ -0,0 +1,165 @@
1
+ import * as plugins from './plugins.js';
2
+ import { PalletBundleError } from './errors.js';
3
+
4
+ /**
5
+ * The service units an installer writes for Pallet, to the "Service unit contract" in Pallet's readme.
6
+ * Pallet never writes a unit file itself: Spark on fleet nodes and Onebox on its own host install
7
+ * these definitions under their own unit names and install roots.
8
+ */
9
+
10
+ /** The one network provider the guard's supported host profile orders itself before. */
11
+ export const palletGuardNetworkProviderName = 'systemd-networkd.service';
12
+
13
+ /** How long a stop of the node may take: a five-minute native operation, then the database joins. */
14
+ export const palletNodeStopTimeoutSeconds = 360;
15
+
16
+ export interface IPalletGuardUnitOptions {
17
+ unitName: string;
18
+ /**
19
+ * The boot command's executable. It runs `guard-recover`: `pallet-control` itself, or an
20
+ * installer's own executable that verifies the installed bundle before it starts that mode.
21
+ */
22
+ executable: string;
23
+ workingDirectory: string;
24
+ }
25
+
26
+ export interface IPalletContainerdUnitOptions {
27
+ unitName: string;
28
+ /** `<bundle>/pallet-control` of the selected installation. */
29
+ controlExecutable: string;
30
+ workingDirectory: string;
31
+ guardUnitName: string;
32
+ /** The node unit, the only CRI client, which this unit starts before. */
33
+ nodeUnitName: string;
34
+ }
35
+
36
+ export interface IPalletNodeUnitOptions {
37
+ unitName: string;
38
+ /** `<bundle>/pallet-control` of the selected installation. */
39
+ controlExecutable: string;
40
+ workingDirectory: string;
41
+ guardUnitName: string;
42
+ containerdUnitName: string;
43
+ }
44
+
45
+ const definition = (optionsArg: plugins.smartdaemon.ISystemdServiceDefinitionOptions) => {
46
+ try { return new plugins.smartdaemon.SystemdServiceDefinition(optionsArg); }
47
+ catch (error) { throw new PalletBundleError('invalid_options', { cause: error }); }
48
+ };
49
+
50
+ /**
51
+ * The retained allocation-pool guard's boot unit: a oneshot that recovers the commissioned guard
52
+ * before networkd, required by `network-pre.target` and by networkd itself. The installer installs it
53
+ * only after the first `guard-commission`, and never replaces an installed one with other bytes.
54
+ */
55
+ export const createPalletGuardServiceDefinition = (optionsArg: IPalletGuardUnitOptions) => definition({
56
+ unitName: optionsArg.unitName,
57
+ description: 'serve.zone retained allocation-pool guard',
58
+ executable: optionsArg.executable,
59
+ args: ['guard-recover'],
60
+ workingDirectory: optionsArg.workingDirectory,
61
+ user: 'root',
62
+ group: 'root',
63
+ restart: 'no',
64
+ killMode: 'mixed',
65
+ timeoutStopSeconds: 360,
66
+ startup: { type: 'oneshot', remainAfterExit: true, timeoutStartSeconds: 600 },
67
+ dependencies: {
68
+ defaultDependencies: false,
69
+ after: [],
70
+ before: ['network-pre.target', palletGuardNetworkProviderName, 'shutdown.target'],
71
+ requires: [],
72
+ wants: ['network-pre.target'],
73
+ conflicts: ['shutdown.target'],
74
+ },
75
+ install: { wantedBy: [], requiredBy: ['network-pre.target', palletGuardNetworkProviderName] },
76
+ });
77
+
78
+ /**
79
+ * Pallet's containerd unit: `pallet-control containerd-serve` as the root main process with null stdin,
80
+ * ready on the `READY=1` its native owner (a child of the main process) sends. Stopping the unit
81
+ * signals only `containerd-serve`, which stops containerd itself; every shim and workload keeps
82
+ * running and is reattached by the next containerd. containerd's exit fails the process and the
83
+ * service manager restarts it — Pallet never restarts containerd inside the process.
84
+ */
85
+ export const createPalletContainerdServiceDefinition = (optionsArg: IPalletContainerdUnitOptions) => definition({
86
+ unitName: optionsArg.unitName,
87
+ description: 'serve.zone Pallet containerd',
88
+ executable: optionsArg.controlExecutable,
89
+ args: ['containerd-serve'],
90
+ workingDirectory: optionsArg.workingDirectory,
91
+ user: 'root',
92
+ group: 'root',
93
+ restart: 'always',
94
+ restartSeconds: 2,
95
+ killMode: 'process',
96
+ // Pallet's 30 s containerd grace plus the owner's joins; the contract's floor is 45 s.
97
+ timeoutStopSeconds: 60,
98
+ // containerd-serve waits up to 60 s for CRI readiness, then imports the pause image.
99
+ startup: { type: 'notify', notifyAccess: 'all', timeoutStartSeconds: 120 },
100
+ delegate: true,
101
+ limitNoFile: 'infinity',
102
+ tasksMax: 'infinity',
103
+ oomScoreAdjust: -999,
104
+ dependencies: {
105
+ defaultDependencies: true,
106
+ after: [optionsArg.guardUnitName],
107
+ before: [optionsArg.nodeUnitName],
108
+ requires: [optionsArg.guardUnitName],
109
+ wants: [],
110
+ conflicts: [],
111
+ },
112
+ });
113
+
114
+ /**
115
+ * The node unit: `pallet-control runtime-unit` as the root main process with null stdin. It runs the
116
+ * same node lifetime as `runtime-serve`, but standard input is not its parent's lifetime: SIGTERM or
117
+ * SIGINT stops it. `KillMode=mixed` signals only the main process, which joins every owner and child
118
+ * it started, and the service manager removes whatever remains of the unit's cgroup when the stop
119
+ * timeout ends, before any successor starts. A failed lifetime is restarted by the service manager,
120
+ * never inside the process.
121
+ */
122
+ export const createPalletNodeServiceDefinition = (optionsArg: IPalletNodeUnitOptions) => definition({
123
+ unitName: optionsArg.unitName,
124
+ description: 'serve.zone Pallet node',
125
+ executable: optionsArg.controlExecutable,
126
+ args: ['runtime-unit'],
127
+ workingDirectory: optionsArg.workingDirectory,
128
+ user: 'root',
129
+ group: 'root',
130
+ restart: 'on-failure',
131
+ restartSeconds: 5,
132
+ killMode: 'mixed',
133
+ timeoutStopSeconds: palletNodeStopTimeoutSeconds,
134
+ dependencies: {
135
+ defaultDependencies: true,
136
+ after: [optionsArg.guardUnitName, optionsArg.containerdUnitName],
137
+ before: [],
138
+ requires: [optionsArg.guardUnitName],
139
+ wants: [optionsArg.containerdUnitName],
140
+ conflicts: [],
141
+ },
142
+ });
143
+
144
+ /**
145
+ * Whether the service manager's own reading of the containerd unit, drop-ins included, is the
146
+ * contract. Admission and activation read the loaded unit rather than the file, so a drop-in that
147
+ * weakens the contract is refused whatever file produced it.
148
+ */
149
+ export const palletContainerdServiceSettingsMatch = (settingsArg: plugins.smartdaemon.ISystemdServiceSettings,
150
+ unitNameArg: string): boolean =>
151
+ settingsArg.unitName === unitNameArg && settingsArg.type === 'notify' && settingsArg.notifyAccess === 'all' &&
152
+ settingsArg.restart === 'always' && typeof settingsArg.restartMicroseconds === 'number' &&
153
+ settingsArg.restartMicroseconds > 0 && settingsArg.restartMicroseconds <= 10_000_000 && settingsArg.killMode === 'process' &&
154
+ settingsArg.delegate && typeof settingsArg.timeoutStopMicroseconds === 'number' &&
155
+ settingsArg.timeoutStopMicroseconds >= 45_000_000 && settingsArg.limitNoFile === 'infinity' &&
156
+ settingsArg.limitNoFileSoft === 'infinity' && settingsArg.tasksMax === 'infinity' && settingsArg.oomScoreAdjust === -999;
157
+
158
+ /** The same reading of the node unit: no restart inside a stop, and a stop long enough to join. */
159
+ export const palletNodeServiceSettingsMatch = (settingsArg: plugins.smartdaemon.ISystemdServiceSettings,
160
+ unitNameArg: string): boolean =>
161
+ settingsArg.unitName === unitNameArg && settingsArg.type === 'exec' && settingsArg.killMode === 'mixed' &&
162
+ settingsArg.restart === 'on-failure' &&
163
+ typeof settingsArg.timeoutStopMicroseconds === 'number' &&
164
+ settingsArg.timeoutStopMicroseconds >= palletNodeStopTimeoutSeconds * 1_000_000 && !settingsArg.delegate &&
165
+ Object.keys(settingsArg.environment).length === 0;