@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.
Files changed (250) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +212 -37
  3. package/dist/child-command.d.ts +5 -8
  4. package/dist/child-command.d.ts.map +1 -1
  5. package/dist/child-command.js +10 -14
  6. package/dist/child-command.js.map +1 -1
  7. package/dist/child-environment.d.ts +19 -0
  8. package/dist/child-environment.d.ts.map +1 -0
  9. package/dist/child-environment.js +18 -0
  10. package/dist/child-environment.js.map +1 -0
  11. package/dist/claude-login-lock.d.ts +9 -0
  12. package/dist/claude-login-lock.d.ts.map +1 -0
  13. package/dist/claude-login-lock.js +15 -0
  14. package/dist/claude-login-lock.js.map +1 -0
  15. package/dist/cloudflare/client.d.ts +50 -0
  16. package/dist/cloudflare/client.d.ts.map +1 -0
  17. package/dist/cloudflare/client.js +108 -0
  18. package/dist/cloudflare/client.js.map +1 -0
  19. package/dist/cloudflare/cloudflare.d.ts +17 -0
  20. package/dist/cloudflare/cloudflare.d.ts.map +1 -0
  21. package/dist/cloudflare/cloudflare.js +704 -0
  22. package/dist/cloudflare/cloudflare.js.map +1 -0
  23. package/dist/cloudflare/model.d.ts +42 -0
  24. package/dist/cloudflare/model.d.ts.map +1 -0
  25. package/dist/cloudflare/model.js +2 -0
  26. package/dist/cloudflare/model.js.map +1 -0
  27. package/dist/cloudflare/shell/environment.d.ts +12 -0
  28. package/dist/cloudflare/shell/environment.d.ts.map +1 -0
  29. package/dist/cloudflare/shell/environment.js +12 -0
  30. package/dist/cloudflare/shell/environment.js.map +1 -0
  31. package/dist/cloudflare/shell/shell.d.ts +2 -0
  32. package/dist/cloudflare/shell/shell.d.ts.map +1 -0
  33. package/dist/cloudflare/shell/shell.js +155 -0
  34. package/dist/cloudflare/shell/shell.js.map +1 -0
  35. package/dist/credential-environment.d.ts +20 -0
  36. package/dist/credential-environment.d.ts.map +1 -0
  37. package/dist/credential-environment.js +20 -0
  38. package/dist/credential-environment.js.map +1 -0
  39. package/dist/durable-session.d.ts +81 -0
  40. package/dist/durable-session.d.ts.map +1 -0
  41. package/dist/durable-session.js +550 -0
  42. package/dist/durable-session.js.map +1 -0
  43. package/dist/exe-dev/commands.d.ts +23 -13
  44. package/dist/exe-dev/commands.d.ts.map +1 -1
  45. package/dist/exe-dev/commands.js +11 -57
  46. package/dist/exe-dev/commands.js.map +1 -1
  47. package/dist/exe-dev/exe-dev.d.ts +9 -3
  48. package/dist/exe-dev/exe-dev.d.ts.map +1 -1
  49. package/dist/exe-dev/exe-dev.js +61 -128
  50. package/dist/exe-dev/exe-dev.js.map +1 -1
  51. package/dist/exe-dev/grants.d.ts.map +1 -1
  52. package/dist/exe-dev/grants.js +35 -15
  53. package/dist/exe-dev/grants.js.map +1 -1
  54. package/dist/exe-dev/model.d.ts +15 -19
  55. package/dist/exe-dev/model.d.ts.map +1 -1
  56. package/dist/exe-dev/model.js +9 -12
  57. package/dist/exe-dev/model.js.map +1 -1
  58. package/dist/exe-dev/preflight.d.ts +8 -4
  59. package/dist/exe-dev/preflight.d.ts.map +1 -1
  60. package/dist/exe-dev/preflight.js +26 -35
  61. package/dist/exe-dev/preflight.js.map +1 -1
  62. package/dist/executor.d.ts +292 -44
  63. package/dist/executor.d.ts.map +1 -1
  64. package/dist/executor.js +187 -19
  65. package/dist/executor.js.map +1 -1
  66. package/dist/guard.d.ts +57 -7
  67. package/dist/guard.d.ts.map +1 -1
  68. package/dist/guard.js +274 -59
  69. package/dist/guard.js.map +1 -1
  70. package/dist/harness-preflight.d.ts +27 -5
  71. package/dist/harness-preflight.d.ts.map +1 -1
  72. package/dist/harness-preflight.js +32 -31
  73. package/dist/harness-preflight.js.map +1 -1
  74. package/dist/keyed-lock.d.ts +2 -3
  75. package/dist/keyed-lock.d.ts.map +1 -1
  76. package/dist/keyed-lock.js +2 -3
  77. package/dist/keyed-lock.js.map +1 -1
  78. package/dist/leftovers.d.ts +9 -0
  79. package/dist/leftovers.d.ts.map +1 -0
  80. package/dist/leftovers.js +14 -0
  81. package/dist/leftovers.js.map +1 -0
  82. package/dist/local/credentials.d.ts +21 -0
  83. package/dist/local/credentials.d.ts.map +1 -0
  84. package/dist/local/credentials.js +39 -0
  85. package/dist/local/credentials.js.map +1 -0
  86. package/dist/local/leftovers.d.ts +8 -0
  87. package/dist/local/leftovers.d.ts.map +1 -0
  88. package/dist/local/leftovers.js +23 -0
  89. package/dist/local/leftovers.js.map +1 -0
  90. package/dist/local/local.d.ts +17 -11
  91. package/dist/local/local.d.ts.map +1 -1
  92. package/dist/local/local.js +205 -132
  93. package/dist/local/local.js.map +1 -1
  94. package/dist/memory-preflight.d.ts +27 -0
  95. package/dist/memory-preflight.d.ts.map +1 -0
  96. package/dist/memory-preflight.js +83 -0
  97. package/dist/memory-preflight.js.map +1 -0
  98. package/dist/pre-push-hook.d.ts +20 -0
  99. package/dist/pre-push-hook.d.ts.map +1 -0
  100. package/dist/pre-push-hook.js +33 -0
  101. package/dist/pre-push-hook.js.map +1 -0
  102. package/dist/prepared-attempt.d.ts +41 -3
  103. package/dist/prepared-attempt.d.ts.map +1 -1
  104. package/dist/prepared-attempt.js +109 -31
  105. package/dist/prepared-attempt.js.map +1 -1
  106. package/dist/provision.d.ts +129 -15
  107. package/dist/provision.d.ts.map +1 -1
  108. package/dist/provision.js +428 -69
  109. package/dist/provision.js.map +1 -1
  110. package/dist/remote/commands.d.ts +51 -0
  111. package/dist/remote/commands.d.ts.map +1 -0
  112. package/dist/remote/commands.js +94 -0
  113. package/dist/remote/commands.js.map +1 -0
  114. package/dist/remote/container.d.ts +90 -0
  115. package/dist/remote/container.d.ts.map +1 -0
  116. package/dist/remote/container.js +480 -0
  117. package/dist/remote/container.js.map +1 -0
  118. package/dist/remote/leftovers.d.ts +14 -0
  119. package/dist/remote/leftovers.d.ts.map +1 -0
  120. package/dist/remote/leftovers.js +22 -0
  121. package/dist/remote/leftovers.js.map +1 -0
  122. package/dist/remote/liveness.d.ts +33 -0
  123. package/dist/remote/liveness.d.ts.map +1 -0
  124. package/dist/remote/liveness.js +196 -0
  125. package/dist/remote/liveness.js.map +1 -0
  126. package/dist/remote/machine.d.ts +110 -0
  127. package/dist/remote/machine.d.ts.map +1 -0
  128. package/dist/remote/machine.js +2 -0
  129. package/dist/remote/machine.js.map +1 -0
  130. package/dist/remote/remote.d.ts +28 -0
  131. package/dist/remote/remote.d.ts.map +1 -0
  132. package/dist/remote/remote.js +556 -0
  133. package/dist/remote/remote.js.map +1 -0
  134. package/dist/remote/sweep.d.ts +17 -0
  135. package/dist/remote/sweep.d.ts.map +1 -0
  136. package/dist/remote/sweep.js +117 -0
  137. package/dist/remote/sweep.js.map +1 -0
  138. package/dist/renewable-credential.d.ts +17 -0
  139. package/dist/renewable-credential.d.ts.map +1 -0
  140. package/dist/renewable-credential.js +58 -0
  141. package/dist/renewable-credential.js.map +1 -0
  142. package/dist/salvage.d.ts +38 -0
  143. package/dist/salvage.d.ts.map +1 -0
  144. package/dist/salvage.js +198 -0
  145. package/dist/salvage.js.map +1 -0
  146. package/dist/ssh/api-keys.d.ts +36 -0
  147. package/dist/ssh/api-keys.d.ts.map +1 -0
  148. package/dist/ssh/api-keys.js +72 -0
  149. package/dist/ssh/api-keys.js.map +1 -0
  150. package/dist/ssh/codex-homes.d.ts +21 -0
  151. package/dist/ssh/codex-homes.d.ts.map +1 -0
  152. package/dist/ssh/codex-homes.js +78 -0
  153. package/dist/ssh/codex-homes.js.map +1 -0
  154. package/dist/ssh/container.d.ts +32 -0
  155. package/dist/ssh/container.d.ts.map +1 -0
  156. package/dist/ssh/container.js +282 -0
  157. package/dist/ssh/container.js.map +1 -0
  158. package/dist/ssh/credentials.d.ts +43 -0
  159. package/dist/ssh/credentials.d.ts.map +1 -0
  160. package/dist/ssh/credentials.js +86 -0
  161. package/dist/ssh/credentials.js.map +1 -0
  162. package/dist/ssh/gateway/admin.d.ts +45 -0
  163. package/dist/ssh/gateway/admin.d.ts.map +1 -0
  164. package/dist/ssh/gateway/admin.js +129 -0
  165. package/dist/ssh/gateway/admin.js.map +1 -0
  166. package/dist/ssh/gateway/attachments.d.ts +31 -0
  167. package/dist/ssh/gateway/attachments.d.ts.map +1 -0
  168. package/dist/ssh/gateway/attachments.js +168 -0
  169. package/dist/ssh/gateway/attachments.js.map +1 -0
  170. package/dist/ssh/gateway/environment.d.ts +71 -0
  171. package/dist/ssh/gateway/environment.d.ts.map +1 -0
  172. package/dist/ssh/gateway/environment.js +148 -0
  173. package/dist/ssh/gateway/environment.js.map +1 -0
  174. package/dist/ssh/gateway/forward.d.ts +74 -0
  175. package/dist/ssh/gateway/forward.d.ts.map +1 -0
  176. package/dist/ssh/gateway/forward.js +247 -0
  177. package/dist/ssh/gateway/forward.js.map +1 -0
  178. package/dist/ssh/gateway/preflight.d.ts +67 -0
  179. package/dist/ssh/gateway/preflight.d.ts.map +1 -0
  180. package/dist/ssh/gateway/preflight.js +320 -0
  181. package/dist/ssh/gateway/preflight.js.map +1 -0
  182. package/dist/ssh/limits.d.ts +60 -0
  183. package/dist/ssh/limits.d.ts.map +1 -0
  184. package/dist/ssh/limits.js +84 -0
  185. package/dist/ssh/limits.js.map +1 -0
  186. package/dist/ssh/model.d.ts +153 -0
  187. package/dist/ssh/model.d.ts.map +1 -0
  188. package/dist/ssh/model.js +2 -0
  189. package/dist/ssh/model.js.map +1 -0
  190. package/dist/ssh/preflight.d.ts +35 -0
  191. package/dist/ssh/preflight.d.ts.map +1 -0
  192. package/dist/ssh/preflight.js +87 -0
  193. package/dist/ssh/preflight.js.map +1 -0
  194. package/dist/ssh/provisioner/boat.d.ts +33 -0
  195. package/dist/ssh/provisioner/boat.d.ts.map +1 -0
  196. package/dist/ssh/provisioner/boat.js +247 -0
  197. package/dist/ssh/provisioner/boat.js.map +1 -0
  198. package/dist/ssh/provisioner/hetzner.d.ts +32 -0
  199. package/dist/ssh/provisioner/hetzner.d.ts.map +1 -0
  200. package/dist/ssh/provisioner/hetzner.js +337 -0
  201. package/dist/ssh/provisioner/hetzner.js.map +1 -0
  202. package/dist/ssh/provisioner/host-key.d.ts +11 -0
  203. package/dist/ssh/provisioner/host-key.d.ts.map +1 -0
  204. package/dist/ssh/provisioner/host-key.js +57 -0
  205. package/dist/ssh/provisioner/host-key.js.map +1 -0
  206. package/dist/ssh/provisioner/http.d.ts +45 -0
  207. package/dist/ssh/provisioner/http.d.ts.map +1 -0
  208. package/dist/ssh/provisioner/http.js +72 -0
  209. package/dist/ssh/provisioner/http.js.map +1 -0
  210. package/dist/ssh/provisioner/machines.d.ts +48 -0
  211. package/dist/ssh/provisioner/machines.d.ts.map +1 -0
  212. package/dist/ssh/provisioner/machines.js +686 -0
  213. package/dist/ssh/provisioner/machines.js.map +1 -0
  214. package/dist/ssh/provisioner/model.d.ts +92 -0
  215. package/dist/ssh/provisioner/model.d.ts.map +1 -0
  216. package/dist/ssh/provisioner/model.js +37 -0
  217. package/dist/ssh/provisioner/model.js.map +1 -0
  218. package/dist/ssh/ssh.d.ts +23 -0
  219. package/dist/ssh/ssh.d.ts.map +1 -0
  220. package/dist/ssh/ssh.js +145 -0
  221. package/dist/ssh/ssh.js.map +1 -0
  222. package/dist/ssh/worker-records.d.ts +18 -0
  223. package/dist/ssh/worker-records.d.ts.map +1 -0
  224. package/dist/ssh/worker-records.js +100 -0
  225. package/dist/ssh/worker-records.js.map +1 -0
  226. package/dist/ssh/workers.d.ts +80 -0
  227. package/dist/ssh/workers.d.ts.map +1 -0
  228. package/dist/ssh/workers.js +247 -0
  229. package/dist/ssh/workers.js.map +1 -0
  230. package/package.json +25 -9
  231. package/dist/exe-dev/channel.d.ts +0 -12
  232. package/dist/exe-dev/channel.d.ts.map +0 -1
  233. package/dist/exe-dev/channel.js +0 -42
  234. package/dist/exe-dev/channel.js.map +0 -1
  235. package/dist/exe-dev/shell.d.ts +0 -12
  236. package/dist/exe-dev/shell.d.ts.map +0 -1
  237. package/dist/exe-dev/shell.js +0 -31
  238. package/dist/exe-dev/shell.js.map +0 -1
  239. package/dist/exe-dev/sweep.d.ts +0 -10
  240. package/dist/exe-dev/sweep.d.ts.map +0 -1
  241. package/dist/exe-dev/sweep.js +0 -117
  242. package/dist/exe-dev/sweep.js.map +0 -1
  243. package/dist/invalid-frame.d.ts +0 -17
  244. package/dist/invalid-frame.d.ts.map +0 -1
  245. package/dist/invalid-frame.js +0 -111
  246. package/dist/invalid-frame.js.map +0 -1
  247. package/dist/run-host-session.d.ts +0 -27
  248. package/dist/run-host-session.d.ts.map +0 -1
  249. package/dist/run-host-session.js +0 -303
  250. package/dist/run-host-session.js.map +0 -1
@@ -0,0 +1,74 @@
1
+ import type { RemoteShell } from "@usine/agents/child-host";
2
+ import { SshDestination } from "@usine/core/worker";
3
+ import { DateTime, Effect, Schema, Stream } from "effect";
4
+ import { ChildProcess, type ChildProcessSpawner } from "effect/process";
5
+ export interface OpenForward {
6
+ /** The SSH client number, starting at 1. `reopen` replaces only this generation. */
7
+ readonly generation: number;
8
+ readonly url: URL;
9
+ }
10
+ declare const GatewayForwardClosed_base: Schema.Class<GatewayForwardClosed, Schema.TaggedStruct<"GatewayForwardClosed", {
11
+ readonly gateway: Schema.brand<Schema.String, "SshDestination">;
12
+ readonly generation: Schema.Int;
13
+ readonly since: Schema.DateTimeUtc;
14
+ readonly reason: Schema.NonEmptyString;
15
+ }>, import("effect/Cause").YieldableError>;
16
+ /**
17
+ * The error for a closed admin API forward, whose SSH client didn't open or exited. `since` is
18
+ * when the Gateway became unreachable, and `reason` is the SSH client's error.
19
+ */
20
+ export declare class GatewayForwardClosed extends GatewayForwardClosed_base {
21
+ }
22
+ /**
23
+ * An SSH forward from a local port to a Gateway's admin API (ADR 0027, amendment of
24
+ * 2026-10-19). It opens with the first Worker behind the Gateway, reopens after a pause when
25
+ * its client exits, and closes with the last Worker or the executor.
26
+ */
27
+ export interface GatewayForward {
28
+ /** The Gateway's SSH destination, used to identify it in logs and messages. */
29
+ readonly gateway: SshDestination;
30
+ /** The SSH command an operator can run to open the same forward manually. */
31
+ readonly command: string;
32
+ /** Returns the open forward, waiting while a client is opening. Fails immediately while the forward is closed. */
33
+ readonly current: Effect.Effect<OpenForward, GatewayForwardClosed>;
34
+ /**
35
+ * Replaces SSH client `generation`, or ends the pause after it closed, and waits for the next
36
+ * client. If another request already replaced it, waits for the current forward.
37
+ */
38
+ readonly reopen: (generation: number) => Effect.Effect<OpenForward, GatewayForwardClosed>;
39
+ /** Records an unanswered request sent at `sentAt`, and returns when the first unanswered request since the last response was sent. */
40
+ readonly unanswered: (sentAt: DateTime.Utc) => Effect.Effect<DateTime.Utc>;
41
+ /** Records a response from the admin API, so the next unanswered request starts a new outage. */
42
+ readonly answered: Effect.Effect<void>;
43
+ }
44
+ /**
45
+ * Opens a forward to the admin API at `adminPort` on `gateway`, using the SSH client and
46
+ * environment of `shell`, and keeps it open until the caller's scope closes. Each client
47
+ * listens on a kernel-picked port of 127.0.0.1, so it never conflicts with an operator's
48
+ * forward.
49
+ */
50
+ export declare const gatewayForwardFor: (spawner: {
51
+ spawn(command: ChildProcess.Command): Effect.Effect<ChildProcessSpawner.ChildProcessHandle, import("effect/PlatformError").PlatformError, import("effect/Scope").Scope>;
52
+ exitCode(command: ChildProcess.Command): Effect.Effect<ChildProcessSpawner.ExitCode, import("effect/PlatformError").PlatformError>;
53
+ streamString(command: ChildProcess.Command, options?: {
54
+ readonly includeStderr?: boolean | undefined;
55
+ }): Stream.Stream<string, import("effect/PlatformError").PlatformError>;
56
+ streamLines(command: ChildProcess.Command, options?: {
57
+ readonly includeStderr?: boolean | undefined;
58
+ }): Stream.Stream<string, import("effect/PlatformError").PlatformError>;
59
+ lines(command: ChildProcess.Command, options?: {
60
+ readonly includeStderr?: boolean | undefined;
61
+ }): Effect.Effect<Array<string>, import("effect/PlatformError").PlatformError>;
62
+ string(command: ChildProcess.Command, options?: {
63
+ readonly includeStderr?: boolean | undefined;
64
+ }): Effect.Effect<string, import("effect/PlatformError").PlatformError>;
65
+ }, shell: Pick<RemoteShell, "ssh" | "sshEnvironment">, gateway: string & import("effect/Brand").Brand<"SshDestination">, adminPort: number & import("effect/Brand").Brand<"GatewayAdminPort">) => Effect.Effect<{
66
+ gateway: string & import("effect/Brand").Brand<"SshDestination">;
67
+ command: string;
68
+ current: Effect.Effect<OpenForward, GatewayForwardClosed, never>;
69
+ reopen: (generation: number) => Effect.Effect<OpenForward, GatewayForwardClosed, never>;
70
+ unanswered: (sentAt: DateTime.Utc) => Effect.Effect<DateTime.Utc, never, never>;
71
+ answered: Effect.Effect<void, never, never>;
72
+ }, never, import("effect/Scope").Scope>;
73
+ export {};
74
+ //# sourceMappingURL=forward.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward.d.ts","sourceRoot":"","sources":["../../../src/ssh/gateway/forward.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAyB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,QAAQ,EAAY,MAAM,EAA8B,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAcxE,MAAM,WAAW,WAAW;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;CACnB;;;;;;;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,yBAOxC;CAAG;AAEL;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kHAAkH;IAClH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;IACnE;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;IAC1F,sIAAsI;IACtI,QAAQ,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC3E,iGAAiG;IACjG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CACxC;AA6DD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;yBAyQA,QAAQ,CAAC,GAAG;;uCAWxC,CAAC"}
@@ -0,0 +1,247 @@
1
+ import { createServer } from "node:net";
2
+ import { SshDestination } from "@usine/core/worker";
3
+ import { DateTime, Deferred, Effect, Fiber, Option, Ref, Result, Schema, Stream } from "effect";
4
+ import { ChildProcess } from "effect/process";
5
+ /** The line the SSH client prints through `LocalCommand` when its local port is listening. */
6
+ const OPEN_LINE = "usine-gateway-forward-open";
7
+ /** The time an SSH client has to connect and listen before it's replaced. */
8
+ const OPEN_TIMEOUT = "30 seconds";
9
+ /** The time a request waits for the forward to open. */
10
+ const OPEN_WAIT = "1 minute";
11
+ /** The maximum pause between SSH clients, in milliseconds. The pause doubles from one second after each failure. */
12
+ const MAX_PAUSE_MILLIS = 60_000;
13
+ /**
14
+ * The error for a closed admin API forward, whose SSH client didn't open or exited. `since` is
15
+ * when the Gateway became unreachable, and `reason` is the SSH client's error.
16
+ */
17
+ export class GatewayForwardClosed extends Schema.TaggedError("@usine/executor/GatewayForwardClosed")("GatewayForwardClosed", {
18
+ gateway: SshDestination,
19
+ generation: Schema.Int,
20
+ since: Schema.DateTimeUtc,
21
+ reason: Schema.NonEmptyString,
22
+ }) {
23
+ }
24
+ const notOpened = (reason) => ({ _tag: "NotOpened", reason });
25
+ const exited = (reason) => ({ _tag: "Exited", reason });
26
+ const REPLACED = { _tag: "Replaced" };
27
+ const ListenerAddress = Schema.Struct({ port: Schema.Int });
28
+ /** Returns a free port on 127.0.0.1 picked by the kernel, released for the SSH client to listen on. */
29
+ const freeLocalPort = Effect.callback((resume) => {
30
+ const server = createServer();
31
+ server.once("error", (cause) => resume(Effect.fail(cause)));
32
+ server.listen(0, "127.0.0.1", () => {
33
+ const address = Schema.decodeUnknownOption(ListenerAddress)(server.address());
34
+ server.close(() => resume(Option.match(address, {
35
+ onNone: () => Effect.fail(new Error("the listener reported no port")),
36
+ onSome: ({ port }) => Effect.succeed(port),
37
+ })));
38
+ });
39
+ });
40
+ /** Returns the SSH client's stderr on one line, or its exit code if stderr is empty. */
41
+ const complaint = (stderr, exitCode) => {
42
+ const lines = stderr
43
+ .split("\n")
44
+ .map((line) => line.trim())
45
+ .filter((line) => line !== "");
46
+ return lines.length > 0 ? lines.join("; ") : `ssh exited with code ${String(exitCode)}`;
47
+ };
48
+ const pauseAfter = (failures) => Math.min(1000 * 2 ** Math.max(failures - 1, 0), MAX_PAUSE_MILLIS);
49
+ /**
50
+ * Opens a forward to the admin API at `adminPort` on `gateway`, using the SSH client and
51
+ * environment of `shell`, and keeps it open until the caller's scope closes. Each client
52
+ * listens on a kernel-picked port of 127.0.0.1, so it never conflicts with an operator's
53
+ * forward.
54
+ */
55
+ export const gatewayForwardFor = Effect.fn("SshExecutor.gatewayForwardFor")(function* (spawner, shell, gateway, adminPort) {
56
+ const fields = { gateway, adminPort };
57
+ yield* Effect.annotateCurrentSpan(fields);
58
+ const forwardSpec = (port) => `127.0.0.1:${String(port)}:127.0.0.1:${String(adminPort)}`;
59
+ const command = [
60
+ shell.ssh.command,
61
+ ...shell.ssh.args,
62
+ "-N",
63
+ "-o",
64
+ "ExitOnForwardFailure=yes",
65
+ "-L",
66
+ forwardSpec(adminPort),
67
+ gateway,
68
+ ].join(" ");
69
+ const argumentsFor = (port) => [
70
+ ...shell.ssh.args,
71
+ "-N",
72
+ // LocalCommand runs when all listeners are up, ExitOnForwardFailure exits if a listener fails, and ControlPath=none avoids the operator's shared connections.
73
+ "-o",
74
+ "ExitOnForwardFailure=yes",
75
+ "-o",
76
+ "ControlPath=none",
77
+ "-o",
78
+ "PermitLocalCommand=yes",
79
+ "-o",
80
+ `LocalCommand=echo ${OPEN_LINE}`,
81
+ "-L",
82
+ forwardSpec(port),
83
+ gateway,
84
+ ];
85
+ const generationNumbered = (number) => Effect.all({
86
+ opened: Deferred.make(),
87
+ replace: Deferred.make(),
88
+ next: Deferred.make(),
89
+ }).pipe(Effect.map((signals) => ({ number, ...signals })));
90
+ const first = yield* generationNumbered(1);
91
+ const state = yield* Ref.make({ _tag: "Opening", generation: first });
92
+ const runClient = Effect.fn("SshExecutor.runGatewayForward")(function* (generation, closedSince) {
93
+ yield* Effect.annotateCurrentSpan({ ...fields, generation: generation.number });
94
+ const picked = yield* Effect.result(freeLocalPort);
95
+ if (Result.isFailure(picked))
96
+ return notOpened(`no free port on 127.0.0.1: ${picked.failure.message}`);
97
+ const port = picked.success;
98
+ yield* Effect.annotateCurrentSpan({ port });
99
+ yield* Effect.logDebug("Starting an SSH client for the forward to the Gateway's admin API", {
100
+ ...fields,
101
+ generation: generation.number,
102
+ port,
103
+ });
104
+ return yield* Effect.scoped(Effect.gen(function* () {
105
+ const handle = yield* spawner.spawn(ChildProcess.make(shell.ssh.command, argumentsFor(port), {
106
+ env: { ...shell.sshEnvironment },
107
+ extendEnv: false,
108
+ detached: false,
109
+ forceKillAfter: "5 seconds",
110
+ stdin: "ignore",
111
+ }));
112
+ const stderr = yield* handle.stderr.pipe(Stream.decodeText(), Stream.mkString, Effect.forkScoped);
113
+ const reported = (exitCode) => Fiber.join(stderr).pipe(Effect.map((text) => complaint(text, exitCode)));
114
+ const listening = yield* handle.stdout.pipe(Stream.decodeText(), Stream.splitLines, Stream.filter((line) => line.trim() === OPEN_LINE), Stream.runHead, Effect.timeoutOption(OPEN_TIMEOUT));
115
+ if (Option.isNone(listening))
116
+ return notOpened(`ssh didn't open the forward within ${OPEN_TIMEOUT}`);
117
+ if (Option.isNone(listening.value))
118
+ return notOpened(yield* reported(Number(yield* handle.exitCode)));
119
+ const open = {
120
+ generation: generation.number,
121
+ url: new URL(`http://127.0.0.1:${String(port)}`),
122
+ };
123
+ yield* Ref.set(state, { _tag: "Open", generation, open });
124
+ yield* Deferred.succeed(generation.opened, open);
125
+ yield* Option.match(closedSince, {
126
+ onNone: () => Effect.logInfo("Opened the forward to the Gateway's admin API", {
127
+ ...fields,
128
+ generation: generation.number,
129
+ port,
130
+ }),
131
+ onSome: (since) => Effect.logInfo("Reopened the forward to the Gateway's admin API", {
132
+ ...fields,
133
+ generation: generation.number,
134
+ port,
135
+ closedSince: DateTime.formatIso(since),
136
+ }),
137
+ });
138
+ // Sets the state to closed first, so no request uses a port the client no longer listens on.
139
+ const closedNow = (reason) => DateTime.now.pipe(Effect.flatMap((since) => Ref.set(state, {
140
+ _tag: "Closed",
141
+ generation,
142
+ closed: new GatewayForwardClosed({
143
+ gateway,
144
+ generation: generation.number,
145
+ since,
146
+ reason,
147
+ }),
148
+ })));
149
+ return yield* Effect.raceFirst(handle.exitCode.pipe(Effect.flatMap((code) => reported(Number(code))), Effect.catch((error) => Effect.succeed(`can't read the SSH client's exit code: ${error.message}`)), Effect.tap(closedNow), Effect.map(exited)), Deferred.await(generation.replace).pipe(Effect.andThen(closedNow("replaced after a request got no response")), Effect.as(REPLACED)));
150
+ })).pipe(Effect.catch((error) => Effect.succeed(notOpened(`can't run ${shell.ssh.command}: ${error.message}`))));
151
+ });
152
+ // Runs until the caller's scope closes, with one span per SSH client.
153
+ const supervise = Effect.gen(function* () {
154
+ let generation = first;
155
+ let failures = 0;
156
+ let closedSince = Option.none();
157
+ while (true) {
158
+ const end = yield* runClient(generation, closedSince);
159
+ const now = yield* DateTime.now;
160
+ if (end._tag === "Replaced") {
161
+ failures = 0;
162
+ closedSince = Option.some(now);
163
+ yield* Effect.logInfo("Replacing the forward to the Gateway's admin API, which a request found unanswered", { ...fields, generation: generation.number });
164
+ }
165
+ else {
166
+ failures = end._tag === "Exited" ? 1 : failures + 1;
167
+ const since = end._tag === "Exited" ? now : Option.getOrElse(closedSince, () => now);
168
+ closedSince = Option.some(since);
169
+ const pause = pauseAfter(failures);
170
+ const closed = new GatewayForwardClosed({
171
+ gateway,
172
+ generation: generation.number,
173
+ since,
174
+ reason: end.reason,
175
+ });
176
+ yield* Ref.set(state, { _tag: "Closed", generation, closed });
177
+ yield* Deferred.fail(generation.opened, closed);
178
+ yield* Effect.logWarning(end._tag === "Exited"
179
+ ? "The forward to the Gateway's admin API exited; reopening it after a pause"
180
+ : "Could not open the forward to the Gateway's admin API; trying again after a pause", {
181
+ ...fields,
182
+ generation: generation.number,
183
+ closedSince: DateTime.formatIso(since),
184
+ failures,
185
+ pauseSeconds: pause / 1000,
186
+ reason: end.reason,
187
+ });
188
+ const trigger = yield* Effect.raceFirst(Effect.sleep(pause).pipe(Effect.as("pause elapsed")), Deferred.await(generation.replace).pipe(Effect.as("request")));
189
+ yield* Effect.logInfo("Reopening the forward to the Gateway's admin API", {
190
+ ...fields,
191
+ generation: generation.number + 1,
192
+ closedSince: DateTime.formatIso(since),
193
+ trigger,
194
+ });
195
+ }
196
+ const next = yield* generationNumbered(generation.number + 1);
197
+ yield* Ref.set(state, { _tag: "Opening", generation: next });
198
+ yield* Deferred.succeed(generation.next, next);
199
+ generation = next;
200
+ }
201
+ });
202
+ yield* supervise.pipe(Effect.onInterrupt(() => Effect.logInfo("Closed the forward to the Gateway's admin API", fields)), Effect.forkScoped);
203
+ const notOpenedWithin = (generation) => DateTime.now.pipe(Effect.flatMap((since) => new GatewayForwardClosed({
204
+ gateway,
205
+ generation,
206
+ since,
207
+ reason: `the forward didn't open within ${OPEN_WAIT}`,
208
+ })));
209
+ const current = Effect.gen(function* () {
210
+ const now = yield* Ref.get(state);
211
+ switch (now._tag) {
212
+ case "Open":
213
+ return now.open;
214
+ case "Closed":
215
+ return yield* now.closed;
216
+ case "Opening":
217
+ return yield* Deferred.await(now.generation.opened).pipe(Effect.timeoutOrElse({
218
+ duration: OPEN_WAIT,
219
+ orElse: () => notOpenedWithin(now.generation.number),
220
+ }));
221
+ }
222
+ });
223
+ const reopen = Effect.fn("SshExecutor.reopenGatewayForward")(function* (generation) {
224
+ yield* Effect.annotateCurrentSpan({ ...fields, generation });
225
+ const now = yield* Ref.get(state);
226
+ if (now.generation.number !== generation) {
227
+ yield* Effect.logDebug("Another request replaced the forward to the Gateway's admin API already", { ...fields, generation, current: now.generation.number });
228
+ return yield* current;
229
+ }
230
+ yield* Deferred.succeed(now.generation.replace, undefined);
231
+ return yield* Deferred.await(now.generation.next).pipe(Effect.flatMap((next) => Deferred.await(next.opened)), Effect.timeoutOrElse({
232
+ duration: OPEN_WAIT,
233
+ orElse: () => notOpenedWithin(generation + 1),
234
+ }));
235
+ });
236
+ const silentSince = yield* Ref.make(Option.none());
237
+ const unanswered = (sentAt) => Ref.modify(silentSince, (since) => {
238
+ const first = Option.match(since, {
239
+ onNone: () => sentAt,
240
+ onSome: (earlier) => DateTime.min(earlier, sentAt),
241
+ });
242
+ return [first, Option.some(first)];
243
+ });
244
+ const answered = Ref.set(silentSince, Option.none());
245
+ return { gateway, command, current, reopen, unanswered, answered };
246
+ });
247
+ //# sourceMappingURL=forward.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward.js","sourceRoot":"","sources":["../../../src/ssh/gateway/forward.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,OAAO,EAAyB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChG,OAAO,EAAE,YAAY,EAA4B,MAAM,gBAAgB,CAAC;AAExE,8FAA8F;AAC9F,MAAM,SAAS,GAAG,4BAA4B,CAAC;AAE/C,6EAA6E;AAC7E,MAAM,YAAY,GAAG,YAAY,CAAC;AAElC,wDAAwD;AACxD,MAAM,SAAS,GAAG,UAAU,CAAC;AAE7B,oHAAoH;AACpH,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAQhC;;;GAGG;AACH,MAAM,OAAO,oBAAqB,SAAQ,MAAM,CAAC,WAAW,CAC1D,sCAAsC,CACvC,CAAC,sBAAsB,EAAE;IACxB,OAAO,EAAE,cAAc;IACvB,UAAU,EAAE,MAAM,CAAC,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC,WAAW;IACzB,MAAM,EAAE,MAAM,CAAC,cAAc;CAC9B,CAAC;CAAG;AAiDL,MAAM,SAAS,GAAG,CAAC,MAAc,EAAa,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;AACjF,MAAM,MAAM,GAAG,CAAC,MAAc,EAAa,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AAC3E,MAAM,QAAQ,GAAc,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAEjD,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;AAE5D,uGAAuG;AACvG,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAgB,CAAC,MAAM,EAAE,EAAE;IAC9D,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;QACjC,MAAM,OAAO,GAAG,MAAM,CAAC,mBAAmB,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9E,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAChB,MAAM,CACJ,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE;YACpB,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACrE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;SAC3C,CAAC,CACH,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wFAAwF;AACxF,MAAM,SAAS,GAAG,CAAC,MAAc,EAAE,QAAgB,EAAU,EAAE;IAC7D,MAAM,KAAK,GAAG,MAAM;SACjB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IACjC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,wBAAwB,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC1F,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,QAAgB,EAAU,EAAE,CAC9C,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,EAAE,CAAC,+BAA+B,CAAC,CAAC,QAAQ,CAAC,EACnF,OAA2D,EAC3D,KAAkD,EAClD,OAAuB,EACvB,SAA2B;IAE3B,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IACtC,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,aAAa,MAAM,CAAC,IAAI,CAAC,cAAc,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;IACjG,MAAM,OAAO,GAAG;QACd,KAAK,CAAC,GAAG,CAAC,OAAO;QACjB,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI;QACjB,IAAI;QACJ,IAAI;QACJ,0BAA0B;QAC1B,IAAI;QACJ,WAAW,CAAC,SAAS,CAAC;QACtB,OAAO;KACR,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC;QACrC,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI;QACjB,IAAI;QACJ,8JAA8J;QAC9J,IAAI;QACJ,0BAA0B;QAC1B,IAAI;QACJ,kBAAkB;QAClB,IAAI;QACJ,wBAAwB;QACxB,IAAI;QACJ,qBAAqB,SAAS,EAAE;QAChC,IAAI;QACJ,WAAW,CAAC,IAAI,CAAC;QACjB,OAAO;KACR,CAAC;IAEF,MAAM,kBAAkB,GAAG,CAAC,MAAc,EAAE,EAAE,CAC5C,MAAM,CAAC,GAAG,CAAC;QACT,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAqC;QAC1D,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAQ;QAC9B,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAc;KAClC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAc,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAe,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IAEpF,MAAM,SAAS,GAAG,MAAM,CAAC,EAAE,CAAC,+BAA+B,CAAC,CAAC,QAAQ,CAAC,EACpE,UAAsB,EACtB,WAAwC;QAExC,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QAChF,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QACnD,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;YAC1B,OAAO,SAAS,CAAC,8BAA8B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3E,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC;QAC5B,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,mEAAmE,EAAE;YAC1F,GAAG,MAAM;YACT,UAAU,EAAE,UAAU,CAAC,MAAM;YAC7B,IAAI;SACL,CAAC,CAAC;QACH,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CACzB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CACjC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE;gBACvD,GAAG,EAAE,EAAE,GAAG,KAAK,CAAC,cAAc,EAAE;gBAChC,SAAS,EAAE,KAAK;gBAChB,QAAQ,EAAE,KAAK;gBACf,cAAc,EAAE,WAAW;gBAC3B,KAAK,EAAE,QAAQ;aAChB,CAAC,CACH,CAAC;YACF,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CACtC,MAAM,CAAC,UAAU,EAAE,EACnB,MAAM,CAAC,QAAQ,EACf,MAAM,CAAC,UAAU,CAClB,CAAC;YACF,MAAM,QAAQ,GAAG,CAAC,QAAgB,EAAE,EAAE,CACpC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC3E,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CACzC,MAAM,CAAC,UAAU,EAAE,EACnB,MAAM,CAAC,UAAU,EACjB,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC,EAClD,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CACnC,CAAC;YACF,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;gBAC1B,OAAO,SAAS,CAAC,sCAAsC,YAAY,EAAE,CAAC,CAAC;YACzE,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;gBAChC,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAEpE,MAAM,IAAI,GAAgB;gBACxB,UAAU,EAAE,UAAU,CAAC,MAAM;gBAC7B,GAAG,EAAE,IAAI,GAAG,CAAC,oBAAoB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;aACjD,CAAC;YACF,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACjD,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE;gBAC/B,MAAM,EAAE,GAAG,EAAE,CACX,MAAM,CAAC,OAAO,CAAC,+CAA+C,EAAE;oBAC9D,GAAG,MAAM;oBACT,UAAU,EAAE,UAAU,CAAC,MAAM;oBAC7B,IAAI;iBACL,CAAC;gBACJ,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAChB,MAAM,CAAC,OAAO,CAAC,iDAAiD,EAAE;oBAChE,GAAG,MAAM;oBACT,UAAU,EAAE,UAAU,CAAC,MAAM;oBAC7B,IAAI;oBACJ,WAAW,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC;iBACvC,CAAC;aACL,CAAC,CAAC;YACH,6FAA6F;YAC7F,MAAM,SAAS,GAAG,CAAC,MAAc,EAAE,EAAE,CACnC,QAAQ,CAAC,GAAG,CAAC,IAAI,CACf,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CACvB,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE;gBACb,IAAI,EAAE,QAAQ;gBACd,UAAU;gBACV,MAAM,EAAE,IAAI,oBAAoB,CAAC;oBAC/B,OAAO;oBACP,UAAU,EAAE,UAAU,CAAC,MAAM;oBAC7B,KAAK;oBACL,MAAM;iBACP,CAAC;aACH,CAAC,CACH,CACF,CAAC;YACJ,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAC5B,MAAM,CAAC,QAAQ,CAAC,IAAI,CAClB,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAChD,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CACrB,MAAM,CAAC,OAAO,CAAC,0CAA0C,KAAK,CAAC,OAAO,EAAE,CAAC,CAC1E,EACD,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EACrB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CACnB,EACD,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CACrC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,0CAA0C,CAAC,CAAC,EACrE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CACpB,CACF,CAAC;QACJ,CAAC,CAAC,CACH,CAAC,IAAI,CACJ,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CACrB,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,aAAa,KAAK,CAAC,GAAG,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAC9E,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,sEAAsE;IACtE,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACpC,IAAI,UAAU,GAAG,KAAK,CAAC;QACvB,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,IAAI,WAAW,GAAG,MAAM,CAAC,IAAI,EAAgB,CAAC;QAC9C,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;YACtD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;YAChC,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBAC5B,QAAQ,GAAG,CAAC,CAAC;gBACb,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC/B,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CACnB,oFAAoF,EACpF,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,MAAM,EAAE,CAC7C,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,QAAQ,GAAG,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC;gBACpD,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;gBACrF,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACjC,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;gBACnC,MAAM,MAAM,GAAG,IAAI,oBAAoB,CAAC;oBACtC,OAAO;oBACP,UAAU,EAAE,UAAU,CAAC,MAAM;oBAC7B,KAAK;oBACL,MAAM,EAAE,GAAG,CAAC,MAAM;iBACnB,CAAC,CAAC;gBACH,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC9D,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAChD,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CACtB,GAAG,CAAC,IAAI,KAAK,QAAQ;oBACnB,CAAC,CAAC,2EAA2E;oBAC7E,CAAC,CAAC,mFAAmF,EACvF;oBACE,GAAG,MAAM;oBACT,UAAU,EAAE,UAAU,CAAC,MAAM;oBAC7B,WAAW,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC;oBACtC,QAAQ;oBACR,YAAY,EAAE,KAAK,GAAG,IAAI;oBAC1B,MAAM,EAAE,GAAG,CAAC,MAAM;iBACnB,CACF,CAAC;gBACF,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CACrC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,EACpD,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAC9D,CAAC;gBACF,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,kDAAkD,EAAE;oBACxE,GAAG,MAAM;oBACT,UAAU,EAAE,UAAU,CAAC,MAAM,GAAG,CAAC;oBACjC,WAAW,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC;oBACtC,OAAO;iBACR,CAAC,CAAC;YACL,CAAC;YACD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC9D,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7D,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC/C,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CACnB,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,CACtB,MAAM,CAAC,OAAO,CAAC,+CAA+C,EAAE,MAAM,CAAC,CACxE,EACD,MAAM,CAAC,UAAU,CAClB,CAAC;IAEF,MAAM,eAAe,GAAG,CAAC,UAAkB,EAAE,EAAE,CAC7C,QAAQ,CAAC,GAAG,CAAC,IAAI,CACf,MAAM,CAAC,OAAO,CACZ,CAAC,KAAK,EAAE,EAAE,CACR,IAAI,oBAAoB,CAAC;QACvB,OAAO;QACP,UAAU;QACV,KAAK;QACL,MAAM,EAAE,kCAAkC,SAAS,EAAE;KACtD,CAAC,CACL,CACF,CAAC;IAEJ,MAAM,OAAO,GAA8B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAClC,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;YACjB,KAAK,MAAM;gBACT,OAAO,GAAG,CAAC,IAAI,CAAC;YAClB,KAAK,QAAQ;gBACX,OAAO,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;YAC3B,KAAK,SAAS;gBACZ,OAAO,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CACtD,MAAM,CAAC,aAAa,CAAC;oBACnB,QAAQ,EAAE,SAAS;oBACnB,MAAM,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;iBACrD,CAAC,CACH,CAAC;QACN,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,kCAAkC,CAAC,CAAC,QAAQ,CAAC,EAAE,UAAkB;QACxF,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;QAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACzC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CACpB,yEAAyE,EACzE,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,CAC1D,CAAC;YACF,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC;QACxB,CAAC;QACD,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC3D,OAAO,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CACpD,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EACrD,MAAM,CAAC,aAAa,CAAC;YACnB,QAAQ,EAAE,SAAS;YACnB,MAAM,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,UAAU,GAAG,CAAC,CAAC;SAC9C,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAgB,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,CAAC,MAAoB,EAAE,EAAE,CAC1C,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,EAAE;QAChC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE;YAChC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM;YACpB,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC;SACnD,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAU,CAAC;IAC9C,CAAC,CAAC,CAAC;IACL,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAErD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAA2B,CAAC;AAC9F,CAAC,CAAC,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { type RemoteShell } from "@usine/agents/child-host";
2
+ import type { Harness } from "@usine/core/agent/brain";
3
+ import { HarnessReady, HarnessUnavailableError } from "@usine/core/agent/capability";
4
+ import type { GatewayCredentialKind, WorkerName } from "@usine/core/worker";
5
+ import { Effect, Option } from "effect";
6
+ import { ExecutorOperationError, WorkerCapabilityError } from "../../executor.ts";
7
+ import type { HarnessProbe } from "../../harness-preflight.ts";
8
+ import { type RemoteCommands } from "../../remote/commands.ts";
9
+ import type { MachineGateway, SshMachine } from "../model.ts";
10
+ /**
11
+ * Returns the credential kind the Gateway injects for `harness`, as recorded by the last
12
+ * preflight: Anthropic's for Claude Code, OpenAI's for Codex. Pi uses its own login.
13
+ */
14
+ export declare const gatewayCredentialFor: (gateway: MachineGateway, harness: Harness) => Effect.Effect<Option.Option<GatewayCredentialKind>>;
15
+ /**
16
+ * Verifies the Gateway's admin API for an SSH Worker without attaching anything: the forward
17
+ * opens and the admin API accepts the admin token. Records the Gateway's credential kind for
18
+ * each provider in the Worker's `llmProviders`. Needs no Worker machine.
19
+ */
20
+ export declare const verifyGatewayAdmin: (worker: string & import("effect/Brand").Brand<"WorkerName">, gateway: MachineGateway) => Effect.Effect<void, WorkerCapabilityError, never>;
21
+ /**
22
+ * Verifies the Gateway in front of an SSH Worker without attaching anything: the admin API
23
+ * checks of `verifyGatewayAdmin`, then the machine checks of `verifyGatewayFromMachine`.
24
+ */
25
+ export declare const verifyGateway: (commands: RemoteCommands, worker: WorkerName, machine: SshMachine, gateway: MachineGateway) => Effect.Effect<undefined, ExecutorOperationError | WorkerCapabilityError, never>;
26
+ /**
27
+ * Checks only the binary of a harness whose credential the machine doesn't keep, the Gateway's
28
+ * or an API key in the Attempt's environment, named by `credential` in the log. Codex's version
29
+ * must also be supported.
30
+ */
31
+ export declare const harnessBinaryProbe: (commands: RemoteCommands, host: string, shell: RemoteShell, harness: "claude" | "codex", credential: string) => Effect.Effect<HarnessReady | HarnessUnavailableError, ExecutorOperationError, never>;
32
+ /**
33
+ * The harnesses that can use the operator's login on the machine or the Gateway's credential
34
+ * for their provider, with the hosts each login calls through the forward proxy, which
35
+ * `infra/hetzner/gateway/egress-allowlist` lists, and the API host that an API key in the
36
+ * Attempt's environment calls.
37
+ */
38
+ declare const LOGINS: {
39
+ readonly claude: {
40
+ readonly label: "Claude Code";
41
+ readonly provider: "Anthropic";
42
+ readonly gatewayProvider: "anthropic";
43
+ readonly hosts: readonly ["api.anthropic.com", "platform.claude.com"];
44
+ readonly apiHost: "api.anthropic.com";
45
+ };
46
+ readonly codex: {
47
+ readonly label: "Codex";
48
+ readonly provider: "OpenAI";
49
+ readonly gatewayProvider: "openai";
50
+ readonly hosts: readonly ["chatgpt.com", "auth.openai.com"];
51
+ readonly apiHost: "api.openai.com";
52
+ };
53
+ };
54
+ /** A harness that can use a login, the Gateway's credential, or an API key. */
55
+ export type LoginHarness = keyof typeof LOGINS;
56
+ /**
57
+ * Probes a brain on a Worker behind a Gateway (ADR 0027). If the Gateway has the credential of
58
+ * the harness's provider, checks only the harness binary. Otherwise, Claude Code and Codex use
59
+ * the operator's login (ADR 0026), or an API key in the Attempt's environment if `apiKeyed`
60
+ * returns `true` for the harness: `probe` checks the login or the binary, then the machine must
61
+ * connect to the login's hosts, or the provider's API host, through the forward proxy. Pi goes
62
+ * to `probe`, which rejects it on any remote machine. The checks run through `shell`, on the
63
+ * machine or in a container on it.
64
+ */
65
+ export declare const gatewayHarnessProbe: (commands: RemoteCommands, machine: SshMachine, gateway: MachineGateway, probe: HarnessProbe, shell: RemoteShell, apiKeyed: (harness: LoginHarness) => boolean) => HarnessProbe;
66
+ export {};
67
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../../src/ssh/gateway/preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAe,MAAM,0BAA0B,CAAC;AAEzE,OAAO,KAAK,EAAa,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACrF,OAAO,KAAK,EAAE,qBAAqB,EAAsB,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAiB,MAAM,QAAQ,CAAC;AAEvD,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,0BAA0B,CAAC;AAC7E,OAAO,KAAK,EAAsB,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA0HlF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,YACtB,cAAc,WACd,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CASpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,6IA+B7B,CAAC;AAqEH;;;GAGG;AACH,eAAO,MAAM,aAAa,aACd,cAAc,UAChB,UAAU,WACT,UAAU,WACV,cAAc,oFAItB,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,uMA0B7B,CAAC;AAEH;;;;;GAKG;AACH,QAAA,MAAM,MAAM;;iBAER,KAAK,EAAE,aAAa;iBACpB,QAAQ,EAAE,WAAW;iBACrB,eAAe,EAAE,WAAW;iBAC5B,KAAK;iBACL,OAAO,EAAE,mBAAmB;;;iBAG5B,KAAK,EAAE,OAAO;iBACd,QAAQ,EAAE,QAAQ;iBAClB,eAAe,EAAE,QAAQ;iBACzB,KAAK;iBACL,OAAO,EAAE,gBAAgB;;CAa5B,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,MAAM,CAAC;AAqB/C;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,aACpB,cAAc,WACf,UAAU,WACV,cAAc,SAChB,YAAY,SACZ,WAAW,YACR,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,KAC3C,YAyFF,CAAC"}