@forgezero/agent 0.1.61 → 0.1.63
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 +38 -13
- 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 +7 -17
- package/dist/bootstrap.js +407 -214
- 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 +16 -0
- package/dist/fz-agent.js +486 -363
- package/dist/fz.js +499 -305
- 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 +1 -1
- package/dist/operator-bootstrap.js +509 -307
- package/dist/platform-bootstrap-runtime.d.ts +0 -2
- package/dist/platform-bootstrap-runtime.js +0 -4
- package/dist/platform-fleet-verification.js +110 -44
- package/dist/provision.d.ts +4 -1
- package/dist/provision.js +15 -7
- 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)) {
|
|
@@ -7498,7 +7587,7 @@ function createDeploymentManager(options) {
|
|
|
7498
7587
|
const component2 = plan.spec.components[String(resolved.component ?? "")];
|
|
7499
7588
|
const mounts = component2?.kind === "database" ? [component2.storage] : component2?.storage ?? [];
|
|
7500
7589
|
for (const mount of mounts) {
|
|
7501
|
-
const candidate =
|
|
7590
|
+
const candidate = existsSync4(mount.path) ? mount.path : dirname(mount.path);
|
|
7502
7591
|
const stats = statfsSync(candidate);
|
|
7503
7592
|
const freePercent = Number(stats.bavail) / Number(stats.blocks) * 100;
|
|
7504
7593
|
const minimum = mount.class === "ephemeral" ? 0 : mount.minimumFreePercent ?? 0;
|
|
@@ -7706,6 +7795,8 @@ function createDeploymentManager(options) {
|
|
|
7706
7795
|
};
|
|
7707
7796
|
return {
|
|
7708
7797
|
async latestRevision() {
|
|
7798
|
+
if (bootstrapBundle)
|
|
7799
|
+
return bootstrapBundle.manifest.revision;
|
|
7709
7800
|
const result = await checked({
|
|
7710
7801
|
argv: ["git", "ls-remote", "--exit-code", options.repository, `refs/heads/${options.branch}`],
|
|
7711
7802
|
env: await gitEnvironment()
|
|
@@ -7750,7 +7841,7 @@ function createDeploymentManager(options) {
|
|
|
7750
7841
|
}
|
|
7751
7842
|
|
|
7752
7843
|
// src/control.ts
|
|
7753
|
-
import { chmodSync as
|
|
7844
|
+
import { chmodSync as chmodSync5, existsSync as existsSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
7754
7845
|
import { connect, createServer as createServer2 } from "net";
|
|
7755
7846
|
var DEFAULT_CONTROL_SOCKET = "/run/forgezero/control.sock";
|
|
7756
7847
|
var MAX_REQUEST_BYTES = 16 * 1024;
|
|
@@ -7807,7 +7898,7 @@ async function handleControl(manager, request) {
|
|
|
7807
7898
|
}
|
|
7808
7899
|
}
|
|
7809
7900
|
function startControlServer(manager, socketPath = DEFAULT_CONTROL_SOCKET) {
|
|
7810
|
-
if (
|
|
7901
|
+
if (existsSync5(socketPath))
|
|
7811
7902
|
unlinkSync3(socketPath);
|
|
7812
7903
|
const server = createServer2((socket) => {
|
|
7813
7904
|
let buffer = "";
|
|
@@ -7829,7 +7920,7 @@ function startControlServer(manager, socketPath = DEFAULT_CONTROL_SOCKET) {
|
|
|
7829
7920
|
});
|
|
7830
7921
|
socket.on("error", () => socket.destroy());
|
|
7831
7922
|
});
|
|
7832
|
-
server.listen(socketPath, () =>
|
|
7923
|
+
server.listen(socketPath, () => chmodSync5(socketPath, 384));
|
|
7833
7924
|
return server;
|
|
7834
7925
|
}
|
|
7835
7926
|
function requestControl(request, socketPath = DEFAULT_CONTROL_SOCKET) {
|
|
@@ -7868,6 +7959,7 @@ async function postSigned(options, operation, body) {
|
|
|
7868
7959
|
}
|
|
7869
7960
|
async function deployClaim(options, claim) {
|
|
7870
7961
|
const manager = options.manager ?? options.managerFor?.(claim);
|
|
7962
|
+
const claimOwnedManager = !options.manager && claim.source.auth?.kind === "ephemeral-token";
|
|
7871
7963
|
if (!manager)
|
|
7872
7964
|
throw new Error("deployment pull has no manager for this claim");
|
|
7873
7965
|
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
@@ -7918,6 +8010,8 @@ async function deployClaim(options, claim) {
|
|
|
7918
8010
|
stopped = true;
|
|
7919
8011
|
clearTimer(timer);
|
|
7920
8012
|
await renewal;
|
|
8013
|
+
if (claimOwnedManager)
|
|
8014
|
+
await manager.stop();
|
|
7921
8015
|
}
|
|
7922
8016
|
if (claimLost)
|
|
7923
8017
|
throw claimLost;
|
|
@@ -8026,14 +8120,14 @@ function startDeploymentPull(options) {
|
|
|
8026
8120
|
|
|
8027
8121
|
// src/guest-enrolment.ts
|
|
8028
8122
|
import {
|
|
8029
|
-
chmodSync as
|
|
8030
|
-
existsSync as
|
|
8031
|
-
mkdirSync as
|
|
8032
|
-
readFileSync as
|
|
8033
|
-
renameSync as
|
|
8123
|
+
chmodSync as chmodSync6,
|
|
8124
|
+
existsSync as existsSync6,
|
|
8125
|
+
mkdirSync as mkdirSync4,
|
|
8126
|
+
readFileSync as readFileSync4,
|
|
8127
|
+
renameSync as renameSync4,
|
|
8034
8128
|
statSync,
|
|
8035
8129
|
unlinkSync as unlinkSync4,
|
|
8036
|
-
writeFileSync as
|
|
8130
|
+
writeFileSync as writeFileSync4
|
|
8037
8131
|
} from "fs";
|
|
8038
8132
|
import { dirname as dirname2 } from "path";
|
|
8039
8133
|
function privateNetworkAttachmentFromEnvironment(env = process.env) {
|
|
@@ -8063,7 +8157,7 @@ var validBinding = (value, expectedNodeKey) => {
|
|
|
8063
8157
|
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
8158
|
};
|
|
8065
8159
|
function loadGuestBinding(path2, expectedNodeKey) {
|
|
8066
|
-
if (!
|
|
8160
|
+
if (!existsSync6(path2))
|
|
8067
8161
|
return null;
|
|
8068
8162
|
const mode = statSync(path2).mode & 511;
|
|
8069
8163
|
if ((mode & 63) !== 0) {
|
|
@@ -8071,7 +8165,7 @@ function loadGuestBinding(path2, expectedNodeKey) {
|
|
|
8071
8165
|
}
|
|
8072
8166
|
let parsed;
|
|
8073
8167
|
try {
|
|
8074
|
-
parsed = JSON.parse(
|
|
8168
|
+
parsed = JSON.parse(readFileSync4(path2, "utf8"));
|
|
8075
8169
|
} catch {
|
|
8076
8170
|
throw new Error(`guest enrolment state at ${path2} is malformed`);
|
|
8077
8171
|
}
|
|
@@ -8081,15 +8175,15 @@ function loadGuestBinding(path2, expectedNodeKey) {
|
|
|
8081
8175
|
return parsed;
|
|
8082
8176
|
}
|
|
8083
8177
|
function persistGuestBinding(path2, binding) {
|
|
8084
|
-
|
|
8178
|
+
mkdirSync4(dirname2(path2), { recursive: true, mode: 448 });
|
|
8085
8179
|
const temporary = `${path2}.next`;
|
|
8086
|
-
|
|
8180
|
+
writeFileSync4(temporary, `${JSON.stringify(binding)}
|
|
8087
8181
|
`, { mode: 384 });
|
|
8088
|
-
|
|
8089
|
-
|
|
8182
|
+
chmodSync6(temporary, 384);
|
|
8183
|
+
renameSync4(temporary, path2);
|
|
8090
8184
|
}
|
|
8091
8185
|
async function enrolGuestIdentity(options) {
|
|
8092
|
-
const token = options.token?.trim() ?? (options.tokenPath ?
|
|
8186
|
+
const token = options.token?.trim() ?? (options.tokenPath ? readFileSync4(options.tokenPath, "utf8").trim() : "");
|
|
8093
8187
|
if (!token.startsWith("fze_"))
|
|
8094
8188
|
throw new Error("guest enrolment credential is malformed");
|
|
8095
8189
|
const payload = await postSignedNode({
|
|
@@ -8426,8 +8520,8 @@ function startMigrationPull(options) {
|
|
|
8426
8520
|
}
|
|
8427
8521
|
|
|
8428
8522
|
// src/ssh-bootstrap.ts
|
|
8429
|
-
import { createHash as
|
|
8430
|
-
import { chmodSync as
|
|
8523
|
+
import { createHash as createHash6 } from "crypto";
|
|
8524
|
+
import { chmodSync as chmodSync7, mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
8431
8525
|
import { tmpdir as tmpdir2 } from "os";
|
|
8432
8526
|
import { join as join3 } from "path";
|
|
8433
8527
|
import { fileURLToPath } from "url";
|
|
@@ -8448,7 +8542,7 @@ function assertSupportedGuestImage(imageKey) {
|
|
|
8448
8542
|
}
|
|
8449
8543
|
|
|
8450
8544
|
// src/version.ts
|
|
8451
|
-
var VERSION2 = "0.1.
|
|
8545
|
+
var VERSION2 = "0.1.63";
|
|
8452
8546
|
|
|
8453
8547
|
// src/ssh-bootstrap.ts
|
|
8454
8548
|
class SshBootstrapError extends Error {
|
|
@@ -8566,7 +8660,7 @@ async function pinnedKnownHost(claim, pinned, directory, exec) {
|
|
|
8566
8660
|
if (!/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}$/.test(key))
|
|
8567
8661
|
throw new SshBootstrapError("HOST_KEY_INVALID");
|
|
8568
8662
|
const path2 = join3(directory, "known_hosts");
|
|
8569
|
-
|
|
8663
|
+
writeFileSync5(path2, `${pinned.hostKeyAlias} ${key}
|
|
8570
8664
|
`, { mode: 384, flag: "wx" });
|
|
8571
8665
|
return path2;
|
|
8572
8666
|
}
|
|
@@ -8576,11 +8670,11 @@ var verifiedBunArchive = async (directory) => {
|
|
|
8576
8670
|
if (!response.ok)
|
|
8577
8671
|
throw new SshBootstrapError("BUN_DOWNLOAD_FAILED");
|
|
8578
8672
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
8579
|
-
if (
|
|
8673
|
+
if (createHash6("sha256").update(bytes).digest("hex") !== BUN_RELEASE_SHA256) {
|
|
8580
8674
|
throw new SshBootstrapError("BUN_CHECKSUM_MISMATCH");
|
|
8581
8675
|
}
|
|
8582
8676
|
const archive = join3(directory, "bun.zip");
|
|
8583
|
-
|
|
8677
|
+
writeFileSync5(archive, bytes, { mode: 384, flag: "wx" });
|
|
8584
8678
|
return archive;
|
|
8585
8679
|
};
|
|
8586
8680
|
var remote = (exec, ssh, destination, code, argv2, secret = false, stdin) => checked(exec, ["ssh", ...ssh, destination, "--", ...argv2], code, { secret, ...stdin !== undefined ? { stdin } : {} });
|
|
@@ -8596,14 +8690,14 @@ async function executeSshBootstrap(claim, options) {
|
|
|
8596
8690
|
throw new SshBootstrapError("TARGET_API_INVALID");
|
|
8597
8691
|
const exec = options.exec ?? defaultExec;
|
|
8598
8692
|
const directory = mkdtempSync2(join3(tmpdir2(), "forgezero-ssh-bootstrap-"));
|
|
8599
|
-
|
|
8693
|
+
chmodSync7(directory, 448);
|
|
8600
8694
|
let sshKeyPath = options.sshKeyPath;
|
|
8601
8695
|
let remoteDirectory = "";
|
|
8602
8696
|
let pinned;
|
|
8603
8697
|
try {
|
|
8604
8698
|
if (claim.target.credential.source === "vault") {
|
|
8605
8699
|
sshKeyPath = join3(directory, "identity");
|
|
8606
|
-
|
|
8700
|
+
writeFileSync5(sshKeyPath, `${claim.target.credential.privateKey.trim()}
|
|
8607
8701
|
`, { mode: 384, flag: "wx" });
|
|
8608
8702
|
}
|
|
8609
8703
|
const sourceHost = claim.target.host.includes(":") ? `[${claim.target.host}]` : claim.target.host;
|
|
@@ -8630,11 +8724,11 @@ async function executeSshBootstrap(claim, options) {
|
|
|
8630
8724
|
`
|
|
8631
8725
|
};
|
|
8632
8726
|
for (const [name, content] of Object.entries(files))
|
|
8633
|
-
|
|
8727
|
+
writeFileSync5(join3(directory, name), content, { mode: 384, flag: "wx" });
|
|
8634
8728
|
const fzCliPath = options.fzCliPath ?? fileURLToPath(new URL("./fz.js", import.meta.url));
|
|
8635
8729
|
const fzAgentPath = options.fzAgentPath ?? fileURLToPath(new URL("./fz-agent.js", import.meta.url));
|
|
8636
8730
|
for (const [source, name] of [[fzCliPath, "fz.js"], [fzAgentPath, "fz-agent.js"]]) {
|
|
8637
|
-
if (!
|
|
8731
|
+
if (!readFileSync5(source).length)
|
|
8638
8732
|
throw new SshBootstrapError("PACKAGE_ARTIFACT_MISSING");
|
|
8639
8733
|
await checked(exec, [
|
|
8640
8734
|
"scp",
|
|
@@ -8798,7 +8892,7 @@ async function executeSshBootstrap(claim, options) {
|
|
|
8798
8892
|
]);
|
|
8799
8893
|
} catch {}
|
|
8800
8894
|
}
|
|
8801
|
-
|
|
8895
|
+
rmSync3(directory, { recursive: true, force: true });
|
|
8802
8896
|
}
|
|
8803
8897
|
}
|
|
8804
8898
|
var post3 = (options, operation, body) => postSignedNode(options, `v1/node/bootstrap/${operation}`, body);
|
|
@@ -8908,14 +9002,14 @@ async function executeMetalAdmission(claim, options) {
|
|
|
8908
9002
|
}
|
|
8909
9003
|
const exec = options.exec ?? defaultExec;
|
|
8910
9004
|
const directory = mkdtempSync2(join3(tmpdir2(), "forgezero-metal-admission-"));
|
|
8911
|
-
|
|
9005
|
+
chmodSync7(directory, 448);
|
|
8912
9006
|
let sshKeyPath = options.sshKeyPath;
|
|
8913
9007
|
let remoteDirectory = "";
|
|
8914
9008
|
let pinned;
|
|
8915
9009
|
try {
|
|
8916
9010
|
if (claim.target.credential.source === "vault") {
|
|
8917
9011
|
sshKeyPath = join3(directory, "identity");
|
|
8918
|
-
|
|
9012
|
+
writeFileSync5(sshKeyPath, `${claim.target.credential.privateKey.trim()}
|
|
8919
9013
|
`, { mode: 384, flag: "wx" });
|
|
8920
9014
|
}
|
|
8921
9015
|
const sourceHost = claim.target.host.includes(":") ? `[${claim.target.host}]` : claim.target.host;
|
|
@@ -8932,7 +9026,7 @@ async function executeMetalAdmission(claim, options) {
|
|
|
8932
9026
|
const fzCliPath = options.fzCliPath ?? fileURLToPath(new URL("./fz.js", import.meta.url));
|
|
8933
9027
|
const fzAgentPath = options.fzAgentPath ?? fileURLToPath(new URL("./fz-agent.js", import.meta.url));
|
|
8934
9028
|
for (const [source, name] of [[fzCliPath, "fz.js"], [fzAgentPath, "fz-agent.js"]]) {
|
|
8935
|
-
if (!
|
|
9029
|
+
if (!readFileSync5(source).length)
|
|
8936
9030
|
throw new SshBootstrapError("PACKAGE_ARTIFACT_MISSING");
|
|
8937
9031
|
await checked(exec, [
|
|
8938
9032
|
"scp",
|
|
@@ -8963,7 +9057,7 @@ async function executeMetalAdmission(claim, options) {
|
|
|
8963
9057
|
}
|
|
8964
9058
|
};
|
|
8965
9059
|
const configPath = join3(directory, "metal.json");
|
|
8966
|
-
|
|
9060
|
+
writeFileSync5(configPath, `${JSON.stringify(config, null, 2)}
|
|
8967
9061
|
`, { mode: 384, flag: "wx" });
|
|
8968
9062
|
await checked(exec, [
|
|
8969
9063
|
"scp",
|
|
@@ -9065,7 +9159,7 @@ async function executeMetalAdmission(claim, options) {
|
|
|
9065
9159
|
]);
|
|
9066
9160
|
} catch {}
|
|
9067
9161
|
}
|
|
9068
|
-
|
|
9162
|
+
rmSync3(directory, { recursive: true, force: true });
|
|
9069
9163
|
}
|
|
9070
9164
|
}
|
|
9071
9165
|
async function pullMetalAdmissionOnce(options) {
|
|
@@ -9150,20 +9244,20 @@ function startMetalAdmissionPull(options) {
|
|
|
9150
9244
|
}
|
|
9151
9245
|
|
|
9152
9246
|
// src/metal-helper-socket.ts
|
|
9153
|
-
import { chmodSync as
|
|
9247
|
+
import { chmodSync as chmodSync8, existsSync as existsSync8, unlinkSync as unlinkSync6 } from "fs";
|
|
9154
9248
|
import { connect as connect2, createServer as createServer3 } from "net";
|
|
9155
9249
|
|
|
9156
9250
|
// src/metal-provision.ts
|
|
9157
|
-
import { createHash as
|
|
9251
|
+
import { createHash as createHash7 } from "crypto";
|
|
9158
9252
|
import {
|
|
9159
|
-
existsSync as
|
|
9160
|
-
mkdirSync as
|
|
9161
|
-
readFileSync as
|
|
9253
|
+
existsSync as existsSync7,
|
|
9254
|
+
mkdirSync as mkdirSync5,
|
|
9255
|
+
readFileSync as readFileSync6,
|
|
9162
9256
|
readdirSync,
|
|
9163
|
-
rmSync as
|
|
9257
|
+
rmSync as rmSync4,
|
|
9164
9258
|
statSync as statSync2,
|
|
9165
9259
|
unlinkSync as unlinkSync5,
|
|
9166
|
-
writeFileSync as
|
|
9260
|
+
writeFileSync as writeFileSync6
|
|
9167
9261
|
} from "fs";
|
|
9168
9262
|
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4 } from "path";
|
|
9169
9263
|
import { isIP as isIP2 } from "net";
|
|
@@ -9293,8 +9387,8 @@ function membersOfLinuxList(value, label) {
|
|
|
9293
9387
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
9294
9388
|
return members;
|
|
9295
9389
|
}
|
|
9296
|
-
var guestNameFor = (computeKey) => `fzg-${
|
|
9297
|
-
var tapNameFor = (computeKey) => `fzt${
|
|
9390
|
+
var guestNameFor = (computeKey) => `fzg-${createHash7("sha256").update(computeKey).digest("hex").slice(0, 16)}`;
|
|
9391
|
+
var tapNameFor = (computeKey) => `fzt${createHash7("sha256").update(computeKey).digest("hex").slice(0, 12)}`;
|
|
9298
9392
|
var macForAddress = (address) => {
|
|
9299
9393
|
const octets = address.split(".").map(Number);
|
|
9300
9394
|
if (octets.length !== 4 || octets.some((value) => !Number.isInteger(value) || value < 0 || value > 255)) {
|
|
@@ -9374,9 +9468,9 @@ function validateMetalProfile(profile) {
|
|
|
9374
9468
|
}
|
|
9375
9469
|
}
|
|
9376
9470
|
var readManifests = (stateDir) => {
|
|
9377
|
-
if (!
|
|
9471
|
+
if (!existsSync7(stateDir))
|
|
9378
9472
|
return [];
|
|
9379
|
-
return readdirSync(stateDir).filter((name) => name.endsWith(".json")).map((name) => JSON.parse(
|
|
9473
|
+
return readdirSync(stateDir).filter((name) => name.endsWith(".json")).map((name) => JSON.parse(readFileSync6(join4(stateDir, name), "utf8")));
|
|
9380
9474
|
};
|
|
9381
9475
|
function allocateAddress(profile, computeKey, rows) {
|
|
9382
9476
|
const existing = rows.find((row2) => row2.computeKey === computeKey);
|
|
@@ -9384,7 +9478,7 @@ function allocateAddress(profile, computeKey, rows) {
|
|
|
9384
9478
|
return existing.address;
|
|
9385
9479
|
const used = new Set(rows.map((row2) => row2.address));
|
|
9386
9480
|
const width = profile.addressEnd - profile.addressStart + 1;
|
|
9387
|
-
const start =
|
|
9481
|
+
const start = createHash7("sha256").update(computeKey).digest().readUInt16BE(0) % width;
|
|
9388
9482
|
for (let offset = 0;offset < width; offset += 1) {
|
|
9389
9483
|
const last = profile.addressStart + (start + offset) % width;
|
|
9390
9484
|
const address = `${profile.subnetPrefix}.${last}`;
|
|
@@ -9480,8 +9574,6 @@ function guestBootstrapOperations(profile, attested = Boolean(profile.confidenti
|
|
|
9480
9574
|
"FZ_AGENT_USER=forgezero-agent",
|
|
9481
9575
|
"FZ_SOCKET_PATH=/run/forgezero/vault.sock",
|
|
9482
9576
|
"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
9577
|
"FZ_DEPLOY_ROOT=/opt/forgezero",
|
|
9486
9578
|
`FZ_DEPLOY_PULL=${hasEnrolment}`,
|
|
9487
9579
|
`FZ_AGENT_EGRESS_ENFORCE=${hasEnrolment}`,
|
|
@@ -9561,7 +9653,7 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
9561
9653
|
if (digest !== image.sha256)
|
|
9562
9654
|
throw new MetalProvisionError("configured image checksum mismatch");
|
|
9563
9655
|
for (const path2 of [profile.stateDir, profile.seedDir, profile.unitDir])
|
|
9564
|
-
|
|
9656
|
+
mkdirSync5(path2, { recursive: true, mode: 448 });
|
|
9565
9657
|
const manifests = readManifests(profile.stateDir);
|
|
9566
9658
|
if (claim.access) {
|
|
9567
9659
|
if (!/^[a-z_][a-z0-9_-]{0,31}$/.test(claim.access.sshUser)) {
|
|
@@ -9591,7 +9683,7 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
9591
9683
|
allowedMemoryNodes: cpuPool.memoryNodes,
|
|
9592
9684
|
phase: "allocating"
|
|
9593
9685
|
};
|
|
9594
|
-
const save = () =>
|
|
9686
|
+
const save = () => writeFileSync6(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
9595
9687
|
`, { mode: 384 });
|
|
9596
9688
|
save();
|
|
9597
9689
|
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
@@ -9605,9 +9697,9 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
9605
9697
|
}
|
|
9606
9698
|
const seedBase = join4(profile.seedDir, name);
|
|
9607
9699
|
const init = cloudInit(profile, claim, manifest);
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9700
|
+
writeFileSync6(`${seedBase}-user-data`, init.userData, { mode: 384 });
|
|
9701
|
+
writeFileSync6(`${seedBase}-meta-data`, init.metaData, { mode: 384 });
|
|
9702
|
+
writeFileSync6(`${seedBase}-network-config`, init.networkConfig, { mode: 384 });
|
|
9611
9703
|
const seed = `${seedBase}-seed.iso`;
|
|
9612
9704
|
await checked2(exec, [
|
|
9613
9705
|
"cloud-localds",
|
|
@@ -9640,8 +9732,8 @@ async function provisionMetalGuest(profile, claim, exec) {
|
|
|
9640
9732
|
}
|
|
9641
9733
|
const service = `forgezero-guest@${name}.service`;
|
|
9642
9734
|
const unitPath = join4(profile.unitDir, service);
|
|
9643
|
-
|
|
9644
|
-
|
|
9735
|
+
mkdirSync5(dirname3(unitPath), { recursive: true });
|
|
9736
|
+
writeFileSync6(unitPath, guestUnit(spec), { mode: 420 });
|
|
9645
9737
|
await checked2(exec, ["systemctl", "daemon-reload"]);
|
|
9646
9738
|
if (prior?.phase === "running") {
|
|
9647
9739
|
await checked2(exec, ["systemctl", "restart", service]);
|
|
@@ -9658,10 +9750,10 @@ function legacyPlatformManifest(profile, claim, name) {
|
|
|
9658
9750
|
return;
|
|
9659
9751
|
const legacyDir = profile.legacyStateDir ?? "/etc/forgezero/guests";
|
|
9660
9752
|
const legacyPath = join4(legacyDir, `${name}.conf`);
|
|
9661
|
-
if (!
|
|
9753
|
+
if (!existsSync7(legacyPath))
|
|
9662
9754
|
return;
|
|
9663
9755
|
const values = new Map;
|
|
9664
|
-
for (const line of
|
|
9756
|
+
for (const line of readFileSync6(legacyPath, "utf8").split(/\r?\n/)) {
|
|
9665
9757
|
if (!line || line.startsWith("#"))
|
|
9666
9758
|
continue;
|
|
9667
9759
|
const match = /^([A-Z][A-Z0-9_]*)=([^\s'"`$;|&<>]+)$/.exec(line);
|
|
@@ -9695,7 +9787,7 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
9695
9787
|
const service = `forgezero-guest@${name}.service`;
|
|
9696
9788
|
const unitPath = join4(profile.unitDir, service);
|
|
9697
9789
|
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
9698
|
-
const manifest =
|
|
9790
|
+
const manifest = existsSync7(manifestPath) ? JSON.parse(readFileSync6(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
|
|
9699
9791
|
if (!manifest) {
|
|
9700
9792
|
const seedBase = join4(profile.seedDir, name);
|
|
9701
9793
|
const localArtifacts = [
|
|
@@ -9705,7 +9797,7 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
9705
9797
|
`${seedBase}-meta-data`,
|
|
9706
9798
|
`${seedBase}-network-config`
|
|
9707
9799
|
];
|
|
9708
|
-
if (localArtifacts.some(
|
|
9800
|
+
if (localArtifacts.some(existsSync7)) {
|
|
9709
9801
|
throw new MetalProvisionError("guest artifacts exist without owned inventory");
|
|
9710
9802
|
}
|
|
9711
9803
|
const [volume, active] = await Promise.all([
|
|
@@ -9722,7 +9814,7 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
9722
9814
|
if (!currentIdentity && !legacyPlatformIdentity) {
|
|
9723
9815
|
throw new MetalProvisionError("compute identity conflicts with host inventory");
|
|
9724
9816
|
}
|
|
9725
|
-
if (
|
|
9817
|
+
if (existsSync7(unitPath))
|
|
9726
9818
|
await checked2(exec, ["systemctl", "disable", "--now", service]);
|
|
9727
9819
|
else if (legacyPlatformIdentity)
|
|
9728
9820
|
await checked2(exec, ["systemctl", "disable", "--now", service]);
|
|
@@ -9739,15 +9831,15 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
9739
9831
|
join4(profile.seedDir, `${name}-network-config`),
|
|
9740
9832
|
manifestPath
|
|
9741
9833
|
])
|
|
9742
|
-
if (
|
|
9834
|
+
if (existsSync7(path2))
|
|
9743
9835
|
unlinkSync5(path2);
|
|
9744
9836
|
if (legacyPlatformIdentity) {
|
|
9745
9837
|
const legacyConfig = join4(profile.legacyStateDir ?? "/etc/forgezero/guests", `${name}.conf`);
|
|
9746
9838
|
const legacyDropIn = join4(profile.unitDir, `${service}.d`);
|
|
9747
|
-
if (
|
|
9839
|
+
if (existsSync7(legacyConfig))
|
|
9748
9840
|
unlinkSync5(legacyConfig);
|
|
9749
|
-
if (
|
|
9750
|
-
|
|
9841
|
+
if (existsSync7(legacyDropIn))
|
|
9842
|
+
rmSync4(legacyDropIn, { recursive: true });
|
|
9751
9843
|
}
|
|
9752
9844
|
await checked2(exec, ["systemctl", "daemon-reload"]);
|
|
9753
9845
|
return {};
|
|
@@ -9785,7 +9877,7 @@ command exceeded ${COMMAND_TIMEOUT_MS}ms`.trim() : stderr
|
|
|
9785
9877
|
function startMetalHelper(options) {
|
|
9786
9878
|
validateMetalProfile(options.profile);
|
|
9787
9879
|
const socketPath = options.socketPath ?? DEFAULT_METAL_HELPER_SOCKET;
|
|
9788
|
-
if (
|
|
9880
|
+
if (existsSync8(socketPath))
|
|
9789
9881
|
unlinkSync6(socketPath);
|
|
9790
9882
|
let tail = Promise.resolve();
|
|
9791
9883
|
const server = createServer3((socket) => {
|
|
@@ -9832,7 +9924,7 @@ function startMetalHelper(options) {
|
|
|
9832
9924
|
});
|
|
9833
9925
|
socket.on("error", () => socket.destroy());
|
|
9834
9926
|
});
|
|
9835
|
-
server.listen(socketPath, () =>
|
|
9927
|
+
server.listen(socketPath, () => chmodSync8(socketPath, 432));
|
|
9836
9928
|
return {
|
|
9837
9929
|
server,
|
|
9838
9930
|
async stop() {
|
|
@@ -9872,7 +9964,7 @@ function requestMetalProvision(claim, socketPath = DEFAULT_METAL_HELPER_SOCKET)
|
|
|
9872
9964
|
}
|
|
9873
9965
|
|
|
9874
9966
|
// src/deployment-runner.ts
|
|
9875
|
-
import { chmodSync as
|
|
9967
|
+
import { chmodSync as chmodSync9, existsSync as existsSync9, realpathSync, unlinkSync as unlinkSync7 } from "fs";
|
|
9876
9968
|
import { isAbsolute as isAbsolute4, resolve, sep } from "path";
|
|
9877
9969
|
import { connect as connect3, createServer as createServer4 } from "net";
|
|
9878
9970
|
var DEFAULT_DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock";
|
|
@@ -9989,7 +10081,7 @@ function startDeploymentRunner(options) {
|
|
|
9989
10081
|
const root = resolve(options.root);
|
|
9990
10082
|
const home = resolve(options.home);
|
|
9991
10083
|
const socketPath = options.socketPath ?? DEFAULT_DEPLOYMENT_RUNNER_SOCKET;
|
|
9992
|
-
if (
|
|
10084
|
+
if (existsSync9(socketPath))
|
|
9993
10085
|
unlinkSync7(socketPath);
|
|
9994
10086
|
const active = new Set;
|
|
9995
10087
|
const server = createServer4((socket) => {
|
|
@@ -10034,7 +10126,7 @@ function startDeploymentRunner(options) {
|
|
|
10034
10126
|
const ready = new Promise((resolveReady, rejectReady) => {
|
|
10035
10127
|
server.once("error", rejectReady);
|
|
10036
10128
|
server.listen(socketPath, () => {
|
|
10037
|
-
|
|
10129
|
+
chmodSync9(socketPath, options.socketMode ?? 432);
|
|
10038
10130
|
server.off("error", rejectReady);
|
|
10039
10131
|
resolveReady();
|
|
10040
10132
|
});
|
|
@@ -10085,7 +10177,7 @@ function requestDeploymentCommand(input, socketPath = DEFAULT_DEPLOYMENT_RUNNER_
|
|
|
10085
10177
|
}
|
|
10086
10178
|
|
|
10087
10179
|
// src/snp-attestation.ts
|
|
10088
|
-
import { existsSync as
|
|
10180
|
+
import { existsSync as existsSync10 } from "fs";
|
|
10089
10181
|
|
|
10090
10182
|
// ../runtime/dist/snp.js
|
|
10091
10183
|
var REPORT_BYTES = 1184;
|
|
@@ -10178,7 +10270,7 @@ function createSnpAttestationSource(options = {}) {
|
|
|
10178
10270
|
const python = options.python ?? "python3";
|
|
10179
10271
|
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
10180
10272
|
const spawn = options.spawn ?? Bun.spawn;
|
|
10181
|
-
const exists = options.exists ??
|
|
10273
|
+
const exists = options.exists ?? existsSync10;
|
|
10182
10274
|
return {
|
|
10183
10275
|
name: "linux-sev-guest",
|
|
10184
10276
|
async report(nonce) {
|
|
@@ -10264,7 +10356,7 @@ function startNodeAttestation(options) {
|
|
|
10264
10356
|
}
|
|
10265
10357
|
|
|
10266
10358
|
// src/metal-isolation.ts
|
|
10267
|
-
import { mkdirSync as
|
|
10359
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
10268
10360
|
import { join as join5 } from "path";
|
|
10269
10361
|
var members = (list) => list.split(",").flatMap((part) => {
|
|
10270
10362
|
const [first, last = first] = part.split("-").map(Number);
|
|
@@ -10342,16 +10434,16 @@ async function applyMetalIsolation(profile, exec = defaultExec2) {
|
|
|
10342
10434
|
validateMetalProfile(profile);
|
|
10343
10435
|
await requireGuestsInSlice(exec);
|
|
10344
10436
|
const unitDir = profile.unitDir;
|
|
10345
|
-
|
|
10346
|
-
|
|
10437
|
+
mkdirSync6(unitDir, { recursive: true });
|
|
10438
|
+
writeFileSync7(join5(unitDir, "forgezero-guests.slice"), metalGuestSliceUnit(profile), { mode: 420 });
|
|
10347
10439
|
for (const unit of ["system.slice", "user.slice"]) {
|
|
10348
10440
|
const directory = join5(unitDir, `${unit}.d`);
|
|
10349
|
-
|
|
10350
|
-
|
|
10441
|
+
mkdirSync6(directory, { recursive: true });
|
|
10442
|
+
writeFileSync7(join5(directory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "slice"), { mode: 420 });
|
|
10351
10443
|
}
|
|
10352
10444
|
const initDirectory = join5(unitDir, "init.scope.d");
|
|
10353
|
-
|
|
10354
|
-
|
|
10445
|
+
mkdirSync6(initDirectory, { recursive: true });
|
|
10446
|
+
writeFileSync7(join5(initDirectory, "50-forgezero-housekeeping.conf"), metalHousekeepingDropIn(profile, "scope"), { mode: 420 });
|
|
10355
10447
|
await checked3(exec, ["systemctl", "daemon-reload"]);
|
|
10356
10448
|
await requireGuestsInSlice(exec);
|
|
10357
10449
|
const properties = [`AllowedCPUs=${profile.housekeepingCpus}`];
|
|
@@ -10386,7 +10478,7 @@ async function closeServerWithin(server, timeoutMs) {
|
|
|
10386
10478
|
}
|
|
10387
10479
|
|
|
10388
10480
|
// src/lifecycle-helper.ts
|
|
10389
|
-
import { chmodSync as
|
|
10481
|
+
import { chmodSync as chmodSync10, existsSync as existsSync11, readFileSync as readFileSync7, unlinkSync as unlinkSync8 } from "fs";
|
|
10390
10482
|
import { connect as connect4, createConnection, createServer as createServer5, isIP as isIP3 } from "net";
|
|
10391
10483
|
var DEFAULT_LIFECYCLE_HELPER_SOCKET = "/run/forgezero-lifecycle/helper.sock";
|
|
10392
10484
|
var MAX_REQUEST_BYTES4 = 16 * 1024;
|
|
@@ -10430,7 +10522,7 @@ function validateLifecycleProfile(profile) {
|
|
|
10430
10522
|
}
|
|
10431
10523
|
}
|
|
10432
10524
|
function loadLifecycleProfile(path2) {
|
|
10433
|
-
const profile = JSON.parse(
|
|
10525
|
+
const profile = JSON.parse(readFileSync7(path2, "utf8"));
|
|
10434
10526
|
validateLifecycleProfile(profile);
|
|
10435
10527
|
return profile;
|
|
10436
10528
|
}
|
|
@@ -10527,7 +10619,7 @@ async function executeLifecycleAction(profile, claim, exec = spawnLifecycleComma
|
|
|
10527
10619
|
function startLifecycleHelper(options) {
|
|
10528
10620
|
validateLifecycleProfile(options.profile);
|
|
10529
10621
|
const socketPath = options.socketPath ?? DEFAULT_LIFECYCLE_HELPER_SOCKET;
|
|
10530
|
-
if (
|
|
10622
|
+
if (existsSync11(socketPath))
|
|
10531
10623
|
unlinkSync8(socketPath);
|
|
10532
10624
|
let tail = Promise.resolve();
|
|
10533
10625
|
const server = createServer5((socket) => {
|
|
@@ -10568,7 +10660,7 @@ function startLifecycleHelper(options) {
|
|
|
10568
10660
|
});
|
|
10569
10661
|
socket.on("error", () => socket.destroy());
|
|
10570
10662
|
});
|
|
10571
|
-
server.listen(socketPath, () =>
|
|
10663
|
+
server.listen(socketPath, () => chmodSync10(socketPath, 432));
|
|
10572
10664
|
return {
|
|
10573
10665
|
server,
|
|
10574
10666
|
async stop() {
|
|
@@ -10608,7 +10700,7 @@ function requestLifecycleAction(claim, socketPath = DEFAULT_LIFECYCLE_HELPER_SOC
|
|
|
10608
10700
|
}
|
|
10609
10701
|
|
|
10610
10702
|
// src/warp-config.ts
|
|
10611
|
-
import { chmodSync as
|
|
10703
|
+
import { chmodSync as chmodSync11, mkdirSync as mkdirSync7, renameSync as renameSync5, symlinkSync as symlinkSync2, unlinkSync as unlinkSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
10612
10704
|
var xml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10613
10705
|
function renderWarpMdm(options) {
|
|
10614
10706
|
if (!/^[a-z0-9][a-z0-9-]{0,62}$/i.test(options.organization))
|
|
@@ -10629,12 +10721,12 @@ function renderWarpMdm(options) {
|
|
|
10629
10721
|
function materializeWarpMdm(options) {
|
|
10630
10722
|
const runtimePath = options.runtimePath ?? "/run/forgezero-warp/mdm.xml";
|
|
10631
10723
|
const servicePath = options.servicePath ?? "/var/lib/cloudflare-warp/mdm.xml";
|
|
10632
|
-
|
|
10633
|
-
|
|
10724
|
+
mkdirSync7(runtimePath.replace(/\/[^/]+$/, ""), { recursive: true, mode: 448 });
|
|
10725
|
+
mkdirSync7(servicePath.replace(/\/[^/]+$/, ""), { recursive: true, mode: 448 });
|
|
10634
10726
|
const next = `${runtimePath}.next`;
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10727
|
+
writeFileSync8(next, renderWarpMdm(options), { mode: 384 });
|
|
10728
|
+
chmodSync11(next, 384);
|
|
10729
|
+
renameSync5(next, runtimePath);
|
|
10638
10730
|
try {
|
|
10639
10731
|
unlinkSync9(servicePath);
|
|
10640
10732
|
} catch (cause) {
|
|
@@ -10646,7 +10738,7 @@ function materializeWarpMdm(options) {
|
|
|
10646
10738
|
}
|
|
10647
10739
|
|
|
10648
10740
|
// src/mesh-connector.ts
|
|
10649
|
-
import { constants, closeSync, fstatSync, openSync, readFileSync as
|
|
10741
|
+
import { constants, closeSync, fstatSync, openSync, readFileSync as readFileSync8 } from "fs";
|
|
10650
10742
|
import { join as join6 } from "path";
|
|
10651
10743
|
var TOKEN = /^[A-Za-z0-9._-]{40,16384}$/;
|
|
10652
10744
|
function readMeshConnectorCredential(name = "CF_WARP_CONNECTOR_TOKEN") {
|
|
@@ -10663,7 +10755,7 @@ function readMeshConnectorCredential(name = "CF_WARP_CONNECTOR_TOKEN") {
|
|
|
10663
10755
|
if (!metadata.isFile() || metadata.nlink !== 1 || metadata.size < 40 || metadata.size > 16384) {
|
|
10664
10756
|
throw new Error("Mesh connector systemd credential is malformed");
|
|
10665
10757
|
}
|
|
10666
|
-
const token =
|
|
10758
|
+
const token = readFileSync8(descriptor, "utf8").trim();
|
|
10667
10759
|
if (!TOKEN.test(token))
|
|
10668
10760
|
throw new Error("Mesh connector systemd credential is malformed");
|
|
10669
10761
|
return token;
|
|
@@ -10689,20 +10781,20 @@ async function configureMeshConnector(input) {
|
|
|
10689
10781
|
}
|
|
10690
10782
|
|
|
10691
10783
|
// src/agent-update.ts
|
|
10692
|
-
import { createHash as
|
|
10784
|
+
import { createHash as createHash8, timingSafeEqual, randomUUID as randomUUID2 } from "crypto";
|
|
10693
10785
|
import {
|
|
10694
|
-
chmodSync as
|
|
10786
|
+
chmodSync as chmodSync12,
|
|
10695
10787
|
closeSync as closeSync2,
|
|
10696
|
-
existsSync as
|
|
10788
|
+
existsSync as existsSync12,
|
|
10697
10789
|
fsyncSync,
|
|
10698
|
-
mkdirSync as
|
|
10790
|
+
mkdirSync as mkdirSync8,
|
|
10699
10791
|
openSync as openSync2,
|
|
10700
|
-
readFileSync as
|
|
10792
|
+
readFileSync as readFileSync9,
|
|
10701
10793
|
readlinkSync,
|
|
10702
|
-
renameSync as
|
|
10703
|
-
rmSync as
|
|
10794
|
+
renameSync as renameSync6,
|
|
10795
|
+
rmSync as rmSync5,
|
|
10704
10796
|
symlinkSync as symlinkSync3,
|
|
10705
|
-
writeFileSync as
|
|
10797
|
+
writeFileSync as writeFileSync9
|
|
10706
10798
|
} from "fs";
|
|
10707
10799
|
import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
|
|
10708
10800
|
var DEFAULT_AGENT_RELEASE_ROOT = "/opt/forgezero/agent";
|
|
@@ -10784,9 +10876,9 @@ var checked4 = async (run2, input, label) => {
|
|
|
10784
10876
|
return result;
|
|
10785
10877
|
};
|
|
10786
10878
|
async function validateReleaseDirectory(directory, release, run2) {
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
const manifest = JSON.parse(
|
|
10879
|
+
chmodSync12(directory, 493);
|
|
10880
|
+
chmodSync12(join7(directory, "dist"), 493);
|
|
10881
|
+
const manifest = JSON.parse(readFileSync9(join7(directory, "package.json"), "utf8"));
|
|
10790
10882
|
if (manifest.name !== release.package || manifest.version !== release.version) {
|
|
10791
10883
|
throw new Error("agent update manifest does not match the selected release");
|
|
10792
10884
|
}
|
|
@@ -10794,11 +10886,11 @@ async function validateReleaseDirectory(directory, release, run2) {
|
|
|
10794
10886
|
const cli = join7(directory, "dist", "fz.js");
|
|
10795
10887
|
const gitSsh = join7(directory, "dist", "fz-git-ssh.js");
|
|
10796
10888
|
for (const binary of [agent, cli, gitSsh]) {
|
|
10797
|
-
if (!
|
|
10889
|
+
if (!readFileSync9(binary, "utf8").startsWith(`#!/usr/bin/env bun
|
|
10798
10890
|
`)) {
|
|
10799
10891
|
throw new Error("agent update artifact is not a self-contained Bun executable");
|
|
10800
10892
|
}
|
|
10801
|
-
|
|
10893
|
+
chmodSync12(binary, 493);
|
|
10802
10894
|
}
|
|
10803
10895
|
const version = (await checked4(run2, { command: agent, args: ["--version"] }, "agent update smoke test")).output.trim();
|
|
10804
10896
|
if (version !== release.version)
|
|
@@ -10817,7 +10909,7 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
10817
10909
|
const archive = join7(stage2, "agent.tgz");
|
|
10818
10910
|
const unpacked = join7(stage2, "unpacked");
|
|
10819
10911
|
const run2 = options.run ?? command;
|
|
10820
|
-
|
|
10912
|
+
mkdirSync8(unpacked, { recursive: true, mode: 448 });
|
|
10821
10913
|
try {
|
|
10822
10914
|
const response = await (options.fetch ?? globalThis.fetch)(release.tarball, {
|
|
10823
10915
|
redirect: "error",
|
|
@@ -10833,10 +10925,10 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
10833
10925
|
throw new Error("agent update tarball is empty or exceeds the size limit");
|
|
10834
10926
|
}
|
|
10835
10927
|
const expected = Buffer.from(release.integrity.slice("sha512-".length), "base64");
|
|
10836
|
-
const actual =
|
|
10928
|
+
const actual = createHash8("sha512").update(bytes).digest();
|
|
10837
10929
|
if (!timingSafeEqual(actual, expected))
|
|
10838
10930
|
throw new Error("agent update integrity mismatch");
|
|
10839
|
-
|
|
10931
|
+
writeFileSync9(archive, bytes, { mode: 384, flag: "wx" });
|
|
10840
10932
|
for (const [member, relative] of [
|
|
10841
10933
|
["package/package.json", "package.json"],
|
|
10842
10934
|
["package/dist/fz-agent.js", "dist/fz-agent.js"],
|
|
@@ -10848,23 +10940,23 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
10848
10940
|
args: ["-xOzf", archive, member]
|
|
10849
10941
|
}, `agent update extraction of ${member}`);
|
|
10850
10942
|
const destination = join7(unpacked, relative);
|
|
10851
|
-
|
|
10852
|
-
|
|
10943
|
+
mkdirSync8(dirname4(destination), { recursive: true, mode: 448 });
|
|
10944
|
+
writeFileSync9(destination, extracted.output, { mode: 384, flag: "wx" });
|
|
10853
10945
|
}
|
|
10854
10946
|
await validateReleaseDirectory(unpacked, release, run2);
|
|
10855
|
-
if (!
|
|
10856
|
-
|
|
10947
|
+
if (!existsSync12(finalDirectory)) {
|
|
10948
|
+
renameSync6(unpacked, finalDirectory);
|
|
10857
10949
|
syncReleaseDirectory(finalDirectory);
|
|
10858
10950
|
} else
|
|
10859
10951
|
await validateReleaseDirectory(finalDirectory, release, run2);
|
|
10860
|
-
if (!
|
|
10952
|
+
if (!existsSync12(currentLink)) {
|
|
10861
10953
|
throw new Error("agent update requires an active immutable release to roll back to");
|
|
10862
10954
|
}
|
|
10863
10955
|
const previousTarget = readlinkSync(currentLink);
|
|
10864
10956
|
if (previousTarget !== join7("versions", options.currentVersion)) {
|
|
10865
10957
|
throw new Error("agent update current release does not match the running version");
|
|
10866
10958
|
}
|
|
10867
|
-
if (!
|
|
10959
|
+
if (!existsSync12(join7(root, previousTarget))) {
|
|
10868
10960
|
throw new Error("agent update rollback release is missing");
|
|
10869
10961
|
}
|
|
10870
10962
|
return {
|
|
@@ -10876,44 +10968,44 @@ async function stageAgentRelease(releaseInput, options) {
|
|
|
10876
10968
|
currentLink
|
|
10877
10969
|
};
|
|
10878
10970
|
} finally {
|
|
10879
|
-
|
|
10971
|
+
rmSync5(stage2, { recursive: true, force: true });
|
|
10880
10972
|
}
|
|
10881
10973
|
}
|
|
10882
10974
|
function selectAgentRelease(staged) {
|
|
10883
10975
|
const next = join7(dirname4(staged.currentLink), `.current.${randomUUID2()}.next`);
|
|
10884
10976
|
try {
|
|
10885
10977
|
symlinkSync3(staged.nextTarget, next);
|
|
10886
|
-
|
|
10978
|
+
renameSync6(next, staged.currentLink);
|
|
10887
10979
|
syncPath(dirname4(staged.currentLink));
|
|
10888
10980
|
} finally {
|
|
10889
|
-
|
|
10981
|
+
rmSync5(next, { force: true });
|
|
10890
10982
|
}
|
|
10891
10983
|
}
|
|
10892
10984
|
function restoreAgentRelease(staged) {
|
|
10893
10985
|
const next = join7(dirname4(staged.currentLink), `.current.${randomUUID2()}.rollback`);
|
|
10894
10986
|
try {
|
|
10895
10987
|
symlinkSync3(staged.previousTarget, next);
|
|
10896
|
-
|
|
10988
|
+
renameSync6(next, staged.currentLink);
|
|
10897
10989
|
syncPath(dirname4(staged.currentLink));
|
|
10898
10990
|
} finally {
|
|
10899
|
-
|
|
10991
|
+
rmSync5(next, { force: true });
|
|
10900
10992
|
}
|
|
10901
10993
|
}
|
|
10902
10994
|
|
|
10903
10995
|
// src/agent-update-helper.ts
|
|
10904
10996
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
10905
10997
|
import {
|
|
10906
|
-
chmodSync as
|
|
10998
|
+
chmodSync as chmodSync13,
|
|
10907
10999
|
closeSync as closeSync3,
|
|
10908
|
-
existsSync as
|
|
11000
|
+
existsSync as existsSync13,
|
|
10909
11001
|
fsyncSync as fsyncSync2,
|
|
10910
|
-
mkdirSync as
|
|
11002
|
+
mkdirSync as mkdirSync9,
|
|
10911
11003
|
openSync as openSync3,
|
|
10912
|
-
readFileSync as
|
|
10913
|
-
renameSync as
|
|
10914
|
-
rmSync as
|
|
11004
|
+
readFileSync as readFileSync10,
|
|
11005
|
+
renameSync as renameSync7,
|
|
11006
|
+
rmSync as rmSync6,
|
|
10915
11007
|
unlinkSync as unlinkSync10,
|
|
10916
|
-
writeFileSync as
|
|
11008
|
+
writeFileSync as writeFileSync10
|
|
10917
11009
|
} from "fs";
|
|
10918
11010
|
import { connect as connect5, createServer as createServer6 } from "net";
|
|
10919
11011
|
import { dirname as dirname5, join as join8, resolve as resolve3 } from "path";
|
|
@@ -11011,22 +11103,22 @@ function validateJournal(value, root) {
|
|
|
11011
11103
|
return journal;
|
|
11012
11104
|
}
|
|
11013
11105
|
function readJournal(path2, root) {
|
|
11014
|
-
if (!
|
|
11106
|
+
if (!existsSync13(path2))
|
|
11015
11107
|
return;
|
|
11016
|
-
return validateJournal(JSON.parse(
|
|
11108
|
+
return validateJournal(JSON.parse(readFileSync10(path2, "utf8")), root);
|
|
11017
11109
|
}
|
|
11018
11110
|
function writeAtomic(path2, value, mode) {
|
|
11019
|
-
|
|
11111
|
+
mkdirSync9(dirname5(path2), { recursive: true, mode: 493 });
|
|
11020
11112
|
const next = `${path2}.${randomUUID3()}.next`;
|
|
11021
11113
|
let file;
|
|
11022
11114
|
try {
|
|
11023
11115
|
file = openSync3(next, "wx", mode);
|
|
11024
|
-
|
|
11116
|
+
writeFileSync10(file, `${JSON.stringify(value)}
|
|
11025
11117
|
`);
|
|
11026
11118
|
fsyncSync2(file);
|
|
11027
11119
|
closeSync3(file);
|
|
11028
11120
|
file = undefined;
|
|
11029
|
-
|
|
11121
|
+
renameSync7(next, path2);
|
|
11030
11122
|
const directory = openSync3(dirname5(path2), "r");
|
|
11031
11123
|
try {
|
|
11032
11124
|
fsyncSync2(directory);
|
|
@@ -11036,7 +11128,7 @@ function writeAtomic(path2, value, mode) {
|
|
|
11036
11128
|
} finally {
|
|
11037
11129
|
if (file !== undefined)
|
|
11038
11130
|
closeSync3(file);
|
|
11039
|
-
|
|
11131
|
+
rmSync6(next, { force: true });
|
|
11040
11132
|
}
|
|
11041
11133
|
}
|
|
11042
11134
|
var publicReceipt = (journal) => {
|
|
@@ -11069,9 +11161,9 @@ function writeUpdateState(journalPath, receiptPath, journal) {
|
|
|
11069
11161
|
}
|
|
11070
11162
|
function readAgentUpdateReceipt(path2 = AGENT_UPDATE_RECEIPT) {
|
|
11071
11163
|
try {
|
|
11072
|
-
if (!
|
|
11164
|
+
if (!existsSync13(path2))
|
|
11073
11165
|
return;
|
|
11074
|
-
return validateReceipt(JSON.parse(
|
|
11166
|
+
return validateReceipt(JSON.parse(readFileSync10(path2, "utf8")));
|
|
11075
11167
|
} catch {
|
|
11076
11168
|
return;
|
|
11077
11169
|
}
|
|
@@ -11225,7 +11317,7 @@ async function recoverInterruptedAgentUpdate(options = {}) {
|
|
|
11225
11317
|
return publicReceipt(journal);
|
|
11226
11318
|
}
|
|
11227
11319
|
const staged = stagedFromJournal(journal, root);
|
|
11228
|
-
if (!
|
|
11320
|
+
if (!existsSync13(join8(root, journal.previousTarget))) {
|
|
11229
11321
|
throw new Error("Agent update rollback release is missing");
|
|
11230
11322
|
}
|
|
11231
11323
|
const run2 = options.run ?? runCommand;
|
|
@@ -11255,9 +11347,9 @@ async function recoverInterruptedAgentUpdate(options = {}) {
|
|
|
11255
11347
|
}
|
|
11256
11348
|
function startAgentUpdateHelper(options = {}) {
|
|
11257
11349
|
const socketPath = options.socketPath ?? DEFAULT_AGENT_UPDATE_SOCKET;
|
|
11258
|
-
if (
|
|
11350
|
+
if (existsSync13(socketPath))
|
|
11259
11351
|
unlinkSync10(socketPath);
|
|
11260
|
-
|
|
11352
|
+
mkdirSync9(dirname5(socketPath), { recursive: true, mode: 488 });
|
|
11261
11353
|
const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
11262
11354
|
const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
|
|
11263
11355
|
const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
|
|
@@ -11336,7 +11428,7 @@ function startAgentUpdateHelper(options = {}) {
|
|
|
11336
11428
|
});
|
|
11337
11429
|
socket.on("error", () => socket.destroy());
|
|
11338
11430
|
});
|
|
11339
|
-
server.listen(socketPath, () =>
|
|
11431
|
+
server.listen(socketPath, () => chmodSync13(socketPath, 432));
|
|
11340
11432
|
return server;
|
|
11341
11433
|
}
|
|
11342
11434
|
function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, timeoutMs = 90000) {
|
|
@@ -11366,13 +11458,13 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
|
|
|
11366
11458
|
}
|
|
11367
11459
|
|
|
11368
11460
|
// src/agent-heartbeat.ts
|
|
11369
|
-
import { readFileSync as
|
|
11461
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
11370
11462
|
var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
|
|
11371
11463
|
var remoteOutcome4 = (cause) => cause instanceof SignedNodeHttpError && cause.status < 500 ? "refused" : cause instanceof SignedNodeHttpError ? "retryable" : "failed";
|
|
11372
11464
|
|
|
11373
11465
|
class AgentUpdateRefusedError extends Error {
|
|
11374
11466
|
}
|
|
11375
|
-
function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease =
|
|
11467
|
+
function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = readFileSync11("/etc/os-release", "utf8"), architecture = process.arch) {
|
|
11376
11468
|
const values = Object.fromEntries(osRelease.split(`
|
|
11377
11469
|
`).flatMap((line) => {
|
|
11378
11470
|
const separator = line.indexOf("=");
|
|
@@ -11490,34 +11582,34 @@ function startAgentHeartbeat(options) {
|
|
|
11490
11582
|
}
|
|
11491
11583
|
|
|
11492
11584
|
// src/software-helper.ts
|
|
11493
|
-
import { chmodSync as
|
|
11585
|
+
import { chmodSync as chmodSync14, existsSync as existsSync16, mkdirSync as mkdirSync12, unlinkSync as unlinkSync11 } from "fs";
|
|
11494
11586
|
import { connect as connect6, createServer as createServer7 } from "net";
|
|
11495
11587
|
import { dirname as dirname8 } from "path";
|
|
11496
11588
|
|
|
11497
11589
|
// src/service-supervisor.ts
|
|
11498
|
-
import { createHash as
|
|
11499
|
-
import { existsSync as
|
|
11590
|
+
import { createHash as createHash9 } from "crypto";
|
|
11591
|
+
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
11592
|
import { dirname as dirname6, join as join9, resolve as resolve4, sep as sep2 } from "path";
|
|
11501
11593
|
var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
|
|
11502
11594
|
var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
|
|
11503
11595
|
var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
|
|
11504
11596
|
var SERVICE_NGINX_DIRECTORY = "/etc/nginx/conf.d";
|
|
11505
|
-
var idFor = (key) =>
|
|
11597
|
+
var idFor = (key) => createHash9("sha256").update(key).digest("hex").slice(0, 24);
|
|
11506
11598
|
var statePath = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
|
|
11507
11599
|
var within2 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep2}`);
|
|
11508
11600
|
var defaultHost = {
|
|
11509
11601
|
write(path2, content, mode) {
|
|
11510
|
-
|
|
11602
|
+
mkdirSync10(dirname6(path2), { recursive: true, mode: 493 });
|
|
11511
11603
|
const next = `${path2}.next`;
|
|
11512
|
-
|
|
11513
|
-
|
|
11604
|
+
writeFileSync11(next, content, { mode });
|
|
11605
|
+
renameSync8(next, path2);
|
|
11514
11606
|
},
|
|
11515
|
-
read: (path2) =>
|
|
11516
|
-
exists:
|
|
11517
|
-
list: (path2) =>
|
|
11607
|
+
read: (path2) => readFileSync12(path2, "utf8"),
|
|
11608
|
+
exists: existsSync14,
|
|
11609
|
+
list: (path2) => existsSync14(path2) ? readdirSync2(path2) : [],
|
|
11518
11610
|
realpath: realpathSync2,
|
|
11519
|
-
mkdir: (path2, mode) =>
|
|
11520
|
-
remove: (path2) =>
|
|
11611
|
+
mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
|
|
11612
|
+
remove: (path2) => rmSync7(path2, { force: true }),
|
|
11521
11613
|
async exec(argv2) {
|
|
11522
11614
|
const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: {
|
|
11523
11615
|
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
@@ -11575,7 +11667,7 @@ function allocatedPorts(request, id2, previous, states) {
|
|
|
11575
11667
|
if (previous && previous.applicationPorts.length === required && previous.applicationPorts.every((port) => port >= allocation.from && port <= allocation.to && !occupied.has(port)))
|
|
11576
11668
|
return [...previous.applicationPorts];
|
|
11577
11669
|
const width = allocation.to - allocation.from + 1;
|
|
11578
|
-
const start = Number.parseInt(
|
|
11670
|
+
const start = Number.parseInt(createHash9("sha256").update(request.key).digest("hex").slice(0, 8), 16) % width;
|
|
11579
11671
|
for (let offset = 0;offset < width; offset += 1) {
|
|
11580
11672
|
const first = allocation.from + (start + offset) % width;
|
|
11581
11673
|
const candidate = Array.from({ length: required }, (_, index) => first + index);
|
|
@@ -11712,22 +11804,22 @@ async function activateSupervisedService(request, host = defaultHost) {
|
|
|
11712
11804
|
}
|
|
11713
11805
|
|
|
11714
11806
|
// src/container-supervisor.ts
|
|
11715
|
-
import { createHash as
|
|
11716
|
-
import { existsSync as
|
|
11807
|
+
import { createHash as createHash10 } from "crypto";
|
|
11808
|
+
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
11809
|
import { dirname as dirname7, resolve as resolve5, sep as sep3 } from "path";
|
|
11718
11810
|
var defaultHost2 = {
|
|
11719
11811
|
realpath: realpathSync3,
|
|
11720
|
-
exists:
|
|
11721
|
-
read: (path2) =>
|
|
11812
|
+
exists: existsSync15,
|
|
11813
|
+
read: (path2) => readFileSync13(path2, "utf8"),
|
|
11722
11814
|
write(path2, content, mode) {
|
|
11723
|
-
|
|
11815
|
+
mkdirSync11(dirname7(path2), { recursive: true, mode: 493 });
|
|
11724
11816
|
const next = `${path2}.next`;
|
|
11725
|
-
|
|
11726
|
-
|
|
11817
|
+
writeFileSync12(next, content, { mode });
|
|
11818
|
+
renameSync9(next, path2);
|
|
11727
11819
|
},
|
|
11728
|
-
remove: (path2) =>
|
|
11729
|
-
list: (path2) =>
|
|
11730
|
-
mkdir: (path2, mode) =>
|
|
11820
|
+
remove: (path2) => rmSync8(path2, { force: true }),
|
|
11821
|
+
list: (path2) => existsSync15(path2) ? readdirSync3(path2) : [],
|
|
11822
|
+
mkdir: (path2, mode) => mkdirSync11(path2, { recursive: true, mode }),
|
|
11731
11823
|
async exec(argv2) {
|
|
11732
11824
|
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
11825
|
const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
|
|
@@ -11745,7 +11837,7 @@ var defaultHost2 = {
|
|
|
11745
11837
|
now: Date.now
|
|
11746
11838
|
};
|
|
11747
11839
|
var within3 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep3}`);
|
|
11748
|
-
var idFor2 = (key) =>
|
|
11840
|
+
var idFor2 = (key) => createHash10("sha256").update(key).digest("hex").slice(0, 24);
|
|
11749
11841
|
var statePath2 = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
|
|
11750
11842
|
var nameFor = (id2, slot) => `forgezero-${id2}-slot${slot}`;
|
|
11751
11843
|
function validateRequest(request, host) {
|
|
@@ -12010,9 +12102,9 @@ var MAX_REQUEST_BYTES6 = 128 * 1024;
|
|
|
12010
12102
|
var MAX_PENDING_REQUESTS = 128;
|
|
12011
12103
|
function startSoftwareHelper(options = {}) {
|
|
12012
12104
|
const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
|
|
12013
|
-
if (
|
|
12105
|
+
if (existsSync16(socketPath))
|
|
12014
12106
|
unlinkSync11(socketPath);
|
|
12015
|
-
|
|
12107
|
+
mkdirSync12(dirname8(socketPath), { recursive: true, mode: 488 });
|
|
12016
12108
|
const ensure = options.ensure ?? ensureSoftwareRequirements;
|
|
12017
12109
|
const activate = options.activate ?? activateSupervisedService;
|
|
12018
12110
|
const buildContainer = options.buildContainer ?? buildContainerImage;
|
|
@@ -12092,7 +12184,7 @@ function startSoftwareHelper(options = {}) {
|
|
|
12092
12184
|
});
|
|
12093
12185
|
socket.on("error", () => socket.destroy());
|
|
12094
12186
|
});
|
|
12095
|
-
server.listen(socketPath, () =>
|
|
12187
|
+
server.listen(socketPath, () => chmodSync14(socketPath, 432));
|
|
12096
12188
|
return server;
|
|
12097
12189
|
}
|
|
12098
12190
|
function requestContainer(op, request, socketPath, timeoutMs) {
|
|
@@ -13121,20 +13213,20 @@ var METAL_SYSTEMD_CREDENTIALS = {
|
|
|
13121
13213
|
|
|
13122
13214
|
// src/platform-bootstrap-runtime.ts
|
|
13123
13215
|
import {
|
|
13124
|
-
chmodSync as
|
|
13216
|
+
chmodSync as chmodSync15,
|
|
13125
13217
|
chownSync,
|
|
13126
13218
|
copyFileSync as copyFileSync2,
|
|
13127
|
-
existsSync as
|
|
13128
|
-
lstatSync as
|
|
13129
|
-
mkdirSync as
|
|
13130
|
-
readFileSync as
|
|
13219
|
+
existsSync as existsSync17,
|
|
13220
|
+
lstatSync as lstatSync3,
|
|
13221
|
+
mkdirSync as mkdirSync13,
|
|
13222
|
+
readFileSync as readFileSync14,
|
|
13131
13223
|
readdirSync as readdirSync4,
|
|
13132
13224
|
realpathSync as realpathSync5,
|
|
13133
|
-
renameSync as
|
|
13134
|
-
rmSync as
|
|
13225
|
+
renameSync as renameSync10,
|
|
13226
|
+
rmSync as rmSync9,
|
|
13135
13227
|
statSync as statSync3,
|
|
13136
13228
|
symlinkSync as symlinkSync4,
|
|
13137
|
-
writeFileSync as
|
|
13229
|
+
writeFileSync as writeFileSync13
|
|
13138
13230
|
} from "fs";
|
|
13139
13231
|
import { join as join10 } from "path";
|
|
13140
13232
|
var boundedInteger2 = (name, value, minimum, maximum) => {
|
|
@@ -13188,21 +13280,21 @@ var platformExec = async (argv2) => {
|
|
|
13188
13280
|
};
|
|
13189
13281
|
var replaceLink = (path2, target2) => {
|
|
13190
13282
|
const pending = `${path2}.next`;
|
|
13191
|
-
|
|
13283
|
+
rmSync9(pending, { force: true });
|
|
13192
13284
|
if (!target2) {
|
|
13193
|
-
|
|
13285
|
+
rmSync9(path2, { force: true });
|
|
13194
13286
|
return;
|
|
13195
13287
|
}
|
|
13196
13288
|
symlinkSync4(target2, pending);
|
|
13197
|
-
|
|
13289
|
+
renameSync10(pending, path2);
|
|
13198
13290
|
};
|
|
13199
13291
|
var secureRelease = (path2, uid, gid) => {
|
|
13200
13292
|
const visit = (current) => {
|
|
13201
|
-
const metadata =
|
|
13293
|
+
const metadata = lstatSync3(current);
|
|
13202
13294
|
if (metadata.isSymbolicLink())
|
|
13203
13295
|
throw new Error("release contains a symbolic link");
|
|
13204
13296
|
chownSync(current, uid, gid);
|
|
13205
|
-
|
|
13297
|
+
chmodSync15(current, metadata.isDirectory() ? 365 : 292);
|
|
13206
13298
|
if (metadata.isDirectory())
|
|
13207
13299
|
for (const name of readdirSync4(current))
|
|
13208
13300
|
visit(join10(current, name));
|
|
@@ -13225,9 +13317,9 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
13225
13317
|
const request = options.fetch ?? fetch;
|
|
13226
13318
|
const sleep2 = options.sleep ?? Bun.sleep;
|
|
13227
13319
|
const slots = join10(normalized.root, "slots");
|
|
13228
|
-
|
|
13320
|
+
mkdirSync13(slots, { recursive: true, mode: 493 });
|
|
13229
13321
|
const slotFile = join10(normalized.root, ".forge-slot");
|
|
13230
|
-
const previousSlot =
|
|
13322
|
+
const previousSlot = existsSync17(slotFile) ? readFileSync14(slotFile, "utf8").trim() : undefined;
|
|
13231
13323
|
const target2 = previousSlot === "blue" ? "green" : "blue";
|
|
13232
13324
|
const port = target2 === "blue" ? normalized.bluePort : normalized.greenPort;
|
|
13233
13325
|
const targetLink = join10(slots, target2);
|
|
@@ -13267,26 +13359,26 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
13267
13359
|
}
|
|
13268
13360
|
const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
|
|
13269
13361
|
const backup = `${upstream}.forgezero-backup`;
|
|
13270
|
-
if (
|
|
13362
|
+
if (existsSync17(upstream))
|
|
13271
13363
|
copyFileSync2(upstream, backup);
|
|
13272
13364
|
else
|
|
13273
|
-
|
|
13274
|
-
|
|
13365
|
+
rmSync9(backup, { force: true });
|
|
13366
|
+
writeFileSync13(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
|
|
13275
13367
|
`, { mode: 420 });
|
|
13276
13368
|
const test = await exec(["/usr/sbin/nginx", "-t"]);
|
|
13277
13369
|
const reload = test.exitCode === 0 ? await exec(["/usr/sbin/nginx", "-s", "reload"]) : test;
|
|
13278
13370
|
if (reload.exitCode !== 0) {
|
|
13279
|
-
if (
|
|
13280
|
-
|
|
13371
|
+
if (existsSync17(backup))
|
|
13372
|
+
renameSync10(backup, upstream);
|
|
13281
13373
|
else
|
|
13282
|
-
|
|
13374
|
+
rmSync9(upstream, { force: true });
|
|
13283
13375
|
await exec(["/usr/sbin/nginx", "-t"]);
|
|
13284
13376
|
await exec(["/usr/sbin/nginx", "-s", "reload"]);
|
|
13285
13377
|
await stopTarget();
|
|
13286
13378
|
throw new Error("nginx refused the promoted upstream");
|
|
13287
13379
|
}
|
|
13288
|
-
|
|
13289
|
-
|
|
13380
|
+
rmSync9(backup, { force: true });
|
|
13381
|
+
writeFileSync13(slotFile, `${target2}
|
|
13290
13382
|
`, { mode: 420 });
|
|
13291
13383
|
if (previousSlot && previousSlot !== target2) {
|
|
13292
13384
|
await sleep2(normalized.drainDeadlineMs);
|
|
@@ -13295,12 +13387,12 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
|
|
|
13295
13387
|
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
13388
|
for (const path2 of old)
|
|
13297
13389
|
if (path2 !== release)
|
|
13298
|
-
|
|
13390
|
+
rmSync9(path2, { recursive: true, force: true });
|
|
13299
13391
|
return { release, slot: target2 };
|
|
13300
13392
|
}
|
|
13301
13393
|
|
|
13302
13394
|
// src/recovery-host.ts
|
|
13303
|
-
import { existsSync as
|
|
13395
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as statSync4 } from "fs";
|
|
13304
13396
|
import { hostname } from "os";
|
|
13305
13397
|
import { join as join11 } from "path";
|
|
13306
13398
|
var execute2 = async (argv2) => {
|
|
@@ -13315,18 +13407,18 @@ var checked7 = async (run2, argv2, expected = 0) => {
|
|
|
13315
13407
|
return result;
|
|
13316
13408
|
};
|
|
13317
13409
|
var activeSlot = () => {
|
|
13318
|
-
const slot =
|
|
13410
|
+
const slot = readFileSync15("/opt/forgezero/.forge-slot", "utf8").trim();
|
|
13319
13411
|
if (slot !== "blue" && slot !== "green")
|
|
13320
13412
|
throw new Error("invalid active slot");
|
|
13321
13413
|
return slot;
|
|
13322
13414
|
};
|
|
13323
13415
|
var exactState = (path2, expected) => {
|
|
13324
|
-
if (!
|
|
13416
|
+
if (!readFileSync15(path2, "utf8").split(`
|
|
13325
13417
|
`).includes(expected))
|
|
13326
13418
|
throw new Error(`state mismatch: ${expected}`);
|
|
13327
13419
|
};
|
|
13328
13420
|
var nonEmpty = (path2) => {
|
|
13329
|
-
if (!
|
|
13421
|
+
if (!existsSync18(path2) || !statSync4(path2).isFile() || statSync4(path2).size < 1)
|
|
13330
13422
|
throw new Error(`required file missing: ${path2}`);
|
|
13331
13423
|
};
|
|
13332
13424
|
var option = (args, name) => {
|
|
@@ -13422,18 +13514,18 @@ async function runRecoveryHost(args, run2 = execute2) {
|
|
|
13422
13514
|
}
|
|
13423
13515
|
|
|
13424
13516
|
// src/platform-fleet-verification.ts
|
|
13425
|
-
import { lstatSync as
|
|
13517
|
+
import { lstatSync as lstatSync6 } from "fs";
|
|
13426
13518
|
|
|
13427
13519
|
// src/bootstrap.ts
|
|
13428
13520
|
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
|
|
13521
|
+
chmodSync as chmodSync17,
|
|
13522
|
+
existsSync as existsSync20,
|
|
13523
|
+
lstatSync as lstatSync5,
|
|
13524
|
+
mkdirSync as mkdirSync15,
|
|
13525
|
+
readFileSync as readFileSync17,
|
|
13526
|
+
renameSync as renameSync12,
|
|
13527
|
+
rmSync as rmSync11,
|
|
13528
|
+
writeFileSync as writeFileSync15
|
|
13437
13529
|
} from "fs";
|
|
13438
13530
|
import { dirname as dirname10 } from "path";
|
|
13439
13531
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -13505,7 +13597,11 @@ function agentEgressUnit(options) {
|
|
|
13505
13597
|
const user = options.user ?? "forgezero";
|
|
13506
13598
|
if (!/^[a-z_][a-z0-9_-]{0,30}$/.test(user))
|
|
13507
13599
|
throw new Error("invalid Agent service user");
|
|
13508
|
-
const
|
|
13600
|
+
const bootstrapBundleEnabled = Boolean(options.bootstrapBundlePath && options.bootstrapBundleManifestPath);
|
|
13601
|
+
if (Boolean(options.bootstrapBundlePath) !== Boolean(options.bootstrapBundleManifestPath) || bootstrapBundleEnabled && ![options.bootstrapBundlePath, options.bootstrapBundleManifestPath].every((path2) => path2.startsWith("/") && !/[\r\n\0:]/.test(path2))) {
|
|
13602
|
+
throw new Error("bootstrap bundle and manifest must be supplied together as absolute paths");
|
|
13603
|
+
}
|
|
13604
|
+
const deploymentEnabled = Boolean(options.repository || bootstrapBundleEnabled || options.pullDeployments);
|
|
13509
13605
|
const runnerLoopbackPorts = normalizeEgressTcpPorts(options.runnerLoopbackPorts ?? []);
|
|
13510
13606
|
const runnerPublicTcpPorts = normalizeEgressTcpPorts(options.runnerPublicTcpPorts ?? DEFAULT_RUNNER_PUBLIC_TCP_PORTS);
|
|
13511
13607
|
if (deploymentEnabled && runnerPublicTcpPorts.length < 1) {
|
|
@@ -13975,6 +14071,8 @@ function agentUnit(options) {
|
|
|
13975
14071
|
options.gitPublicKeyPath ? `FZ_GIT_PUBLIC_KEY_FILE=${options.gitPublicKeyPath}` : null,
|
|
13976
14072
|
options.repository ? `FZ_DEPLOY_REPO=${options.repository}` : null,
|
|
13977
14073
|
options.branch ? `FZ_DEPLOY_BRANCH=${options.branch}` : null,
|
|
14074
|
+
options.bootstrapBundlePath ? `FZ_BOOTSTRAP_BUNDLE=${options.bootstrapBundlePath}` : null,
|
|
14075
|
+
options.bootstrapBundleManifestPath ? `FZ_BOOTSTRAP_BUNDLE_MANIFEST=${options.bootstrapBundleManifestPath}` : null,
|
|
13978
14076
|
options.profile ? `FZ_DEPLOY_PROFILE=${options.profile}` : null,
|
|
13979
14077
|
options.repository && options.branch ? `FZ_DEPLOY_KEY=${options.project ?? "platform"}:${options.environment ?? "production"}` : null,
|
|
13980
14078
|
deploymentEnabled ? `FZ_DEPLOY_ROOT=${deployRoot}` : null,
|
|
@@ -14357,19 +14455,19 @@ function planProvision(options) {
|
|
|
14357
14455
|
}
|
|
14358
14456
|
|
|
14359
14457
|
// src/cli/agent-install.ts
|
|
14360
|
-
import { randomBytes as
|
|
14458
|
+
import { randomBytes as randomBytes6 } from "crypto";
|
|
14361
14459
|
import {
|
|
14362
|
-
chmodSync as
|
|
14460
|
+
chmodSync as chmodSync16,
|
|
14363
14461
|
copyFileSync as copyFileSync3,
|
|
14364
|
-
existsSync as
|
|
14365
|
-
lstatSync as
|
|
14366
|
-
mkdirSync as
|
|
14367
|
-
readFileSync as
|
|
14462
|
+
existsSync as existsSync19,
|
|
14463
|
+
lstatSync as lstatSync4,
|
|
14464
|
+
mkdirSync as mkdirSync14,
|
|
14465
|
+
readFileSync as readFileSync16,
|
|
14368
14466
|
realpathSync as realpathSync6,
|
|
14369
|
-
renameSync as
|
|
14370
|
-
rmSync as
|
|
14467
|
+
renameSync as renameSync11,
|
|
14468
|
+
rmSync as rmSync10,
|
|
14371
14469
|
symlinkSync as symlinkSync5,
|
|
14372
|
-
writeFileSync as
|
|
14470
|
+
writeFileSync as writeFileSync14
|
|
14373
14471
|
} from "fs";
|
|
14374
14472
|
import { dirname as dirname9 } from "path";
|
|
14375
14473
|
async function readCapabilities(run2) {
|
|
@@ -14430,52 +14528,52 @@ var runProvisionOperation = async (operation) => {
|
|
|
14430
14528
|
}
|
|
14431
14529
|
if (operation.kind === "install-runtime") {
|
|
14432
14530
|
const release = `/opt/forgezero/agent/versions/${operation.version}`;
|
|
14433
|
-
|
|
14434
|
-
|
|
14531
|
+
mkdirSync14(`${release}/dist`, { recursive: true, mode: 493 });
|
|
14532
|
+
mkdirSync14(dirname9(operation.binary), { recursive: true, mode: 493 });
|
|
14435
14533
|
copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
|
|
14436
|
-
|
|
14534
|
+
chmodSync16(`${release}/dist/fz-agent.js`, 493);
|
|
14437
14535
|
const gitSshSource = `${dirname9(operation.source)}/fz-git-ssh.js`;
|
|
14438
|
-
if (!
|
|
14536
|
+
if (!existsSync19(gitSshSource))
|
|
14439
14537
|
return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
|
|
14440
14538
|
copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
|
|
14441
|
-
|
|
14539
|
+
chmodSync16(`${release}/dist/fz-git-ssh.js`, 493);
|
|
14442
14540
|
const pending = "/opt/forgezero/agent/current.next";
|
|
14443
|
-
|
|
14541
|
+
rmSync10(pending, { force: true });
|
|
14444
14542
|
symlinkSync5(`versions/${operation.version}`, pending);
|
|
14445
|
-
|
|
14446
|
-
|
|
14543
|
+
renameSync11(pending, "/opt/forgezero/agent/current");
|
|
14544
|
+
rmSync10(operation.binary, { force: true });
|
|
14447
14545
|
symlinkSync5("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
|
|
14448
14546
|
const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
|
|
14449
|
-
|
|
14547
|
+
rmSync10(gitSshBinary, { force: true });
|
|
14450
14548
|
symlinkSync5("/opt/forgezero/agent/current/dist/fz-git-ssh.js", gitSshBinary);
|
|
14451
14549
|
return { stdout: "", exitCode: 0 };
|
|
14452
14550
|
}
|
|
14453
14551
|
if (operation.kind === "ensure-seed") {
|
|
14454
|
-
if (
|
|
14552
|
+
if (existsSync19(operation.credential) && lstatSync4(operation.credential).size > 0)
|
|
14455
14553
|
return { stdout: "", exitCode: 0 };
|
|
14456
|
-
const seed =
|
|
14554
|
+
const seed = randomBytes6(32).toString("base64url");
|
|
14457
14555
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
|
|
14458
14556
|
if (result.exitCode === 0)
|
|
14459
|
-
|
|
14557
|
+
chmodSync16(operation.credential, 256);
|
|
14460
14558
|
return result;
|
|
14461
14559
|
}
|
|
14462
14560
|
if (operation.kind === "ensure-git-identity") {
|
|
14463
14561
|
const key = "/run/forgezero-git-deploy-key";
|
|
14464
14562
|
const publicKey = `${key}.pub`;
|
|
14465
14563
|
try {
|
|
14466
|
-
if (!
|
|
14467
|
-
|
|
14468
|
-
|
|
14564
|
+
if (!existsSync19(operation.credential) || lstatSync4(operation.credential).size < 1) {
|
|
14565
|
+
rmSync10(key, { force: true });
|
|
14566
|
+
rmSync10(publicKey, { force: true });
|
|
14469
14567
|
let result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-compute", "-f", key]);
|
|
14470
14568
|
if (result.exitCode !== 0)
|
|
14471
14569
|
return result;
|
|
14472
14570
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=git-deploy-key", key, operation.credential]);
|
|
14473
14571
|
if (result.exitCode !== 0)
|
|
14474
14572
|
return result;
|
|
14475
|
-
|
|
14573
|
+
chmodSync16(operation.credential, 256);
|
|
14476
14574
|
}
|
|
14477
|
-
if (!
|
|
14478
|
-
if (!
|
|
14575
|
+
if (!existsSync19(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
|
|
14576
|
+
if (!existsSync19(key)) {
|
|
14479
14577
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=git-deploy-key", operation.credential, key]);
|
|
14480
14578
|
if (decrypted.exitCode !== 0)
|
|
14481
14579
|
return decrypted;
|
|
@@ -14483,30 +14581,30 @@ var runProvisionOperation = async (operation) => {
|
|
|
14483
14581
|
const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
14484
14582
|
if (derived.exitCode !== 0)
|
|
14485
14583
|
return derived;
|
|
14486
|
-
|
|
14584
|
+
writeFileSync14(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
|
|
14487
14585
|
`, { mode: 292 });
|
|
14488
14586
|
}
|
|
14489
14587
|
return { stdout: "", exitCode: 0 };
|
|
14490
14588
|
} finally {
|
|
14491
|
-
|
|
14492
|
-
|
|
14589
|
+
rmSync10(key, { force: true });
|
|
14590
|
+
rmSync10(publicKey, { force: true });
|
|
14493
14591
|
}
|
|
14494
14592
|
}
|
|
14495
14593
|
if (operation.kind === "ensure-bootstrap-ssh-identity") {
|
|
14496
14594
|
const key = "/run/forgezero-bootstrap-ssh-key";
|
|
14497
14595
|
const generatedPublicKey = `${key}.pub`;
|
|
14498
14596
|
try {
|
|
14499
|
-
if (!
|
|
14500
|
-
|
|
14501
|
-
|
|
14597
|
+
if (!existsSync19(operation.credential) || lstatSync4(operation.credential).size < 1) {
|
|
14598
|
+
rmSync10(key, { force: true });
|
|
14599
|
+
rmSync10(generatedPublicKey, { force: true });
|
|
14502
14600
|
let result;
|
|
14503
14601
|
if (operation.source) {
|
|
14504
|
-
const source =
|
|
14602
|
+
const source = existsSync19(operation.source) ? lstatSync4(operation.source) : undefined;
|
|
14505
14603
|
if (!source?.isFile() || source.isSymbolicLink() || source.uid !== 0 || source.nlink !== 1 || (source.mode & 63) !== 0 || source.size < 32 || source.size > 16 * 1024) {
|
|
14506
14604
|
return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
|
|
14507
14605
|
}
|
|
14508
14606
|
copyFileSync3(operation.source, key);
|
|
14509
|
-
|
|
14607
|
+
chmodSync16(key, 384);
|
|
14510
14608
|
} else {
|
|
14511
14609
|
result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-bootstrap-runner", "-f", key]);
|
|
14512
14610
|
if (result.exitCode !== 0)
|
|
@@ -14519,48 +14617,48 @@ var runProvisionOperation = async (operation) => {
|
|
|
14519
14617
|
result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=bootstrap-ssh-key", key, operation.credential]);
|
|
14520
14618
|
if (result.exitCode !== 0)
|
|
14521
14619
|
return result;
|
|
14522
|
-
|
|
14620
|
+
chmodSync16(operation.credential, 256);
|
|
14523
14621
|
}
|
|
14524
|
-
if (!
|
|
14525
|
-
if (!
|
|
14622
|
+
if (!existsSync19(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
|
|
14623
|
+
if (!existsSync19(key)) {
|
|
14526
14624
|
const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
|
|
14527
14625
|
if (decrypted.exitCode !== 0)
|
|
14528
14626
|
return decrypted;
|
|
14529
|
-
|
|
14627
|
+
chmodSync16(key, 384);
|
|
14530
14628
|
}
|
|
14531
14629
|
const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
|
|
14532
14630
|
if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
|
|
14533
14631
|
return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
|
|
14534
14632
|
}
|
|
14535
|
-
|
|
14536
|
-
|
|
14633
|
+
mkdirSync14(dirname9(operation.publicKey), { recursive: true, mode: 493 });
|
|
14634
|
+
writeFileSync14(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
|
|
14537
14635
|
`, { mode: 292 });
|
|
14538
|
-
|
|
14636
|
+
chmodSync16(operation.publicKey, 292);
|
|
14539
14637
|
}
|
|
14540
14638
|
if (operation.source)
|
|
14541
|
-
|
|
14639
|
+
rmSync10(operation.source, { force: true });
|
|
14542
14640
|
return { stdout: "", exitCode: 0 };
|
|
14543
14641
|
} finally {
|
|
14544
|
-
|
|
14545
|
-
|
|
14642
|
+
rmSync10(key, { force: true });
|
|
14643
|
+
rmSync10(generatedPublicKey, { force: true });
|
|
14546
14644
|
}
|
|
14547
14645
|
}
|
|
14548
14646
|
if (operation.kind === "ensure-enrolment") {
|
|
14549
|
-
if (
|
|
14647
|
+
if (existsSync19(operation.state) && lstatSync4(operation.state).size > 0 || existsSync19(operation.credential) && lstatSync4(operation.credential).size > 0)
|
|
14550
14648
|
return { stdout: "", exitCode: 0 };
|
|
14551
|
-
if (!
|
|
14649
|
+
if (!existsSync19(operation.source))
|
|
14552
14650
|
return { stdout: "enrolment source is missing", exitCode: 1 };
|
|
14553
14651
|
const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
|
|
14554
14652
|
if (result.exitCode === 0) {
|
|
14555
|
-
|
|
14556
|
-
|
|
14653
|
+
chmodSync16(operation.credential, 256);
|
|
14654
|
+
rmSync10(operation.source, { force: true });
|
|
14557
14655
|
}
|
|
14558
14656
|
return result;
|
|
14559
14657
|
}
|
|
14560
14658
|
if (operation.kind === "wait-socket") {
|
|
14561
14659
|
for (let attempt = 0;attempt < operation.attempts; attempt += 1) {
|
|
14562
14660
|
try {
|
|
14563
|
-
if (
|
|
14661
|
+
if (lstatSync4(operation.path).isSocket())
|
|
14564
14662
|
return { stdout: "", exitCode: 0 };
|
|
14565
14663
|
} catch {}
|
|
14566
14664
|
await Bun.sleep(operation.intervalMs);
|
|
@@ -14568,7 +14666,7 @@ var runProvisionOperation = async (operation) => {
|
|
|
14568
14666
|
return { stdout: `socket did not become ready: ${operation.path}`, exitCode: 1 };
|
|
14569
14667
|
}
|
|
14570
14668
|
if (operation.kind === "verify-file")
|
|
14571
|
-
return
|
|
14669
|
+
return existsSync19(operation.path) && lstatSync4(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
|
|
14572
14670
|
if (operation.kind === "verify-egress") {
|
|
14573
14671
|
const active = await fixed(["/usr/bin/systemctl", "is-active", "forgezero-agent-egress.service"]);
|
|
14574
14672
|
if (active.exitCode !== 0)
|
|
@@ -14590,25 +14688,25 @@ var runProvisionOperation = async (operation) => {
|
|
|
14590
14688
|
}
|
|
14591
14689
|
}
|
|
14592
14690
|
if (operation.kind === "install-warp") {
|
|
14593
|
-
const os =
|
|
14691
|
+
const os = readFileSync16("/etc/os-release", "utf8");
|
|
14594
14692
|
if (!/^ID=ubuntu$/m.test(os) || !/^VERSION_ID="?26\.04"?$/m.test(os))
|
|
14595
14693
|
return { stdout: "unsupported WARP host OS", exitCode: 1 };
|
|
14596
14694
|
const response = await fetch("https://pkg.cloudflareclient.com/pubkey.gpg", { signal: AbortSignal.timeout(30000) });
|
|
14597
14695
|
if (!response.ok)
|
|
14598
14696
|
return { stdout: `WARP key HTTP ${response.status}`, exitCode: 1 };
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14697
|
+
mkdirSync14("/usr/share/keyrings", { recursive: true, mode: 493 });
|
|
14698
|
+
mkdirSync14("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
|
|
14699
|
+
mkdirSync14("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
|
|
14602
14700
|
const key = "/run/cloudflare-warp-key.gpg";
|
|
14603
|
-
|
|
14701
|
+
writeFileSync14(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
|
|
14604
14702
|
let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
|
|
14605
|
-
|
|
14703
|
+
rmSync10(key, { force: true });
|
|
14606
14704
|
if (result.exitCode !== 0)
|
|
14607
14705
|
return result;
|
|
14608
14706
|
const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
|
|
14609
14707
|
if (!codename)
|
|
14610
14708
|
return { stdout: "Ubuntu codename missing", exitCode: 1 };
|
|
14611
|
-
|
|
14709
|
+
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
14710
|
`, { mode: 420 });
|
|
14613
14711
|
result = await fixed(["/usr/bin/apt-get", "update", "-qq"]);
|
|
14614
14712
|
return result.exitCode === 0 ? fixed(["/usr/bin/apt-get", "install", "-y", "cloudflare-warp"]) : result;
|
|
@@ -14623,7 +14721,7 @@ async function localRunner(operation) {
|
|
|
14623
14721
|
if (capability.kind === "version")
|
|
14624
14722
|
return fixed(capability.argv);
|
|
14625
14723
|
try {
|
|
14626
|
-
const metadata =
|
|
14724
|
+
const metadata = lstatSync4(capability.path);
|
|
14627
14725
|
const present = capability.nodeType === "directory" ? metadata.isDirectory() : true;
|
|
14628
14726
|
return { stdout: present ? `yes
|
|
14629
14727
|
` : `no
|
|
@@ -14670,6 +14768,7 @@ var SEED_CREDENTIAL = `${CREDS}/seed-sync-root.cred`;
|
|
|
14670
14768
|
var BACKUP_RECOVERY_CREDENTIAL = `${CREDS}/backup-recovery-root.cred`;
|
|
14671
14769
|
var BOOTSTRAP_SSH_CREDENTIAL = `${CREDS}/bootstrap-ssh-key.cred`;
|
|
14672
14770
|
var BOOTSTRAP_SSH_PUBLIC_KEY = "/etc/forgezero/bootstrap/runner.pub";
|
|
14771
|
+
var BOOTSTRAP_RELEASE_EVIDENCE = "/var/lib/forgezero/bootstrap-release.json";
|
|
14673
14772
|
var LIFECYCLE_PROFILE = "/etc/forgezero/lifecycle.json";
|
|
14674
14773
|
var CONTROL_SOCKET = "/run/forgezero/control.sock";
|
|
14675
14774
|
var CLOUDFLARED_METRICS_ADDRESS = "127.0.0.1:20241";
|
|
@@ -14862,19 +14961,19 @@ function localBootstrapHost() {
|
|
|
14862
14961
|
};
|
|
14863
14962
|
return {
|
|
14864
14963
|
uid: () => process.getuid?.() ?? -1,
|
|
14865
|
-
exists:
|
|
14866
|
-
read: (path2) =>
|
|
14964
|
+
exists: existsSync20,
|
|
14965
|
+
read: (path2) => readFileSync17(path2, "utf8"),
|
|
14867
14966
|
write(path2, content, mode) {
|
|
14868
|
-
|
|
14967
|
+
mkdirSync15(dirname10(path2), { recursive: true, mode: 493 });
|
|
14869
14968
|
const temporary = `${path2}.next.${process.pid}`;
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14969
|
+
writeFileSync15(temporary, content, { mode });
|
|
14970
|
+
chmodSync17(temporary, mode);
|
|
14971
|
+
renameSync12(temporary, path2);
|
|
14873
14972
|
},
|
|
14874
|
-
mkdir: (path2, mode) =>
|
|
14875
|
-
remove: (path2) =>
|
|
14973
|
+
mkdir: (path2, mode) => mkdirSync15(path2, { recursive: true, mode }),
|
|
14974
|
+
remove: (path2) => rmSync11(path2, { force: true }),
|
|
14876
14975
|
inspect(path2) {
|
|
14877
|
-
const value =
|
|
14976
|
+
const value = lstatSync5(path2);
|
|
14878
14977
|
return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
|
|
14879
14978
|
},
|
|
14880
14979
|
exec: execute3,
|
|
@@ -14896,7 +14995,12 @@ function localBootstrapHost() {
|
|
|
14896
14995
|
async installAgent(config) {
|
|
14897
14996
|
const capabilities = await readCapabilities(localRunner);
|
|
14898
14997
|
const deployRoot = config.deployRoot ?? "/opt/forgezero";
|
|
14899
|
-
const hasBinding =
|
|
14998
|
+
const hasBinding = existsSync20("/var/lib/forgezero/enrolment.json");
|
|
14999
|
+
const initialBundle = config.kind === "platform" && !existsSync20(BOOTSTRAP_RELEASE_EVIDENCE) ? {
|
|
15000
|
+
path: config.bootstrapBundle.bundleFile,
|
|
15001
|
+
manifestPath: config.bootstrapBundle.manifestFile,
|
|
15002
|
+
manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync17(config.bootstrapBundle.manifestFile, "utf8")))
|
|
15003
|
+
} : undefined;
|
|
14900
15004
|
if (config.kind === "platform") {
|
|
14901
15005
|
const lifecycle = config.database.role === "none" ? {
|
|
14902
15006
|
apiUnits: ["forgezero@blue.service", "forgezero@green.service"],
|
|
@@ -14908,8 +15012,8 @@ function localBootstrapHost() {
|
|
|
14908
15012
|
databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
|
|
14909
15013
|
databasePorts: [8529]
|
|
14910
15014
|
};
|
|
14911
|
-
|
|
14912
|
-
|
|
15015
|
+
mkdirSync15(dirname10(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
|
|
15016
|
+
writeFileSync15(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
|
|
14913
15017
|
`, { mode: 256 });
|
|
14914
15018
|
}
|
|
14915
15019
|
const plan = planInstall({
|
|
@@ -14917,15 +15021,17 @@ function localBootstrapHost() {
|
|
|
14917
15021
|
socketPath: DEFAULT_SOCKET,
|
|
14918
15022
|
seedPath: "/var/lib/forgezero/node.seed",
|
|
14919
15023
|
controlSocketPath: "/run/forgezero/control.sock",
|
|
14920
|
-
repository: config.repository,
|
|
14921
|
-
branch: config.branch,
|
|
15024
|
+
repository: initialBundle?.path ?? (config.kind === "enrolled-compute" ? config.repository : undefined),
|
|
15025
|
+
branch: initialBundle?.manifest.branch ?? (config.kind === "enrolled-compute" ? config.branch : undefined),
|
|
15026
|
+
bootstrapBundlePath: initialBundle?.path,
|
|
15027
|
+
bootstrapBundleManifestPath: initialBundle?.manifestPath,
|
|
14922
15028
|
profile: config.kind === "platform" ? config.profile : config.profile,
|
|
14923
15029
|
deployRoot,
|
|
14924
15030
|
deploymentCredentials: config.deploymentCredentials,
|
|
14925
15031
|
publicApiUrl: config.apiUrl,
|
|
14926
|
-
gitCredentialPath: "/etc/forgezero/creds/git-deploy-key.cred",
|
|
14927
|
-
gitPublicKeyPath: "/etc/forgezero/git/deploy.pub",
|
|
14928
|
-
generateGitIdentity: true,
|
|
15032
|
+
gitCredentialPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
|
|
15033
|
+
gitPublicKeyPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
|
|
15034
|
+
generateGitIdentity: config.kind === "enrolled-compute" && config.gitDeployKey === true,
|
|
14929
15035
|
pullDeployments: hasBinding,
|
|
14930
15036
|
pullMigrations: config.kind === "platform" && hasBinding,
|
|
14931
15037
|
pullBootstrap: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner),
|
|
@@ -14939,9 +15045,11 @@ function localBootstrapHost() {
|
|
|
14939
15045
|
telemetryEndpoint: config.telemetryEndpoint,
|
|
14940
15046
|
binPath: "/usr/local/lib/forgezero/agent/fz-agent",
|
|
14941
15047
|
sourceBinPath: PACKAGED_AGENT_BIN,
|
|
15048
|
+
...existsSync20(ENROL_CREDENTIAL) || hasBinding ? {
|
|
15049
|
+
enrolTokenCredentialPath: existsSync20(ENROL_CREDENTIAL) ? ENROL_CREDENTIAL : undefined,
|
|
15050
|
+
enrolStatePath: "/var/lib/forgezero/enrolment.json"
|
|
15051
|
+
} : {},
|
|
14942
15052
|
...hasBinding ? {
|
|
14943
|
-
enrolTokenCredentialPath: ENROL_CREDENTIAL,
|
|
14944
|
-
enrolStatePath: "/var/lib/forgezero/enrolment.json",
|
|
14945
15053
|
apiUrl: config.apiUrl,
|
|
14946
15054
|
project: config.kind === "enrolled-compute" ? config.realm : "platform",
|
|
14947
15055
|
environment: config.kind === "enrolled-compute" ? undefined : config.environment,
|
|
@@ -14949,8 +15057,8 @@ function localBootstrapHost() {
|
|
|
14949
15057
|
} : {}
|
|
14950
15058
|
});
|
|
14951
15059
|
for (const unit3 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
|
|
14952
|
-
|
|
14953
|
-
|
|
15060
|
+
mkdirSync15(dirname10(unit3.path), { recursive: true, mode: 493 });
|
|
15061
|
+
writeFileSync15(unit3.path, unit3.unit, { mode: 420 });
|
|
14954
15062
|
}
|
|
14955
15063
|
await applyPlan(plan, localRunner);
|
|
14956
15064
|
return plan;
|
|
@@ -14964,7 +15072,7 @@ var localRuntime = () => ({
|
|
|
14964
15072
|
bootstrapStatus: () => bootstrapStatus(),
|
|
14965
15073
|
characterDevice(path2) {
|
|
14966
15074
|
try {
|
|
14967
|
-
return
|
|
15075
|
+
return lstatSync6(path2).isCharacterDevice();
|
|
14968
15076
|
} catch {
|
|
14969
15077
|
return false;
|
|
14970
15078
|
}
|
|
@@ -15002,8 +15110,8 @@ async function verifyPlatformFleetHost(expectedVersion, runtime = localRuntime()
|
|
|
15002
15110
|
}
|
|
15003
15111
|
|
|
15004
15112
|
// src/community-rehearsal-host.ts
|
|
15005
|
-
import { createHash as
|
|
15006
|
-
import { lstatSync as
|
|
15113
|
+
import { createHash as createHash11 } from "crypto";
|
|
15114
|
+
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
15115
|
import { dirname as dirname11 } from "path";
|
|
15008
15116
|
var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
|
|
15009
15117
|
var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
|
|
@@ -15200,10 +15308,10 @@ async function exec(argv2, stdin) {
|
|
|
15200
15308
|
async function applyOperations(operations) {
|
|
15201
15309
|
for (const operation of operations) {
|
|
15202
15310
|
if (operation.kind === "remove-tree")
|
|
15203
|
-
|
|
15311
|
+
rmSync12(operation.path, { recursive: true, force: true });
|
|
15204
15312
|
else if (operation.kind === "write") {
|
|
15205
|
-
|
|
15206
|
-
|
|
15313
|
+
mkdirSync16(dirname11(operation.path), { recursive: true });
|
|
15314
|
+
writeFileSync16(operation.path, operation.content, { mode: operation.mode });
|
|
15207
15315
|
} else if (operation.kind === "unlink") {
|
|
15208
15316
|
try {
|
|
15209
15317
|
unlinkSync12(operation.path);
|
|
@@ -15251,8 +15359,8 @@ async function runCommunityRehearsalHost(request) {
|
|
|
15251
15359
|
return { ok: true, action: request.action, node: node.name };
|
|
15252
15360
|
}
|
|
15253
15361
|
if (request.action === "prepare") {
|
|
15254
|
-
const metadata =
|
|
15255
|
-
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 ||
|
|
15362
|
+
const metadata = lstatSync7(request.archivePath);
|
|
15363
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash11("sha256").update(readFileSync18(request.archivePath)).digest("hex") !== request.archiveSha256) {
|
|
15256
15364
|
throw new Error("community rehearsal archive is not the declared bounded release");
|
|
15257
15365
|
}
|
|
15258
15366
|
await ensureCommunityRehearsalArango();
|
|
@@ -15300,13 +15408,13 @@ async function runCommunityRehearsalHost(request) {
|
|
|
15300
15408
|
}
|
|
15301
15409
|
|
|
15302
15410
|
// src/supervised-app.ts
|
|
15303
|
-
import { lstatSync as
|
|
15411
|
+
import { lstatSync as lstatSync8, readFileSync as readFileSync19 } from "fs";
|
|
15304
15412
|
function readConfig(path2) {
|
|
15305
|
-
const stat =
|
|
15413
|
+
const stat = lstatSync8(path2);
|
|
15306
15414
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== 0 || (stat.mode & 18) !== 0 || stat.size > 128 * 1024) {
|
|
15307
15415
|
throw new Error("supervised app config is unsafe");
|
|
15308
15416
|
}
|
|
15309
|
-
const value = JSON.parse(
|
|
15417
|
+
const value = JSON.parse(readFileSync19(path2, "utf8"));
|
|
15310
15418
|
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
15419
|
throw new Error("supervised app config is malformed");
|
|
15312
15420
|
}
|
|
@@ -15352,17 +15460,17 @@ function agentCommandArgs(args, command2) {
|
|
|
15352
15460
|
return args.slice(index + 1);
|
|
15353
15461
|
}
|
|
15354
15462
|
function loadOrCreateSeed(path2) {
|
|
15355
|
-
if (
|
|
15356
|
-
const seed2 = new Uint8Array(Buffer.from(
|
|
15463
|
+
if (existsSync21(path2)) {
|
|
15464
|
+
const seed2 = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
|
|
15357
15465
|
if (seed2.length < 32) {
|
|
15358
15466
|
throw new Error(`agent: the seed at ${path2} is too short to derive a key from.`);
|
|
15359
15467
|
}
|
|
15360
15468
|
return seed2;
|
|
15361
15469
|
}
|
|
15362
|
-
|
|
15363
|
-
const seed = new Uint8Array(
|
|
15364
|
-
|
|
15365
|
-
|
|
15470
|
+
mkdirSync17(dirname12(path2), { recursive: true });
|
|
15471
|
+
const seed = new Uint8Array(randomBytes7(32));
|
|
15472
|
+
writeFileSync17(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
|
|
15473
|
+
chmodSync18(path2, 384);
|
|
15366
15474
|
return seed;
|
|
15367
15475
|
}
|
|
15368
15476
|
var DEFAULT_SOCKET_PATH = DEFAULT_SOCKET;
|
|
@@ -15391,9 +15499,9 @@ function loadSeedCredential(name = DEFAULT_SEED_CREDENTIAL, directory = process.
|
|
|
15391
15499
|
if (!directory)
|
|
15392
15500
|
throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the node identity.");
|
|
15393
15501
|
const path2 = `${directory}/${name}`;
|
|
15394
|
-
if (!
|
|
15502
|
+
if (!existsSync21(path2))
|
|
15395
15503
|
throw new Error(`agent: the systemd credential ${name} is missing at ${path2}.`);
|
|
15396
|
-
const seed = new Uint8Array(Buffer.from(
|
|
15504
|
+
const seed = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
|
|
15397
15505
|
if (seed.length < 32)
|
|
15398
15506
|
throw new Error(`agent: the systemd credential ${name} is too short to derive a key from.`);
|
|
15399
15507
|
return seed;
|
|
@@ -15403,7 +15511,7 @@ function loadTextCredential(name, directory = process.env.CREDENTIALS_DIRECTORY)
|
|
|
15403
15511
|
throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the credential.");
|
|
15404
15512
|
if (!/^[A-Za-z0-9_.-]+$/.test(name))
|
|
15405
15513
|
throw new Error("agent: invalid systemd credential name.");
|
|
15406
|
-
const value =
|
|
15514
|
+
const value = readFileSync20(`${directory}/${name}`, "utf8").trim();
|
|
15407
15515
|
if (!value)
|
|
15408
15516
|
throw new Error(`agent: systemd credential ${name} is empty.`);
|
|
15409
15517
|
return value;
|
|
@@ -15528,7 +15636,7 @@ if (import.meta.main) {
|
|
|
15528
15636
|
if (configPath !== "/etc/forgezero/deploy-activation.json" || !release) {
|
|
15529
15637
|
throw new Error("platform-activate requires the fixed config and one absolute release path");
|
|
15530
15638
|
}
|
|
15531
|
-
const config = JSON.parse(
|
|
15639
|
+
const config = JSON.parse(readFileSync20(configPath, "utf8"));
|
|
15532
15640
|
const result = await activatePlatformRelease(config, release);
|
|
15533
15641
|
console.log(`promoted ${result.release} on ${result.slot}`);
|
|
15534
15642
|
process.exit(0);
|
|
@@ -15553,13 +15661,13 @@ if (import.meta.main) {
|
|
|
15553
15661
|
if (args.length !== 1)
|
|
15554
15662
|
throw new Error("project-release-check accepts no coordinates");
|
|
15555
15663
|
for (const relative of ["src/index.ts", "bun.lock", "src/generated/shared/contract-manifest.json"]) {
|
|
15556
|
-
const stat =
|
|
15664
|
+
const stat = lstatSync9(join12(process.cwd(), relative));
|
|
15557
15665
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1) {
|
|
15558
15666
|
throw new Error(`project release is missing ${relative}`);
|
|
15559
15667
|
}
|
|
15560
15668
|
}
|
|
15561
15669
|
for (const relative of ["package.json", "bun.lock"]) {
|
|
15562
|
-
const contents =
|
|
15670
|
+
const contents = readFileSync20(join12(process.cwd(), relative), "utf8");
|
|
15563
15671
|
if (/(?:workspace|file):/.test(contents)) {
|
|
15564
15672
|
throw new Error(`project release ${relative} contains a local dependency`);
|
|
15565
15673
|
}
|
|
@@ -15604,8 +15712,8 @@ if (import.meta.main) {
|
|
|
15604
15712
|
keys: keys2,
|
|
15605
15713
|
label: process.env.FZ_NODE_LABEL,
|
|
15606
15714
|
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 ?
|
|
15715
|
+
gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_GIT_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
|
|
15716
|
+
bootstrapSshPublicKey: process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
|
|
15609
15717
|
privateNetworkAttachment: privateNetworkAttachmentFromEnvironment()
|
|
15610
15718
|
});
|
|
15611
15719
|
console.log(`[agent] enrolled ${binding2.computeReference} in project ${binding2.projectKey}/${binding2.environmentKey}`);
|
|
@@ -15647,7 +15755,7 @@ if (import.meta.main) {
|
|
|
15647
15755
|
const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
|
|
15648
15756
|
if (!profilePath)
|
|
15649
15757
|
throw new Error("metal-helper requires --profile=/absolute/path.json");
|
|
15650
|
-
const profile2 = JSON.parse(
|
|
15758
|
+
const profile2 = JSON.parse(readFileSync20(profilePath, "utf8"));
|
|
15651
15759
|
const helper = startMetalHelper({
|
|
15652
15760
|
profile: profile2,
|
|
15653
15761
|
socketPath: process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET
|
|
@@ -15797,7 +15905,7 @@ if (import.meta.main) {
|
|
|
15797
15905
|
const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
|
|
15798
15906
|
if (!profilePath)
|
|
15799
15907
|
throw new Error("metal-isolation requires --profile=/absolute/path.json");
|
|
15800
|
-
const profile2 = JSON.parse(
|
|
15908
|
+
const profile2 = JSON.parse(readFileSync20(profilePath, "utf8"));
|
|
15801
15909
|
await applyMetalIsolation(profile2);
|
|
15802
15910
|
console.log("[metal-isolation] host and guest cgroup boundaries active");
|
|
15803
15911
|
process.exit(0);
|
|
@@ -15842,7 +15950,7 @@ if (import.meta.main) {
|
|
|
15842
15950
|
if (claimArg !== "-" && !claimArg.startsWith("/")) {
|
|
15843
15951
|
throw new Error("metal-apply claim path must be absolute");
|
|
15844
15952
|
}
|
|
15845
|
-
const raw =
|
|
15953
|
+
const raw = readFileSync20(claimArg === "-" ? "/dev/stdin" : claimArg, "utf8");
|
|
15846
15954
|
if (Buffer.byteLength(raw) > 32 * 1024)
|
|
15847
15955
|
throw new Error("metal-apply claim exceeds 32 KiB");
|
|
15848
15956
|
const claim = JSON.parse(raw);
|
|
@@ -15867,7 +15975,7 @@ if (import.meta.main) {
|
|
|
15867
15975
|
if (commandArgs.length !== 1 || commandArgs[0] !== "--request=-") {
|
|
15868
15976
|
throw new Error("community-rehearsal requires exactly --request=-");
|
|
15869
15977
|
}
|
|
15870
|
-
const raw =
|
|
15978
|
+
const raw = readFileSync20("/dev/stdin", "utf8");
|
|
15871
15979
|
if (Buffer.byteLength(raw) > 128 * 1024)
|
|
15872
15980
|
throw new Error("community rehearsal request exceeds 128 KiB");
|
|
15873
15981
|
const request = parseCommunityRehearsalHostRequest(JSON.parse(raw));
|
|
@@ -15878,7 +15986,7 @@ if (import.meta.main) {
|
|
|
15878
15986
|
if (args.length !== 1 || args[0] !== "--request=-") {
|
|
15879
15987
|
throw new Error("metal-admission-proof requires exactly --request=-");
|
|
15880
15988
|
}
|
|
15881
|
-
const raw =
|
|
15989
|
+
const raw = readFileSync20("/dev/stdin", "utf8");
|
|
15882
15990
|
if (Buffer.byteLength(raw) > 32 * 1024)
|
|
15883
15991
|
throw new Error("metal admission proof request exceeds 32 KiB");
|
|
15884
15992
|
const request = JSON.parse(raw);
|
|
@@ -15896,9 +16004,9 @@ if (import.meta.main) {
|
|
|
15896
16004
|
vcpu: cpus().length,
|
|
15897
16005
|
memoryGib: Math.max(1, Math.floor(totalmem() / 1024 ** 3)),
|
|
15898
16006
|
diskGib: Math.max(1, Math.floor(Number(filesystem.blocks) * Number(filesystem.bsize) / 1024 ** 3)),
|
|
15899
|
-
kvm:
|
|
15900
|
-
snpHost:
|
|
15901
|
-
helper:
|
|
16007
|
+
kvm: existsSync21("/dev/kvm"),
|
|
16008
|
+
snpHost: existsSync21("/dev/sev"),
|
|
16009
|
+
helper: existsSync21(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
|
|
15902
16010
|
}
|
|
15903
16011
|
};
|
|
15904
16012
|
const envelope = signRequest(keys2, keys2.ed25519.publicKey, {
|
|
@@ -15933,9 +16041,9 @@ if (import.meta.main) {
|
|
|
15933
16041
|
metalHostname: process.env.FZ_METAL_HOSTNAME,
|
|
15934
16042
|
run: (claim) => requestMetalProvision(claim, process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET),
|
|
15935
16043
|
metalPreflight: () => ({
|
|
15936
|
-
snpHost:
|
|
15937
|
-
kvm:
|
|
15938
|
-
helper:
|
|
16044
|
+
snpHost: existsSync21("/dev/sev"),
|
|
16045
|
+
kvm: existsSync21("/dev/kvm"),
|
|
16046
|
+
helper: existsSync21(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
|
|
15939
16047
|
}),
|
|
15940
16048
|
onEvent: (event, detail) => console.log(`[metal-agent] ${event}${detail ? ` ${JSON.stringify(detail)}` : ""}`)
|
|
15941
16049
|
});
|
|
@@ -16018,7 +16126,7 @@ if (import.meta.main) {
|
|
|
16018
16126
|
const telemetry = new AgentTelemetryRuntime(createAgentTelemetry(resolveAgentTelemetryConfig()));
|
|
16019
16127
|
telemetry.event("agent.started");
|
|
16020
16128
|
telemetry.setDraining(false);
|
|
16021
|
-
const attestationSource =
|
|
16129
|
+
const attestationSource = existsSync21("/dev/sev-guest") ? createSnpAttestationSource() : undefined;
|
|
16022
16130
|
const running = runAgent({
|
|
16023
16131
|
socketPath: process.env.FZ_SOCKET_PATH ?? DEFAULT_SOCKET_PATH,
|
|
16024
16132
|
seedCredential: process.env.FZ_SEED_CREDENTIAL,
|
|
@@ -16033,8 +16141,8 @@ if (import.meta.main) {
|
|
|
16033
16141
|
const enrolmentStatePath = process.env.FZ_ENROL_STATE_FILE ?? DEFAULT_ENROLMENT_STATE_PATH;
|
|
16034
16142
|
let binding = loadGuestBinding(enrolmentStatePath, nodeKey);
|
|
16035
16143
|
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 &&
|
|
16144
|
+
const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY && existsSync21(`${process.env.CREDENTIALS_DIRECTORY}/${enrolmentCredential}`));
|
|
16145
|
+
const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE && existsSync21(process.env.FZ_ENROL_TOKEN_FILE));
|
|
16038
16146
|
if (!binding && (enrolmentCredentialAvailable || legacyEnrolmentFileAvailable) && process.env.FZ_API) {
|
|
16039
16147
|
binding = await enrolGuestIdentity({
|
|
16040
16148
|
apiUrl: process.env.FZ_API,
|
|
@@ -16045,8 +16153,8 @@ if (import.meta.main) {
|
|
|
16045
16153
|
keys,
|
|
16046
16154
|
label: process.env.FZ_NODE_LABEL,
|
|
16047
16155
|
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 ?
|
|
16156
|
+
gitDeployPublicKey: process.env.FZ_GIT_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_GIT_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
|
|
16157
|
+
bootstrapSshPublicKey: process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE ? readFileSync20(process.env.FZ_BOOTSTRAP_SSH_PUBLIC_KEY_FILE, "utf8").trim() : undefined,
|
|
16050
16158
|
privateNetworkAttachment: privateNetworkAttachmentFromEnvironment()
|
|
16051
16159
|
});
|
|
16052
16160
|
console.log(`[agent] enrolled ${binding.computeReference} in project ${binding.projectKey}/${binding.environmentKey}`);
|
|
@@ -16112,7 +16220,7 @@ if (import.meta.main) {
|
|
|
16112
16220
|
const bootstrapEnabled = process.env.FZ_BOOTSTRAP_PULL === "true";
|
|
16113
16221
|
const bootstrapCredential = process.env.FZ_BOOTSTRAP_SSH_KEY_CREDENTIAL;
|
|
16114
16222
|
const bootstrapKeyPath = bootstrapCredential && process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/${bootstrapCredential}` : undefined;
|
|
16115
|
-
if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !
|
|
16223
|
+
if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !existsSync21(bootstrapKeyPath) || !process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT)) {
|
|
16116
16224
|
throw new Error("agent: bootstrap runner requires enrolment, API, local SSH credential and target OTLP coordinate");
|
|
16117
16225
|
}
|
|
16118
16226
|
const bootstrapPull = bootstrapEnabled ? startSshBootstrapPull({
|
|
@@ -16149,12 +16257,21 @@ if (import.meta.main) {
|
|
|
16149
16257
|
const repository = process.env.FZ_DEPLOY_REPO;
|
|
16150
16258
|
const branch = process.env.FZ_DEPLOY_BRANCH;
|
|
16151
16259
|
const profile = process.env.FZ_DEPLOY_PROFILE;
|
|
16260
|
+
const bootstrapBundlePath = process.env.FZ_BOOTSTRAP_BUNDLE;
|
|
16261
|
+
const bootstrapBundleManifestPath = process.env.FZ_BOOTSTRAP_BUNDLE_MANIFEST;
|
|
16262
|
+
if (Boolean(bootstrapBundlePath) !== Boolean(bootstrapBundleManifestPath)) {
|
|
16263
|
+
throw new Error("agent: bootstrap bundle and manifest coordinates must be supplied together");
|
|
16264
|
+
}
|
|
16265
|
+
const bootstrapBundle = bootstrapBundlePath && bootstrapBundleManifestPath ? {
|
|
16266
|
+
path: bootstrapBundlePath,
|
|
16267
|
+
manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync20(bootstrapBundleManifestPath, "utf8")))
|
|
16268
|
+
} : undefined;
|
|
16152
16269
|
const root = process.env.FZ_DEPLOY_ROOT;
|
|
16153
16270
|
const pullEnabled = process.env.FZ_DEPLOY_PULL === "true" && Boolean(process.env.FZ_API);
|
|
16154
16271
|
if (pullEnabled && !binding) {
|
|
16155
16272
|
throw new Error("agent: outbound guest deployment requires a persisted tenant enrolment binding");
|
|
16156
16273
|
}
|
|
16157
|
-
if (root && (repository && branch && profile || pullEnabled)) {
|
|
16274
|
+
if (root && (repository && branch && profile || bootstrapBundle && branch && profile || pullEnabled)) {
|
|
16158
16275
|
const managers = new Map;
|
|
16159
16276
|
const managerOptions = (source, key) => ({
|
|
16160
16277
|
key,
|
|
@@ -16181,7 +16298,10 @@ if (import.meta.main) {
|
|
|
16181
16298
|
activateContainer: (request) => requestContainerActivation(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
|
|
16182
16299
|
projectExec: process.env.FZ_DEPLOY_RUNNER_SOCKET ? (input) => requestDeploymentCommand(input, process.env.FZ_DEPLOY_RUNNER_SOCKET) : undefined
|
|
16183
16300
|
});
|
|
16184
|
-
const staticManager = repository && branch && profile ? createDeploymentManager(managerOptions({ repository, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `${repository}:${branch}:${profile}`)) :
|
|
16301
|
+
const staticManager = repository && branch && profile ? createDeploymentManager(managerOptions({ repository, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `${repository}:${branch}:${profile}`)) : bootstrapBundle && branch && profile ? createDeploymentManager({
|
|
16302
|
+
...managerOptions({ repository: bootstrapBundle.path, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `bootstrap:${bootstrapBundle.manifest.sha256}:${profile}`),
|
|
16303
|
+
bootstrapBundle
|
|
16304
|
+
}) : undefined;
|
|
16185
16305
|
if (staticManager)
|
|
16186
16306
|
managers.set("__static__", staticManager);
|
|
16187
16307
|
const control = staticManager ? startControlServer(staticManager, process.env.FZ_CONTROL_SOCKET ?? DEFAULT_CONTROL_SOCKET) : undefined;
|
|
@@ -16196,6 +16316,9 @@ if (import.meta.main) {
|
|
|
16196
16316
|
canClaim: () => deploymentIntake?.state === "running",
|
|
16197
16317
|
manager: staticManager,
|
|
16198
16318
|
managerFor: staticManager ? undefined : (claim) => {
|
|
16319
|
+
if (claim.source.auth?.kind === "ephemeral-token") {
|
|
16320
|
+
return createDeploymentManager(managerOptions(claim.source, claim.pipelineKey));
|
|
16321
|
+
}
|
|
16199
16322
|
const cacheKey = [
|
|
16200
16323
|
claim.pipelineKey,
|
|
16201
16324
|
claim.source.repository,
|