@tablation/crew 0.126.4 → 0.126.5
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/cli.js +36 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15097,6 +15097,12 @@ function sshArgsFor(o) {
|
|
|
15097
15097
|
"ServerAliveCountMax=3",
|
|
15098
15098
|
"-o",
|
|
15099
15099
|
"ExitOnForwardFailure=yes",
|
|
15100
|
+
// Offer only the ship key. Without this OpenSSH offers every ssh-agent key
|
|
15101
|
+
// before the `-i` one, the relay hashes whichever it sees first, and a
|
|
15102
|
+
// slug owned by the ship key's fingerprint is rejected as a conflict
|
|
15103
|
+
// (CREW-2932).
|
|
15104
|
+
"-o",
|
|
15105
|
+
"IdentitiesOnly=yes",
|
|
15100
15106
|
"-i",
|
|
15101
15107
|
o.privateKeyPath,
|
|
15102
15108
|
"-p",
|
|
@@ -15173,6 +15179,7 @@ function isPidAlive(pid) {
|
|
|
15173
15179
|
}
|
|
15174
15180
|
}
|
|
15175
15181
|
var CONTROL_ONLY_HASH = "control-only";
|
|
15182
|
+
var TUNNEL_REJECTED_AFTER = 2;
|
|
15176
15183
|
function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, stateDir, mcpSecrets = /* @__PURE__ */ new Map(), deps = {}) {
|
|
15177
15184
|
const alive = deps.isPidAlive ?? isPidAlive;
|
|
15178
15185
|
const plannedIds = new Set(plans.map((p) => p.workspaceId));
|
|
@@ -15203,6 +15210,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
15203
15210
|
deps.onStatus?.(plan.workspaceId, "disconnected", existing);
|
|
15204
15211
|
continue;
|
|
15205
15212
|
}
|
|
15213
|
+
const diedQuickly = existing !== void 0 && existing.status === "connecting" && !alive(existing.pid) && controlUnchanged && secretUnchanged && slugUnchanged && existing.mountsHash === mountsHash;
|
|
15214
|
+
const quickExits = diedQuickly ? (existing.quickExits ?? 0) + 1 : 0;
|
|
15206
15215
|
let settledStatus = "connecting";
|
|
15207
15216
|
const tunnel = startTunnel({
|
|
15208
15217
|
relayHost,
|
|
@@ -15230,6 +15239,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
15230
15239
|
mcpSecret: currentSecret,
|
|
15231
15240
|
slug: slug2,
|
|
15232
15241
|
liveSlug: slug2,
|
|
15242
|
+
quickExits,
|
|
15233
15243
|
endpointId: existing?.endpointId,
|
|
15234
15244
|
registeredKey: existing?.registeredKey
|
|
15235
15245
|
};
|
|
@@ -15254,6 +15264,18 @@ function readAllPersistedWorkspaceIds(stateDir) {
|
|
|
15254
15264
|
if (!existsSync35(dir)) return [];
|
|
15255
15265
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
15256
15266
|
}
|
|
15267
|
+
var TUNNEL_REJECTED_KEY = "tunnel_rejected";
|
|
15268
|
+
function tunnelRejectedMessage(workspaceId, quickExits) {
|
|
15269
|
+
return `the relay tunnel for workspace ${workspaceId} exited before settling on ${quickExits} consecutive cycles, which is how a refused claim looks (the relay logs "slug already owned by another workspace/ssh key") \u2014 rotate the slug with \`crew rotate-passenger-url\` or check the relay journal`;
|
|
15270
|
+
}
|
|
15271
|
+
function rejectedTunnels(stateDir) {
|
|
15272
|
+
const out = [];
|
|
15273
|
+
for (const workspaceId of readAllPersistedWorkspaceIds(stateDir)) {
|
|
15274
|
+
const q2 = readPersistedTunnel(stateDir, workspaceId)?.quickExits ?? 0;
|
|
15275
|
+
if (q2 >= TUNNEL_REJECTED_AFTER) out.push({ workspaceId, quickExits: q2 });
|
|
15276
|
+
}
|
|
15277
|
+
return out;
|
|
15278
|
+
}
|
|
15257
15279
|
|
|
15258
15280
|
// src/pool-member-host.ts
|
|
15259
15281
|
function loadShip(crewHome) {
|
|
@@ -25442,6 +25464,15 @@ if (command === "passenger-sync-daemon") {
|
|
|
25442
25464
|
})
|
|
25443
25465
|
);
|
|
25444
25466
|
if (status2 === "disconnected") pending.push(unregisterEndpoint(workspaceId, info, reconcile));
|
|
25467
|
+
const rejectedKey = `${TUNNEL_REJECTED_KEY}:${workspaceId}`;
|
|
25468
|
+
if ((info?.quickExits ?? 0) >= TUNNEL_REJECTED_AFTER) {
|
|
25469
|
+
pending.push(raiseShipAttention({ state, emit, route: owningRoute, ship: cfg.ship }, {
|
|
25470
|
+
kind: TUNNEL_REJECTED_KEY,
|
|
25471
|
+
key: rejectedKey,
|
|
25472
|
+
message: tunnelRejectedMessage(workspaceId, info.quickExits),
|
|
25473
|
+
since: (/* @__PURE__ */ new Date()).toISOString()
|
|
25474
|
+
}).then(() => void 0).catch(() => void 0));
|
|
25475
|
+
} else if (status2 === "connected") clearShipAttention(state, rejectedKey);
|
|
25445
25476
|
}
|
|
25446
25477
|
});
|
|
25447
25478
|
for (const plan of containerPlans) pending.push(reconcileEndpoint(plan.workspaceId, reconcile));
|
|
@@ -30122,6 +30153,9 @@ watermark: ${state.watermark()}
|
|
|
30122
30153
|
if (!flag("offline")) {
|
|
30123
30154
|
for (const message of managedRuntimeWarnings(await doctorShipKind(route2, cfg.ship), cfg.ship)) problems.push({ severity: "attention", kind: "managed_runtime_default", message });
|
|
30124
30155
|
}
|
|
30156
|
+
for (const t of rejectedTunnels(cfg.ship.stateDir)) {
|
|
30157
|
+
problems.push({ severity: "attention", kind: TUNNEL_REJECTED_KEY, message: tunnelRejectedMessage(t.workspaceId, t.quickExits), remedy: "rotate the slug with `crew rotate-passenger-url`" });
|
|
30158
|
+
}
|
|
30125
30159
|
const previewProblem2 = await previewHostProblem(cfg);
|
|
30126
30160
|
if (previewProblem2) problems.push(previewHostFinding(previewProblem2));
|
|
30127
30161
|
if (cfg.routes.some((x) => x.route === route2.route && x.enabled && x.hostPeriscopes)) {
|
|
@@ -30142,6 +30176,8 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
30142
30176
|
`
|
|
30143
30177
|
);
|
|
30144
30178
|
for (const w of roleCapWarnings(cfg.ship)) process.stdout.write(`role concurrency: WARN ${w}
|
|
30179
|
+
`);
|
|
30180
|
+
for (const t of rejectedTunnels(cfg.ship.stateDir)) process.stdout.write(`relay tunnel: ATTENTION ${tunnelRejectedMessage(t.workspaceId, t.quickExits)}
|
|
30145
30181
|
`);
|
|
30146
30182
|
process.stdout.write(`periscopes: ${periscopeSummary(cfg)}
|
|
30147
30183
|
`);
|
package/package.json
CHANGED