@kdonev/termscape 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +288 -0
- package/dist/agents/env.d.ts +36 -0
- package/dist/agents/env.d.ts.map +1 -0
- package/dist/agents/env.js +61 -0
- package/dist/agents/env.js.map +1 -0
- package/dist/agents/profiles.d.ts +45 -0
- package/dist/agents/profiles.d.ts.map +1 -0
- package/dist/agents/profiles.js +123 -0
- package/dist/agents/profiles.js.map +1 -0
- package/dist/agents/resolve.d.ts +16 -0
- package/dist/agents/resolve.d.ts.map +1 -0
- package/dist/agents/resolve.js +71 -0
- package/dist/agents/resolve.js.map +1 -0
- package/dist/agents/router.d.ts +29 -0
- package/dist/agents/router.d.ts.map +1 -0
- package/dist/agents/router.js +83 -0
- package/dist/agents/router.js.map +1 -0
- package/dist/agents/tokens.d.ts +21 -0
- package/dist/agents/tokens.d.ts.map +1 -0
- package/dist/agents/tokens.js +39 -0
- package/dist/agents/tokens.js.map +1 -0
- package/dist/agents/wiring.d.ts +30 -0
- package/dist/agents/wiring.d.ts.map +1 -0
- package/dist/agents/wiring.js +110 -0
- package/dist/agents/wiring.js.map +1 -0
- package/dist/browser.d.ts +13 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +34 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +200 -0
- package/dist/cli.js.map +1 -0
- package/dist/db/index.d.ts +14 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +24 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations.d.ts +15 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +176 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/store.d.ts +71 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.js +341 -0
- package/dist/db/store.js.map +1 -0
- package/dist/hub.d.ts +188 -0
- package/dist/hub.d.ts.map +1 -0
- package/dist/hub.js +636 -0
- package/dist/hub.js.map +1 -0
- package/dist/hub.tgz +0 -0
- package/dist/mcp/server.d.ts +31 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +66 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/paths.d.ts +27 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +31 -0
- package/dist/paths.js.map +1 -0
- package/dist/protocol/address.d.ts +15 -0
- package/dist/protocol/address.d.ts.map +1 -0
- package/dist/protocol/address.js +39 -0
- package/dist/protocol/address.js.map +1 -0
- package/dist/protocol/domain.d.ts +151 -0
- package/dist/protocol/domain.d.ts.map +1 -0
- package/dist/protocol/domain.js +104 -0
- package/dist/protocol/domain.js.map +1 -0
- package/dist/protocol/index.d.ts +7 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +7 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/inject.d.ts +29 -0
- package/dist/protocol/inject.d.ts.map +1 -0
- package/dist/protocol/inject.js +40 -0
- package/dist/protocol/inject.js.map +1 -0
- package/dist/protocol/mcp-tools.d.ts +45 -0
- package/dist/protocol/mcp-tools.d.ts.map +1 -0
- package/dist/protocol/mcp-tools.js +46 -0
- package/dist/protocol/mcp-tools.js.map +1 -0
- package/dist/protocol/peer.d.ts +304 -0
- package/dist/protocol/peer.d.ts.map +1 -0
- package/dist/protocol/peer.js +183 -0
- package/dist/protocol/peer.js.map +1 -0
- package/dist/protocol/ws.d.ts +416 -0
- package/dist/protocol/ws.d.ts.map +1 -0
- package/dist/protocol/ws.js +119 -0
- package/dist/protocol/ws.js.map +1 -0
- package/dist/remote/deployer.d.ts +78 -0
- package/dist/remote/deployer.d.ts.map +1 -0
- package/dist/remote/deployer.js +216 -0
- package/dist/remote/deployer.js.map +1 -0
- package/dist/remote/enroll.d.ts +14 -0
- package/dist/remote/enroll.d.ts.map +1 -0
- package/dist/remote/enroll.js +244 -0
- package/dist/remote/enroll.js.map +1 -0
- package/dist/remote/join-script.d.ts +29 -0
- package/dist/remote/join-script.d.ts.map +1 -0
- package/dist/remote/join-script.js +709 -0
- package/dist/remote/join-script.js.map +1 -0
- package/dist/remote/join.d.ts +19 -0
- package/dist/remote/join.d.ts.map +1 -0
- package/dist/remote/join.js +195 -0
- package/dist/remote/join.js.map +1 -0
- package/dist/remote/lan.d.ts +45 -0
- package/dist/remote/lan.d.ts.map +1 -0
- package/dist/remote/lan.js +105 -0
- package/dist/remote/lan.js.map +1 -0
- package/dist/remote/peer-serve.d.ts +27 -0
- package/dist/remote/peer-serve.d.ts.map +1 -0
- package/dist/remote/peer-serve.js +274 -0
- package/dist/remote/peer-serve.js.map +1 -0
- package/dist/remote/peer.d.ts +62 -0
- package/dist/remote/peer.d.ts.map +1 -0
- package/dist/remote/peer.js +250 -0
- package/dist/remote/peer.js.map +1 -0
- package/dist/remote/registry.d.ts +104 -0
- package/dist/remote/registry.d.ts.map +1 -0
- package/dist/remote/registry.js +359 -0
- package/dist/remote/registry.js.map +1 -0
- package/dist/remote/tarball.d.ts +9 -0
- package/dist/remote/tarball.d.ts.map +1 -0
- package/dist/remote/tarball.js +22 -0
- package/dist/remote/tarball.js.map +1 -0
- package/dist/server.d.ts +54 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +438 -0
- package/dist/server.js.map +1 -0
- package/dist/session/manager.d.ts +104 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +421 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/pty.d.ts +114 -0
- package/dist/session/pty.d.ts.map +1 -0
- package/dist/session/pty.js +300 -0
- package/dist/session/pty.js.map +1 -0
- package/package.json +49 -0
- package/web/assets/index-B2JtCtHa.css +1 -0
- package/web/assets/index-BOqZOoep.js +107 -0
- package/web/index.html +13 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { basename } from 'node:path';
|
|
4
|
+
import { Client as SshClient } from 'ssh2';
|
|
5
|
+
const DEFAULT_REMOTE_DIR = '~/.termscape';
|
|
6
|
+
function connectConfig(c) {
|
|
7
|
+
const cfg = {
|
|
8
|
+
host: c.sshHost,
|
|
9
|
+
port: c.sshPort,
|
|
10
|
+
username: c.sshUser,
|
|
11
|
+
readyTimeout: 20_000,
|
|
12
|
+
};
|
|
13
|
+
if (c.privateKeyPath) {
|
|
14
|
+
cfg.privateKey = readFileSync(c.privateKeyPath);
|
|
15
|
+
if (c.passphrase)
|
|
16
|
+
cfg.passphrase = c.passphrase;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
// Fall back to the user's running SSH agent, which is how most people
|
|
20
|
+
// already authenticate; we never prompt for or store a password.
|
|
21
|
+
cfg.agent = process.env.SSH_AUTH_SOCK ?? (process.platform === 'win32' ? 'pageant' : undefined);
|
|
22
|
+
}
|
|
23
|
+
return cfg;
|
|
24
|
+
}
|
|
25
|
+
export function sshConnect(c) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const conn = new SshClient();
|
|
28
|
+
conn.on('ready', () => resolve(conn));
|
|
29
|
+
conn.on('error', (err) => reject(new Error(`ssh ${c.sshUser}@${c.sshHost}: ${err.message}`)));
|
|
30
|
+
conn.connect(connectConfig(c));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function exec(conn, command) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
conn.exec(command, (err, stream) => {
|
|
36
|
+
if (err)
|
|
37
|
+
return reject(err);
|
|
38
|
+
let stdout = '';
|
|
39
|
+
let stderr = '';
|
|
40
|
+
stream.on('data', (d) => (stdout += d.toString()));
|
|
41
|
+
stream.stderr.on('data', (d) => (stderr += d.toString()));
|
|
42
|
+
stream.on('close', (code) => resolve({ code: code ?? 0, stdout, stderr }));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** What is already on the remote machine, before we change anything. */
|
|
47
|
+
export async function probe(conn, remoteDir = DEFAULT_REMOTE_DIR) {
|
|
48
|
+
const node = await exec(conn, 'node --version 2>/dev/null || true');
|
|
49
|
+
const nodeVersion = node.stdout.trim() || null;
|
|
50
|
+
const nodeMajor = nodeVersion ? Number(nodeVersion.replace(/^v/, '').split('.')[0]) : 0;
|
|
51
|
+
const installed = await exec(conn, `cat ${remoteDir}/hub/package.json 2>/dev/null | grep '"version"' || true`);
|
|
52
|
+
const m = installed.stdout.match(/"version"\s*:\s*"([^"]+)"/);
|
|
53
|
+
return { nodeVersion, nodeMajor, installedVersion: m?.[1] ?? null };
|
|
54
|
+
}
|
|
55
|
+
async function upload(conn, localPath, remotePath) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
conn.sftp((err, sftp) => {
|
|
58
|
+
if (err)
|
|
59
|
+
return reject(err);
|
|
60
|
+
sftp.fastPut(localPath, remotePath, (e) => (e ? reject(e) : resolve()));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Install or upgrade the hub on the remote machine. Native modules are rebuilt
|
|
66
|
+
* there, because node-pty and better-sqlite3 prebuilds are per platform and
|
|
67
|
+
* per architecture and the remote is frequently neither.
|
|
68
|
+
*/
|
|
69
|
+
export async function provision(conn, opts) {
|
|
70
|
+
const remoteDir = opts.remoteDir ?? DEFAULT_REMOTE_DIR;
|
|
71
|
+
const log = opts.log ?? (() => { });
|
|
72
|
+
if (!opts.packagePath) {
|
|
73
|
+
throw new Error('remote hub is missing or out of date and no package tarball was supplied; ' +
|
|
74
|
+
'run `npm pack -w @termscape/hub` and pass its path');
|
|
75
|
+
}
|
|
76
|
+
const tarball = basename(opts.packagePath);
|
|
77
|
+
log(`uploading ${tarball}`);
|
|
78
|
+
await exec(conn, `mkdir -p ${remoteDir}`);
|
|
79
|
+
await upload(conn, opts.packagePath, `${remoteDir}/${tarball}`);
|
|
80
|
+
log('installing on remote');
|
|
81
|
+
// The dependency tree is the slowest thing to rebuild over there and the
|
|
82
|
+
// tarball never carries one, so it is moved aside rather than deleted along
|
|
83
|
+
// with the install it lives in.
|
|
84
|
+
await exec(conn, `cd ${remoteDir} && rm -rf node_modules.kept` +
|
|
85
|
+
` && if [ -d hub/node_modules ]; then mv hub/node_modules node_modules.kept; fi` +
|
|
86
|
+
` && rm -rf hub && mkdir -p hub && tar xzf ${tarball} -C hub --strip-components=1` +
|
|
87
|
+
` && if [ -d node_modules.kept ]; then mv node_modules.kept hub/node_modules; fi`);
|
|
88
|
+
// What the tree already there has to match to be worth keeping: the
|
|
89
|
+
// fingerprint the tarball shipped, plus the two things it cannot know - the
|
|
90
|
+
// Node ABI those modules were built against, and the platform.
|
|
91
|
+
const stamp = `"$(cat deps.fingerprint 2>/dev/null || echo none)-` +
|
|
92
|
+
`$(node -p 'process.versions.node.split(".")[0] + "-" + process.platform + "-" + process.arch')"`;
|
|
93
|
+
// The stamp claims the tree still works; the require confirms it does.
|
|
94
|
+
// Trusting the stamp alone would trade a slow deploy for a remote hub that
|
|
95
|
+
// cannot load its own modules.
|
|
96
|
+
const reusable = await exec(conn, `cd ${remoteDir}/hub && [ -d node_modules ]` +
|
|
97
|
+
` && [ "$(cat ../deps.stamp 2>/dev/null)" = ${stamp} ]` +
|
|
98
|
+
` && node -e 'require("node-pty"); require("better-sqlite3")'`);
|
|
99
|
+
if (reusable.code === 0) {
|
|
100
|
+
log('dependencies unchanged; keeping the modules already there');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// A half-finished tree must not inherit the last deploy's stamp.
|
|
104
|
+
await exec(conn, `rm -f ${remoteDir}/deps.stamp`);
|
|
105
|
+
// Prebuilt binaries first: node-pty and better-sqlite3 publish them for the
|
|
106
|
+
// mainstream platforms, and downloading one beats compiling it every time.
|
|
107
|
+
let install = await exec(conn, `cd ${remoteDir}/hub && npm install --omit=dev --no-audit --no-fund 2>&1 | tail -20`);
|
|
108
|
+
if (install.code !== 0) {
|
|
109
|
+
log('no prebuilt binaries for this platform; compiling instead');
|
|
110
|
+
install = await exec(conn, `cd ${remoteDir}/hub && npm install --omit=dev --no-audit --no-fund --build-from-source 2>&1 | tail -20`);
|
|
111
|
+
}
|
|
112
|
+
if (install.code !== 0) {
|
|
113
|
+
throw new Error(`remote install failed (exit ${install.code}). This host has no prebuilt ` +
|
|
114
|
+
`binaries and no C++ toolchain for node-pty and better-sqlite3.\n` +
|
|
115
|
+
`${install.stdout}\n${install.stderr}`);
|
|
116
|
+
}
|
|
117
|
+
await exec(conn, `cd ${remoteDir}/hub && printf '%s' ${stamp} > ../deps.stamp`);
|
|
118
|
+
log('installed');
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Start the remote hub detached, so it outlives this SSH session — the whole
|
|
122
|
+
* reason for running a daemon instead of an ssh subprocess.
|
|
123
|
+
*/
|
|
124
|
+
export async function startRemoteHub(conn, opts) {
|
|
125
|
+
const remoteDir = opts.remoteDir ?? DEFAULT_REMOTE_DIR;
|
|
126
|
+
const logFile = `${remoteDir}/hub.log`;
|
|
127
|
+
// Reuse an already-running daemon rather than starting a second one.
|
|
128
|
+
const existing = await exec(conn, `cat ${remoteDir}/hub.port 2>/dev/null || true`);
|
|
129
|
+
const existingPort = Number(existing.stdout.trim());
|
|
130
|
+
if (existingPort > 0) {
|
|
131
|
+
const alive = await exec(conn, `curl -s -m 2 http://127.0.0.1:${existingPort}/health || true`);
|
|
132
|
+
const m = alive.stdout.match(/"hubVersion"\s*:\s*"([^"]+)"/);
|
|
133
|
+
if (m)
|
|
134
|
+
return { port: existingPort, version: m[1] };
|
|
135
|
+
}
|
|
136
|
+
await exec(conn, `cd ${remoteDir}/hub && TERMSCAPE_HOME=${remoteDir} nohup node dist/cli.js --headless --port 0 --token '${opts.token}' > ${logFile} 2>&1 &`);
|
|
137
|
+
// The CLI prints TERMSCAPE_PORT=<n> precisely so this can be parsed.
|
|
138
|
+
for (let i = 0; i < 40; i++) {
|
|
139
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
140
|
+
const out = await exec(conn, `grep -m1 TERMSCAPE_PORT= ${logFile} 2>/dev/null || true`);
|
|
141
|
+
const m = out.stdout.match(/TERMSCAPE_PORT=(\d+)/);
|
|
142
|
+
if (m) {
|
|
143
|
+
const port = Number(m[1]);
|
|
144
|
+
await exec(conn, `echo ${port} > ${remoteDir}/hub.port`);
|
|
145
|
+
const v = await exec(conn, `curl -s -m 3 http://127.0.0.1:${port}/health || true`);
|
|
146
|
+
const vm = v.stdout.match(/"hubVersion"\s*:\s*"([^"]+)"/);
|
|
147
|
+
return { port, version: vm?.[1] ?? 'unknown' };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const tail = await exec(conn, `tail -30 ${logFile} 2>/dev/null || true`);
|
|
151
|
+
throw new Error(`remote hub did not report a port.\n${tail.stdout}`);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Local TCP listener that pipes every connection through the SSH channel to
|
|
155
|
+
* the remote hub's loopback port. Gives us a plain ws:// URL to hand the peer
|
|
156
|
+
* client, which keeps SSH entirely out of the peer protocol.
|
|
157
|
+
*/
|
|
158
|
+
export function openTunnel(conn, remotePort) {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
const server = createServer((socket) => {
|
|
161
|
+
conn.forwardOut('127.0.0.1', 0, '127.0.0.1', remotePort, (err, stream) => {
|
|
162
|
+
if (err) {
|
|
163
|
+
socket.destroy();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
socket.pipe(stream).pipe(socket);
|
|
167
|
+
stream.on('error', () => socket.destroy());
|
|
168
|
+
socket.on('error', () => stream.destroy());
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
server.on('error', reject);
|
|
172
|
+
// Loopback only: the tunnel entrance must not be reachable from the network.
|
|
173
|
+
server.listen(0, '127.0.0.1', () => {
|
|
174
|
+
const addr = server.address();
|
|
175
|
+
if (!addr || typeof addr === 'string')
|
|
176
|
+
return reject(new Error('tunnel bind failed'));
|
|
177
|
+
resolve({ localPort: addr.port, server });
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/** Probe, provision if needed, start, and tunnel. */
|
|
182
|
+
export async function deploy(opts) {
|
|
183
|
+
const log = opts.log ?? (() => { });
|
|
184
|
+
const conn = await sshConnect(opts);
|
|
185
|
+
try {
|
|
186
|
+
const p = await probe(conn, opts.remoteDir);
|
|
187
|
+
if (!p.nodeVersion) {
|
|
188
|
+
throw new Error('no node on the remote host; install Node 22 or newer there first');
|
|
189
|
+
}
|
|
190
|
+
if (p.nodeMajor < 22) {
|
|
191
|
+
throw new Error(`remote node is ${p.nodeVersion}; this hub needs Node 22 or newer`);
|
|
192
|
+
}
|
|
193
|
+
log(`remote node ${p.nodeVersion}, hub ${p.installedVersion ?? 'not installed'}`);
|
|
194
|
+
if (p.installedVersion !== opts.expectedVersion) {
|
|
195
|
+
await provision(conn, opts);
|
|
196
|
+
}
|
|
197
|
+
const { port, version } = await startRemoteHub(conn, opts);
|
|
198
|
+
log(`remote hub ${version} on 127.0.0.1:${port}`);
|
|
199
|
+
const { localPort, server } = await openTunnel(conn, port);
|
|
200
|
+
log(`tunnel ready on 127.0.0.1:${localPort}`);
|
|
201
|
+
return {
|
|
202
|
+
localUrl: `ws://127.0.0.1:${localPort}/peer`,
|
|
203
|
+
remotePort: port,
|
|
204
|
+
remoteHubVersion: version,
|
|
205
|
+
dispose: async () => {
|
|
206
|
+
await new Promise((res) => server.close(() => res()));
|
|
207
|
+
conn.end();
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
conn.end();
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=deployer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployer.js","sourceRoot":"","sources":["../../src/remote/deployer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { Hub } from '../hub.js';
|
|
3
|
+
export interface EnrollOptions {
|
|
4
|
+
hub: Hub;
|
|
5
|
+
/** Origin another machine can reach us on, or null when we are loopback-bound. */
|
|
6
|
+
enrollOrigin: () => string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface Enrollment {
|
|
9
|
+
/** Mint a single-use token for one download. */
|
|
10
|
+
mint(): string;
|
|
11
|
+
pendingCount(): number;
|
|
12
|
+
}
|
|
13
|
+
export declare function registerEnrollment(app: FastifyInstance, opts: EnrollOptions): Enrollment;
|
|
14
|
+
//# sourceMappingURL=enroll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enroll.d.ts","sourceRoot":"","sources":["../../src/remote/enroll.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAQ/C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AA0BrC,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,GAAG,CAAC;IACT,kFAAkF;IAClF,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,UAAU;IACzB,gDAAgD;IAChD,IAAI,IAAI,MAAM,CAAC;IACf,YAAY,IAAI,MAAM,CAAC;CACxB;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,aAAa,GAClB,UAAU,CAoKZ"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { PeerRequest, PEER_SCHEMA_VERSION, } from '../protocol/index.js';
|
|
4
|
+
import { HUB_VERSION } from '../hub.js';
|
|
5
|
+
import { joinScriptPosix, joinScriptPowerShell } from './join-script.js';
|
|
6
|
+
import { hubTarballPath } from './tarball.js';
|
|
7
|
+
/**
|
|
8
|
+
* The pull half of attaching a machine.
|
|
9
|
+
*
|
|
10
|
+
* Instead of this hub reaching out over SSH, the other machine opens this
|
|
11
|
+
* hub's page, downloads an installer, runs it, and dials back. That inverts
|
|
12
|
+
* who needs credentials — nobody types an SSH key here — and it puts install
|
|
13
|
+
* failures in the terminal of the person who can actually fix them.
|
|
14
|
+
*
|
|
15
|
+
* Enrollment tokens are single-use and short-lived, and live in memory only.
|
|
16
|
+
* A hub restart invalidates outstanding downloads, which is the right trade
|
|
17
|
+
* for a credential that is supposed to be spent within minutes of minting.
|
|
18
|
+
*/
|
|
19
|
+
const ENROLL_TTL_MS = 15 * 60 * 1000;
|
|
20
|
+
/**
|
|
21
|
+
* Minting is unauthenticated, so it has to be bounded. Nobody has 64 machines
|
|
22
|
+
* mid-join at once; anything past that is someone hammering the page, and the
|
|
23
|
+
* oldest unspent token is the safest thing to drop.
|
|
24
|
+
*/
|
|
25
|
+
const MAX_PENDING = 64;
|
|
26
|
+
export function registerEnrollment(app, opts) {
|
|
27
|
+
const { hub, enrollOrigin } = opts;
|
|
28
|
+
/** token -> expiry. Single-use: matching deletes. */
|
|
29
|
+
const pending = new Map();
|
|
30
|
+
function sweep() {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
for (const [token, expires] of pending) {
|
|
33
|
+
if (expires <= now)
|
|
34
|
+
pending.delete(token);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function mint() {
|
|
38
|
+
sweep();
|
|
39
|
+
while (pending.size >= MAX_PENDING) {
|
|
40
|
+
// Insertion-ordered, so the first key is the oldest still-unspent token.
|
|
41
|
+
pending.delete(pending.keys().next().value);
|
|
42
|
+
}
|
|
43
|
+
const token = randomBytes(24).toString('base64url');
|
|
44
|
+
pending.set(token, Date.now() + ENROLL_TTL_MS);
|
|
45
|
+
return token;
|
|
46
|
+
}
|
|
47
|
+
function consume(token) {
|
|
48
|
+
sweep();
|
|
49
|
+
if (!pending.has(token))
|
|
50
|
+
return false;
|
|
51
|
+
pending.delete(token);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
/* ------------------------------------------------------------- the page */
|
|
55
|
+
/**
|
|
56
|
+
* The one route served without the client token. It is reachable only when
|
|
57
|
+
* the hub was deliberately bound off loopback, and it exposes nothing but a
|
|
58
|
+
* command to copy.
|
|
59
|
+
*/
|
|
60
|
+
app.get('/join', async (_req, reply) => {
|
|
61
|
+
const origin = enrollOrigin();
|
|
62
|
+
if (!origin) {
|
|
63
|
+
return reply
|
|
64
|
+
.code(404)
|
|
65
|
+
.type('text/html; charset=utf-8')
|
|
66
|
+
.send(notReachablePage());
|
|
67
|
+
}
|
|
68
|
+
return reply
|
|
69
|
+
.type('text/html; charset=utf-8')
|
|
70
|
+
.header('cache-control', 'no-store')
|
|
71
|
+
.send(joinPage(origin));
|
|
72
|
+
});
|
|
73
|
+
app.get('/join.sh', async (_req, reply) => {
|
|
74
|
+
const origin = enrollOrigin();
|
|
75
|
+
if (!origin)
|
|
76
|
+
return reply.code(404).send('# this hub is not reachable from other machines\n');
|
|
77
|
+
return reply
|
|
78
|
+
.type('text/x-shellscript; charset=utf-8')
|
|
79
|
+
.header('cache-control', 'no-store')
|
|
80
|
+
.send(joinScriptPosix(origin, mint()));
|
|
81
|
+
});
|
|
82
|
+
app.get('/join.ps1', async (_req, reply) => {
|
|
83
|
+
const origin = enrollOrigin();
|
|
84
|
+
if (!origin)
|
|
85
|
+
return reply.code(404).send('# this hub is not reachable from other machines\n');
|
|
86
|
+
return reply
|
|
87
|
+
.type('text/plain; charset=utf-8')
|
|
88
|
+
.header('cache-control', 'no-store')
|
|
89
|
+
.send(joinScriptPowerShell(origin, mint()));
|
|
90
|
+
});
|
|
91
|
+
app.get('/hub.tgz', async (_req, reply) => {
|
|
92
|
+
const tarball = hubTarballPath();
|
|
93
|
+
if (!tarball) {
|
|
94
|
+
return reply
|
|
95
|
+
.code(503)
|
|
96
|
+
.send('hub package not built; run `npm run build` on the hub machine\n');
|
|
97
|
+
}
|
|
98
|
+
return reply.type('application/gzip').send(createReadStream(tarball));
|
|
99
|
+
});
|
|
100
|
+
/* --------------------------------------------------------- inbound peers */
|
|
101
|
+
/**
|
|
102
|
+
* A host dialling in. It speaks first, exactly as we would if we had dialled
|
|
103
|
+
* it; the only difference is that its token is an enrollment or host token
|
|
104
|
+
* rather than one we minted for a deploy.
|
|
105
|
+
*/
|
|
106
|
+
app.get('/peer-in', { websocket: true }, (socket) => {
|
|
107
|
+
let settled = false;
|
|
108
|
+
const reply = (msg) => {
|
|
109
|
+
if (socket.readyState === socket.OPEN)
|
|
110
|
+
socket.send(JSON.stringify(msg));
|
|
111
|
+
};
|
|
112
|
+
const refuse = (message) => {
|
|
113
|
+
reply({ t: 'err', id: 'hello', message });
|
|
114
|
+
socket.close();
|
|
115
|
+
};
|
|
116
|
+
const onHello = (raw) => {
|
|
117
|
+
// Everything after the handshake belongs to the PeerConnection that
|
|
118
|
+
// adopts this socket, not to us.
|
|
119
|
+
if (settled)
|
|
120
|
+
return;
|
|
121
|
+
let parsed;
|
|
122
|
+
try {
|
|
123
|
+
parsed = PeerRequest.safeParse(JSON.parse(raw.toString('utf8')));
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return refuse('unparseable hello');
|
|
127
|
+
}
|
|
128
|
+
if (!parsed.success || parsed.data.t !== 'hello') {
|
|
129
|
+
return refuse('expected hello');
|
|
130
|
+
}
|
|
131
|
+
const hello = parsed.data;
|
|
132
|
+
if (hello.schemaVersion !== PEER_SCHEMA_VERSION) {
|
|
133
|
+
return refuse(`peer schema mismatch: you speak v${hello.schemaVersion}, this hub speaks ` +
|
|
134
|
+
`v${PEER_SCHEMA_VERSION}. Re-run the join command to get a matching hub.`);
|
|
135
|
+
}
|
|
136
|
+
let host = null;
|
|
137
|
+
let issuedToken;
|
|
138
|
+
const known = hub.store.hostByToken(hello.token);
|
|
139
|
+
if (known) {
|
|
140
|
+
// A host we have seen before, coming back after a restart or a drop.
|
|
141
|
+
host = { ...known, hubVersion: hello.hubVersion };
|
|
142
|
+
hub.store.upsertHost(host);
|
|
143
|
+
}
|
|
144
|
+
else if (consume(hello.token)) {
|
|
145
|
+
if (!hello.enroll)
|
|
146
|
+
return refuse('enrollment token used without host details');
|
|
147
|
+
issuedToken = randomBytes(24).toString('base64url');
|
|
148
|
+
host = {
|
|
149
|
+
id: randomUUID(),
|
|
150
|
+
label: hello.enroll.label,
|
|
151
|
+
kind: 'enrolled',
|
|
152
|
+
sshHost: null,
|
|
153
|
+
sshUser: null,
|
|
154
|
+
sshPort: 22,
|
|
155
|
+
platform: `${hello.enroll.platform}-${hello.enroll.arch}`,
|
|
156
|
+
hubVersion: hello.hubVersion,
|
|
157
|
+
state: 'connecting',
|
|
158
|
+
lastSeenAt: null,
|
|
159
|
+
error: null,
|
|
160
|
+
};
|
|
161
|
+
hub.store.upsertHost({ ...host, hostToken: issuedToken });
|
|
162
|
+
hub.emit('host', host);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
return refuse('unknown or expired enrollment token; open the join page again');
|
|
166
|
+
}
|
|
167
|
+
settled = true;
|
|
168
|
+
reply({
|
|
169
|
+
t: 'welcome',
|
|
170
|
+
hubVersion: HUB_VERSION,
|
|
171
|
+
schemaVersion: PEER_SCHEMA_VERSION,
|
|
172
|
+
...(issuedToken ? { hostToken: issuedToken } : {}),
|
|
173
|
+
});
|
|
174
|
+
hub.peers.addInbound(host, socket, hello.hubVersion);
|
|
175
|
+
};
|
|
176
|
+
socket.on('message', onHello);
|
|
177
|
+
});
|
|
178
|
+
return { mint, pendingCount: () => (sweep(), pending.size) };
|
|
179
|
+
}
|
|
180
|
+
/* ------------------------------------------------------------------ pages */
|
|
181
|
+
const PAGE_CSS = `
|
|
182
|
+
:root { color-scheme: dark; }
|
|
183
|
+
body {
|
|
184
|
+
margin: 0; padding: 48px 24px;
|
|
185
|
+
background: #0a0c10; color: #d5dae2;
|
|
186
|
+
font: 14px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
187
|
+
}
|
|
188
|
+
main { max-width: 660px; margin: 0 auto; }
|
|
189
|
+
h1 { font-size: 22px; font-weight: 600; margin: 0 0 4px; }
|
|
190
|
+
p.lede { color: #7c8596; margin: 0 0 32px; }
|
|
191
|
+
h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .08em;
|
|
192
|
+
color: #7c8596; font-weight: 600; margin: 28px 0 8px; }
|
|
193
|
+
pre {
|
|
194
|
+
background: #12161e; border: 1px solid #262d3b; border-radius: 6px;
|
|
195
|
+
padding: 12px 14px; overflow-x: auto; margin: 0;
|
|
196
|
+
font: 12.5px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
197
|
+
color: #d5dae2;
|
|
198
|
+
}
|
|
199
|
+
.note { color: #7c8596; font-size: 12.5px; margin-top: 10px; }
|
|
200
|
+
.warn { color: #d8b271; }
|
|
201
|
+
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
202
|
+
`;
|
|
203
|
+
function joinPage(origin) {
|
|
204
|
+
return `<!doctype html>
|
|
205
|
+
<meta charset="utf-8">
|
|
206
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
207
|
+
<title>Join this canvas</title>
|
|
208
|
+
<style>${PAGE_CSS}</style>
|
|
209
|
+
<main>
|
|
210
|
+
<h1>Add this machine to the canvas</h1>
|
|
211
|
+
<p class="lede">Run one of these here. It installs the hub under
|
|
212
|
+
<code>~/.termscape</code> and connects back to <code>${escapeHtml(origin)}</code>.</p>
|
|
213
|
+
|
|
214
|
+
<h2>macOS · Linux</h2>
|
|
215
|
+
<pre>curl -fsSL ${escapeHtml(origin)}/join.sh | sh</pre>
|
|
216
|
+
|
|
217
|
+
<h2>Windows (PowerShell)</h2>
|
|
218
|
+
<pre>irm ${escapeHtml(origin)}/join.ps1 | iex</pre>
|
|
219
|
+
|
|
220
|
+
<p class="note">Needs Node 22 or newer. The installer says exactly what is
|
|
221
|
+
missing before it changes anything.</p>
|
|
222
|
+
<p class="note warn">Each download carries its own single-use key, good for
|
|
223
|
+
15 minutes. Run the command soon after copying it.</p>
|
|
224
|
+
</main>
|
|
225
|
+
`;
|
|
226
|
+
}
|
|
227
|
+
function notReachablePage() {
|
|
228
|
+
return `<!doctype html>
|
|
229
|
+
<meta charset="utf-8">
|
|
230
|
+
<title>Not reachable</title>
|
|
231
|
+
<style>${PAGE_CSS}</style>
|
|
232
|
+
<main>
|
|
233
|
+
<h1>This hub is bound to loopback</h1>
|
|
234
|
+
<p class="lede">Nothing outside this machine can reach it, so there is
|
|
235
|
+
nothing to join yet.</p>
|
|
236
|
+
<p class="note">Restart the hub with <code>--listen lan</code> to make it
|
|
237
|
+
reachable on your local network, then reload this page.</p>
|
|
238
|
+
</main>
|
|
239
|
+
`;
|
|
240
|
+
}
|
|
241
|
+
function escapeHtml(s) {
|
|
242
|
+
return s.replace(/[&<>"]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=enroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enroll.js","sourceRoot":"","sources":["../../src/remote/enroll.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The installers a joining machine downloads and runs.
|
|
3
|
+
*
|
|
4
|
+
* They are generated rather than shipped as files so the hub's own origin and
|
|
5
|
+
* a freshly minted single-use token are baked in — the person running this has
|
|
6
|
+
* nothing to configure and nothing to paste twice.
|
|
7
|
+
*
|
|
8
|
+
* Both scripts follow the same order, and it matters: work out what is missing
|
|
9
|
+
* first, and say precisely what is happening *before* changing anything. The
|
|
10
|
+
* SSH deploy path could only report a failure as a wall of build output in a
|
|
11
|
+
* tooltip; here the message lands in the terminal of the person standing at
|
|
12
|
+
* the keyboard.
|
|
13
|
+
*
|
|
14
|
+
* They are also deliberately chatty. Two steps take a minute or more — pulling
|
|
15
|
+
* ~30MB of Node, and an `npm install` that may compile native modules — and a
|
|
16
|
+
* terminal that prints nothing for that long is indistinguishable from one
|
|
17
|
+
* that has hung. Every long step therefore says what it is about to do, ticks
|
|
18
|
+
* while it runs, and reports how long it took.
|
|
19
|
+
*
|
|
20
|
+
* When Node is missing or too old, a private copy is fetched into
|
|
21
|
+
* `~/.termscape/node` rather than installed system-wide. That is deliberate:
|
|
22
|
+
* it needs no administrator rights, no package manager, and no PATH surgery
|
|
23
|
+
* that a fresh shell would have to pick up; and uninstalling is deleting one
|
|
24
|
+
* directory. The download is checksum-verified against nodejs.org's own
|
|
25
|
+
* SHASUMS256.txt, because this is a binary we are about to execute.
|
|
26
|
+
*/
|
|
27
|
+
export declare function joinScriptPosix(origin: string, token: string): string;
|
|
28
|
+
export declare function joinScriptPowerShell(origin: string, token: string): string;
|
|
29
|
+
//# sourceMappingURL=join-script.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"join-script.d.ts","sourceRoot":"","sources":["../../src/remote/join-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAUH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CA8SrE;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAoX1E"}
|