@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/dist/fz-agent.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
|
-
import { randomBytes as
|
|
6
|
-
import { readFileSync as
|
|
5
|
+
import { randomBytes as randomBytes7 } from "crypto";
|
|
6
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync17, existsSync as existsSync21, mkdirSync as mkdirSync17, chmodSync as chmodSync18, lstatSync as lstatSync9, statfsSync as statfsSync2 } from "fs";
|
|
7
7
|
import { cpus, totalmem } from "os";
|
|
8
8
|
import { dirname as dirname12, join as join12 } from "path";
|
|
9
9
|
|
|
@@ -4641,8 +4641,8 @@ function safeOp(line) {
|
|
|
4641
4641
|
}
|
|
4642
4642
|
|
|
4643
4643
|
// src/deployment.ts
|
|
4644
|
-
import { chmodSync as
|
|
4645
|
-
import { createHash as
|
|
4644
|
+
import { chmodSync as chmodSync4, existsSync as existsSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, statfsSync, writeFileSync as writeFileSync3 } from "fs";
|
|
4645
|
+
import { createHash as createHash5, randomUUID } from "crypto";
|
|
4646
4646
|
import { dirname, isAbsolute as isAbsolute2, join as join2 } from "path";
|
|
4647
4647
|
|
|
4648
4648
|
// ../runtime/dist/queue.js
|
|
@@ -4980,7 +4980,8 @@ var SOFTWARE_CATALOG = [
|
|
|
4980
4980
|
{ id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4981
4981
|
{ id: "cloudflare-warp", version: "2026.6.822.0-min", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4982
4982
|
{ id: "ufw", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4983
|
-
{ id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
|
|
4983
|
+
{ id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
|
|
4984
|
+
{ id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
|
|
4984
4985
|
];
|
|
4985
4986
|
var UBUNTU_2604_X64 = [
|
|
4986
4987
|
{ requirement: { id: "bun", version: "1.3.14" } },
|
|
@@ -4990,7 +4991,8 @@ var UBUNTU_2604_X64 = [
|
|
|
4990
4991
|
{ requirement: { id: "cloudflared", version: "2026.7.3" } },
|
|
4991
4992
|
{ requirement: { id: "cloudflare-warp", version: "2026.6.822.0-min" } },
|
|
4992
4993
|
{ requirement: { id: "ufw", version: "ubuntu-26.04" } },
|
|
4993
|
-
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } }
|
|
4994
|
+
{ requirement: { id: "openssh-client", version: "ubuntu-26.04" } },
|
|
4995
|
+
{ requirement: { id: "git", version: "ubuntu-26.04" } }
|
|
4994
4996
|
];
|
|
4995
4997
|
var path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
4996
4998
|
var DOCKER_DAEMON_PATH = "/etc/docker/daemon.json";
|
|
@@ -5077,7 +5079,7 @@ async function executeSoftwareOperation(operation) {
|
|
|
5077
5079
|
const supported = observed && observed.some((part, index) => part > minimum[index] && observed.slice(0, index).every((prior, priorIndex) => prior === minimum[priorIndex])) || observed?.every((part, index) => part === minimum[index]);
|
|
5078
5080
|
return { ...result, exitCode: result.exitCode === 0 && supported ? 0 : 1 };
|
|
5079
5081
|
});
|
|
5080
|
-
const binaries = software === "ufw" ? ["/usr/sbin/ufw"] : ["/usr/bin/ssh", "/usr/bin/scp", "/usr/bin/ssh-keyscan", "/usr/bin/ssh-keygen"];
|
|
5082
|
+
const binaries = software === "ufw" ? ["/usr/sbin/ufw"] : software === "git" ? ["/usr/bin/git"] : ["/usr/bin/ssh", "/usr/bin/scp", "/usr/bin/ssh-keyscan", "/usr/bin/ssh-keygen"];
|
|
5081
5083
|
try {
|
|
5082
5084
|
binaries.forEach((binary) => accessSync(binary));
|
|
5083
5085
|
return { exitCode: 0, output: "" };
|
|
@@ -5085,7 +5087,7 @@ async function executeSoftwareOperation(operation) {
|
|
|
5085
5087
|
return { exitCode: 1, output: cause instanceof Error ? cause.message : String(cause) };
|
|
5086
5088
|
}
|
|
5087
5089
|
}
|
|
5088
|
-
if (software === "docker" || software === "nginx" || software === "ufw" || software === "openssh-client") {
|
|
5090
|
+
if (software === "docker" || software === "nginx" || software === "ufw" || software === "openssh-client" || software === "git") {
|
|
5089
5091
|
const packageName = software === "openssh-client" ? "openssh-client" : software === "docker" ? "docker.io" : software;
|
|
5090
5092
|
const installed = await aptInstall(packageName);
|
|
5091
5093
|
if (installed.exitCode !== 0 || software !== "nginx" && software !== "docker")
|
|
@@ -5185,7 +5187,7 @@ function validateSoftwareRequirements(value, _options = {}) {
|
|
|
5185
5187
|
if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
|
|
5186
5188
|
throw new Error("software requirement contains an unknown field");
|
|
5187
5189
|
}
|
|
5188
|
-
if (!["bun", "docker", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
|
|
5190
|
+
if (!["bun", "docker", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client", "git"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
|
|
5189
5191
|
throw new Error("software requirement coordinate is invalid");
|
|
5190
5192
|
}
|
|
5191
5193
|
const requirement = { id: row.id, version: row.version };
|
|
@@ -7120,6 +7122,65 @@ async function runDeploymentPlan(options) {
|
|
|
7120
7122
|
// src/deploy-compiler.ts
|
|
7121
7123
|
var DEPLOY_PLAN_FILE = ".fz/deploy.plan.json";
|
|
7122
7124
|
|
|
7125
|
+
// src/bootstrap-bundle.ts
|
|
7126
|
+
import { createHash as createHash4, randomBytes as randomBytes5 } from "crypto";
|
|
7127
|
+
import {
|
|
7128
|
+
chmodSync as chmodSync3,
|
|
7129
|
+
createReadStream,
|
|
7130
|
+
existsSync as existsSync3,
|
|
7131
|
+
lstatSync,
|
|
7132
|
+
mkdirSync as mkdirSync2,
|
|
7133
|
+
readFileSync as readFileSync2,
|
|
7134
|
+
renameSync as renameSync2,
|
|
7135
|
+
rmSync as rmSync2,
|
|
7136
|
+
writeFileSync as writeFileSync2
|
|
7137
|
+
} from "fs";
|
|
7138
|
+
var BOOTSTRAP_BUNDLE_FORMAT = 1;
|
|
7139
|
+
var BOOTSTRAP_BUNDLE_KIND = "forgezero-api-git-bundle";
|
|
7140
|
+
var MAX_BOOTSTRAP_BUNDLE_BYTES = 512 * 1024 * 1024;
|
|
7141
|
+
async function sha256File(path2) {
|
|
7142
|
+
const hash = createHash4("sha256");
|
|
7143
|
+
await new Promise((resolveDone, reject) => {
|
|
7144
|
+
const stream = createReadStream(path2);
|
|
7145
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
7146
|
+
stream.on("error", reject);
|
|
7147
|
+
stream.on("end", resolveDone);
|
|
7148
|
+
});
|
|
7149
|
+
return hash.digest("hex");
|
|
7150
|
+
}
|
|
7151
|
+
var branchName = (value) => {
|
|
7152
|
+
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("-")) {
|
|
7153
|
+
throw new Error("bootstrap bundle branch is malformed");
|
|
7154
|
+
}
|
|
7155
|
+
return value;
|
|
7156
|
+
};
|
|
7157
|
+
function parseBootstrapBundleManifest(value) {
|
|
7158
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
7159
|
+
throw new Error("bootstrap bundle manifest must be an object");
|
|
7160
|
+
const source = value;
|
|
7161
|
+
const allowed = ["format", "kind", "branch", "revision", "sha256", "bytes", "createdAt"];
|
|
7162
|
+
const unknown = Object.keys(source).filter((key) => !allowed.includes(key));
|
|
7163
|
+
if (unknown.length)
|
|
7164
|
+
throw new Error(`bootstrap bundle manifest contains unknown field ${unknown[0]}`);
|
|
7165
|
+
if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
|
|
7166
|
+
throw new Error("bootstrap bundle manifest format is unsupported");
|
|
7167
|
+
}
|
|
7168
|
+
branchName(source.branch);
|
|
7169
|
+
if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
|
|
7170
|
+
throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
|
|
7171
|
+
}
|
|
7172
|
+
if (typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256)) {
|
|
7173
|
+
throw new Error("bootstrap bundle digest is malformed");
|
|
7174
|
+
}
|
|
7175
|
+
if (!Number.isSafeInteger(source.bytes) || Number(source.bytes) < 1 || Number(source.bytes) > MAX_BOOTSTRAP_BUNDLE_BYTES) {
|
|
7176
|
+
throw new Error("bootstrap bundle size is outside the supported boundary");
|
|
7177
|
+
}
|
|
7178
|
+
if (typeof source.createdAt !== "string" || Number.isNaN(Date.parse(source.createdAt))) {
|
|
7179
|
+
throw new Error("bootstrap bundle creation time is malformed");
|
|
7180
|
+
}
|
|
7181
|
+
return source;
|
|
7182
|
+
}
|
|
7183
|
+
|
|
7123
7184
|
// src/deployment.ts
|
|
7124
7185
|
class DeploymentError extends Error {
|
|
7125
7186
|
code;
|
|
@@ -7165,24 +7226,24 @@ function persistCapacityCalibration(args) {
|
|
|
7165
7226
|
if (!isAbsolute2(args.directory)) {
|
|
7166
7227
|
throw new DeploymentError("PIPELINE_FAILED", "capacity evidence directory must be an absolute Agent-owned path");
|
|
7167
7228
|
}
|
|
7168
|
-
if (!
|
|
7169
|
-
const parent =
|
|
7229
|
+
if (!existsSync4(args.directory)) {
|
|
7230
|
+
const parent = lstatSync2(dirname(args.directory));
|
|
7170
7231
|
if (!parent.isDirectory() || parent.isSymbolicLink()) {
|
|
7171
7232
|
throw new DeploymentError("PIPELINE_FAILED", "capacity evidence parent must be a real Agent-owned directory");
|
|
7172
7233
|
}
|
|
7173
|
-
|
|
7234
|
+
mkdirSync3(args.directory, { mode: 448 });
|
|
7174
7235
|
}
|
|
7175
|
-
const stats =
|
|
7236
|
+
const stats = lstatSync2(args.directory);
|
|
7176
7237
|
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
7177
7238
|
throw new DeploymentError("PIPELINE_FAILED", "capacity evidence directory must be a real Agent-owned directory");
|
|
7178
7239
|
}
|
|
7179
|
-
const identity =
|
|
7240
|
+
const identity = createHash5("sha256").update(`${args.deploymentKey}\x00${args.profile}\x00${args.revision}`).digest("hex").slice(0, 24);
|
|
7180
7241
|
const path2 = join2(args.directory, `${identity}-${args.measuredAtTs}-${randomUUID().slice(0, 8)}.json`);
|
|
7181
7242
|
const next = `${path2}.next`;
|
|
7182
7243
|
const recommendedCoordinate = {
|
|
7183
7244
|
FZ_CONCURRENCY_LIMIT: String(args.calibration.recommendedConcurrency)
|
|
7184
7245
|
};
|
|
7185
|
-
|
|
7246
|
+
writeFileSync3(next, `${JSON.stringify({
|
|
7186
7247
|
format: 1,
|
|
7187
7248
|
kind: "forgezero-node-capacity-calibration",
|
|
7188
7249
|
deploymentKey: args.deploymentKey,
|
|
@@ -7194,22 +7255,29 @@ function persistCapacityCalibration(args) {
|
|
|
7194
7255
|
calibration: args.calibration
|
|
7195
7256
|
}, null, 2)}
|
|
7196
7257
|
`, { mode: 384, flag: "wx" });
|
|
7197
|
-
|
|
7198
|
-
|
|
7258
|
+
chmodSync4(next, 384);
|
|
7259
|
+
renameSync3(next, path2);
|
|
7199
7260
|
return path2;
|
|
7200
7261
|
}
|
|
7201
7262
|
function createDeploymentManager(options) {
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7263
|
+
const bootstrapBundle = options.bootstrapBundle ? { path: options.bootstrapBundle.path, manifest: parseBootstrapBundleManifest(options.bootstrapBundle.manifest) } : undefined;
|
|
7264
|
+
if (bootstrapBundle) {
|
|
7265
|
+
if (!isAbsolute2(bootstrapBundle.path) || bootstrapBundle.path !== options.repository || bootstrapBundle.manifest.branch !== options.branch) {
|
|
7266
|
+
throw new DeploymentError("SOURCE_FAILED", "The bootstrap bundle source coordinates disagree.");
|
|
7267
|
+
}
|
|
7268
|
+
} else {
|
|
7269
|
+
try {
|
|
7270
|
+
gitNetworkTarget(options.repository);
|
|
7271
|
+
} catch (cause) {
|
|
7272
|
+
throw new DeploymentError("SOURCE_FAILED", cause instanceof GitEgressError ? cause.message : "The Git source is malformed.");
|
|
7273
|
+
}
|
|
7206
7274
|
}
|
|
7207
7275
|
const queue = createQueue({ width: options.width ?? 4 });
|
|
7208
7276
|
const activeRevisions = new Map;
|
|
7209
7277
|
const exec = options.exec ?? spawnExact;
|
|
7210
7278
|
const projectExec = options.projectExec ?? exec;
|
|
7211
7279
|
const now = options.now ?? Date.now;
|
|
7212
|
-
const readDefinition = options.readDefinition ?? ((path2) => JSON.parse(
|
|
7280
|
+
const readDefinition = options.readDefinition ?? ((path2) => JSON.parse(readFileSync3(path2, "utf8")));
|
|
7213
7281
|
const credentialsDirectory = process.env.CREDENTIALS_DIRECTORY;
|
|
7214
7282
|
const gitCredentialPath = options.gitCredentialPath ?? (credentialsDirectory ? join2(credentialsDirectory, "git-deploy-key") : undefined);
|
|
7215
7283
|
const knownHostsPath = options.knownHostsPath ?? "/etc/forgezero/git/known_hosts";
|
|
@@ -7221,13 +7289,13 @@ function createDeploymentManager(options) {
|
|
|
7221
7289
|
if (content.length > 16385 || /\r|\0/.test(content)) {
|
|
7222
7290
|
throw new DeploymentError("SOURCE_FAILED", "The pinned Git host keys are malformed.");
|
|
7223
7291
|
}
|
|
7224
|
-
if (
|
|
7292
|
+
if (existsSync4(knownHostsPath) && readFileSync3(knownHostsPath, "utf8") === content)
|
|
7225
7293
|
return;
|
|
7226
|
-
|
|
7294
|
+
mkdirSync3(dirname(knownHostsPath), { recursive: true, mode: 448 });
|
|
7227
7295
|
const next = `${knownHostsPath}.${process.pid}.${randomUUID()}.next`;
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7296
|
+
writeFileSync3(next, content, { mode: 384, flag: "wx" });
|
|
7297
|
+
renameSync3(next, knownHostsPath);
|
|
7298
|
+
chmodSync4(knownHostsPath, 384);
|
|
7231
7299
|
};
|
|
7232
7300
|
const gitBaseEnvironment = () => ({
|
|
7233
7301
|
GIT_TERMINAL_PROMPT: "0",
|
|
@@ -7264,6 +7332,8 @@ function createDeploymentManager(options) {
|
|
|
7264
7332
|
return httpsGitVersion;
|
|
7265
7333
|
};
|
|
7266
7334
|
const gitEnvironment = async () => {
|
|
7335
|
+
if (bootstrapBundle)
|
|
7336
|
+
return gitBaseEnvironment();
|
|
7267
7337
|
let target;
|
|
7268
7338
|
try {
|
|
7269
7339
|
target = await resolvePinnedGitTarget(options.repository, options.resolveGitHost);
|
|
@@ -7285,22 +7355,25 @@ function createDeploymentManager(options) {
|
|
|
7285
7355
|
["http.curloptResolve", curlResolveValue(target)]
|
|
7286
7356
|
]);
|
|
7287
7357
|
}
|
|
7288
|
-
if (auth.kind === "vault-token") {
|
|
7358
|
+
if (auth.kind === "vault-token" || auth.kind === "ephemeral-token") {
|
|
7289
7359
|
if (!https)
|
|
7290
7360
|
throw new DeploymentError("SOURCE_FAILED", "Vault Git tokens may only be sent to HTTPS sources.");
|
|
7291
|
-
if (!/^[A-Z_][A-Z0-9_]{0,127}$/.test(auth.secret)) {
|
|
7361
|
+
if (auth.kind === "vault-token" && !/^[A-Z_][A-Z0-9_]{0,127}$/.test(auth.secret)) {
|
|
7292
7362
|
throw new DeploymentError("SOURCE_FAILED", "The Git token secret name is invalid.");
|
|
7293
7363
|
}
|
|
7294
|
-
if (!options.cache) {
|
|
7364
|
+
if (auth.kind === "vault-token" && !options.cache) {
|
|
7295
7365
|
throw new DeploymentError("SECRET_MISSING", `Git source credential ${auth.secret} is unavailable.`);
|
|
7296
7366
|
}
|
|
7297
7367
|
const username = auth.username ?? "x-access-token";
|
|
7298
7368
|
if (!/^[A-Za-z0-9._@+-]{1,128}$/.test(username)) {
|
|
7299
7369
|
throw new DeploymentError("SOURCE_FAILED", "The Git token username is invalid.");
|
|
7300
7370
|
}
|
|
7301
|
-
|
|
7371
|
+
if (auth.kind === "ephemeral-token" && auth.expiresAtTs <= Date.now() + 30000) {
|
|
7372
|
+
throw new DeploymentError("SOURCE_FAILED", "The one-claim Git credential is expired or too close to expiry.");
|
|
7373
|
+
}
|
|
7374
|
+
const token = auth.kind === "ephemeral-token" ? auth.token : await options.cache.get(auth.secret);
|
|
7302
7375
|
if (!token || token.length > 8192 || /[\r\n\0]/.test(token)) {
|
|
7303
|
-
throw new DeploymentError("SOURCE_FAILED",
|
|
7376
|
+
throw new DeploymentError("SOURCE_FAILED", "The Git source credential is malformed.");
|
|
7304
7377
|
}
|
|
7305
7378
|
const origin = new URL(options.repository).origin;
|
|
7306
7379
|
return withGitConfig(gitBaseEnvironment(), [
|
|
@@ -7344,11 +7417,26 @@ function createDeploymentManager(options) {
|
|
|
7344
7417
|
await checked({ argv: ["/usr/bin/test", "-d", releasesDirectory] }, "SOURCE_FAILED");
|
|
7345
7418
|
await checked({ argv: ["/usr/bin/test", "-w", releasesDirectory] }, "SOURCE_FAILED");
|
|
7346
7419
|
await checked({ argv: ["/usr/bin/install", "-d", "-m", "0770", release] }, "SOURCE_FAILED");
|
|
7420
|
+
if (bootstrapBundle) {
|
|
7421
|
+
let metadata;
|
|
7422
|
+
try {
|
|
7423
|
+
metadata = lstatSync2(bootstrapBundle.path);
|
|
7424
|
+
} catch {
|
|
7425
|
+
throw new DeploymentError("SOURCE_FAILED", "The attended bootstrap bundle is missing.");
|
|
7426
|
+
}
|
|
7427
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size !== bootstrapBundle.manifest.bytes || await sha256File(bootstrapBundle.path) !== bootstrapBundle.manifest.sha256) {
|
|
7428
|
+
throw new DeploymentError("SOURCE_FAILED", "The attended bootstrap bundle failed its manifest check.");
|
|
7429
|
+
}
|
|
7430
|
+
await checked({ argv: ["git", "bundle", "verify", bootstrapBundle.path], env }, "SOURCE_FAILED");
|
|
7431
|
+
}
|
|
7347
7432
|
await checked({
|
|
7348
7433
|
argv: ["git", "clone", "--quiet", "--no-checkout", "--branch", options.branch, "--depth", "100", options.repository, release],
|
|
7349
7434
|
env
|
|
7350
7435
|
}, "SOURCE_FAILED");
|
|
7351
|
-
if (request.revision) {
|
|
7436
|
+
if (bootstrapBundle && request.revision && request.revision !== bootstrapBundle.manifest.revision) {
|
|
7437
|
+
throw new DeploymentError("BAD_REVISION", "The requested revision is not the attended bootstrap bundle revision.");
|
|
7438
|
+
}
|
|
7439
|
+
if (request.revision && !bootstrapBundle) {
|
|
7352
7440
|
let present = await exec({
|
|
7353
7441
|
argv: ["git", "cat-file", "-e", `${request.revision}^{commit}`],
|
|
7354
7442
|
cwd: release,
|
|
@@ -7371,17 +7459,18 @@ function createDeploymentManager(options) {
|
|
|
7371
7459
|
env
|
|
7372
7460
|
}, "SOURCE_FAILED");
|
|
7373
7461
|
}
|
|
7462
|
+
const desiredRevision = request.revision ?? bootstrapBundle?.manifest.revision ?? `origin/${options.branch}`;
|
|
7374
7463
|
await checked({
|
|
7375
|
-
argv: ["git", "checkout", "--quiet", "--detach",
|
|
7464
|
+
argv: ["git", "checkout", "--quiet", "--detach", desiredRevision],
|
|
7376
7465
|
cwd: release,
|
|
7377
7466
|
env
|
|
7378
7467
|
}, "SOURCE_FAILED");
|
|
7379
7468
|
const head = (await checked({ argv: ["git", "rev-parse", "HEAD"], cwd: release, env }, "SOURCE_FAILED")).output.trim();
|
|
7380
|
-
if (request.revision && head !==
|
|
7381
|
-
throw new DeploymentError("SOURCE_FAILED", `Git checked out ${head}, not requested ${
|
|
7469
|
+
if ((request.revision || bootstrapBundle) && head !== desiredRevision) {
|
|
7470
|
+
throw new DeploymentError("SOURCE_FAILED", `Git checked out ${head}, not requested ${desiredRevision}.`);
|
|
7382
7471
|
}
|
|
7383
7472
|
const planPath = join2(release, DEPLOY_PLAN_FILE);
|
|
7384
|
-
if (
|
|
7473
|
+
if (existsSync4(planPath)) {
|
|
7385
7474
|
const plan = parseDeploymentPlan(readDefinition(planPath));
|
|
7386
7475
|
for (const workflow of Object.values(plan.spec.workflows))
|
|
7387
7476
|
for (const stage2 of Object.values(workflow.stages)) {
|
|
@@ -7416,10 +7505,28 @@ function createDeploymentManager(options) {
|
|
|
7416
7505
|
const inputValues = {};
|
|
7417
7506
|
for (const [name, definition2] of Object.entries(plan.spec.inputs ?? {})) {
|
|
7418
7507
|
const raw = options.environment?.[name];
|
|
7419
|
-
if (raw === undefined)
|
|
7508
|
+
if (raw === undefined) {
|
|
7509
|
+
if (definition2.default !== undefined)
|
|
7510
|
+
inputValues[name] = definition2.default;
|
|
7420
7511
|
continue;
|
|
7512
|
+
}
|
|
7421
7513
|
inputValues[name] = definition2.type === "boolean" ? raw === "true" : definition2.type === "integer" || definition2.type === "number" ? Number(raw) : raw;
|
|
7422
7514
|
}
|
|
7515
|
+
const resolvedTargets = Object.entries(plan.spec.targets).filter(([, target]) => target.selector.profiles.includes(options.profile)).map(([name, target]) => {
|
|
7516
|
+
const desired = typeof target.cardinality.desired === "number" ? target.cardinality.desired : target.cardinality.desired.$ref.startsWith("inputs.") ? inputValues[target.cardinality.desired.$ref.slice("inputs.".length)] : undefined;
|
|
7517
|
+
if (!Number.isSafeInteger(desired) || Number(desired) < target.cardinality.minimum || Number(desired) > target.cardinality.maximum) {
|
|
7518
|
+
throw new DeploymentError("PIPELINE_FAILED", `target ${name} desired cardinality did not resolve to a bounded integer`);
|
|
7519
|
+
}
|
|
7520
|
+
return {
|
|
7521
|
+
name,
|
|
7522
|
+
profiles: [...target.selector.profiles],
|
|
7523
|
+
confidentialCompute: target.selector.confidentialCompute ?? "disabled",
|
|
7524
|
+
labels: { ...target.selector.labels ?? {} },
|
|
7525
|
+
minimum: target.cardinality.minimum,
|
|
7526
|
+
desired: Number(desired),
|
|
7527
|
+
maximum: target.cardinality.maximum
|
|
7528
|
+
};
|
|
7529
|
+
});
|
|
7423
7530
|
const projectExec2 = options.projectExec ?? spawnExact;
|
|
7424
7531
|
const materializePlanArgv = (argv2) => argv2.map((argument) => {
|
|
7425
7532
|
if (argument === "${FZ_RELEASE}")
|
|
@@ -7498,7 +7605,7 @@ function createDeploymentManager(options) {
|
|
|
7498
7605
|
const component2 = plan.spec.components[String(resolved.component ?? "")];
|
|
7499
7606
|
const mounts = component2?.kind === "database" ? [component2.storage] : component2?.storage ?? [];
|
|
7500
7607
|
for (const mount of mounts) {
|
|
7501
|
-
const candidate =
|
|
7608
|
+
const candidate = existsSync4(mount.path) ? mount.path : dirname(mount.path);
|
|
7502
7609
|
const stats = statfsSync(candidate);
|
|
7503
7610
|
const freePercent = Number(stats.bavail) / Number(stats.blocks) * 100;
|
|
7504
7611
|
const minimum = mount.class === "ephemeral" ? 0 : mount.minimumFreePercent ?? 0;
|
|
@@ -7579,11 +7686,12 @@ function createDeploymentManager(options) {
|
|
|
7579
7686
|
branch: options.branch,
|
|
7580
7687
|
revision: head,
|
|
7581
7688
|
definitionDigest: plan.sourceDigest,
|
|
7582
|
-
definitionVersion:
|
|
7689
|
+
definitionVersion: 2,
|
|
7583
7690
|
profile: options.profile,
|
|
7584
7691
|
release,
|
|
7585
7692
|
ok: true,
|
|
7586
|
-
phases: [phase]
|
|
7693
|
+
phases: [phase],
|
|
7694
|
+
resolvedTargets
|
|
7587
7695
|
};
|
|
7588
7696
|
}
|
|
7589
7697
|
const definition = parseDeployDefinition(readDefinition(join2(release, ".fz", "deploy.json")));
|
|
@@ -7696,7 +7804,7 @@ function createDeploymentManager(options) {
|
|
|
7696
7804
|
branch: options.branch,
|
|
7697
7805
|
revision: head,
|
|
7698
7806
|
definitionDigest,
|
|
7699
|
-
definitionVersion:
|
|
7807
|
+
definitionVersion: 2,
|
|
7700
7808
|
profile: options.profile,
|
|
7701
7809
|
release,
|
|
7702
7810
|
ok: true,
|
|
@@ -7706,6 +7814,8 @@ function createDeploymentManager(options) {
|
|
|
7706
7814
|
};
|
|
7707
7815
|
return {
|
|
7708
7816
|
async latestRevision() {
|
|
7817
|
+
if (bootstrapBundle)
|
|
7818
|
+
return bootstrapBundle.manifest.revision;
|
|
7709
7819
|
const result = await checked({
|
|
7710
7820
|
argv: ["git", "ls-remote", "--exit-code", options.repository, `refs/heads/${options.branch}`],
|
|
7711
7821
|
env: await gitEnvironment()
|
|
@@ -7750,7 +7860,7 @@ function createDeploymentManager(options) {
|
|
|
7750
7860
|
}
|
|
7751
7861
|
|
|
7752
7862
|
// src/control.ts
|
|
7753
|
-
import { chmodSync as
|
|
7863
|
+
import { chmodSync as chmodSync5, existsSync as existsSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
7754
7864
|
import { connect, createServer as createServer2 } from "net";
|
|
7755
7865
|
var DEFAULT_CONTROL_SOCKET = "/run/forgezero/control.sock";
|
|
7756
7866
|
var MAX_REQUEST_BYTES = 16 * 1024;
|
|
@@ -7807,7 +7917,7 @@ async function handleControl(manager, request) {
|
|
|
7807
7917
|
}
|
|
7808
7918
|
}
|
|
7809
7919
|
function startControlServer(manager, socketPath = DEFAULT_CONTROL_SOCKET) {
|
|
7810
|
-
if (
|
|
7920
|
+
if (existsSync5(socketPath))
|
|
7811
7921
|
unlinkSync3(socketPath);
|
|
7812
7922
|
const server = createServer2((socket) => {
|
|
7813
7923
|
let buffer = "";
|
|
@@ -7829,7 +7939,7 @@ function startControlServer(manager, socketPath = DEFAULT_CONTROL_SOCKET) {
|
|
|
7829
7939
|
});
|
|
7830
7940
|
socket.on("error", () => socket.destroy());
|
|
7831
7941
|
});
|
|
7832
|
-
server.listen(socketPath, () =>
|
|
7942
|
+
server.listen(socketPath, () => chmodSync5(socketPath, 384));
|
|
7833
7943
|
return server;
|
|
7834
7944
|
}
|
|
7835
7945
|
function requestControl(request, socketPath = DEFAULT_CONTROL_SOCKET) {
|
|
@@ -7868,6 +7978,7 @@ async function postSigned(options, operation, body) {
|
|
|
7868
7978
|
}
|
|
7869
7979
|
async function deployClaim(options, claim) {
|
|
7870
7980
|
const manager = options.manager ?? options.managerFor?.(claim);
|
|
7981
|
+
const claimOwnedManager = !options.manager && claim.source.auth?.kind === "ephemeral-token";
|
|
7871
7982
|
if (!manager)
|
|
7872
7983
|
throw new Error("deployment pull has no manager for this claim");
|
|
7873
7984
|
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
@@ -7918,6 +8029,8 @@ async function deployClaim(options, claim) {
|
|
|
7918
8029
|
stopped = true;
|
|
7919
8030
|
clearTimer(timer);
|
|
7920
8031
|
await renewal;
|
|
8032
|
+
if (claimOwnedManager)
|
|
8033
|
+
await manager.stop();
|
|
7921
8034
|
}
|
|
7922
8035
|
if (claimLost)
|
|
7923
8036
|
throw claimLost;
|
|
@@ -7977,6 +8090,11 @@ async function pullDeploymentOnce(options) {
|
|
|
7977
8090
|
definitionDigest: result.definitionDigest,
|
|
7978
8091
|
definitionVersion: result.definitionVersion,
|
|
7979
8092
|
profile: result.profile,
|
|
8093
|
+
...result.resolvedTargets ? { resolvedTargets: result.resolvedTargets.map((target) => ({
|
|
8094
|
+
...target,
|
|
8095
|
+
profiles: [...target.profiles],
|
|
8096
|
+
labels: { ...target.labels }
|
|
8097
|
+
})) } : {},
|
|
7980
8098
|
...result.capacity ? { capacity: {
|
|
7981
8099
|
recommendedConcurrency: result.capacity.recommendedConcurrency,
|
|
7982
8100
|
stopReason: result.capacity.stopReason,
|
|
@@ -8026,14 +8144,14 @@ function startDeploymentPull(options) {
|
|
|
8026
8144
|
|
|
8027
8145
|
// src/guest-enrolment.ts
|
|
8028
8146
|
import {
|
|
8029
|
-
chmodSync as
|
|
8030
|
-
existsSync as
|
|
8031
|
-
mkdirSync as
|
|
8032
|
-
readFileSync as
|
|
8033
|
-
renameSync as
|
|
8147
|
+
chmodSync as chmodSync6,
|
|
8148
|
+
existsSync as existsSync6,
|
|
8149
|
+
mkdirSync as mkdirSync4,
|
|
8150
|
+
readFileSync as readFileSync4,
|
|
8151
|
+
renameSync as renameSync4,
|
|
8034
8152
|
statSync,
|
|
8035
8153
|
unlinkSync as unlinkSync4,
|
|
8036
|
-
writeFileSync as
|
|
8154
|
+
writeFileSync as writeFileSync4
|
|
8037
8155
|
} from "fs";
|
|
8038
8156
|
import { dirname as dirname2 } from "path";
|
|
8039
8157
|
function privateNetworkAttachmentFromEnvironment(env = process.env) {
|
|
@@ -8063,7 +8181,7 @@ var validBinding = (value, expectedNodeKey) => {
|
|
|
8063
8181
|
return ["nodeKey", "computeReference", "projectKey", "environmentKey"].every((key) => typeof row2[key] === "string" && row2[key].length > 0) && (row2.realm === "platform" || row2.realm === "tenant" && typeof row2.tenantSlug === "string" && row2.tenantSlug.length > 0) && (!expectedNodeKey || row2.nodeKey === expectedNodeKey);
|
|
8064
8182
|
};
|
|
8065
8183
|
function loadGuestBinding(path2, expectedNodeKey) {
|
|
8066
|
-
if (!
|
|
8184
|
+
if (!existsSync6(path2))
|
|
8067
8185
|
return null;
|
|
8068
8186
|
const mode = statSync(path2).mode & 511;
|
|
8069
8187
|
if ((mode & 63) !== 0) {
|
|
@@ -8071,7 +8189,7 @@ function loadGuestBinding(path2, expectedNodeKey) {
|
|
|
8071
8189
|
}
|
|
8072
8190
|
let parsed;
|
|
8073
8191
|
try {
|
|
8074
|
-
parsed = JSON.parse(
|
|
8192
|
+
parsed = JSON.parse(readFileSync4(path2, "utf8"));
|
|
8075
8193
|
} catch {
|
|
8076
8194
|
throw new Error(`guest enrolment state at ${path2} is malformed`);
|
|
8077
8195
|
}
|
|
@@ -8081,15 +8199,15 @@ function loadGuestBinding(path2, expectedNodeKey) {
|
|
|
8081
8199
|
return parsed;
|
|
8082
8200
|
}
|
|
8083
8201
|
function persistGuestBinding(path2, binding) {
|
|
8084
|
-
|
|
8202
|
+
mkdirSync4(dirname2(path2), { recursive: true, mode: 448 });
|
|
8085
8203
|
const temporary = `${path2}.next`;
|
|
8086
|
-
|
|
8204
|
+
writeFileSync4(temporary, `${JSON.stringify(binding)}
|
|
8087
8205
|
`, { mode: 384 });
|
|
8088
|
-
|
|
8089
|
-
|
|
8206
|
+
chmodSync6(temporary, 384);
|
|
8207
|
+
renameSync4(temporary, path2);
|
|
8090
8208
|
}
|
|
8091
8209
|
async function enrolGuestIdentity(options) {
|
|
8092
|
-
const token = options.token?.trim() ?? (options.tokenPath ?
|
|
8210
|
+
const token = options.token?.trim() ?? (options.tokenPath ? readFileSync4(options.tokenPath, "utf8").trim() : "");
|
|
8093
8211
|
if (!token.startsWith("fze_"))
|
|
8094
8212
|
throw new Error("guest enrolment credential is malformed");
|
|
8095
8213
|
const payload = await postSignedNode({
|
|
@@ -8426,8 +8544,8 @@ function startMigrationPull(options) {
|
|
|
8426
8544
|
}
|
|
8427
8545
|
|
|
8428
8546
|
// src/ssh-bootstrap.ts
|
|
8429
|
-
import { createHash as
|
|
8430
|
-
import { chmodSync as
|
|
8547
|
+
import { createHash as createHash6 } from "crypto";
|
|
8548
|
+
import { chmodSync as chmodSync7, mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
8431
8549
|
import { tmpdir as tmpdir2 } from "os";
|
|
8432
8550
|
import { join as join3 } from "path";
|
|
8433
8551
|
import { fileURLToPath } from "url";
|
|
@@ -8448,7 +8566,7 @@ function assertSupportedGuestImage(imageKey) {
|
|
|
8448
8566
|
}
|
|
8449
8567
|
|
|
8450
8568
|
// src/version.ts
|
|
8451
|
-
var VERSION2 = "0.1.
|
|
8569
|
+
var VERSION2 = "0.1.64";
|
|
8452
8570
|
|
|
8453
8571
|
// src/ssh-bootstrap.ts
|
|
8454
8572
|
class SshBootstrapError extends Error {
|
|
@@ -8566,7 +8684,7 @@ async function pinnedKnownHost(claim, pinned, directory, exec) {
|
|
|
8566
8684
|
if (!/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}$/.test(key))
|
|
8567
8685
|
throw new SshBootstrapError("HOST_KEY_INVALID");
|
|
8568
8686
|
const path2 = join3(directory, "known_hosts");
|
|
8569
|
-
|
|
8687
|
+
writeFileSync5(path2, `${pinned.hostKeyAlias} ${key}
|
|
8570
8688
|
`, { mode: 384, flag: "wx" });
|
|
8571
8689
|
return path2;
|
|
8572
8690
|
}
|
|
@@ -8576,11 +8694,11 @@ var verifiedBunArchive = async (directory) => {
|
|
|
8576
8694
|
if (!response.ok)
|
|
8577
8695
|
throw new SshBootstrapError("BUN_DOWNLOAD_FAILED");
|
|
8578
8696
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
8579
|
-
if (
|
|
8697
|
+
if (createHash6("sha256").update(bytes).digest("hex") !== BUN_RELEASE_SHA256) {
|
|
8580
8698
|
throw new SshBootstrapError("BUN_CHECKSUM_MISMATCH");
|
|
8581
8699
|
}
|
|
8582
8700
|
const archive = join3(directory, "bun.zip");
|
|
8583
|
-
|
|
8701
|
+
writeFileSync5(archive, bytes, { mode: 384, flag: "wx" });
|
|
8584
8702
|
return archive;
|
|
8585
8703
|
};
|
|
8586
8704
|
var remote = (exec, ssh, destination, code, argv2, secret = false, stdin) => checked(exec, ["ssh", ...ssh, destination, "--", ...argv2], code, { secret, ...stdin !== undefined ? { stdin } : {} });
|
|
@@ -8596,14 +8714,14 @@ async function executeSshBootstrap(claim, options) {
|
|
|
8596
8714
|
throw new SshBootstrapError("TARGET_API_INVALID");
|
|
8597
8715
|
const exec = options.exec ?? defaultExec;
|
|
8598
8716
|
const directory = mkdtempSync2(join3(tmpdir2(), "forgezero-ssh-bootstrap-"));
|
|
8599
|
-
|
|
8717
|
+
chmodSync7(directory, 448);
|
|
8600
8718
|
let sshKeyPath = options.sshKeyPath;
|
|
8601
8719
|
let remoteDirectory = "";
|
|
8602
8720
|
let pinned;
|
|
8603
8721
|
try {
|
|
8604
8722
|
if (claim.target.credential.source === "vault") {
|
|
8605
8723
|
sshKeyPath = join3(directory, "identity");
|
|
8606
|
-
|
|
8724
|
+
writeFileSync5(sshKeyPath, `${claim.target.credential.privateKey.trim()}
|
|
8607
8725
|
`, { mode: 384, flag: "wx" });
|
|
8608
8726
|
}
|
|
8609
8727
|
const sourceHost = claim.target.host.includes(":") ? `[${claim.target.host}]` : claim.target.host;
|
|
@@ -8630,11 +8748,11 @@ async function executeSshBootstrap(claim, options) {
|
|
|
8630
8748
|
`
|
|
8631
8749
|
};
|
|
8632
8750
|
for (const [name, content] of Object.entries(files))
|
|
8633
|
-
|
|
8751
|
+
writeFileSync5(join3(directory, name), content, { mode: 384, flag: "wx" });
|
|
8634
8752
|
const fzCliPath = options.fzCliPath ?? fileURLToPath(new URL("./fz.js", import.meta.url));
|
|
8635
8753
|
const fzAgentPath = options.fzAgentPath ?? fileURLToPath(new URL("./fz-agent.js", import.meta.url));
|
|
8636
8754
|
for (const [source, name] of [[fzCliPath, "fz.js"], [fzAgentPath, "fz-agent.js"]]) {
|
|
8637
|
-
if (!
|
|
8755
|
+
if (!readFileSync5(source).length)
|
|
8638
8756
|
throw new SshBootstrapError("PACKAGE_ARTIFACT_MISSING");
|
|
8639
8757
|
await checked(exec, [
|
|
8640
8758
|
"scp",
|
|
@@ -8798,7 +8916,7 @@ async function executeSshBootstrap(claim, options) {
|
|
|
8798
8916
|
]);
|
|
8799
8917
|
} catch {}
|
|
8800
8918
|
}
|
|
8801
|
-
|
|
8919
|
+
rmSync3(directory, { recursive: true, force: true });
|
|
8802
8920
|
}
|
|
8803
8921
|
}
|
|
8804
8922
|
var post3 = (options, operation, body) => postSignedNode(options, `v1/node/bootstrap/${operation}`, body);
|
|
@@ -8908,14 +9026,14 @@ async function executeMetalAdmission(claim, options) {
|
|
|
8908
9026
|
}
|
|
8909
9027
|
const exec = options.exec ?? defaultExec;
|
|
8910
9028
|
const directory = mkdtempSync2(join3(tmpdir2(), "forgezero-metal-admission-"));
|
|
8911
|
-
|
|
9029
|
+
chmodSync7(directory, 448);
|
|
8912
9030
|
let sshKeyPath = options.sshKeyPath;
|
|
8913
9031
|
let remoteDirectory = "";
|
|
8914
9032
|
let pinned;
|
|
8915
9033
|
try {
|
|
8916
9034
|
if (claim.target.credential.source === "vault") {
|
|
8917
9035
|
sshKeyPath = join3(directory, "identity");
|
|
8918
|
-
|
|
9036
|
+
writeFileSync5(sshKeyPath, `${claim.target.credential.privateKey.trim()}
|
|
8919
9037
|
`, { mode: 384, flag: "wx" });
|
|
8920
9038
|
}
|
|
8921
9039
|
const sourceHost = claim.target.host.includes(":") ? `[${claim.target.host}]` : claim.target.host;
|
|
@@ -8932,7 +9050,7 @@ async function executeMetalAdmission(claim, options) {
|
|
|
8932
9050
|
const fzCliPath = options.fzCliPath ?? fileURLToPath(new URL("./fz.js", import.meta.url));
|
|
8933
9051
|
const fzAgentPath = options.fzAgentPath ?? fileURLToPath(new URL("./fz-agent.js", import.meta.url));
|
|
8934
9052
|
for (const [source, name] of [[fzCliPath, "fz.js"], [fzAgentPath, "fz-agent.js"]]) {
|
|
8935
|
-
if (!
|
|
9053
|
+
if (!readFileSync5(source).length)
|
|
8936
9054
|
throw new SshBootstrapError("PACKAGE_ARTIFACT_MISSING");
|
|
8937
9055
|
await checked(exec, [
|
|
8938
9056
|
"scp",
|
|
@@ -8963,7 +9081,7 @@ async function executeMetalAdmission(claim, options) {
|
|
|
8963
9081
|
}
|
|
8964
9082
|
};
|
|
8965
9083
|
const configPath = join3(directory, "metal.json");
|
|
8966
|
-
|
|
9084
|
+
writeFileSync5(configPath, `${JSON.stringify(config, null, 2)}
|
|
8967
9085
|
`, { mode: 384, flag: "wx" });
|
|
8968
9086
|
await checked(exec, [
|
|
8969
9087
|
"scp",
|
|
@@ -9065,7 +9183,7 @@ async function executeMetalAdmission(claim, options) {
|
|
|
9065
9183
|
]);
|
|
9066
9184
|
} catch {}
|
|
9067
9185
|
}
|
|
9068
|
-
|
|
9186
|
+
rmSync3(directory, { recursive: true, force: true });
|
|
9069
9187
|
}
|
|
9070
9188
|
}
|
|
9071
9189
|
async function pullMetalAdmissionOnce(options) {
|
|
@@ -9150,20 +9268,20 @@ function startMetalAdmissionPull(options) {
|
|
|
9150
9268
|
}
|
|
9151
9269
|
|
|
9152
9270
|
// src/metal-helper-socket.ts
|
|
9153
|
-
import { chmodSync as
|
|
9271
|
+
import { chmodSync as chmodSync8, existsSync as existsSync8, unlinkSync as unlinkSync6 } from "fs";
|
|
9154
9272
|
import { connect as connect2, createServer as createServer3 } from "net";
|
|
9155
9273
|
|
|
9156
9274
|
// src/metal-provision.ts
|
|
9157
|
-
import { createHash as
|
|
9275
|
+
import { createHash as createHash7 } from "crypto";
|
|
9158
9276
|
import {
|
|
9159
|
-
existsSync as
|
|
9160
|
-
mkdirSync as
|
|
9161
|
-
readFileSync as
|
|
9277
|
+
existsSync as existsSync7,
|
|
9278
|
+
mkdirSync as mkdirSync5,
|
|
9279
|
+
readFileSync as readFileSync6,
|
|
9162
9280
|
readdirSync,
|
|
9163
|
-
rmSync as
|
|
9281
|
+
rmSync as rmSync4,
|
|
9164
9282
|
statSync as statSync2,
|
|
9165
9283
|
unlinkSync as unlinkSync5,
|
|
9166
|
-
writeFileSync as
|
|
9284
|
+
writeFileSync as writeFileSync6
|
|
9167
9285
|
} from "fs";
|
|
9168
9286
|
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4 } from "path";
|
|
9169
9287
|
import { isIP as isIP2 } from "net";
|
|
@@ -9293,8 +9411,8 @@ function membersOfLinuxList(value, label) {
|
|
|
9293
9411
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
9294
9412
|
return members;
|
|
9295
9413
|
}
|
|
9296
|
-
var guestNameFor = (computeKey) => `fzg-${
|
|
9297
|
-
var tapNameFor = (computeKey) => `fzt${
|
|
9414
|
+
var guestNameFor = (computeKey) => `fzg-${createHash7("sha256").update(computeKey).digest("hex").slice(0, 16)}`;
|
|
9415
|
+
var tapNameFor = (computeKey) => `fzt${createHash7("sha256").update(computeKey).digest("hex").slice(0, 12)}`;
|
|
9298
9416
|
var macForAddress = (address) => {
|
|
9299
9417
|
const octets = address.split(".").map(Number);
|
|
9300
9418
|
if (octets.length !== 4 || octets.some((value) => !Number.isInteger(value) || value < 0 || value > 255)) {
|
|
@@ -9374,9 +9492,9 @@ function validateMetalProfile(profile) {
|
|
|
9374
9492
|
}
|
|
9375
9493
|
}
|
|
9376
9494
|
var readManifests = (stateDir) => {
|
|
9377
|
-
if (!
|
|
9495
|
+
if (!existsSync7(stateDir))
|
|
9378
9496
|
return [];
|
|
9379
|
-
return readdirSync(stateDir).filter((name) => name.endsWith(".json")).map((name) => JSON.parse(
|
|
9497
|
+
return readdirSync(stateDir).filter((name) => name.endsWith(".json")).map((name) => JSON.parse(readFileSync6(join4(stateDir, name), "utf8")));
|
|
9380
9498
|
};
|
|
9381
9499
|
function allocateAddress(profile, computeKey, rows) {
|
|
9382
9500
|
const existing = rows.find((row2) => row2.computeKey === computeKey);
|
|
@@ -9384,7 +9502,7 @@ function allocateAddress(profile, computeKey, rows) {
|
|
|
9384
9502
|
return existing.address;
|
|
9385
9503
|
const used = new Set(rows.map((row2) => row2.address));
|
|
9386
9504
|
const width = profile.addressEnd - profile.addressStart + 1;
|
|
9387
|
-
const start =
|
|
9505
|
+
const start = createHash7("sha256").update(computeKey).digest().readUInt16BE(0) % width;
|
|
9388
9506
|
for (let offset = 0;offset < width; offset += 1) {
|
|
9389
9507
|
const last = profile.addressStart + (start + offset) % width;
|
|
9390
9508
|
const address = `${profile.subnetPrefix}.${last}`;
|
|
@@ -9480,8 +9598,6 @@ function guestBootstrapOperations(profile, attested = Boolean(profile.confidenti
|
|
|
9480
9598
|
"FZ_AGENT_USER=forgezero-agent",
|
|
9481
9599
|
"FZ_SOCKET_PATH=/run/forgezero/vault.sock",
|
|
9482
9600
|
"FZ_SEED_CREDENTIAL_PATH=/etc/forgezero/creds/agent-seed.cred",
|
|
9483
|
-
"FZ_GIT_CREDENTIAL_PATH=/etc/forgezero/creds/git-deploy-key.cred",
|
|
9484
|
-
"FZ_GIT_PUBLIC_KEY_PATH=/etc/forgezero/git/deploy.pub",
|
|
9485
9601
|
"FZ_DEPLOY_ROOT=/opt/forgezero",
|
|
9486
9602
|
`FZ_DEPLOY_PULL=${hasEnrolment}`,
|
|
9487
9603
|
`FZ_AGENT_EGRESS_ENFORCE=${hasEnrolment}`,
|
|
@@ -9561,7 +9677,7 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
9561
9677
|
if (digest !== image.sha256)
|
|
9562
9678
|
throw new MetalProvisionError("configured image checksum mismatch");
|
|
9563
9679
|
for (const path2 of [profile.stateDir, profile.seedDir, profile.unitDir])
|
|
9564
|
-
|
|
9680
|
+
mkdirSync5(path2, { recursive: true, mode: 448 });
|
|
9565
9681
|
const manifests = readManifests(profile.stateDir);
|
|
9566
9682
|
if (claim.access) {
|
|
9567
9683
|
if (!/^[a-z_][a-z0-9_-]{0,31}$/.test(claim.access.sshUser)) {
|
|
@@ -9591,7 +9707,7 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
9591
9707
|
allowedMemoryNodes: cpuPool.memoryNodes,
|
|
9592
9708
|
phase: "allocating"
|
|
9593
9709
|
};
|
|
9594
|
-
const save = () =>
|
|
9710
|
+
const save = () => writeFileSync6(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
9595
9711
|
`, { mode: 384 });
|
|
9596
9712
|
save();
|
|
9597
9713
|
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
@@ -9605,9 +9721,9 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
9605
9721
|
}
|
|
9606
9722
|
const seedBase = join4(profile.seedDir, name);
|
|
9607
9723
|
const init = cloudInit(profile, claim, manifest);
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9724
|
+
writeFileSync6(`${seedBase}-user-data`, init.userData, { mode: 384 });
|
|
9725
|
+
writeFileSync6(`${seedBase}-meta-data`, init.metaData, { mode: 384 });
|
|
9726
|
+
writeFileSync6(`${seedBase}-network-config`, init.networkConfig, { mode: 384 });
|
|
9611
9727
|
const seed = `${seedBase}-seed.iso`;
|
|
9612
9728
|
await checked2(exec, [
|
|
9613
9729
|
"cloud-localds",
|
|
@@ -9640,8 +9756,8 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
9640
9756
|
}
|
|
9641
9757
|
const service = `forgezero-guest@${name}.service`;
|
|
9642
9758
|
const unitPath = join4(profile.unitDir, service);
|
|
9643
|
-
|
|
9644
|
-
|
|
9759
|
+
mkdirSync5(dirname3(unitPath), { recursive: true });
|
|
9760
|
+
writeFileSync6(unitPath, guestUnit(spec), { mode: 420 });
|
|
9645
9761
|
await checked2(exec, ["systemctl", "daemon-reload"]);
|
|
9646
9762
|
if (prior?.phase === "running") {
|
|
9647
9763
|
await checked2(exec, ["systemctl", "restart", service]);
|
|
@@ -9658,10 +9774,10 @@ function legacyPlatformManifest(profile, claim, name) {
|
|
|
9658
9774
|
return;
|
|
9659
9775
|
const legacyDir = profile.legacyStateDir ?? "/etc/forgezero/guests";
|
|
9660
9776
|
const legacyPath = join4(legacyDir, `${name}.conf`);
|
|
9661
|
-
if (!
|
|
9777
|
+
if (!existsSync7(legacyPath))
|
|
9662
9778
|
return;
|
|
9663
9779
|
const values = new Map;
|
|
9664
|
-
for (const line of
|
|
9780
|
+
for (const line of readFileSync6(legacyPath, "utf8").split(/\r?\n/)) {
|
|
9665
9781
|
if (!line || line.startsWith("#"))
|
|
9666
9782
|
continue;
|
|
9667
9783
|
const match = /^([A-Z][A-Z0-9_]*)=([^\s'"`$;|&<>]+)$/.exec(line);
|
|
@@ -9695,7 +9811,7 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
9695
9811
|
const service = `forgezero-guest@${name}.service`;
|
|
9696
9812
|
const unitPath = join4(profile.unitDir, service);
|
|
9697
9813
|
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
9698
|
-
const manifest =
|
|
9814
|
+
const manifest = existsSync7(manifestPath) ? JSON.parse(readFileSync6(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
|
|
9699
9815
|
if (!manifest) {
|
|
9700
9816
|
const seedBase = join4(profile.seedDir, name);
|
|
9701
9817
|
const localArtifacts = [
|
|
@@ -9705,7 +9821,7 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
9705
9821
|
`${seedBase}-meta-data`,
|
|
9706
9822
|
`${seedBase}-network-config`
|
|
9707
9823
|
];
|
|
9708
|
-
if (localArtifacts.some(
|
|
9824
|
+
if (localArtifacts.some(existsSync7)) {
|
|
9709
9825
|
throw new MetalProvisionError("guest artifacts exist without owned inventory");
|
|
9710
9826
|
}
|
|
9711
9827
|
const [volume, active] = await Promise.all([
|
|
@@ -9722,7 +9838,7 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
9722
9838
|
if (!currentIdentity && !legacyPlatformIdentity) {
|
|
9723
9839
|
throw new MetalProvisionError("compute identity conflicts with host inventory");
|
|
9724
9840
|
}
|
|
9725
|
-
if (
|
|
9841
|
+
if (existsSync7(unitPath))
|
|
9726
9842
|
await checked2(exec, ["systemctl", "disable", "--now", service]);
|
|
9727
9843
|
else if (legacyPlatformIdentity)
|
|
9728
9844
|
await checked2(exec, ["systemctl", "disable", "--now", service]);
|
|
@@ -9739,15 +9855,15 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
9739
9855
|
join4(profile.seedDir, `${name}-network-config`),
|
|
9740
9856
|
manifestPath
|
|
9741
9857
|
])
|
|
9742
|
-
if (
|
|
9858
|
+
if (existsSync7(path2))
|
|
9743
9859
|
unlinkSync5(path2);
|
|
9744
9860
|
if (legacyPlatformIdentity) {
|
|
9745
9861
|
const legacyConfig = join4(profile.legacyStateDir ?? "/etc/forgezero/guests", `${name}.conf`);
|
|
9746
9862
|
const legacyDropIn = join4(profile.unitDir, `${service}.d`);
|
|
9747
|
-
if (
|
|
9863
|
+
if (existsSync7(legacyConfig))
|
|
9748
9864
|
unlinkSync5(legacyConfig);
|
|
9749
|
-
if (
|
|
9750
|
-
|
|
9865
|
+
if (existsSync7(legacyDropIn))
|
|
9866
|
+
rmSync4(legacyDropIn, { recursive: true });
|
|
9751
9867
|
}
|
|
9752
9868
|
await checked2(exec, ["systemctl", "daemon-reload"]);
|
|
9753
9869
|
return {};
|
|
@@ -9785,7 +9901,7 @@ command exceeded ${COMMAND_TIMEOUT_MS}ms`.trim() : stderr
|
|
|
9785
9901
|
function startMetalHelper(options) {
|
|
9786
9902
|
validateMetalProfile(options.profile);
|
|
9787
9903
|
const socketPath = options.socketPath ?? DEFAULT_METAL_HELPER_SOCKET;
|
|
9788
|
-
if (
|
|
9904
|
+
if (existsSync8(socketPath))
|
|
9789
9905
|
unlinkSync6(socketPath);
|
|
9790
9906
|
let tail = Promise.resolve();
|
|
9791
9907
|
const server = createServer3((socket) => {
|
|
@@ -9832,7 +9948,7 @@ function startMetalHelper(options) {
|
|
|
9832
9948
|
});
|
|
9833
9949
|
socket.on("error", () => socket.destroy());
|
|
9834
9950
|
});
|
|
9835
|
-
server.listen(socketPath, () =>
|
|
9951
|
+
server.listen(socketPath, () => chmodSync8(socketPath, 432));
|
|
9836
9952
|
return {
|
|
9837
9953
|
server,
|
|
9838
9954
|
async stop() {
|
|
@@ -9872,7 +9988,7 @@ function requestMetalProvision(claim, socketPath = DEFAULT_METAL_HELPER_SOCKET)
|
|
|
9872
9988
|
}
|
|
9873
9989
|
|
|
9874
9990
|
// src/deployment-runner.ts
|
|
9875
|
-
import { chmodSync as
|
|
9991
|
+
import { chmodSync as chmodSync9, existsSync as existsSync9, realpathSync, unlinkSync as unlinkSync7 } from "fs";
|
|
9876
9992
|
import { isAbsolute as isAbsolute4, resolve, sep } from "path";
|
|
9877
9993
|
import { connect as connect3, createServer as createServer4 } from "net";
|
|
9878
9994
|
var DEFAULT_DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock";
|
|
@@ -9989,7 +10105,7 @@ function startDeploymentRunner(options) {
|
|
|
9989
10105
|
const root = resolve(options.root);
|
|
9990
10106
|
const home = resolve(options.home);
|
|
9991
10107
|
const socketPath = options.socketPath ?? DEFAULT_DEPLOYMENT_RUNNER_SOCKET;
|
|
9992
|
-
if (
|
|
10108
|
+
if (existsSync9(socketPath))
|
|
9993
10109
|
unlinkSync7(socketPath);
|
|
9994
10110
|
const active = new Set;
|
|
9995
10111
|
const server = createServer4((socket) => {
|
|
@@ -10034,7 +10150,7 @@ function startDeploymentRunner(options) {
|
|
|
10034
10150
|
const ready = new Promise((resolveReady, rejectReady) => {
|
|
10035
10151
|
server.once("error", rejectReady);
|
|
10036
10152
|
server.listen(socketPath, () => {
|
|
10037
|
-
|
|
10153
|
+
chmodSync9(socketPath, options.socketMode ?? 432);
|
|
10038
10154
|
server.off("error", rejectReady);
|
|
10039
10155
|
resolveReady();
|
|
10040
10156
|
});
|
|
@@ -10085,7 +10201,7 @@ function requestDeploymentCommand(input, socketPath = DEFAULT_DEPLOYMENT_RUNNER_
|
|
|
10085
10201
|
}
|
|
10086
10202
|
|
|
10087
10203
|
// src/snp-attestation.ts
|
|
10088
|
-
import { existsSync as
|
|
10204
|
+
import { existsSync as existsSync10 } from "fs";
|
|
10089
10205
|
|
|
10090
10206
|
// ../runtime/dist/snp.js
|
|
10091
10207
|
var REPORT_BYTES = 1184;
|
|
@@ -10178,7 +10294,7 @@ function createSnpAttestationSource(options = {}) {
|
|
|
10178
10294
|
const python = options.python ?? "python3";
|
|
10179
10295
|
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
10180
10296
|
const spawn = options.spawn ?? Bun.spawn;
|
|
10181
|
-
const exists = options.exists ??
|
|
10297
|
+
const exists = options.exists ?? existsSync10;
|
|
10182
10298
|
return {
|
|
10183
10299
|
name: "linux-sev-guest",
|
|
10184
10300
|
async report(nonce) {
|
|
@@ -10264,7 +10380,7 @@ function startNodeAttestation(options) {
|
|
|
10264
10380
|
}
|
|
10265
10381
|
|
|
10266
10382
|
// src/metal-isolation.ts
|
|
10267
|
-
import { mkdirSync as
|
|
10383
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
10268
10384
|
import { join as join5 } from "path";
|
|
10269
10385
|
var members = (list) => list.split(",").flatMap((part) => {
|
|
10270
10386
|
const [first, last = first] = part.split("-").map(Number);
|
|
@@ -10342,16 +10458,16 @@ async function applyMetalIsolation(profile, exec = defaultExec2) {
|
|
|
10342
10458
|
validateMetalProfile(profile);
|
|
10343
10459
|
await requireGuestsInSlice(exec);
|
|
10344
10460
|
const unitDir = profile.unitDir;
|
|
10345
|
-
|
|
10346
|
-
|
|
10461
|
+
mkdirSync6(unitDir, { recursive: true });
|
|
10462
|
+
writeFileSync7(join5(unitDir, "forgezero-guests.slice"), metalGuestSliceUnit(profile), { mode: 420 });
|
|
10347
10463
|
for (const unit of ["system.slice", "user.slice"]) {
|
|
10348
10464
|
const directory = join5(unitDir, `${unit}.d`);
|
|
10349
|
-
|
|
10350
|
-
|
|
10465
|
+
mkdirSync6(directory, { recursive: true });
|
|
10466
|
+
writeFileSync7(join5(directory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "slice"), { mode: 420 });
|
|
10351
10467
|
}
|
|
10352
10468
|
const initDirectory = join5(unitDir, "init.scope.d");
|
|
10353
|
-
|
|
10354
|
-
|
|
10469
|
+
mkdirSync6(initDirectory, { recursive: true });
|
|
10470
|
+
writeFileSync7(join5(initDirectory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "scope"), { mode: 420 });
|
|
10355
10471
|
await checked3(exec, ["systemctl", "daemon-reload"]);
|
|
10356
10472
|
await requireGuestsInSlice(exec);
|
|
10357
10473
|
const properties = [`AllowedCPUs=${profile.housekeepingCpus}`];
|
|
@@ -10386,7 +10502,7 @@ async function closeServerWithin(server, timeoutMs) {
|
|
|
10386
10502
|
}
|
|
10387
10503
|
|
|
10388
10504
|
// src/lifecycle-helper.ts
|
|
10389
|
-
import { chmodSync as
|
|
10505
|
+
import { chmodSync as chmodSync10, existsSync as existsSync11, readFileSync as readFileSync7, unlinkSync as unlinkSync8 } from "fs";
|
|
10390
10506
|
import { connect as connect4, createConnection, createServer as createServer5, isIP as isIP3 } from "net";
|
|
10391
10507
|
var DEFAULT_LIFECYCLE_HELPER_SOCKET = "/run/forgezero-lifecycle/helper.sock";
|
|
10392
10508
|
var MAX_REQUEST_BYTES4 = 16 * 1024;
|
|
@@ -10430,7 +10546,7 @@ function validateLifecycleProfile(profile) {
|
|
|
10430
10546
|
}
|
|
10431
10547
|
}
|
|
10432
10548
|
function loadLifecycleProfile(path2) {
|
|
10433
|
-
const profile = JSON.parse(
|
|
10549
|
+
const profile = JSON.parse(readFileSync7(path2, "utf8"));
|
|
10434
10550
|
validateLifecycleProfile(profile);
|
|
10435
10551
|
return profile;
|
|
10436
10552
|
}
|
|
@@ -10527,7 +10643,7 @@ async function executeLifecycleAction(profile, claim, exec = spawnLifecycleComma
|
|
|
10527
10643
|
function startLifecycleHelper(options) {
|
|
10528
10644
|
validateLifecycleProfile(options.profile);
|
|
10529
10645
|
const socketPath = options.socketPath ?? DEFAULT_LIFECYCLE_HELPER_SOCKET;
|
|
10530
|
-
if (
|
|
10646
|
+
if (existsSync11(socketPath))
|
|
10531
10647
|
unlinkSync8(socketPath);
|
|
10532
10648
|
let tail = Promise.resolve();
|
|
10533
10649
|
const server = createServer5((socket) => {
|
|
@@ -10568,7 +10684,7 @@ function startLifecycleHelper(options) {
|
|
|
10568
10684
|
});
|
|
10569
10685
|
socket.on("error", () => socket.destroy());
|
|
10570
10686
|
});
|
|
10571
|
-
server.listen(socketPath, () =>
|
|
10687
|
+
server.listen(socketPath, () => chmodSync10(socketPath, 432));
|
|
10572
10688
|
return {
|
|
10573
10689
|
server,
|
|
10574
10690
|
async stop() {
|
|
@@ -10608,7 +10724,7 @@ function requestLifecycleAction(claim, socketPath = DEFAULT_LIFECYCLE_HELPER_SOC
|
|
|
10608
10724
|
}
|
|
10609
10725
|
|
|
10610
10726
|
// src/warp-config.ts
|
|
10611
|
-
import { chmodSync as
|
|
10727
|
+
import { chmodSync as chmodSync11, mkdirSync as mkdirSync7, renameSync as renameSync5, symlinkSync as symlinkSync2, unlinkSync as unlinkSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
10612
10728
|
var xml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10613
10729
|
function renderWarpMdm(options) {
|
|
10614
10730
|
if (!/^[a-z0-9][a-z0-9-]{0,62}$/i.test(options.organization))
|
|
@@ -10629,12 +10745,12 @@ function renderWarpMdm(options) {
|
|
|
10629
10745
|
function materializeWarpMdm(options) {
|
|
10630
10746
|
const runtimePath = options.runtimePath ?? "/run/forgezero-warp/mdm.xml";
|
|
10631
10747
|
const servicePath = options.servicePath ?? "/var/lib/cloudflare-warp/mdm.xml";
|
|
10632
|
-
|
|
10633
|
-
|
|
10748
|
+
mkdirSync7(runtimePath.replace(/\/[^/]+$/, ""), { recursive: true, mode: 448 });
|
|
10749
|
+
mkdirSync7(servicePath.replace(/\/[^/]+$/, ""), { recursive: true, mode: 448 });
|
|
10634
10750
|
const next = `${runtimePath}.next`;
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10751
|
+
writeFileSync8(next, renderWarpMdm(options), { mode: 384 });
|
|
10752
|
+
chmodSync11(next, 384);
|
|
10753
|
+
renameSync5(next, runtimePath);
|
|
10638
10754
|
try {
|
|
10639
10755
|
unlinkSync9(servicePath);
|
|
10640
10756
|
} catch (cause) {
|
|
@@ -10646,7 +10762,7 @@ function materializeWarpMdm(options) {
|
|
|
10646
10762
|
}
|
|
10647
10763
|
|
|
10648
10764
|
// src/mesh-connector.ts
|
|
10649
|
-
import { constants, closeSync, fstatSync, openSync, readFileSync as
|
|
10765
|
+
import { constants, closeSync, fstatSync, openSync, readFileSync as readFileSync8 } from "fs";
|
|
10650
10766
|
import { join as join6 } from "path";
|
|
10651
10767
|
var TOKEN = /^[A-Za-z0-9._-]{40,16384}$/;
|
|
10652
10768
|
function readMeshConnectorCredential(name = "CF_WARP_CONNECTOR_TOKEN") {
|
|
@@ -10663,7 +10779,7 @@ function readMeshConnectorCredential(name = "CF_WARP_CONNECTOR_TOKEN") {
|
|
|
10663
10779
|
if (!metadata.isFile() || metadata.nlink !== 1 || metadata.size < 40 || metadata.size > 16384) {
|
|
10664
10780
|
throw new Error("Mesh connector systemd credential is malformed");
|
|
10665
10781
|
}
|
|
10666
|
-
const token =
|
|
10782
|
+
const token = readFileSync8(descriptor, "utf8").trim();
|
|
10667
10783
|
if (!TOKEN.test(token))
|
|
10668
10784
|
throw new Error("Mesh connector systemd credential is malformed");
|
|
10669
10785
|
return token;
|
|
@@ -10689,20 +10805,20 @@ async function configureMeshConnector(input) {
|
|
|
10689
10805
|
}
|
|
10690
10806
|
|
|
10691
10807
|
// src/agent-update.ts
|
|
10692
|
-
import { createHash as
|
|
10808
|
+
import { createHash as createHash8, timingSafeEqual, randomUUID as randomUUID2 } from "crypto";
|
|
10693
10809
|
import {
|
|
10694
|
-
chmodSync as
|
|
10810
|
+
chmodSync as chmodSync12,
|
|
10695
10811
|
closeSync as closeSync2,
|
|
10696
|
-
existsSync as
|
|
10812
|
+
existsSync as existsSync12,
|
|
10697
10813
|
fsyncSync,
|
|
10698
|
-
mkdirSync as
|
|
10814
|
+
mkdirSync as mkdirSync8,
|
|
10699
10815
|
openSync as openSync2,
|
|
10700
|
-
readFileSync as
|
|
10816
|
+
readFileSync as readFileSync9,
|
|
10701
10817
|
readlinkSync,
|
|
10702
|
-
renameSync as
|
|
10703
|
-
rmSync as
|
|
10818
|
+
renameSync as renameSync6,
|
|
10819
|
+
rmSync as rmSync5,
|
|
10704
10820
|
symlinkSync as symlinkSync3,
|
|
10705
|
-
writeFileSync as
|
|
10821
|
+
writeFileSync as writeFileSync9
|
|
10706
10822
|
} from "fs";
|
|
10707
10823
|
import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
|
|
10708
10824
|
var DEFAULT_AGENT_RELEASE_ROOT = "/opt/forgezero/agent";
|
|
@@ -10784,9 +10900,9 @@ var checked4 = async (run2, input, label) => {
|
|
|
10784
10900
|
return result;
|
|
10785
10901
|
};
|
|
10786
10902
|
async function validateReleaseDirectory(directory, release, run2) {
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
const manifest = JSON.parse(
|
|
10903
|
+
chmodSync12(directory, 493);
|
|
10904
|
+
chmodSync12(join7(directory, "dist"), 493);
|
|
10905
|
+
const manifest = JSON.parse(readFileSync9(join7(directory, "package.json"), "utf8"));
|
|
10790
10906
|
if (manifest.name !== release.package || manifest.version !== release.version) {
|
|
10791
10907
|
throw new Error("agent update manifest does not match the selected release");
|
|
10792
10908
|
}
|
|
@@ -10794,11 +10910,11 @@ async function validateReleaseDirectory(directory, release, run2) {
|
|
|
10794
10910
|
const cli = join7(directory, "dist", "fz.js");
|
|
10795
10911
|
const gitSsh = join7(directory, "dist", "fz-git-ssh.js");
|
|
10796
10912
|
for (const binary of [agent, cli, gitSsh]) {
|
|
10797
|
-
if (!
|
|
10913
|
+
if (!readFileSync9(binary, "utf8").startsWith(`#!/usr/bin/env bun
|
|
10798
10914
|
`)) {
|
|
10799
10915
|
throw new Error("agent update artifact is not a self-contained Bun executable");
|
|
10800
10916
|
}
|
|
10801
|
-
|
|
10917
|
+
chmodSync12(binary, 493);
|
|
10802
10918
|
}
|
|
10803
10919
|
const version = (await checked4(run2, { command: agent, args: ["--version"] }, "agent update smoke test")).output.trim();
|
|
10804
10920
|
if (version !== release.version)
|
|
@@ -10817,7 +10933,7 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
10817
10933
|
const archive = join7(stage2, "agent.tgz");
|
|
10818
10934
|
const unpacked = join7(stage2, "unpacked");
|
|
10819
10935
|
const run2 = options.run ?? command;
|
|
10820
|
-
|
|
10936
|
+
mkdirSync8(unpacked, { recursive: true, mode: 448 });
|
|
10821
10937
|
try {
|
|
10822
10938
|
const response = await (options.fetch ?? globalThis.fetch)(release.tarball, {
|
|
10823
10939
|
redirect: "error",
|
|
@@ -10833,10 +10949,10 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
10833
10949
|
throw new Error("agent update tarball is empty or exceeds the size limit");
|
|
10834
10950
|
}
|
|
10835
10951
|
const expected = Buffer.from(release.integrity.slice("sha512-".length), "base64");
|
|
10836
|
-
const actual =
|
|
10952
|
+
const actual = createHash8("sha512").update(bytes).digest();
|
|
10837
10953
|
if (!timingSafeEqual(actual, expected))
|
|
10838
10954
|
throw new Error("agent update integrity mismatch");
|
|
10839
|
-
|
|
10955
|
+
writeFileSync9(archive, bytes, { mode: 384, flag: "wx" });
|
|
10840
10956
|
for (const [member, relative] of [
|
|
10841
10957
|
["package/package.json", "package.json"],
|
|
10842
10958
|
["package/dist/fz-agent.js", "dist/fz-agent.js"],
|
|
@@ -10848,23 +10964,23 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
10848
10964
|
args: ["-xOzf", archive, member]
|
|
10849
10965
|
}, `agent update extraction of ${member}`);
|
|
10850
10966
|
const destination = join7(unpacked, relative);
|
|
10851
|
-
|
|
10852
|
-
|
|
10967
|
+
mkdirSync8(dirname4(destination), { recursive: true, mode: 448 });
|
|
10968
|
+
writeFileSync9(destination, extracted.output, { mode: 384, flag: "wx" });
|
|
10853
10969
|
}
|
|
10854
10970
|
await validateReleaseDirectory(unpacked, release, run2);
|
|
10855
|
-
if (!
|
|
10856
|
-
|
|
10971
|
+
if (!existsSync12(finalDirectory)) {
|
|
10972
|
+
renameSync6(unpacked, finalDirectory);
|
|
10857
10973
|
syncReleaseDirectory(finalDirectory);
|
|
10858
10974
|
} else
|
|
10859
10975
|
await validateReleaseDirectory(finalDirectory, release, run2);
|
|
10860
|
-
if (!
|
|
10976
|
+
if (!existsSync12(currentLink)) {
|
|
10861
10977
|
throw new Error("agent update requires an active immutable release to roll back to");
|
|
10862
10978
|
}
|
|
10863
10979
|
const previousTarget = readlinkSync(currentLink);
|
|
10864
10980
|
if (previousTarget !== join7("versions", options.currentVersion)) {
|
|
10865
10981
|
throw new Error("agent update current release does not match the running version");
|
|
10866
10982
|
}
|
|
10867
|
-
if (!
|
|
10983
|
+
if (!existsSync12(join7(root, previousTarget))) {
|
|
10868
10984
|
throw new Error("agent update rollback release is missing");
|
|
10869
10985
|
}
|
|
10870
10986
|
return {
|
|
@@ -10876,44 +10992,44 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
10876
10992
|
currentLink
|
|
10877
10993
|
};
|
|
10878
10994
|
} finally {
|
|
10879
|
-
|
|
10995
|
+
rmSync5(stage2, { recursive: true, force: true });
|
|
10880
10996
|
}
|
|
10881
10997
|
}
|
|
10882
10998
|
function selectAgentRelease(staged) {
|
|
10883
10999
|
const next = join7(dirname4(staged.currentLink), `.current.${randomUUID2()}.next`);
|
|
10884
11000
|
try {
|
|
10885
11001
|
symlinkSync3(staged.nextTarget, next);
|
|
10886
|
-
|
|
11002
|
+
renameSync6(next, staged.currentLink);
|
|
10887
11003
|
syncPath(dirname4(staged.currentLink));
|
|
10888
11004
|
} finally {
|
|
10889
|
-
|
|
11005
|
+
rmSync5(next, { force: true });
|
|
10890
11006
|
}
|
|
10891
11007
|
}
|
|
10892
11008
|
function restoreAgentRelease(staged) {
|
|
10893
11009
|
const next = join7(dirname4(staged.currentLink), `.current.${randomUUID2()}.rollback`);
|
|
10894
11010
|
try {
|
|
10895
11011
|
symlinkSync3(staged.previousTarget, next);
|
|
10896
|
-
|
|
11012
|
+
renameSync6(next, staged.currentLink);
|
|
10897
11013
|
syncPath(dirname4(staged.currentLink));
|
|
10898
11014
|
} finally {
|
|
10899
|
-
|
|
11015
|
+
rmSync5(next, { force: true });
|
|
10900
11016
|
}
|
|
10901
11017
|
}
|
|
10902
11018
|
|
|
10903
11019
|
// src/agent-update-helper.ts
|
|
10904
11020
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
10905
11021
|
import {
|
|
10906
|
-
chmodSync as
|
|
11022
|
+
chmodSync as chmodSync13,
|
|
10907
11023
|
closeSync as closeSync3,
|
|
10908
|
-
existsSync as
|
|
11024
|
+
existsSync as existsSync13,
|
|
10909
11025
|
fsyncSync as fsyncSync2,
|
|
10910
|
-
mkdirSync as
|
|
11026
|
+
mkdirSync as mkdirSync9,
|
|
10911
11027
|
openSync as openSync3,
|
|
10912
|
-
readFileSync as
|
|
10913
|
-
renameSync as
|
|
10914
|
-
rmSync as
|
|
11028
|
+
readFileSync as readFileSync10,
|
|
11029
|
+
renameSync as renameSync7,
|
|
11030
|
+
rmSync as rmSync6,
|
|
10915
11031
|
unlinkSync as unlinkSync10,
|
|
10916
|
-
writeFileSync as
|
|
11032
|
+
writeFileSync as writeFileSync10
|
|
10917
11033
|
} from "fs";
|
|
10918
11034
|
import { connect as connect5, createServer as createServer6 } from "net";
|
|
10919
11035
|
import { dirname as dirname5, join as join8, resolve as resolve3 } from "path";
|
|
@@ -11011,22 +11127,22 @@ function validateJournal(value, root) {
|
|
|
11011
11127
|
return journal;
|
|
11012
11128
|
}
|
|
11013
11129
|
function readJournal(path2, root) {
|
|
11014
|
-
if (!
|
|
11130
|
+
if (!existsSync13(path2))
|
|
11015
11131
|
return;
|
|
11016
|
-
return validateJournal(JSON.parse(
|
|
11132
|
+
return validateJournal(JSON.parse(readFileSync10(path2, "utf8")), root);
|
|
11017
11133
|
}
|
|
11018
11134
|
function writeAtomic(path2, value, mode) {
|
|
11019
|
-
|
|
11135
|
+
mkdirSync9(dirname5(path2), { recursive: true, mode: 493 });
|
|
11020
11136
|
const next = `${path2}.${randomUUID3()}.next`;
|
|
11021
11137
|
let file;
|
|
11022
11138
|
try {
|
|
11023
11139
|
file = openSync3(next, "wx", mode);
|
|
11024
|
-
|
|
11140
|
+
writeFileSync10(file, `${JSON.stringify(value)}
|
|
11025
11141
|
`);
|
|
11026
11142
|
fsyncSync2(file);
|
|
11027
11143
|
closeSync3(file);
|
|
11028
11144
|
file = undefined;
|
|
11029
|
-
|
|
11145
|
+
renameSync7(next, path2);
|
|
11030
11146
|
const directory = openSync3(dirname5(path2), "r");
|
|
11031
11147
|
try {
|
|
11032
11148
|
fsyncSync2(directory);
|
|
@@ -11036,7 +11152,7 @@ function writeAtomic(path2, value, mode) {
|
|
|
11036
11152
|
} finally {
|
|
11037
11153
|
if (file !== undefined)
|
|
11038
11154
|
closeSync3(file);
|
|
11039
|
-
|
|
11155
|
+
rmSync6(next, { force: true });
|
|
11040
11156
|
}
|
|
11041
11157
|
}
|
|
11042
11158
|
var publicReceipt = (journal) => {
|
|
@@ -11069,9 +11185,9 @@ function writeUpdateState(journalPath, receiptPath, journal) {
|
|
|
11069
11185
|
}
|
|
11070
11186
|
function readAgentUpdateReceipt(path2 = AGENT_UPDATE_RECEIPT) {
|
|
11071
11187
|
try {
|
|
11072
|
-
if (!
|
|
11188
|
+
if (!existsSync13(path2))
|
|
11073
11189
|
return;
|
|
11074
|
-
return validateReceipt(JSON.parse(
|
|
11190
|
+
return validateReceipt(JSON.parse(readFileSync10(path2, "utf8")));
|
|
11075
11191
|
} catch {
|
|
11076
11192
|
return;
|
|
11077
11193
|
}
|
|
@@ -11225,7 +11341,7 @@ async function recoverInterruptedAgentUpdate(options = {}) {
|
|
|
11225
11341
|
return publicReceipt(journal);
|
|
11226
11342
|
}
|
|
11227
11343
|
const staged = stagedFromJournal(journal, root);
|
|
11228
|
-
if (!
|
|
11344
|
+
if (!existsSync13(join8(root, journal.previousTarget))) {
|
|
11229
11345
|
throw new Error("Agent update rollback release is missing");
|
|
11230
11346
|
}
|
|
11231
11347
|
const run2 = options.run ?? runCommand;
|
|
@@ -11255,9 +11371,9 @@ async function recoverInterruptedAgentUpdate(options = {}) {
|
|
|
11255
11371
|
}
|
|
11256
11372
|
function startAgentUpdateHelper(options = {}) {
|
|
11257
11373
|
const socketPath = options.socketPath ?? DEFAULT_AGENT_UPDATE_SOCKET;
|
|
11258
|
-
if (
|
|
11374
|
+
if (existsSync13(socketPath))
|
|
11259
11375
|
unlinkSync10(socketPath);
|
|
11260
|
-
|
|
11376
|
+
mkdirSync9(dirname5(socketPath), { recursive: true, mode: 488 });
|
|
11261
11377
|
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
11262
11378
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
11263
11379
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
@@ -11336,7 +11452,7 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
11336
11452
|
});
|
|
11337
11453
|
socket.on("error", () => socket.destroy());
|
|
11338
11454
|
});
|
|
11339
|
-
server.listen(socketPath, () =>
|
|
11455
|
+
server.listen(socketPath, () => chmodSync13(socketPath, 432));
|
|
11340
11456
|
return server;
|
|
11341
11457
|
}
|
|
11342
11458
|
function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, timeoutMs = 90000) {
|
|
@@ -11366,13 +11482,13 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
|
|
|
11366
11482
|
}
|
|
11367
11483
|
|
|
11368
11484
|
// src/agent-heartbeat.ts
|
|
11369
|
-
import { readFileSync as
|
|
11485
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
11370
11486
|
var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
|
|
11371
11487
|
var remoteOutcome4 = (cause) => cause instanceof SignedNodeHttpError && cause.status < 500 ? "refused" : cause instanceof SignedNodeHttpError ? "retryable" : "failed";
|
|
11372
11488
|
|
|
11373
11489
|
class AgentUpdateRefusedError extends Error {
|
|
11374
11490
|
}
|
|
11375
|
-
function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease =
|
|
11491
|
+
function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = readFileSync11("/etc/os-release", "utf8"), architecture = process.arch) {
|
|
11376
11492
|
const values = Object.fromEntries(osRelease.split(`
|
|
11377
11493
|
`).flatMap((line) => {
|
|
11378
11494
|
const separator = line.indexOf("=");
|
|
@@ -11490,34 +11606,34 @@ function startAgentHeartbeat(options) {
|
|
|
11490
11606
|
}
|
|
11491
11607
|
|
|
11492
11608
|
// src/software-helper.ts
|
|
11493
|
-
import { chmodSync as
|
|
11609
|
+
import { chmodSync as chmodSync14, existsSync as existsSync16, mkdirSync as mkdirSync12, unlinkSync as unlinkSync11 } from "fs";
|
|
11494
11610
|
import { connect as connect6, createServer as createServer7 } from "net";
|
|
11495
11611
|
import { dirname as dirname8 } from "path";
|
|
11496
11612
|
|
|
11497
11613
|
// src/service-supervisor.ts
|
|
11498
|
-
import { createHash as
|
|
11499
|
-
import { existsSync as
|
|
11614
|
+
import { createHash as createHash9 } from "crypto";
|
|
11615
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync8, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "fs";
|
|
11500
11616
|
import { dirname as dirname6, join as join9, resolve as resolve4, sep as sep2 } from "path";
|
|
11501
11617
|
var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
|
|
11502
11618
|
var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
|
|
11503
11619
|
var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
|
|
11504
11620
|
var SERVICE_NGINX_DIRECTORY = "/etc/nginx/conf.d";
|
|
11505
|
-
var idFor = (key) =>
|
|
11621
|
+
var idFor = (key) => createHash9("sha256").update(key).digest("hex").slice(0, 24);
|
|
11506
11622
|
var statePath = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
|
|
11507
11623
|
var within2 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep2}`);
|
|
11508
11624
|
var defaultHost = {
|
|
11509
11625
|
write(path2, content, mode) {
|
|
11510
|
-
|
|
11626
|
+
mkdirSync10(dirname6(path2), { recursive: true, mode: 493 });
|
|
11511
11627
|
const next = `${path2}.next`;
|
|
11512
|
-
|
|
11513
|
-
|
|
11628
|
+
writeFileSync11(next, content, { mode });
|
|
11629
|
+
renameSync8(next, path2);
|
|
11514
11630
|
},
|
|
11515
|
-
read: (path2) =>
|
|
11516
|
-
exists:
|
|
11517
|
-
list: (path2) =>
|
|
11631
|
+
read: (path2) => readFileSync12(path2, "utf8"),
|
|
11632
|
+
exists: existsSync14,
|
|
11633
|
+
list: (path2) => existsSync14(path2) ? readdirSync2(path2) : [],
|
|
11518
11634
|
realpath: realpathSync2,
|
|
11519
|
-
mkdir: (path2, mode) =>
|
|
11520
|
-
remove: (path2) =>
|
|
11635
|
+
mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
|
|
11636
|
+
remove: (path2) => rmSync7(path2, { force: true }),
|
|
11521
11637
|
async exec(argv2) {
|
|
11522
11638
|
const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: {
|
|
11523
11639
|
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
@@ -11575,7 +11691,7 @@ function allocatedPorts(request, id2, previous, states) {
|
|
|
11575
11691
|
if (previous && previous.applicationPorts.length === required && previous.applicationPorts.every((port) => port >= allocation.from && port <= allocation.to && !occupied.has(port)))
|
|
11576
11692
|
return [...previous.applicationPorts];
|
|
11577
11693
|
const width = allocation.to - allocation.from + 1;
|
|
11578
|
-
const start = Number.parseInt(
|
|
11694
|
+
const start = Number.parseInt(createHash9("sha256").update(request.key).digest("hex").slice(0, 8), 16) % width;
|
|
11579
11695
|
for (let offset = 0;offset < width; offset += 1) {
|
|
11580
11696
|
const first = allocation.from + (start + offset) % width;
|
|
11581
11697
|
const candidate = Array.from({ length: required }, (_, index) => first + index);
|
|
@@ -11712,22 +11828,22 @@ async function activateSupervisedService(request, host = defaultHost) {
|
|
|
11712
11828
|
}
|
|
11713
11829
|
|
|
11714
11830
|
// src/container-supervisor.ts
|
|
11715
|
-
import { createHash as
|
|
11716
|
-
import { existsSync as
|
|
11831
|
+
import { createHash as createHash10 } from "crypto";
|
|
11832
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync13, readdirSync as readdirSync3, realpathSync as realpathSync3, renameSync as renameSync9, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "fs";
|
|
11717
11833
|
import { dirname as dirname7, resolve as resolve5, sep as sep3 } from "path";
|
|
11718
11834
|
var defaultHost2 = {
|
|
11719
11835
|
realpath: realpathSync3,
|
|
11720
|
-
exists:
|
|
11721
|
-
read: (path2) =>
|
|
11836
|
+
exists: existsSync15,
|
|
11837
|
+
read: (path2) => readFileSync13(path2, "utf8"),
|
|
11722
11838
|
write(path2, content, mode) {
|
|
11723
|
-
|
|
11839
|
+
mkdirSync11(dirname7(path2), { recursive: true, mode: 493 });
|
|
11724
11840
|
const next = `${path2}.next`;
|
|
11725
|
-
|
|
11726
|
-
|
|
11841
|
+
writeFileSync12(next, content, { mode });
|
|
11842
|
+
renameSync9(next, path2);
|
|
11727
11843
|
},
|
|
11728
|
-
remove: (path2) =>
|
|
11729
|
-
list: (path2) =>
|
|
11730
|
-
mkdir: (path2, mode) =>
|
|
11844
|
+
remove: (path2) => rmSync8(path2, { force: true }),
|
|
11845
|
+
list: (path2) => existsSync15(path2) ? readdirSync3(path2) : [],
|
|
11846
|
+
mkdir: (path2, mode) => mkdirSync11(path2, { recursive: true, mode }),
|
|
11731
11847
|
async exec(argv2) {
|
|
11732
11848
|
const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" } });
|
|
11733
11849
|
const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
|
|
@@ -11745,7 +11861,7 @@ var defaultHost2 = {
|
|
|
11745
11861
|
now: Date.now
|
|
11746
11862
|
};
|
|
11747
11863
|
var within3 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep3}`);
|
|
11748
|
-
var idFor2 = (key) =>
|
|
11864
|
+
var idFor2 = (key) => createHash10("sha256").update(key).digest("hex").slice(0, 24);
|
|
11749
11865
|
var statePath2 = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
|
|
11750
11866
|
var nameFor = (id2, slot) => `forgezero-${id2}-slot${slot}`;
|
|
11751
11867
|
function validateRequest(request, host) {
|
|
@@ -12010,9 +12126,9 @@ var MAX_REQUEST_BYTES6 = 128 * 1024;
|
|
|
12010
12126
|
var MAX_PENDING_REQUESTS = 128;
|
|
12011
12127
|
function startSoftwareHelper(options = {}) {
|
|
12012
12128
|
const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
|
|
12013
|
-
if (
|
|
12129
|
+
if (existsSync16(socketPath))
|
|
12014
12130
|
unlinkSync11(socketPath);
|
|
12015
|
-
|
|
12131
|
+
mkdirSync12(dirname8(socketPath), { recursive: true, mode: 488 });
|
|
12016
12132
|
const ensure = options.ensure ?? ensureSoftwareRequirements;
|
|
12017
12133
|
const activate = options.activate ?? activateSupervisedService;
|
|
12018
12134
|
const buildContainer = options.buildContainer ?? buildContainerImage;
|
|
@@ -12092,7 +12208,7 @@ function startSoftwareHelper(options = {}) {
|
|
|
12092
12208
|
});
|
|
12093
12209
|
socket.on("error", () => socket.destroy());
|
|
12094
12210
|
});
|
|
12095
|
-
server.listen(socketPath, () =>
|
|
12211
|
+
server.listen(socketPath, () => chmodSync14(socketPath, 432));
|
|
12096
12212
|
return server;
|
|
12097
12213
|
}
|
|
12098
12214
|
function requestContainer(op, request, socketPath, timeoutMs) {
|
|
@@ -13121,20 +13237,20 @@ var METAL_SYSTEMD_CREDENTIALS = {
|
|
|
13121
13237
|
|
|
13122
13238
|
// src/platform-bootstrap-runtime.ts
|
|
13123
13239
|
import {
|
|
13124
|
-
chmodSync as
|
|
13240
|
+
chmodSync as chmodSync15,
|
|
13125
13241
|
chownSync,
|
|
13126
13242
|
copyFileSync as copyFileSync2,
|
|
13127
|
-
existsSync as
|
|
13128
|
-
lstatSync as
|
|
13129
|
-
mkdirSync as
|
|
13130
|
-
readFileSync as
|
|
13243
|
+
existsSync as existsSync17,
|
|
13244
|
+
lstatSync as lstatSync3,
|
|
13245
|
+
mkdirSync as mkdirSync13,
|
|
13246
|
+
readFileSync as readFileSync14,
|
|
13131
13247
|
readdirSync as readdirSync4,
|
|
13132
13248
|
realpathSync as realpathSync5,
|
|
13133
|
-
renameSync as
|
|
13134
|
-
rmSync as
|
|
13249
|
+
renameSync as renameSync10,
|
|
13250
|
+
rmSync as rmSync9,
|
|
13135
13251
|
statSync as statSync3,
|
|
13136
13252
|
symlinkSync as symlinkSync4,
|
|
13137
|
-
writeFileSync as
|
|
13253
|
+
writeFileSync as writeFileSync13
|
|
13138
13254
|
} from "fs";
|
|
13139
13255
|
import { join as join10 } from "path";
|
|
13140
13256
|
var boundedInteger2 = (name, value, minimum, maximum) => {
|
|
@@ -13188,21 +13304,21 @@ var platformExec = async (argv2) => {
|
|
|
13188
13304
|
};
|
|
13189
13305
|
var replaceLink = (path2, target2) => {
|
|
13190
13306
|
const pending = `${path2}.next`;
|
|
13191
|
-
|
|
13307
|
+
rmSync9(pending, { force: true });
|
|
13192
13308
|
if (!target2) {
|
|
13193
|
-
|
|
13309
|
+
rmSync9(path2, { force: true });
|
|
13194
13310
|
return;
|
|
13195
13311
|
}
|
|
13196
13312
|
symlinkSync4(target2, pending);
|
|
13197
|
-
|
|
13313
|
+
renameSync10(pending, path2);
|
|
13198
13314
|
};
|
|
13199
13315
|
var secureRelease = (path2, uid, gid) => {
|
|
13200
13316
|
const visit = (current) => {
|
|
13201
|
-
const metadata =
|
|
13317
|
+
const metadata = lstatSync3(current);
|
|
13202
13318
|
if (metadata.isSymbolicLink())
|
|
13203
13319
|
throw new Error("release contains a symbolic link");
|
|
13204
13320
|
chownSync(current, uid, gid);
|
|
13205
|
-
|
|
13321
|
+
chmodSync15(current, metadata.isDirectory() ? 365 : 292);
|
|
13206
13322
|
if (metadata.isDirectory())
|
|
13207
13323
|
for (const name of readdirSync4(current))
|
|
13208
13324
|
visit(join10(current, name));
|
|
@@ -13225,9 +13341,9 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
13225
13341
|
const request = options.fetch ?? fetch;
|
|
13226
13342
|
const sleep2 = options.sleep ?? Bun.sleep;
|
|
13227
13343
|
const slots = join10(normalized.root, "slots");
|
|
13228
|
-
|
|
13344
|
+
mkdirSync13(slots, { recursive: true, mode: 493 });
|
|
13229
13345
|
const slotFile = join10(normalized.root, ".forge-slot");
|
|
13230
|
-
const previousSlot =
|
|
13346
|
+
const previousSlot = existsSync17(slotFile) ? readFileSync14(slotFile, "utf8").trim() : undefined;
|
|
13231
13347
|
const target2 = previousSlot === "blue" ? "green" : "blue";
|
|
13232
13348
|
const port = target2 === "blue" ? normalized.bluePort : normalized.greenPort;
|
|
13233
13349
|
const targetLink = join10(slots, target2);
|
|
@@ -13267,26 +13383,26 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
13267
13383
|
}
|
|
13268
13384
|
const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
|
|
13269
13385
|
const backup = `${upstream}.forgezero-backup`;
|
|
13270
|
-
if (
|
|
13386
|
+
if (existsSync17(upstream))
|
|
13271
13387
|
copyFileSync2(upstream, backup);
|
|
13272
13388
|
else
|
|
13273
|
-
|
|
13274
|
-
|
|
13389
|
+
rmSync9(backup, { force: true });
|
|
13390
|
+
writeFileSync13(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
|
|
13275
13391
|
`, { mode: 420 });
|
|
13276
13392
|
const test = await exec(["/usr/sbin/nginx", "-t"]);
|
|
13277
13393
|
const reload = test.exitCode === 0 ? await exec(["/usr/sbin/nginx", "-s", "reload"]) : test;
|
|
13278
13394
|
if (reload.exitCode !== 0) {
|
|
13279
|
-
if (
|
|
13280
|
-
|
|
13395
|
+
if (existsSync17(backup))
|
|
13396
|
+
renameSync10(backup, upstream);
|
|
13281
13397
|
else
|
|
13282
|
-
|
|
13398
|
+
rmSync9(upstream, { force: true });
|
|
13283
13399
|
await exec(["/usr/sbin/nginx", "-t"]);
|
|
13284
13400
|
await exec(["/usr/sbin/nginx", "-s", "reload"]);
|
|
13285
13401
|
await stopTarget();
|
|
13286
13402
|
throw new Error("nginx refused the promoted upstream");
|
|
13287
13403
|
}
|
|
13288
|
-
|
|
13289
|
-
|
|
13404
|
+
rmSync9(backup, { force: true });
|
|
13405
|
+
writeFileSync13(slotFile, `${target2}
|
|
13290
13406
|
`, { mode: 420 });
|
|
13291
13407
|
if (previousSlot && previousSlot !== target2) {
|
|
13292
13408
|
await sleep2(normalized.drainDeadlineMs);
|
|
@@ -13295,12 +13411,12 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
13295
13411
|
const old = readdirSync4(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join10(releases, entry.name)).sort((left, right) => statSync3(right).mtimeMs - statSync3(left).mtimeMs).slice(normalized.keepReleases);
|
|
13296
13412
|
for (const path2 of old)
|
|
13297
13413
|
if (path2 !== release)
|
|
13298
|
-
|
|
13414
|
+
rmSync9(path2, { recursive: true, force: true });
|
|
13299
13415
|
return { release, slot: target2 };
|
|
13300
13416
|
}
|
|
13301
13417
|
|
|
13302
13418
|
// src/recovery-host.ts
|
|
13303
|
-
import { existsSync as
|
|
13419
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as statSync4 } from "fs";
|
|
13304
13420
|
import { hostname } from "os";
|
|
13305
13421
|
import { join as join11 } from "path";
|
|
13306
13422
|
var execute2 = async (argv2) => {
|
|
@@ -13315,18 +13431,18 @@ var checked7 = async (run2, argv2, expected = 0) => {
|
|
|
13315
13431
|
return result;
|
|
13316
13432
|
};
|
|
13317
13433
|
var activeSlot = () => {
|
|
13318
|
-
const slot =
|
|
13434
|
+
const slot = readFileSync15("/opt/forgezero/.forge-slot", "utf8").trim();
|
|
13319
13435
|
if (slot !== "blue" && slot !== "green")
|
|
13320
13436
|
throw new Error("invalid active slot");
|
|
13321
13437
|
return slot;
|
|
13322
13438
|
};
|
|
13323
13439
|
var exactState = (path2, expected) => {
|
|
13324
|
-
if (!
|
|
13440
|
+
if (!readFileSync15(path2, "utf8").split(`
|
|
13325
13441
|
`).includes(expected))
|
|
13326
13442
|
throw new Error(`state mismatch: ${expected}`);
|
|
13327
13443
|
};
|
|
13328
13444
|
var nonEmpty = (path2) => {
|
|
13329
|
-
if (!
|
|
13445
|
+
if (!existsSync18(path2) || !statSync4(path2).isFile() || statSync4(path2).size < 1)
|
|
13330
13446
|
throw new Error(`required file missing: ${path2}`);
|
|
13331
13447
|
};
|
|
13332
13448
|
var option = (args, name) => {
|
|
@@ -13422,18 +13538,18 @@ async function runRecoveryHost(args, run2 = execute2) {
|
|
|
13422
13538
|
}
|
|
13423
13539
|
|
|
13424
13540
|
// src/platform-fleet-verification.ts
|
|
13425
|
-
import { lstatSync as
|
|
13541
|
+
import { lstatSync as lstatSync6 } from "fs";
|
|
13426
13542
|
|
|
13427
13543
|
// src/bootstrap.ts
|
|
13428
13544
|
import {
|
|
13429
|
-
chmodSync as
|
|
13430
|
-
existsSync as
|
|
13431
|
-
lstatSync as
|
|
13432
|
-
mkdirSync as
|
|
13433
|
-
readFileSync as
|
|
13434
|
-
renameSync as
|
|
13435
|
-
rmSync as
|
|
13436
|
-
writeFileSync as
|
|
13545
|
+
chmodSync as chmodSync17,
|
|
13546
|
+
existsSync as existsSync20,
|
|
13547
|
+
lstatSync as lstatSync5,
|
|
13548
|
+
mkdirSync as mkdirSync15,
|
|
13549
|
+
readFileSync as readFileSync17,
|
|
13550
|
+
renameSync as renameSync12,
|
|
13551
|
+
rmSync as rmSync11,
|
|
13552
|
+
writeFileSync as writeFileSync15
|
|
13437
13553
|
} from "fs";
|
|
13438
13554
|
import { dirname as dirname10 } from "path";
|
|
13439
13555
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -13505,7 +13621,11 @@ function agentEgressUnit(options) {
|
|
|
13505
13621
|
const user = options.user ?? "forgezero";
|
|
13506
13622
|
if (!/^[a-z_][a-z0-9_-]{0,30}$/.test(user))
|
|
13507
13623
|
throw new Error("invalid Agent service user");
|
|
13508
|
-
const
|
|
13624
|
+
const bootstrapBundleEnabled = Boolean(options.bootstrapBundlePath && options.bootstrapBundleManifestPath);
|
|
13625
|
+
if (Boolean(options.bootstrapBundlePath) !== Boolean(options.bootstrapBundleManifestPath) || bootstrapBundleEnabled && ![options.bootstrapBundlePath, options.bootstrapBundleManifestPath].every((path2) => path2.startsWith("/") && !/[\r\n\0:]/.test(path2))) {
|
|
13626
|
+
throw new Error("bootstrap bundle and manifest must be supplied together as absolute paths");
|
|
13627
|
+
}
|
|
13628
|
+
const deploymentEnabled = Boolean(options.repository || bootstrapBundleEnabled || options.pullDeployments);
|
|
13509
13629
|
const runnerLoopbackPorts = normalizeEgressTcpPorts(options.runnerLoopbackPorts ?? []);
|
|
13510
13630
|
const runnerPublicTcpPorts = normalizeEgressTcpPorts(options.runnerPublicTcpPorts ?? DEFAULT_RUNNER_PUBLIC_TCP_PORTS);
|
|
13511
13631
|
if (deploymentEnabled && runnerPublicTcpPorts.length < 1) {
|
|
@@ -13911,17 +14031,16 @@ function agentUnit(options) {
|
|
|
13911
14031
|
if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
|
|
13912
14032
|
throw new Error("migration pull and lifecycle profile must be supplied together");
|
|
13913
14033
|
}
|
|
13914
|
-
const
|
|
13915
|
-
if (
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
|
|
13919
|
-
|
|
13920
|
-
|
|
13921
|
-
throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
|
|
14034
|
+
const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
|
|
14035
|
+
if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath)) {
|
|
14036
|
+
throw new Error("bootstrap SSH credential and public key paths must be supplied together");
|
|
14037
|
+
}
|
|
14038
|
+
const bootstrapEnabled = Boolean(options.pullBootstrap);
|
|
14039
|
+
if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
|
|
14040
|
+
throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
|
|
13922
14041
|
}
|
|
13923
14042
|
const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
|
|
13924
|
-
const bootstrapSshCredentialPath =
|
|
14043
|
+
const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
|
|
13925
14044
|
const warpValues = [
|
|
13926
14045
|
options.warpOrganization,
|
|
13927
14046
|
options.warpClientIdCredentialPath,
|
|
@@ -13975,6 +14094,8 @@ function agentUnit(options) {
|
|
|
13975
14094
|
options.gitPublicKeyPath ? `FZ_GIT_PUBLIC_KEY_FILE=${options.gitPublicKeyPath}` : null,
|
|
13976
14095
|
options.repository ? `FZ_DEPLOY_REPO=${options.repository}` : null,
|
|
13977
14096
|
options.branch ? `FZ_DEPLOY_BRANCH=${options.branch}` : null,
|
|
14097
|
+
options.bootstrapBundlePath ? `FZ_BOOTSTRAP_BUNDLE=${options.bootstrapBundlePath}` : null,
|
|
14098
|
+
options.bootstrapBundleManifestPath ? `FZ_BOOTSTRAP_BUNDLE_MANIFEST=${options.bootstrapBundleManifestPath}` : null,
|
|
13978
14099
|
options.profile ? `FZ_DEPLOY_PROFILE=${options.profile}` : null,
|
|
13979
14100
|
options.repository && options.branch ? `FZ_DEPLOY_KEY=${options.project ?? "platform"}:${options.environment ?? "production"}` : null,
|
|
13980
14101
|
deploymentEnabled ? `FZ_DEPLOY_ROOT=${deployRoot}` : null,
|
|
@@ -14158,15 +14279,13 @@ function planProvision(options) {
|
|
|
14158
14279
|
if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
|
|
14159
14280
|
throw new Error("migration pull and lifecycle profile must be supplied together");
|
|
14160
14281
|
}
|
|
14161
|
-
const
|
|
14162
|
-
if (
|
|
14163
|
-
|
|
14164
|
-
|
|
14165
|
-
|
|
14166
|
-
|
|
14167
|
-
|
|
14168
|
-
].some(Boolean) && !bootstrapEnabled) {
|
|
14169
|
-
throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
|
|
14282
|
+
const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
|
|
14283
|
+
if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath) || options.bootstrapSshSourcePath && !bootstrapIdentityEnabled) {
|
|
14284
|
+
throw new Error("bootstrap SSH credential and public key paths must be supplied together");
|
|
14285
|
+
}
|
|
14286
|
+
const bootstrapEnabled = Boolean(options.pullBootstrap);
|
|
14287
|
+
if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
|
|
14288
|
+
throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
|
|
14170
14289
|
}
|
|
14171
14290
|
const warpValues = [
|
|
14172
14291
|
options.warpOrganization,
|
|
@@ -14176,13 +14295,13 @@ function planProvision(options) {
|
|
|
14176
14295
|
const warpEnabled = warpValues.every(Boolean);
|
|
14177
14296
|
if (warpValues.some(Boolean) && !warpEnabled)
|
|
14178
14297
|
throw new Error("WARP configuration must be supplied together");
|
|
14179
|
-
const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath
|
|
14180
|
-
if (
|
|
14181
|
-
throw new Error("direct enrolment credential
|
|
14298
|
+
const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath);
|
|
14299
|
+
if (options.enrolTokenCredentialPath && !options.enrolStatePath || options.enrolTokenSourcePath && (!options.enrolTokenCredentialPath || !options.enrolStatePath)) {
|
|
14300
|
+
throw new Error("direct enrolment credential requires its durable state path");
|
|
14182
14301
|
}
|
|
14183
14302
|
const enrolTokenSourcePath = options.enrolTokenSourcePath ? systemdPath(options.enrolTokenSourcePath, "enrolment source") : undefined;
|
|
14184
14303
|
const enrolTokenCredentialPath = enrolmentEnabled ? systemdPath(options.enrolTokenCredentialPath, "enrolment credential") : undefined;
|
|
14185
|
-
const enrolStatePath =
|
|
14304
|
+
const enrolStatePath = options.enrolStatePath ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
|
|
14186
14305
|
const enrolStateDir = enrolStatePath?.replace(/\/[^/]+$/, "");
|
|
14187
14306
|
const sourceBinPath = options.sourceBinPath ? systemdPath(options.sourceBinPath, "agent source binary") : undefined;
|
|
14188
14307
|
const binPath = options.binPath ? systemdPath(options.binPath, "agent binary") : undefined;
|
|
@@ -14194,8 +14313,8 @@ function planProvision(options) {
|
|
|
14194
14313
|
const gitPublicKeyDir = gitPublicKeyPath?.replace(/\/[^/]+$/, "");
|
|
14195
14314
|
const lifecycleProfilePath = lifecycleEnabled ? systemdPath(options.lifecycleProfilePath, "lifecycle profile") : undefined;
|
|
14196
14315
|
const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
|
|
14197
|
-
const bootstrapSshCredentialPath =
|
|
14198
|
-
const bootstrapSshPublicKeyPath =
|
|
14316
|
+
const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
|
|
14317
|
+
const bootstrapSshPublicKeyPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshPublicKeyPath, "bootstrap SSH public key") : undefined;
|
|
14199
14318
|
const bootstrapSshSourcePath = options.bootstrapSshSourcePath ? systemdPath(options.bootstrapSshSourcePath, "bootstrap SSH private-key source") : undefined;
|
|
14200
14319
|
const bootstrapSshPublicKeyDir = bootstrapSshPublicKeyPath?.replace(/\/[^/]+$/, "");
|
|
14201
14320
|
const warpClientIdCredentialPath = warpEnabled ? systemdPath(options.warpClientIdCredentialPath, "WARP client-id credential") : undefined;
|
|
@@ -14295,7 +14414,7 @@ function planProvision(options) {
|
|
|
14295
14414
|
step2("Git deploy identity directory", { kind: "directories", directories: [{ path: gitPublicKeyDir, mode: 493, owner: "root", group: "root" }] }),
|
|
14296
14415
|
step2("unique encrypted Git deploy identity", { kind: "ensure-git-identity", credential: gitCredentialPath, publicKey: gitPublicKeyPath })
|
|
14297
14416
|
] : [],
|
|
14298
|
-
...
|
|
14417
|
+
...bootstrapIdentityEnabled ? [
|
|
14299
14418
|
step2("bootstrap SSH public identity directory", { kind: "directories", directories: [
|
|
14300
14419
|
{ path: bootstrapSshPublicKeyDir, mode: 493, owner: "root", group: "root" }
|
|
14301
14420
|
] }),
|
|
@@ -14331,6 +14450,10 @@ function planProvision(options) {
|
|
|
14331
14450
|
{ argv: ["/usr/bin/rm", "-f", "/etc/systemd/system/forgezero-deploy-runner.socket"] },
|
|
14332
14451
|
{ argv: ["/usr/bin/systemctl", "daemon-reload"] }
|
|
14333
14452
|
] })] : [],
|
|
14453
|
+
...enrolStatePath && !enrolmentEnabled ? [step2("retire consumed enrolment unit", { kind: "commands", commands: [
|
|
14454
|
+
{ argv: ["/usr/bin/systemctl", "disable", "--now", "forgezero-agent-enrol.service"], acceptedExitCodes: [0, 1, 5] },
|
|
14455
|
+
{ argv: ["/usr/bin/rm", "-f", ENROLMENT_UNIT_PATH] }
|
|
14456
|
+
] })] : [],
|
|
14334
14457
|
step2("enable and converge services", { kind: "commands", commands: [
|
|
14335
14458
|
{ argv: ["/usr/bin/systemctl", "enable", ...enabledUnits] },
|
|
14336
14459
|
{ argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-agent.service"], acceptedExitCodes: [0, 1] },
|
|
@@ -14357,19 +14480,19 @@ function planProvision(options) {
|
|
|
14357
14480
|
}
|
|
14358
14481
|
|
|
14359
14482
|
// src/cli/agent-install.ts
|
|
14360
|
-
import { randomBytes as
|
|
14483
|
+
import { randomBytes as randomBytes6 } from "crypto";
|
|
14361
14484
|
import {
|
|
14362
|
-
chmodSync as
|
|
14485
|
+
chmodSync as chmodSync16,
|
|
14363
14486
|
copyFileSync as copyFileSync3,
|
|
14364
|
-
existsSync as
|
|
14365
|
-
lstatSync as
|
|
14366
|
-
mkdirSync as
|
|
14367
|
-
readFileSync as
|
|
14487
|
+
existsSync as existsSync19,
|
|
14488
|
+
lstatSync as lstatSync4,
|
|
14489
|
+
mkdirSync as mkdirSync14,
|
|
14490
|
+
readFileSync as readFileSync16,
|
|
14368
14491
|
realpathSync as realpathSync6,
|
|
14369
|
-
renameSync as
|
|
14370
|
-
rmSync as
|
|
14492
|
+
renameSync as renameSync11,
|
|
14493
|
+
rmSync as rmSync10,
|
|
14371
14494
|
symlinkSync as symlinkSync5,
|
|
14372
|
-
writeFileSync as
|
|
14495
|
+
writeFileSync as writeFileSync14
|
|
14373
14496
|
} from "fs";
|
|
14374
14497
|
import { dirname as dirname9 } from "path";
|
|
14375
14498
|
async function readCapabilities(run2) {
|
|
@@ -14430,52 +14553,52 @@ var runProvisionOperation = async (operation) => {
|
|
|
14430
14553
|
}
|
|
14431
14554
|
if (operation.kind === "install-runtime") {
|
|
14432
14555
|
const release = `/opt/forgezero/agent/versions/${operation.version}`;
|
|
14433
|
-
|
|
14434
|
-
|
|
14556
|
+
mkdirSync14(`${release}/dist`, { recursive: true, mode: 493 });
|
|
14557
|
+
mkdirSync14(dirname9(operation.binary), { recursive: true, mode: 493 });
|
|
14435
14558
|
copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
|
|
14436
|
-
|
|
14559
|
+
chmodSync16(`${release}/dist/fz-agent.js`, 493);
|
|
14437
14560
|
const gitSshSource = `${dirname9(operation.source)}/fz-git-ssh.js`;
|
|
14438
|
-
if (!
|
|
14561
|
+
if (!existsSync19(gitSshSource))
|
|
14439
14562
|
return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
|
|
14440
14563
|
copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
|
|
14441
|
-
|
|
14564
|
+
chmodSync16(`${release}/dist/fz-git-ssh.js`, 493);
|
|
14442
14565
|
const pending = "/opt/forgezero/agent/current.next";
|
|
14443
|
-
|
|
14566
|
+
rmSync10(pending, { force: true });
|
|
14444
14567
|
symlinkSync5(`versions/${operation.version}`, pending);
|
|
14445
|
-
|
|
14446
|
-
|
|
14568
|
+
renameSync11(pending, "/opt/forgezero/agent/current");
|
|
14569
|
+
rmSync10(operation.binary, { force: true });
|
|
14447
14570
|
symlinkSync5("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
|
|
14448
14571
|
const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
|
|
14449
|
-
|
|
14572
|
+
rmSync10(gitSshBinary, { force: true });
|
|
14450
14573
|
symlinkSync5("/opt/forgezero/agent/current/dist/fz-git-ssh.js", gitSshBinary);
|
|
14451
14574
|
return { stdout: "", exitCode: 0 };
|
|
14452
14575
|
}
|
|
14453
14576
|
if (operation.kind === "ensure-seed") {
|
|
14454
|
-
if (
|
|
14577
|
+
if (existsSync19(operation.credential) && lstatSync4(operation.credential).size > 0)
|
|
14455
14578
|
return { stdout: "", exitCode: 0 };
|
|
14456
|
-
const seed =
|
|
14579
|
+
const seed = randomBytes6(32).toString("base64url");
|
|
14457
14580
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
|
|
14458
14581
|
if (result.exitCode === 0)
|
|
14459
|
-
|
|
14582
|
+
chmodSync16(operation.credential, 256);
|
|
14460
14583
|
return result;
|
|
14461
14584
|
}
|
|
14462
14585
|
if (operation.kind === "ensure-git-identity") {
|
|
14463
14586
|
const key = "/run/forgezero-git-deploy-key";
|
|
14464
14587
|
const publicKey = `${key}.pub`;
|
|
14465
14588
|
try {
|
|
14466
|
-
if (!
|
|
14467
|
-
|
|
14468
|
-
|
|
14589
|
+
if (!existsSync19(operation.credential) || lstatSync4(operation.credential).size < 1) {
|
|
14590
|
+
rmSync10(key, { force: true });
|
|
14591
|
+
rmSync10(publicKey, { force: true });
|
|
14469
14592
|
let result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-compute", "-f", key]);
|
|
14470
14593
|
if (result.exitCode !== 0)
|
|
14471
14594
|
return result;
|
|
14472
14595
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=git-deploy-key", key, operation.credential]);
|
|
14473
14596
|
if (result.exitCode !== 0)
|
|
14474
14597
|
return result;
|
|
14475
|
-
|
|
14598
|
+
chmodSync16(operation.credential, 256);
|
|
14476
14599
|
}
|
|
14477
|
-
if (!
|
|
14478
|
-
if (!
|
|
14600
|
+
if (!existsSync19(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
|
|
14601
|
+
if (!existsSync19(key)) {
|
|
14479
14602
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=git-deploy-key", operation.credential, key]);
|
|
14480
14603
|
if (decrypted.exitCode !== 0)
|
|
14481
14604
|
return decrypted;
|
|
@@ -14483,30 +14606,30 @@ var runProvisionOperation = async (operation) => {
|
|
|
14483
14606
|
const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
14484
14607
|
if (derived.exitCode !== 0)
|
|
14485
14608
|
return derived;
|
|
14486
|
-
|
|
14609
|
+
writeFileSync14(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
|
|
14487
14610
|
`, { mode: 292 });
|
|
14488
14611
|
}
|
|
14489
14612
|
return { stdout: "", exitCode: 0 };
|
|
14490
14613
|
} finally {
|
|
14491
|
-
|
|
14492
|
-
|
|
14614
|
+
rmSync10(key, { force: true });
|
|
14615
|
+
rmSync10(publicKey, { force: true });
|
|
14493
14616
|
}
|
|
14494
14617
|
}
|
|
14495
14618
|
if (operation.kind === "ensure-bootstrap-ssh-identity") {
|
|
14496
14619
|
const key = "/run/forgezero-bootstrap-ssh-key";
|
|
14497
14620
|
const generatedPublicKey = `${key}.pub`;
|
|
14498
14621
|
try {
|
|
14499
|
-
if (!
|
|
14500
|
-
|
|
14501
|
-
|
|
14622
|
+
if (!existsSync19(operation.credential) || lstatSync4(operation.credential).size < 1) {
|
|
14623
|
+
rmSync10(key, { force: true });
|
|
14624
|
+
rmSync10(generatedPublicKey, { force: true });
|
|
14502
14625
|
let result;
|
|
14503
14626
|
if (operation.source) {
|
|
14504
|
-
const source =
|
|
14627
|
+
const source = existsSync19(operation.source) ? lstatSync4(operation.source) : undefined;
|
|
14505
14628
|
if (!source?.isFile() || source.isSymbolicLink() || source.uid !== 0 || source.nlink !== 1 || (source.mode & 63) !== 0 || source.size < 32 || source.size > 16 * 1024) {
|
|
14506
14629
|
return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
|
|
14507
14630
|
}
|
|
14508
14631
|
copyFileSync3(operation.source, key);
|
|
14509
|
-
|
|
14632
|
+
chmodSync16(key, 384);
|
|
14510
14633
|
} else {
|
|
14511
14634
|
result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-bootstrap-runner", "-f", key]);
|
|
14512
14635
|
if (result.exitCode !== 0)
|
|
@@ -14519,48 +14642,48 @@ var runProvisionOperation = async (operation) => {
|
|
|
14519
14642
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=bootstrap-ssh-key", key, operation.credential]);
|
|
14520
14643
|
if (result.exitCode !== 0)
|
|
14521
14644
|
return result;
|
|
14522
|
-
|
|
14645
|
+
chmodSync16(operation.credential, 256);
|
|
14523
14646
|
}
|
|
14524
|
-
if (!
|
|
14525
|
-
if (!
|
|
14647
|
+
if (!existsSync19(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
|
|
14648
|
+
if (!existsSync19(key)) {
|
|
14526
14649
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
|
|
14527
14650
|
if (decrypted.exitCode !== 0)
|
|
14528
14651
|
return decrypted;
|
|
14529
|
-
|
|
14652
|
+
chmodSync16(key, 384);
|
|
14530
14653
|
}
|
|
14531
14654
|
const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
14532
14655
|
if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
|
|
14533
14656
|
return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
|
|
14534
14657
|
}
|
|
14535
|
-
|
|
14536
|
-
|
|
14658
|
+
mkdirSync14(dirname9(operation.publicKey), { recursive: true, mode: 493 });
|
|
14659
|
+
writeFileSync14(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
|
|
14537
14660
|
`, { mode: 292 });
|
|
14538
|
-
|
|
14661
|
+
chmodSync16(operation.publicKey, 292);
|
|
14539
14662
|
}
|
|
14540
14663
|
if (operation.source)
|
|
14541
|
-
|
|
14664
|
+
rmSync10(operation.source, { force: true });
|
|
14542
14665
|
return { stdout: "", exitCode: 0 };
|
|
14543
14666
|
} finally {
|
|
14544
|
-
|
|
14545
|
-
|
|
14667
|
+
rmSync10(key, { force: true });
|
|
14668
|
+
rmSync10(generatedPublicKey, { force: true });
|
|
14546
14669
|
}
|
|
14547
14670
|
}
|
|
14548
14671
|
if (operation.kind === "ensure-enrolment") {
|
|
14549
|
-
if (
|
|
14672
|
+
if (existsSync19(operation.state) && lstatSync4(operation.state).size > 0 || existsSync19(operation.credential) && lstatSync4(operation.credential).size > 0)
|
|
14550
14673
|
return { stdout: "", exitCode: 0 };
|
|
14551
|
-
if (!
|
|
14674
|
+
if (!existsSync19(operation.source))
|
|
14552
14675
|
return { stdout: "enrolment source is missing", exitCode: 1 };
|
|
14553
14676
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
|
|
14554
14677
|
if (result.exitCode === 0) {
|
|
14555
|
-
|
|
14556
|
-
|
|
14678
|
+
chmodSync16(operation.credential, 256);
|
|
14679
|
+
rmSync10(operation.source, { force: true });
|
|
14557
14680
|
}
|
|
14558
14681
|
return result;
|
|
14559
14682
|
}
|
|
14560
14683
|
if (operation.kind === "wait-socket") {
|
|
14561
14684
|
for (let attempt = 0;attempt < operation.attempts; attempt += 1) {
|
|
14562
14685
|
try {
|
|
14563
|
-
if (
|
|
14686
|
+
if (lstatSync4(operation.path).isSocket())
|
|
14564
14687
|
return { stdout: "", exitCode: 0 };
|
|
14565
14688
|
} catch {}
|
|
14566
14689
|
await Bun.sleep(operation.intervalMs);
|
|
@@ -14568,7 +14691,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
14568
14691
|
return { stdout: `socket did not become ready: ${operation.path}`, exitCode: 1 };
|
|
14569
14692
|
}
|
|
14570
14693
|
if (operation.kind === "verify-file")
|
|
14571
|
-
return
|
|
14694
|
+
return existsSync19(operation.path) && lstatSync4(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
|
|
14572
14695
|
if (operation.kind === "verify-egress") {
|
|
14573
14696
|
const active = await fixed(["/usr/bin/systemctl", "is-active", "forgezero-agent-egress.service"]);
|
|
14574
14697
|
if (active.exitCode !== 0)
|
|
@@ -14590,25 +14713,25 @@ var runProvisionOperation = async (operation) => {
|
|
|
14590
14713
|
}
|
|
14591
14714
|
}
|
|
14592
14715
|
if (operation.kind === "install-warp") {
|
|
14593
|
-
const os =
|
|
14716
|
+
const os = readFileSync16("/etc/os-release", "utf8");
|
|
14594
14717
|
if (!/^ID=ubuntu$/m.test(os) || !/^VERSION_ID="?26\.04"?$/m.test(os))
|
|
14595
14718
|
return { stdout: "unsupported WARP host OS", exitCode: 1 };
|
|
14596
14719
|
const response = await fetch("https://pkg.cloudflareclient.com/pubkey.gpg", { signal: AbortSignal.timeout(30000) });
|
|
14597
14720
|
if (!response.ok)
|
|
14598
14721
|
return { stdout: `WARP key HTTP ${response.status}`, exitCode: 1 };
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14722
|
+
mkdirSync14("/usr/share/keyrings", { recursive: true, mode: 493 });
|
|
14723
|
+
mkdirSync14("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
|
|
14724
|
+
mkdirSync14("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
|
|
14602
14725
|
const key = "/run/cloudflare-warp-key.gpg";
|
|
14603
|
-
|
|
14726
|
+
writeFileSync14(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
|
|
14604
14727
|
let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
|
|
14605
|
-
|
|
14728
|
+
rmSync10(key, { force: true });
|
|
14606
14729
|
if (result.exitCode !== 0)
|
|
14607
14730
|
return result;
|
|
14608
14731
|
const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
|
|
14609
14732
|
if (!codename)
|
|
14610
14733
|
return { stdout: "Ubuntu codename missing", exitCode: 1 };
|
|
14611
|
-
|
|
14734
|
+
writeFileSync14("/etc/apt/sources.list.d/cloudflare-client.list", `deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ ${codename} main
|
|
14612
14735
|
`, { mode: 420 });
|
|
14613
14736
|
result = await fixed(["/usr/bin/apt-get", "update", "-qq"]);
|
|
14614
14737
|
return result.exitCode === 0 ? fixed(["/usr/bin/apt-get", "install", "-y", "cloudflare-warp"]) : result;
|
|
@@ -14623,7 +14746,7 @@ async function localRunner(operation) {
|
|
|
14623
14746
|
if (capability.kind === "version")
|
|
14624
14747
|
return fixed(capability.argv);
|
|
14625
14748
|
try {
|
|
14626
|
-
const metadata =
|
|
14749
|
+
const metadata = lstatSync4(capability.path);
|
|
14627
14750
|
const present = capability.nodeType === "directory" ? metadata.isDirectory() : true;
|
|
14628
14751
|
return { stdout: present ? `yes
|
|
14629
14752
|
` : `no
|
|
@@ -14670,6 +14793,7 @@ var SEED_CREDENTIAL = `${CREDS}/seed-sync-root.cred`;
|
|
|
14670
14793
|
var BACKUP_RECOVERY_CREDENTIAL = `${CREDS}/backup-recovery-root.cred`;
|
|
14671
14794
|
var BOOTSTRAP_SSH_CREDENTIAL = `${CREDS}/bootstrap-ssh-key.cred`;
|
|
14672
14795
|
var BOOTSTRAP_SSH_PUBLIC_KEY = "/etc/forgezero/bootstrap/runner.pub";
|
|
14796
|
+
var BOOTSTRAP_RELEASE_EVIDENCE = "/var/lib/forgezero/bootstrap-release.json";
|
|
14673
14797
|
var LIFECYCLE_PROFILE = "/etc/forgezero/lifecycle.json";
|
|
14674
14798
|
var CONTROL_SOCKET = "/run/forgezero/control.sock";
|
|
14675
14799
|
var CLOUDFLARED_METRICS_ADDRESS = "127.0.0.1:20241";
|
|
@@ -14846,6 +14970,65 @@ async function bootstrapStatus(host = localBootstrapHost()) {
|
|
|
14846
14970
|
problems.push("durable Agent enrolment state is missing");
|
|
14847
14971
|
return { initialized: problems.length === 0, kind: state.kind, profile: state.profile, services, problems };
|
|
14848
14972
|
}
|
|
14973
|
+
function planBootstrapAgentInstall(config, phase, context) {
|
|
14974
|
+
const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
|
|
14975
|
+
if (phase === "bootstrap") {
|
|
14976
|
+
if (config.kind !== "platform" || hasBinding || !initialBundle) {
|
|
14977
|
+
throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
|
|
14978
|
+
}
|
|
14979
|
+
} else if (phase === "enrol") {
|
|
14980
|
+
if (hasBinding || !hasEnrolCredential) {
|
|
14981
|
+
throw new Error("Agent enrolment requires one unbound host and its sealed one-use credential");
|
|
14982
|
+
}
|
|
14983
|
+
} else if (!hasBinding) {
|
|
14984
|
+
throw new Error("bound Agent convergence requires durable enrolment state");
|
|
14985
|
+
}
|
|
14986
|
+
const bound = phase === "bound";
|
|
14987
|
+
const enrolling = phase === "enrol";
|
|
14988
|
+
const authenticated = enrolling || bound;
|
|
14989
|
+
const bootstrapRunner = platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner);
|
|
14990
|
+
const options = {
|
|
14991
|
+
capabilities,
|
|
14992
|
+
socketPath: DEFAULT_SOCKET,
|
|
14993
|
+
seedPath: "/var/lib/forgezero/node.seed",
|
|
14994
|
+
controlSocketPath: CONTROL_SOCKET,
|
|
14995
|
+
repository: phase === "bootstrap" ? initialBundle.path : bound && config.kind === "enrolled-compute" ? config.repository : undefined,
|
|
14996
|
+
branch: phase === "bootstrap" ? initialBundle.manifest.branch : bound && config.kind === "enrolled-compute" ? config.branch : undefined,
|
|
14997
|
+
bootstrapBundlePath: phase === "bootstrap" ? initialBundle.path : undefined,
|
|
14998
|
+
bootstrapBundleManifestPath: phase === "bootstrap" ? initialBundle.manifestPath : undefined,
|
|
14999
|
+
profile: config.profile,
|
|
15000
|
+
deployRoot: config.deployRoot ?? "/opt/forgezero",
|
|
15001
|
+
deploymentCredentials: config.deploymentCredentials,
|
|
15002
|
+
publicApiUrl: config.apiUrl,
|
|
15003
|
+
gitCredentialPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
|
|
15004
|
+
gitPublicKeyPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
|
|
15005
|
+
generateGitIdentity: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey === true,
|
|
15006
|
+
pullDeployments: bound,
|
|
15007
|
+
pullMigrations: bound && config.kind === "platform",
|
|
15008
|
+
pullBootstrap: bound && bootstrapRunner,
|
|
15009
|
+
bootstrapSshCredentialPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
|
|
15010
|
+
bootstrapSshSourcePath: enrolling && config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
|
|
15011
|
+
bootstrapSshPublicKeyPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
|
|
15012
|
+
bootstrapTargetTelemetryEndpoint: bound && bootstrapRunner ? platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined : undefined,
|
|
15013
|
+
lifecycleProfilePath: bound && config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
|
|
15014
|
+
enforceEgress: true,
|
|
15015
|
+
nodeHostname: config.nodeHostname,
|
|
15016
|
+
telemetryEndpoint: config.telemetryEndpoint,
|
|
15017
|
+
binPath: "/usr/local/lib/forgezero/agent/fz-agent",
|
|
15018
|
+
sourceBinPath: PACKAGED_AGENT_BIN,
|
|
15019
|
+
...enrolling ? {
|
|
15020
|
+
enrolTokenCredentialPath: ENROL_CREDENTIAL,
|
|
15021
|
+
enrolStatePath: "/var/lib/forgezero/enrolment.json"
|
|
15022
|
+
} : bound ? { enrolStatePath: "/var/lib/forgezero/enrolment.json" } : {},
|
|
15023
|
+
...authenticated ? {
|
|
15024
|
+
apiUrl: config.apiUrl,
|
|
15025
|
+
project: config.kind === "enrolled-compute" ? config.realm : "platform",
|
|
15026
|
+
environment: config.kind === "enrolled-compute" ? undefined : config.environment,
|
|
15027
|
+
nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
|
|
15028
|
+
} : {}
|
|
15029
|
+
};
|
|
15030
|
+
return planInstall(options);
|
|
15031
|
+
}
|
|
14849
15032
|
function localBootstrapHost() {
|
|
14850
15033
|
const execute3 = async (argv2, options = {}) => {
|
|
14851
15034
|
const child = Bun.spawn([...argv2], { stdin: options.stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
|
|
@@ -14862,19 +15045,19 @@ function localBootstrapHost() {
|
|
|
14862
15045
|
};
|
|
14863
15046
|
return {
|
|
14864
15047
|
uid: () => process.getuid?.() ?? -1,
|
|
14865
|
-
exists:
|
|
14866
|
-
read: (path2) =>
|
|
15048
|
+
exists: existsSync20,
|
|
15049
|
+
read: (path2) => readFileSync17(path2, "utf8"),
|
|
14867
15050
|
write(path2, content, mode) {
|
|
14868
|
-
|
|
15051
|
+
mkdirSync15(dirname10(path2), { recursive: true, mode: 493 });
|
|
14869
15052
|
const temporary = `${path2}.next.${process.pid}`;
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
15053
|
+
writeFileSync15(temporary, content, { mode });
|
|
15054
|
+
chmodSync17(temporary, mode);
|
|
15055
|
+
renameSync12(temporary, path2);
|
|
14873
15056
|
},
|
|
14874
|
-
mkdir: (path2, mode) =>
|
|
14875
|
-
remove: (path2) =>
|
|
15057
|
+
mkdir: (path2, mode) => mkdirSync15(path2, { recursive: true, mode }),
|
|
15058
|
+
remove: (path2) => rmSync11(path2, { force: true }),
|
|
14876
15059
|
inspect(path2) {
|
|
14877
|
-
const value =
|
|
15060
|
+
const value = lstatSync5(path2);
|
|
14878
15061
|
return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
|
|
14879
15062
|
},
|
|
14880
15063
|
exec: execute3,
|
|
@@ -14893,10 +15076,15 @@ function localBootstrapHost() {
|
|
|
14893
15076
|
throw new Error(`Agent software requirements failed: ${result.output.trim()}`);
|
|
14894
15077
|
return result;
|
|
14895
15078
|
},
|
|
14896
|
-
async installAgent(config) {
|
|
15079
|
+
async installAgent(config, phase) {
|
|
14897
15080
|
const capabilities = await readCapabilities(localRunner);
|
|
14898
|
-
const
|
|
14899
|
-
const
|
|
15081
|
+
const hasBinding = existsSync20("/var/lib/forgezero/enrolment.json");
|
|
15082
|
+
const hasEnrolCredential = existsSync20(ENROL_CREDENTIAL);
|
|
15083
|
+
const initialBundle = config.kind === "platform" && !existsSync20(BOOTSTRAP_RELEASE_EVIDENCE) ? {
|
|
15084
|
+
path: config.bootstrapBundle.bundleFile,
|
|
15085
|
+
manifestPath: config.bootstrapBundle.manifestFile,
|
|
15086
|
+
manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync17(config.bootstrapBundle.manifestFile, "utf8")))
|
|
15087
|
+
} : undefined;
|
|
14900
15088
|
if (config.kind === "platform") {
|
|
14901
15089
|
const lifecycle = config.database.role === "none" ? {
|
|
14902
15090
|
apiUnits: ["forgezero@blue.service", "forgezero@green.service"],
|
|
@@ -14908,49 +15096,19 @@ function localBootstrapHost() {
|
|
|
14908
15096
|
databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
|
|
14909
15097
|
databasePorts: [8529]
|
|
14910
15098
|
};
|
|
14911
|
-
|
|
14912
|
-
|
|
15099
|
+
mkdirSync15(dirname10(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
|
|
15100
|
+
writeFileSync15(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
|
|
14913
15101
|
`, { mode: 256 });
|
|
14914
15102
|
}
|
|
14915
|
-
const plan =
|
|
15103
|
+
const plan = planBootstrapAgentInstall(config, phase, {
|
|
14916
15104
|
capabilities,
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
repository: config.repository,
|
|
14921
|
-
branch: config.branch,
|
|
14922
|
-
profile: config.kind === "platform" ? config.profile : config.profile,
|
|
14923
|
-
deployRoot,
|
|
14924
|
-
deploymentCredentials: config.deploymentCredentials,
|
|
14925
|
-
publicApiUrl: config.apiUrl,
|
|
14926
|
-
gitCredentialPath: "/etc/forgezero/creds/git-deploy-key.cred",
|
|
14927
|
-
gitPublicKeyPath: "/etc/forgezero/git/deploy.pub",
|
|
14928
|
-
generateGitIdentity: true,
|
|
14929
|
-
pullDeployments: hasBinding,
|
|
14930
|
-
pullMigrations: config.kind === "platform" && hasBinding,
|
|
14931
|
-
pullBootstrap: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner),
|
|
14932
|
-
bootstrapSshCredentialPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
|
|
14933
|
-
bootstrapSshSourcePath: config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
|
|
14934
|
-
bootstrapSshPublicKeyPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
|
|
14935
|
-
bootstrapTargetTelemetryEndpoint: platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined,
|
|
14936
|
-
lifecycleProfilePath: config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
|
|
14937
|
-
enforceEgress: true,
|
|
14938
|
-
nodeHostname: config.nodeHostname,
|
|
14939
|
-
telemetryEndpoint: config.telemetryEndpoint,
|
|
14940
|
-
binPath: "/usr/local/lib/forgezero/agent/fz-agent",
|
|
14941
|
-
sourceBinPath: PACKAGED_AGENT_BIN,
|
|
14942
|
-
...hasBinding ? {
|
|
14943
|
-
enrolTokenCredentialPath: ENROL_CREDENTIAL,
|
|
14944
|
-
enrolStatePath: "/var/lib/forgezero/enrolment.json",
|
|
14945
|
-
apiUrl: config.apiUrl,
|
|
14946
|
-
project: config.kind === "enrolled-compute" ? config.realm : "platform",
|
|
14947
|
-
environment: config.kind === "enrolled-compute" ? undefined : config.environment,
|
|
14948
|
-
nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
|
|
14949
|
-
} : {}
|
|
15105
|
+
hasBinding,
|
|
15106
|
+
hasEnrolCredential,
|
|
15107
|
+
initialBundle
|
|
14950
15108
|
});
|
|
14951
15109
|
for (const unit3 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
|
|
14952
|
-
|
|
14953
|
-
|
|
15110
|
+
mkdirSync15(dirname10(unit3.path), { recursive: true, mode: 493 });
|
|
15111
|
+
writeFileSync15(unit3.path, unit3.unit, { mode: 420 });
|
|
14954
15112
|
}
|
|
14955
15113
|
await applyPlan(plan, localRunner);
|
|
14956
15114
|
return plan;
|
|
@@ -14964,7 +15122,7 @@ var localRuntime = () => ({
|
|
|
14964
15122
|
bootstrapStatus: () => bootstrapStatus(),
|
|
14965
15123
|
characterDevice(path2) {
|
|
14966
15124
|
try {
|
|
14967
|
-
return
|
|
15125
|
+
return lstatSync6(path2).isCharacterDevice();
|
|
14968
15126
|
} catch {
|
|
14969
15127
|
return false;
|
|
14970
15128
|
}
|
|
@@ -15002,8 +15160,8 @@ async function verifyPlatformFleetHost(expectedVersion, runtime = localRuntime()
|
|
|
15002
15160
|
}
|
|
15003
15161
|
|
|
15004
15162
|
// src/community-rehearsal-host.ts
|
|
15005
|
-
import { createHash as
|
|
15006
|
-
import { lstatSync as
|
|
15163
|
+
import { createHash as createHash11 } from "crypto";
|
|
15164
|
+
import { lstatSync as lstatSync7, mkdirSync as mkdirSync16, readFileSync as readFileSync18, rmSync as rmSync12, symlinkSync as symlinkSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
15007
15165
|
import { dirname as dirname11 } from "path";
|
|
15008
15166
|
var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
15009
15167
|
var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
@@ -15200,10 +15358,10 @@ async function exec(argv2, stdin) {
|
|
|
15200
15358
|
async function applyOperations(operations) {
|
|
15201
15359
|
for (const operation of operations) {
|
|
15202
15360
|
if (operation.kind === "remove-tree")
|
|
15203
|
-
|
|
15361
|
+
rmSync12(operation.path, { recursive: true, force: true });
|
|
15204
15362
|
else if (operation.kind === "write") {
|
|
15205
|
-
|
|
15206
|
-
|
|
15363
|
+
mkdirSync16(dirname11(operation.path), { recursive: true });
|
|
15364
|
+
writeFileSync16(operation.path, operation.content, { mode: operation.mode });
|
|
15207
15365
|
} else if (operation.kind === "unlink") {
|
|
15208
15366
|
try {
|
|
15209
15367
|
unlinkSync12(operation.path);
|
|
@@ -15251,8 +15409,8 @@ async function runCommunityRehearsalHost(request) {
|
|
|
15251
15409
|
return { ok: true, action: request.action, node: node.name };
|
|
15252
15410
|
}
|
|
15253
15411
|
if (request.action === "prepare") {
|
|
15254
|
-
const metadata =
|
|
15255
|
-
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 ||
|
|
15412
|
+
const metadata = lstatSync7(request.archivePath);
|
|
15413
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash11("sha256").update(readFileSync18(request.archivePath)).digest("hex") !== request.archiveSha256) {
|
|
15256
15414
|
throw new Error("community rehearsal archive is not the declared bounded release");
|
|
15257
15415
|
}
|
|
15258
15416
|
await ensureCommunityRehearsalArango();
|
|
@@ -15300,13 +15458,13 @@ async function runCommunityRehearsalHost(request) {
|
|
|
15300
15458
|
}
|
|
15301
15459
|
|
|
15302
15460
|
// src/supervised-app.ts
|
|
15303
|
-
import { lstatSync as
|
|
15461
|
+
import { lstatSync as lstatSync8, readFileSync as readFileSync19 } from "fs";
|
|
15304
15462
|
function readConfig(path2) {
|
|
15305
|
-
const stat =
|
|
15463
|
+
const stat = lstatSync8(path2);
|
|
15306
15464
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== 0 || (stat.mode & 18) !== 0 || stat.size > 128 * 1024) {
|
|
15307
15465
|
throw new Error("supervised app config is unsafe");
|
|
15308
15466
|
}
|
|
15309
|
-
const value = JSON.parse(
|
|
15467
|
+
const value = JSON.parse(readFileSync19(path2, "utf8"));
|
|
15310
15468
|
if (value.format !== 1 || typeof value.root !== "string" || !value.root.startsWith("/") || typeof value.release !== "string" || !value.release.startsWith(`${value.root}/releases/`) || typeof value.home !== "string" || value.home !== `${value.root}/app-home` || !Array.isArray(value.argv) || value.argv.length < 1 || value.argv.length > 256 || value.argv.some((argument) => typeof argument !== "string" || !argument || argument.includes("\x00")) || !value.environment || typeof value.environment !== "object" || Array.isArray(value.environment) || Object.entries(value.environment).some(([name, item]) => !/^FZ_(?:APP_PORT|RELEASE)$/.test(name) || typeof item !== "string" || /[\r\n\0]/.test(item))) {
|
|
15311
15469
|
throw new Error("supervised app config is malformed");
|
|
15312
15470
|
}
|
|
@@ -15352,17 +15510,17 @@ function agentCommandArgs(args, command2) {
|
|
|
15352
15510
|
return args.slice(index + 1);
|
|
15353
15511
|
}
|
|
15354
15512
|
function loadOrCreateSeed(path2) {
|
|
15355
|
-
if (
|
|
15356
|
-
const seed2 = new Uint8Array(Buffer.from(
|
|
15513
|
+
if (existsSync21(path2)) {
|
|
15514
|
+
const seed2 = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
|
|
15357
15515
|
if (seed2.length < 32) {
|
|
15358
15516
|
throw new Error(`agent: the seed at ${path2} is too short to derive a key from.`);
|
|
15359
15517
|
}
|
|
15360
15518
|
return seed2;
|
|
15361
15519
|
}
|
|
15362
|
-
|
|
15363
|
-
const seed = new Uint8Array(
|
|
15364
|
-
|
|
15365
|
-
|
|
15520
|
+
mkdirSync17(dirname12(path2), { recursive: true });
|
|
15521
|
+
const seed = new Uint8Array(randomBytes7(32));
|
|
15522
|
+
writeFileSync17(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
|
|
15523
|
+
chmodSync18(path2, 384);
|
|
15366
15524
|
return seed;
|
|
15367
15525
|
}
|
|
15368
15526
|
var DEFAULT_SOCKET_PATH = DEFAULT_SOCKET;
|
|
@@ -15391,9 +15549,9 @@ function loadSeedCredential(name = DEFAULT_SEED_CREDENTIAL, directory = process.
|
|
|
15391
15549
|
if (!directory)
|
|
15392
15550
|
throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the node identity.");
|
|
15393
15551
|
const path2 = `${directory}/${name}`;
|
|
15394
|
-
if (!
|
|
15552
|
+
if (!existsSync21(path2))
|
|
15395
15553
|
throw new Error(`agent: the systemd credential ${name} is missing at ${path2}.`);
|
|
15396
|
-
const seed = new Uint8Array(Buffer.from(
|
|
15554
|
+
const seed = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
|
|
15397
15555
|
if (seed.length < 32)
|
|
15398
15556
|
throw new Error(`agent: the systemd credential ${name} is too short to derive a key from.`);
|
|
15399
15557
|
return seed;
|
|
@@ -15403,7 +15561,7 @@ function loadTextCredential(name, directory = process.env.CREDENTIALS_DIRECTORY)
|
|
|
15403
15561
|
throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the credential.");
|
|
15404
15562
|
if (!/^[A-Za-z0-9_.-]+$/.test(name))
|
|
15405
15563
|
throw new Error("agent: invalid systemd credential name.");
|
|
15406
|
-
const value =
|
|
15564
|
+
const value = readFileSync20(`${directory}/${name}`, "utf8").trim();
|
|
15407
15565
|
if (!value)
|
|
15408
15566
|
throw new Error(`agent: systemd credential ${name} is empty.`);
|
|
15409
15567
|
return value;
|
|
@@ -15528,7 +15686,7 @@ if (import.meta.main) {
|
|
|
15528
15686
|
if (configPath !== "/etc/forgezero/deploy-activation.json" || !release) {
|
|
15529
15687
|
throw new Error("platform-activate requires the fixed config and one absolute release path");
|
|
15530
15688
|
}
|
|
15531
|
-
const config = JSON.parse(
|
|
15689
|
+
const config = JSON.parse(readFileSync20(configPath, "utf8"));
|
|
15532
15690
|
const result = await activatePlatformRelease(config, release);
|
|
15533
15691
|
console.log(`promoted ${result.release} on ${result.slot}`);
|
|
15534
15692
|
process.exit(0);
|
|
@@ -15553,13 +15711,13 @@ if (import.meta.main) {
|
|
|
15553
15711
|
if (args.length !== 1)
|
|
15554
15712
|
throw new Error("project-release-check accepts no coordinates");
|
|
15555
15713
|
for (const relative of ["src/index.ts", "bun.lock", "src/generated/shared/contract-manifest.json"]) {
|
|
15556
|
-
const stat =
|
|
15714
|
+
const stat = lstatSync9(join12(process.cwd(), relative));
|
|
15557
15715
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1) {
|
|
15558
15716
|
throw new Error(`project release is missing ${relative}`);
|
|
15559
15717
|
}
|
|
15560
15718
|
}
|
|
15561
15719
|
for (const relative of ["package.json", "bun.lock"]) {
|
|
15562
|
-
const contents =
|
|
15720
|
+
const contents = readFileSync20(join12(process.cwd(), relative), "utf8");
|
|
15563
15721
|
if (/(?:workspace|file):/.test(contents)) {
|
|
15564
15722
|
throw new Error(`project release ${relative} contains a local dependency`);
|
|
15565
15723
|
}
|
|
@@ -15604,8 +15762,8 @@ if (import.meta.main) {
|
|
|
15604
15762
|
keys: keys2,
|
|
15605
15763
|
label: process.env.FZ_NODE_LABEL,
|
|
15606
15764
|
edgeHostname: process.env.FZ_NODE_HOSTNAME,
|
|
15607
|
-
gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ?
|
|
15608
|
-
bootstrapSshPublicKey: process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE ?
|
|
15765
|
+
gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_GIT_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
|
|
15766
|
+
bootstrapSshPublicKey: process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
|
|
15609
15767
|
privateNetworkAttachment: privateNetworkAttachmentFromEnvironment()
|
|
15610
15768
|
});
|
|
15611
15769
|
console.log(`[agent] enrolled ${binding2.computeReference} in project ${binding2.projectKey}/${binding2.environmentKey}`);
|
|
@@ -15647,7 +15805,7 @@ if (import.meta.main) {
|
|
|
15647
15805
|
const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
|
|
15648
15806
|
if (!profilePath)
|
|
15649
15807
|
throw new Error("metal-helper requires --profile=/absolute/path.json");
|
|
15650
|
-
const profile2 = JSON.parse(
|
|
15808
|
+
const profile2 = JSON.parse(readFileSync20(profilePath, "utf8"));
|
|
15651
15809
|
const helper = startMetalHelper({
|
|
15652
15810
|
profile: profile2,
|
|
15653
15811
|
socketPath: process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET
|
|
@@ -15797,7 +15955,7 @@ if (import.meta.main) {
|
|
|
15797
15955
|
const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
|
|
15798
15956
|
if (!profilePath)
|
|
15799
15957
|
throw new Error("metal-isolation requires --profile=/absolute/path.json");
|
|
15800
|
-
const profile2 = JSON.parse(
|
|
15958
|
+
const profile2 = JSON.parse(readFileSync20(profilePath, "utf8"));
|
|
15801
15959
|
await applyMetalIsolation(profile2);
|
|
15802
15960
|
console.log("[metal-isolation] host and guest cgroup boundaries active");
|
|
15803
15961
|
process.exit(0);
|
|
@@ -15842,7 +16000,7 @@ if (import.meta.main) {
|
|
|
15842
16000
|
if (claimArg !== "-" && !claimArg.startsWith("/")) {
|
|
15843
16001
|
throw new Error("metal-apply claim path must be absolute");
|
|
15844
16002
|
}
|
|
15845
|
-
const raw =
|
|
16003
|
+
const raw = readFileSync20(claimArg === "-" ? "/dev/stdin" : claimArg, "utf8");
|
|
15846
16004
|
if (Buffer.byteLength(raw) > 32 * 1024)
|
|
15847
16005
|
throw new Error("metal-apply claim exceeds 32 KiB");
|
|
15848
16006
|
const claim = JSON.parse(raw);
|
|
@@ -15867,7 +16025,7 @@ if (import.meta.main) {
|
|
|
15867
16025
|
if (commandArgs.length !== 1 || commandArgs[0] !== "--request=-") {
|
|
15868
16026
|
throw new Error("community-rehearsal requires exactly --request=-");
|
|
15869
16027
|
}
|
|
15870
|
-
const raw =
|
|
16028
|
+
const raw = readFileSync20("/dev/stdin", "utf8");
|
|
15871
16029
|
if (Buffer.byteLength(raw) > 128 * 1024)
|
|
15872
16030
|
throw new Error("community rehearsal request exceeds 128 KiB");
|
|
15873
16031
|
const request = parseCommunityRehearsalHostRequest(JSON.parse(raw));
|
|
@@ -15878,7 +16036,7 @@ if (import.meta.main) {
|
|
|
15878
16036
|
if (args.length !== 1 || args[0] !== "--request=-") {
|
|
15879
16037
|
throw new Error("metal-admission-proof requires exactly --request=-");
|
|
15880
16038
|
}
|
|
15881
|
-
const raw =
|
|
16039
|
+
const raw = readFileSync20("/dev/stdin", "utf8");
|
|
15882
16040
|
if (Buffer.byteLength(raw) > 32 * 1024)
|
|
15883
16041
|
throw new Error("metal admission proof request exceeds 32 KiB");
|
|
15884
16042
|
const request = JSON.parse(raw);
|
|
@@ -15896,9 +16054,9 @@ if (import.meta.main) {
|
|
|
15896
16054
|
vcpu: cpus().length,
|
|
15897
16055
|
memoryGib: Math.max(1, Math.floor(totalmem() / 1024 ** 3)),
|
|
15898
16056
|
diskGib: Math.max(1, Math.floor(Number(filesystem.blocks) * Number(filesystem.bsize) / 1024 ** 3)),
|
|
15899
|
-
kvm:
|
|
15900
|
-
snpHost:
|
|
15901
|
-
helper:
|
|
16057
|
+
kvm: existsSync21("/dev/kvm"),
|
|
16058
|
+
snpHost: existsSync21("/dev/sev"),
|
|
16059
|
+
helper: existsSync21(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
|
|
15902
16060
|
}
|
|
15903
16061
|
};
|
|
15904
16062
|
const envelope = signRequest(keys2, keys2.ed25519.publicKey, {
|
|
@@ -15933,9 +16091,9 @@ if (import.meta.main) {
|
|
|
15933
16091
|
metalHostname: process.env.FZ_METAL_HOSTNAME,
|
|
15934
16092
|
run: (claim) => requestMetalProvision(claim, process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET),
|
|
15935
16093
|
metalPreflight: () => ({
|
|
15936
|
-
snpHost:
|
|
15937
|
-
kvm:
|
|
15938
|
-
helper:
|
|
16094
|
+
snpHost: existsSync21("/dev/sev"),
|
|
16095
|
+
kvm: existsSync21("/dev/kvm"),
|
|
16096
|
+
helper: existsSync21(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
|
|
15939
16097
|
}),
|
|
15940
16098
|
onEvent: (event, detail) => console.log(`[metal-agent] ${event}${detail ? ` ${JSON.stringify(detail)}` : ""}`)
|
|
15941
16099
|
});
|
|
@@ -16018,7 +16176,7 @@ if (import.meta.main) {
|
|
|
16018
16176
|
const telemetry = new AgentTelemetryRuntime(createAgentTelemetry(resolveAgentTelemetryConfig()));
|
|
16019
16177
|
telemetry.event("agent.started");
|
|
16020
16178
|
telemetry.setDraining(false);
|
|
16021
|
-
const attestationSource =
|
|
16179
|
+
const attestationSource = existsSync21("/dev/sev-guest") ? createSnpAttestationSource() : undefined;
|
|
16022
16180
|
const running = runAgent({
|
|
16023
16181
|
socketPath: process.env.FZ_SOCKET_PATH ?? DEFAULT_SOCKET_PATH,
|
|
16024
16182
|
seedCredential: process.env.FZ_SEED_CREDENTIAL,
|
|
@@ -16033,8 +16191,8 @@ if (import.meta.main) {
|
|
|
16033
16191
|
const enrolmentStatePath = process.env.FZ_ENROL_STATE_FILE ?? DEFAULT_ENROLMENT_STATE_PATH;
|
|
16034
16192
|
let binding = loadGuestBinding(enrolmentStatePath, nodeKey);
|
|
16035
16193
|
const enrolmentCredential = process.env.FZ_ENROL_TOKEN_CREDENTIAL;
|
|
16036
|
-
const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY &&
|
|
16037
|
-
const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE &&
|
|
16194
|
+
const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY && existsSync21(`${process.env.CREDENTIALS_DIRECTORY}/${enrolmentCredential}`));
|
|
16195
|
+
const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE && existsSync21(process.env.FZ_ENROL_TOKEN_FILE));
|
|
16038
16196
|
if (!binding && (enrolmentCredentialAvailable || legacyEnrolmentFileAvailable) && process.env.FZ_API) {
|
|
16039
16197
|
binding = await enrolGuestIdentity({
|
|
16040
16198
|
apiUrl: process.env.FZ_API,
|
|
@@ -16045,8 +16203,8 @@ if (import.meta.main) {
|
|
|
16045
16203
|
keys,
|
|
16046
16204
|
label: process.env.FZ_NODE_LABEL,
|
|
16047
16205
|
edgeHostname: process.env.FZ_NODE_HOSTNAME,
|
|
16048
|
-
gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ?
|
|
16049
|
-
bootstrapSshPublicKey: process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE ?
|
|
16206
|
+
gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_GIT_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
|
|
16207
|
+
bootstrapSshPublicKey: process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
|
|
16050
16208
|
privateNetworkAttachment: privateNetworkAttachmentFromEnvironment()
|
|
16051
16209
|
});
|
|
16052
16210
|
console.log(`[agent] enrolled ${binding.computeReference} in project ${binding.projectKey}/${binding.environmentKey}`);
|
|
@@ -16112,7 +16270,7 @@ if (import.meta.main) {
|
|
|
16112
16270
|
const bootstrapEnabled = process.env.FZ_BOOTSTRAP_PULL === "true";
|
|
16113
16271
|
const bootstrapCredential = process.env.FZ_BOOTSTRAP_SSH_KEY_CREDENTIAL;
|
|
16114
16272
|
const bootstrapKeyPath = bootstrapCredential && process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/${bootstrapCredential}` : undefined;
|
|
16115
|
-
if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !
|
|
16273
|
+
if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !existsSync21(bootstrapKeyPath) || !process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT)) {
|
|
16116
16274
|
throw new Error("agent: bootstrap runner requires enrolment, API, local SSH credential and target OTLP coordinate");
|
|
16117
16275
|
}
|
|
16118
16276
|
const bootstrapPull = bootstrapEnabled ? startSshBootstrapPull({
|
|
@@ -16149,12 +16307,21 @@ if (import.meta.main) {
|
|
|
16149
16307
|
const repository = process.env.FZ_DEPLOY_REPO;
|
|
16150
16308
|
const branch = process.env.FZ_DEPLOY_BRANCH;
|
|
16151
16309
|
const profile = process.env.FZ_DEPLOY_PROFILE;
|
|
16310
|
+
const bootstrapBundlePath = process.env.FZ_BOOTSTRAP_BUNDLE;
|
|
16311
|
+
const bootstrapBundleManifestPath = process.env.FZ_BOOTSTRAP_BUNDLE_MANIFEST;
|
|
16312
|
+
if (Boolean(bootstrapBundlePath) !== Boolean(bootstrapBundleManifestPath)) {
|
|
16313
|
+
throw new Error("agent: bootstrap bundle and manifest coordinates must be supplied together");
|
|
16314
|
+
}
|
|
16315
|
+
const bootstrapBundle = bootstrapBundlePath && bootstrapBundleManifestPath ? {
|
|
16316
|
+
path: bootstrapBundlePath,
|
|
16317
|
+
manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync20(bootstrapBundleManifestPath, "utf8")))
|
|
16318
|
+
} : undefined;
|
|
16152
16319
|
const root = process.env.FZ_DEPLOY_ROOT;
|
|
16153
16320
|
const pullEnabled = process.env.FZ_DEPLOY_PULL === "true" && Boolean(process.env.FZ_API);
|
|
16154
16321
|
if (pullEnabled && !binding) {
|
|
16155
16322
|
throw new Error("agent: outbound guest deployment requires a persisted tenant enrolment binding");
|
|
16156
16323
|
}
|
|
16157
|
-
if (root && (repository && branch && profile || pullEnabled)) {
|
|
16324
|
+
if (root && (repository && branch && profile || bootstrapBundle && branch && profile || pullEnabled)) {
|
|
16158
16325
|
const managers = new Map;
|
|
16159
16326
|
const managerOptions = (source, key) => ({
|
|
16160
16327
|
key,
|
|
@@ -16181,7 +16348,10 @@ if (import.meta.main) {
|
|
|
16181
16348
|
activateContainer: (request) => requestContainerActivation(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
|
|
16182
16349
|
projectExec: process.env.FZ_DEPLOY_RUNNER_SOCKET ? (input) => requestDeploymentCommand(input, process.env.FZ_DEPLOY_RUNNER_SOCKET) : undefined
|
|
16183
16350
|
});
|
|
16184
|
-
const staticManager = repository && branch && profile ? createDeploymentManager(managerOptions({ repository, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `${repository}:${branch}:${profile}`)) :
|
|
16351
|
+
const staticManager = repository && branch && profile ? createDeploymentManager(managerOptions({ repository, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `${repository}:${branch}:${profile}`)) : bootstrapBundle && branch && profile ? createDeploymentManager({
|
|
16352
|
+
...managerOptions({ repository: bootstrapBundle.path, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `bootstrap:${bootstrapBundle.manifest.sha256}:${profile}`),
|
|
16353
|
+
bootstrapBundle
|
|
16354
|
+
}) : undefined;
|
|
16185
16355
|
if (staticManager)
|
|
16186
16356
|
managers.set("__static__", staticManager);
|
|
16187
16357
|
const control = staticManager ? startControlServer(staticManager, process.env.FZ_CONTROL_SOCKET ?? DEFAULT_CONTROL_SOCKET) : undefined;
|
|
@@ -16196,6 +16366,9 @@ if (import.meta.main) {
|
|
|
16196
16366
|
canClaim: () => deploymentIntake?.state === "running",
|
|
16197
16367
|
manager: staticManager,
|
|
16198
16368
|
managerFor: staticManager ? undefined : (claim) => {
|
|
16369
|
+
if (claim.source.auth?.kind === "ephemeral-token") {
|
|
16370
|
+
return createDeploymentManager(managerOptions(claim.source, claim.pipelineKey));
|
|
16371
|
+
}
|
|
16199
16372
|
const cacheKey = [
|
|
16200
16373
|
claim.pipelineKey,
|
|
16201
16374
|
claim.source.repository,
|