@forgezero/agent 0.1.123 → 0.1.125

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.
@@ -917,7 +917,7 @@ async function postSignedNode(options, path, body) {
917
917
  }
918
918
 
919
919
  // src/version.ts
920
- var VERSION3 = "0.1.123";
920
+ var VERSION3 = "0.1.125";
921
921
 
922
922
  // src/agent-heartbeat.ts
923
923
  function readAgentHostMetrics() {
package/dist/bootstrap.js CHANGED
@@ -1460,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1460
1460
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1461
1461
 
1462
1462
  // src/version.ts
1463
- var VERSION = "0.1.123";
1463
+ var VERSION = "0.1.125";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -9,6 +9,8 @@ export declare const COMMUNITY_REHEARSAL_COORDINATOR_PORT = 18529;
9
9
  export declare const COMMUNITY_REHEARSAL_DATABASE_PORT_RANGE = "18528:18539";
10
10
  export declare const COMMUNITY_REHEARSAL_API_PORT = 18787;
11
11
  export declare const COMMUNITY_REHEARSAL_FIREWALL_COMMENT = "fz-community-rehearsal";
12
+ export declare const COMMUNITY_REHEARSAL_EGRESS_TABLE = "forgezero_agent_egress";
13
+ export declare const COMMUNITY_REHEARSAL_EGRESS_CHAIN = "output";
12
14
  export interface CommunityRehearsalNode {
13
15
  name: string;
14
16
  address: string;
@@ -48,6 +50,11 @@ export type CommunityHostOperation = {
48
50
  path: typeof COMMUNITY_DATABASE_ROOT | typeof COMMUNITY_REHEARSAL_ROOT | typeof COMMUNITY_CREDENTIAL_PARENT;
49
51
  } | {
50
52
  kind: 'clear-firewall';
53
+ } | {
54
+ kind: 'prepare-egress';
55
+ addresses: readonly string[];
56
+ } | {
57
+ kind: 'clear-egress';
51
58
  } | {
52
59
  kind: 'write';
53
60
  path: string;
@@ -75,5 +82,7 @@ export declare function planCommunityRehearsalPrepare(request: Extract<Community
75
82
  /** Remove only the dedicated rehearsal units, credentials, release and data. */
76
83
  export declare function planCommunityRehearsalCleanup(): CommunityHostOperation[];
77
84
  export declare function communityRehearsalFirewallRuleNumbers(status: string): number[];
85
+ export declare function communityRehearsalEgressRuleHandles(ruleset: string): number[];
86
+ export declare function communityRehearsalEgressRuleArguments(uid: string, addresses: readonly string[]): readonly (readonly string[])[];
78
87
  /** Execute only the dedicated four-node rehearsal operation selected by a typed request. */
79
88
  export declare function runCommunityRehearsalHost(request: CommunityRehearsalHostRequest): Promise<Record<string, unknown>>;
@@ -577,6 +577,8 @@ var COMMUNITY_REHEARSAL_COORDINATOR_PORT = 18529;
577
577
  var COMMUNITY_REHEARSAL_DATABASE_PORT_RANGE = "18528:18539";
578
578
  var COMMUNITY_REHEARSAL_API_PORT = 18787;
579
579
  var COMMUNITY_REHEARSAL_FIREWALL_COMMENT = "fz-community-rehearsal";
580
+ var COMMUNITY_REHEARSAL_EGRESS_TABLE = "forgezero_agent_egress";
581
+ var COMMUNITY_REHEARSAL_EGRESS_CHAIN = "output";
580
582
  var RELEASE = /^[a-f0-9]{64}$/;
581
583
  var SAFE_NAME = /^[a-z][a-z0-9-]{0,62}$/;
582
584
  async function ensureCommunityRehearsalArango(execute = executeSoftwareOperation) {
@@ -705,10 +707,10 @@ User=forgezero
705
707
  Group=forgezero
706
708
  WorkingDirectory=${COMMUNITY_REHEARSAL_ROOT}/current
707
709
  LoadCredentialEncrypted=arangodb-jwt:${COMMUNITY_CREDENTIAL_ROOT}/arangodb-jwt.cred
708
- Environment=HOST=127.0.0.1
710
+ Environment=HOST=${node.address}
709
711
  Environment=PORT=${COMMUNITY_REHEARSAL_API_PORT}
710
712
  Environment=NODE_NAME=${node.name}
711
- Environment=ARANGO_URL=http://127.0.0.1:${COMMUNITY_REHEARSAL_COORDINATOR_PORT}
713
+ Environment=ARANGO_URL=http://${node.address}:${COMMUNITY_REHEARSAL_COORDINATOR_PORT}
712
714
  Environment=ARANGO_URLS=${urls}
713
715
  Environment=ARANGO_READ_PREFERRED_URLS=${readPreferred}
714
716
  Environment=ARANGO_READ_PREFERRED_FALLBACK=error
@@ -740,6 +742,7 @@ function planCommunityRehearsalPrepare(request) {
740
742
  { kind: "exec", argv: ["/usr/bin/systemctl", "stop", "forgezero-community-api.service"], accepted: [0, 5] },
741
743
  { kind: "exec", argv: ["/usr/bin/systemctl", "stop", "forgezero-rehearsal-db.service"], accepted: [0, 5] },
742
744
  { kind: "clear-firewall" },
745
+ { kind: "prepare-egress", addresses: topology.map(({ address }) => address) },
743
746
  ...topology.map(({ address }) => ({
744
747
  kind: "exec",
745
748
  argv: [
@@ -757,6 +760,23 @@ function planCommunityRehearsalPrepare(request) {
757
760
  COMMUNITY_REHEARSAL_FIREWALL_COMMENT
758
761
  ]
759
762
  })),
763
+ ...topology.map(({ address }) => ({
764
+ kind: "exec",
765
+ argv: [
766
+ "/usr/sbin/ufw",
767
+ "allow",
768
+ "from",
769
+ address,
770
+ "to",
771
+ "any",
772
+ "port",
773
+ String(COMMUNITY_REHEARSAL_API_PORT),
774
+ "proto",
775
+ "tcp",
776
+ "comment",
777
+ COMMUNITY_REHEARSAL_FIREWALL_COMMENT
778
+ ]
779
+ })),
760
780
  { kind: "remove-tree", path: COMMUNITY_DATABASE_ROOT },
761
781
  { kind: "exec", argv: ["/usr/bin/install", "-d", "-m", "0700", "-o", "root", "-g", "root", COMMUNITY_CREDENTIAL_ROOT] },
762
782
  { kind: "exec", argv: ["/usr/bin/install", "-d", "-m", "0700", "-o", "arangodb", "-g", "arangodb", COMMUNITY_DATABASE_ROOT] },
@@ -785,6 +805,7 @@ function planCommunityRehearsalCleanup() {
785
805
  { kind: "remove-tree", path: COMMUNITY_DATABASE_ROOT },
786
806
  { kind: "remove-tree", path: COMMUNITY_REHEARSAL_ROOT },
787
807
  { kind: "remove-tree", path: COMMUNITY_CREDENTIAL_PARENT },
808
+ { kind: "clear-egress" },
788
809
  { kind: "clear-firewall" },
789
810
  { kind: "exec", argv: ["/usr/bin/systemctl", "daemon-reload"] },
790
811
  { kind: "exec", argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-community-api.service", "forgezero-rehearsal-db.service"], accepted: [0, 1, 5] }
@@ -807,6 +828,10 @@ async function applyOperations(operations) {
807
828
  rmSync2(operation.path, { recursive: true, force: true });
808
829
  else if (operation.kind === "clear-firewall")
809
830
  await clearCommunityRehearsalFirewall();
831
+ else if (operation.kind === "prepare-egress")
832
+ await prepareCommunityRehearsalEgress(operation.addresses);
833
+ else if (operation.kind === "clear-egress")
834
+ await clearCommunityRehearsalEgress();
810
835
  else if (operation.kind === "write") {
811
836
  mkdirSync2(dirname2(operation.path), { recursive: true });
812
837
  writeFileSync2(operation.path, operation.content, { mode: operation.mode });
@@ -837,6 +862,49 @@ function communityRehearsalFirewallRuleNumbers(status) {
837
862
  return match ? [Number(match[1])] : [];
838
863
  }).filter((number) => Number.isSafeInteger(number) && number > 0).sort((left, right) => right - left);
839
864
  }
865
+ function communityRehearsalEgressRuleHandles(ruleset) {
866
+ return ruleset.split(/\r?\n/).flatMap((line) => {
867
+ if (!line.includes(`comment "${COMMUNITY_REHEARSAL_FIREWALL_COMMENT}"`))
868
+ return [];
869
+ const match = line.match(/# handle (\d+)\s*$/);
870
+ return match ? [Number(match[1])] : [];
871
+ }).filter((number) => Number.isSafeInteger(number) && number > 0).sort((left, right) => right - left);
872
+ }
873
+ function communityRehearsalEgressRuleArguments(uid, addresses) {
874
+ if (!/^\d+$/.test(uid) || addresses.some((address) => isIP(address) !== 4) || new Set(addresses).size !== addresses.length) {
875
+ throw new Error("rehearsal egress arguments require one uid and unique IPv4 addresses");
876
+ }
877
+ return addresses.flatMap((address) => [
878
+ [
879
+ "meta",
880
+ "skuid",
881
+ uid,
882
+ "ct",
883
+ "state",
884
+ "established,related",
885
+ "ip",
886
+ "daddr",
887
+ address,
888
+ "accept",
889
+ "comment",
890
+ COMMUNITY_REHEARSAL_FIREWALL_COMMENT
891
+ ],
892
+ [
893
+ "meta",
894
+ "skuid",
895
+ uid,
896
+ "ip",
897
+ "daddr",
898
+ address,
899
+ "tcp",
900
+ "dport",
901
+ String(COMMUNITY_REHEARSAL_COORDINATOR_PORT),
902
+ "accept",
903
+ "comment",
904
+ COMMUNITY_REHEARSAL_FIREWALL_COMMENT
905
+ ]
906
+ ]);
907
+ }
840
908
  async function clearCommunityRehearsalFirewall() {
841
909
  const status = await exec(["/usr/sbin/ufw", "status", "numbered"]);
842
910
  if (status.exitCode !== 0)
@@ -848,8 +916,74 @@ async function clearCommunityRehearsalFirewall() {
848
916
  }
849
917
  }
850
918
  }
919
+ var rehearsalEgressRules = async () => {
920
+ const result = await exec([
921
+ "/usr/sbin/nft",
922
+ "-a",
923
+ "list",
924
+ "chain",
925
+ "inet",
926
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
927
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN
928
+ ]);
929
+ if (result.exitCode === 0)
930
+ return { exists: true, output: result.output };
931
+ if (/No such file or directory|does not exist/i.test(result.output))
932
+ return { exists: false, output: result.output };
933
+ throw new Error(`unable to inspect rehearsal egress rules: ${result.output.trim()}`);
934
+ };
935
+ async function clearCommunityRehearsalEgress() {
936
+ const listed = await rehearsalEgressRules();
937
+ if (!listed.exists)
938
+ return;
939
+ for (const handle of communityRehearsalEgressRuleHandles(listed.output)) {
940
+ const removed = await exec([
941
+ "/usr/sbin/nft",
942
+ "delete",
943
+ "rule",
944
+ "inet",
945
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
946
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN,
947
+ "handle",
948
+ String(handle)
949
+ ]);
950
+ if (removed.exitCode !== 0) {
951
+ throw new Error(`unable to remove rehearsal egress rule ${handle}: ${removed.output.trim()}`);
952
+ }
953
+ }
954
+ }
955
+ async function prepareCommunityRehearsalEgress(addresses) {
956
+ await clearCommunityRehearsalEgress();
957
+ if (!(await rehearsalEgressRules()).exists)
958
+ return;
959
+ const identity = await exec(["/usr/bin/id", "-u", "forgezero"]);
960
+ const uid = identity.output.trim();
961
+ if (identity.exitCode !== 0 || !/^\d+$/.test(uid))
962
+ throw new Error("unable to resolve the forgezero service uid");
963
+ for (const rule of communityRehearsalEgressRuleArguments(uid, addresses)) {
964
+ const inserted = await exec([
965
+ "/usr/sbin/nft",
966
+ "insert",
967
+ "rule",
968
+ "inet",
969
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
970
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN,
971
+ ...rule
972
+ ]);
973
+ if (inserted.exitCode !== 0) {
974
+ await clearCommunityRehearsalEgress();
975
+ throw new Error(`unable to install a rehearsal egress rule: ${inserted.output.trim()}`);
976
+ }
977
+ }
978
+ const installed = communityRehearsalEgressRuleHandles((await rehearsalEgressRules()).output);
979
+ if (installed.length !== addresses.length * 2) {
980
+ await clearCommunityRehearsalEgress();
981
+ throw new Error("rehearsal egress rule verification failed");
982
+ }
983
+ }
851
984
  var responseFor = async (request) => {
852
- const base = `http://127.0.0.1:${COMMUNITY_REHEARSAL_API_PORT}`;
985
+ const node = request.topology.find((entry) => entry.name === request.node);
986
+ const base = `http://${node.address}:${COMMUNITY_REHEARSAL_API_PORT}`;
853
987
  if (request.operation === "health")
854
988
  return fetch(`${base}/health`, { signal: AbortSignal.timeout(15000) });
855
989
  if (request.operation === "init")
@@ -911,7 +1045,7 @@ async function runCommunityRehearsalHost(request) {
911
1045
  }
912
1046
  if (request.action === "database-ready" || request.action === "starter-ready" || request.action === "api-ready") {
913
1047
  const starter = topology.find((entry) => entry.starter);
914
- const url = request.action === "database-ready" ? `http://127.0.0.1:${COMMUNITY_REHEARSAL_COORDINATOR_PORT}/_api/version` : request.action === "starter-ready" ? `http://${starter.address}:${COMMUNITY_REHEARSAL_STARTER_PORT}/version` : `http://127.0.0.1:${COMMUNITY_REHEARSAL_API_PORT}/health`;
1048
+ const url = request.action === "database-ready" ? `http://${node.address}:${COMMUNITY_REHEARSAL_COORDINATOR_PORT}/_api/version` : request.action === "starter-ready" ? `http://${starter.address}:${COMMUNITY_REHEARSAL_STARTER_PORT}/version` : `http://${node.address}:${COMMUNITY_REHEARSAL_API_PORT}/health`;
915
1049
  const response2 = await fetch(url, { signal: AbortSignal.timeout(2000) }).catch(() => {
916
1050
  return;
917
1051
  });
@@ -935,10 +1069,14 @@ export {
935
1069
  parseCommunityRehearsalHostRequest,
936
1070
  ensureCommunityRehearsalArango,
937
1071
  communityRehearsalFirewallRuleNumbers,
1072
+ communityRehearsalEgressRuleHandles,
1073
+ communityRehearsalEgressRuleArguments,
938
1074
  COMMUNITY_REHEARSAL_VERSION,
939
1075
  COMMUNITY_REHEARSAL_STARTER_PORT,
940
1076
  COMMUNITY_REHEARSAL_ROOT,
941
1077
  COMMUNITY_REHEARSAL_FIREWALL_COMMENT,
1078
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
1079
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN,
942
1080
  COMMUNITY_REHEARSAL_DATABASE_PORT_RANGE,
943
1081
  COMMUNITY_REHEARSAL_COORDINATOR_PORT,
944
1082
  COMMUNITY_REHEARSAL_API_PORT,
package/dist/fz-agent.js CHANGED
@@ -9640,7 +9640,7 @@ async function writeAndCloseProcessInput(input, value) {
9640
9640
  }
9641
9641
 
9642
9642
  // src/version.ts
9643
- var VERSION2 = "0.1.123";
9643
+ var VERSION2 = "0.1.125";
9644
9644
 
9645
9645
  // src/ssh-bootstrap.ts
9646
9646
  class SshBootstrapError extends Error {
@@ -17125,6 +17125,8 @@ var COMMUNITY_REHEARSAL_COORDINATOR_PORT = 18529;
17125
17125
  var COMMUNITY_REHEARSAL_DATABASE_PORT_RANGE = "18528:18539";
17126
17126
  var COMMUNITY_REHEARSAL_API_PORT = 18787;
17127
17127
  var COMMUNITY_REHEARSAL_FIREWALL_COMMENT = "fz-community-rehearsal";
17128
+ var COMMUNITY_REHEARSAL_EGRESS_TABLE = "forgezero_agent_egress";
17129
+ var COMMUNITY_REHEARSAL_EGRESS_CHAIN = "output";
17128
17130
  var RELEASE = /^[a-f0-9]{64}$/;
17129
17131
  var SAFE_NAME2 = /^[a-z][a-z0-9-]{0,62}$/;
17130
17132
  async function ensureCommunityRehearsalArango(execute3 = executeSoftwareOperation) {
@@ -17253,10 +17255,10 @@ User=forgezero
17253
17255
  Group=forgezero
17254
17256
  WorkingDirectory=${COMMUNITY_REHEARSAL_ROOT}/current
17255
17257
  LoadCredentialEncrypted=arangodb-jwt:${COMMUNITY_CREDENTIAL_ROOT}/arangodb-jwt.cred
17256
- Environment=HOST=127.0.0.1
17258
+ Environment=HOST=${node.address}
17257
17259
  Environment=PORT=${COMMUNITY_REHEARSAL_API_PORT}
17258
17260
  Environment=NODE_NAME=${node.name}
17259
- Environment=ARANGO_URL=http://127.0.0.1:${COMMUNITY_REHEARSAL_COORDINATOR_PORT}
17261
+ Environment=ARANGO_URL=http://${node.address}:${COMMUNITY_REHEARSAL_COORDINATOR_PORT}
17260
17262
  Environment=ARANGO_URLS=${urls}
17261
17263
  Environment=ARANGO_READ_PREFERRED_URLS=${readPreferred}
17262
17264
  Environment=ARANGO_READ_PREFERRED_FALLBACK=error
@@ -17288,6 +17290,7 @@ function planCommunityRehearsalPrepare(request) {
17288
17290
  { kind: "exec", argv: ["/usr/bin/systemctl", "stop", "forgezero-community-api.service"], accepted: [0, 5] },
17289
17291
  { kind: "exec", argv: ["/usr/bin/systemctl", "stop", "forgezero-rehearsal-db.service"], accepted: [0, 5] },
17290
17292
  { kind: "clear-firewall" },
17293
+ { kind: "prepare-egress", addresses: topology.map(({ address }) => address) },
17291
17294
  ...topology.map(({ address }) => ({
17292
17295
  kind: "exec",
17293
17296
  argv: [
@@ -17305,6 +17308,23 @@ function planCommunityRehearsalPrepare(request) {
17305
17308
  COMMUNITY_REHEARSAL_FIREWALL_COMMENT
17306
17309
  ]
17307
17310
  })),
17311
+ ...topology.map(({ address }) => ({
17312
+ kind: "exec",
17313
+ argv: [
17314
+ "/usr/sbin/ufw",
17315
+ "allow",
17316
+ "from",
17317
+ address,
17318
+ "to",
17319
+ "any",
17320
+ "port",
17321
+ String(COMMUNITY_REHEARSAL_API_PORT),
17322
+ "proto",
17323
+ "tcp",
17324
+ "comment",
17325
+ COMMUNITY_REHEARSAL_FIREWALL_COMMENT
17326
+ ]
17327
+ })),
17308
17328
  { kind: "remove-tree", path: COMMUNITY_DATABASE_ROOT },
17309
17329
  { kind: "exec", argv: ["/usr/bin/install", "-d", "-m", "0700", "-o", "root", "-g", "root", COMMUNITY_CREDENTIAL_ROOT] },
17310
17330
  { kind: "exec", argv: ["/usr/bin/install", "-d", "-m", "0700", "-o", "arangodb", "-g", "arangodb", COMMUNITY_DATABASE_ROOT] },
@@ -17333,6 +17353,7 @@ function planCommunityRehearsalCleanup() {
17333
17353
  { kind: "remove-tree", path: COMMUNITY_DATABASE_ROOT },
17334
17354
  { kind: "remove-tree", path: COMMUNITY_REHEARSAL_ROOT },
17335
17355
  { kind: "remove-tree", path: COMMUNITY_CREDENTIAL_PARENT },
17356
+ { kind: "clear-egress" },
17336
17357
  { kind: "clear-firewall" },
17337
17358
  { kind: "exec", argv: ["/usr/bin/systemctl", "daemon-reload"] },
17338
17359
  { kind: "exec", argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-community-api.service", "forgezero-rehearsal-db.service"], accepted: [0, 1, 5] }
@@ -17355,6 +17376,10 @@ async function applyOperations(operations) {
17355
17376
  rmSync13(operation.path, { recursive: true, force: true });
17356
17377
  else if (operation.kind === "clear-firewall")
17357
17378
  await clearCommunityRehearsalFirewall();
17379
+ else if (operation.kind === "prepare-egress")
17380
+ await prepareCommunityRehearsalEgress(operation.addresses);
17381
+ else if (operation.kind === "clear-egress")
17382
+ await clearCommunityRehearsalEgress();
17358
17383
  else if (operation.kind === "write") {
17359
17384
  mkdirSync17(dirname15(operation.path), { recursive: true });
17360
17385
  writeFileSync17(operation.path, operation.content, { mode: operation.mode });
@@ -17385,6 +17410,49 @@ function communityRehearsalFirewallRuleNumbers(status) {
17385
17410
  return match ? [Number(match[1])] : [];
17386
17411
  }).filter((number2) => Number.isSafeInteger(number2) && number2 > 0).sort((left, right) => right - left);
17387
17412
  }
17413
+ function communityRehearsalEgressRuleHandles(ruleset) {
17414
+ return ruleset.split(/\r?\n/).flatMap((line) => {
17415
+ if (!line.includes(`comment "${COMMUNITY_REHEARSAL_FIREWALL_COMMENT}"`))
17416
+ return [];
17417
+ const match = line.match(/# handle (\d+)\s*$/);
17418
+ return match ? [Number(match[1])] : [];
17419
+ }).filter((number2) => Number.isSafeInteger(number2) && number2 > 0).sort((left, right) => right - left);
17420
+ }
17421
+ function communityRehearsalEgressRuleArguments(uid, addresses) {
17422
+ if (!/^\d+$/.test(uid) || addresses.some((address) => isIP6(address) !== 4) || new Set(addresses).size !== addresses.length) {
17423
+ throw new Error("rehearsal egress arguments require one uid and unique IPv4 addresses");
17424
+ }
17425
+ return addresses.flatMap((address) => [
17426
+ [
17427
+ "meta",
17428
+ "skuid",
17429
+ uid,
17430
+ "ct",
17431
+ "state",
17432
+ "established,related",
17433
+ "ip",
17434
+ "daddr",
17435
+ address,
17436
+ "accept",
17437
+ "comment",
17438
+ COMMUNITY_REHEARSAL_FIREWALL_COMMENT
17439
+ ],
17440
+ [
17441
+ "meta",
17442
+ "skuid",
17443
+ uid,
17444
+ "ip",
17445
+ "daddr",
17446
+ address,
17447
+ "tcp",
17448
+ "dport",
17449
+ String(COMMUNITY_REHEARSAL_COORDINATOR_PORT),
17450
+ "accept",
17451
+ "comment",
17452
+ COMMUNITY_REHEARSAL_FIREWALL_COMMENT
17453
+ ]
17454
+ ]);
17455
+ }
17388
17456
  async function clearCommunityRehearsalFirewall() {
17389
17457
  const status = await exec(["/usr/sbin/ufw", "status", "numbered"]);
17390
17458
  if (status.exitCode !== 0)
@@ -17396,8 +17464,74 @@ async function clearCommunityRehearsalFirewall() {
17396
17464
  }
17397
17465
  }
17398
17466
  }
17467
+ var rehearsalEgressRules = async () => {
17468
+ const result = await exec([
17469
+ "/usr/sbin/nft",
17470
+ "-a",
17471
+ "list",
17472
+ "chain",
17473
+ "inet",
17474
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
17475
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN
17476
+ ]);
17477
+ if (result.exitCode === 0)
17478
+ return { exists: true, output: result.output };
17479
+ if (/No such file or directory|does not exist/i.test(result.output))
17480
+ return { exists: false, output: result.output };
17481
+ throw new Error(`unable to inspect rehearsal egress rules: ${result.output.trim()}`);
17482
+ };
17483
+ async function clearCommunityRehearsalEgress() {
17484
+ const listed = await rehearsalEgressRules();
17485
+ if (!listed.exists)
17486
+ return;
17487
+ for (const handle of communityRehearsalEgressRuleHandles(listed.output)) {
17488
+ const removed = await exec([
17489
+ "/usr/sbin/nft",
17490
+ "delete",
17491
+ "rule",
17492
+ "inet",
17493
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
17494
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN,
17495
+ "handle",
17496
+ String(handle)
17497
+ ]);
17498
+ if (removed.exitCode !== 0) {
17499
+ throw new Error(`unable to remove rehearsal egress rule ${handle}: ${removed.output.trim()}`);
17500
+ }
17501
+ }
17502
+ }
17503
+ async function prepareCommunityRehearsalEgress(addresses) {
17504
+ await clearCommunityRehearsalEgress();
17505
+ if (!(await rehearsalEgressRules()).exists)
17506
+ return;
17507
+ const identity = await exec(["/usr/bin/id", "-u", "forgezero"]);
17508
+ const uid = identity.output.trim();
17509
+ if (identity.exitCode !== 0 || !/^\d+$/.test(uid))
17510
+ throw new Error("unable to resolve the forgezero service uid");
17511
+ for (const rule of communityRehearsalEgressRuleArguments(uid, addresses)) {
17512
+ const inserted = await exec([
17513
+ "/usr/sbin/nft",
17514
+ "insert",
17515
+ "rule",
17516
+ "inet",
17517
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
17518
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN,
17519
+ ...rule
17520
+ ]);
17521
+ if (inserted.exitCode !== 0) {
17522
+ await clearCommunityRehearsalEgress();
17523
+ throw new Error(`unable to install a rehearsal egress rule: ${inserted.output.trim()}`);
17524
+ }
17525
+ }
17526
+ const installed = communityRehearsalEgressRuleHandles((await rehearsalEgressRules()).output);
17527
+ if (installed.length !== addresses.length * 2) {
17528
+ await clearCommunityRehearsalEgress();
17529
+ throw new Error("rehearsal egress rule verification failed");
17530
+ }
17531
+ }
17399
17532
  var responseFor = async (request) => {
17400
- const base = `http://127.0.0.1:${COMMUNITY_REHEARSAL_API_PORT}`;
17533
+ const node = request.topology.find((entry) => entry.name === request.node);
17534
+ const base = `http://${node.address}:${COMMUNITY_REHEARSAL_API_PORT}`;
17401
17535
  if (request.operation === "health")
17402
17536
  return fetch(`${base}/health`, { signal: AbortSignal.timeout(15000) });
17403
17537
  if (request.operation === "init")
@@ -17459,7 +17593,7 @@ async function runCommunityRehearsalHost(request) {
17459
17593
  }
17460
17594
  if (request.action === "database-ready" || request.action === "starter-ready" || request.action === "api-ready") {
17461
17595
  const starter = topology.find((entry) => entry.starter);
17462
- const url = request.action === "database-ready" ? `http://127.0.0.1:${COMMUNITY_REHEARSAL_COORDINATOR_PORT}/_api/version` : request.action === "starter-ready" ? `http://${starter.address}:${COMMUNITY_REHEARSAL_STARTER_PORT}/version` : `http://127.0.0.1:${COMMUNITY_REHEARSAL_API_PORT}/health`;
17596
+ const url = request.action === "database-ready" ? `http://${node.address}:${COMMUNITY_REHEARSAL_COORDINATOR_PORT}/_api/version` : request.action === "starter-ready" ? `http://${starter.address}:${COMMUNITY_REHEARSAL_STARTER_PORT}/version` : `http://${node.address}:${COMMUNITY_REHEARSAL_API_PORT}/health`;
17463
17597
  const response2 = await fetch(url, { signal: AbortSignal.timeout(2000) }).catch(() => {
17464
17598
  return;
17465
17599
  });
package/dist/fz.js CHANGED
@@ -4844,7 +4844,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
4844
4844
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
4845
4845
 
4846
4846
  // src/version.ts
4847
- var VERSION2 = "0.1.123";
4847
+ var VERSION2 = "0.1.125";
4848
4848
 
4849
4849
  // src/software.ts
4850
4850
  var PINNED_BUN_VERSION = "1.3.14";
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
366
366
  }
367
367
 
368
368
  // src/version.ts
369
- var VERSION = "0.1.123";
369
+ var VERSION = "0.1.125";
370
370
 
371
371
  // src/otel-collector.ts
372
372
  var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
@@ -1460,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1460
1460
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1461
1461
 
1462
1462
  // src/version.ts
1463
- var VERSION = "0.1.123";
1463
+ var VERSION = "0.1.125";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -3020,7 +3020,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3020
3020
  }
3021
3021
 
3022
3022
  // src/version.ts
3023
- var VERSION3 = "0.1.123";
3023
+ var VERSION3 = "0.1.125";
3024
3024
 
3025
3025
  // src/egress-policy.ts
3026
3026
  import { realpathSync as realpathSync3 } from "node:fs";
package/dist/provision.js CHANGED
@@ -3020,7 +3020,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3020
3020
  }
3021
3021
 
3022
3022
  // src/version.ts
3023
- var VERSION3 = "0.1.123";
3023
+ var VERSION3 = "0.1.125";
3024
3024
 
3025
3025
  // src/egress-policy.ts
3026
3026
  import { realpathSync as realpathSync3 } from "node:fs";
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** One package version shared by both public binaries. Pinned to package.json by tests. */
2
- export declare const VERSION = "0.1.123";
2
+ export declare const VERSION = "0.1.125";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.123",
3
+ "version": "0.1.125",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",