@openagt/servers 0.0.0-stage → 0.0.2
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 +21 -0
- package/bin/servers +11 -0
- package/dist/cli.d.ts +68 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +317 -0
- package/dist/cli.js.map +1 -0
- package/dist/dashboard/dashboard.css +3 -0
- package/dist/dashboard/dashboard.js +503 -0
- package/dist/install.d.ts +43 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +103 -0
- package/dist/install.js.map +1 -0
- package/dist/logins.d.ts +105 -0
- package/dist/logins.d.ts.map +1 -0
- package/dist/logins.js +244 -0
- package/dist/logins.js.map +1 -0
- package/dist/ssh.d.ts +63 -0
- package/dist/ssh.d.ts.map +1 -0
- package/dist/ssh.js +113 -0
- package/dist/ssh.js.map +1 -0
- package/dist/steps.d.ts +82 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +427 -0
- package/dist/steps.js.map +1 -0
- package/dist/store.d.ts +126 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +248 -0
- package/dist/store.js.map +1 -0
- package/package.json +47 -3
- package/README.md +0 -3
package/dist/logins.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type Exec, type Session } from './ssh.js';
|
|
2
|
+
import { type LoginKind, type Server } from './store.js';
|
|
3
|
+
/**
|
|
4
|
+
* The two logins a server works with, and how each gets there: over SSH, as the install's own
|
|
5
|
+
* scripts do, never through the browser and never through the server's door.
|
|
6
|
+
*
|
|
7
|
+
* GitHub's is this computer's own, read from `gh` and copied. Claude's is made new for the
|
|
8
|
+
* server: `claude setup-token` asks the person in this computer's browser and shows a login
|
|
9
|
+
* that lasts a year; this computer's own Claude login is never read. Neither is written
|
|
10
|
+
* anywhere on this computer, and what a server said is kept without them.
|
|
11
|
+
*
|
|
12
|
+
* Taking a login off the server deletes it there. It does not end the login: GitHub's is still
|
|
13
|
+
* this computer's own, and Claude's is good until its year is over.
|
|
14
|
+
*/
|
|
15
|
+
export type Failed = {
|
|
16
|
+
ok: false;
|
|
17
|
+
error: string;
|
|
18
|
+
};
|
|
19
|
+
/** The GitHub account `gh` is logged in to on this computer: whose login a copy would be. */
|
|
20
|
+
export declare function ownGithubAccount(run?: Exec): Promise<{
|
|
21
|
+
ok: true;
|
|
22
|
+
account: string;
|
|
23
|
+
} | Failed>;
|
|
24
|
+
/** `text` without the login it may repeat. */
|
|
25
|
+
export declare function scrubbed(text: string, login: string): string;
|
|
26
|
+
export type GithubGiven = {
|
|
27
|
+
ok: true;
|
|
28
|
+
account: string;
|
|
29
|
+
} | (Failed & {
|
|
30
|
+
/** The server was sent the login. Its script leaves no login behind when it fails; a line that broke half-way leaves that unknown. */
|
|
31
|
+
reached: boolean;
|
|
32
|
+
});
|
|
33
|
+
/** Copy this computer's GitHub login to the server on a line that is already open. */
|
|
34
|
+
export declare function giveGithub(session: Session, run?: Exec): Promise<GithubGiven>;
|
|
35
|
+
/**
|
|
36
|
+
* Have the server get a GitHub login of its own, on a line that is already open: the server asks
|
|
37
|
+
* GitHub, GitHub answers with a code, `show` puts the code before the person, and the server
|
|
38
|
+
* waits until the person has typed it at GitHub and said yes there. No login passes through this
|
|
39
|
+
* computer.
|
|
40
|
+
*/
|
|
41
|
+
export declare function askGithub(session: Session, show: (code: string) => Promise<unknown>): Promise<GithubGiven>;
|
|
42
|
+
/** Write down how a copy of the GitHub login went. */
|
|
43
|
+
export declare function noteGithub(kept: Server, given: GithubGiven): void;
|
|
44
|
+
/** The login among what Claude Code showed, once all of it is there. */
|
|
45
|
+
export declare function mintedIn(shown: string): string | undefined;
|
|
46
|
+
/** Why Claude Code ended without a login, from what it showed: its last lines, never a part of a login. */
|
|
47
|
+
export declare function notMinted(shown: string): string;
|
|
48
|
+
/** Which process the lent terminal's first program said it is, once its whole line is there: half a number names another process. */
|
|
49
|
+
export declare function startedAs(shown: string): number | undefined;
|
|
50
|
+
export interface MintOptions {
|
|
51
|
+
signal?: AbortSignal;
|
|
52
|
+
/** How long the person has to say yes in the browser. */
|
|
53
|
+
timeoutMs?: number;
|
|
54
|
+
/** The command that makes the login; Claude Code's own when unsaid. */
|
|
55
|
+
command?: string;
|
|
56
|
+
}
|
|
57
|
+
/** How a Claude login is made for a server. */
|
|
58
|
+
export type Mint = (opts?: MintOptions) => Promise<{
|
|
59
|
+
ok: true;
|
|
60
|
+
login: string;
|
|
61
|
+
} | Failed>;
|
|
62
|
+
/**
|
|
63
|
+
* Make a Claude login that lasts a year, with Claude Code's own `claude setup-token`: it opens
|
|
64
|
+
* this computer's browser, the person says yes there, and it shows the login once. It keeps
|
|
65
|
+
* nothing, and neither does this.
|
|
66
|
+
*
|
|
67
|
+
* Claude Code draws on a terminal's screen and this process has none, so the system's `script`
|
|
68
|
+
* lends it one, wide enough that the login stays on one line. Its input is a pipe nothing ever
|
|
69
|
+
* writes to. `script` gives its program a group of its own, so that program first says which
|
|
70
|
+
* process it is: at the end both groups are ended, whether or not Claude Code heeds a terminal
|
|
71
|
+
* that went away.
|
|
72
|
+
*/
|
|
73
|
+
export declare const mintClaude: Mint;
|
|
74
|
+
/** One change to one login of a server. */
|
|
75
|
+
export interface Job {
|
|
76
|
+
kind: LoginKind;
|
|
77
|
+
/** Take the login off the server, where otherwise the server is given one. */
|
|
78
|
+
remove: boolean;
|
|
79
|
+
/** GitHub: the server gets a login of its own, with a code a person types at GitHub, where otherwise this computer's is copied. */
|
|
80
|
+
own?: boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface LoginOptions extends Job {
|
|
83
|
+
env: NodeJS.ProcessEnv;
|
|
84
|
+
address: string;
|
|
85
|
+
exec?: Exec;
|
|
86
|
+
mint?: Mint;
|
|
87
|
+
/** Stops waiting for the person. What was already on its way to the server arrives all the same. */
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
/** How often the file is told the work still goes on. */
|
|
90
|
+
beatMs?: number;
|
|
91
|
+
}
|
|
92
|
+
/** Refused: the login is already being changed, in another process. */
|
|
93
|
+
export declare class AlreadyAtWork extends Error {
|
|
94
|
+
constructor(address: string, kind: LoginKind);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Change one login of a server: give it one, or take it off. Run as a process of its own: a
|
|
98
|
+
* person asked in the browser takes a while, a server may too, and a command a page runs has
|
|
99
|
+
* seconds. How it ended is written to the file, for the page.
|
|
100
|
+
*
|
|
101
|
+
* Once the server is being told, it is told to the end: a line cut half-way would leave on the
|
|
102
|
+
* server what this computer does not know of.
|
|
103
|
+
*/
|
|
104
|
+
export declare function changeLogin(opts: LoginOptions): Promise<void>;
|
|
105
|
+
//# sourceMappingURL=logins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logins.d.ts","sourceRoot":"","sources":["../src/logins.ts"],"names":[],"mappings":"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"}
|
package/dist/logins.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { exec as ownExec, lastLines, openSession, withAdvice } from './ssh.js';
|
|
3
|
+
import { GITHUB_CODE_TIMEOUT_MS, LOGIN_TIMEOUT_MS, claudeLoginScript, claudeLogoutScript, githubCodeScript, githubLoginScript, githubLogoutScript, githubWaitScript } from './steps.js';
|
|
4
|
+
import { BEAT_MS, atWork, changeServer, heartbeat, isAccount, isGithubCode } from './store.js';
|
|
5
|
+
/** How long one of this computer's own commands gets to answer. */
|
|
6
|
+
const OWN_TIMEOUT_MS = 15_000;
|
|
7
|
+
/** Why this computer's `gh` gave nothing, in words: never its own, which could hold what it was asked for. */
|
|
8
|
+
function noGh(said, otherwise) {
|
|
9
|
+
return { ok: false, error: `There is no GitHub login to copy from this computer. ${/could not start/.test(said.err) ? '`gh` is not on this computer.' : otherwise}` };
|
|
10
|
+
}
|
|
11
|
+
/** The GitHub account `gh` is logged in to on this computer: whose login a copy would be. */
|
|
12
|
+
export async function ownGithubAccount(run = ownExec) {
|
|
13
|
+
const said = await run('gh', ['api', '--hostname', 'github.com', 'user', '--jq', '.login'], '', { timeoutMs: OWN_TIMEOUT_MS });
|
|
14
|
+
const account = said.out.trim();
|
|
15
|
+
return said.code === 0 && isAccount(account) ? { ok: true, account } : noGh(said, '`gh` is not logged in to github.com here, or GitHub does not answer: see `gh auth status`.');
|
|
16
|
+
}
|
|
17
|
+
/** This computer's GitHub login, as `gh` keeps it. */
|
|
18
|
+
async function ownGithubLogin(run) {
|
|
19
|
+
const said = await run('gh', ['auth', 'token', '--hostname', 'github.com'], '', { timeoutMs: OWN_TIMEOUT_MS });
|
|
20
|
+
const login = said.out.trim();
|
|
21
|
+
// What it reads as is what GitHub hands out; anything else is not sent anywhere.
|
|
22
|
+
return said.code === 0 && /^[A-Za-z0-9_]{20,255}$/.test(login) ? { ok: true, login } : noGh(said, '`gh` is not logged in to github.com here: run `gh auth login`.');
|
|
23
|
+
}
|
|
24
|
+
/** `text` without the login it may repeat. */
|
|
25
|
+
export function scrubbed(text, login) {
|
|
26
|
+
return login ? text.replaceAll(login, '•••') : text;
|
|
27
|
+
}
|
|
28
|
+
/** Run a login's script on the server. What comes back never holds the login. */
|
|
29
|
+
async function send(session, { as, script }, login = '', timeoutMs = LOGIN_TIMEOUT_MS) {
|
|
30
|
+
const said = await session.run(as, script, timeoutMs);
|
|
31
|
+
const clean = { out: scrubbed(said.out, login), err: scrubbed(said.err, login) };
|
|
32
|
+
return said.code === 0 ? { ok: true, out: clean.out } : { ok: false, error: lastLines(clean) || 'The server did not answer.' };
|
|
33
|
+
}
|
|
34
|
+
/** Copy this computer's GitHub login to the server on a line that is already open. */
|
|
35
|
+
export async function giveGithub(session, run = ownExec) {
|
|
36
|
+
const own = await ownGithubLogin(run);
|
|
37
|
+
if (!own.ok)
|
|
38
|
+
return { ...own, reached: false };
|
|
39
|
+
const sent = await send(session, githubLoginScript(own.login), own.login);
|
|
40
|
+
if (!sent.ok)
|
|
41
|
+
return { ...sent, reached: true };
|
|
42
|
+
const account = sent.out.trim().split('\n').at(-1)?.trim();
|
|
43
|
+
return isAccount(account) ? { ok: true, account } : { ok: false, error: 'The server took the login and did not say whose it is.', reached: true };
|
|
44
|
+
}
|
|
45
|
+
/** What the server answers once its `gh` waits for a code: the process, the file it writes to, and the code. */
|
|
46
|
+
const ASKING = /^(\d{1,10}) (\/[A-Za-z0-9._/-]{1,200}) (\S+)$/;
|
|
47
|
+
/**
|
|
48
|
+
* Have the server get a GitHub login of its own, on a line that is already open: the server asks
|
|
49
|
+
* GitHub, GitHub answers with a code, `show` puts the code before the person, and the server
|
|
50
|
+
* waits until the person has typed it at GitHub and said yes there. No login passes through this
|
|
51
|
+
* computer.
|
|
52
|
+
*/
|
|
53
|
+
export async function askGithub(session, show) {
|
|
54
|
+
const asked = await send(session, githubCodeScript());
|
|
55
|
+
if (!asked.ok)
|
|
56
|
+
return { ...asked, reached: true };
|
|
57
|
+
const [, pid, log, code] = ASKING.exec(asked.out.trim().split('\n').at(-1) ?? '') ?? [];
|
|
58
|
+
if (pid === undefined || log === undefined || !isGithubCode(code))
|
|
59
|
+
return { ok: false, error: 'The server answered something else than a code from GitHub.', reached: true };
|
|
60
|
+
await show(code);
|
|
61
|
+
const sent = await send(session, githubWaitScript(pid, log), '', GITHUB_CODE_TIMEOUT_MS);
|
|
62
|
+
if (!sent.ok)
|
|
63
|
+
return { ...sent, reached: true };
|
|
64
|
+
const account = sent.out.trim().split('\n').at(-1)?.trim();
|
|
65
|
+
return isAccount(account) ? { ok: true, account } : { ok: false, error: 'The server has a login and did not say whose it is.', reached: true };
|
|
66
|
+
}
|
|
67
|
+
/** What is known of a login beyond a try: since when it is there, and whose it is. */
|
|
68
|
+
const known = (login) => ({ ...(login?.since ? { since: login.since } : {}), ...(login?.account ? { account: login.account } : {}) });
|
|
69
|
+
/** Write down how a copy of the GitHub login went. */
|
|
70
|
+
export function noteGithub(kept, given) {
|
|
71
|
+
if (given.ok)
|
|
72
|
+
kept.github = { since: new Date().toISOString(), account: given.account };
|
|
73
|
+
// One that was sent and did not arrive replaced what the server had: this computer no longer counts on a login there.
|
|
74
|
+
else
|
|
75
|
+
kept.github = { ...(given.reached ? {} : known(kept.github)), failed: given.error };
|
|
76
|
+
}
|
|
77
|
+
/** What Claude Code writes before and after the login it made, and what such a login reads as. The gap between two words is of any width. */
|
|
78
|
+
const MINTED = /Your\s+OAuth\s+token[^\n]*:\s*(sk-ant-[A-Za-z0-9_-]{40,400})\s+Store\s+this\s+token\s+securely/;
|
|
79
|
+
/** What a terminal is told beside words: colours, where the cursor goes, a link's address. */
|
|
80
|
+
const TERMINAL = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;
|
|
81
|
+
/** A colour: the one thing a terminal is told that leaves the words on its two sides together. */
|
|
82
|
+
const COLOUR = /\x1b\[[0-9;:]*m/g;
|
|
83
|
+
/**
|
|
84
|
+
* The words Claude Code showed, without what only a terminal reads. A colour goes; anything else
|
|
85
|
+
* a terminal is told is a gap: Claude Code's screen puts a word at its column where a plain line
|
|
86
|
+
* holds a space, and a login the cursor moved in is not one login.
|
|
87
|
+
*/
|
|
88
|
+
const words = (shown) => shown.replace(COLOUR, '').replace(TERMINAL, ' ').replaceAll('\r', '\n');
|
|
89
|
+
/** The login among what Claude Code showed, once all of it is there. */
|
|
90
|
+
export function mintedIn(shown) {
|
|
91
|
+
return MINTED.exec(words(shown))?.[1];
|
|
92
|
+
}
|
|
93
|
+
/** Why Claude Code ended without a login, from what it showed: its last lines, never a part of a login. */
|
|
94
|
+
export function notMinted(shown) {
|
|
95
|
+
const text = words(shown);
|
|
96
|
+
// However the screen was drawn: where a login or its heading shows, none of the screen is told.
|
|
97
|
+
if (/YourOAuthtoken|sk-ant-/.test(text.replace(/\s/g, '')))
|
|
98
|
+
return 'Claude Code made a login, and this OpenAgent could not read it off what Claude Code showed.';
|
|
99
|
+
const lines = [...new Set(text.split('\n').map(line => line.trim().replace(/\s+/g, ' ')).filter(line => line && !STARTED_AS.test(line)))];
|
|
100
|
+
return ['Claude Code ended without a login.', ...lines.slice(-5)].join('\n');
|
|
101
|
+
}
|
|
102
|
+
/** Which process the lent terminal's first program said it is, once its whole line is there: half a number names another process. */
|
|
103
|
+
export function startedAs(shown) {
|
|
104
|
+
return Number(/^started as (\d+)\n/m.exec(words(shown))?.[1]) || undefined;
|
|
105
|
+
}
|
|
106
|
+
const MINT_TIMEOUT_MS = 10 * 60_000;
|
|
107
|
+
/** How much of what Claude Code showed is kept: its end, where the login is. */
|
|
108
|
+
const SHOWN = 256 * 1024;
|
|
109
|
+
/** The line the lent terminal's first program writes of itself, before it becomes Claude Code. */
|
|
110
|
+
const STARTED_AS = /^started as \d+$/;
|
|
111
|
+
/**
|
|
112
|
+
* Make a Claude login that lasts a year, with Claude Code's own `claude setup-token`: it opens
|
|
113
|
+
* this computer's browser, the person says yes there, and it shows the login once. It keeps
|
|
114
|
+
* nothing, and neither does this.
|
|
115
|
+
*
|
|
116
|
+
* Claude Code draws on a terminal's screen and this process has none, so the system's `script`
|
|
117
|
+
* lends it one, wide enough that the login stays on one line. Its input is a pipe nothing ever
|
|
118
|
+
* writes to. `script` gives its program a group of its own, so that program first says which
|
|
119
|
+
* process it is: at the end both groups are ended, whether or not Claude Code heeds a terminal
|
|
120
|
+
* that went away.
|
|
121
|
+
*/
|
|
122
|
+
export const mintClaude = ({ signal, timeoutMs = MINT_TIMEOUT_MS, command = 'claude setup-token' } = {}) => new Promise(resolve => {
|
|
123
|
+
const inner = `echo "started as $$"; stty cols 250 rows 50; command -v ${command.split(' ')[0]} >/dev/null || { echo "Claude Code is not on this computer."; exit 1; }; exec ${command}`;
|
|
124
|
+
const lent = process.platform === 'darwin' ? 'script -q /dev/null sh -c "$0"' : 'script -q -e -c "$0" /dev/null';
|
|
125
|
+
// On Linux `script` starts its program with the person's own shell, and the line above is written for sh.
|
|
126
|
+
const child = spawn('sh', ['-c', `tail -f /dev/null | { ${lent}; kill 0; }`, inner], { stdio: ['ignore', 'pipe', 'pipe'], detached: true, env: { ...process.env, SHELL: '/bin/sh' } });
|
|
127
|
+
let shown = '';
|
|
128
|
+
let lentTo;
|
|
129
|
+
let ended = false;
|
|
130
|
+
const end = (answer) => {
|
|
131
|
+
if (ended)
|
|
132
|
+
return;
|
|
133
|
+
ended = true;
|
|
134
|
+
clearTimeout(timer);
|
|
135
|
+
signal?.removeEventListener('abort', stopped);
|
|
136
|
+
for (const group of [lentTo, child.pid]) {
|
|
137
|
+
if (group === undefined || group <= 1)
|
|
138
|
+
continue;
|
|
139
|
+
try {
|
|
140
|
+
process.kill(-group, 'SIGKILL');
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Gone already.
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
child.stdout.destroy();
|
|
147
|
+
child.stderr.destroy();
|
|
148
|
+
resolve(answer);
|
|
149
|
+
};
|
|
150
|
+
const stopped = () => end({ ok: false, error: 'The login was stopped.' });
|
|
151
|
+
const timer = setTimeout(() => end({ ok: false, error: `No yes came from the browser in ${Math.round(timeoutMs / 60_000)} minutes.` }), timeoutMs);
|
|
152
|
+
signal?.addEventListener('abort', stopped, { once: true });
|
|
153
|
+
if (signal?.aborted)
|
|
154
|
+
stopped();
|
|
155
|
+
const read = (said) => {
|
|
156
|
+
shown = (shown + said).slice(-SHOWN);
|
|
157
|
+
// Read once, as it comes: it is the first thing written, and a long wait scrolls it out of what is kept.
|
|
158
|
+
lentTo ??= startedAs(shown);
|
|
159
|
+
const login = mintedIn(shown);
|
|
160
|
+
if (login)
|
|
161
|
+
end({ ok: true, login });
|
|
162
|
+
};
|
|
163
|
+
child.stdout.setEncoding('utf8').on('data', read);
|
|
164
|
+
child.stderr.setEncoding('utf8').on('data', read);
|
|
165
|
+
child.once('error', error => end({ ok: false, error: `Claude Code could not be started: ${error.message}` }));
|
|
166
|
+
child.once('close', () => end({ ok: false, error: notMinted(shown) }));
|
|
167
|
+
});
|
|
168
|
+
const NAMES = { github: 'GitHub', claude: 'Claude' };
|
|
169
|
+
/** Refused: the login is already being changed, in another process. */
|
|
170
|
+
export class AlreadyAtWork extends Error {
|
|
171
|
+
constructor(address, kind) {
|
|
172
|
+
super(`The ${NAMES[kind]} login of ${address} is being changed right now`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Open a line to the server, meet it, and do `work` on it; a server that cannot be met is told with what to do about it. */
|
|
176
|
+
async function onServer(server, run, work, signal) {
|
|
177
|
+
const session = await openSession(server, { ...(run ? { exec: run } : {}), ...(signal ? { signal } : {}) });
|
|
178
|
+
try {
|
|
179
|
+
const met = await session.meet();
|
|
180
|
+
if (!met.ok)
|
|
181
|
+
return { ok: false, error: withAdvice(server, met.said || 'The server did not answer.'), reached: false };
|
|
182
|
+
return await work(session);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
await session.close();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Change one login of a server: give it one, or take it off. Run as a process of its own: a
|
|
190
|
+
* person asked in the browser takes a while, a server may too, and a command a page runs has
|
|
191
|
+
* seconds. How it ended is written to the file, for the page.
|
|
192
|
+
*
|
|
193
|
+
* Once the server is being told, it is told to the end: a line cut half-way would leave on the
|
|
194
|
+
* server what this computer does not know of.
|
|
195
|
+
*/
|
|
196
|
+
export async function changeLogin(opts) {
|
|
197
|
+
const { env, address, kind, remove } = opts;
|
|
198
|
+
const mine = (kept) => kept[kind]?.worker?.pid === process.pid;
|
|
199
|
+
const beat = () => ({ pid: process.pid, seen: new Date().toISOString() });
|
|
200
|
+
// This process's from here on; the command that started it held it under its own name until now.
|
|
201
|
+
const server = await changeServer(env, address, kept => {
|
|
202
|
+
const worker = kept[kind]?.worker;
|
|
203
|
+
if (atWork(worker) && worker.pid !== process.pid && worker.pid !== process.ppid)
|
|
204
|
+
throw new AlreadyAtWork(address, kind);
|
|
205
|
+
kept[kind] = { ...known(kept[kind]), worker: beat() };
|
|
206
|
+
return { address: kept.address, user: kept.user };
|
|
207
|
+
});
|
|
208
|
+
if (!server)
|
|
209
|
+
throw new Error(`no server at ${address}`);
|
|
210
|
+
const beating = heartbeat(() => changeServer(env, address, kept => void (mine(kept) && (kept[kind].worker = beat()))), opts.beatMs ?? BEAT_MS);
|
|
211
|
+
const failed = (error) => (kept) => void (kept[kind] = { ...known(kept[kind]), failed: error });
|
|
212
|
+
let after = failed('The work stopped before it ended.');
|
|
213
|
+
try {
|
|
214
|
+
if (remove) {
|
|
215
|
+
const gone = await onServer(server, opts.exec, session => send(session, kind === 'github' ? githubLogoutScript() : claudeLogoutScript()));
|
|
216
|
+
after = gone.ok ? kept => void delete kept[kind] : failed(gone.error);
|
|
217
|
+
}
|
|
218
|
+
else if (kind === 'github' && opts.own) {
|
|
219
|
+
// The wait for the person is cut when this process is told to stop: a second press asks the server anew.
|
|
220
|
+
const shown = (code) => changeServer(env, address, kept => void (mine(kept) && Object.assign(kept.github, { asking: true, code })));
|
|
221
|
+
const given = await onServer(server, opts.exec, session => askGithub(session, shown), opts.signal);
|
|
222
|
+
after = kept => noteGithub(kept, given);
|
|
223
|
+
}
|
|
224
|
+
else if (kind === 'github') {
|
|
225
|
+
const given = await onServer(server, opts.exec, session => giveGithub(session, opts.exec));
|
|
226
|
+
after = kept => noteGithub(kept, given);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
await changeServer(env, address, kept => void (mine(kept) && (kept.claude.asking = true)));
|
|
230
|
+
const minted = await (opts.mint ?? mintClaude)(opts.signal ? { signal: opts.signal } : {});
|
|
231
|
+
await changeServer(env, address, kept => void (mine(kept) && delete kept.claude.asking));
|
|
232
|
+
if (!minted.ok)
|
|
233
|
+
return void (after = failed(minted.error));
|
|
234
|
+
const given = await onServer(server, opts.exec, session => send(session, claudeLoginScript(minted.login), minted.login));
|
|
235
|
+
after = given.ok ? kept => void (kept.claude = { since: new Date().toISOString() }) : failed(given.error);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
await beating.stop();
|
|
240
|
+
// One that was started over is another process's by now: nothing of it is this one's to write.
|
|
241
|
+
await changeServer(env, address, kept => void (mine(kept) && after(kept))).catch(() => { });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=logins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logins.js","sourceRoot":"","sources":["../src/logins.ts"],"names":[],"mappings":"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"}
|
package/dist/ssh.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { SERVER_USER } from './steps.js';
|
|
2
|
+
/**
|
|
3
|
+
* A script run on the server, over SSH, with this computer's own `ssh`: its keys, its agent and
|
|
4
|
+
* its settings are the person's, and OpenAgent keeps none of its own. Nothing is ever asked: a
|
|
5
|
+
* key that needs a password typed, or a server that wants one, is a failure told in words.
|
|
6
|
+
*
|
|
7
|
+
* The first time this computer meets a server its key is taken as it is and remembered, as when
|
|
8
|
+
* a person answers "yes" in a terminal. A server that later shows another key is refused by
|
|
9
|
+
* `ssh` itself, before anything is sent.
|
|
10
|
+
*/
|
|
11
|
+
/** How a command ended, and what it said. */
|
|
12
|
+
export interface Said {
|
|
13
|
+
/** The exit code; `null` for a command that was stopped, or could not start. */
|
|
14
|
+
code: number | null;
|
|
15
|
+
out: string;
|
|
16
|
+
err: string;
|
|
17
|
+
}
|
|
18
|
+
/** How one command is run and fed a text: the real one when unsaid. */
|
|
19
|
+
export type Exec = (bin: string, args: readonly string[], stdin: string, opts: {
|
|
20
|
+
timeoutMs: number;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}) => Promise<Said>;
|
|
23
|
+
export declare const exec: Exec;
|
|
24
|
+
/** The end of what a command said, its errors last: where the reason of a failure is. */
|
|
25
|
+
export declare function lastLines(said: Pick<Said, 'out' | 'err'>): string;
|
|
26
|
+
/**
|
|
27
|
+
* What to do about a failure to log in to the server, which `ssh` and `sudo` word their own way,
|
|
28
|
+
* after those words. Only for the first thing asked of a server: later a server's own words
|
|
29
|
+
* could read like one of these.
|
|
30
|
+
*/
|
|
31
|
+
export declare function withAdvice(server: {
|
|
32
|
+
address: string;
|
|
33
|
+
user: string;
|
|
34
|
+
}, said: string): string;
|
|
35
|
+
export interface Session {
|
|
36
|
+
/**
|
|
37
|
+
* Meet the server: log in, and answer what its key is known by. `undefined` when `ssh` did not
|
|
38
|
+
* say, which changes nothing: the key is the one `ssh` remembers either way.
|
|
39
|
+
*/
|
|
40
|
+
meet(): Promise<{
|
|
41
|
+
ok: true;
|
|
42
|
+
fingerprint?: string;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
said: string;
|
|
46
|
+
}>;
|
|
47
|
+
/** Run `script` on the server as root or as the server's own user. */
|
|
48
|
+
run(as: 'root' | typeof SERVER_USER, script: string, timeoutMs: number): Promise<Said>;
|
|
49
|
+
/** Hang up. */
|
|
50
|
+
close(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A line to one server, kept open between the scripts, so the keys are asked for once (a key
|
|
54
|
+
* that wants a touch or a confirmation each time it is used would otherwise ask at every step).
|
|
55
|
+
*/
|
|
56
|
+
export declare function openSession(server: {
|
|
57
|
+
address: string;
|
|
58
|
+
user: string;
|
|
59
|
+
}, opts?: {
|
|
60
|
+
exec?: Exec;
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
}): Promise<Session>;
|
|
63
|
+
//# sourceMappingURL=ssh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssh.d.ts","sourceRoot":"","sources":["../src/ssh.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC;;;;;;;;GAQG;AAEH,6CAA6C;AAC7C,MAAM,WAAW,IAAI;IACnB,gFAAgF;IAChF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,uEAAuE;AACvE,MAAM,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAK5I,eAAO,MAAM,IAAI,EAAE,IAoCf,CAAA;AAKJ,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG,MAAM,CAGjE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAS1F;AAED,MAAM,WAAW,OAAO;IACtB;;;OAGG;IACH,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjF,sEAAsE;IACtE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtF,eAAe;IACf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAKD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAqC/I"}
|
package/dist/ssh.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { SERVER_USER } from './steps.js';
|
|
6
|
+
/** How much of what a command says is kept: its end, where a failure tells its reason. */
|
|
7
|
+
const KEPT = 64 * 1024;
|
|
8
|
+
export const exec = (bin, args, stdin, { timeoutMs, signal }) => new Promise(resolve => {
|
|
9
|
+
const child = spawn(bin, [...args], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
10
|
+
let out = '';
|
|
11
|
+
let err = '';
|
|
12
|
+
let stopped;
|
|
13
|
+
const stop = (why) => {
|
|
14
|
+
stopped ??= why;
|
|
15
|
+
child.kill('SIGKILL');
|
|
16
|
+
};
|
|
17
|
+
const timer = setTimeout(() => stop(`No answer for ${Math.round(timeoutMs / 60_000)} minutes.`), timeoutMs);
|
|
18
|
+
const aborted = () => stop('The install was stopped.');
|
|
19
|
+
signal?.addEventListener('abort', aborted, { once: true });
|
|
20
|
+
if (signal?.aborted)
|
|
21
|
+
aborted();
|
|
22
|
+
child.stdout.setEncoding('utf8').on('data', (said) => (out = (out + said).slice(-KEPT)));
|
|
23
|
+
child.stderr.setEncoding('utf8').on('data', (said) => (err = (err + said).slice(-KEPT)));
|
|
24
|
+
let ended = false;
|
|
25
|
+
const done = (code, more = '') => {
|
|
26
|
+
if (ended)
|
|
27
|
+
return;
|
|
28
|
+
ended = true;
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
signal?.removeEventListener('abort', aborted);
|
|
31
|
+
// Whatever still holds the command's output open is not listened to any longer.
|
|
32
|
+
child.stdout.destroy();
|
|
33
|
+
child.stderr.destroy();
|
|
34
|
+
resolve({ code: stopped === undefined ? code : null, out, err: [err.trimEnd(), stopped, more].filter(Boolean).join('\n') });
|
|
35
|
+
};
|
|
36
|
+
child.once('error', error => done(null, `${bin} could not start: ${error.message}`));
|
|
37
|
+
child.once('close', code => done(code));
|
|
38
|
+
// `ssh` leaves a process behind that keeps the line open for the next script, and that one
|
|
39
|
+
// may hold this command's output open long after the command ended: its end is the exit,
|
|
40
|
+
// a moment later so what it said last has arrived.
|
|
41
|
+
child.once('exit', code => void setTimeout(() => done(code), 250));
|
|
42
|
+
// A command that ended before it read its text is told by its exit, not by a broken pipe here.
|
|
43
|
+
child.stdin.on('error', () => { });
|
|
44
|
+
child.stdin.end(stdin);
|
|
45
|
+
});
|
|
46
|
+
/** How many of a command's last lines are kept for the page. */
|
|
47
|
+
const SAID_LINES = 15;
|
|
48
|
+
/** The end of what a command said, its errors last: where the reason of a failure is. */
|
|
49
|
+
export function lastLines(said) {
|
|
50
|
+
const lines = [said.out, said.err].flatMap(text => text.split('\n').map(line => line.trimEnd()).filter(Boolean));
|
|
51
|
+
return lines.slice(-SAID_LINES).join('\n');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* What to do about a failure to log in to the server, which `ssh` and `sudo` word their own way,
|
|
55
|
+
* after those words. Only for the first thing asked of a server: later a server's own words
|
|
56
|
+
* could read like one of these.
|
|
57
|
+
*/
|
|
58
|
+
export function withAdvice(server, said) {
|
|
59
|
+
const advice = /REMOTE HOST IDENTIFICATION HAS CHANGED|Host key verification failed/.test(said)
|
|
60
|
+
? 'This server shows another key than the one this computer remembers for it. Nothing was sent to it.'
|
|
61
|
+
: /Permission denied/.test(said)
|
|
62
|
+
? `OpenAgent logs in with this computer's own SSH keys. Check that "ssh ${server.user}@${server.address}" works in a terminal without asking for anything.`
|
|
63
|
+
: /sudo: a password is required|sudo: a terminal is required|sudo: command not found|sudo: not found/.test(said)
|
|
64
|
+
? `The user ${server.user} cannot become root without a password. Use root, or a user whose sudo asks for none.`
|
|
65
|
+
: undefined;
|
|
66
|
+
return [said, advice].filter(Boolean).join('\n');
|
|
67
|
+
}
|
|
68
|
+
/** What `ssh -v` prints about itself, which is nothing the server said. */
|
|
69
|
+
const NOISE = /^(debug\d:|OpenSSH_|Authenticated to |Transferred: |Bytes per second: )/;
|
|
70
|
+
/**
|
|
71
|
+
* A line to one server, kept open between the scripts, so the keys are asked for once (a key
|
|
72
|
+
* that wants a touch or a confirmation each time it is used would otherwise ask at every step).
|
|
73
|
+
*/
|
|
74
|
+
export async function openSession(server, opts = {}) {
|
|
75
|
+
const run = opts.exec ?? exec;
|
|
76
|
+
// The line's own socket, in a folder only this user reads; a short path, which a socket needs.
|
|
77
|
+
const dir = await mkdtemp(join(tmpdir(), 'oas-'));
|
|
78
|
+
const options = [
|
|
79
|
+
['BatchMode', 'yes'],
|
|
80
|
+
['StrictHostKeyChecking', 'accept-new'],
|
|
81
|
+
['ConnectTimeout', '20'],
|
|
82
|
+
['ServerAliveInterval', '15'],
|
|
83
|
+
['ServerAliveCountMax', '4'],
|
|
84
|
+
['ControlMaster', 'auto'],
|
|
85
|
+
['ControlPath', join(dir, 's')],
|
|
86
|
+
['ControlPersist', '120'],
|
|
87
|
+
// Whatever this computer's own settings say for every server: a server met a moment ago gets no way back into this computer.
|
|
88
|
+
['ForwardAgent', 'no'],
|
|
89
|
+
['ForwardX11', 'no'],
|
|
90
|
+
['ClearAllForwardings', 'yes'],
|
|
91
|
+
['PermitLocalCommand', 'no'],
|
|
92
|
+
['RequestTTY', 'no'],
|
|
93
|
+
].flatMap(([name, value]) => ['-o', `${name}=${value}`]);
|
|
94
|
+
// `--` ends the options: an address is never read as one.
|
|
95
|
+
const args = (remote, more = []) => [...options, ...more, '-l', server.user, '--', server.address, remote];
|
|
96
|
+
const root = server.user === 'root' ? '' : 'sudo -n ';
|
|
97
|
+
const signal = opts.signal ? { signal: opts.signal } : {};
|
|
98
|
+
return {
|
|
99
|
+
async meet() {
|
|
100
|
+
const said = await run('ssh', args('true', ['-v']), '', { timeoutMs: 60_000, ...signal });
|
|
101
|
+
if (said.code !== 0)
|
|
102
|
+
return { ok: false, said: said.err.split('\n').filter(line => !NOISE.test(line)).join('\n').trim() };
|
|
103
|
+
const fingerprint = /^debug1: Server host key: \S+ (SHA256:\S+)/m.exec(said.err)?.[1];
|
|
104
|
+
return { ok: true, ...(fingerprint ? { fingerprint } : {}) };
|
|
105
|
+
},
|
|
106
|
+
run: (as, script, timeoutMs) => run('ssh', args(as === 'root' ? `${root}bash -s` : `${root}runuser -l ${SERVER_USER} -c 'bash -s'`), script, { timeoutMs, ...signal }),
|
|
107
|
+
async close() {
|
|
108
|
+
await run('ssh', ['-o', `ControlPath=${join(dir, 's')}`, '-O', 'exit', '--', server.address], '', { timeoutMs: 5_000 }).catch(() => { });
|
|
109
|
+
await rm(dir, { recursive: true, force: true });
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=ssh.js.map
|
package/dist/ssh.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssh.js","sourceRoot":"","sources":["../src/ssh.ts"],"names":[],"mappings":"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"}
|