@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.
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The steps of an install, in order: what each is called on the page, who runs it on the server,
3
+ * and the script itself. A step first looks whether its work is already done and then does
4
+ * nothing, so the same list is the first install, a second try after a failure, and an update.
5
+ *
6
+ * `root` is only who installs. What runs afterwards runs as the server's own user `openagent`:
7
+ * the programs are root's, so an agent that works there cannot change OpenAgent itself.
8
+ */
9
+ export declare const STEP_IDS: readonly ['login', 'user', 'tools', 'openagent', 'agent', 'service', 'door'];
10
+ export type StepId = (typeof STEP_IDS)[number];
11
+ /** What a script is written for. */
12
+ export interface Facts {
13
+ /** The server's number or name: what its certificate is for. */
14
+ address: string;
15
+ /** The OpenAgent the server gets: the one that runs on this computer. */
16
+ version: string;
17
+ }
18
+ export interface StepDef {
19
+ id: StepId;
20
+ /** The step's line on the page. */
21
+ label: string;
22
+ as: 'root' | 'openagent';
23
+ /** How long the step may take: a server that stopped answering is not waited for longer. */
24
+ timeoutMs: number;
25
+ script(facts: Facts): string;
26
+ }
27
+ /** The server's user everything runs as once it is installed. */
28
+ export declare const SERVER_USER = "openagent";
29
+ /** Where the server keeps the Claude login: root's file, which the service reads as it starts. An agent on the server works with the login, and so can read it; it cannot change the file. */
30
+ export declare const CLAUDE_FILE = "/etc/openagent/claude.env";
31
+ /** `value` as one word of a shell script, whatever it holds. */
32
+ export declare function quoted(value: string): string;
33
+ /** What the service file holds. `KillMode=process`: a run is a process of its own and goes on when OpenAgent starts again, as on a laptop. */
34
+ export declare const SERVICE_UNIT = "[Unit]\nDescription=OpenAgent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nUser=openagent\nWorkingDirectory=/home/openagent\nEnvironment=PATH=/home/openagent/.local/bin:/opt/openagent/node/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nEnvironmentFile=-/etc/openagent/claude.env\nExecStart=/opt/openagent/node/bin/openagent\nRestart=always\nRestartSec=5\nKillMode=process\nAmbientCapabilities=CAP_NET_BIND_SERVICE\n\n[Install]\nWantedBy=multi-user.target";
35
+ export declare const STEPS: readonly StepDef[];
36
+ /** The script that asks the server's door for a link a new machine gets in with. Run as the server's own user. */
37
+ export declare function linkScript(): string;
38
+ /** A script that puts a login on the server or takes it off, and who runs it there. */
39
+ export interface LoginScript {
40
+ as: StepDef['as'];
41
+ script: string;
42
+ }
43
+ /** How long a login's script may take: a server that stopped answering is not waited for longer. */
44
+ export declare const LOGIN_TIMEOUT_MS: number;
45
+ /**
46
+ * Give the server a GitHub login: `gh` keeps it in its own file, and git asks `gh` for it. The
47
+ * server then holds this one login, and answers whose it is; when any of that fails, it holds none.
48
+ *
49
+ * The login is a word of the script, which the server's shell reads where a program would read:
50
+ * it is on no command line, and no program the script starts finds it among what it was given.
51
+ */
52
+ export declare function githubLoginScript(login: string): LoginScript;
53
+ /** How long a person has to type the code at GitHub: GitHub's own limit is fifteen minutes. */
54
+ export declare const GITHUB_CODE_TIMEOUT_MS: number;
55
+ /**
56
+ * Have the server ask GitHub for a login of its own: its `gh` is started and left waiting, and
57
+ * GitHub answers with a short code that a person types at github.com/login/device. The script
58
+ * prints `<process> <file> <code>` and ends; {@link githubWaitScript} sees the rest. One that
59
+ * still waits from an earlier press is ended first. The login the server had goes, as with a
60
+ * copy: the server has one GitHub login, the last one asked for.
61
+ */
62
+ export declare function githubCodeScript(): LoginScript;
63
+ /**
64
+ * Wait until the server's `gh` has its answer from GitHub, for the code {@link githubCodeScript}
65
+ * printed, and say whose login it is. All of it or none, as with a copy.
66
+ */
67
+ export declare function githubWaitScript(pid: string, log: string): LoginScript;
68
+ /** Take the GitHub login off the server. */
69
+ export declare function githubLogoutScript(): LoginScript;
70
+ /**
71
+ * Give the server a Claude login: Claude Code is first asked, as the server's own user, whether
72
+ * it reads the login; then the login is written to root's file and OpenAgent is started again
73
+ * with it. A run that is at work goes on with the login it started with. When OpenAgent does not
74
+ * start with it, the file is put back as it was.
75
+ *
76
+ * Claude Code is the server user's own program: root asks it as that user, for half a minute at
77
+ * most, and without the lock root holds, which a program that never ended would hold with it.
78
+ */
79
+ export declare function claudeLoginScript(login: string): LoginScript;
80
+ /** Take the Claude login off the server, and start OpenAgent again without it. */
81
+ export declare function claudeLogoutScript(): LoginScript;
82
+ //# sourceMappingURL=steps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,eAAO,MAAM,QAAQ,YAAI,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAU,CAAA;AACpG,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAA;AAE9C,oCAAoC;AACpC,MAAM,WAAW,KAAK;IACpB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAA;IACf,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,MAAM,GAAG,WAAW,CAAA;IACxB,4FAA4F;IAC5F,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAA;CAC7B;AAED,iEAAiE;AACjE,eAAO,MAAM,WAAW,cAAc,CAAA;AAOtC,8LAA8L;AAC9L,eAAO,MAAM,WAAW,8BAA8B,CAAA;AAOtD,gEAAgE;AAChE,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5C;AAgDD,8IAA8I;AAC9I,eAAO,MAAM,YAAY,yfAiBE,CAAA;AAE3B,eAAO,MAAM,KAAK,EAAE,SAAS,OAAO,EAqMnC,CAAA;AAED,kHAAkH;AAClH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,oGAAoG;AACpG,eAAO,MAAM,gBAAgB,QAAa,CAAA;AAM1C;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAoB5D;AAED,+FAA+F;AAC/F,eAAO,MAAM,sBAAsB,QAAc,CAAA;AAqBjD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,IAAI,WAAW,CAgC9C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CA8BtE;AAED,4CAA4C;AAC5C,wBAAgB,kBAAkB,IAAI,WAAW,CAahD;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CA8B5D;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,IAAI,WAAW,CAchD"}
package/dist/steps.js ADDED
@@ -0,0 +1,427 @@
1
+ /**
2
+ * The steps of an install, in order: what each is called on the page, who runs it on the server,
3
+ * and the script itself. A step first looks whether its work is already done and then does
4
+ * nothing, so the same list is the first install, a second try after a failure, and an update.
5
+ *
6
+ * `root` is only who installs. What runs afterwards runs as the server's own user `openagent`:
7
+ * the programs are root's, so an agent that works there cannot change OpenAgent itself.
8
+ */
9
+ export const STEP_IDS = ['login', 'user', 'tools', 'openagent', 'agent', 'service', 'door'];
10
+ /** The server's user everything runs as once it is installed. */
11
+ export const SERVER_USER = 'openagent';
12
+ /** Where the install keeps what it brings: Node, and OpenAgent inside it. */
13
+ const ROOT = '/opt/openagent';
14
+ /** The Node the server gets: the newest of this line, read off nodejs.org with its checksum. */
15
+ const NODE_LINE = 24;
16
+ /** Where the server keeps the Claude login: root's file, which the service reads as it starts. An agent on the server works with the login, and so can read it; it cannot change the file. */
17
+ export const CLAUDE_FILE = '/etc/openagent/claude.env';
18
+ /** The name Claude Code reads a login under that was made for a computer nobody sits at. */
19
+ const CLAUDE_NAME = 'CLAUDE_CODE_OAUTH_TOKEN';
20
+ /** The door's own command, inside the OpenAgent the install brought. */
21
+ const DOOR = `${ROOT}/node/bin/node ${ROOT}/node/lib/node_modules/@openagt/dashboard/node_modules/@openagt/remote-access/bin/remote-access`;
22
+ /** `value` as one word of a shell script, whatever it holds. */
23
+ export function quoted(value) {
24
+ return `'${value.replaceAll("'", `'\\''`)}'`;
25
+ }
26
+ /**
27
+ * Held by every script root runs, for as long as it runs: a script still at work after this computer stopped waiting for it is not joined by a second one.
28
+ * In a folder only root writes: the server's user cannot put a file of its own there first.
29
+ */
30
+ const LOCK = '/run/openagent-install.lock';
31
+ /**
32
+ * A whole script around `body`. The body is a function called once the shell has read all of
33
+ * it, with nothing to read from: the script itself comes in where a program it starts would
34
+ * otherwise read, and would be eaten by the first one that does.
35
+ *
36
+ * Root finds its programs in the system's own folders only, and nothing the server's user can
37
+ * write is ever on its way to one.
38
+ */
39
+ function script(as, vars, body) {
40
+ const system = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
41
+ return [
42
+ 'set -euo pipefail',
43
+ 'umask 022',
44
+ 'export DEBIAN_FRONTEND=noninteractive',
45
+ `export PATH="${as === 'root' ? system : `$HOME/.local/bin:${ROOT}/node/bin:${system}`}"`,
46
+ ...Object.entries(vars).map(([name, value]) => `${name}=${quoted(value)}`),
47
+ 'fail() { echo "$1" >&2; exit 1; }',
48
+ ...(as === 'root' ? [`exec 9>${LOCK}`, 'flock -n 9 || fail "Another install is still at work on this server. Try again in a moment."'] : []),
49
+ 'main() {',
50
+ body.trim(),
51
+ '}',
52
+ 'main </dev/null',
53
+ '',
54
+ ].join('\n');
55
+ }
56
+ const MINUTE = 60_000;
57
+ /** Defines `started`, which waits until OpenAgent answers on the server; when it does not, it shows what the service last said and answers no. */
58
+ const STARTED = `
59
+ started() {
60
+ for _ in $(seq 30); do
61
+ curl -fsS -o /dev/null http://127.0.0.1:4200/ 2>/dev/null && return 0
62
+ sleep 1
63
+ done
64
+ journalctl -u openagent -n 20 --no-pager >&2 || true
65
+ return 1
66
+ }`;
67
+ const NOT_STARTED = 'fail "OpenAgent did not start on this server."';
68
+ /** What the service file holds. `KillMode=process`: a run is a process of its own and goes on when OpenAgent starts again, as on a laptop. */
69
+ export const SERVICE_UNIT = `[Unit]
70
+ Description=OpenAgent
71
+ After=network-online.target
72
+ Wants=network-online.target
73
+
74
+ [Service]
75
+ User=${SERVER_USER}
76
+ WorkingDirectory=/home/${SERVER_USER}
77
+ Environment=PATH=/home/${SERVER_USER}/.local/bin:${ROOT}/node/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
78
+ EnvironmentFile=-${CLAUDE_FILE}
79
+ ExecStart=${ROOT}/node/bin/openagent
80
+ Restart=always
81
+ RestartSec=5
82
+ KillMode=process
83
+ AmbientCapabilities=CAP_NET_BIND_SERVICE
84
+
85
+ [Install]
86
+ WantedBy=multi-user.target`;
87
+ export const STEPS = [
88
+ {
89
+ id: 'login',
90
+ label: 'Log in',
91
+ as: 'root',
92
+ timeoutMs: 2 * MINUTE,
93
+ script: () => script('root', {}, `
94
+ . /etc/os-release 2>/dev/null || true
95
+ case "\${ID:-}" in
96
+ ubuntu | debian) ;;
97
+ *) fail "OpenAgent installs on Ubuntu and Debian. This server runs \${PRETTY_NAME:-something else}." ;;
98
+ esac
99
+ case "$(uname -m)" in
100
+ x86_64 | aarch64) ;;
101
+ *) fail "OpenAgent has no install for this server's chip, $(uname -m)." ;;
102
+ esac
103
+ [ -d /run/systemd/system ] || fail "This server does not start its programs with systemd, which OpenAgent's service needs."
104
+ # Nothing is changed under an agent at work: its own programs would be replaced while it runs.
105
+ if pgrep -u ${SERVER_USER} -f 'agent-runner/bin/agent-runner run' >/dev/null 2>&1; then
106
+ fail "An agent is working on this server. Stop it, or wait until it ends, then try again."
107
+ fi
108
+ echo "\${PRETTY_NAME:-$ID}, $(uname -m)"
109
+ `),
110
+ },
111
+ {
112
+ id: 'user',
113
+ label: `The user ${SERVER_USER}`,
114
+ as: 'root',
115
+ timeoutMs: 2 * MINUTE,
116
+ script: () => script('root', {}, `
117
+ id ${SERVER_USER} >/dev/null 2>&1 || useradd --create-home --shell /bin/bash ${SERVER_USER}
118
+ `),
119
+ },
120
+ {
121
+ id: 'tools',
122
+ label: 'git, gh, Node and Chrome',
123
+ as: 'root',
124
+ timeoutMs: 20 * MINUTE,
125
+ script: () => script('root', {}, `
126
+ . /etc/os-release
127
+ # A server that was just made often installs its own updates in its first minutes: the install waits its turn.
128
+ apt() { apt-get -y -q -o DPkg::Lock::Timeout=600 "$@"; }
129
+ apt update
130
+ apt install git curl ca-certificates xz-utils procps
131
+ dir=$(mktemp -d)
132
+ trap 'rm -rf "$dir"' EXIT
133
+
134
+ # From GitHub's own package source, signed with its key: the gh these systems bring themselves is years old.
135
+ if ! command -v gh >/dev/null; then
136
+ install -d -m 755 /etc/apt/keyrings
137
+ curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg -o "$dir/gh.gpg"
138
+ install -m 644 "$dir/gh.gpg" /etc/apt/keyrings/githubcli-archive-keyring.gpg
139
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list
140
+ apt update
141
+ apt install gh
142
+ fi
143
+
144
+ have=$(${ROOT}/node/bin/node -p 'process.versions.node.split(".")[0]' 2>/dev/null || true)
145
+ if [ "$have" != ${NODE_LINE} ]; then
146
+ arch=x64
147
+ [ "$(uname -m)" = aarch64 ] && arch=arm64
148
+ from=https://nodejs.org/dist/latest-v${NODE_LINE}.x
149
+ curl -fsSL "$from/SHASUMS256.txt" -o "$dir/SHASUMS256.txt"
150
+ file=$(grep -o "node-v[0-9.]*-linux-$arch\\.tar\\.xz" "$dir/SHASUMS256.txt" | head -n 1 || true)
151
+ [ -n "$file" ] || fail "nodejs.org lists no Node ${NODE_LINE} for this server's chip."
152
+ curl -fsSL "$from/$file" -o "$dir/$file"
153
+ (cd "$dir" && grep " $file\\$" SHASUMS256.txt | sha256sum -c - >/dev/null) || fail "The Node that was downloaded is not the one nodejs.org lists."
154
+ rm -rf ${ROOT}/node.new
155
+ mkdir -p ${ROOT}/node.new
156
+ # Root's, whoever the download names as the files' owner: on a server that owner could be the user the agents run as.
157
+ tar -xJf "$dir/$file" -C ${ROOT}/node.new --strip-components=1 --no-same-owner
158
+ rm -rf ${ROOT}/node
159
+ mv ${ROOT}/node.new ${ROOT}/node
160
+ touch ${ROOT}/restart
161
+ fi
162
+ for bin in node npm npx; do ln -sf ${ROOT}/node/bin/$bin /usr/local/bin/$bin; done
163
+
164
+ # A browser that is there and does not start is none: Ubuntu's own package is only a pointer to its snap store.
165
+ browser() { for name in google-chrome chromium chromium-browser; do "$name" --version >/dev/null 2>&1 && return 0; done; return 1; }
166
+ if ! browser; then
167
+ if [ "$(uname -m)" = x86_64 ]; then
168
+ curl -fsSL https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -o "$dir/chrome.deb"
169
+ apt install "$dir/chrome.deb"
170
+ elif [ "$ID" = debian ]; then
171
+ # Google ships no Chrome for this chip: Chromium is the same browser without its name.
172
+ apt install chromium
173
+ else
174
+ apt install chromium-browser
175
+ fi
176
+ browser || fail "Chrome was installed, and it does not start on this server."
177
+ fi
178
+ `),
179
+ },
180
+ {
181
+ id: 'openagent',
182
+ label: 'OpenAgent',
183
+ as: 'root',
184
+ timeoutMs: 20 * MINUTE,
185
+ script: ({ version }) => script('root', { VERSION: version }, `
186
+ # Read off its file, not asked of OpenAgent itself: nothing a package brought runs as root, neither here nor at the install.
187
+ have=$(${ROOT}/node/bin/node -p 'require("${ROOT}/node/lib/node_modules/@openagt/dashboard/package.json").version' 2>/dev/null || true)
188
+ if [ "$have" != "$VERSION" ]; then
189
+ ${ROOT}/node/bin/npm install --global --ignore-scripts --no-fund --no-audit --loglevel=error "@openagt/dashboard@$VERSION"
190
+ touch ${ROOT}/restart
191
+ fi
192
+ ln -sf ${ROOT}/node/bin/openagent /usr/local/bin/openagent
193
+ `),
194
+ },
195
+ {
196
+ id: 'agent',
197
+ label: 'Claude Code',
198
+ as: 'openagent',
199
+ timeoutMs: 10 * MINUTE,
200
+ script: () => script('openagent', {}, `
201
+ # Claude Code's own installer, into this user's home: it keeps itself up to date there, as on a laptop.
202
+ command -v claude >/dev/null || curl -fsSL https://claude.ai/install.sh | bash
203
+ command -v claude >/dev/null || fail "Claude Code's installer ended without a claude command."
204
+ `),
205
+ },
206
+ {
207
+ id: 'service',
208
+ label: 'The service',
209
+ as: 'root',
210
+ timeoutMs: 3 * MINUTE,
211
+ script: () => script('root', { UNIT: SERVICE_UNIT }, `${STARTED}
212
+ file=/etc/systemd/system/openagent.service
213
+ if [ "$(cat "$file" 2>/dev/null || true)" != "$UNIT" ]; then
214
+ printf '%s\\n' "$UNIT" > "$file"
215
+ systemctl daemon-reload
216
+ touch ${ROOT}/restart
217
+ fi
218
+ # A firewall that is on lets the door's two ports through; one that is off is left off.
219
+ if command -v ufw >/dev/null && ufw status | grep '^Status: active' >/dev/null; then
220
+ ufw allow 80/tcp >/dev/null
221
+ ufw allow 443/tcp >/dev/null
222
+ fi
223
+ systemctl enable --quiet openagent
224
+ if [ -e ${ROOT}/restart ] || ! systemctl is-active --quiet openagent; then
225
+ systemctl restart openagent
226
+ rm -f ${ROOT}/restart
227
+ fi
228
+ started || ${NOT_STARTED}
229
+ `),
230
+ },
231
+ {
232
+ id: 'door',
233
+ label: 'The door',
234
+ as: 'openagent',
235
+ timeoutMs: 6 * MINUTE,
236
+ script: ({ address }) => script('openagent', { ADDRESS: address }, `
237
+ open() { case "$(${DOOR} status)" in *'"listening":true'*'"address":"'"$ADDRESS"'"'*) return 0 ;; *) return 1 ;; esac; }
238
+ open && exit 0
239
+ ${DOOR} https "$ADDRESS" >/dev/null
240
+ # A certificate takes a moment: its office reads the proof on port 80 first.
241
+ for _ in $(seq 150); do
242
+ open && exit 0
243
+ sleep 2
244
+ done
245
+ ${DOOR} status | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.error(JSON.parse(s).problem??"The door did not open."))' || true
246
+ fail "The door needs ports 80 and 443 of this server open to the internet: check the firewall of the company the server is rented from."
247
+ `),
248
+ },
249
+ ];
250
+ /** The script that asks the server's door for a link a new machine gets in with. Run as the server's own user. */
251
+ export function linkScript() {
252
+ return script(SERVER_USER, {}, `\n ${DOOR} add\n`);
253
+ }
254
+ /** How long a login's script may take: a server that stopped answering is not waited for longer. */
255
+ export const LOGIN_TIMEOUT_MS = 2 * MINUTE;
256
+ /** `gh` reads and writes its own file in the server user's home, whatever that user's shell says of another place or another login. */
257
+ const GH_OWN = 'unset GH_TOKEN GITHUB_TOKEN GH_CONFIG_DIR XDG_CONFIG_HOME';
258
+ const GH_FILE = '"$HOME/.config/gh/hosts.yml"';
259
+ /**
260
+ * Give the server a GitHub login: `gh` keeps it in its own file, and git asks `gh` for it. The
261
+ * server then holds this one login, and answers whose it is; when any of that fails, it holds none.
262
+ *
263
+ * The login is a word of the script, which the server's shell reads where a program would read:
264
+ * it is on no command line, and no program the script starts finds it among what it was given.
265
+ */
266
+ export function githubLoginScript(login) {
267
+ return {
268
+ as: SERVER_USER,
269
+ script: script(SERVER_USER, { LOGIN: login }, `
270
+ ${GH_OWN}
271
+ ${GH_STOP_ASKING}
272
+ rm -f ${GH_FILE}
273
+ given() {
274
+ printf '%s\\n' "$LOGIN" | gh auth login --hostname github.com --git-protocol https --with-token &&
275
+ gh auth setup-git --hostname github.com &&
276
+ gh api --hostname github.com user --jq .login
277
+ }
278
+ # All of it or none: a login that is on the server and not known of on this computer could not be taken off from there.
279
+ given || { rm -f ${GH_FILE}; exit 1; }
280
+ `),
281
+ };
282
+ }
283
+ /** How long a person has to type the code at GitHub: GitHub's own limit is fifteen minutes. */
284
+ export const GITHUB_CODE_TIMEOUT_MS = 16 * MINUTE;
285
+ /** Where the server's `gh` writes while it waits for the code to be typed, and the one that waits right now. */
286
+ const GH_ASKING = '"$HOME/.cache/openagent-github-login"';
287
+ /** Whether the process `$1` is a `gh` that asks GitHub for a login: a number kept in a file may be another process's by now. */
288
+ const GH_IS_ASKING = `asking() { ps -o args= -p "$1" 2>/dev/null | grep -q 'gh auth login'; }`;
289
+ /**
290
+ * End the server's `gh` that still waits for a code, when one does, and forget it: every script
291
+ * that changes the server's GitHub login starts with this, so a code from an earlier press brings
292
+ * nothing once the login was asked for anew, copied or taken off, and its wait changes nothing.
293
+ */
294
+ const GH_STOP_ASKING = `${GH_IS_ASKING}
295
+ waiting=$(cat ${GH_ASKING}.pid 2>/dev/null || true)
296
+ case "$waiting" in
297
+ '' | *[!0-9]*) ;;
298
+ *) if asking "$waiting"; then kill "$waiting" 2>/dev/null || true; fi ;;
299
+ esac
300
+ rm -f ${GH_ASKING}.pid`;
301
+ /**
302
+ * Have the server ask GitHub for a login of its own: its `gh` is started and left waiting, and
303
+ * GitHub answers with a short code that a person types at github.com/login/device. The script
304
+ * prints `<process> <file> <code>` and ends; {@link githubWaitScript} sees the rest. One that
305
+ * still waits from an earlier press is ended first. The login the server had goes, as with a
306
+ * copy: the server has one GitHub login, the last one asked for.
307
+ */
308
+ export function githubCodeScript() {
309
+ return {
310
+ as: SERVER_USER,
311
+ script: script(SERVER_USER, {}, `
312
+ ${GH_OWN}
313
+ command -v gh >/dev/null || fail "gh is not on this server yet. Press Update on its row first."
314
+ mkdir -p "$HOME/.cache"
315
+ ${GH_STOP_ASKING}
316
+ rm -f ${GH_FILE}
317
+ log=$(umask 077; mktemp ${GH_ASKING}.XXXXXX)
318
+ nohup setsid gh auth login --hostname github.com --git-protocol https --web >"$log" 2>&1 &
319
+ pid=$!
320
+ echo "$pid" > ${GH_ASKING}.pid
321
+ for _ in $(seq 1 150); do
322
+ grep -q 'one-time code' "$log" && break
323
+ asking "$pid" || break
324
+ sleep 0.2
325
+ done
326
+ code=$(grep -oE 'one-time code: [A-Z0-9]{4}-[A-Z0-9]{4}' "$log" | head -n 1 | awk '{print $NF}' || true)
327
+ if [ -z "$code" ]; then
328
+ kill "$pid" 2>/dev/null || true
329
+ tail -n 5 "$log" >&2
330
+ rm -f "$log" ${GH_ASKING}.pid
331
+ fail "GitHub gave the server no code."
332
+ fi
333
+ echo "$pid $log $code"
334
+ `),
335
+ };
336
+ }
337
+ /**
338
+ * Wait until the server's `gh` has its answer from GitHub, for the code {@link githubCodeScript}
339
+ * printed, and say whose login it is. All of it or none, as with a copy.
340
+ */
341
+ export function githubWaitScript(pid, log) {
342
+ return {
343
+ as: SERVER_USER,
344
+ script: script(SERVER_USER, { PID: pid, LOG: log }, `
345
+ ${GH_OWN}
346
+ ${GH_IS_ASKING}
347
+ while asking "$PID"; do sleep 1; done
348
+ # Asked for anew, copied or taken off in the meantime: the server's login is that change's to say, and none of it is this wait's.
349
+ if [ "$(cat ${GH_ASKING}.pid 2>/dev/null || true)" != "$PID" ]; then
350
+ rm -f "$LOG"
351
+ fail "The login was asked for anew, or changed another way, before the code was typed."
352
+ fi
353
+ rm -f ${GH_ASKING}.pid
354
+ # The login the server had was taken off before GitHub was asked: one that is there now came from GitHub.
355
+ given() {
356
+ gh auth setup-git --hostname github.com &&
357
+ gh api --hostname github.com user --jq .login
358
+ }
359
+ if ! given; then
360
+ grep -v 'one-time code' "$LOG" | tail -n 3 >&2 || true
361
+ rm -f "$LOG" ${GH_FILE}
362
+ fail "No login came from GitHub: the code was not typed in time, or it was refused."
363
+ fi
364
+ rm -f "$LOG"
365
+ `),
366
+ };
367
+ }
368
+ /** Take the GitHub login off the server. */
369
+ export function githubLogoutScript() {
370
+ return {
371
+ as: SERVER_USER,
372
+ script: script(SERVER_USER, {}, `
373
+ ${GH_OWN}
374
+ ${GH_STOP_ASKING}
375
+ rm -f ${GH_FILE}
376
+ `),
377
+ };
378
+ }
379
+ /**
380
+ * Give the server a Claude login: Claude Code is first asked, as the server's own user, whether
381
+ * it reads the login; then the login is written to root's file and OpenAgent is started again
382
+ * with it. A run that is at work goes on with the login it started with. When OpenAgent does not
383
+ * start with it, the file is put back as it was.
384
+ *
385
+ * Claude Code is the server user's own program: root asks it as that user, for half a minute at
386
+ * most, and without the lock root holds, which a program that never ended would hold with it.
387
+ */
388
+ export function claudeLoginScript(login) {
389
+ return {
390
+ as: 'root',
391
+ script: script('root', { LOGIN: login }, `${STARTED}
392
+ # What is written where the service reads its settings is a login's letters and nothing else.
393
+ case "$LOGIN" in '' | *[!A-Za-z0-9_-]*) fail "What was sent is no Claude login." ;; esac
394
+ said=$(${CLAUDE_NAME}="$LOGIN" timeout 30 runuser -u ${SERVER_USER} -- env HOME=/home/${SERVER_USER} /home/${SERVER_USER}/.local/bin/claude auth status 2>&1 9>&- || true)
395
+ printf '%s' "$said" | grep -Eq '"loggedIn": *true' || fail "Claude Code on this server does not take the login."
396
+ install -d -m 755 "$(dirname ${CLAUDE_FILE})"
397
+ rm -f ${CLAUDE_FILE}.old
398
+ [ ! -e ${CLAUDE_FILE} ] || cp -p ${CLAUDE_FILE} ${CLAUDE_FILE}.old
399
+ (umask 077 && printf '${CLAUDE_NAME}=%s\\n' "$LOGIN" > ${CLAUDE_FILE}.new)
400
+ mv ${CLAUDE_FILE}.new ${CLAUDE_FILE}
401
+ with_it() {
402
+ systemctl restart openagent && started &&
403
+ tr '\\0' '\\n' < "/proc/$(systemctl show -p MainPID --value openagent)/environ" | grep -q '^${CLAUDE_NAME}='
404
+ }
405
+ if ! with_it; then
406
+ # As it was: a login that is on the server and not known of on this computer could not be taken off from there.
407
+ if [ -e ${CLAUDE_FILE}.old ]; then mv ${CLAUDE_FILE}.old ${CLAUDE_FILE}; else rm -f ${CLAUDE_FILE}; fi
408
+ systemctl restart openagent || true
409
+ fail "OpenAgent did not start with the login. The server's Claude login is as it was before."
410
+ fi
411
+ rm -f ${CLAUDE_FILE}.old
412
+ `),
413
+ };
414
+ }
415
+ /** Take the Claude login off the server, and start OpenAgent again without it. */
416
+ export function claudeLogoutScript() {
417
+ return {
418
+ as: 'root',
419
+ script: script('root', {}, `${STARTED}
420
+ [ -e ${CLAUDE_FILE} ] || exit 0
421
+ rm -f ${CLAUDE_FILE} ${CLAUDE_FILE}.old
422
+ systemctl restart openagent
423
+ started || ${NOT_STARTED}
424
+ `),
425
+ };
426
+ }
427
+ //# sourceMappingURL=steps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ import { type StepId } from './steps.js';
2
+ /**
3
+ * The servers this computer installed OpenAgent on, in the person's home folder: where each is,
4
+ * who logs in there, and how far its install got. The command and the install's own process both
5
+ * write it, so every change goes through {@link update}.
6
+ *
7
+ * It holds no key, no password and no login: the way in is this computer's own SSH keys, the key
8
+ * the server's door gives is OpenAgent's, kept with its machines, and a GitHub or Claude login
9
+ * goes to the server and is not kept here.
10
+ */
11
+ export type StepState = 'waiting' | 'running' | 'done' | 'failed';
12
+ export interface Step {
13
+ id: StepId;
14
+ state: StepState;
15
+ /** For a step that failed: what the server said, its last lines. */
16
+ said?: string;
17
+ }
18
+ /** The install's own process. */
19
+ export interface Worker {
20
+ pid: number;
21
+ /** ISO 8601: written again every few seconds for as long as the process works. */
22
+ seen: string;
23
+ }
24
+ /** The two logins a server works with. */
25
+ export declare const LOGIN_KINDS: readonly ['github', 'claude'];
26
+ export type LoginKind = (typeof LOGIN_KINDS)[number];
27
+ /**
28
+ * One login of a server, as this computer left it. The login itself is never kept here: only
29
+ * that it was put there, when, and for GitHub whose it is.
30
+ */
31
+ export interface Login {
32
+ /** ISO 8601: when this computer put the login on the server. Absent while the server has none from here. */
33
+ since?: string;
34
+ /** GitHub: the account, as the server's own `gh` answered. */
35
+ account?: string;
36
+ /** GitHub: to be copied once the install's steps are done. */
37
+ wanted?: boolean;
38
+ /** Why the last try ended without a login. */
39
+ failed?: string;
40
+ /** The process that changes the login right now, and when it last said it still does. */
41
+ worker?: Worker;
42
+ /** That process waits for the person: for a yes in this computer's browser (Claude), or for a code typed at GitHub. */
43
+ asking?: boolean;
44
+ /** GitHub: the code the person types at github.com/login/device while the server waits for it. It is no login: it says which waiting server a person's yes is for. */
45
+ code?: string;
46
+ }
47
+ export interface Server {
48
+ /** The server's number or name: what a person typed, and what its certificate is for. */
49
+ address: string;
50
+ /** Who logs in over SSH: `root`, or a user that becomes root without a password. */
51
+ user: string;
52
+ /** What the server's key is known by, as SSH showed it when this computer first met it. */
53
+ fingerprint?: string;
54
+ steps: Step[];
55
+ /** The process that installs right now, and when it last said it still does. */
56
+ worker?: Worker;
57
+ /** The install was asked for and the server is not among the machines yet: the page adds it once the steps are done. */
58
+ adding: boolean;
59
+ /** The GitHub login this computer copied to the server. */
60
+ github?: Login;
61
+ /** The Claude login this computer made for the server. */
62
+ claude?: Login;
63
+ }
64
+ export interface Store {
65
+ servers: Server[];
66
+ }
67
+ /** A cap, so a file edited by hand cannot bloat it. */
68
+ export declare const MAX_SERVERS = 50;
69
+ /** The file's path: beside OpenAgent's own home file, by the same rule. */
70
+ export declare function storePath(env: NodeJS.ProcessEnv): string;
71
+ /**
72
+ * `said` as a server's address is kept, or `undefined` for what is none: a number, or a name, in
73
+ * the one form the server's door keeps it in too, so the two always read the same. Nothing that
74
+ * could be read as an option of a command passes.
75
+ */
76
+ export declare function addressOf(said: string): string | undefined;
77
+ /** Whether `said` is a user name a Linux server has. */
78
+ export declare const isUser: (said: unknown) => said is string;
79
+ /** Whether `said` is a version npm keeps a package under. */
80
+ export declare const isVersion: (said: unknown) => said is string;
81
+ /** Whether `said` reads as the code GitHub gives a device to show: two groups of four. */
82
+ export declare const isGithubCode: (said: unknown) => said is string;
83
+ /** How often the install's process says it still works, and after how long of silence it is taken for gone. */
84
+ export declare const BEAT_MS = 10000;
85
+ export declare const SILENT_MS = 45000;
86
+ /**
87
+ * Say again and again that a work is alive: `beat` every `everyMs`, one at a time, a failed one
88
+ * left at that. `stop` ends it and waits for the one on its way: a beat changes the file, and
89
+ * one that landed after the work's own last word would write when the work has ended.
90
+ */
91
+ export declare function heartbeat(beat: () => Promise<unknown>, everyMs: number): {
92
+ stop(): Promise<void>;
93
+ };
94
+ /**
95
+ * Whether the server's install runs right now: its process is there, and said so a moment ago.
96
+ * The process's number alone is no proof: one that died without a word (a laptop that was
97
+ * switched off) leaves its number in the file, and the system gives it to another program later.
98
+ */
99
+ export declare function working(server: Server, now?: number): boolean;
100
+ /** Whether a process that wrote itself to the file is still at work: it is there, and said so a moment ago. */
101
+ export declare function atWork(worker: Worker | undefined, now?: number): boolean;
102
+ /** Whether `said` is a GitHub account's name. */
103
+ export declare const isAccount: (said: unknown) => said is string;
104
+ /** Where the door of the server at `address` answers: what OpenAgent's machines know it by. */
105
+ export declare function doorUrl(address: string): string;
106
+ /** Every step, not started. */
107
+ export declare const freshSteps: () => Step[];
108
+ /** Read what a file holds, forgivingly: anything malformed is left out, never a reason to fail. */
109
+ export declare function sanitize(value: unknown): Store;
110
+ /** The file is there and cannot be read as what it should hold: a typo made by hand, a disk that failed. */
111
+ export declare class Unreadable extends Error {
112
+ constructor(path: string);
113
+ }
114
+ /** The file as it stands; no file reads as no server. Throws {@link Unreadable} for a file that is there and cannot be read. */
115
+ export declare function readStore(env: NodeJS.ProcessEnv): Promise<Store>;
116
+ /**
117
+ * Change the file: read it, apply `change`, write it back, with no other writer in between.
118
+ * Written to a temporary file and renamed, so a reader never sees half a file, owner-only.
119
+ * `change` answers what the caller wants to know of the change.
120
+ */
121
+ export declare function update<T>(env: NodeJS.ProcessEnv, change: (store: Store) => T): Promise<T>;
122
+ /** Change the one server at `address`, when the file still holds it; answers what `apply` did, or `undefined` for a server that is gone. */
123
+ export declare function changeServer<T>(env: NodeJS.ProcessEnv, address: string, apply: (server: Server) => T): Promise<T | undefined>;
124
+ /** Whether a process with this id is running. */
125
+ export declare function pidAlive(pid: number): boolean;
126
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}