@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.
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +1 -1
- package/dist/community-rehearsal-host.d.ts +9 -0
- package/dist/community-rehearsal-host.js +142 -4
- package/dist/fz-agent.js +139 -5
- package/dist/fz.js +1 -1
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.js +1 -1
- package/dist/platform-fleet-verification.js +1 -1
- package/dist/provision.js +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/agent-heartbeat.js
CHANGED
package/dist/bootstrap.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
4847
|
+
var VERSION2 = "0.1.125";
|
|
4848
4848
|
|
|
4849
4849
|
// src/software.ts
|
|
4850
4850
|
var PINNED_BUN_VERSION = "1.3.14";
|
package/dist/metal-bootstrap.js
CHANGED
|
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
// src/version.ts
|
|
369
|
-
var VERSION = "0.1.
|
|
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";
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
2
|
+
export declare const VERSION = "0.1.125";
|