@forgezero/agent 0.1.85 → 0.1.87

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.
@@ -16,6 +16,7 @@ import {
16
16
  } from "node:fs";
17
17
  import { dirname, join, resolve } from "node:path";
18
18
  var DEFAULT_AGENT_RELEASE_ROOT = "/opt/forgezero/agent";
19
+ var DEFAULT_AGENT_CANDIDATE_LINK = `${DEFAULT_AGENT_RELEASE_ROOT}/candidate`;
19
20
  var DEFAULT_AGENT_UPDATE_SOCKET = "/run/forgezero-update/helper.sock";
20
21
  var MAX_AGENT_TARBALL_BYTES = 32 * 1024 * 1024;
21
22
  var VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
@@ -209,25 +210,90 @@ function restoreAgentRelease(staged) {
209
210
  rmSync(next, { force: true });
210
211
  }
211
212
  }
213
+ function selectAgentCandidate(staged) {
214
+ const link = join(dirname(staged.currentLink), "candidate");
215
+ const next = `${link}.${randomUUID()}.next`;
216
+ try {
217
+ symlinkSync(staged.nextTarget, next);
218
+ renameSync(next, link);
219
+ syncPath(dirname(link));
220
+ } finally {
221
+ rmSync(next, { force: true });
222
+ }
223
+ }
224
+ function clearAgentCandidate(root = DEFAULT_AGENT_RELEASE_ROOT) {
225
+ rmSync(join(resolve(root), "candidate"), { force: true });
226
+ }
212
227
 
213
228
  // src/agent-update-helper.ts
214
- import { randomUUID as randomUUID2 } from "node:crypto";
229
+ import { randomUUID as randomUUID3 } from "node:crypto";
215
230
  import {
216
- chmodSync as chmodSync2,
231
+ chmodSync as chmodSync3,
217
232
  closeSync as closeSync2,
218
- existsSync as existsSync2,
233
+ existsSync as existsSync3,
219
234
  fsyncSync as fsyncSync2,
220
235
  mkdirSync as mkdirSync2,
221
236
  openSync as openSync2,
222
237
  readFileSync as readFileSync2,
223
- renameSync as renameSync2,
224
- rmSync as rmSync2,
225
- unlinkSync,
238
+ renameSync as renameSync3,
239
+ rmSync as rmSync3,
240
+ unlinkSync as unlinkSync2,
226
241
  writeFileSync as writeFileSync2
227
242
  } from "node:fs";
228
- import { connect, createServer } from "node:net";
229
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
243
+ import { connect as connect2, createServer as createServer2 } from "node:net";
244
+ import { dirname as dirname3, join as join2, resolve as resolve2 } from "node:path";
230
245
  import { DEFAULT_SOCKET } from "@forgezero/vault";
246
+
247
+ // src/agent-handover.ts
248
+ import { randomUUID as randomUUID2 } from "node:crypto";
249
+ import { chmodSync as chmodSync2, existsSync as existsSync2, renameSync as renameSync2, rmSync as rmSync2, symlinkSync as symlinkSync2, unlinkSync } from "node:fs";
250
+ import { basename, dirname as dirname2 } from "node:path";
251
+ import { connect, createServer } from "node:net";
252
+ var DEFAULT_AGENT_CANDIDATE_READY_SOCKET = "/run/forgezero/candidate-ready.sock";
253
+ function switchAgentSocketRoute(route, backend) {
254
+ if (dirname2(route) !== dirname2(backend))
255
+ throw new Error("Agent handover sockets must share one runtime directory");
256
+ const target = basename(backend);
257
+ const next = `${route}.${randomUUID2()}.next`;
258
+ try {
259
+ symlinkSync2(target, next);
260
+ renameSync2(next, route);
261
+ } finally {
262
+ rmSync2(next, { force: true });
263
+ }
264
+ }
265
+ function probeAgentCandidate(expected, socketPath = DEFAULT_AGENT_CANDIDATE_READY_SOCKET, timeoutMs = 5000) {
266
+ return new Promise((resolve2) => {
267
+ const socket = connect(socketPath);
268
+ let buffer = "";
269
+ let settled = false;
270
+ const finish = (value) => {
271
+ if (settled)
272
+ return;
273
+ settled = true;
274
+ clearTimeout(timer);
275
+ socket.destroy();
276
+ resolve2(value);
277
+ };
278
+ const timer = setTimeout(() => finish(false), timeoutMs);
279
+ socket.on("data", (chunk) => {
280
+ buffer += chunk.toString("utf8");
281
+ const newline = buffer.indexOf(`
282
+ `);
283
+ if (newline < 0)
284
+ return;
285
+ try {
286
+ const value = JSON.parse(buffer.slice(0, newline));
287
+ finish(value.version === expected.version && typeof value.nodeKey === "string" && typeof value.bound === "boolean" && ["ready", "partial", "unavailable", "unbound"].includes(value.vault ?? ""));
288
+ } catch {
289
+ finish(false);
290
+ }
291
+ });
292
+ socket.on("error", () => finish(false));
293
+ });
294
+ }
295
+
296
+ // src/agent-update-helper.ts
231
297
  var AGENT_UPDATE_GROUP = "forgezero-update";
232
298
  var AGENT_UPDATE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-agent-update-helper.service";
233
299
  var AGENT_UPDATE_JOURNAL = "/var/lib/forgezero/agent-update.json";
@@ -322,13 +388,13 @@ function validateJournal(value, root) {
322
388
  return journal;
323
389
  }
324
390
  function readJournal(path, root) {
325
- if (!existsSync2(path))
391
+ if (!existsSync3(path))
326
392
  return;
327
393
  return validateJournal(JSON.parse(readFileSync2(path, "utf8")), root);
328
394
  }
329
395
  function writeAtomic(path, value, mode) {
330
- mkdirSync2(dirname2(path), { recursive: true, mode: 493 });
331
- const next = `${path}.${randomUUID2()}.next`;
396
+ mkdirSync2(dirname3(path), { recursive: true, mode: 493 });
397
+ const next = `${path}.${randomUUID3()}.next`;
332
398
  let file;
333
399
  try {
334
400
  file = openSync2(next, "wx", mode);
@@ -337,8 +403,8 @@ function writeAtomic(path, value, mode) {
337
403
  fsyncSync2(file);
338
404
  closeSync2(file);
339
405
  file = undefined;
340
- renameSync2(next, path);
341
- const directory = openSync2(dirname2(path), "r");
406
+ renameSync3(next, path);
407
+ const directory = openSync2(dirname3(path), "r");
342
408
  try {
343
409
  fsyncSync2(directory);
344
410
  } finally {
@@ -347,7 +413,7 @@ function writeAtomic(path, value, mode) {
347
413
  } finally {
348
414
  if (file !== undefined)
349
415
  closeSync2(file);
350
- rmSync2(next, { force: true });
416
+ rmSync3(next, { force: true });
351
417
  }
352
418
  }
353
419
  var publicReceipt = (journal) => {
@@ -380,7 +446,7 @@ function writeUpdateState(journalPath, receiptPath, journal) {
380
446
  }
381
447
  function readAgentUpdateReceipt(path = AGENT_UPDATE_RECEIPT) {
382
448
  try {
383
- if (!existsSync2(path))
449
+ if (!existsSync3(path))
384
450
  return;
385
451
  return validateReceipt(JSON.parse(readFileSync2(path, "utf8")));
386
452
  } catch {
@@ -420,10 +486,32 @@ var restartAgent = async (target, run) => {
420
486
  throw new Error(`systemd could not restart ${service}`);
421
487
  }
422
488
  };
489
+ var candidateUnit = (target) => target === "compute" ? "forgezero-agent-candidate.service" : "forgezero-metal-agent-candidate.service";
490
+ var startCandidate = async (staged, target, run, probe = (expected) => probeAgentCandidate(expected, DEFAULT_AGENT_CANDIDATE_READY_SOCKET)) => {
491
+ selectAgentCandidate(staged);
492
+ if (!await runOk(run, "/usr/bin/systemctl", ["restart", candidateUnit(target)])) {
493
+ clearAgentCandidate(dirname3(staged.currentLink));
494
+ throw new Error(`systemd could not start ${candidateUnit(target)}`);
495
+ }
496
+ if (!await probe({ version: staged.version })) {
497
+ await run({ command: "/usr/bin/systemctl", args: ["stop", candidateUnit(target)] });
498
+ clearAgentCandidate(dirname3(staged.currentLink));
499
+ throw new Error("the candidate Agent did not prove its identity and durable state");
500
+ }
501
+ };
502
+ var stopCandidate = async (staged, target, run) => {
503
+ await run({ command: "/usr/bin/systemctl", args: ["stop", candidateUnit(target)] });
504
+ clearAgentCandidate(dirname3(staged.currentLink));
505
+ };
506
+ var socketPaths = (publicSocket = process.env.FZ_AGENT_PUBLIC_SOCKET ?? DEFAULT_SOCKET) => ({
507
+ route: `${publicSocket}.backend`,
508
+ active: `${publicSocket}.backend.active`,
509
+ candidate: `${publicSocket}.backend.candidate`
510
+ });
423
511
  var targetProbe = (target, run) => target === "compute" ? () => probeAgentSocket() : async () => await runOk(run, "/usr/bin/systemctl", ["is-active", "--quiet", "forgezero-metal-agent.service"]) && await runOk(run, "/usr/bin/systemctl", ["is-active", "--quiet", "forgezero-metal-helper.service"]);
424
512
  function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
425
513
  return new Promise((resolve3) => {
426
- const socket = connect(socketPath);
514
+ const socket = connect2(socketPath);
427
515
  let settled = false;
428
516
  let buffer = "";
429
517
  const finish = (value) => {
@@ -456,12 +544,13 @@ function probeAgentSocket(socketPath = DEFAULT_SOCKET, timeoutMs = 5000) {
456
544
  async function activateAgentRelease(staged, options = {}) {
457
545
  const run = options.run ?? runCommand;
458
546
  const target = options.target ?? "compute";
459
- const probe = options.probe ?? targetProbe(target, run);
547
+ const paths = socketPaths(options.publicSocketPath);
548
+ const probe = options.probe ?? (target === "compute" ? () => probeAgentSocket(paths.active) : targetProbe(target, run));
460
549
  const now = options.now ?? Date.now;
461
550
  const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
462
551
  const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
463
- const previous = readJournal(journalPath, dirname2(staged.currentLink));
464
- const attemptId = options.attemptId ?? randomUUID2();
552
+ const previous = readJournal(journalPath, dirname3(staged.currentLink));
553
+ const attemptId = options.attemptId ?? randomUUID3();
465
554
  if (!ATTEMPT_ID.test(attemptId))
466
555
  throw new Error("Agent update attempt ID is invalid");
467
556
  const startedAtTs = now();
@@ -483,11 +572,18 @@ async function activateAgentRelease(staged, options = {}) {
483
572
  writeUpdateState(journalPath, receiptPath, journal);
484
573
  let selectionAttempted = false;
485
574
  try {
575
+ if (!options.candidatePrepared)
576
+ await startCandidate(staged, target, run);
577
+ if (target === "compute")
578
+ switchAgentSocketRoute(paths.route, paths.candidate);
486
579
  selectionAttempted = true;
487
580
  selectAgentRelease(staged);
488
581
  await restartAgent(target, run);
489
582
  if (!await probe())
490
- throw new Error("the replacement Agent did not answer its retained Vault socket");
583
+ throw new Error("the replacement Agent did not answer its active Vault backend");
584
+ if (target === "compute")
585
+ switchAgentSocketRoute(paths.route, paths.active);
586
+ await stopCandidate(staged, target, run);
491
587
  journal = { ...journal, outcome: "active", updatedAtTs: now(), rollbackHealthy: undefined };
492
588
  writeUpdateState(journalPath, receiptPath, journal);
493
589
  run({
@@ -505,10 +601,13 @@ async function activateAgentRelease(staged, options = {}) {
505
601
  restored = true;
506
602
  await restartAgent(target, run);
507
603
  rollbackHealthy = await probe();
604
+ if (rollbackHealthy && target === "compute")
605
+ switchAgentSocketRoute(paths.route, paths.active);
508
606
  } catch {
509
607
  rollbackHealthy = false;
510
608
  }
511
609
  }
610
+ await stopCandidate(staged, target, run).catch(() => {});
512
611
  const failures = failureCount + 1;
513
612
  const updatedAtTs = now();
514
613
  journal = {
@@ -528,28 +627,38 @@ async function recoverInterruptedAgentUpdate(options = {}) {
528
627
  const root = resolve2(options.root ?? DEFAULT_AGENT_RELEASE_ROOT);
529
628
  const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
530
629
  const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
630
+ const run = options.run ?? runCommand;
531
631
  const journal = readJournal(journalPath, root);
532
- if (!journal)
632
+ if (!journal) {
633
+ for (const target of ["compute", "metal"]) {
634
+ await run({ command: "/usr/bin/systemctl", args: ["stop", candidateUnit(target)] });
635
+ }
636
+ clearAgentCandidate(root);
533
637
  return;
638
+ }
534
639
  if (journal.outcome !== "activating") {
640
+ await stopCandidate(stagedFromJournal(journal, root), journal.target, run).catch(() => {});
535
641
  writeAtomic(receiptPath, publicReceipt(journal), 416);
536
642
  return publicReceipt(journal);
537
643
  }
538
644
  const staged = stagedFromJournal(journal, root);
539
- if (!existsSync2(join2(root, journal.previousTarget))) {
645
+ if (!existsSync3(join2(root, journal.previousTarget))) {
540
646
  throw new Error("Agent update rollback release is missing");
541
647
  }
542
- const run = options.run ?? runCommand;
543
- const probe = options.probe ?? targetProbe(journal.target, run);
648
+ const paths = socketPaths(options.publicSocketPath);
649
+ const probe = options.probe ?? (journal.target === "compute" ? () => probeAgentSocket(paths.active) : targetProbe(journal.target, run));
544
650
  restoreAgentRelease(staged);
545
651
  let rollbackHealthy = false;
546
652
  let failureMessage = "activation was interrupted before its health verdict became durable";
547
653
  try {
548
654
  await restartAgent(journal.target, run);
549
655
  rollbackHealthy = await probe();
656
+ if (rollbackHealthy && journal.target === "compute")
657
+ switchAgentSocketRoute(paths.route, paths.active);
550
658
  } catch (cause) {
551
659
  failureMessage = `${failureMessage}; ${cause instanceof Error ? cause.message : String(cause)}`;
552
660
  }
661
+ await stopCandidate(staged, journal.target, run).catch(() => {});
553
662
  const failures = journal.failureCount + 1;
554
663
  const updatedAtTs = (options.now ?? Date.now)();
555
664
  const recovered = {
@@ -566,15 +675,23 @@ async function recoverInterruptedAgentUpdate(options = {}) {
566
675
  }
567
676
  function startAgentUpdateHelper(options = {}) {
568
677
  const socketPath = options.socketPath ?? DEFAULT_AGENT_UPDATE_SOCKET;
569
- if (existsSync2(socketPath))
570
- unlinkSync(socketPath);
571
- mkdirSync2(dirname2(socketPath), { recursive: true, mode: 488 });
572
- const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
678
+ if (existsSync3(socketPath))
679
+ unlinkSync2(socketPath);
680
+ mkdirSync2(dirname3(socketPath), { recursive: true, mode: 488 });
573
681
  const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
574
682
  const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
575
683
  const releaseRoot = options.root ?? DEFAULT_AGENT_RELEASE_ROOT;
576
- const activate = options.activate ?? ((staged, target, attemptId) => activateAgentRelease(staged, { target, attemptId, journalPath, receiptPath, now: options.now }));
684
+ const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
685
+ const activate = options.activate ?? ((staged, target, attemptId) => activateAgentRelease(staged, {
686
+ target,
687
+ attemptId,
688
+ journalPath,
689
+ receiptPath,
690
+ now: options.now,
691
+ candidatePrepared: true
692
+ }));
577
693
  let busy = true;
694
+ let pending;
578
695
  let blocked;
579
696
  (options.recover ?? (() => recoverInterruptedAgentUpdate({
580
697
  root: releaseRoot,
@@ -586,7 +703,7 @@ function startAgentUpdateHelper(options = {}) {
586
703
  }).finally(() => {
587
704
  busy = false;
588
705
  });
589
- const server = createServer((socket) => {
706
+ const server = createServer2((socket) => {
590
707
  let buffer = "";
591
708
  socket.on("data", (chunk) => {
592
709
  buffer += chunk.toString("utf8");
@@ -605,16 +722,49 @@ function startAgentUpdateHelper(options = {}) {
605
722
  Promise.resolve().then(() => JSON.parse(line)).then(async (request) => {
606
723
  if (blocked)
607
724
  throw new Error(`update journal needs operator recovery: ${blocked}`);
608
- if (busy)
609
- throw new Error("another Agent update or recovery is already active");
610
- if (request.op !== "apply")
611
- throw new Error("unknown update operation");
612
725
  if (request.target !== "compute" && request.target !== "metal") {
613
726
  throw new Error("agent update target is invalid");
614
727
  }
615
- const attemptId = request.attemptId ?? randomUUID2();
728
+ const attemptId = request.attemptId ?? randomUUID3();
616
729
  if (!ATTEMPT_ID.test(attemptId))
617
730
  throw new Error("Agent update attempt ID is invalid");
731
+ if (request.op === "commit" || request.op === "abort") {
732
+ if (!pending || pending.attemptId !== attemptId || pending.target !== request.target || pending.staged.fromVersion !== request.currentVersion || pending.staged.version !== request.targetVersion) {
733
+ throw new Error("Agent update handover does not match the prepared candidate");
734
+ }
735
+ const selected = pending;
736
+ pending = undefined;
737
+ if (request.op === "abort") {
738
+ await stopCandidate(selected.staged, selected.target, runCommand);
739
+ busy = false;
740
+ const response3 = {
741
+ ok: true,
742
+ status: "aborted",
743
+ version: selected.staged.version,
744
+ attemptId
745
+ };
746
+ socket.end(`${JSON.stringify(response3)}
747
+ `);
748
+ return;
749
+ }
750
+ const outcome = await activate(selected.staged, selected.target, attemptId);
751
+ busy = false;
752
+ if (outcome?.ok === false)
753
+ throw new Error(outcome.reason ?? "Agent handover failed");
754
+ const response2 = {
755
+ ok: true,
756
+ status: "active",
757
+ version: selected.staged.version,
758
+ attemptId
759
+ };
760
+ socket.end(`${JSON.stringify(response2)}
761
+ `);
762
+ return;
763
+ }
764
+ if (request.op !== "prepare")
765
+ throw new Error("unknown update operation");
766
+ if (busy)
767
+ throw new Error("another Agent update or recovery is already active");
618
768
  const prior = readJournal(journalPath, releaseRoot);
619
769
  const now = (options.now ?? Date.now)();
620
770
  if (prior?.targetVersion === request.release.version && (prior.outcome === "rolled-back" || prior.outcome === "failed") && (prior.retryAfterTs ?? 0) > now)
@@ -625,17 +775,23 @@ function startAgentUpdateHelper(options = {}) {
625
775
  currentVersion: request.currentVersion,
626
776
  root: releaseRoot
627
777
  });
628
- const response = { ok: true, status: "staged", version: staged.version, attemptId };
778
+ await startCandidate(staged, request.target, runCommand);
779
+ pending = { staged, target: request.target, attemptId };
780
+ ownsBusy = false;
781
+ setTimer(() => {
782
+ if (pending?.attemptId !== attemptId)
783
+ return;
784
+ const expired = pending;
785
+ pending = undefined;
786
+ stopCandidate(expired.staged, expired.target, runCommand).finally(() => {
787
+ busy = false;
788
+ });
789
+ }, 180000);
790
+ const response = { ok: true, status: "prepared", version: staged.version, attemptId };
629
791
  socket.end(`${JSON.stringify(response)}
630
792
  `);
631
- setTimer(() => void activate(staged, request.target, attemptId).catch((cause) => {
632
- blocked = cause instanceof Error ? cause.message : String(cause);
633
- }).finally(() => {
634
- busy = false;
635
- }), 100);
636
- ownsBusy = false;
637
793
  }).catch((cause) => {
638
- if (ownsBusy)
794
+ if (ownsBusy && !pending)
639
795
  busy = false;
640
796
  const response = {
641
797
  ok: false,
@@ -647,12 +803,12 @@ function startAgentUpdateHelper(options = {}) {
647
803
  });
648
804
  socket.on("error", () => socket.destroy());
649
805
  });
650
- server.listen(socketPath, () => chmodSync2(socketPath, 432));
806
+ server.listen(socketPath, () => chmodSync3(socketPath, 432));
651
807
  return server;
652
808
  }
653
809
  function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, timeoutMs = 90000) {
654
810
  return new Promise((resolve3, reject) => {
655
- const socket = connect(socketPath, () => socket.write(`${JSON.stringify(request)}
811
+ const socket = connect2(socketPath, () => socket.write(`${JSON.stringify(request)}
656
812
  `));
657
813
  let buffer = "";
658
814
  socket.setTimeout(timeoutMs, () => {
@@ -680,22 +836,22 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
680
836
  import { createHash as createHash2 } from "node:crypto";
681
837
  import {
682
838
  accessSync,
683
- chmodSync as chmodSync3,
839
+ chmodSync as chmodSync4,
684
840
  copyFileSync,
685
841
  createReadStream,
686
- existsSync as existsSync3,
842
+ existsSync as existsSync4,
687
843
  mkdtempSync,
688
844
  mkdirSync as mkdirSync3,
689
845
  readFileSync as readFileSync3,
690
- renameSync as renameSync3,
691
- rmSync as rmSync3,
846
+ renameSync as renameSync4,
847
+ rmSync as rmSync4,
692
848
  statSync,
693
- symlinkSync as symlinkSync2,
694
- unlinkSync as unlinkSync2,
849
+ symlinkSync as symlinkSync3,
850
+ unlinkSync as unlinkSync3,
695
851
  writeFileSync as writeFileSync3
696
852
  } from "node:fs";
697
853
  import { tmpdir } from "node:os";
698
- import { dirname as dirname3, join as join3 } from "node:path";
854
+ import { dirname as dirname4, join as join3 } from "node:path";
699
855
  var PINNED_BUN_VERSION = "1.3.14";
700
856
  var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
701
857
  var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
@@ -868,23 +1024,23 @@ var softwareDownloadCommand = (url, destination, maximumBytes) => {
868
1024
  ];
869
1025
  };
870
1026
  var download = async (url, destination, sha256, maximumBytes = 512 * 1024 * 1024) => {
871
- if (existsSync3(destination))
1027
+ if (existsSync4(destination))
872
1028
  throw new Error("download destination already exists");
873
1029
  const received = await run(softwareDownloadCommand(url, destination, maximumBytes));
874
1030
  if (received.exitCode !== 0) {
875
- rmSync3(destination, { force: true });
1031
+ rmSync4(destination, { force: true });
876
1032
  throw new Error(`software download failed (${received.exitCode}): ${received.output.trim()}`);
877
1033
  }
878
- chmodSync3(destination, 384);
1034
+ chmodSync4(destination, 384);
879
1035
  if (statSync(destination).size > maximumBytes) {
880
- rmSync3(destination, { force: true });
1036
+ rmSync4(destination, { force: true });
881
1037
  throw new Error("download exceeds reviewed size bound");
882
1038
  }
883
1039
  const digest = createHash2("sha256");
884
1040
  for await (const chunk of createReadStream(destination))
885
1041
  digest.update(chunk);
886
1042
  if (digest.digest("hex") !== sha256) {
887
- rmSync3(destination, { force: true });
1043
+ rmSync4(destination, { force: true });
888
1044
  throw new Error("download checksum mismatch");
889
1045
  }
890
1046
  };
@@ -900,12 +1056,12 @@ var aptInstallMany = async (names) => {
900
1056
  return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", ...names], environment) : update;
901
1057
  };
902
1058
  var writeOwnedPolicy = (pathValue, content, mode = 420) => {
903
- if (existsSync3(pathValue)) {
1059
+ if (existsSync4(pathValue)) {
904
1060
  if (readFileSync3(pathValue, "utf8") !== content)
905
1061
  throw new Error(`refusing to overwrite non-ForgeZero policy at ${pathValue}`);
906
1062
  return;
907
1063
  }
908
- mkdirSync3(dirname3(pathValue), { recursive: true, mode: 493 });
1064
+ mkdirSync3(dirname4(pathValue), { recursive: true, mode: 493 });
909
1065
  writeFileSync3(pathValue, content, { mode, flag: "wx" });
910
1066
  };
911
1067
  var installContainerdRuntime = async (directory, osVersion) => {
@@ -917,7 +1073,7 @@ var installContainerdRuntime = async (directory, osVersion) => {
917
1073
  const nerdctlExtract = await run(["/usr/bin/tar", "-xzf", nerdctl, "-C", "/usr/local/bin", "nerdctl"]);
918
1074
  if (nerdctlExtract.exitCode !== 0)
919
1075
  return nerdctlExtract;
920
- chmodSync3("/usr/local/bin/nerdctl", 493);
1076
+ chmodSync4("/usr/local/bin/nerdctl", 493);
921
1077
  const buildkit = join3(directory, "buildkit.tar.gz");
922
1078
  await download(`https://github.com/moby/buildkit/releases/download/v${BUILDKIT_VERSION}/buildkit-v${BUILDKIT_VERSION}.linux-amd64.tar.gz`, buildkit, BUILDKIT_SHA256);
923
1079
  const unpacked = join3(directory, "buildkit");
@@ -927,15 +1083,15 @@ var installContainerdRuntime = async (directory, osVersion) => {
927
1083
  return buildkitExtract;
928
1084
  for (const binary of ["buildctl", "buildkitd"]) {
929
1085
  copyFileSync(join3(unpacked, "bin", binary), `/usr/local/bin/${binary}`);
930
- chmodSync3(`/usr/local/bin/${binary}`, 493);
1086
+ chmodSync4(`/usr/local/bin/${binary}`, 493);
931
1087
  }
932
1088
  mkdirSync3("/etc/containerd", { recursive: true, mode: 493 });
933
1089
  const expected = CONTAINERD_CONFIG(false);
934
1090
  const kataExpected = CONTAINERD_CONFIG(true);
935
- if (existsSync3(CONTAINERD_CONFIG_PATH) && ![expected, kataExpected].includes(readFileSync3(CONTAINERD_CONFIG_PATH, "utf8"))) {
1091
+ if (existsSync4(CONTAINERD_CONFIG_PATH) && ![expected, kataExpected].includes(readFileSync3(CONTAINERD_CONFIG_PATH, "utf8"))) {
936
1092
  return { exitCode: 2, output: "refusing to overwrite an existing containerd configuration that differs from the reviewed ForgeZero policy" };
937
1093
  }
938
- if (!existsSync3(CONTAINERD_CONFIG_PATH))
1094
+ if (!existsSync4(CONTAINERD_CONFIG_PATH))
939
1095
  writeFileSync3(CONTAINERD_CONFIG_PATH, expected, { mode: 420, flag: "wx" });
940
1096
  writeOwnedPolicy("/etc/systemd/system/forgezero-buildkit.service", BUILDKIT_UNIT);
941
1097
  mkdirSync3("/var/lib/forgezero/containerd", { recursive: true, mode: 448 });
@@ -948,10 +1104,10 @@ var installContainerdRuntime = async (directory, osVersion) => {
948
1104
  };
949
1105
  var installKataRuntime = async (directory) => {
950
1106
  for (const pathValue of ["/dev/kvm", "/dev/sev"])
951
- if (!existsSync3(pathValue))
1107
+ if (!existsSync4(pathValue))
952
1108
  return { exitCode: 2, output: `${pathValue} is required for Kata SEV-SNP` };
953
1109
  for (const parameter of ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"]) {
954
- if (!existsSync3(parameter) || !/^(1|Y)$/i.test(readFileSync3(parameter, "utf8").trim()))
1110
+ if (!existsSync4(parameter) || !/^(1|Y)$/i.test(readFileSync3(parameter, "utf8").trim()))
955
1111
  return { exitCode: 2, output: `${parameter} does not enable SEV-SNP` };
956
1112
  }
957
1113
  const dependencies = await aptInstallMany(["zstd"]);
@@ -966,7 +1122,7 @@ var installKataRuntime = async (directory) => {
966
1122
  const extracted = await run(["/usr/bin/tar", "-xf", tar, "-C", "/"]);
967
1123
  if (extracted.exitCode !== 0)
968
1124
  return extracted;
969
- if (!existsSync3("/opt/kata/bin/containerd-shim-kata-v2") || !existsSync3(KATA_SNP_CONFIG_PATH)) {
1125
+ if (!existsSync4("/opt/kata/bin/containerd-shim-kata-v2") || !existsSync4(KATA_SNP_CONFIG_PATH)) {
970
1126
  return { exitCode: 2, output: "Kata archive does not contain the reviewed QEMU SNP runtime and configuration" };
971
1127
  }
972
1128
  const snpConfig = readFileSync3(KATA_SNP_CONFIG_PATH, "utf8");
@@ -974,19 +1130,19 @@ var installKataRuntime = async (directory) => {
974
1130
  return { exitCode: 2, output: "Kata QEMU configuration does not enable confidential SEV-SNP guests" };
975
1131
  }
976
1132
  mkdirSync3("/etc/kata-containers", { recursive: true, mode: 493 });
977
- if (existsSync3(KATA_ACTIVE_CONFIG_PATH) && readFileSync3(KATA_ACTIVE_CONFIG_PATH, "utf8") !== snpConfig) {
1133
+ if (existsSync4(KATA_ACTIVE_CONFIG_PATH) && readFileSync3(KATA_ACTIVE_CONFIG_PATH, "utf8") !== snpConfig) {
978
1134
  return { exitCode: 2, output: "refusing to overwrite a non-ForgeZero Kata runtime configuration" };
979
1135
  }
980
- if (!existsSync3(KATA_ACTIVE_CONFIG_PATH))
1136
+ if (!existsSync4(KATA_ACTIVE_CONFIG_PATH))
981
1137
  copyFileSync(KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH);
982
- chmodSync3(KATA_ACTIVE_CONFIG_PATH, 420);
1138
+ chmodSync4(KATA_ACTIVE_CONFIG_PATH, 420);
983
1139
  try {
984
- unlinkSync2("/usr/local/bin/containerd-shim-kata-v2");
1140
+ unlinkSync3("/usr/local/bin/containerd-shim-kata-v2");
985
1141
  } catch {}
986
- symlinkSync2("/opt/kata/bin/containerd-shim-kata-v2", "/usr/local/bin/containerd-shim-kata-v2");
1142
+ symlinkSync3("/opt/kata/bin/containerd-shim-kata-v2", "/usr/local/bin/containerd-shim-kata-v2");
987
1143
  const base = CONTAINERD_CONFIG(false);
988
1144
  const kata = CONTAINERD_CONFIG(true);
989
- if (!existsSync3(CONTAINERD_CONFIG_PATH) || ![base, kata].includes(readFileSync3(CONTAINERD_CONFIG_PATH, "utf8"))) {
1145
+ if (!existsSync4(CONTAINERD_CONFIG_PATH) || ![base, kata].includes(readFileSync3(CONTAINERD_CONFIG_PATH, "utf8"))) {
990
1146
  return { exitCode: 2, output: "containerd must use the reviewed ForgeZero policy before Kata is installed" };
991
1147
  }
992
1148
  if (readFileSync3(CONTAINERD_CONFIG_PATH, "utf8") !== kata)
@@ -1006,7 +1162,7 @@ async function executeSoftwareOperation(operation) {
1006
1162
  const binary = await run(["/usr/bin/docker", "--version"]);
1007
1163
  if (!successful(binary, /Docker version/))
1008
1164
  return { ...binary, exitCode: 1 };
1009
- if (!existsSync3(DOCKER_DAEMON_PATH) || readFileSync3(DOCKER_DAEMON_PATH, "utf8") !== DOCKER_DAEMON_CONFIG)
1165
+ if (!existsSync4(DOCKER_DAEMON_PATH) || readFileSync3(DOCKER_DAEMON_PATH, "utf8") !== DOCKER_DAEMON_CONFIG)
1010
1166
  return { exitCode: 1, output: "Docker daemon policy is absent or differs from the reviewed ForgeZero policy" };
1011
1167
  const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "docker.service"]);
1012
1168
  return active.exitCode === 0 ? { exitCode: 0, output: binary.output } : active;
@@ -1019,17 +1175,17 @@ async function executeSoftwareOperation(operation) {
1019
1175
  run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]),
1020
1176
  run(["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-buildkit.service"])
1021
1177
  ]);
1022
- const policy = existsSync3(CONTAINERD_CONFIG_PATH) ? readFileSync3(CONTAINERD_CONFIG_PATH, "utf8") : "";
1178
+ const policy = existsSync4(CONTAINERD_CONFIG_PATH) ? readFileSync3(CONTAINERD_CONFIG_PATH, "utf8") : "";
1023
1179
  const validPolicy = policy === CONTAINERD_CONFIG(false) || policy === CONTAINERD_CONFIG(true);
1024
1180
  return successful(containerd, /containerd/) && successful(nerdctl, new RegExp(NERDCTL_VERSION.replaceAll(".", "\\."))) && successful(buildkit, new RegExp(BUILDKIT_VERSION.replaceAll(".", "\\."))) && active.exitCode === 0 && builder.exitCode === 0 && validPolicy ? { exitCode: 0, output: `${containerd.output}${nerdctl.output}${buildkit.output}` } : { exitCode: 1, output: "containerd, nerdctl, BuildKit, or ForgeZero storage/runtime policy is unavailable" };
1025
1181
  }
1026
1182
  if (software === "kata-containers") {
1027
1183
  const shim = await run(["/opt/kata/bin/containerd-shim-kata-v2", "--version"]);
1028
- const policy = existsSync3(CONTAINERD_CONFIG_PATH) ? readFileSync3(CONTAINERD_CONFIG_PATH, "utf8") : "";
1029
- const activeConfig = existsSync3(KATA_ACTIVE_CONFIG_PATH) ? readFileSync3(KATA_ACTIVE_CONFIG_PATH, "utf8") : "";
1030
- const host = ["/dev/kvm", "/dev/sev", KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH].every(existsSync3) && ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"].every((parameter) => existsSync3(parameter) && /^(1|Y)$/i.test(readFileSync3(parameter, "utf8").trim()));
1184
+ const policy = existsSync4(CONTAINERD_CONFIG_PATH) ? readFileSync3(CONTAINERD_CONFIG_PATH, "utf8") : "";
1185
+ const activeConfig = existsSync4(KATA_ACTIVE_CONFIG_PATH) ? readFileSync3(KATA_ACTIVE_CONFIG_PATH, "utf8") : "";
1186
+ const host = ["/dev/kvm", "/dev/sev", KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH].every(existsSync4) && ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"].every((parameter) => existsSync4(parameter) && /^(1|Y)$/i.test(readFileSync3(parameter, "utf8").trim()));
1031
1187
  const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]);
1032
- return successful(shim, /kata.*4\.0\.0/i) && policy === CONTAINERD_CONFIG(true) && activeConfig === (existsSync3(KATA_SNP_CONFIG_PATH) ? readFileSync3(KATA_SNP_CONFIG_PATH, "utf8") : "") && host && active.exitCode === 0 ? { exitCode: 0, output: shim.output } : { exitCode: 1, output: "Kata QEMU SNP runtime, host capability, or containerd policy is unavailable" };
1188
+ return successful(shim, /kata.*4\.0\.0/i) && policy === CONTAINERD_CONFIG(true) && activeConfig === (existsSync4(KATA_SNP_CONFIG_PATH) ? readFileSync3(KATA_SNP_CONFIG_PATH, "utf8") : "") && host && active.exitCode === 0 ? { exitCode: 0, output: shim.output } : { exitCode: 1, output: "Kata QEMU SNP runtime, host capability, or containerd policy is unavailable" };
1033
1189
  }
1034
1190
  if (software === "nginx") {
1035
1191
  const binary = await run(["/usr/sbin/nginx", "-v"]);
@@ -1070,10 +1226,10 @@ async function executeSoftwareOperation(operation) {
1070
1226
  return installed;
1071
1227
  if (software === "docker") {
1072
1228
  mkdirSync3("/etc/docker", { recursive: true, mode: 493 });
1073
- if (existsSync3(DOCKER_DAEMON_PATH) && readFileSync3(DOCKER_DAEMON_PATH, "utf8") !== DOCKER_DAEMON_CONFIG) {
1229
+ if (existsSync4(DOCKER_DAEMON_PATH) && readFileSync3(DOCKER_DAEMON_PATH, "utf8") !== DOCKER_DAEMON_CONFIG) {
1074
1230
  return { exitCode: 2, output: "refusing to overwrite an existing Docker daemon configuration that differs from the reviewed ForgeZero policy" };
1075
1231
  }
1076
- if (!existsSync3(DOCKER_DAEMON_PATH))
1232
+ if (!existsSync4(DOCKER_DAEMON_PATH))
1077
1233
  writeFileSync3(DOCKER_DAEMON_PATH, DOCKER_DAEMON_CONFIG, { mode: 420, flag: "wx" });
1078
1234
  const enabled = await run(["/usr/bin/systemctl", "enable", "docker.service"]);
1079
1235
  return enabled.exitCode === 0 ? run(["/usr/bin/systemctl", "restart", "docker.service"]) : enabled;
@@ -1123,20 +1279,20 @@ async function executeSoftwareOperation(operation) {
1123
1279
  return unzipped;
1124
1280
  mkdirSync3("/usr/local/lib/forgezero/runtime", { recursive: true, mode: 493 });
1125
1281
  copyFileSync(join3(unpacked, "bun-linux-x64", "bun"), "/usr/local/lib/forgezero/runtime/bun.next");
1126
- chmodSync3("/usr/local/lib/forgezero/runtime/bun.next", 493);
1127
- renameSync3("/usr/local/lib/forgezero/runtime/bun.next", "/usr/local/lib/forgezero/runtime/bun");
1282
+ chmodSync4("/usr/local/lib/forgezero/runtime/bun.next", 493);
1283
+ renameSync4("/usr/local/lib/forgezero/runtime/bun.next", "/usr/local/lib/forgezero/runtime/bun");
1128
1284
  try {
1129
- unlinkSync2("/usr/local/bin/bun");
1285
+ unlinkSync3("/usr/local/bin/bun");
1130
1286
  } catch {}
1131
- symlinkSync2("/usr/local/lib/forgezero/runtime/bun", "/usr/local/bin/bun");
1287
+ symlinkSync3("/usr/local/lib/forgezero/runtime/bun", "/usr/local/bin/bun");
1132
1288
  return { exitCode: 0, output: "" };
1133
1289
  }
1134
1290
  if (software === "cloudflared") {
1135
1291
  const binary = join3(directory, "cloudflared");
1136
1292
  await download("https://github.com/cloudflare/cloudflared/releases/download/2026.7.3/cloudflared-linux-amd64", binary, CLOUDFLARED_SHA256);
1137
- chmodSync3(binary, 493);
1293
+ chmodSync4(binary, 493);
1138
1294
  copyFileSync(binary, "/usr/local/bin/cloudflared");
1139
- chmodSync3("/usr/local/bin/cloudflared", 493);
1295
+ chmodSync4("/usr/local/bin/cloudflared", 493);
1140
1296
  return { exitCode: 0, output: "" };
1141
1297
  }
1142
1298
  const deb = join3(directory, "arangodb.deb");
@@ -1149,7 +1305,7 @@ async function executeSoftwareOperation(operation) {
1149
1305
  await run(["/usr/bin/systemctl", "disable", "--now", "arangodb3.service"]);
1150
1306
  return { exitCode: 0, output: "" };
1151
1307
  } finally {
1152
- rmSync3(directory, { recursive: true, force: true });
1308
+ rmSync4(directory, { recursive: true, force: true });
1153
1309
  }
1154
1310
  }
1155
1311
  function observeSoftwareHost(osRelease = readFileSync3("/etc/os-release", "utf8"), architecture = process.arch) {
@@ -1648,9 +1804,9 @@ function phasePipeline(definition, phase, profile, executeRelease = false) {
1648
1804
 
1649
1805
  // src/deployment-connectivity.ts
1650
1806
  import { createHash as createHash3 } from "node:crypto";
1651
- import { mkdirSync as mkdirSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
1807
+ import { mkdirSync as mkdirSync4, renameSync as renameSync5, writeFileSync as writeFileSync4 } from "node:fs";
1652
1808
  import { createConnection } from "node:net";
1653
- import { dirname as dirname4 } from "node:path";
1809
+ import { dirname as dirname5 } from "node:path";
1654
1810
 
1655
1811
  // src/process-input.ts
1656
1812
  async function writeAndCloseProcessInput(input, value) {
@@ -1671,7 +1827,7 @@ var checked2 = async (host, argv2, label) => {
1671
1827
  return result.output.trim();
1672
1828
  };
1673
1829
  async function sealWithSystemd(name, path2, value) {
1674
- mkdirSync4(dirname4(path2), { recursive: true, mode: 448 });
1830
+ mkdirSync4(dirname5(path2), { recursive: true, mode: 448 });
1675
1831
  const next = `${path2}.next`;
1676
1832
  const child = Bun.spawn(["/usr/bin/systemd-creds", "encrypt", `--name=${name}`, "-", next], {
1677
1833
  stdin: "pipe",
@@ -1687,15 +1843,15 @@ async function sealWithSystemd(name, path2, value) {
1687
1843
  ]);
1688
1844
  if (exitCode !== 0)
1689
1845
  throw new Error(`systemd credential sealing failed: ${`${stdout}${stderr}`.replaceAll(value, "[REDACTED]").slice(0, 512)}`);
1690
- renameSync4(next, path2);
1846
+ renameSync5(next, path2);
1691
1847
  }
1692
1848
  var defaultHost = {
1693
1849
  seal: sealWithSystemd,
1694
1850
  write(path2, content, mode) {
1695
- mkdirSync4(dirname4(path2), { recursive: true, mode: 493 });
1851
+ mkdirSync4(dirname5(path2), { recursive: true, mode: 493 });
1696
1852
  const next = `${path2}.next`;
1697
1853
  writeFileSync4(next, content, { mode });
1698
- renameSync4(next, path2);
1854
+ renameSync5(next, path2);
1699
1855
  },
1700
1856
  async exec(argv2) {
1701
1857
  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" } });
@@ -1927,14 +2083,14 @@ WantedBy=multi-user.target
1927
2083
  }
1928
2084
 
1929
2085
  // src/software-helper.ts
1930
- import { chmodSync as chmodSync4, existsSync as existsSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync3 } from "node:fs";
1931
- import { connect as connect2, createServer as createServer2 } from "node:net";
1932
- import { dirname as dirname7 } from "node:path";
2086
+ import { chmodSync as chmodSync5, existsSync as existsSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "node:fs";
2087
+ import { connect as connect3, createServer as createServer3 } from "node:net";
2088
+ import { dirname as dirname8 } from "node:path";
1933
2089
 
1934
2090
  // src/service-supervisor.ts
1935
2091
  import { createHash as createHash4 } from "node:crypto";
1936
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync4, readdirSync, realpathSync, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
1937
- import { dirname as dirname5, join as join4, resolve as resolve3, sep } from "node:path";
2092
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, readdirSync, realpathSync, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "node:fs";
2093
+ import { dirname as dirname6, join as join4, resolve as resolve3, sep } from "node:path";
1938
2094
  var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
1939
2095
  var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
1940
2096
  var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
@@ -1944,17 +2100,17 @@ var statePath = (id) => `${SERVICE_STATE_DIRECTORY}/${id}.json`;
1944
2100
  var within = (root, path2) => path2 === root || path2.startsWith(`${root}${sep}`);
1945
2101
  var defaultHost2 = {
1946
2102
  write(path2, content, mode) {
1947
- mkdirSync5(dirname5(path2), { recursive: true, mode: 493 });
2103
+ mkdirSync5(dirname6(path2), { recursive: true, mode: 493 });
1948
2104
  const next = `${path2}.next`;
1949
2105
  writeFileSync5(next, content, { mode });
1950
- renameSync5(next, path2);
2106
+ renameSync6(next, path2);
1951
2107
  },
1952
2108
  read: (path2) => readFileSync4(path2, "utf8"),
1953
- exists: existsSync4,
1954
- list: (path2) => existsSync4(path2) ? readdirSync(path2) : [],
2109
+ exists: existsSync5,
2110
+ list: (path2) => existsSync5(path2) ? readdirSync(path2) : [],
1955
2111
  realpath: realpathSync,
1956
2112
  mkdir: (path2, mode) => mkdirSync5(path2, { recursive: true, mode }),
1957
- remove: (path2) => rmSync4(path2, { force: true }),
2113
+ remove: (path2) => rmSync5(path2, { force: true }),
1958
2114
  async exec(argv2) {
1959
2115
  const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: {
1960
2116
  PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
@@ -2150,20 +2306,20 @@ async function activateSupervisedService(request, host = defaultHost2) {
2150
2306
 
2151
2307
  // src/container-supervisor.ts
2152
2308
  import { createHash as createHash5 } from "node:crypto";
2153
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync5, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "node:fs";
2154
- import { dirname as dirname6, resolve as resolve4, sep as sep2 } from "node:path";
2309
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync7, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "node:fs";
2310
+ import { dirname as dirname7, resolve as resolve4, sep as sep2 } from "node:path";
2155
2311
  var defaultHost3 = {
2156
2312
  realpath: realpathSync2,
2157
- exists: existsSync5,
2313
+ exists: existsSync6,
2158
2314
  read: (path2) => readFileSync5(path2, "utf8"),
2159
2315
  write(path2, content, mode) {
2160
- mkdirSync6(dirname6(path2), { recursive: true, mode: 493 });
2316
+ mkdirSync6(dirname7(path2), { recursive: true, mode: 493 });
2161
2317
  const next = `${path2}.next`;
2162
2318
  writeFileSync6(next, content, { mode });
2163
- renameSync6(next, path2);
2319
+ renameSync7(next, path2);
2164
2320
  },
2165
- remove: (path2) => rmSync5(path2, { force: true }),
2166
- list: (path2) => existsSync5(path2) ? readdirSync2(path2) : [],
2321
+ remove: (path2) => rmSync6(path2, { force: true }),
2322
+ list: (path2) => existsSync6(path2) ? readdirSync2(path2) : [],
2167
2323
  mkdir: (path2, mode) => mkdirSync6(path2, { recursive: true, mode }),
2168
2324
  async exec(argv2) {
2169
2325
  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" } });
@@ -2458,9 +2614,9 @@ var MAX_REQUEST_BYTES2 = 128 * 1024;
2458
2614
  var MAX_PENDING_REQUESTS = 128;
2459
2615
  function startSoftwareHelper(options = {}) {
2460
2616
  const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
2461
- if (existsSync6(socketPath))
2462
- unlinkSync3(socketPath);
2463
- mkdirSync7(dirname7(socketPath), { recursive: true, mode: 488 });
2617
+ if (existsSync7(socketPath))
2618
+ unlinkSync4(socketPath);
2619
+ mkdirSync7(dirname8(socketPath), { recursive: true, mode: 488 });
2464
2620
  const ensure = options.ensure ?? ensureSoftwareRequirements;
2465
2621
  const activate = options.activate ?? activateSupervisedService;
2466
2622
  const buildContainer = options.buildContainer ?? buildContainerImage;
@@ -2468,7 +2624,7 @@ function startSoftwareHelper(options = {}) {
2468
2624
  const applyConnectivity = options.applyConnectivity ?? applyDeploymentConnectivity;
2469
2625
  let tail = Promise.resolve();
2470
2626
  let pending = 0;
2471
- const server = createServer2((socket) => {
2627
+ const server = createServer3((socket) => {
2472
2628
  let buffer = "";
2473
2629
  socket.on("data", (chunk) => {
2474
2630
  buffer += chunk.toString("utf8");
@@ -2547,12 +2703,12 @@ function startSoftwareHelper(options = {}) {
2547
2703
  });
2548
2704
  socket.on("error", () => socket.destroy());
2549
2705
  });
2550
- server.listen(socketPath, () => chmodSync4(socketPath, 432));
2706
+ server.listen(socketPath, () => chmodSync5(socketPath, 432));
2551
2707
  return server;
2552
2708
  }
2553
2709
  function requestContainer(op, request, socketPath, timeoutMs) {
2554
2710
  return new Promise((resolve5, reject) => {
2555
- const socket = connect2(socketPath, () => socket.write(`${JSON.stringify({ op, request })}
2711
+ const socket = connect3(socketPath, () => socket.write(`${JSON.stringify({ op, request })}
2556
2712
  `));
2557
2713
  let buffer = "";
2558
2714
  socket.setTimeout(timeoutMs, () => {
@@ -2590,7 +2746,7 @@ function requestDeploymentConnectivity(request, socketPath = DEFAULT_SOFTWARE_HE
2590
2746
  }
2591
2747
  function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 10 * 60000) {
2592
2748
  return new Promise((resolve5, reject) => {
2593
- const socket = connect2(socketPath, () => socket.write(`${JSON.stringify({ op: "activate-service", request })}
2749
+ const socket = connect3(socketPath, () => socket.write(`${JSON.stringify({ op: "activate-service", request })}
2594
2750
  `));
2595
2751
  let buffer = "";
2596
2752
  socket.setTimeout(timeoutMs, () => {
@@ -2619,7 +2775,7 @@ function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_
2619
2775
  function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
2620
2776
  validateSoftwareRequirements(requirements);
2621
2777
  return new Promise((resolve5, reject) => {
2622
- const socket = connect2(socketPath, () => socket.write(`${JSON.stringify({ op: "ensure", requirements })}
2778
+ const socket = connect3(socketPath, () => socket.write(`${JSON.stringify({ op: "ensure", requirements })}
2623
2779
  `));
2624
2780
  let buffer = "";
2625
2781
  socket.setTimeout(timeoutMs, () => {
@@ -2647,7 +2803,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
2647
2803
  }
2648
2804
 
2649
2805
  // src/version.ts
2650
- var VERSION3 = "0.1.85";
2806
+ var VERSION3 = "0.1.87";
2651
2807
 
2652
2808
  // src/egress-policy.ts
2653
2809
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -2763,6 +2919,7 @@ var LIFECYCLE_GROUP = "forgezero-lifecycle";
2763
2919
  var DEPLOYMENT_RUNNER_UNIT_PATH = "/etc/systemd/system/forgezero-deploy-runner.service";
2764
2920
  var AGENT_SOCKET_UNIT_PATH = "/etc/systemd/system/forgezero-agent.socket";
2765
2921
  var AGENT_SOCKET_PROXY_UNIT_PATH = "/etc/systemd/system/forgezero-agent-proxy.service";
2922
+ var AGENT_CANDIDATE_UNIT_PATH = "/etc/systemd/system/forgezero-agent-candidate.service";
2766
2923
  var DEPLOYMENT_RUNNER_SOCKET = "/run/forgezero-deploy/runner.sock";
2767
2924
  var ENROLMENT_UNIT_PATH = "/etc/systemd/system/forgezero-agent-enrol.service";
2768
2925
  var LIFECYCLE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-lifecycle-helper.service";
@@ -2874,6 +3031,7 @@ Type=simple
2874
3031
  User=root
2875
3032
  Group=${AGENT_UPDATE_GROUP}
2876
3033
  Environment=FZ_AGENT_UPDATE_SOCKET=${DEFAULT_AGENT_UPDATE_SOCKET}
3034
+ Environment=FZ_AGENT_PUBLIC_SOCKET=${systemdPath(options.socketPath, "agent socket")}
2877
3035
  ExecStart=${bin} update-helper
2878
3036
  Restart=always
2879
3037
  RestartSec=2
@@ -2918,13 +3076,11 @@ WantedBy=sockets.target
2918
3076
  `;
2919
3077
  }
2920
3078
  function agentSocketProxyUnit(options) {
2921
- const backend = agentBackendSocketPath(options.socketPath);
3079
+ const backend = agentRoutingSocketPath(options.socketPath);
2922
3080
  const user = options.user ?? "forgezero";
2923
3081
  return `[Unit]
2924
3082
  Description=ForgeZero application Vault socket proxy
2925
3083
  Documentation=https://www.forgezero.net/docs/agent
2926
- Requires=forgezero-agent.service
2927
- After=forgezero-agent.service
2928
3084
 
2929
3085
  [Service]
2930
3086
  User=${user}
@@ -2945,12 +3101,26 @@ RestrictAddressFamilies=AF_UNIX
2945
3101
  `;
2946
3102
  }
2947
3103
  function agentBackendSocketPath(publicSocketPath) {
3104
+ const socket = systemdPath(publicSocketPath, "agent socket");
3105
+ const backend = `${socket}.backend.active`;
3106
+ if (Buffer.byteLength(backend) > 100)
3107
+ throw new Error("agent socket path is too long for a Unix socket");
3108
+ return backend;
3109
+ }
3110
+ function agentRoutingSocketPath(publicSocketPath) {
2948
3111
  const socket = systemdPath(publicSocketPath, "agent socket");
2949
3112
  const backend = `${socket}.backend`;
2950
3113
  if (Buffer.byteLength(backend) > 100)
2951
3114
  throw new Error("agent socket path is too long for a Unix socket");
2952
3115
  return backend;
2953
3116
  }
3117
+ function agentCandidateSocketPath(publicSocketPath) {
3118
+ const socket = systemdPath(publicSocketPath, "agent socket");
3119
+ const backend = `${socket}.backend.candidate`;
3120
+ if (Buffer.byteLength(backend) > 100)
3121
+ throw new Error("agent socket path is too long for a Unix socket");
3122
+ return backend;
3123
+ }
2954
3124
  var systemdPath = (value, label) => {
2955
3125
  if (!value || !/^\/[A-Za-z0-9._@/-]+$/.test(value))
2956
3126
  throw new Error(`invalid ${label} path`);
@@ -3234,7 +3404,11 @@ function agentUnit(options) {
3234
3404
  }
3235
3405
  const environment = [
3236
3406
  "NODE_ENV=production",
3237
- `FZ_SOCKET_PATH=${agentBackendSocketPath(options.socketPath)}`,
3407
+ `FZ_SOCKET_PATH=${options.backendSocketPath ?? agentBackendSocketPath(options.socketPath)}`,
3408
+ `FZ_AGENT_PUBLIC_SOCKET=${options.socketPath}`,
3409
+ `FZ_AGENT_ROUTE_SOCKET=${agentRoutingSocketPath(options.socketPath)}`,
3410
+ options.handoverCandidate ? "FZ_AGENT_HANDOVER_CANDIDATE=true" : null,
3411
+ options.handoverCandidate ? `FZ_AGENT_HANDOVER_READY_SOCKET=${DEFAULT_AGENT_CANDIDATE_READY_SOCKET}` : null,
3238
3412
  `FZ_CONTROL_SOCKET=${controlSocketPath}`,
3239
3413
  `FZ_SEED_CREDENTIAL=agent-seed`,
3240
3414
  `FZ_AGENT_MODE=${options.mode}`,
@@ -3371,6 +3545,26 @@ ${deploymentWrites}
3371
3545
  WantedBy=multi-user.target
3372
3546
  `;
3373
3547
  }
3548
+ function agentCandidateUnit(options) {
3549
+ return agentUnit({
3550
+ ...options,
3551
+ binPath: `${DEFAULT_AGENT_RELEASE_ROOT}/candidate/dist/fz-agent.js`,
3552
+ backendSocketPath: agentCandidateSocketPath(options.socketPath),
3553
+ handoverCandidate: true,
3554
+ gitCredentialPath: undefined,
3555
+ deploymentCredentials: {},
3556
+ bootstrapSshCredentialPath: undefined,
3557
+ bootstrapSshPublicKeyPath: undefined,
3558
+ pullBootstrap: false,
3559
+ pullDeployments: false,
3560
+ pullMigrations: false,
3561
+ lifecycleProfilePath: undefined,
3562
+ bootstrapTargetTelemetryEndpoint: undefined,
3563
+ repository: undefined,
3564
+ bootstrapBundlePath: undefined,
3565
+ bootstrapBundleManifestPath: undefined
3566
+ }).replace("Description=ForgeZero node agent", "Description=ForgeZero candidate node agent");
3567
+ }
3374
3568
  var renderOperation = (operation) => {
3375
3569
  if (operation.kind === "commands")
3376
3570
  return operation.commands.map(({ argv: argv2 }) => argv2.join(" ")).join(`
@@ -3506,6 +3700,7 @@ function planProvision(options) {
3506
3700
  auxiliaryUnits: [
3507
3701
  { path: AGENT_SOCKET_UNIT_PATH, unit: agentSocketUnit(options) },
3508
3702
  { path: AGENT_SOCKET_PROXY_UNIT_PATH, unit: agentSocketProxyUnit(options) },
3703
+ { path: AGENT_CANDIDATE_UNIT_PATH, unit: agentCandidateUnit(options) },
3509
3704
  { path: AGENT_UPDATE_HELPER_UNIT_PATH, unit: agentUpdateHelperUnit(options) },
3510
3705
  ...options.enforceEgress ? [
3511
3706
  { path: AGENT_EGRESS_UNIT_PATH, unit: agentEgressUnit(options) }
@@ -3773,19 +3968,19 @@ function platformCommunityRehearsalDeletionClaims(seedGuests, rehearsalGuest) {
3773
3968
 
3774
3969
  // src/platform-bootstrap-runtime.ts
3775
3970
  import {
3776
- chmodSync as chmodSync5,
3971
+ chmodSync as chmodSync6,
3777
3972
  chownSync,
3778
3973
  copyFileSync as copyFileSync2,
3779
- existsSync as existsSync7,
3974
+ existsSync as existsSync8,
3780
3975
  lstatSync,
3781
3976
  mkdirSync as mkdirSync8,
3782
3977
  readFileSync as readFileSync6,
3783
3978
  readdirSync as readdirSync3,
3784
3979
  realpathSync as realpathSync4,
3785
- renameSync as renameSync7,
3786
- rmSync as rmSync6,
3980
+ renameSync as renameSync8,
3981
+ rmSync as rmSync7,
3787
3982
  statSync as statSync2,
3788
- symlinkSync as symlinkSync3,
3983
+ symlinkSync as symlinkSync4,
3789
3984
  writeFileSync as writeFileSync7
3790
3985
  } from "node:fs";
3791
3986
  import { join as join5 } from "node:path";
@@ -3820,12 +4015,14 @@ var httpsOrigin = (name, raw) => {
3820
4015
  return value.origin;
3821
4016
  };
3822
4017
  function validatePlatformInitialInventory(value) {
3823
- if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "region", "computes"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
4018
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "region", "computes", "deployment"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
3824
4019
  throw new Error("initial platform inventory coordinates are invalid");
3825
4020
  }
3826
4021
  safeAtom("initialInventory.region.label", value.region.label);
3827
4022
  if (value.region.city !== undefined)
3828
4023
  safeAtom("initialInventory.region.city", value.region.city);
4024
+ if (value.deployment !== undefined && (!value.deployment || typeof value.deployment !== "object" || Array.isArray(value.deployment) || Object.keys(value.deployment).some((key) => !["source", "branch", "revision", "bundleSha256"].includes(key)) || value.deployment.source !== "bootstrap-bundle" || !["dev", "main"].includes(value.deployment.branch) || !/^[a-f0-9]{40}$/.test(value.deployment.revision) || !/^[a-f0-9]{64}$/.test(value.deployment.bundleSha256)))
4025
+ throw new Error("initial platform deployment evidence is invalid");
3829
4026
  const computes = validatePlatformGenesisGuests(value.computes.map((compute) => {
3830
4027
  if (!compute || typeof compute !== "object" || Array.isArray(compute) || Object.keys(compute).some((key) => ![
3831
4028
  "name",
@@ -3860,7 +4057,8 @@ function validatePlatformInitialInventory(value) {
3860
4057
  databaseRole: compute.databaseRole,
3861
4058
  databaseAgency: compute.databaseAgency,
3862
4059
  confidential: true
3863
- }))
4060
+ })),
4061
+ ...value.deployment ? { deployment: { ...value.deployment } } : {}
3864
4062
  };
3865
4063
  }
3866
4064
  var systemdValue = (name, raw) => {
@@ -4213,13 +4411,13 @@ var platformExec = async (argv2) => {
4213
4411
  };
4214
4412
  var replaceLink = (path2, target) => {
4215
4413
  const pending = `${path2}.next`;
4216
- rmSync6(pending, { force: true });
4414
+ rmSync7(pending, { force: true });
4217
4415
  if (!target) {
4218
- rmSync6(path2, { force: true });
4416
+ rmSync7(path2, { force: true });
4219
4417
  return;
4220
4418
  }
4221
- symlinkSync3(target, pending);
4222
- renameSync7(pending, path2);
4419
+ symlinkSync4(target, pending);
4420
+ renameSync8(pending, path2);
4223
4421
  };
4224
4422
  var secureRelease = (path2, uid, gid) => {
4225
4423
  const visit = (current2) => {
@@ -4227,7 +4425,7 @@ var secureRelease = (path2, uid, gid) => {
4227
4425
  if (metadata.isSymbolicLink())
4228
4426
  throw new Error("release contains a symbolic link");
4229
4427
  chownSync(current2, uid, gid);
4230
- chmodSync5(current2, metadata.isDirectory() ? 365 : 292);
4428
+ chmodSync6(current2, metadata.isDirectory() ? 365 : 292);
4231
4429
  if (metadata.isDirectory())
4232
4430
  for (const name of readdirSync3(current2))
4233
4431
  visit(join5(current2, name));
@@ -4252,7 +4450,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
4252
4450
  const slots = join5(normalized.root, "slots");
4253
4451
  mkdirSync8(slots, { recursive: true, mode: 493 });
4254
4452
  const slotFile = join5(normalized.root, ".forge-slot");
4255
- const previousSlot = existsSync7(slotFile) ? readFileSync6(slotFile, "utf8").trim() : undefined;
4453
+ const previousSlot = existsSync8(slotFile) ? readFileSync6(slotFile, "utf8").trim() : undefined;
4256
4454
  const target = previousSlot === "blue" ? "green" : "blue";
4257
4455
  const port = target === "blue" ? normalized.bluePort : normalized.greenPort;
4258
4456
  const targetLink = join5(slots, target);
@@ -4292,25 +4490,25 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
4292
4490
  }
4293
4491
  const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
4294
4492
  const backup = `${upstream}.forgezero-backup`;
4295
- if (existsSync7(upstream))
4493
+ if (existsSync8(upstream))
4296
4494
  copyFileSync2(upstream, backup);
4297
4495
  else
4298
- rmSync6(backup, { force: true });
4496
+ rmSync7(backup, { force: true });
4299
4497
  writeFileSync7(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
4300
4498
  `, { mode: 420 });
4301
4499
  const test = await exec(["/usr/sbin/nginx", "-t"]);
4302
4500
  const reload = test.exitCode === 0 ? await exec(["/usr/sbin/nginx", "-s", "reload"]) : test;
4303
4501
  if (reload.exitCode !== 0) {
4304
- if (existsSync7(backup))
4305
- renameSync7(backup, upstream);
4502
+ if (existsSync8(backup))
4503
+ renameSync8(backup, upstream);
4306
4504
  else
4307
- rmSync6(upstream, { force: true });
4505
+ rmSync7(upstream, { force: true });
4308
4506
  await exec(["/usr/sbin/nginx", "-t"]);
4309
4507
  await exec(["/usr/sbin/nginx", "-s", "reload"]);
4310
4508
  await stopTarget();
4311
4509
  throw new Error("nginx refused the promoted upstream");
4312
4510
  }
4313
- rmSync6(backup, { force: true });
4511
+ rmSync7(backup, { force: true });
4314
4512
  writeFileSync7(slotFile, `${target}
4315
4513
  `, { mode: 420 });
4316
4514
  if (previousSlot && previousSlot !== target) {
@@ -4320,7 +4518,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
4320
4518
  const old = readdirSync3(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join5(releases, entry.name)).sort((left, right) => statSync2(right).mtimeMs - statSync2(left).mtimeMs).slice(normalized.keepReleases);
4321
4519
  for (const path2 of old)
4322
4520
  if (path2 !== release)
4323
- rmSync6(path2, { recursive: true, force: true });
4521
+ rmSync7(path2, { recursive: true, force: true });
4324
4522
  return { release, slot: target };
4325
4523
  }
4326
4524
  function validateCollectorUnit(unit2) {
@@ -4366,34 +4564,34 @@ import { lstatSync as lstatSync5 } from "node:fs";
4366
4564
  // src/bootstrap.ts
4367
4565
  import { createHash as createHash7, createHmac as createHmac2, randomBytes as randomBytes3 } from "node:crypto";
4368
4566
  import {
4369
- chmodSync as chmodSync8,
4370
- existsSync as existsSync10,
4567
+ chmodSync as chmodSync9,
4568
+ existsSync as existsSync11,
4371
4569
  lstatSync as lstatSync4,
4372
4570
  mkdirSync as mkdirSync11,
4373
4571
  readFileSync as readFileSync9,
4374
- renameSync as renameSync10,
4375
- rmSync as rmSync9,
4572
+ renameSync as renameSync11,
4573
+ rmSync as rmSync10,
4376
4574
  writeFileSync as writeFileSync10
4377
4575
  } from "node:fs";
4378
- import { dirname as dirname11 } from "node:path";
4576
+ import { dirname as dirname12 } from "node:path";
4379
4577
  import { fileURLToPath } from "node:url";
4380
4578
 
4381
4579
  // src/cli/agent-install.ts
4382
4580
  import { randomBytes } from "node:crypto";
4383
4581
  import {
4384
- chmodSync as chmodSync6,
4582
+ chmodSync as chmodSync7,
4385
4583
  copyFileSync as copyFileSync3,
4386
- existsSync as existsSync8,
4584
+ existsSync as existsSync9,
4387
4585
  lstatSync as lstatSync2,
4388
4586
  mkdirSync as mkdirSync9,
4389
4587
  readFileSync as readFileSync7,
4390
4588
  realpathSync as realpathSync5,
4391
- renameSync as renameSync8,
4392
- rmSync as rmSync7,
4393
- symlinkSync as symlinkSync4,
4589
+ renameSync as renameSync9,
4590
+ rmSync as rmSync8,
4591
+ symlinkSync as symlinkSync5,
4394
4592
  writeFileSync as writeFileSync8
4395
4593
  } from "node:fs";
4396
- import { dirname as dirname8 } from "node:path";
4594
+ import { dirname as dirname9 } from "node:path";
4397
4595
  async function readCapabilities(run2) {
4398
4596
  const answers = {};
4399
4597
  const checks = Object.entries(CAPABILITY_CHECKS);
@@ -4451,51 +4649,51 @@ var runProvisionOperation = async (operation) => {
4451
4649
  if (operation.kind === "install-runtime") {
4452
4650
  const release = `/opt/forgezero/agent/versions/${operation.version}`;
4453
4651
  mkdirSync9(`${release}/dist`, { recursive: true, mode: 493 });
4454
- mkdirSync9(dirname8(operation.binary), { recursive: true, mode: 493 });
4652
+ mkdirSync9(dirname9(operation.binary), { recursive: true, mode: 493 });
4455
4653
  copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
4456
- chmodSync6(`${release}/dist/fz-agent.js`, 493);
4457
- const gitSshSource = `${dirname8(operation.source)}/fz-git-ssh.js`;
4458
- if (!existsSync8(gitSshSource))
4654
+ chmodSync7(`${release}/dist/fz-agent.js`, 493);
4655
+ const gitSshSource = `${dirname9(operation.source)}/fz-git-ssh.js`;
4656
+ if (!existsSync9(gitSshSource))
4459
4657
  return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
4460
4658
  copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
4461
- chmodSync6(`${release}/dist/fz-git-ssh.js`, 493);
4659
+ chmodSync7(`${release}/dist/fz-git-ssh.js`, 493);
4462
4660
  const pending = "/opt/forgezero/agent/current.next";
4463
- rmSync7(pending, { force: true });
4464
- symlinkSync4(`versions/${operation.version}`, pending);
4465
- renameSync8(pending, "/opt/forgezero/agent/current");
4466
- rmSync7(operation.binary, { force: true });
4467
- symlinkSync4("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
4661
+ rmSync8(pending, { force: true });
4662
+ symlinkSync5(`versions/${operation.version}`, pending);
4663
+ renameSync9(pending, "/opt/forgezero/agent/current");
4664
+ rmSync8(operation.binary, { force: true });
4665
+ symlinkSync5("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
4468
4666
  const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
4469
- rmSync7(gitSshBinary, { force: true });
4470
- symlinkSync4("/opt/forgezero/agent/current/dist/fz-git-ssh.js", gitSshBinary);
4667
+ rmSync8(gitSshBinary, { force: true });
4668
+ symlinkSync5("/opt/forgezero/agent/current/dist/fz-git-ssh.js", gitSshBinary);
4471
4669
  return { stdout: "", exitCode: 0 };
4472
4670
  }
4473
4671
  if (operation.kind === "ensure-seed") {
4474
- if (existsSync8(operation.credential) && lstatSync2(operation.credential).size > 0)
4672
+ if (existsSync9(operation.credential) && lstatSync2(operation.credential).size > 0)
4475
4673
  return { stdout: "", exitCode: 0 };
4476
4674
  const seed = randomBytes(32).toString("base64url");
4477
4675
  const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
4478
4676
  if (result.exitCode === 0)
4479
- chmodSync6(operation.credential, 256);
4677
+ chmodSync7(operation.credential, 256);
4480
4678
  return result;
4481
4679
  }
4482
4680
  if (operation.kind === "ensure-git-identity") {
4483
4681
  const key = "/run/forgezero-git-deploy-key";
4484
4682
  const publicKey2 = `${key}.pub`;
4485
4683
  try {
4486
- if (!existsSync8(operation.credential) || lstatSync2(operation.credential).size < 1) {
4487
- rmSync7(key, { force: true });
4488
- rmSync7(publicKey2, { force: true });
4684
+ if (!existsSync9(operation.credential) || lstatSync2(operation.credential).size < 1) {
4685
+ rmSync8(key, { force: true });
4686
+ rmSync8(publicKey2, { force: true });
4489
4687
  let result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-compute", "-f", key]);
4490
4688
  if (result.exitCode !== 0)
4491
4689
  return result;
4492
4690
  result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=git-deploy-key", key, operation.credential]);
4493
4691
  if (result.exitCode !== 0)
4494
4692
  return result;
4495
- chmodSync6(operation.credential, 256);
4693
+ chmodSync7(operation.credential, 256);
4496
4694
  }
4497
- if (!existsSync8(operation.publicKey) || lstatSync2(operation.publicKey).size < 1) {
4498
- if (!existsSync8(key)) {
4695
+ if (!existsSync9(operation.publicKey) || lstatSync2(operation.publicKey).size < 1) {
4696
+ if (!existsSync9(key)) {
4499
4697
  const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=git-deploy-key", operation.credential, key]);
4500
4698
  if (decrypted.exitCode !== 0)
4501
4699
  return decrypted;
@@ -4508,25 +4706,25 @@ var runProvisionOperation = async (operation) => {
4508
4706
  }
4509
4707
  return { stdout: "", exitCode: 0 };
4510
4708
  } finally {
4511
- rmSync7(key, { force: true });
4512
- rmSync7(publicKey2, { force: true });
4709
+ rmSync8(key, { force: true });
4710
+ rmSync8(publicKey2, { force: true });
4513
4711
  }
4514
4712
  }
4515
4713
  if (operation.kind === "ensure-bootstrap-ssh-identity") {
4516
4714
  const key = "/run/forgezero-bootstrap-ssh-key";
4517
4715
  const generatedPublicKey = `${key}.pub`;
4518
4716
  try {
4519
- if (!existsSync8(operation.credential) || lstatSync2(operation.credential).size < 1) {
4520
- rmSync7(key, { force: true });
4521
- rmSync7(generatedPublicKey, { force: true });
4717
+ if (!existsSync9(operation.credential) || lstatSync2(operation.credential).size < 1) {
4718
+ rmSync8(key, { force: true });
4719
+ rmSync8(generatedPublicKey, { force: true });
4522
4720
  let result;
4523
4721
  if (operation.source) {
4524
- const source = existsSync8(operation.source) ? lstatSync2(operation.source) : undefined;
4722
+ const source = existsSync9(operation.source) ? lstatSync2(operation.source) : undefined;
4525
4723
  if (!source?.isFile() || source.isSymbolicLink() || source.uid !== 0 || source.nlink !== 1 || (source.mode & 63) !== 0 || source.size < 32 || source.size > 16 * 1024) {
4526
4724
  return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
4527
4725
  }
4528
4726
  copyFileSync3(operation.source, key);
4529
- chmodSync6(key, 384);
4727
+ chmodSync7(key, 384);
4530
4728
  } else {
4531
4729
  result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-bootstrap-runner", "-f", key]);
4532
4730
  if (result.exitCode !== 0)
@@ -4539,41 +4737,41 @@ var runProvisionOperation = async (operation) => {
4539
4737
  result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=bootstrap-ssh-key", key, operation.credential]);
4540
4738
  if (result.exitCode !== 0)
4541
4739
  return result;
4542
- chmodSync6(operation.credential, 256);
4740
+ chmodSync7(operation.credential, 256);
4543
4741
  }
4544
- if (!existsSync8(operation.publicKey) || lstatSync2(operation.publicKey).size < 1) {
4545
- if (!existsSync8(key)) {
4742
+ if (!existsSync9(operation.publicKey) || lstatSync2(operation.publicKey).size < 1) {
4743
+ if (!existsSync9(key)) {
4546
4744
  const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
4547
4745
  if (decrypted.exitCode !== 0)
4548
4746
  return decrypted;
4549
- chmodSync6(key, 384);
4747
+ chmodSync7(key, 384);
4550
4748
  }
4551
4749
  const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
4552
4750
  if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
4553
4751
  return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
4554
4752
  }
4555
- mkdirSync9(dirname8(operation.publicKey), { recursive: true, mode: 493 });
4753
+ mkdirSync9(dirname9(operation.publicKey), { recursive: true, mode: 493 });
4556
4754
  writeFileSync8(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
4557
4755
  `, { mode: 292 });
4558
- chmodSync6(operation.publicKey, 292);
4756
+ chmodSync7(operation.publicKey, 292);
4559
4757
  }
4560
4758
  if (operation.source)
4561
- rmSync7(operation.source, { force: true });
4759
+ rmSync8(operation.source, { force: true });
4562
4760
  return { stdout: "", exitCode: 0 };
4563
4761
  } finally {
4564
- rmSync7(key, { force: true });
4565
- rmSync7(generatedPublicKey, { force: true });
4762
+ rmSync8(key, { force: true });
4763
+ rmSync8(generatedPublicKey, { force: true });
4566
4764
  }
4567
4765
  }
4568
4766
  if (operation.kind === "ensure-enrolment") {
4569
- if (existsSync8(operation.state) && lstatSync2(operation.state).size > 0 || existsSync8(operation.credential) && lstatSync2(operation.credential).size > 0)
4767
+ if (existsSync9(operation.state) && lstatSync2(operation.state).size > 0 || existsSync9(operation.credential) && lstatSync2(operation.credential).size > 0)
4570
4768
  return { stdout: "", exitCode: 0 };
4571
- if (!existsSync8(operation.source))
4769
+ if (!existsSync9(operation.source))
4572
4770
  return { stdout: "enrolment source is missing", exitCode: 1 };
4573
4771
  const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
4574
4772
  if (result.exitCode === 0) {
4575
- chmodSync6(operation.credential, 256);
4576
- rmSync7(operation.source, { force: true });
4773
+ chmodSync7(operation.credential, 256);
4774
+ rmSync8(operation.source, { force: true });
4577
4775
  }
4578
4776
  return result;
4579
4777
  }
@@ -4588,7 +4786,7 @@ var runProvisionOperation = async (operation) => {
4588
4786
  return { stdout: `socket did not become ready: ${operation.path}`, exitCode: 1 };
4589
4787
  }
4590
4788
  if (operation.kind === "verify-file")
4591
- return existsSync8(operation.path) && lstatSync2(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
4789
+ return existsSync9(operation.path) && lstatSync2(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
4592
4790
  if (operation.kind === "verify-egress") {
4593
4791
  const active = await fixed(["/usr/bin/systemctl", "is-active", "forgezero-agent-egress.service"]);
4594
4792
  if (active.exitCode !== 0)
@@ -4622,7 +4820,7 @@ var runProvisionOperation = async (operation) => {
4622
4820
  const key = "/run/cloudflare-warp-key.gpg";
4623
4821
  writeFileSync8(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
4624
4822
  let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
4625
- rmSync7(key, { force: true });
4823
+ rmSync8(key, { force: true });
4626
4824
  if (result.exitCode !== 0)
4627
4825
  return result;
4628
4826
  const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
@@ -4671,9 +4869,9 @@ async function applyPlan(plan, run2) {
4671
4869
 
4672
4870
  // src/cloudflare-bootstrap.ts
4673
4871
  import { constants } from "node:fs";
4674
- import { createHmac, randomUUID as randomUUID3 } from "node:crypto";
4872
+ import { createHmac, randomUUID as randomUUID4 } from "node:crypto";
4675
4873
  import { chmod, lstat, mkdir, open, readdir, rename, rmdir, stat, unlink } from "node:fs/promises";
4676
- import { dirname as dirname9, join as join6, resolve as resolve5 } from "node:path";
4874
+ import { dirname as dirname10, join as join6, resolve as resolve5 } from "node:path";
4677
4875
  import { isIP as isIP3 } from "node:net";
4678
4876
 
4679
4877
  // src/cloudflare-edge.ts
@@ -4689,17 +4887,17 @@ import { DEFAULT_SOCKET as DEFAULT_SOCKET2 } from "@forgezero/vault";
4689
4887
  // src/bootstrap-bundle.ts
4690
4888
  import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
4691
4889
  import {
4692
- chmodSync as chmodSync7,
4890
+ chmodSync as chmodSync8,
4693
4891
  createReadStream as createReadStream2,
4694
- existsSync as existsSync9,
4892
+ existsSync as existsSync10,
4695
4893
  lstatSync as lstatSync3,
4696
4894
  mkdirSync as mkdirSync10,
4697
4895
  readFileSync as readFileSync8,
4698
- renameSync as renameSync9,
4699
- rmSync as rmSync8,
4896
+ renameSync as renameSync10,
4897
+ rmSync as rmSync9,
4700
4898
  writeFileSync as writeFileSync9
4701
4899
  } from "node:fs";
4702
- import { dirname as dirname10, isAbsolute, resolve as resolve6 } from "node:path";
4900
+ import { dirname as dirname11, isAbsolute, resolve as resolve6 } from "node:path";
4703
4901
  var BOOTSTRAP_BUNDLE_FORMAT = 1;
4704
4902
  var BOOTSTRAP_BUNDLE_KIND = "forgezero-api-git-bundle";
4705
4903
  var MAX_BOOTSTRAP_BUNDLE_BYTES = 512 * 1024 * 1024;
@@ -5004,17 +5202,17 @@ function localBootstrapHost() {
5004
5202
  };
5005
5203
  return {
5006
5204
  uid: () => process.getuid?.() ?? -1,
5007
- exists: existsSync10,
5205
+ exists: existsSync11,
5008
5206
  read: (path2) => readFileSync9(path2, "utf8"),
5009
5207
  write(path2, content, mode) {
5010
- mkdirSync11(dirname11(path2), { recursive: true, mode: 493 });
5208
+ mkdirSync11(dirname12(path2), { recursive: true, mode: 493 });
5011
5209
  const temporary = `${path2}.next.${process.pid}`;
5012
5210
  writeFileSync10(temporary, content, { mode });
5013
- chmodSync8(temporary, mode);
5014
- renameSync10(temporary, path2);
5211
+ chmodSync9(temporary, mode);
5212
+ renameSync11(temporary, path2);
5015
5213
  },
5016
5214
  mkdir: (path2, mode) => mkdirSync11(path2, { recursive: true, mode }),
5017
- remove: (path2) => rmSync9(path2, { force: true }),
5215
+ remove: (path2) => rmSync10(path2, { force: true }),
5018
5216
  inspect(path2) {
5019
5217
  const value = lstatSync4(path2);
5020
5218
  return { regular: value.isFile(), symbolic: value.isSymbolicLink(), uid: value.uid, mode: value.mode, links: value.nlink, size: value.size };
@@ -5037,9 +5235,9 @@ function localBootstrapHost() {
5037
5235
  },
5038
5236
  async installAgent(config, phase) {
5039
5237
  const capabilities = await readCapabilities(localRunner);
5040
- const hasBinding = existsSync10("/var/lib/forgezero/enrolment.json");
5041
- const hasEnrolCredential = existsSync10(ENROL_CREDENTIAL);
5042
- const initialBundle = config.kind === "platform" && !existsSync10(BOOTSTRAP_RELEASE_EVIDENCE) ? {
5238
+ const hasBinding = existsSync11("/var/lib/forgezero/enrolment.json");
5239
+ const hasEnrolCredential = existsSync11(ENROL_CREDENTIAL);
5240
+ const initialBundle = config.kind === "platform" && !existsSync11(BOOTSTRAP_RELEASE_EVIDENCE) ? {
5043
5241
  path: config.bootstrapBundle.bundleFile,
5044
5242
  manifestPath: config.bootstrapBundle.manifestFile,
5045
5243
  manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync9(config.bootstrapBundle.manifestFile, "utf8")))
@@ -5055,7 +5253,7 @@ function localBootstrapHost() {
5055
5253
  databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
5056
5254
  databasePorts: [8529]
5057
5255
  };
5058
- mkdirSync11(dirname11(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
5256
+ mkdirSync11(dirname12(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
5059
5257
  writeFileSync10(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
5060
5258
  `, { mode: 256 });
5061
5259
  }
@@ -5066,7 +5264,7 @@ function localBootstrapHost() {
5066
5264
  initialBundle
5067
5265
  });
5068
5266
  for (const unit2 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
5069
- mkdirSync11(dirname11(unit2.path), { recursive: true, mode: 493 });
5267
+ mkdirSync11(dirname12(unit2.path), { recursive: true, mode: 493 });
5070
5268
  writeFileSync10(unit2.path, unit2.unit, { mode: 420 });
5071
5269
  }
5072
5270
  await applyPlan(plan, localRunner);