@forgezero/agent 0.1.20 → 0.1.21
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/README.md +6 -0
- package/dist/fz-agent.js +50 -20
- package/dist/fz.js +1 -1
- package/dist/shutdown.d.ts +10 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,6 +144,12 @@ read Vault data, accept arbitrary commands, or retain tenant credentials. Root
|
|
|
144
144
|
SSH is an operator-only platform-genesis/recovery path, not the normal tenant
|
|
145
145
|
dispatch mechanism.
|
|
146
146
|
|
|
147
|
+
Shutdown has one process-wide deadline. New deployment claims and background
|
|
148
|
+
sync stop first, claimed work drains under its lease fence, and only then do the
|
|
149
|
+
queues and application socket close. Vault/attestation calls or a stale local
|
|
150
|
+
socket cannot consume systemd's longer stop timeout: exceeding the Agent deadline
|
|
151
|
+
is reported and exits non-zero instead of being silently killed midway by PID 1.
|
|
152
|
+
|
|
147
153
|
Repository read authorization is explicit per pipeline: public HTTPS, the
|
|
148
154
|
compute's systemd-sealed SSH deploy key, or a fine-grained HTTPS token selected
|
|
149
155
|
by a project-vault secret name. A signed claim contains the mode and secret name
|
package/dist/fz-agent.js
CHANGED
|
@@ -2714,8 +2714,31 @@ async function applyMetalIsolation(profile, exec = defaultExec) {
|
|
|
2714
2714
|
}
|
|
2715
2715
|
}
|
|
2716
2716
|
|
|
2717
|
+
// src/shutdown.ts
|
|
2718
|
+
async function settleWithin(work, timeoutMs, setTimer = setTimeout, clearTimer = clearTimeout) {
|
|
2719
|
+
let timer;
|
|
2720
|
+
try {
|
|
2721
|
+
return await Promise.race([
|
|
2722
|
+
work.then(() => true),
|
|
2723
|
+
new Promise((resolve2) => {
|
|
2724
|
+
timer = setTimer(() => resolve2(false), Math.max(1, timeoutMs));
|
|
2725
|
+
timer.unref?.();
|
|
2726
|
+
})
|
|
2727
|
+
]);
|
|
2728
|
+
} finally {
|
|
2729
|
+
if (timer)
|
|
2730
|
+
clearTimer(timer);
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
async function closeServerWithin(server, timeoutMs) {
|
|
2734
|
+
const closed = await settleWithin(new Promise((resolve2) => server.close(() => resolve2())), timeoutMs);
|
|
2735
|
+
if (!closed)
|
|
2736
|
+
server.unref();
|
|
2737
|
+
return closed;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2717
2740
|
// src/version.ts
|
|
2718
|
-
var VERSION = "0.1.
|
|
2741
|
+
var VERSION = "0.1.21";
|
|
2719
2742
|
|
|
2720
2743
|
// src/index.ts
|
|
2721
2744
|
function loadOrCreateSeed(path) {
|
|
@@ -3150,24 +3173,26 @@ if (import.meta.main) {
|
|
|
3150
3173
|
return;
|
|
3151
3174
|
stopping = true;
|
|
3152
3175
|
console.log(`[agent] ${signal}: stopping deployment intake and draining`);
|
|
3153
|
-
if (control)
|
|
3154
|
-
await new Promise((resolve2) => control.close(() => resolve2()));
|
|
3155
3176
|
const deadlineMs = Math.max(1, Number(process.env.FZ_DRAIN_DEADLINE_MS ?? 30000));
|
|
3156
3177
|
const deadline = Date.now() + deadlineMs;
|
|
3178
|
+
const remaining = () => Math.max(1, deadline - Date.now());
|
|
3179
|
+
const controlClosed = control ? await settleWithin(new Promise((resolve2) => control.close(() => resolve2())), remaining()) : true;
|
|
3157
3180
|
const pullDrain = pull?.stop() ?? Promise.resolve();
|
|
3158
3181
|
const vaultDrain = vaultSync?.stop() ?? Promise.resolve();
|
|
3159
3182
|
const attestationDrain = attestationLoop?.stop() ?? Promise.resolve();
|
|
3160
|
-
const
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
const pullDrained = await pullWithinDeadline;
|
|
3165
|
-
await Promise.all([vaultDrain, attestationDrain]);
|
|
3166
|
-
const managerReports = await Promise.all([...new Set(managers.values())].map((manager) => manager.stop(Math.max(1, deadline - Date.now()))));
|
|
3167
|
-
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
3183
|
+
const pullDrained = pull ? await settleWithin(pullDrain, remaining()) : true;
|
|
3184
|
+
const backgroundDrained = await settleWithin(Promise.all([vaultDrain, attestationDrain]), remaining());
|
|
3185
|
+
const managerReports = await Promise.all([...new Set(managers.values())].map((manager) => manager.stop(remaining())));
|
|
3186
|
+
const socketClosed = await closeServerWithin(server, remaining());
|
|
3168
3187
|
const timedOut = managerReports.some((report) => report.timedOut);
|
|
3169
|
-
console.log(`[agent] drain ${JSON.stringify({
|
|
3170
|
-
|
|
3188
|
+
console.log(`[agent] drain ${JSON.stringify({
|
|
3189
|
+
managers: managerReports,
|
|
3190
|
+
controlClosed,
|
|
3191
|
+
pullDrained,
|
|
3192
|
+
backgroundDrained,
|
|
3193
|
+
socketClosed
|
|
3194
|
+
})}`);
|
|
3195
|
+
process.exit(timedOut || !controlClosed || !pullDrained || !backgroundDrained || !socketClosed ? 1 : 0);
|
|
3171
3196
|
};
|
|
3172
3197
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
3173
3198
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
@@ -3175,19 +3200,24 @@ if (import.meta.main) {
|
|
|
3175
3200
|
console.log("[agent] signing/vault mode only; deployment root and pull are not configured");
|
|
3176
3201
|
if (vaultSync || attestationLoop) {
|
|
3177
3202
|
let stopping = false;
|
|
3178
|
-
const stop = async () => {
|
|
3203
|
+
const stop = async (signal) => {
|
|
3179
3204
|
if (stopping)
|
|
3180
3205
|
return;
|
|
3181
3206
|
stopping = true;
|
|
3182
|
-
|
|
3207
|
+
const deadlineMs = Math.max(1, Number(process.env.FZ_DRAIN_DEADLINE_MS ?? 30000));
|
|
3208
|
+
const deadline = Date.now() + deadlineMs;
|
|
3209
|
+
const remaining = () => Math.max(1, deadline - Date.now());
|
|
3210
|
+
console.log(`[agent] ${signal}: stopping background intake and draining`);
|
|
3211
|
+
const backgroundDrained = await settleWithin(Promise.all([
|
|
3183
3212
|
vaultSync?.stop() ?? Promise.resolve(),
|
|
3184
3213
|
attestationLoop?.stop() ?? Promise.resolve()
|
|
3185
|
-
]);
|
|
3186
|
-
|
|
3187
|
-
|
|
3214
|
+
]), remaining());
|
|
3215
|
+
const socketClosed = await closeServerWithin(server, remaining());
|
|
3216
|
+
console.log(`[agent] drain ${JSON.stringify({ backgroundDrained, socketClosed })}`);
|
|
3217
|
+
process.exit(backgroundDrained && socketClosed ? 0 : 1);
|
|
3188
3218
|
};
|
|
3189
|
-
process.on("SIGTERM", () => void stop());
|
|
3190
|
-
process.on("SIGINT", () => void stop());
|
|
3219
|
+
process.on("SIGTERM", () => void stop("SIGTERM"));
|
|
3220
|
+
process.on("SIGINT", () => void stop("SIGINT"));
|
|
3191
3221
|
}
|
|
3192
3222
|
}
|
|
3193
3223
|
}
|
package/dist/fz.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Server } from 'node:net';
|
|
2
|
+
/**
|
|
3
|
+
* Wait for one shutdown stage without allowing it to consume systemd's entire
|
|
4
|
+
* stop timeout. The work is not cancelled: deployment/lease code retains its
|
|
5
|
+
* own fencing rules, while the caller decides whether an expired global drain
|
|
6
|
+
* deadline must terminate the process non-zero.
|
|
7
|
+
*/
|
|
8
|
+
export declare function settleWithin(work: Promise<unknown>, timeoutMs: number, setTimer?: typeof setTimeout, clearTimer?: typeof clearTimeout): Promise<boolean>;
|
|
9
|
+
/** Stop accepting local Vault clients, but never let one stale socket defeat shutdown. */
|
|
10
|
+
export declare function closeServerWithin(server: Server, timeoutMs: number): Promise<boolean>;
|
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.21";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "Publishing happens from an operator's machine, not CI \u2014 CLAUDE.md records that the absence of CI is deliberate. npm's `provenance` attests a tarball was built by a recognised CI provider from a named commit, so it cannot be produced here: it was set, and the first publish failed with `Automatic provenance generation not supported for provider: null`. A setting that can never be satisfied is worse than none, because it reads as a guarantee nobody is getting. Restore it the day this publishes from CI, and not before.",
|
|
3
3
|
"name": "@forgezero/agent",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.21",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"check": "tsc --noEmit",
|