@forgezero/agent 0.1.144 → 0.1.146

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.
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
366
366
  }
367
367
 
368
368
  // src/version.ts
369
- var VERSION = "0.1.144";
369
+ var VERSION = "0.1.146";
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.144";
1463
+ var VERSION = "0.1.146";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -3555,6 +3555,9 @@ function renderPlatformActivationFiles(input) {
3555
3555
  boundedInteger("greenPort", input.greenPort, 1024, 65535);
3556
3556
  if (input.bluePort === input.greenPort)
3557
3557
  throw new Error("Activation slot ports must differ.");
3558
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(input.seedMeshAddress) || input.seedMeshAddress.startsWith(".") || input.seedMeshAddress.endsWith(".")) {
3559
+ throw new Error("Activation seed mesh address is malformed.");
3560
+ }
3558
3561
  boundedInteger("keepReleases", input.keepReleases, 2, 100);
3559
3562
  const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
3560
3563
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
@@ -3565,6 +3568,7 @@ function renderPlatformActivationFiles(input) {
3565
3568
  `FZ_USER=${input.serviceUser}`,
3566
3569
  `FZ_BLUE_PORT=${input.bluePort}`,
3567
3570
  `FZ_GREEN_PORT=${input.greenPort}`,
3571
+ `FZ_SEED_MESH_ADDRESS=${input.seedMeshAddress}`,
3568
3572
  `FZ_HEALTH_PATH=${input.healthPath}`,
3569
3573
  `FZ_KEEP_RELEASES=${input.keepReleases}`,
3570
3574
  `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
@@ -4937,11 +4941,15 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4937
4941
  concurrencyLimit: runtime.environment.concurrencyLimit,
4938
4942
  workerDrainSeconds: Math.ceil(runtime.environment.drainDeadlineMs / 1000)
4939
4943
  });
4944
+ const seedMeshAddress = runtime.environment.databaseAddress ?? config.database.address;
4945
+ if (!seedMeshAddress)
4946
+ throw new Error("platform activation requires the private seed mesh address");
4940
4947
  const activation = renderPlatformActivationFiles({
4941
4948
  root: config.deployRoot ?? "/opt/forgezero",
4942
4949
  serviceUser: runtime.serviceUser,
4943
4950
  bluePort: runtime.bluePort,
4944
4951
  greenPort: runtime.greenPort,
4952
+ seedMeshAddress,
4945
4953
  healthPath: runtime.healthPath,
4946
4954
  keepReleases: runtime.keepReleases,
4947
4955
  drainDeadlineMs: runtime.environment.drainDeadlineMs
@@ -176,6 +176,8 @@ export interface PlatformActivationConfig {
176
176
  serviceUser: string;
177
177
  bluePort: number;
178
178
  greenPort: number;
179
+ /** Private address used by the seed mesh; never exposed through nginx. */
180
+ seedMeshAddress: string;
179
181
  healthPath: string;
180
182
  keepReleases: number;
181
183
  drainDeadlineMs?: number;
@@ -1168,6 +1168,9 @@ function renderPlatformActivationFiles(input) {
1168
1168
  boundedInteger("greenPort", input.greenPort, 1024, 65535);
1169
1169
  if (input.bluePort === input.greenPort)
1170
1170
  throw new Error("Activation slot ports must differ.");
1171
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(input.seedMeshAddress) || input.seedMeshAddress.startsWith(".") || input.seedMeshAddress.endsWith(".")) {
1172
+ throw new Error("Activation seed mesh address is malformed.");
1173
+ }
1171
1174
  boundedInteger("keepReleases", input.keepReleases, 2, 100);
1172
1175
  const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
1173
1176
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
@@ -1178,6 +1181,7 @@ function renderPlatformActivationFiles(input) {
1178
1181
  `FZ_USER=${input.serviceUser}`,
1179
1182
  `FZ_BLUE_PORT=${input.bluePort}`,
1180
1183
  `FZ_GREEN_PORT=${input.greenPort}`,
1184
+ `FZ_SEED_MESH_ADDRESS=${input.seedMeshAddress}`,
1181
1185
  `FZ_HEALTH_PATH=${input.healthPath}`,
1182
1186
  `FZ_KEEP_RELEASES=${input.keepReleases}`,
1183
1187
  `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
@@ -1274,6 +1278,15 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
1274
1278
  }
1275
1279
  const target = previousSlot === "blue" ? "green" : "blue";
1276
1280
  const port = target === "blue" ? normalized.bluePort : normalized.greenPort;
1281
+ const meshProbe = async (slotPort, path2) => {
1282
+ try {
1283
+ const response = await request(`http://${normalized.seedMeshAddress}:${slotPort}/_internal/forgezero/seed-mesh/${path2}`, { signal: AbortSignal.timeout(2000) });
1284
+ return response.ok;
1285
+ } catch {
1286
+ return false;
1287
+ }
1288
+ };
1289
+ const previousHeldSeed = previousSlot ? await meshProbe(previousSlot === "blue" ? normalized.bluePort : normalized.greenPort, "resident") : false;
1277
1290
  const targetLink = join2(slots, target);
1278
1291
  let previousTarget;
1279
1292
  try {
@@ -1311,6 +1324,19 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
1311
1324
  await stopTarget();
1312
1325
  throw new Error("target slot failed its health deadline");
1313
1326
  }
1327
+ if (previousHeldSeed) {
1328
+ let seedReady = false;
1329
+ for (let attempt = 0;attempt < 30; attempt += 1) {
1330
+ seedReady = await meshProbe(port, "ready");
1331
+ if (seedReady)
1332
+ break;
1333
+ await sleep(1000);
1334
+ }
1335
+ if (!seedReady) {
1336
+ await stopTarget();
1337
+ throw new Error("target slot failed RAM-seed handover deadline");
1338
+ }
1339
+ }
1314
1340
  const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
1315
1341
  const backup = `${upstream}.forgezero-backup`;
1316
1342
  if (existsSync2(upstream))
@@ -3050,7 +3050,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3050
3050
  }
3051
3051
 
3052
3052
  // src/version.ts
3053
- var VERSION3 = "0.1.144";
3053
+ var VERSION3 = "0.1.146";
3054
3054
 
3055
3055
  // src/egress-policy.ts
3056
3056
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -4685,6 +4685,9 @@ function renderPlatformActivationFiles(input) {
4685
4685
  boundedInteger("greenPort", input.greenPort, 1024, 65535);
4686
4686
  if (input.bluePort === input.greenPort)
4687
4687
  throw new Error("Activation slot ports must differ.");
4688
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(input.seedMeshAddress) || input.seedMeshAddress.startsWith(".") || input.seedMeshAddress.endsWith(".")) {
4689
+ throw new Error("Activation seed mesh address is malformed.");
4690
+ }
4688
4691
  boundedInteger("keepReleases", input.keepReleases, 2, 100);
4689
4692
  const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
4690
4693
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
@@ -4695,6 +4698,7 @@ function renderPlatformActivationFiles(input) {
4695
4698
  `FZ_USER=${input.serviceUser}`,
4696
4699
  `FZ_BLUE_PORT=${input.bluePort}`,
4697
4700
  `FZ_GREEN_PORT=${input.greenPort}`,
4701
+ `FZ_SEED_MESH_ADDRESS=${input.seedMeshAddress}`,
4698
4702
  `FZ_HEALTH_PATH=${input.healthPath}`,
4699
4703
  `FZ_KEEP_RELEASES=${input.keepReleases}`,
4700
4704
  `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
@@ -4791,6 +4795,15 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
4791
4795
  }
4792
4796
  const target = previousSlot === "blue" ? "green" : "blue";
4793
4797
  const port = target === "blue" ? normalized.bluePort : normalized.greenPort;
4798
+ const meshProbe = async (slotPort, path2) => {
4799
+ try {
4800
+ const response = await request(`http://${normalized.seedMeshAddress}:${slotPort}/_internal/forgezero/seed-mesh/${path2}`, { signal: AbortSignal.timeout(2000) });
4801
+ return response.ok;
4802
+ } catch {
4803
+ return false;
4804
+ }
4805
+ };
4806
+ const previousHeldSeed = previousSlot ? await meshProbe(previousSlot === "blue" ? normalized.bluePort : normalized.greenPort, "resident") : false;
4794
4807
  const targetLink = join5(slots, target);
4795
4808
  let previousTarget;
4796
4809
  try {
@@ -4828,6 +4841,19 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
4828
4841
  await stopTarget();
4829
4842
  throw new Error("target slot failed its health deadline");
4830
4843
  }
4844
+ if (previousHeldSeed) {
4845
+ let seedReady = false;
4846
+ for (let attempt = 0;attempt < 30; attempt += 1) {
4847
+ seedReady = await meshProbe(port, "ready");
4848
+ if (seedReady)
4849
+ break;
4850
+ await sleep(1000);
4851
+ }
4852
+ if (!seedReady) {
4853
+ await stopTarget();
4854
+ throw new Error("target slot failed RAM-seed handover deadline");
4855
+ }
4856
+ }
4831
4857
  const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
4832
4858
  const backup = `${upstream}.forgezero-backup`;
4833
4859
  if (existsSync8(upstream))
package/dist/provision.js CHANGED
@@ -3050,7 +3050,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3050
3050
  }
3051
3051
 
3052
3052
  // src/version.ts
3053
- var VERSION3 = "0.1.144";
3053
+ var VERSION3 = "0.1.146";
3054
3054
 
3055
3055
  // src/egress-policy.ts
3056
3056
  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.144";
2
+ export declare const VERSION = "0.1.146";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.144",
3
+ "version": "0.1.146",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",