@forgezero/agent 0.1.41 → 0.1.43

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.
Files changed (64) hide show
  1. package/README.md +299 -86
  2. package/dist/agent-heartbeat.js +6 -3
  3. package/dist/agent-update-helper.js +5 -2
  4. package/dist/agent-update.js +5 -2
  5. package/dist/bootstrap.d.ts +17 -8
  6. package/dist/bootstrap.js +1509 -512
  7. package/dist/cli/agent-install.d.ts +6 -5
  8. package/dist/cli/cloudflare-bootstrap.d.ts +12 -1
  9. package/dist/cli/maintenance.d.ts +23 -0
  10. package/dist/cli/run.d.ts +3 -1
  11. package/dist/cli/session-store.d.ts +5 -0
  12. package/dist/cloudflare-bootstrap.d.ts +73 -35
  13. package/dist/cloudflare-bootstrap.js +587 -90
  14. package/dist/cloudflare-edge.d.ts +64 -12
  15. package/dist/cloudflare-edge.js +103 -8
  16. package/dist/community-rehearsal-host.d.ts +51 -0
  17. package/dist/community-rehearsal-host.js +272 -0
  18. package/dist/credential-schema.d.ts +54 -0
  19. package/dist/credential-schema.js +47 -0
  20. package/dist/definition.d.ts +31 -5
  21. package/dist/definition.js +271 -44
  22. package/dist/deploy-file.js +294 -68
  23. package/dist/deployment-runner.js +18 -5
  24. package/dist/deployment.d.ts +13 -1
  25. package/dist/fz-agent.js +3932 -582
  26. package/dist/fz-git-ssh.js +122 -0
  27. package/dist/fz.js +3636 -1263
  28. package/dist/git-ssh.d.ts +5 -0
  29. package/dist/guest-enrolment.d.ts +2 -0
  30. package/dist/guest-enrolment.js +1 -0
  31. package/dist/host-maintenance.d.ts +39 -0
  32. package/dist/host-maintenance.js +135 -0
  33. package/dist/index.d.ts +4 -2
  34. package/dist/mesh-connector.d.ts +16 -0
  35. package/dist/mesh-connector.js +46 -0
  36. package/dist/metal-bootstrap.js +150 -7
  37. package/dist/metal-helper-socket.js +61 -31
  38. package/dist/metal-provision.d.ts +2 -2
  39. package/dist/metal-provision.js +62 -32
  40. package/dist/operator-bootstrap.d.ts +90 -0
  41. package/dist/operator-bootstrap.js +5709 -0
  42. package/dist/otel-collector.d.ts +18 -0
  43. package/dist/pipeline.d.ts +3 -2
  44. package/dist/pipeline.js +1 -1
  45. package/dist/platform-bootstrap-runtime.d.ts +39 -21
  46. package/dist/platform-bootstrap-runtime.js +182 -59
  47. package/dist/platform-fleet-verification.d.ts +19 -0
  48. package/dist/platform-fleet-verification.js +3873 -0
  49. package/dist/platform-genesis-config.d.ts +7 -0
  50. package/dist/platform-genesis.d.ts +17 -0
  51. package/dist/provision.d.ts +76 -3
  52. package/dist/provision.js +1061 -229
  53. package/dist/recovery-host.d.ts +7 -0
  54. package/dist/recovery-host.js +124 -0
  55. package/dist/service-supervisor.d.ts +42 -0
  56. package/dist/software-helper.d.ts +4 -0
  57. package/dist/software-helper.js +865 -63
  58. package/dist/software.d.ts +14 -3
  59. package/dist/software.js +163 -37
  60. package/dist/ssh-bootstrap.d.ts +97 -0
  61. package/dist/supervised-app.d.ts +2 -0
  62. package/dist/version.d.ts +1 -1
  63. package/package.json +175 -164
  64. package/schema/{deploy-v2.json → deploy-v3.json} +53 -6
@@ -0,0 +1,7 @@
1
+ export interface RecoveryHostResult {
2
+ exitCode: number;
3
+ stdout: string;
4
+ stderr: string;
5
+ }
6
+ export type RecoveryHostExec = (argv: readonly string[]) => Promise<RecoveryHostResult>;
7
+ export declare function runRecoveryHost(args: readonly string[], run?: RecoveryHostExec): Promise<void>;
@@ -0,0 +1,124 @@
1
+ // src/recovery-host.ts
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import { hostname } from "node:os";
4
+ import { join } from "node:path";
5
+ var execute = async (argv) => {
6
+ const child = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe", env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" } });
7
+ const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
8
+ return { stdout, stderr, exitCode };
9
+ };
10
+ var checked = async (run, argv, expected = 0) => {
11
+ const result = await run(argv);
12
+ if (result.exitCode !== expected)
13
+ throw new Error(`${argv[0]} failed (${result.exitCode})`);
14
+ return result;
15
+ };
16
+ var activeSlot = () => {
17
+ const slot = readFileSync("/opt/forgezero/.forge-slot", "utf8").trim();
18
+ if (slot !== "blue" && slot !== "green")
19
+ throw new Error("invalid active slot");
20
+ return slot;
21
+ };
22
+ var exactState = (path, expected) => {
23
+ if (!readFileSync(path, "utf8").split(`
24
+ `).includes(expected))
25
+ throw new Error(`state mismatch: ${expected}`);
26
+ };
27
+ var nonEmpty = (path) => {
28
+ if (!existsSync(path) || !statSync(path).isFile() || statSync(path).size < 1)
29
+ throw new Error(`required file missing: ${path}`);
30
+ };
31
+ var option = (args, name) => {
32
+ const value = args.find((arg) => arg.startsWith(`--${name}=`))?.slice(name.length + 3);
33
+ if (!value || /[\0\r\n]/.test(value))
34
+ throw new Error(`missing or invalid --${name}`);
35
+ return value;
36
+ };
37
+ var unit = (value) => {
38
+ if (!/^forgezero-dr-[a-z0-9-]{1,64}$/.test(value))
39
+ throw new Error("invalid recovery unit");
40
+ return value;
41
+ };
42
+ async function runRecoveryHost(args, run = execute) {
43
+ const action = args[0];
44
+ if (action === "preflight") {
45
+ const identity = option(args, "identity");
46
+ if (hostname().split(".")[0] !== identity)
47
+ throw new Error("recovery host identity mismatch");
48
+ const profile = option(args, "profile");
49
+ const role = option(args, "db-role");
50
+ const software = option(args, "software-profile");
51
+ const apiOrigin = option(args, "api-origin");
52
+ const edgeHostname = option(args, "edge-hostname");
53
+ const databaseAddress = args.find((arg) => arg.startsWith("--db-address="))?.slice(13);
54
+ exactState("/opt/forgezero/.forge-state", `profile=${profile}`);
55
+ exactState("/opt/forgezero/.forge-state", `db_role=${role}`);
56
+ exactState("/opt/forgezero/.forge-state", `software_profile=${software}`);
57
+ if (databaseAddress)
58
+ exactState("/opt/forgezero/.forge-state", `db_address=${databaseAddress}`);
59
+ exactState("/opt/forgezero/shared/.env", "ARANGO_DB=fz");
60
+ exactState("/opt/forgezero/shared/.env", "FZ_DATABASE_MODE=platform");
61
+ exactState("/opt/forgezero/shared/.env", `API_ORIGIN=${apiOrigin}`);
62
+ exactState("/opt/forgezero/shared/.env", `FZ_NODE_HOSTNAME=${edgeHostname}`);
63
+ nonEmpty("/etc/forgezero/creds/arangodb-jwt.cred");
64
+ const hasDatabase = role !== "none";
65
+ if (hasDatabase) {
66
+ nonEmpty("/etc/forgezero/creds/backup-s3-secret.cred");
67
+ nonEmpty("/etc/forgezero/creds/backup-recovery-root.cred");
68
+ }
69
+ const slot = activeSlot();
70
+ nonEmpty(join("/opt/forgezero/slots", slot, "src/server/maintenance/restore-backup.ts"));
71
+ nonEmpty(join("/opt/forgezero/slots", slot, "src/server/maintenance/verify-recovery-cluster.ts"));
72
+ await checked(run, ["/usr/bin/test", "-x", "/usr/local/bin/fz"]);
73
+ await checked(run, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-db.service"], hasDatabase ? 0 : 3);
74
+ if (args.includes("--require-stopped")) {
75
+ await checked(run, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@blue.service"], 3);
76
+ await checked(run, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@green.service"], 3);
77
+ }
78
+ return;
79
+ }
80
+ if (action === "fence") {
81
+ await checked(run, ["/usr/bin/systemctl", "stop", "forgezero@blue.service", "forgezero@green.service"]);
82
+ await checked(run, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@blue.service"], 3);
83
+ await checked(run, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@green.service"], 3);
84
+ const role = option(args, "db-role");
85
+ await checked(run, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-db.service"], role === "none" ? 3 : 0);
86
+ return;
87
+ }
88
+ if (action === "start-active") {
89
+ const slot = activeSlot();
90
+ const inactive = slot === "blue" ? "green" : "blue";
91
+ await checked(run, ["/usr/bin/systemctl", "stop", `forgezero@${inactive}.service`]);
92
+ await checked(run, ["/usr/bin/systemctl", "start", `forgezero@${slot}.service`]);
93
+ await checked(run, ["/usr/bin/systemctl", "is-active", "--quiet", `forgezero@${slot}.service`]);
94
+ await checked(run, ["/usr/bin/systemctl", "is-active", "--quiet", `forgezero@${inactive}.service`], 3);
95
+ return;
96
+ }
97
+ if (action === "maintenance") {
98
+ const separator = args.indexOf("--");
99
+ if (separator < 0 || separator === args.length - 1)
100
+ throw new Error("maintenance requires fixed fz argv after --");
101
+ const fzArgs = args.slice(separator + 1);
102
+ if (fzArgs[0] !== "db" || fzArgs.some((arg) => /[\0\r\n]/.test(arg)))
103
+ throw new Error("maintenance accepts only fz db argv");
104
+ const slot = activeSlot();
105
+ const properties = [
106
+ "--property=User=forgezero",
107
+ `--property=WorkingDirectory=/opt/forgezero/slots/${slot}`,
108
+ "--property=Environment=NODE_ENV=production",
109
+ "--property=EnvironmentFile=/opt/forgezero/shared/.env",
110
+ "--property=LoadCredentialEncrypted=arangodb-jwt:/etc/forgezero/creds/arangodb-jwt.cred",
111
+ ...args.includes("--fleet-fenced") ? ["--property=Environment=FZ_RECOVERY_FLEET_FENCED=all-api-and-scheduler-replicas-are-stopped"] : [],
112
+ ...args.includes("--backup-credentials") ? [
113
+ "--property=LoadCredentialEncrypted=backup-s3-secret:/etc/forgezero/creds/backup-s3-secret.cred",
114
+ "--property=LoadCredentialEncrypted=backup-recovery-root:/etc/forgezero/creds/backup-recovery-root.cred"
115
+ ] : []
116
+ ];
117
+ await checked(run, ["/usr/bin/systemd-run", "--quiet", "--wait", "--pipe", "--collect", `--unit=${unit(option(args, "unit"))}`, ...properties, "/usr/local/bin/fz", ...fzArgs]);
118
+ return;
119
+ }
120
+ throw new Error("unknown recovery-host action");
121
+ }
122
+ export {
123
+ runRecoveryHost
124
+ };
@@ -0,0 +1,42 @@
1
+ import { type DeploymentService } from './definition';
2
+ export declare const SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
3
+ export declare const SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
4
+ export declare const SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
5
+ export declare const SERVICE_NGINX_DIRECTORY = "/etc/nginx/conf.d";
6
+ export interface ServiceActivationRequest {
7
+ key: string;
8
+ revision: string;
9
+ release: string;
10
+ root: string;
11
+ service: DeploymentService;
12
+ }
13
+ export interface ServiceActivationState {
14
+ format: 1;
15
+ id: string;
16
+ key: string;
17
+ revision: string;
18
+ strategy: DeploymentService['strategy'];
19
+ publicPort: number;
20
+ applicationPorts: number[];
21
+ activeSlot: 0 | 1;
22
+ healthPath: string;
23
+ updatedAtTs: number;
24
+ }
25
+ export interface ServiceSupervisorHost {
26
+ write(path: string, content: string, mode: number): void;
27
+ read(path: string): string;
28
+ exists(path: string): boolean;
29
+ list(path: string): string[];
30
+ realpath(path: string): string;
31
+ mkdir(path: string, mode: number): void;
32
+ remove(path: string): void;
33
+ exec(argv: readonly string[]): Promise<{
34
+ exitCode: number;
35
+ output: string;
36
+ }>;
37
+ health(port: number, path: string): Promise<boolean>;
38
+ sleep(ms: number): Promise<void>;
39
+ now(): number;
40
+ }
41
+ /** Serialized by the root helper. Starts the new slot, proves health, switches nginx, then drains old. */
42
+ export declare function activateSupervisedService(request: ServiceActivationRequest, host?: ServiceSupervisorHost): Promise<ServiceActivationState>;
@@ -1,12 +1,16 @@
1
1
  import { type Server } from 'node:net';
2
2
  import { ensureSoftwareRequirements, type SoftwareRequirement } from './software';
3
+ import { activateSupervisedService, type ServiceActivationRequest, type ServiceActivationState } from './service-supervisor';
3
4
  export declare const DEFAULT_SOFTWARE_HELPER_SOCKET = "/run/forgezero-software/helper.sock";
4
5
  export declare const SOFTWARE_HELPER_GROUP = "forgezero-software";
5
6
  export declare const SOFTWARE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-software-helper.service";
6
7
  export declare function startSoftwareHelper(options?: {
7
8
  socketPath?: string;
8
9
  ensure?: typeof ensureSoftwareRequirements;
10
+ activate?: typeof activateSupervisedService;
11
+ allowedRoot?: string;
9
12
  }): Server;
13
+ export declare function requestServiceActivation(request: ServiceActivationRequest, socketPath?: string, timeoutMs?: number): Promise<ServiceActivationState>;
10
14
  export declare function requestSoftware(requirements: readonly SoftwareRequirement[], socketPath?: string, timeoutMs?: number): Promise<Array<{
11
15
  id: string;
12
16
  version: string;