@usine/executor 0.2.1 → 0.4.0
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/LICENSE +661 -21
- package/README.md +212 -37
- package/dist/child-command.d.ts +5 -8
- package/dist/child-command.d.ts.map +1 -1
- package/dist/child-command.js +10 -14
- package/dist/child-command.js.map +1 -1
- package/dist/child-environment.d.ts +19 -0
- package/dist/child-environment.d.ts.map +1 -0
- package/dist/child-environment.js +18 -0
- package/dist/child-environment.js.map +1 -0
- package/dist/claude-login-lock.d.ts +9 -0
- package/dist/claude-login-lock.d.ts.map +1 -0
- package/dist/claude-login-lock.js +15 -0
- package/dist/claude-login-lock.js.map +1 -0
- package/dist/cloudflare/client.d.ts +50 -0
- package/dist/cloudflare/client.d.ts.map +1 -0
- package/dist/cloudflare/client.js +108 -0
- package/dist/cloudflare/client.js.map +1 -0
- package/dist/cloudflare/cloudflare.d.ts +17 -0
- package/dist/cloudflare/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare/cloudflare.js +704 -0
- package/dist/cloudflare/cloudflare.js.map +1 -0
- package/dist/cloudflare/model.d.ts +42 -0
- package/dist/cloudflare/model.d.ts.map +1 -0
- package/dist/cloudflare/model.js +2 -0
- package/dist/cloudflare/model.js.map +1 -0
- package/dist/cloudflare/shell/environment.d.ts +12 -0
- package/dist/cloudflare/shell/environment.d.ts.map +1 -0
- package/dist/cloudflare/shell/environment.js +12 -0
- package/dist/cloudflare/shell/environment.js.map +1 -0
- package/dist/cloudflare/shell/shell.d.ts +2 -0
- package/dist/cloudflare/shell/shell.d.ts.map +1 -0
- package/dist/cloudflare/shell/shell.js +155 -0
- package/dist/cloudflare/shell/shell.js.map +1 -0
- package/dist/credential-environment.d.ts +20 -0
- package/dist/credential-environment.d.ts.map +1 -0
- package/dist/credential-environment.js +20 -0
- package/dist/credential-environment.js.map +1 -0
- package/dist/durable-session.d.ts +81 -0
- package/dist/durable-session.d.ts.map +1 -0
- package/dist/durable-session.js +550 -0
- package/dist/durable-session.js.map +1 -0
- package/dist/exe-dev/commands.d.ts +23 -13
- package/dist/exe-dev/commands.d.ts.map +1 -1
- package/dist/exe-dev/commands.js +11 -57
- package/dist/exe-dev/commands.js.map +1 -1
- package/dist/exe-dev/exe-dev.d.ts +9 -3
- package/dist/exe-dev/exe-dev.d.ts.map +1 -1
- package/dist/exe-dev/exe-dev.js +61 -128
- package/dist/exe-dev/exe-dev.js.map +1 -1
- package/dist/exe-dev/grants.d.ts.map +1 -1
- package/dist/exe-dev/grants.js +35 -15
- package/dist/exe-dev/grants.js.map +1 -1
- package/dist/exe-dev/model.d.ts +15 -19
- package/dist/exe-dev/model.d.ts.map +1 -1
- package/dist/exe-dev/model.js +9 -12
- package/dist/exe-dev/model.js.map +1 -1
- package/dist/exe-dev/preflight.d.ts +8 -4
- package/dist/exe-dev/preflight.d.ts.map +1 -1
- package/dist/exe-dev/preflight.js +26 -35
- package/dist/exe-dev/preflight.js.map +1 -1
- package/dist/executor.d.ts +292 -44
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +187 -19
- package/dist/executor.js.map +1 -1
- package/dist/guard.d.ts +57 -7
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +274 -59
- package/dist/guard.js.map +1 -1
- package/dist/harness-preflight.d.ts +27 -5
- package/dist/harness-preflight.d.ts.map +1 -1
- package/dist/harness-preflight.js +32 -31
- package/dist/harness-preflight.js.map +1 -1
- package/dist/keyed-lock.d.ts +2 -3
- package/dist/keyed-lock.d.ts.map +1 -1
- package/dist/keyed-lock.js +2 -3
- package/dist/keyed-lock.js.map +1 -1
- package/dist/leftovers.d.ts +9 -0
- package/dist/leftovers.d.ts.map +1 -0
- package/dist/leftovers.js +14 -0
- package/dist/leftovers.js.map +1 -0
- package/dist/local/credentials.d.ts +21 -0
- package/dist/local/credentials.d.ts.map +1 -0
- package/dist/local/credentials.js +39 -0
- package/dist/local/credentials.js.map +1 -0
- package/dist/local/leftovers.d.ts +8 -0
- package/dist/local/leftovers.d.ts.map +1 -0
- package/dist/local/leftovers.js +23 -0
- package/dist/local/leftovers.js.map +1 -0
- package/dist/local/local.d.ts +17 -11
- package/dist/local/local.d.ts.map +1 -1
- package/dist/local/local.js +205 -132
- package/dist/local/local.js.map +1 -1
- package/dist/memory-preflight.d.ts +27 -0
- package/dist/memory-preflight.d.ts.map +1 -0
- package/dist/memory-preflight.js +83 -0
- package/dist/memory-preflight.js.map +1 -0
- package/dist/pre-push-hook.d.ts +20 -0
- package/dist/pre-push-hook.d.ts.map +1 -0
- package/dist/pre-push-hook.js +33 -0
- package/dist/pre-push-hook.js.map +1 -0
- package/dist/prepared-attempt.d.ts +41 -3
- package/dist/prepared-attempt.d.ts.map +1 -1
- package/dist/prepared-attempt.js +109 -31
- package/dist/prepared-attempt.js.map +1 -1
- package/dist/provision.d.ts +129 -15
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +428 -69
- package/dist/provision.js.map +1 -1
- package/dist/remote/commands.d.ts +51 -0
- package/dist/remote/commands.d.ts.map +1 -0
- package/dist/remote/commands.js +94 -0
- package/dist/remote/commands.js.map +1 -0
- package/dist/remote/container.d.ts +90 -0
- package/dist/remote/container.d.ts.map +1 -0
- package/dist/remote/container.js +480 -0
- package/dist/remote/container.js.map +1 -0
- package/dist/remote/leftovers.d.ts +14 -0
- package/dist/remote/leftovers.d.ts.map +1 -0
- package/dist/remote/leftovers.js +22 -0
- package/dist/remote/leftovers.js.map +1 -0
- package/dist/remote/liveness.d.ts +33 -0
- package/dist/remote/liveness.d.ts.map +1 -0
- package/dist/remote/liveness.js +196 -0
- package/dist/remote/liveness.js.map +1 -0
- package/dist/remote/machine.d.ts +110 -0
- package/dist/remote/machine.d.ts.map +1 -0
- package/dist/remote/machine.js +2 -0
- package/dist/remote/machine.js.map +1 -0
- package/dist/remote/remote.d.ts +28 -0
- package/dist/remote/remote.d.ts.map +1 -0
- package/dist/remote/remote.js +556 -0
- package/dist/remote/remote.js.map +1 -0
- package/dist/remote/sweep.d.ts +17 -0
- package/dist/remote/sweep.d.ts.map +1 -0
- package/dist/remote/sweep.js +117 -0
- package/dist/remote/sweep.js.map +1 -0
- package/dist/renewable-credential.d.ts +17 -0
- package/dist/renewable-credential.d.ts.map +1 -0
- package/dist/renewable-credential.js +58 -0
- package/dist/renewable-credential.js.map +1 -0
- package/dist/salvage.d.ts +38 -0
- package/dist/salvage.d.ts.map +1 -0
- package/dist/salvage.js +198 -0
- package/dist/salvage.js.map +1 -0
- package/dist/ssh/api-keys.d.ts +36 -0
- package/dist/ssh/api-keys.d.ts.map +1 -0
- package/dist/ssh/api-keys.js +72 -0
- package/dist/ssh/api-keys.js.map +1 -0
- package/dist/ssh/codex-homes.d.ts +21 -0
- package/dist/ssh/codex-homes.d.ts.map +1 -0
- package/dist/ssh/codex-homes.js +78 -0
- package/dist/ssh/codex-homes.js.map +1 -0
- package/dist/ssh/container.d.ts +32 -0
- package/dist/ssh/container.d.ts.map +1 -0
- package/dist/ssh/container.js +282 -0
- package/dist/ssh/container.js.map +1 -0
- package/dist/ssh/credentials.d.ts +43 -0
- package/dist/ssh/credentials.d.ts.map +1 -0
- package/dist/ssh/credentials.js +86 -0
- package/dist/ssh/credentials.js.map +1 -0
- package/dist/ssh/gateway/admin.d.ts +45 -0
- package/dist/ssh/gateway/admin.d.ts.map +1 -0
- package/dist/ssh/gateway/admin.js +129 -0
- package/dist/ssh/gateway/admin.js.map +1 -0
- package/dist/ssh/gateway/attachments.d.ts +31 -0
- package/dist/ssh/gateway/attachments.d.ts.map +1 -0
- package/dist/ssh/gateway/attachments.js +168 -0
- package/dist/ssh/gateway/attachments.js.map +1 -0
- package/dist/ssh/gateway/environment.d.ts +71 -0
- package/dist/ssh/gateway/environment.d.ts.map +1 -0
- package/dist/ssh/gateway/environment.js +148 -0
- package/dist/ssh/gateway/environment.js.map +1 -0
- package/dist/ssh/gateway/forward.d.ts +74 -0
- package/dist/ssh/gateway/forward.d.ts.map +1 -0
- package/dist/ssh/gateway/forward.js +247 -0
- package/dist/ssh/gateway/forward.js.map +1 -0
- package/dist/ssh/gateway/preflight.d.ts +67 -0
- package/dist/ssh/gateway/preflight.d.ts.map +1 -0
- package/dist/ssh/gateway/preflight.js +320 -0
- package/dist/ssh/gateway/preflight.js.map +1 -0
- package/dist/ssh/limits.d.ts +60 -0
- package/dist/ssh/limits.d.ts.map +1 -0
- package/dist/ssh/limits.js +84 -0
- package/dist/ssh/limits.js.map +1 -0
- package/dist/ssh/model.d.ts +153 -0
- package/dist/ssh/model.d.ts.map +1 -0
- package/dist/ssh/model.js +2 -0
- package/dist/ssh/model.js.map +1 -0
- package/dist/ssh/preflight.d.ts +35 -0
- package/dist/ssh/preflight.d.ts.map +1 -0
- package/dist/ssh/preflight.js +87 -0
- package/dist/ssh/preflight.js.map +1 -0
- package/dist/ssh/provisioner/boat.d.ts +33 -0
- package/dist/ssh/provisioner/boat.d.ts.map +1 -0
- package/dist/ssh/provisioner/boat.js +247 -0
- package/dist/ssh/provisioner/boat.js.map +1 -0
- package/dist/ssh/provisioner/hetzner.d.ts +32 -0
- package/dist/ssh/provisioner/hetzner.d.ts.map +1 -0
- package/dist/ssh/provisioner/hetzner.js +337 -0
- package/dist/ssh/provisioner/hetzner.js.map +1 -0
- package/dist/ssh/provisioner/host-key.d.ts +11 -0
- package/dist/ssh/provisioner/host-key.d.ts.map +1 -0
- package/dist/ssh/provisioner/host-key.js +57 -0
- package/dist/ssh/provisioner/host-key.js.map +1 -0
- package/dist/ssh/provisioner/http.d.ts +45 -0
- package/dist/ssh/provisioner/http.d.ts.map +1 -0
- package/dist/ssh/provisioner/http.js +72 -0
- package/dist/ssh/provisioner/http.js.map +1 -0
- package/dist/ssh/provisioner/machines.d.ts +48 -0
- package/dist/ssh/provisioner/machines.d.ts.map +1 -0
- package/dist/ssh/provisioner/machines.js +686 -0
- package/dist/ssh/provisioner/machines.js.map +1 -0
- package/dist/ssh/provisioner/model.d.ts +92 -0
- package/dist/ssh/provisioner/model.d.ts.map +1 -0
- package/dist/ssh/provisioner/model.js +37 -0
- package/dist/ssh/provisioner/model.js.map +1 -0
- package/dist/ssh/ssh.d.ts +23 -0
- package/dist/ssh/ssh.d.ts.map +1 -0
- package/dist/ssh/ssh.js +145 -0
- package/dist/ssh/ssh.js.map +1 -0
- package/dist/ssh/worker-records.d.ts +18 -0
- package/dist/ssh/worker-records.d.ts.map +1 -0
- package/dist/ssh/worker-records.js +100 -0
- package/dist/ssh/worker-records.js.map +1 -0
- package/dist/ssh/workers.d.ts +80 -0
- package/dist/ssh/workers.d.ts.map +1 -0
- package/dist/ssh/workers.js +247 -0
- package/dist/ssh/workers.js.map +1 -0
- package/package.json +25 -9
- package/dist/exe-dev/channel.d.ts +0 -12
- package/dist/exe-dev/channel.d.ts.map +0 -1
- package/dist/exe-dev/channel.js +0 -42
- package/dist/exe-dev/channel.js.map +0 -1
- package/dist/exe-dev/shell.d.ts +0 -12
- package/dist/exe-dev/shell.d.ts.map +0 -1
- package/dist/exe-dev/shell.js +0 -31
- package/dist/exe-dev/shell.js.map +0 -1
- package/dist/exe-dev/sweep.d.ts +0 -10
- package/dist/exe-dev/sweep.d.ts.map +0 -1
- package/dist/exe-dev/sweep.js +0 -117
- package/dist/exe-dev/sweep.js.map +0 -1
- package/dist/invalid-frame.d.ts +0 -17
- package/dist/invalid-frame.d.ts.map +0 -1
- package/dist/invalid-frame.js +0 -111
- package/dist/invalid-frame.js.map +0 -1
- package/dist/run-host-session.d.ts +0 -27
- package/dist/run-host-session.d.ts.map +0 -1
- package/dist/run-host-session.js +0 -303
- package/dist/run-host-session.js.map +0 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { safeSegment } from "@usine/core/repository";
|
|
2
|
+
import { Effect, Ref } from "effect";
|
|
3
|
+
import { directChildren } from "../remote/sweep.js";
|
|
4
|
+
/**
|
|
5
|
+
* A script that prints each directory in the Codex homes directory `$1` whose Attempt has no
|
|
6
|
+
* checkout in the attempts directory `$2`, NUL-terminated. Prints nothing if `$1` is missing.
|
|
7
|
+
*/
|
|
8
|
+
const HOMES_WITHOUT_CHECKOUT_SCRIPT = String.raw `
|
|
9
|
+
set -e
|
|
10
|
+
[ -d "$1" ] || exit 0
|
|
11
|
+
for home in "$1"/* "$1"/.[!.]* "$1"/..?*; do
|
|
12
|
+
[ -e "$home" ] || continue
|
|
13
|
+
[ -e "$2/$(basename "$home")" ] || printf '%s\0' "$home"
|
|
14
|
+
done
|
|
15
|
+
`.trim();
|
|
16
|
+
/** Returns the Codex home of `attemptId` in the Worker directory `workerDirectory`. */
|
|
17
|
+
export const codexHomeIn = (workerDirectory, attemptId) => `${workerDirectory}/codex/${safeSegment(attemptId)}`;
|
|
18
|
+
/** Returns the Attempt's Codex home on `machine`, which Codex also writes its sessions to. */
|
|
19
|
+
export const attemptCodexHome = (machine, attemptId) => codexHomeIn(machine.workerDirectory, attemptId);
|
|
20
|
+
/**
|
|
21
|
+
* Tracks the Codex homes that Attempts in this process use. `hold` gives an Attempt its Codex
|
|
22
|
+
* home and removes the directory when the caller's scope closes. `sweep` removes the Codex homes
|
|
23
|
+
* on a long-lived machine that no Attempt holds and whose Attempt has no checkout there, which
|
|
24
|
+
* a crash or a failed removal left.
|
|
25
|
+
*/
|
|
26
|
+
export const codexHomesFor = Effect.fn("SshExecutor.codexHomesFor")(function* (commands) {
|
|
27
|
+
// The number of holds on each Codex home, by path.
|
|
28
|
+
const held = yield* Ref.make(new Map());
|
|
29
|
+
const count = (codexHome, change) => Ref.update(held, (current) => {
|
|
30
|
+
const next = new Map(current);
|
|
31
|
+
const holds = (current.get(codexHome) ?? 0) + change;
|
|
32
|
+
if (holds > 0)
|
|
33
|
+
next.set(codexHome, holds);
|
|
34
|
+
else
|
|
35
|
+
next.delete(codexHome);
|
|
36
|
+
return next;
|
|
37
|
+
});
|
|
38
|
+
const hold = Effect.fn("SshExecutor.holdCodexHome")(function* (machine, attemptId) {
|
|
39
|
+
const codexHome = attemptCodexHome(machine, attemptId);
|
|
40
|
+
const fields = { attemptId, worker: machine.address.name, codexHome };
|
|
41
|
+
yield* Effect.annotateCurrentSpan(fields);
|
|
42
|
+
// Held before the caller writes the directory, so a sweep that lists it finds the hold.
|
|
43
|
+
yield* Effect.acquireRelease(count(codexHome, 1), () => commands.run("cleanup", machine.shell, "rm", ["-rf", codexHome]).pipe(Effect.tap(() => Effect.logDebug("Removed the Attempt's Codex home", fields)), Effect.tapError((error) => Effect.logWarning("Could not remove the Attempt's Codex home; the next sweep removes it once the Attempt's checkout is gone", { ...fields, message: error.message })), Effect.ignore, Effect.ensuring(count(codexHome, -1))));
|
|
44
|
+
return codexHome;
|
|
45
|
+
});
|
|
46
|
+
const sweep = Effect.fn("SshExecutor.sweepCodexHomes")(function* (machine) {
|
|
47
|
+
const worker = machine.address.name;
|
|
48
|
+
const host = machine.shell.destination;
|
|
49
|
+
yield* Effect.annotateCurrentSpan({ worker, host });
|
|
50
|
+
const root = `${machine.workerDirectory}/codex`;
|
|
51
|
+
const output = yield* commands.run("cleanup", machine.shell, "sh", [
|
|
52
|
+
"-c",
|
|
53
|
+
HOMES_WITHOUT_CHECKOUT_SCRIPT,
|
|
54
|
+
"sh",
|
|
55
|
+
root,
|
|
56
|
+
`${machine.workerDirectory}/attempts`,
|
|
57
|
+
]);
|
|
58
|
+
const listed = yield* directChildren("cleanup", root, output);
|
|
59
|
+
// Read after the listing, so a Codex home created since then can't be removed.
|
|
60
|
+
const holds = yield* Ref.get(held);
|
|
61
|
+
const leftovers = listed.filter((codexHome) => !holds.has(codexHome));
|
|
62
|
+
if (leftovers.length === 0)
|
|
63
|
+
return yield* Effect.logDebug("No Codex home to remove on the SSH Worker", {
|
|
64
|
+
worker,
|
|
65
|
+
host,
|
|
66
|
+
withoutCheckout: listed.length,
|
|
67
|
+
});
|
|
68
|
+
// Removes each Codex home separately, so one failure doesn't keep the others.
|
|
69
|
+
yield* Effect.forEach(leftovers, (codexHome) => commands.run("cleanup", machine.shell, "rm", ["-rf", codexHome]).pipe(Effect.tap(() => Effect.logInfo("Removed a Codex home that no Attempt holds and whose Attempt has no checkout", { worker, host, codexHome })), Effect.tapError((error) => Effect.logWarning("Could not remove a Codex home; the next sweep tries again", {
|
|
70
|
+
worker,
|
|
71
|
+
host,
|
|
72
|
+
codexHome,
|
|
73
|
+
message: error.message,
|
|
74
|
+
})), Effect.ignore), { discard: true });
|
|
75
|
+
});
|
|
76
|
+
return { hold, sweep };
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=codex-homes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-homes.js","sourceRoot":"","sources":["../../src/ssh/codex-homes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type WorkerName } from "@usine/core/worker";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { type ExecutorError, WorkerCapabilityError } from "../executor.ts";
|
|
4
|
+
import type { RemoteCommands } from "../remote/commands.ts";
|
|
5
|
+
import { type AttemptIsolation, type ContainerSettings } from "../remote/container.ts";
|
|
6
|
+
import { type ResolvedLimits } from "./limits.ts";
|
|
7
|
+
import type { SshMachine } from "./model.ts";
|
|
8
|
+
/**
|
|
9
|
+
* The image of each Attempt container when `usine.yaml` names none, built from
|
|
10
|
+
* `infra/worker-image/Dockerfile` with this release's harness versions.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ATTEMPT_IMAGE: string & import("effect/Brand").Brand<"ContainerImage">;
|
|
13
|
+
/**
|
|
14
|
+
* Returns the container operations of the SSH executor: the preflight checks of a Worker's
|
|
15
|
+
* Docker and image, the isolation they found, a container for a preflight's harness probes, and
|
|
16
|
+
* the removal of containers an earlier process left.
|
|
17
|
+
*/
|
|
18
|
+
export declare const sshContainersFor: (commands: RemoteCommands, machineFor: (worker: WorkerName) => Effect.Effect<SshMachine, ExecutorError>, instance: string, instanceId: string & import("effect/Brand").Brand<"InstanceId">) => Effect.Effect<{
|
|
19
|
+
verify: (machine: SshMachine, slots: number) => Effect.Effect<{
|
|
20
|
+
_tag: "Container";
|
|
21
|
+
settings: ContainerSettings;
|
|
22
|
+
limits: ResolvedLimits;
|
|
23
|
+
} | {
|
|
24
|
+
_tag: "SharedUser";
|
|
25
|
+
degraded: string;
|
|
26
|
+
}, import("../executor.ts").ExecutorOperationError | WorkerCapabilityError, never>;
|
|
27
|
+
isolation: (machine: SshMachine) => Effect.Effect<AttemptIsolation, import("../executor.ts").ExecutorOperationError | WorkerCapabilityError, never>;
|
|
28
|
+
preflightShell: (machine: SshMachine, settings: ContainerSettings, harnesses: readonly ("claude" | "codex" | "pi")[], environment: Readonly<Record<string, string>>) => Effect.Effect<import("@usine/agents/child-host").RemoteShell, import("../executor.ts").ExecutorOperationError, import("effect/Scope").Scope>;
|
|
29
|
+
reclaim: (worker: WorkerName) => Effect.Effect<void, never, never>;
|
|
30
|
+
}, never, never>;
|
|
31
|
+
export type SshContainers = Effect.Success<ReturnType<typeof sshContainersFor>>;
|
|
32
|
+
//# sourceMappingURL=container.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../src/ssh/container.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAS,MAAM,EAA+B,MAAM,QAAQ,CAAC;AACpE,OAAO,EAAE,KAAK,aAAa,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,iBAAiB,EAMvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAKL,KAAK,cAAc,EAEpB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,aAAa,yDAEzB,CAAC;AA8CF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,kDAEN,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC;;;;;;;;;;;sBAkUxD,UAAU;gBAE9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { SSH_FAILURE } from "@usine/agents/child-host";
|
|
2
|
+
import { safeSegment } from "@usine/core/repository";
|
|
3
|
+
import { ContainerImage } from "@usine/core/worker";
|
|
4
|
+
import { Cache, Effect, Option, Random, Ref, Schema } from "effect";
|
|
5
|
+
import { WorkerCapabilityError } from "../executor.js";
|
|
6
|
+
import { keyedLockFor } from "../keyed-lock.js";
|
|
7
|
+
import { CONTAINER_RUNTIME, containersFor, PNPM_STORE_MOUNT, PNPM_STORE_PATH, PNPM_STORE_VOLUME, sharedUser, } from "../remote/container.js";
|
|
8
|
+
import { configuredLimits, limitFields, RESOURCES_SCRIPT, resolveLimits, resourcesFromOutput, } from "./limits.js";
|
|
9
|
+
/**
|
|
10
|
+
* The image of each Attempt container when `usine.yaml` names none, built from
|
|
11
|
+
* `infra/worker-image/Dockerfile` with this release's harness versions.
|
|
12
|
+
*/
|
|
13
|
+
export const ATTEMPT_IMAGE = ContainerImage.make("ghcr.io/basaltbytes/usine-worker:claude-2.1.308-codex-0.159.2");
|
|
14
|
+
/** The exit code of `DETECT_SCRIPT` when the machine has no Docker and the SSH user no access to one. */
|
|
15
|
+
const RUNTIME_ABSENT = 3;
|
|
16
|
+
/** The exit code of `DETECT_SCRIPT` when the SSH user can use Docker's socket but has no `docker` CLI. */
|
|
17
|
+
const SOCKET_WITHOUT_CLI = 4;
|
|
18
|
+
/**
|
|
19
|
+
* A script that prints the version of the container runtime `$1`'s server. Without the CLI, it
|
|
20
|
+
* exits 4 if the SSH user is in the `docker` group or can write Docker's socket, and 3 if not.
|
|
21
|
+
*/
|
|
22
|
+
const DETECT_SCRIPT = String.raw `
|
|
23
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
24
|
+
for group in $(id -nG); do
|
|
25
|
+
if [ "$group" = docker ]; then exit 4; fi
|
|
26
|
+
done
|
|
27
|
+
if [ -w /var/run/docker.sock ]; then exit 4; fi
|
|
28
|
+
exit 3
|
|
29
|
+
fi
|
|
30
|
+
exec "$1" version -f '{{.Server.Version}}'
|
|
31
|
+
`.trim();
|
|
32
|
+
/** A script that prints the SSH user's ID and the ID of image `$2`'s user, separated by a space. */
|
|
33
|
+
const USER_IDS_SCRIPT = String.raw `
|
|
34
|
+
image_uid=$("$1" run --rm --network none --entrypoint id "$2" -u) || exit 1
|
|
35
|
+
printf '%s %s\n' "$(id -u)" "$image_uid"
|
|
36
|
+
`.trim();
|
|
37
|
+
const decodeStoreSize = Schema.decodeUnknownEffect(Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)));
|
|
38
|
+
const containerGap = (worker, message, cause) => new WorkerCapabilityError({ worker, capability: "container", message, cause });
|
|
39
|
+
/**
|
|
40
|
+
* Returns the container operations of the SSH executor: the preflight checks of a Worker's
|
|
41
|
+
* Docker and image, the isolation they found, a container for a preflight's harness probes, and
|
|
42
|
+
* the removal of containers an earlier process left.
|
|
43
|
+
*/
|
|
44
|
+
export const sshContainersFor = Effect.fn("SshExecutor.containersFor")(function* (commands, machineFor, instance, instanceId) {
|
|
45
|
+
const containers = containersFor(commands);
|
|
46
|
+
const preparedStores = new WeakSet();
|
|
47
|
+
const storeLocks = yield* keyedLockFor();
|
|
48
|
+
const prepareStore = Effect.fn("SshExecutor.preparePnpmStore")(function* (machine, image) {
|
|
49
|
+
const fields = { worker: machine.address.name, host: machine.shell.destination, image };
|
|
50
|
+
yield* Effect.annotateCurrentSpan(fields);
|
|
51
|
+
if (preparedStores.has(machine)) {
|
|
52
|
+
yield* Effect.logDebug("Shared pnpm store already prepared on this machine", fields);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
yield* containers.prepareStore("prepare", machine.shell, image, machine.address.name);
|
|
56
|
+
preparedStores.add(machine);
|
|
57
|
+
}, (effect, machine) => effect.pipe(storeLocks.withLock(machine.address.name)));
|
|
58
|
+
const isolations = yield* Ref.make(new Map());
|
|
59
|
+
// Workers whose earlier containers this process removed. Their containers on a shared machine
|
|
60
|
+
// belong to this process, so another Worker's removal keeps them.
|
|
61
|
+
const reclaimed = yield* Ref.make(new Set());
|
|
62
|
+
const imageFor = (machine) => Option.getOrElse(machine.address.container.image, () => ATTEMPT_IMAGE);
|
|
63
|
+
/** Reads the CPUs, memory, and task limit of the machine's kernel. */
|
|
64
|
+
const readResources = Effect.fn("SshExecutor.readMachineResources")(function* (machine) {
|
|
65
|
+
const worker = machine.address.name;
|
|
66
|
+
const host = machine.shell.destination;
|
|
67
|
+
yield* Effect.annotateCurrentSpan({ worker, host });
|
|
68
|
+
const read = yield* commands.inspect("preflight", machine.shell, "sh", [
|
|
69
|
+
"-c",
|
|
70
|
+
RESOURCES_SCRIPT,
|
|
71
|
+
]);
|
|
72
|
+
const resources = resourcesFromOutput(read.stdout);
|
|
73
|
+
if (read.exitCode === 0 && Option.isSome(resources))
|
|
74
|
+
return resources.value;
|
|
75
|
+
return yield* containerGap(worker, read.exitCode === SSH_FAILURE
|
|
76
|
+
? `Can't reach SSH worker "${worker}" at ${host} to read its CPUs and memory`
|
|
77
|
+
: `Can't read the CPUs, memory, and task limit of SSH worker "${worker}" to derive the limits of its containers. Set cpus, memory_mib, and pids under the worker's container in usine.yaml.`, new Error(read.stderr.trim() || `exit code ${read.exitCode}`));
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Returns the limits of each container on the machine when `slots` Attempts run on it at once:
|
|
81
|
+
* the ones `usine.yaml` sets, and the others derived from the machine. Reads the machine only
|
|
82
|
+
* if a limit is unset.
|
|
83
|
+
*/
|
|
84
|
+
const limitsFor = Effect.fn("SshExecutor.containerLimits")(function* (machine, slots) {
|
|
85
|
+
yield* Effect.annotateCurrentSpan({ worker: machine.address.name, slots });
|
|
86
|
+
const configured = configuredLimits(machine.address.container);
|
|
87
|
+
if (Option.isSome(configured))
|
|
88
|
+
return configured.value;
|
|
89
|
+
return resolveLimits(machine.address.container, yield* readResources(machine), slots);
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* Returns the server version of the machine's Docker, or `None` if the machine has none and
|
|
93
|
+
* the SSH user no access to one. Fails if the SSH user can use Docker but Docker doesn't
|
|
94
|
+
* answer, because Attempts must then run in containers: as the SSH user, they could use
|
|
95
|
+
* Docker's socket to act as root.
|
|
96
|
+
*/
|
|
97
|
+
const detect = Effect.fn("SshExecutor.detectContainerRuntime")(function* (machine) {
|
|
98
|
+
const worker = machine.address.name;
|
|
99
|
+
const host = machine.shell.destination;
|
|
100
|
+
yield* Effect.annotateCurrentSpan({ worker, host });
|
|
101
|
+
const detected = yield* commands.inspect("preflight", machine.shell, "sh", [
|
|
102
|
+
"-c",
|
|
103
|
+
DETECT_SCRIPT,
|
|
104
|
+
"sh",
|
|
105
|
+
CONTAINER_RUNTIME,
|
|
106
|
+
]);
|
|
107
|
+
if (detected.exitCode === RUNTIME_ABSENT)
|
|
108
|
+
return Option.none();
|
|
109
|
+
const version = detected.stdout.trim();
|
|
110
|
+
if (detected.exitCode === 0 && version !== "")
|
|
111
|
+
return Option.some(version);
|
|
112
|
+
if (detected.exitCode === SSH_FAILURE)
|
|
113
|
+
return yield* containerGap(worker, `Can't reach SSH worker "${worker}" at ${host} to check Docker`, new Error(detected.stderr.trim() || `ssh exited with code ${SSH_FAILURE}`));
|
|
114
|
+
if (detected.exitCode === SOCKET_WITHOUT_CLI)
|
|
115
|
+
return yield* containerGap(worker, `The SSH user of worker "${worker}" can use Docker's socket, but the docker CLI isn't installed. Install it, so that each attempt runs in its own container, or take the user out of the docker group.`, new Error("docker group or writable socket without the docker CLI"));
|
|
116
|
+
return yield* containerGap(worker, `Docker on SSH worker "${worker}" doesn't respond to its SSH user. Start Docker, and add the user to the docker group.`, new Error(detected.stderr.trim() || `docker version exited with code ${detected.exitCode}`));
|
|
117
|
+
});
|
|
118
|
+
const record = (worker, isolation) => Ref.update(isolations, (current) => new Map(current).set(worker, isolation));
|
|
119
|
+
/**
|
|
120
|
+
* Records that the machine's Worker runs its Attempts as the SSH user, since the machine has no
|
|
121
|
+
* Docker, and logs it.
|
|
122
|
+
*/
|
|
123
|
+
const recordSharedUser = (machine) => record(machine.address.name, sharedUser).pipe(Effect.andThen(Effect.logWarning("SSH Worker has no Docker; its Attempts run as the SSH user and can read each other's credentials", { worker: machine.address.name, host: machine.shell.destination })), Effect.as(sharedUser));
|
|
124
|
+
/**
|
|
125
|
+
* Records that each Attempt of the machine's Worker runs in its own container from the image,
|
|
126
|
+
* with the limits for `slots` Attempts on the machine at once, and logs them. `docker` is the
|
|
127
|
+
* server version of the machine's Docker.
|
|
128
|
+
*/
|
|
129
|
+
const recordContainer = Effect.fn("SshExecutor.recordContainer")(function* (machine, docker, slots) {
|
|
130
|
+
const worker = machine.address.name;
|
|
131
|
+
const image = imageFor(machine);
|
|
132
|
+
yield* Effect.annotateCurrentSpan({ worker, image, slots });
|
|
133
|
+
const limits = yield* limitsFor(machine, slots);
|
|
134
|
+
const settings = { image, limits: limits.limits, instance, instanceId };
|
|
135
|
+
yield* record(worker, { _tag: "Container", settings });
|
|
136
|
+
yield* Effect.logInfo("SSH Worker runs each Attempt in its own container", {
|
|
137
|
+
worker,
|
|
138
|
+
host: machine.shell.destination,
|
|
139
|
+
docker,
|
|
140
|
+
image,
|
|
141
|
+
...limitFields(limits),
|
|
142
|
+
});
|
|
143
|
+
return { _tag: "Container", settings, limits };
|
|
144
|
+
});
|
|
145
|
+
/**
|
|
146
|
+
* Checks the machine's Docker for preflight, pulls the Attempt image, checks that the image's
|
|
147
|
+
* user has the SSH user's ID, so the containers can write the checkouts, and finds the limits
|
|
148
|
+
* of each container when `slots` Attempts run on the machine at once. Only a machine without
|
|
149
|
+
* Docker runs Attempts as the SSH user, which the result reports as degraded; with Docker, any
|
|
150
|
+
* failure fails the preflight.
|
|
151
|
+
*/
|
|
152
|
+
const verify = Effect.fn("SshExecutor.verifyContainers")(function* (machine, slots) {
|
|
153
|
+
const worker = machine.address.name;
|
|
154
|
+
const host = machine.shell.destination;
|
|
155
|
+
const image = imageFor(machine);
|
|
156
|
+
yield* Effect.annotateCurrentSpan({ worker, host, image, slots });
|
|
157
|
+
const version = yield* detect(machine);
|
|
158
|
+
if (Option.isNone(version)) {
|
|
159
|
+
yield* recordSharedUser(machine);
|
|
160
|
+
return {
|
|
161
|
+
_tag: "SharedUser",
|
|
162
|
+
degraded: "Docker isn't installed, so attempts run as the SSH user and can read each other's credentials. Install Docker to run each attempt in its own container.",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const pulled = yield* commands.inspect("preflight", machine.shell, CONTAINER_RUNTIME, [
|
|
166
|
+
"pull",
|
|
167
|
+
"--quiet",
|
|
168
|
+
image,
|
|
169
|
+
]);
|
|
170
|
+
if (pulled.exitCode !== 0) {
|
|
171
|
+
const local = yield* commands.inspect("preflight", machine.shell, CONTAINER_RUNTIME, [
|
|
172
|
+
"image",
|
|
173
|
+
"inspect",
|
|
174
|
+
"--format",
|
|
175
|
+
"{{.Id}}",
|
|
176
|
+
image,
|
|
177
|
+
]);
|
|
178
|
+
if (local.exitCode !== 0)
|
|
179
|
+
return yield* containerGap(worker, `SSH worker "${worker}" can't pull the attempt image ${image}`, new Error(pulled.stderr.trim() || `docker pull exited with code ${pulled.exitCode}`));
|
|
180
|
+
yield* Effect.logWarning("SSH Worker could not pull the Attempt image; its Attempts use the image already on the machine", { worker, host, image, message: pulled.stderr.trim() });
|
|
181
|
+
}
|
|
182
|
+
const ids = yield* commands.inspect("preflight", machine.shell, "sh", [
|
|
183
|
+
"-c",
|
|
184
|
+
USER_IDS_SCRIPT,
|
|
185
|
+
"sh",
|
|
186
|
+
CONTAINER_RUNTIME,
|
|
187
|
+
image,
|
|
188
|
+
]);
|
|
189
|
+
const [hostId, imageId] = ids.stdout.trim().split(" ");
|
|
190
|
+
if (ids.exitCode !== 0 || hostId === undefined || imageId === undefined)
|
|
191
|
+
return yield* containerGap(worker, `SSH worker "${worker}" can't run the attempt image ${image}`, new Error(ids.stderr.trim() || `exit code ${ids.exitCode}`));
|
|
192
|
+
if (hostId !== imageId)
|
|
193
|
+
return yield* containerGap(worker, `The attempt image ${image} runs as user ID ${imageId}, and the SSH user of worker "${worker}" has ID ${hostId}, so its containers can't write the checkouts. Build the image with --build-arg USINE_UID=${hostId}.`, new Error(`user IDs ${hostId} and ${imageId}`));
|
|
194
|
+
yield* containers
|
|
195
|
+
.prepareStore("preflight", machine.shell, image, worker)
|
|
196
|
+
.pipe(Effect.mapError((error) => containerGap(worker, `Can't prepare the shared pnpm store: ${error.message}`, error)));
|
|
197
|
+
preparedStores.add(machine);
|
|
198
|
+
const size = yield* commands
|
|
199
|
+
.run("preflight", machine.shell, CONTAINER_RUNTIME, [
|
|
200
|
+
"run",
|
|
201
|
+
"--rm",
|
|
202
|
+
"--network",
|
|
203
|
+
"none",
|
|
204
|
+
"--mount",
|
|
205
|
+
PNPM_STORE_MOUNT,
|
|
206
|
+
"--entrypoint",
|
|
207
|
+
"du",
|
|
208
|
+
image,
|
|
209
|
+
"-sk",
|
|
210
|
+
PNPM_STORE_PATH,
|
|
211
|
+
])
|
|
212
|
+
.pipe(Effect.flatMap((output) => decodeStoreSize(output.trim().split(/\s+/u)[0])), Effect.mapError((error) => containerGap(worker, `Can't measure the shared pnpm store on worker "${worker}"`, error)));
|
|
213
|
+
yield* Effect.logInfo("Shared pnpm store disk usage", {
|
|
214
|
+
worker,
|
|
215
|
+
host,
|
|
216
|
+
volume: PNPM_STORE_VOLUME,
|
|
217
|
+
sizeKiB: size,
|
|
218
|
+
});
|
|
219
|
+
return yield* recordContainer(machine, version.value, slots);
|
|
220
|
+
});
|
|
221
|
+
/**
|
|
222
|
+
* Returns how new Attempts on the machine run, as the Worker's last preflight found, or detects
|
|
223
|
+
* and logs it. Before any preflight on the Worker, as for the recovery of a provisioned Worker's
|
|
224
|
+
* machine, it derives the unset limits for one Attempt on the machine.
|
|
225
|
+
*/
|
|
226
|
+
const isolationOf = Effect.fn("SshExecutor.isolation")(function* (machine) {
|
|
227
|
+
const recorded = (yield* Ref.get(isolations)).get(machine.address.name);
|
|
228
|
+
if (recorded !== undefined) {
|
|
229
|
+
if (recorded._tag === "Container")
|
|
230
|
+
yield* prepareStore(machine, recorded.settings.image);
|
|
231
|
+
return recorded;
|
|
232
|
+
}
|
|
233
|
+
const version = yield* detect(machine);
|
|
234
|
+
if (Option.isNone(version))
|
|
235
|
+
return yield* recordSharedUser(machine);
|
|
236
|
+
yield* prepareStore(machine, imageFor(machine));
|
|
237
|
+
const { settings } = yield* recordContainer(machine, version.value, 1);
|
|
238
|
+
return { _tag: "Container", settings };
|
|
239
|
+
});
|
|
240
|
+
/**
|
|
241
|
+
* Starts a container from `settings` for a preflight's harness probes, with the login files
|
|
242
|
+
* of `harnesses` and `environment`, until the caller's scope closes. It removes itself after
|
|
243
|
+
* 15 minutes if this process can't.
|
|
244
|
+
*/
|
|
245
|
+
const preflightShell = Effect.fn("SshExecutor.preflightContainer")(function* (machine, settings, harnesses, environment) {
|
|
246
|
+
const suffix = yield* Random.nextIntBetween(0, 1_000_000_000);
|
|
247
|
+
return yield* containers.start("preflight", machine.shell, {
|
|
248
|
+
name: `usine-preflight-${safeSegment(machine.address.name)}-${suffix}`,
|
|
249
|
+
settings,
|
|
250
|
+
worker: machine.address.name,
|
|
251
|
+
attemptId: Option.none(),
|
|
252
|
+
checkout: Option.none(),
|
|
253
|
+
readOnly: [],
|
|
254
|
+
writable: [],
|
|
255
|
+
harnesses,
|
|
256
|
+
environment,
|
|
257
|
+
secrets: {},
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
const sweep = Effect.fn("SshExecutor.removeLeftContainers")(function* (worker) {
|
|
261
|
+
const machine = yield* machineFor(worker);
|
|
262
|
+
yield* Effect.annotateCurrentSpan({ worker, host: machine.shell.destination });
|
|
263
|
+
yield* Ref.update(reclaimed, (current) => new Set(current).add(worker));
|
|
264
|
+
yield* containers.sweep(machine.shell, instance, instanceId, worker, yield* Ref.get(reclaimed));
|
|
265
|
+
});
|
|
266
|
+
// Runs once per Worker in this process, before its first container starts. A failure is
|
|
267
|
+
// logged; the containers stay until the next process.
|
|
268
|
+
const swept = yield* Cache.make({
|
|
269
|
+
capacity: Number.POSITIVE_INFINITY,
|
|
270
|
+
lookup: (worker) => sweep(worker).pipe(Effect.tapError((error) => Effect.logWarning("Could not remove the containers an earlier process left", {
|
|
271
|
+
worker,
|
|
272
|
+
message: error.message,
|
|
273
|
+
})), Effect.ignore),
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
verify,
|
|
277
|
+
isolation: isolationOf,
|
|
278
|
+
preflightShell,
|
|
279
|
+
reclaim: (worker) => Cache.get(swept, worker),
|
|
280
|
+
};
|
|
281
|
+
});
|
|
282
|
+
//# sourceMappingURL=container.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/ssh/container.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { WorkerName } from "@usine/core/worker";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { CredentialProvider, type ExecutorError } from "../executor.ts";
|
|
4
|
+
import type { RemoteCommands } from "../remote/commands.ts";
|
|
5
|
+
import type { SshMachine } from "./model.ts";
|
|
6
|
+
/**
|
|
7
|
+
* Writes each Attempt's token to a directory next to the Worker's checkouts that only the SSH
|
|
8
|
+
* user can read, renews it before expiry, and removes it when the Attempt's scope closes. All
|
|
9
|
+
* Attempts on the Worker can read each other's tokens (ADR 0026). `reclaim` removes credentials
|
|
10
|
+
* left by an earlier process, once per process.
|
|
11
|
+
*/
|
|
12
|
+
export declare const sshCredentialsFor: (commands: RemoteCommands, machineFor: (worker: WorkerName) => Effect.Effect<SshMachine, ExecutorError>) => Effect.Effect<{
|
|
13
|
+
acquire: (attemptId: string & import("effect/Brand").Brand<"AttemptId">, worker: string & import("effect/Brand").Brand<"WorkerName">, repository: string & import("effect/Brand").Brand<"RepositoryName">, permissions: "read" | "write", machine: SshMachine, operation: "cancel" | "cleanup" | "launch" | "preflight" | "prepare") => Effect.Effect<{
|
|
14
|
+
environment: {
|
|
15
|
+
GH_CONFIG_DIR: string;
|
|
16
|
+
GH_PROMPT_DISABLED: string;
|
|
17
|
+
USINE_GITHUB_TOKEN_FILE: string;
|
|
18
|
+
GIT_CONFIG_COUNT: string;
|
|
19
|
+
GIT_CONFIG_KEY_0: string;
|
|
20
|
+
GIT_CONFIG_VALUE_0: string;
|
|
21
|
+
GIT_CONFIG_KEY_1: string;
|
|
22
|
+
GIT_CONFIG_VALUE_1: string;
|
|
23
|
+
};
|
|
24
|
+
gitEnvironment: {
|
|
25
|
+
GH_CONFIG_DIR: string;
|
|
26
|
+
GH_PROMPT_DISABLED: string;
|
|
27
|
+
USINE_GITHUB_TOKEN_FILE: string;
|
|
28
|
+
GIT_CONFIG_COUNT: string;
|
|
29
|
+
GIT_CONFIG_KEY_0: string;
|
|
30
|
+
GIT_CONFIG_VALUE_0: string;
|
|
31
|
+
GIT_CONFIG_KEY_1: string;
|
|
32
|
+
GIT_CONFIG_VALUE_1: string;
|
|
33
|
+
};
|
|
34
|
+
shell: import("@usine/agents/child-host").RemoteShell;
|
|
35
|
+
secrets: {};
|
|
36
|
+
readOnlyPaths: string[];
|
|
37
|
+
writablePaths: never[];
|
|
38
|
+
refresh: Effect.Effect<void, import("../executor.ts").CredentialUnavailableError | import("../executor.ts").ExecutorOperationError, never>;
|
|
39
|
+
scrub: (text: string) => string;
|
|
40
|
+
}, import("../executor.ts").CredentialUnavailableError | import("../executor.ts").ExecutorOperationError, import("effect/Scope").Scope>;
|
|
41
|
+
reclaim: (worker: WorkerName) => Effect.Effect<void, never, never>;
|
|
42
|
+
}, never, CredentialProvider>;
|
|
43
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/ssh/credentials.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAS,MAAM,EAAY,MAAM,QAAQ,CAAC;AAEjD,OAAO,EACL,kBAAkB,EAClB,KAAK,aAAa,EAGnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAuB7C;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,kDAEP,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA+BnD,UAAU;6BA6EnC,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { safeSegment } from "@usine/core/repository";
|
|
2
|
+
import { Cache, Effect, Redacted } from "effect";
|
|
3
|
+
import { credentialEnvironment, GH_CONFIG, ghHostsConfig } from "../credential-environment.js";
|
|
4
|
+
import { CredentialProvider, } from "../executor.js";
|
|
5
|
+
import { renewableCredential } from "../renewable-credential.js";
|
|
6
|
+
/**
|
|
7
|
+
* A script that writes the Attempt's credential directory `$3` inside the credentials root
|
|
8
|
+
* `$2`: the token from the first stdin line, gh's `hosts.yml` from the rest, and gh's
|
|
9
|
+
* `config.yml` from `$1`. Sets mode 700 on both directories. Reading the token from stdin keeps
|
|
10
|
+
* it out of arguments and the environment; each file is written then renamed.
|
|
11
|
+
*/
|
|
12
|
+
const WRITE_CREDENTIAL_SCRIPT = String.raw `
|
|
13
|
+
set -e
|
|
14
|
+
umask 077
|
|
15
|
+
mkdir -p "$2" "$3"
|
|
16
|
+
chmod 700 "$2" "$3"
|
|
17
|
+
IFS= read -r token
|
|
18
|
+
printf '%s' "$token" > "$3/token.next"
|
|
19
|
+
cat > "$3/hosts.next"
|
|
20
|
+
test -f "$3/config.yml" || printf '%s' "$1" > "$3/config.yml"
|
|
21
|
+
mv "$3/token.next" "$3/token"
|
|
22
|
+
mv "$3/hosts.next" "$3/hosts.yml"
|
|
23
|
+
`.trim();
|
|
24
|
+
const credentialsRoot = (machine) => `${machine.workerDirectory}/credentials`;
|
|
25
|
+
/**
|
|
26
|
+
* Writes each Attempt's token to a directory next to the Worker's checkouts that only the SSH
|
|
27
|
+
* user can read, renews it before expiry, and removes it when the Attempt's scope closes. All
|
|
28
|
+
* Attempts on the Worker can read each other's tokens (ADR 0026). `reclaim` removes credentials
|
|
29
|
+
* left by an earlier process, once per process.
|
|
30
|
+
*/
|
|
31
|
+
export const sshCredentialsFor = Effect.fn("SshExecutor.credentialsFor")(function* (commands, machineFor) {
|
|
32
|
+
const provider = yield* CredentialProvider;
|
|
33
|
+
const removeStale = Effect.fn("SshExecutor.removeStaleCredentials")(function* (worker) {
|
|
34
|
+
const machine = yield* machineFor(worker);
|
|
35
|
+
const host = machine.shell.destination;
|
|
36
|
+
yield* Effect.annotateCurrentSpan({ worker, host });
|
|
37
|
+
yield* commands.run("cleanup", machine.shell, "rm", ["-rf", credentialsRoot(machine)]);
|
|
38
|
+
yield* Effect.logInfo("Removed credentials an earlier process left on the SSH Worker", {
|
|
39
|
+
worker,
|
|
40
|
+
host,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
// A failed removal is logged and cached, not retried: the remaining tokens expire within an
|
|
44
|
+
// hour and the next start removes them.
|
|
45
|
+
const reclaimed = yield* Cache.make({
|
|
46
|
+
capacity: Number.POSITIVE_INFINITY,
|
|
47
|
+
lookup: (worker) => removeStale(worker).pipe(Effect.tapError((error) => Effect.logWarning("Could not remove stale credentials from the SSH Worker", {
|
|
48
|
+
worker,
|
|
49
|
+
message: error.message,
|
|
50
|
+
})), Effect.ignore),
|
|
51
|
+
});
|
|
52
|
+
const reclaim = (worker) => Cache.get(reclaimed, worker);
|
|
53
|
+
const write = Effect.fn("SshExecutor.writeCredential")(function* (operation, machine, directory, { value }) {
|
|
54
|
+
yield* Effect.annotateCurrentSpan({ host: machine.shell.destination, directory });
|
|
55
|
+
const payload = Redacted.make(`${Redacted.value(value)}\n${ghHostsConfig(Redacted.value(value))}`);
|
|
56
|
+
yield* commands
|
|
57
|
+
.runWithInput(operation, machine.shell, "sh", ["-c", WRITE_CREDENTIAL_SCRIPT, "sh", GH_CONFIG, credentialsRoot(machine), directory], payload)
|
|
58
|
+
.pipe(Effect.ensuring(Effect.sync(() => Redacted.wipeUnsafe(payload))));
|
|
59
|
+
},
|
|
60
|
+
// Scope closure waits for an in-flight write, so a late renewal cannot run after the removal.
|
|
61
|
+
Effect.uninterruptible);
|
|
62
|
+
const acquire = Effect.fn("SshExecutor.acquireCredential")(function* (attemptId, worker, repository, permissions, machine, operation) {
|
|
63
|
+
const host = machine.shell.destination;
|
|
64
|
+
yield* Effect.annotateCurrentSpan({ attemptId, worker, repository, permissions, host });
|
|
65
|
+
const directory = `${credentialsRoot(machine)}/${safeSegment(attemptId)}`;
|
|
66
|
+
yield* Effect.addFinalizer(() => commands.run("cleanup", machine.shell, "rm", ["-rf", directory]).pipe(Effect.tap(() => Effect.logInfo("Removed the Attempt's credential from the SSH Worker", {
|
|
67
|
+
attemptId,
|
|
68
|
+
worker,
|
|
69
|
+
host,
|
|
70
|
+
})), Effect.tapError((error) => Effect.logError("Could not remove the Attempt's credential from the SSH Worker; it expires within the hour", { attemptId, worker, host, message: error.message })), Effect.ignore));
|
|
71
|
+
const credential = yield* renewableCredential(provider.issue, repository, attemptId, permissions, (issued) => write(operation, machine, directory, issued));
|
|
72
|
+
const environment = credentialEnvironment(directory);
|
|
73
|
+
return {
|
|
74
|
+
environment,
|
|
75
|
+
gitEnvironment: environment,
|
|
76
|
+
shell: machine.shell,
|
|
77
|
+
secrets: {},
|
|
78
|
+
readOnlyPaths: [directory],
|
|
79
|
+
writablePaths: [],
|
|
80
|
+
refresh: credential.refresh(),
|
|
81
|
+
scrub: credential.scrub,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
return { acquire, reclaim };
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/ssh/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type AttachRequest, type Attached, type AttachmentId, type AttachmentList, type Detached, type LlmProviderList } from "@usine/gateway/admin";
|
|
2
|
+
import { Effect, Option, Schema } from "effect";
|
|
3
|
+
import { HttpClient } from "effect/http";
|
|
4
|
+
import type { SshWorkerGateway } from "../model.ts";
|
|
5
|
+
import { type GatewayForward } from "./forward.ts";
|
|
6
|
+
declare const GatewayAdminError_base: Schema.Class<GatewayAdminError, Schema.TaggedStruct<"GatewayAdminError", {
|
|
7
|
+
readonly worker: Schema.brand<Schema.String, "WorkerName">;
|
|
8
|
+
readonly operation: Schema.Literals<readonly ["attach", "renew", "detach", "list", "providers"]>;
|
|
9
|
+
readonly message: Schema.NonEmptyString;
|
|
10
|
+
}>, import("effect/Cause").YieldableError>;
|
|
11
|
+
/**
|
|
12
|
+
* The error for a failed Gateway admin API request. `message` contains no token; the request
|
|
13
|
+
* and raw failure are discarded because an attach request contains the installation token.
|
|
14
|
+
*/
|
|
15
|
+
export declare class GatewayAdminError extends GatewayAdminError_base {
|
|
16
|
+
}
|
|
17
|
+
export interface GatewayAdmin {
|
|
18
|
+
/** Attaches an Attempt's token for the Worker and a repository. Returns the attachment ID and its caller token. */
|
|
19
|
+
readonly attach: (request: AttachRequest) => Effect.Effect<Attached, GatewayAdminError>;
|
|
20
|
+
/** Replaces the token and expiry of attachment `id`, or re-creates it after a Gateway restart. The caller token doesn't change. */
|
|
21
|
+
readonly renew: (id: AttachmentId, request: AttachRequest) => Effect.Effect<Attached, GatewayAdminError>;
|
|
22
|
+
/** Removes attachment `id` and returns whether it existed. */
|
|
23
|
+
readonly detach: (id: AttachmentId) => Effect.Effect<Detached, GatewayAdminError>;
|
|
24
|
+
/** Returns the active attachments for all the Gateway's Workers. */
|
|
25
|
+
readonly list: Effect.Effect<AttachmentList, GatewayAdminError>;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the LLM providers the Gateway has credentials for, with each credential kind. Returns
|
|
28
|
+
* `None` if the Gateway predates this endpoint.
|
|
29
|
+
*/
|
|
30
|
+
readonly providers: Effect.Effect<Option.Option<LlmProviderList>, GatewayAdminError>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the admin API client for the Gateway in front of `worker`, connected through
|
|
34
|
+
* `forward` with the admin token as a bearer token. If the forward is closed or a request gets
|
|
35
|
+
* no response, reopens the forward and retries once.
|
|
36
|
+
*/
|
|
37
|
+
export declare const gatewayAdminFor: (worker: string & import("effect/Brand").Brand<"WorkerName">, gateway: SshWorkerGateway, forward: GatewayForward) => Effect.Effect<{
|
|
38
|
+
attach: (request: AttachRequest) => Effect.Effect<Attached, GatewayAdminError, never>;
|
|
39
|
+
renew: (id: string & import("effect/Brand").Brand<"AttachmentId">, request: AttachRequest) => Effect.Effect<Attached, GatewayAdminError, never>;
|
|
40
|
+
detach: (id: string & import("effect/Brand").Brand<"AttachmentId">) => Effect.Effect<Detached, GatewayAdminError, never>;
|
|
41
|
+
list: Effect.Effect<AttachmentList, GatewayAdminError, never>;
|
|
42
|
+
providers: Effect.Effect<Option.Option<never> | Option.Option<LlmProviderList>, GatewayAdminError, never>;
|
|
43
|
+
}, never, HttpClient.HttpClient>;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=admin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/ssh/gateway/admin.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,QAAQ,EAEb,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAkB,MAAM,EAAE,MAAM,EAAyB,MAAM,EAAE,MAAM,QAAQ,CAAC;AACvF,OAAO,EAAE,UAAU,EAAsC,MAAM,aAAa,CAAC;AAE7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,KAAK,cAAc,EAA0C,MAAM,cAAc,CAAC;;;;;;AAa3F;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,sBAMrC;CAAG;AAEL,MAAM,WAAW,YAAY;IAC3B,mHAAmH;IACnH,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IACxF,mIAAmI;IACnI,QAAQ,CAAC,KAAK,EAAE,CACd,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,aAAa,KACnB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAChD,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAClF,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;IAChE;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,iBAAiB,CAAC,CAAC;CACtF;AAmED;;;;GAIG;AACH,eAAO,MAAM,eAAe;;;;;;gCAgF1B,CAAC"}
|