@openagt/servers 0.0.0-stage → 0.0.1

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.
@@ -0,0 +1,438 @@
1
+ import { Button as e, Checkbox as t, Input as n, SettingsSection as r, defineModule as i, useModuleHost as a, usePolled as o } from "@openagt/dashboard/module";
2
+ import { useEffect as s, useRef as c, useState as l } from "react";
3
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
4
+ //#region dashboard/servers.ts
5
+ var f = (e) => typeof e == "string" && e ? e : void 0, p = [
6
+ "waiting",
7
+ "running",
8
+ "done",
9
+ "failed"
10
+ ];
11
+ function m(e) {
12
+ let { there: t, working: n, asking: r, wanted: i, account: a, since: o, ends: s, said: c } = e && typeof e == "object" ? e : {}, [l, u, d, p] = [
13
+ f(a),
14
+ f(o),
15
+ f(s),
16
+ f(c)
17
+ ];
18
+ return {
19
+ there: t === !0,
20
+ working: n === !0,
21
+ ...r === !0 ? { asking: !0 } : {},
22
+ ...i === !0 ? { wanted: !0 } : {},
23
+ ...l ? { account: l } : {},
24
+ ...u ? { since: u } : {},
25
+ ...d ? { ends: d } : {},
26
+ ...p ? { said: p } : {}
27
+ };
28
+ }
29
+ function h(e) {
30
+ if (!e || typeof e != "object") return;
31
+ let { servers: t } = e;
32
+ if (!Array.isArray(t)) return;
33
+ let n = [];
34
+ for (let e of t) {
35
+ if (!e || typeof e != "object") continue;
36
+ let { address: t, url: r, user: i, fingerprint: a, working: o, adding: s, steps: c, logins: l } = e, { github: u, claude: d } = l && typeof l == "object" ? l : {}, [h, g, _, v] = [
37
+ f(t),
38
+ f(r),
39
+ f(i),
40
+ f(a)
41
+ ];
42
+ if (!h || !g || !_ || !Array.isArray(c)) continue;
43
+ let y = [];
44
+ for (let e of c) {
45
+ if (!e || typeof e != "object") continue;
46
+ let { id: t, label: n, state: r, said: i } = e, [a, o, s] = [
47
+ f(t),
48
+ f(n),
49
+ f(i)
50
+ ];
51
+ a && o && p.includes(r) && y.push({
52
+ id: a,
53
+ label: o,
54
+ state: r,
55
+ ...s ? { said: s } : {}
56
+ });
57
+ }
58
+ n.push({
59
+ address: h,
60
+ url: g,
61
+ user: _,
62
+ ...v ? { fingerprint: v } : {},
63
+ working: o === !0,
64
+ adding: s === !0,
65
+ steps: y,
66
+ logins: {
67
+ github: m(u),
68
+ claude: m(d)
69
+ }
70
+ });
71
+ }
72
+ return n;
73
+ }
74
+ function g(e) {
75
+ if (!e || typeof e != "object") return;
76
+ let { url: t, expires: n } = e, [r, i] = [f(t), f(n)];
77
+ return r && i ? {
78
+ url: r,
79
+ expires: i
80
+ } : void 0;
81
+ }
82
+ async function _(e, t, n, r) {
83
+ let i = {
84
+ ok: !1,
85
+ error: "no project to run the command in"
86
+ };
87
+ for (let [a, o] of t.entries()) {
88
+ let t = await e.runCommand(o.id, n).catch((e) => ({
89
+ ok: !1,
90
+ error: e instanceof Error ? e.message : String(e)
91
+ }));
92
+ if (t.ok) {
93
+ let e = r(t.output);
94
+ return e === void 0 ? {
95
+ ok: !1,
96
+ error: "the command answered something else than expected"
97
+ } : {
98
+ ok: !0,
99
+ value: e
100
+ };
101
+ }
102
+ a === 0 && (i = t);
103
+ }
104
+ return i;
105
+ }
106
+ var v = (e, t) => _(e, t, ["status"], h), y = (e, t, n, r, i, a = !1) => _(e, t, [
107
+ "connect",
108
+ n,
109
+ r,
110
+ i,
111
+ ...a ? ["github"] : []
112
+ ], h);
113
+ function b(e) {
114
+ if (!e || typeof e != "object") return;
115
+ let { account: t, problem: n } = e, [r, i] = [f(t), f(n)];
116
+ return r ? { account: r } : i ? { problem: i } : void 0;
117
+ }
118
+ var ee = (e, t) => _(e, t, ["github"], b), x = (e, t, n, r) => _(e, t, [
119
+ "login",
120
+ n,
121
+ r
122
+ ], h), S = (e, t, n, r) => _(e, t, [
123
+ "logout",
124
+ n,
125
+ r
126
+ ], h), C = (e, t, n) => _(e, t, ["link", n], g), w = (e, t, n) => _(e, t, ["added", n], h), T = (e, t, n) => _(e, t, ["forget", n], h), E = void 0, te = void 0, ne = void 0, D = (e) => e === void 0 ? "" : new Date(e).toLocaleDateString(void 0, {
127
+ day: "numeric",
128
+ month: "short",
129
+ year: "numeric"
130
+ });
131
+ function O({ label: e, words: t, said: n, children: r }) {
132
+ return /* @__PURE__ */ d("li", {
133
+ "aria-label": e,
134
+ className: "space-y-1",
135
+ children: [/* @__PURE__ */ d("div", {
136
+ className: "flex flex-wrap items-center justify-between gap-2",
137
+ children: [/* @__PURE__ */ u("span", { children: t }), /* @__PURE__ */ u("div", {
138
+ className: "flex gap-2",
139
+ children: r
140
+ })]
141
+ }), n !== void 0 && /* @__PURE__ */ u("pre", {
142
+ role: "alert",
143
+ className: "overflow-x-auto whitespace-pre-wrap rounded-md border border-border bg-background p-2 font-mono text-foreground",
144
+ children: n
145
+ })]
146
+ });
147
+ }
148
+ var k = {
149
+ waiting: "○",
150
+ running: "…",
151
+ done: "✓",
152
+ failed: "✕"
153
+ }, re = {
154
+ waiting: "not started",
155
+ running: "running",
156
+ done: "done",
157
+ failed: "failed"
158
+ }, A = (e, t) => Object.fromEntries(Object.entries(e).filter(([e]) => e !== t));
159
+ function j(e, t) {
160
+ let n = (e) => {
161
+ let t = /^(\d+)\.(\d+)\.(\d+)/.exec(e);
162
+ return t ? t.slice(1).map(Number) : void 0;
163
+ }, [r, i] = [n(e), n(t)];
164
+ if (!r || !i) return !1;
165
+ for (let e = 0; e < 3; e++) if (r[e] !== i[e]) return r[e] < i[e];
166
+ return !1;
167
+ }
168
+ function M({ projects: i, everyMs: f = 3e3 }) {
169
+ let p = a(), m = i.map((e) => e.id).join(","), { value: h, reload: g } = o(() => v(p, i), E, f, [m]), { value: _, reload: b } = o(() => p.machines(), te, Math.max(f, 1e4), []), { value: M } = o(() => ee(p, i), ne, Math.max(f, 5 * 6e4), [m]), N = M?.ok && "account" in M.value ? M.value.account : void 0, P = M === void 0 || N !== void 0 ? void 0 : M.ok ? "problem" in M.value ? M.value.problem : void 0 : M.error, [F, I] = l(""), [L, R] = l("root"), [z, B] = l(!1), [V, H] = l(!1), [U, W] = l(), [G, K] = l({}), q = c(/* @__PURE__ */ new Set()), J = h?.ok ? h.value : [], Y = (e, t = _) => t?.machines.find((t) => t.url === e.url), X = (e) => !e.working && e.steps.every((e) => e.state === "done"), Z = async (e) => {
170
+ H(!0), W(void 0);
171
+ let t = await e();
172
+ return t.ok || W(t.error), await g().catch(() => {}), H(!1), t.ok;
173
+ }, Q = async (e, t, n = !1) => !_ || _.version === "unknown" ? (W("This computer could not say which OpenAgent it runs."), !1) : (K((t) => A(t, e)), Z(() => y(p, i, e, t, _.version, n))), $ = async (e) => {
174
+ if (q.current.has(e.address)) return;
175
+ q.current.add(e.address), K((t) => A(t, e.address));
176
+ let t = (t) => K((n) => ({
177
+ ...n,
178
+ [e.address]: t
179
+ }));
180
+ try {
181
+ if (Y(e, await p.machines())?.version === void 0) {
182
+ let n = await C(p, i, e.address), r = n.ok ? await p.addMachine(n.value.url, e.address) : n;
183
+ if (!r.ok) return t(r.error);
184
+ }
185
+ let n = await w(p, i, e.address);
186
+ if (!n.ok) return t(n.error);
187
+ await Promise.all([g(), b()]).catch(() => {});
188
+ } catch (e) {
189
+ t(e instanceof Error ? e.message : String(e));
190
+ } finally {
191
+ q.current.delete(e.address);
192
+ }
193
+ };
194
+ return s(() => {
195
+ if (_) for (let e of J) e.adding && X(e) && G[e.address] === void 0 && $(e);
196
+ }), i.length === 0 ? null : /* @__PURE__ */ d(r, {
197
+ title: "Servers",
198
+ description: "Connect your VPS: give its address and a user name, and OpenAgent installs itself there over SSH, with this computer's own SSH keys. The server then runs OpenAgent by itself, and is one of your machines.",
199
+ children: [
200
+ /* @__PURE__ */ d("form", {
201
+ onSubmit: (e) => {
202
+ e.preventDefault(), !(!F.trim() || !L.trim()) && Q(F.trim(), L.trim(), z && N !== void 0).then((e) => e && I(""));
203
+ },
204
+ className: "flex flex-wrap items-end gap-2 py-3",
205
+ children: [
206
+ /* @__PURE__ */ d("label", {
207
+ className: "min-w-40 flex-1 space-y-1 text-xs text-muted-foreground",
208
+ children: [/* @__PURE__ */ u("span", { children: "The server's address" }), /* @__PURE__ */ u(n, {
209
+ "aria-label": "The server's address",
210
+ placeholder: "203.0.113.5",
211
+ value: F,
212
+ onChange: (e) => I(e.target.value)
213
+ })]
214
+ }),
215
+ /* @__PURE__ */ d("label", {
216
+ className: "w-32 space-y-1 text-xs text-muted-foreground",
217
+ children: [/* @__PURE__ */ u("span", { children: "User name" }), /* @__PURE__ */ u(n, {
218
+ "aria-label": "User name",
219
+ value: L,
220
+ onChange: (e) => R(e.target.value)
221
+ })]
222
+ }),
223
+ /* @__PURE__ */ u(e, {
224
+ size: "sm",
225
+ type: "submit",
226
+ disabled: V || !_ || !F.trim() || !L.trim(),
227
+ children: "Connect"
228
+ })
229
+ ]
230
+ }),
231
+ /* @__PURE__ */ d("div", {
232
+ className: "space-y-2 pb-3",
233
+ children: [/* @__PURE__ */ d("label", {
234
+ className: "flex items-start gap-2 text-xs",
235
+ children: [/* @__PURE__ */ u(t, {
236
+ checked: z && N !== void 0,
237
+ onCheckedChange: (e) => B(e === !0),
238
+ disabled: V || N === void 0,
239
+ className: "mt-0.5",
240
+ "aria-label": "Copy my GitHub login to the server"
241
+ }), /* @__PURE__ */ d("span", { children: [
242
+ N === void 0 ? "Copy my GitHub login to the server." : `Copy my GitHub login (${N}) to the server.`,
243
+ " ",
244
+ /* @__PURE__ */ u("span", {
245
+ className: "text-muted-foreground",
246
+ children: P ?? "It can do there all it can do here, and every agent that runs on the server works with it. It goes there at the install's end, to a server met for the first time too, whose key is taken as it is."
247
+ })
248
+ ] })]
249
+ }), /* @__PURE__ */ u("p", {
250
+ className: "text-xs text-muted-foreground",
251
+ children: "The user is root, or one that becomes root without a password. It is only used to install, to update and to give the server its logins: what runs on the server afterwards runs as a user of its own, openagent."
252
+ })]
253
+ }),
254
+ J.length > 0 && /* @__PURE__ */ u("ul", {
255
+ "aria-label": "Servers",
256
+ className: "divide-y divide-border",
257
+ children: J.map((t) => {
258
+ let n = Y(t), r = !t.working && t.steps.some((e) => e.state === "failed"), a = n?.version !== void 0 && _ !== void 0 && j(n.version, _.version), o = G[t.address], { github: s, claude: c } = t.logins;
259
+ return /* @__PURE__ */ d("li", {
260
+ "aria-label": t.address,
261
+ className: "space-y-2 py-3",
262
+ children: [
263
+ /* @__PURE__ */ d("div", {
264
+ className: "flex flex-wrap items-center justify-between gap-2",
265
+ children: [/* @__PURE__ */ d("div", {
266
+ className: "min-w-0",
267
+ children: [/* @__PURE__ */ u("p", {
268
+ className: "truncate text-sm",
269
+ children: t.address
270
+ }), /* @__PURE__ */ u("p", {
271
+ className: "text-xs text-muted-foreground",
272
+ children: t.working ? "OpenAgent is being installed." : n?.version === void 0 ? n ? "Does not answer right now." : X(t) && !t.adding ? "Installed, and not among your machines." : `Logs in as ${t.user}.` : a ? `OpenAgent ${n.version}: behind this computer, which runs ${_.version}.` : `OpenAgent ${n.version}.`
273
+ })]
274
+ }), /* @__PURE__ */ d("div", {
275
+ className: "flex gap-2",
276
+ children: [
277
+ a && !t.working && !r && /* @__PURE__ */ u(e, {
278
+ size: "sm",
279
+ disabled: V,
280
+ "aria-label": `Update ${t.address}`,
281
+ onClick: () => void Q(t.address, t.user),
282
+ children: "Update"
283
+ }),
284
+ r && /* @__PURE__ */ u(e, {
285
+ size: "sm",
286
+ disabled: V,
287
+ "aria-label": `Try ${t.address} again`,
288
+ onClick: () => void Q(t.address, t.user, s.wanted === !0),
289
+ children: "Try again"
290
+ }),
291
+ X(t) && n?.version === void 0 && (o !== void 0 || !t.adding) && /* @__PURE__ */ u(e, {
292
+ size: "sm",
293
+ variant: "outline",
294
+ disabled: V,
295
+ "aria-label": `Add ${t.address} to Machines`,
296
+ onClick: () => void $(t),
297
+ children: n ? "Add again" : "Add to Machines"
298
+ }),
299
+ !t.working && /* @__PURE__ */ u(e, {
300
+ size: "sm",
301
+ variant: "outline",
302
+ disabled: V,
303
+ "aria-label": `Remove ${t.address}`,
304
+ title: "Takes the server off this list. Nothing is changed on the server: its logins stay there until you remove them.",
305
+ onClick: () => void Z(() => T(p, i, t.address)),
306
+ children: "Remove"
307
+ })
308
+ ]
309
+ })]
310
+ }),
311
+ /* @__PURE__ */ d("ol", {
312
+ "aria-label": `The install on ${t.address}`,
313
+ className: "space-y-1 text-xs",
314
+ children: [
315
+ t.steps.map((e) => /* @__PURE__ */ d("li", {
316
+ "aria-label": `${e.label}: ${re[e.state]}`,
317
+ children: [
318
+ /* @__PURE__ */ d("span", {
319
+ className: e.state === "failed" ? "text-danger" : e.state === "waiting" ? "text-muted-foreground" : void 0,
320
+ children: [/* @__PURE__ */ u("span", {
321
+ "aria-hidden": !0,
322
+ className: "inline-block w-4",
323
+ children: k[e.state]
324
+ }), e.label]
325
+ }),
326
+ e.id === "login" && e.state === "done" && t.fingerprint !== void 0 && /* @__PURE__ */ u("span", {
327
+ className: "ml-2 font-mono text-muted-foreground",
328
+ children: `The server's key: ${t.fingerprint}`
329
+ }),
330
+ e.said !== void 0 && e.state === "failed" && /* @__PURE__ */ u("pre", {
331
+ role: "alert",
332
+ className: "mt-1 ml-4 overflow-x-auto whitespace-pre-wrap rounded-md border border-border bg-background p-2 font-mono text-foreground",
333
+ children: e.said
334
+ })
335
+ ]
336
+ }, e.id)),
337
+ t.logins.github.working && /* @__PURE__ */ d("li", {
338
+ "aria-label": "Your GitHub login: not copied yet",
339
+ className: "text-muted-foreground",
340
+ children: [/* @__PURE__ */ u("span", {
341
+ "aria-hidden": !0,
342
+ className: "inline-block w-4",
343
+ children: k[t.steps.every((e) => e.state === "done") ? "running" : "waiting"]
344
+ }), "Your GitHub login"]
345
+ }),
346
+ X(t) && /* @__PURE__ */ d("li", {
347
+ "aria-label": `Among your machines: ${n ? "done" : o === void 0 ? t.adding ? "running" : "not started" : "failed"}`,
348
+ children: [/* @__PURE__ */ d("span", {
349
+ className: n ? void 0 : o === void 0 ? "text-muted-foreground" : "text-danger",
350
+ children: [/* @__PURE__ */ u("span", {
351
+ "aria-hidden": !0,
352
+ className: "inline-block w-4",
353
+ children: k[n ? "done" : o === void 0 ? t.adding ? "running" : "waiting" : "failed"]
354
+ }), "Among your machines"]
355
+ }), o !== void 0 && !n && /* @__PURE__ */ u("pre", {
356
+ role: "alert",
357
+ className: "mt-1 ml-4 overflow-x-auto whitespace-pre-wrap rounded-md border border-border bg-background p-2 font-mono text-foreground",
358
+ children: o
359
+ })]
360
+ })
361
+ ]
362
+ }),
363
+ X(t) && /* @__PURE__ */ d("ul", {
364
+ "aria-label": `The logins on ${t.address}`,
365
+ className: "space-y-2 border-t border-border pt-2 text-xs",
366
+ children: [/* @__PURE__ */ d(O, {
367
+ label: "GitHub",
368
+ words: s.working ? "GitHub: one moment, the server is being told." : s.there ? `GitHub: logged in as ${s.account ?? "you"} since ${D(s.since)}.` : s.said === void 0 ? "GitHub: no login on the server." : "GitHub: the last try did not go through.",
369
+ said: s.working ? void 0 : s.said,
370
+ children: [/* @__PURE__ */ u(e, {
371
+ size: "sm",
372
+ variant: "outline",
373
+ disabled: V || s.working || N === void 0,
374
+ "aria-label": `Copy my GitHub login to ${t.address}`,
375
+ title: N === void 0 ? P : `Copies the login of ${N} from this computer to the server, over SSH.`,
376
+ onClick: () => void Z(() => x(p, i, t.address, "github")),
377
+ children: s.there ? "Copy again" : "Copy my login"
378
+ }), (s.there || s.said !== void 0) && !s.working && /* @__PURE__ */ u(e, {
379
+ size: "sm",
380
+ variant: "outline",
381
+ disabled: V,
382
+ "aria-label": `Remove the GitHub login from ${t.address}`,
383
+ title: "Deletes the login on the server. The login itself stays good: it is this computer's own. To end it everywhere, take it back on github.com and log this computer's gh in again.",
384
+ onClick: () => void Z(() => S(p, i, t.address, "github")),
385
+ children: "Remove"
386
+ })]
387
+ }), /* @__PURE__ */ d(O, {
388
+ label: "Claude",
389
+ words: c.asking ? "Claude: say yes in the page this computer's browser opened." : c.working ? "Claude: one moment, the server is being told." : c.there ? `Claude: logged in since ${D(c.since)}, good until about ${D(c.ends)}.` : c.said === void 0 ? "Claude: not logged in on the server." : "Claude: the last try did not go through.",
390
+ said: c.working ? void 0 : c.said,
391
+ children: [/* @__PURE__ */ u(e, {
392
+ size: "sm",
393
+ variant: c.there || c.working ? "outline" : "default",
394
+ disabled: V || c.working && c.asking !== !0,
395
+ "aria-label": `Log in to Claude for ${t.address}`,
396
+ title: "Opens this computer's browser, where you say yes once. The login is made for this server alone and lasts a year; this computer's own Claude login is not touched.",
397
+ onClick: () => void Z(() => x(p, i, t.address, "claude")),
398
+ children: c.asking ? "Start over" : c.there ? "Log in again" : "Log in to Claude"
399
+ }), (c.there || c.said !== void 0) && !c.working && /* @__PURE__ */ u(e, {
400
+ size: "sm",
401
+ variant: "outline",
402
+ disabled: V,
403
+ "aria-label": `Remove the Claude login from ${t.address}`,
404
+ title: "Deletes the login on the server and starts OpenAgent there again without it. An agent at work there keeps it until it ends. The login itself stays good until its year is over.",
405
+ onClick: () => void Z(() => S(p, i, t.address, "claude")),
406
+ children: "Remove"
407
+ })]
408
+ })]
409
+ })
410
+ ]
411
+ }, t.address);
412
+ })
413
+ }),
414
+ h !== void 0 && !h.ok && /* @__PURE__ */ u("p", {
415
+ role: "alert",
416
+ className: "py-3 text-xs text-danger",
417
+ children: `The servers could not be read: ${h.error}`
418
+ }),
419
+ U !== void 0 && /* @__PURE__ */ u("p", {
420
+ role: "alert",
421
+ className: "py-3 text-xs text-danger",
422
+ children: U
423
+ })
424
+ ]
425
+ });
426
+ }
427
+ //#endregion
428
+ //#region dashboard/index.tsx
429
+ var N = i({
430
+ settings: [{
431
+ id: "servers",
432
+ order: 20,
433
+ Section: M
434
+ }],
435
+ stylesheet: "dashboard.css"
436
+ });
437
+ //#endregion
438
+ export { N as default };
@@ -0,0 +1,43 @@
1
+ import { type Exec } from './ssh.js';
2
+ import { type Server } from './store.js';
3
+ /**
4
+ * The install itself: the steps, one after the other, on one server, each written to the file
5
+ * as it starts and as it ends, so the page shows where the install is. It stops at the first
6
+ * step that fails, with what the server said. Run as a process of its own: an install takes
7
+ * minutes, and a command a page runs has seconds.
8
+ *
9
+ * Once the steps are done, the GitHub login is copied when the person asked for that.
10
+ */
11
+ export interface InstallOptions {
12
+ env: NodeJS.ProcessEnv;
13
+ address: string;
14
+ /** The OpenAgent the server gets. */
15
+ version: string;
16
+ exec?: Exec;
17
+ /** Stops the install where it is. */
18
+ signal?: AbortSignal;
19
+ /** How often the file is told the install still runs. */
20
+ beatMs?: number;
21
+ }
22
+ /** Refused: the server's install already runs, in another process. */
23
+ export declare class AlreadyWorking extends Error {
24
+ constructor(address: string);
25
+ }
26
+ export declare function install(opts: InstallOptions): Promise<void>;
27
+ export type Link = {
28
+ ok: true;
29
+ url: string;
30
+ expires: string;
31
+ } | {
32
+ ok: false;
33
+ error: string;
34
+ };
35
+ /**
36
+ * Ask the server's door for a link a new machine gets in with: good once, for five minutes.
37
+ * Only a link to that very server is one: what a server answers is not trusted to say where
38
+ * this computer goes to be let in.
39
+ */
40
+ export declare function askLink(server: Pick<Server, 'address' | 'user'>, opts?: {
41
+ exec?: Exec;
42
+ }): Promise<Link>;
43
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AACA,OAAO,EAAsC,KAAK,IAAI,EAAgB,MAAM,UAAU,CAAA;AAEtF,OAAO,EAA2C,KAAK,MAAM,EAAkB,MAAM,YAAY,CAAA;AAEjG;;;;;;;GAOG;AAEH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,qCAAqC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,sEAAsE;AACtE,qBAAa,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAO,EAAE,MAAM,EAE1B;CACF;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDjE;AAED,MAAM,MAAM,IAAI,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAK5F;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC,EAAE,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAejH"}
@@ -0,0 +1,101 @@
1
+ import { giveGithub, noteGithub } from './logins.js';
2
+ import { lastLines, openSession, withAdvice } from './ssh.js';
3
+ import { SERVER_USER, STEPS, linkScript } from './steps.js';
4
+ import { BEAT_MS, changeServer, doorUrl, working } from './store.js';
5
+ /** Refused: the server's install already runs, in another process. */
6
+ export class AlreadyWorking extends Error {
7
+ constructor(address) {
8
+ super(`OpenAgent is being installed on ${address} right now`);
9
+ }
10
+ }
11
+ export async function install(opts) {
12
+ const { env, address, version } = opts;
13
+ const change = (apply) => changeServer(env, address, apply);
14
+ const mine = (kept) => kept.worker?.pid === process.pid;
15
+ const beat = () => change(kept => void (mine(kept) && (kept.worker = { pid: process.pid, seen: new Date().toISOString() })));
16
+ const set = (id, state, said) => change(kept => {
17
+ const step = kept.steps.find(candidate => candidate.id === id);
18
+ step.state = state;
19
+ if (said)
20
+ step.said = said;
21
+ else
22
+ delete step.said;
23
+ });
24
+ // The install is this process's from here on; the command that started it held it under its own name until now.
25
+ const server = await change(kept => {
26
+ if (working(kept) && !mine(kept) && kept.worker.pid !== process.ppid)
27
+ throw new AlreadyWorking(address);
28
+ kept.worker = { pid: process.pid, seen: new Date().toISOString() };
29
+ return { address: kept.address, user: kept.user };
30
+ });
31
+ if (!server)
32
+ throw new Error(`no server at ${address}`);
33
+ const beating = setInterval(() => void beat().catch(() => { }), opts.beatMs ?? BEAT_MS);
34
+ let session;
35
+ try {
36
+ session = await openSession(server, { ...(opts.exec ? { exec: opts.exec } : {}), ...(opts.signal ? { signal: opts.signal } : {}) });
37
+ for (const step of STEPS) {
38
+ await set(step.id, 'running');
39
+ const login = step.id === 'login';
40
+ if (login) {
41
+ const met = await session.meet();
42
+ if (!met.ok)
43
+ return void (await set(step.id, 'failed', withAdvice(server, met.said || 'The server did not answer.')));
44
+ if (met.fingerprint)
45
+ await change(kept => void (kept.fingerprint = met.fingerprint));
46
+ }
47
+ const said = await session.run(step.as, step.script({ address, version }), step.timeoutMs);
48
+ if (said.code !== 0) {
49
+ const words = lastLines(said) || 'The step ended without a word.';
50
+ return void (await set(step.id, 'failed', login ? withAdvice(server, words) : words));
51
+ }
52
+ await set(step.id, 'done');
53
+ }
54
+ // The person's tick. A copy that fails is told on the login's own line: the server is installed all the same.
55
+ if (await change(kept => kept.github?.wanted === true)) {
56
+ const given = await giveGithub(session, opts.exec);
57
+ await change(kept => noteGithub(kept, given));
58
+ }
59
+ }
60
+ finally {
61
+ clearInterval(beating);
62
+ await change(kept => {
63
+ if (!mine(kept))
64
+ return;
65
+ // A step still running here is one this process did not see the end of.
66
+ for (const step of kept.steps)
67
+ if (step.state === 'running')
68
+ Object.assign(step, { state: 'failed', said: 'The install was stopped.' });
69
+ delete kept.worker;
70
+ }).catch(() => { });
71
+ await session?.close();
72
+ }
73
+ }
74
+ /** How long the server gets to answer with a link: less than a page's command has, so the line is always hung up. */
75
+ const LINK_TIMEOUT_MS = 20_000;
76
+ /**
77
+ * Ask the server's door for a link a new machine gets in with: good once, for five minutes.
78
+ * Only a link to that very server is one: what a server answers is not trusted to say where
79
+ * this computer goes to be let in.
80
+ */
81
+ export async function askLink(server, opts = {}) {
82
+ const session = await openSession(server, opts);
83
+ try {
84
+ const said = await session.run(SERVER_USER, linkScript(), LINK_TIMEOUT_MS);
85
+ if (said.code !== 0)
86
+ return { ok: false, error: withAdvice(server, lastLines(said) || 'The server did not answer.') };
87
+ try {
88
+ const { url, expires } = JSON.parse(said.out.trim().split('\n').at(-1) ?? '');
89
+ if (typeof url === 'string' && typeof expires === 'string' && new URL(url).origin === doorUrl(server.address))
90
+ return { ok: true, url, expires };
91
+ }
92
+ catch {
93
+ // Told below, as any answer that is no link.
94
+ }
95
+ return { ok: false, error: `The server answered something else than a link to its own door at ${doorUrl(server.address)}.` };
96
+ }
97
+ finally {
98
+ await session.close();
99
+ }
100
+ }
101
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}