@forgezero/agent 0.1.62 → 0.1.64
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.
- package/README.md +45 -16
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap-bundle.d.ts +31 -0
- package/dist/bootstrap-bundle.js +165 -0
- package/dist/bootstrap.d.ts +24 -18
- package/dist/bootstrap.js +509 -269
- package/dist/cli/agent-install.d.ts +2 -0
- package/dist/community-rehearsal-host.js +7 -5
- package/dist/definition.js +7 -5
- package/dist/deploy-file.js +7 -5
- package/dist/deployment.d.ts +27 -0
- package/dist/fz-agent.js +594 -421
- package/dist/fz.js +847 -372
- package/dist/metal-bootstrap.js +1 -1
- package/dist/metal-helper-socket.js +0 -2
- package/dist/metal-provision.js +0 -2
- package/dist/operator-bootstrap.d.ts +43 -1
- package/dist/operator-bootstrap.js +788 -367
- package/dist/platform-bootstrap-runtime.d.ts +0 -2
- package/dist/platform-bootstrap-runtime.js +0 -4
- package/dist/platform-fleet-verification.js +190 -98
- package/dist/provision.d.ts +4 -1
- package/dist/provision.js +41 -32
- package/dist/software-helper.js +7 -5
- package/dist/software.d.ts +1 -1
- package/dist/software.js +7 -5
- package/dist/version.d.ts +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ Every row links to the detailed explanation and named-import/example area below.
|
|
|
51
51
|
| @forgezero/agent/agent-update | Verified Agent update planning against the platform release coordinate. | Bun/Node host | [Details + example](#forgezero-agent-agent-update) |
|
|
52
52
|
| @forgezero/agent/agent-update-helper | Root-owned fixed update operations with registry origin and integrity validation. | Bun/Node host | [Details + example](#forgezero-agent-agent-update-helper) |
|
|
53
53
|
| @forgezero/agent/bootstrap | Typed one-time platform bootstrap and internal API-driven enrolled-compute activation contracts. | Bun/Node host | [Details + example](#forgezero-agent-bootstrap) |
|
|
54
|
+
| @forgezero/agent/bootstrap-bundle | Owner-only exact-revision Git bundle and SHA-256 manifest for credential-free release generation one. | Bun/Node host | [Details + example](#forgezero-agent-bootstrap-bundle) |
|
|
54
55
|
| @forgezero/agent/operator-bootstrap | Pinned-host operator transport that stages owner-only handoffs and invokes the same typed platform bootstrap through the caller-approved SSH agent. | Bun/Node host | [Details + example](#forgezero-agent-operator-bootstrap) |
|
|
55
56
|
| @forgezero/agent/host-maintenance | Closed host maintenance plans for staging transaction rehearsal and fleet-fenced schema consolidation through fixed transient systemd units. | Bun/Node host | [Details + example](#forgezero-agent-host-maintenance) |
|
|
56
57
|
| @forgezero/agent/capacity-calibration | One-time target-local safe-concurrency calibration with private evidence. | Bun/Node host | [Details + example](#forgezero-agent-capacity-calibration) |
|
|
@@ -82,7 +83,8 @@ bun add -g @forgezero/agent — Install the version-matched fz operator CLI and
|
|
|
82
83
|
fz deploy init — Create a typed forgezero.deploy.ts and its canonical inert execution plan.
|
|
83
84
|
fz deploy compile — Compile and validate TypeScript deployment intent into .fz/deploy.plan.json.
|
|
84
85
|
fz deploy check — Refuse stale plans, unsafe provider/action coordinates and unresolved deployment blockers.
|
|
85
|
-
fz bootstrap platform
|
|
86
|
+
fz bootstrap platform bundle --root /absolute/api --branch dev --output /secure/forgezero/development/api.bundle — Build one verified release-generation-one source bundle without a Git credential.
|
|
87
|
+
fz bootstrap platform remote apply --bootstrap-config /secure/forgezero/development/dev-fz-n1-remote.json — Dry-plan one pinned operator-to-guest bootstrap; add --apply only after review.
|
|
86
88
|
fz bootstrap platform --help — Inspect attended platform bootstrap inputs before applying them.
|
|
87
89
|
fz bootstrap metal --help — Inspect the identity-only physical provisioner bootstrap.
|
|
88
90
|
fz host transactions|schema-plan|schema-apply — Plan fixed supervised database maintenance; add --apply only after reviewing the exact argv and credential names.
|
|
@@ -93,7 +95,8 @@ bun add -g @forgezero/agent
|
|
|
93
95
|
fz deploy init
|
|
94
96
|
fz deploy compile
|
|
95
97
|
fz deploy check
|
|
96
|
-
fz bootstrap platform
|
|
98
|
+
fz bootstrap platform bundle --root /absolute/api --branch dev --output /secure/forgezero/development/api.bundle
|
|
99
|
+
fz bootstrap platform remote apply --bootstrap-config /secure/forgezero/development/dev-fz-n1-remote.json
|
|
97
100
|
fz bootstrap platform --help
|
|
98
101
|
fz bootstrap metal --help
|
|
99
102
|
fz host transactions|schema-plan|schema-apply
|
|
@@ -278,38 +281,64 @@ import type { AgentUpdateOutcome, AgentUpdateReceipt, AgentUpdateRequest, AgentU
|
|
|
278
281
|
<a id="forgezero-agent-bootstrap"></a>
|
|
279
282
|
## @forgezero/agent/bootstrap
|
|
280
283
|
|
|
281
|
-
Typed one-time platform bootstrap and internal API-driven enrolled-compute activation contracts. Named value imports: BOOTSTRAP_STATE_PATH, PLATFORM_BOOTSTRAP_PROFILES, applyBootstrap, bootstrapIdentityDigest, bootstrapStatus, localBootstrapHost, planBootstrap,
|
|
284
|
+
Typed one-time platform bootstrap and internal API-driven enrolled-compute activation contracts. Named value imports: BOOTSTRAP_STATE_PATH, PLATFORM_BOOTSTRAP_PROFILES, applyBootstrap, bootstrapIdentityDigest, bootstrapStatus, localBootstrapHost, planBootstrap, planBootstrapAgentInstall, readBootstrapConfig, resolveInstalledBootstrapKind, validateBootstrapConfig, validateEnrolledComputeBootstrapSecrets, validatePlatformBootstrapSecrets. Named type imports: BootstrapAgentInstallPhase, BootstrapConfig, BootstrapEnvironment, BootstrapHost, BootstrapPlan, BootstrapResult, BootstrapSecrets, BootstrapStatus, BootstrapStep, DatabaseAgencyParticipation, DatabaseBootstrapRole, EnrolledComputeActivationConfig, EnrolledComputeBootstrapSecrets, InstalledBootstrapKind, PlatformBootstrapConfig, PlatformBootstrapProfile, PlatformBootstrapSecrets, PlatformEnrolmentSource. Import only the names used by this file.
|
|
282
285
|
|
|
283
286
|
```text
|
|
284
287
|
import { BOOTSTRAP_STATE_PATH, PLATFORM_BOOTSTRAP_PROFILES, applyBootstrap, bootstrapIdentityDigest, bootstrapStatus, localBootstrapHost } from '@forgezero/agent/bootstrap';
|
|
285
|
-
import { planBootstrap,
|
|
288
|
+
import { planBootstrap, planBootstrapAgentInstall, readBootstrapConfig, resolveInstalledBootstrapKind, validateBootstrapConfig, validateEnrolledComputeBootstrapSecrets } from '@forgezero/agent/bootstrap';
|
|
286
289
|
import { validatePlatformBootstrapSecrets } from '@forgezero/agent/bootstrap';
|
|
287
|
-
import type { BootstrapConfig, BootstrapEnvironment, BootstrapHost, BootstrapPlan, BootstrapResult
|
|
288
|
-
import type { BootstrapStatus, BootstrapStep, DatabaseAgencyParticipation, DatabaseBootstrapRole, EnrolledComputeActivationConfig
|
|
289
|
-
import type { InstalledBootstrapKind, PlatformBootstrapConfig,
|
|
290
|
+
import type { BootstrapAgentInstallPhase, BootstrapConfig, BootstrapEnvironment, BootstrapHost, BootstrapPlan, BootstrapResult } from '@forgezero/agent/bootstrap';
|
|
291
|
+
import type { BootstrapSecrets, BootstrapStatus, BootstrapStep, DatabaseAgencyParticipation, DatabaseBootstrapRole, EnrolledComputeActivationConfig } from '@forgezero/agent/bootstrap';
|
|
292
|
+
import type { EnrolledComputeBootstrapSecrets, InstalledBootstrapKind, PlatformBootstrapConfig, PlatformBootstrapProfile, PlatformBootstrapSecrets, PlatformEnrolmentSource } from '@forgezero/agent/bootstrap';
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
<a id="forgezero-agent-bootstrap-bundle"></a>
|
|
296
|
+
## @forgezero/agent/bootstrap-bundle
|
|
297
|
+
|
|
298
|
+
Owner-only exact-revision Git bundle and SHA-256 manifest for credential-free release generation one. Named value imports: BOOTSTRAP_BUNDLE_FORMAT, BOOTSTRAP_BUNDLE_KIND, MAX_BOOTSTRAP_BUNDLE_BYTES, buildBootstrapBundle, parseBootstrapBundleManifest, readBootstrapBundle, sha256File. Named type imports: BootstrapBundleBuildInput, BootstrapBundleBuildResult, BootstrapBundleCommand, BootstrapBundleCommandResult, BootstrapBundleManifest. Import only the names used by this file.
|
|
299
|
+
|
|
300
|
+
```text
|
|
301
|
+
import { BOOTSTRAP_BUNDLE_FORMAT, BOOTSTRAP_BUNDLE_KIND, MAX_BOOTSTRAP_BUNDLE_BYTES, buildBootstrapBundle, parseBootstrapBundleManifest, readBootstrapBundle } from '@forgezero/agent/bootstrap-bundle';
|
|
302
|
+
import { sha256File } from '@forgezero/agent/bootstrap-bundle';
|
|
303
|
+
import type { BootstrapBundleBuildInput, BootstrapBundleBuildResult, BootstrapBundleCommand, BootstrapBundleCommandResult, BootstrapBundleManifest } from '@forgezero/agent/bootstrap-bundle';
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## @forgezero/agent/bootstrap-bundle — Build release generation one without a deploy key
|
|
307
|
+
|
|
308
|
+
The builder refuses tracked drift, resolves one exact commit, creates an owner-only Git bundle and writes a byte-count/SHA-256 manifest. Bootstrap consumes the artifact after activation.
|
|
309
|
+
|
|
310
|
+
```text
|
|
311
|
+
import { buildBootstrapBundle, readBootstrapBundle } from '@forgezero/agent/bootstrap-bundle';
|
|
312
|
+
|
|
313
|
+
const built = await buildBootstrapBundle({
|
|
314
|
+
repositoryRoot: '/work/forgezero-api', branch: 'main',
|
|
315
|
+
outputPath: '/secure/forgezero/production/api.bundle'
|
|
316
|
+
});
|
|
317
|
+
await readBootstrapBundle(built.bundlePath, built.manifestPath);
|
|
290
318
|
```
|
|
291
319
|
|
|
292
320
|
<a id="forgezero-agent-operator-bootstrap"></a>
|
|
293
321
|
## @forgezero/agent/operator-bootstrap
|
|
294
322
|
|
|
295
|
-
Pinned-host operator transport that stages owner-only handoffs and invokes the same typed platform bootstrap through the caller-approved SSH agent. Named value imports: applyOperatorMetalBootstrap, applyOperatorPlatformBootstrap, planOperatorMetalBootstrap, planOperatorPlatformBootstrap, readOperatorMetalBootstrapRequest, readOperatorPlatformBootstrapRequest. Named type imports: OperatorCommand, OperatorCommandResult, OperatorMetalBootstrapMode, OperatorMetalBootstrapPlan, OperatorMetalBootstrapRequest, OperatorPlatformBootstrapMode, OperatorPlatformBootstrapOptions, OperatorPlatformBootstrapPlan, OperatorPlatformBootstrapRequest, OperatorSshHop. Import only the names used by this file.
|
|
323
|
+
Pinned-host operator transport that stages owner-only handoffs and invokes the same typed platform bootstrap through the caller-approved SSH agent. Named value imports: applyOperatorMetalBootstrap, applyOperatorPlatformBootstrap, applyOperatorPlatformFleetBootstrap, createOperatorSshHop, planOperatorMetalBootstrap, planOperatorPlatformBootstrap, planOperatorPlatformFleetBootstrap, readOperatorMetalBootstrapRequest, readOperatorPlatformBootstrapFleetRequest, readOperatorPlatformBootstrapRequest, writeOperatorMetalBootstrapRequest, writeOperatorPlatformBootstrapFleetRequest, writeOperatorPlatformBootstrapRequest. Named type imports: OperatorCommand, OperatorCommandResult, OperatorMetalBootstrapMode, OperatorMetalBootstrapPlan, OperatorMetalBootstrapRequest, OperatorPlatformBootstrapFleetOutcome, OperatorPlatformBootstrapFleetPlan, OperatorPlatformBootstrapFleetRequest, OperatorPlatformBootstrapFleetResult, OperatorPlatformBootstrapMode, OperatorPlatformBootstrapOptions, OperatorPlatformBootstrapPlan, OperatorPlatformBootstrapRequest, OperatorSshHop. Import only the names used by this file.
|
|
296
324
|
|
|
297
325
|
```text
|
|
298
|
-
import { applyOperatorMetalBootstrap, applyOperatorPlatformBootstrap,
|
|
299
|
-
import
|
|
300
|
-
import
|
|
326
|
+
import { applyOperatorMetalBootstrap, applyOperatorPlatformBootstrap, applyOperatorPlatformFleetBootstrap, createOperatorSshHop, planOperatorMetalBootstrap, planOperatorPlatformBootstrap } from '@forgezero/agent/operator-bootstrap';
|
|
327
|
+
import { planOperatorPlatformFleetBootstrap, readOperatorMetalBootstrapRequest, readOperatorPlatformBootstrapFleetRequest, readOperatorPlatformBootstrapRequest, writeOperatorMetalBootstrapRequest, writeOperatorPlatformBootstrapFleetRequest } from '@forgezero/agent/operator-bootstrap';
|
|
328
|
+
import { writeOperatorPlatformBootstrapRequest } from '@forgezero/agent/operator-bootstrap';
|
|
329
|
+
import type { OperatorCommand, OperatorCommandResult, OperatorMetalBootstrapMode, OperatorMetalBootstrapPlan, OperatorMetalBootstrapRequest, OperatorPlatformBootstrapFleetOutcome } from '@forgezero/agent/operator-bootstrap';
|
|
330
|
+
import type { OperatorPlatformBootstrapFleetPlan, OperatorPlatformBootstrapFleetRequest, OperatorPlatformBootstrapFleetResult, OperatorPlatformBootstrapMode, OperatorPlatformBootstrapOptions, OperatorPlatformBootstrapPlan } from '@forgezero/agent/operator-bootstrap';
|
|
331
|
+
import type { OperatorPlatformBootstrapRequest, OperatorSshHop } from '@forgezero/agent/operator-bootstrap';
|
|
301
332
|
```
|
|
302
333
|
|
|
303
334
|
## @forgezero/agent/operator-bootstrap — Run the same typed platform bootstrap from an operator laptop
|
|
304
335
|
|
|
305
|
-
The request contains
|
|
336
|
+
The request contains the pinned SSH transport, reviewed platform config, and one verified local Git bundle copied identically to every genesis compute. No repository key is created. Hidden credential values are sealed immediately as systemd credentials.
|
|
306
337
|
|
|
307
338
|
```text
|
|
308
|
-
fz bootstrap platform remote
|
|
309
|
-
--bootstrap-config /secure/forgezero/development/dev-fz-n1-remote.json
|
|
339
|
+
fz bootstrap platform remote apply --bootstrap-config /secure/forgezero/development/dev-fz-n1-remote.json
|
|
310
340
|
# Review, then allow the SSH-agent approval prompt:
|
|
311
|
-
fz bootstrap platform remote
|
|
312
|
-
--bootstrap-config /secure/forgezero/development/dev-fz-n1-remote.json --apply
|
|
341
|
+
fz bootstrap platform remote apply --bootstrap-config /secure/forgezero/development/dev-fz-n1-remote.json --apply
|
|
313
342
|
```
|
|
314
343
|
|
|
315
344
|
<a id="forgezero-agent-host-maintenance"></a>
|
package/dist/agent-heartbeat.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const BOOTSTRAP_BUNDLE_FORMAT: 1;
|
|
2
|
+
export declare const BOOTSTRAP_BUNDLE_KIND: "forgezero-api-git-bundle";
|
|
3
|
+
export declare const MAX_BOOTSTRAP_BUNDLE_BYTES: number;
|
|
4
|
+
export interface BootstrapBundleManifest {
|
|
5
|
+
format: typeof BOOTSTRAP_BUNDLE_FORMAT;
|
|
6
|
+
kind: typeof BOOTSTRAP_BUNDLE_KIND;
|
|
7
|
+
branch: string;
|
|
8
|
+
revision: string;
|
|
9
|
+
sha256: string;
|
|
10
|
+
bytes: number;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
}
|
|
13
|
+
export interface BootstrapBundleBuildInput {
|
|
14
|
+
repositoryRoot: string;
|
|
15
|
+
outputPath: string;
|
|
16
|
+
branch: string;
|
|
17
|
+
}
|
|
18
|
+
export interface BootstrapBundleBuildResult {
|
|
19
|
+
bundlePath: string;
|
|
20
|
+
manifestPath: string;
|
|
21
|
+
manifest: BootstrapBundleManifest;
|
|
22
|
+
}
|
|
23
|
+
export interface BootstrapBundleCommandResult {
|
|
24
|
+
exitCode: number;
|
|
25
|
+
output: string;
|
|
26
|
+
}
|
|
27
|
+
export type BootstrapBundleCommand = (argv: readonly string[]) => Promise<BootstrapBundleCommandResult>;
|
|
28
|
+
export declare function sha256File(path: string): Promise<string>;
|
|
29
|
+
export declare function parseBootstrapBundleManifest(value: unknown): BootstrapBundleManifest;
|
|
30
|
+
export declare function readBootstrapBundle(bundlePath: string, manifestPath?: string): Promise<BootstrapBundleBuildResult>;
|
|
31
|
+
export declare function buildBootstrapBundle(input: BootstrapBundleBuildInput, exec?: BootstrapBundleCommand): Promise<BootstrapBundleBuildResult>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/bootstrap-bundle.ts
|
|
3
|
+
import { createHash, randomBytes } from "crypto";
|
|
4
|
+
import {
|
|
5
|
+
chmodSync,
|
|
6
|
+
createReadStream,
|
|
7
|
+
existsSync,
|
|
8
|
+
lstatSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
renameSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
writeFileSync
|
|
14
|
+
} from "fs";
|
|
15
|
+
import { dirname, isAbsolute, resolve } from "path";
|
|
16
|
+
var BOOTSTRAP_BUNDLE_FORMAT = 1;
|
|
17
|
+
var BOOTSTRAP_BUNDLE_KIND = "forgezero-api-git-bundle";
|
|
18
|
+
var MAX_BOOTSTRAP_BUNDLE_BYTES = 512 * 1024 * 1024;
|
|
19
|
+
var run = async (argv) => {
|
|
20
|
+
const child = Bun.spawn([...argv], { stdin: "ignore", stdout: "pipe", stderr: "pipe" });
|
|
21
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
22
|
+
new Response(child.stdout).text(),
|
|
23
|
+
new Response(child.stderr).text(),
|
|
24
|
+
child.exited
|
|
25
|
+
]);
|
|
26
|
+
return { exitCode, output: `${stdout}${stderr}`.slice(0, 65536) };
|
|
27
|
+
};
|
|
28
|
+
var checked = async (exec, argv, label) => {
|
|
29
|
+
const result = await exec(argv);
|
|
30
|
+
if (result.exitCode !== 0)
|
|
31
|
+
throw new Error(`${label} failed${result.output.trim() ? `: ${result.output.trim()}` : ""}`);
|
|
32
|
+
return result.output.trim();
|
|
33
|
+
};
|
|
34
|
+
async function sha256File(path) {
|
|
35
|
+
const hash = createHash("sha256");
|
|
36
|
+
await new Promise((resolveDone, reject) => {
|
|
37
|
+
const stream = createReadStream(path);
|
|
38
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
39
|
+
stream.on("error", reject);
|
|
40
|
+
stream.on("end", resolveDone);
|
|
41
|
+
});
|
|
42
|
+
return hash.digest("hex");
|
|
43
|
+
}
|
|
44
|
+
var branchName = (value) => {
|
|
45
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9](?:[A-Za-z0-9._/-]{0,126}[A-Za-z0-9])?$/.test(value) || value.includes("..") || value.includes("//") || value.startsWith("-")) {
|
|
46
|
+
throw new Error("bootstrap bundle branch is malformed");
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
};
|
|
50
|
+
function parseBootstrapBundleManifest(value) {
|
|
51
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
52
|
+
throw new Error("bootstrap bundle manifest must be an object");
|
|
53
|
+
const source = value;
|
|
54
|
+
const allowed = ["format", "kind", "branch", "revision", "sha256", "bytes", "createdAt"];
|
|
55
|
+
const unknown = Object.keys(source).filter((key) => !allowed.includes(key));
|
|
56
|
+
if (unknown.length)
|
|
57
|
+
throw new Error(`bootstrap bundle manifest contains unknown field ${unknown[0]}`);
|
|
58
|
+
if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
|
|
59
|
+
throw new Error("bootstrap bundle manifest format is unsupported");
|
|
60
|
+
}
|
|
61
|
+
branchName(source.branch);
|
|
62
|
+
if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
|
|
63
|
+
throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
|
|
64
|
+
}
|
|
65
|
+
if (typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256)) {
|
|
66
|
+
throw new Error("bootstrap bundle digest is malformed");
|
|
67
|
+
}
|
|
68
|
+
if (!Number.isSafeInteger(source.bytes) || Number(source.bytes) < 1 || Number(source.bytes) > MAX_BOOTSTRAP_BUNDLE_BYTES) {
|
|
69
|
+
throw new Error("bootstrap bundle size is outside the supported boundary");
|
|
70
|
+
}
|
|
71
|
+
if (typeof source.createdAt !== "string" || Number.isNaN(Date.parse(source.createdAt))) {
|
|
72
|
+
throw new Error("bootstrap bundle creation time is malformed");
|
|
73
|
+
}
|
|
74
|
+
return source;
|
|
75
|
+
}
|
|
76
|
+
function ownerRegularFile(path, maximum, label) {
|
|
77
|
+
if (!isAbsolute(path) || resolve(path) !== path || /[\r\n\0]/.test(path)) {
|
|
78
|
+
throw new Error(`${label} path must be canonical and absolute`);
|
|
79
|
+
}
|
|
80
|
+
const value = lstatSync(path);
|
|
81
|
+
const uid = process.getuid?.() ?? value.uid;
|
|
82
|
+
if (!value.isFile() || value.isSymbolicLink() || value.uid !== uid || value.nlink !== 1 || (value.mode & 63) !== 0 || value.size < 1 || value.size > maximum) {
|
|
83
|
+
throw new Error(`${label} must be an owner-only regular file with one link and at most ${maximum} bytes`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function readBootstrapBundle(bundlePath, manifestPath = `${bundlePath}.json`) {
|
|
87
|
+
ownerRegularFile(bundlePath, MAX_BOOTSTRAP_BUNDLE_BYTES, "bootstrap bundle");
|
|
88
|
+
ownerRegularFile(manifestPath, 16 * 1024, "bootstrap bundle manifest");
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
92
|
+
} catch {
|
|
93
|
+
throw new Error("bootstrap bundle manifest is not valid JSON");
|
|
94
|
+
}
|
|
95
|
+
const manifest = parseBootstrapBundleManifest(parsed);
|
|
96
|
+
const size = lstatSync(bundlePath).size;
|
|
97
|
+
if (size !== manifest.bytes || await sha256File(bundlePath) !== manifest.sha256) {
|
|
98
|
+
throw new Error("bootstrap bundle bytes do not match their manifest");
|
|
99
|
+
}
|
|
100
|
+
return { bundlePath, manifestPath, manifest };
|
|
101
|
+
}
|
|
102
|
+
async function buildBootstrapBundle(input, exec = run) {
|
|
103
|
+
const repositoryRoot = resolve(input.repositoryRoot);
|
|
104
|
+
const outputPath = resolve(input.outputPath);
|
|
105
|
+
const manifestPath = `${outputPath}.json`;
|
|
106
|
+
const branch = branchName(input.branch);
|
|
107
|
+
if (!isAbsolute(input.outputPath) || outputPath !== input.outputPath) {
|
|
108
|
+
throw new Error("bootstrap bundle output must be a canonical absolute path");
|
|
109
|
+
}
|
|
110
|
+
if (existsSync(outputPath) || existsSync(manifestPath)) {
|
|
111
|
+
throw new Error("bootstrap bundle output already exists");
|
|
112
|
+
}
|
|
113
|
+
const root = lstatSync(repositoryRoot);
|
|
114
|
+
if (!root.isDirectory() || root.isSymbolicLink())
|
|
115
|
+
throw new Error("bootstrap bundle source must be a real directory");
|
|
116
|
+
mkdirSync(dirname(outputPath), { recursive: true, mode: 448 });
|
|
117
|
+
const parent = lstatSync(dirname(outputPath));
|
|
118
|
+
const uid = process.getuid?.() ?? parent.uid;
|
|
119
|
+
if (!parent.isDirectory() || parent.isSymbolicLink() || parent.uid !== uid || (parent.mode & 63) !== 0) {
|
|
120
|
+
throw new Error("bootstrap bundle output directory must be caller-owned and owner-only");
|
|
121
|
+
}
|
|
122
|
+
const dirty = await checked(exec, ["git", "-C", repositoryRoot, "status", "--porcelain=v1", "--untracked-files=no"], "Git worktree check");
|
|
123
|
+
if (dirty)
|
|
124
|
+
throw new Error("bootstrap bundle source has tracked changes; commit the reviewed API release first");
|
|
125
|
+
const revision = (await checked(exec, ["git", "-C", repositoryRoot, "rev-parse", "--verify", `refs/heads/${branch}^{commit}`], "Git branch resolution")).toLowerCase();
|
|
126
|
+
if (!/^[a-f0-9]{40}$/.test(revision))
|
|
127
|
+
throw new Error("bootstrap bundle branch did not resolve to one exact commit");
|
|
128
|
+
const temporary = `${outputPath}.next.${process.pid}.${randomBytes(6).toString("hex")}`;
|
|
129
|
+
try {
|
|
130
|
+
await checked(exec, ["git", "-C", repositoryRoot, "bundle", "create", temporary, `refs/heads/${branch}`], "Git bundle creation");
|
|
131
|
+
chmodSync(temporary, 384);
|
|
132
|
+
const file = lstatSync(temporary);
|
|
133
|
+
if (!file.isFile() || file.isSymbolicLink() || file.size < 1 || file.size > MAX_BOOTSTRAP_BUNDLE_BYTES) {
|
|
134
|
+
throw new Error("created bootstrap bundle is outside the supported boundary");
|
|
135
|
+
}
|
|
136
|
+
await checked(exec, ["git", "bundle", "verify", temporary], "Git bundle verification");
|
|
137
|
+
const manifest = {
|
|
138
|
+
format: BOOTSTRAP_BUNDLE_FORMAT,
|
|
139
|
+
kind: BOOTSTRAP_BUNDLE_KIND,
|
|
140
|
+
branch,
|
|
141
|
+
revision,
|
|
142
|
+
sha256: await sha256File(temporary),
|
|
143
|
+
bytes: file.size,
|
|
144
|
+
createdAt: new Date().toISOString()
|
|
145
|
+
};
|
|
146
|
+
const manifestTemporary = `${manifestPath}.next.${process.pid}.${randomBytes(6).toString("hex")}`;
|
|
147
|
+
writeFileSync(manifestTemporary, `${JSON.stringify(manifest, null, 2)}
|
|
148
|
+
`, { mode: 384, flag: "wx" });
|
|
149
|
+
renameSync(temporary, outputPath);
|
|
150
|
+
renameSync(manifestTemporary, manifestPath);
|
|
151
|
+
return { bundlePath: outputPath, manifestPath, manifest };
|
|
152
|
+
} catch (cause) {
|
|
153
|
+
rmSync(temporary, { force: true });
|
|
154
|
+
throw cause;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
sha256File,
|
|
159
|
+
readBootstrapBundle,
|
|
160
|
+
parseBootstrapBundleManifest,
|
|
161
|
+
buildBootstrapBundle,
|
|
162
|
+
MAX_BOOTSTRAP_BUNDLE_BYTES,
|
|
163
|
+
BOOTSTRAP_BUNDLE_KIND,
|
|
164
|
+
BOOTSTRAP_BUNDLE_FORMAT
|
|
165
|
+
};
|
package/dist/bootstrap.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ProvisionPlan } from './cli/agent-install';
|
|
2
|
+
import type { Capabilities } from './provision';
|
|
2
3
|
import { type SoftwareCommandResult, type SoftwareRequirement } from './software';
|
|
3
4
|
import { type PlatformSharedEnvironment } from './platform-bootstrap-runtime';
|
|
5
|
+
import { type BootstrapBundleManifest } from './bootstrap-bundle';
|
|
4
6
|
export declare const PLATFORM_BOOTSTRAP_PROFILES: readonly ["platform-db-api", "platform-api"];
|
|
5
7
|
export type PlatformBootstrapProfile = (typeof PLATFORM_BOOTSTRAP_PROFILES)[number];
|
|
6
8
|
export type DatabaseBootstrapRole = 'master' | 'joiner' | 'none';
|
|
@@ -15,8 +17,11 @@ export interface PlatformBootstrapConfig {
|
|
|
15
17
|
computeReference: string;
|
|
16
18
|
nodeHostname: string;
|
|
17
19
|
apiUrl: string;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
/** One attended local source used only for release generation one. */
|
|
21
|
+
bootstrapBundle: {
|
|
22
|
+
bundleFile: string;
|
|
23
|
+
manifestFile: string;
|
|
24
|
+
};
|
|
20
25
|
deployRoot?: string;
|
|
21
26
|
telemetryEndpoint: string;
|
|
22
27
|
/**
|
|
@@ -76,6 +81,8 @@ export interface EnrolledComputeActivationConfig {
|
|
|
76
81
|
profile?: string;
|
|
77
82
|
deployRoot?: string;
|
|
78
83
|
software?: SoftwareRequirement[];
|
|
84
|
+
/** Compatibility-only per-node SSH deploy key. GitHub App is the scalable default. */
|
|
85
|
+
gitDeployKey?: boolean;
|
|
79
86
|
installCloudflared?: boolean;
|
|
80
87
|
installWarp?: boolean;
|
|
81
88
|
/** Optional node-specific attended Tunnel/KV/Mesh handoff, identical to platform hosts. */
|
|
@@ -136,13 +143,6 @@ export interface BootstrapResult {
|
|
|
136
143
|
inviteUrl: string;
|
|
137
144
|
};
|
|
138
145
|
}
|
|
139
|
-
export interface PlatformBootstrapPreparation {
|
|
140
|
-
kind: 'platform';
|
|
141
|
-
prepared: true;
|
|
142
|
-
identityDigest: string;
|
|
143
|
-
gitPublicKey: string;
|
|
144
|
-
next: 'register this read-only deploy key, then run --apply concurrently on all three genesis Agency members';
|
|
145
|
-
}
|
|
146
146
|
export interface BootstrapStatus {
|
|
147
147
|
initialized: boolean;
|
|
148
148
|
kind?: BootstrapConfig['kind'];
|
|
@@ -151,6 +151,7 @@ export interface BootstrapStatus {
|
|
|
151
151
|
problems: string[];
|
|
152
152
|
}
|
|
153
153
|
export type InstalledBootstrapKind = 'compute' | 'metal';
|
|
154
|
+
export type BootstrapAgentInstallPhase = 'bootstrap' | 'enrol' | 'bound';
|
|
154
155
|
/** One host has one bootstrap authority. Dual state is never resolved by precedence. */
|
|
155
156
|
export declare function resolveInstalledBootstrapKind(states: {
|
|
156
157
|
compute: boolean;
|
|
@@ -176,23 +177,28 @@ export interface BootstrapHost {
|
|
|
176
177
|
}): Promise<SoftwareCommandResult>;
|
|
177
178
|
sleep?(milliseconds: number): Promise<void>;
|
|
178
179
|
ensureSoftware(requirements: readonly SoftwareRequirement[]): Promise<unknown>;
|
|
179
|
-
installAgent(config: BootstrapConfig): Promise<ProvisionPlan>;
|
|
180
|
+
installAgent(config: BootstrapConfig, phase: BootstrapAgentInstallPhase): Promise<ProvisionPlan>;
|
|
180
181
|
}
|
|
181
182
|
export declare const BOOTSTRAP_STATE_PATH = "/var/lib/forgezero/bootstrap.json";
|
|
182
183
|
export declare function validateBootstrapConfig(value: BootstrapConfig): BootstrapConfig;
|
|
183
184
|
export declare function planBootstrap(input: BootstrapConfig, initialized?: boolean): BootstrapPlan;
|
|
184
185
|
export declare function bootstrapIdentityDigest(config: BootstrapConfig): string;
|
|
185
|
-
/**
|
|
186
|
-
* Install only the common Agent boundary and generate its machine-local Git
|
|
187
|
-
* identity. Private repositories cannot be deployed until the owner registers
|
|
188
|
-
* this public key, so pretending one monolithic first command can do both is a
|
|
189
|
-
* sequencing bug. The bound intent makes the later apply resumable and refuses
|
|
190
|
-
* a different config after this first privileged mutation.
|
|
191
|
-
*/
|
|
192
|
-
export declare function preparePlatformBootstrap(input: PlatformBootstrapConfig, host?: BootstrapHost): Promise<PlatformBootstrapPreparation>;
|
|
193
186
|
export declare function bootstrapStatus(host?: BootstrapHost): Promise<BootstrapStatus>;
|
|
194
187
|
export declare function applyBootstrap(input: BootstrapConfig, host?: BootstrapHost, secrets?: BootstrapSecrets, dependencies?: {
|
|
195
188
|
fetcher?: typeof fetch;
|
|
196
189
|
}): Promise<BootstrapResult>;
|
|
197
190
|
export declare function readBootstrapConfig(path: string): BootstrapConfig;
|
|
191
|
+
interface BootstrapAgentInitialBundle {
|
|
192
|
+
path: string;
|
|
193
|
+
manifestPath: string;
|
|
194
|
+
manifest: BootstrapBundleManifest;
|
|
195
|
+
}
|
|
196
|
+
/** Compose one real local Agent convergence without consulting mutable globals. */
|
|
197
|
+
export declare function planBootstrapAgentInstall(config: BootstrapConfig, phase: BootstrapAgentInstallPhase, context: {
|
|
198
|
+
capabilities: Capabilities;
|
|
199
|
+
hasBinding: boolean;
|
|
200
|
+
hasEnrolCredential: boolean;
|
|
201
|
+
initialBundle?: BootstrapAgentInitialBundle;
|
|
202
|
+
}): ProvisionPlan;
|
|
198
203
|
export declare function localBootstrapHost(): BootstrapHost;
|
|
204
|
+
export {};
|