cmux-ssh-here 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.
Files changed (3) hide show
  1. package/README.md +39 -0
  2. package/bin.js +90 -0
  3. package/package.json +28 -0
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # cmux-ssh-here
2
+
3
+ Одной командой поднимает на этой машине эфемерный SSH-сервер с авторизацией по одноразовому токену и печатает [cmux SSH deep link](https://cmux.com/ru/docs/ssh). Кто откроет ссылку в [cmux](https://cmux.com) — мгновенно попадёт в shell под текущим пользователем по локальной сети, без настройки `sshd`, ключей и паролей.
4
+
5
+ ```bash
6
+ npx cmux-ssh-here
7
+ ```
8
+
9
+ Вывод:
10
+
11
+ ```
12
+ Подключение к этой машине через cmux по LAN
13
+ (shell под пользователем you, Ctrl-C тут — выключить):
14
+
15
+ https://cmux.com/deeplink/ssh?host=192.168.1.42&port=52968&user=wudUKicRFRw3&host-key-policy=accept-new&title=your-mac
16
+ ```
17
+
18
+ Открой ссылку — cmux подключится сам. `Ctrl-C` в терминале выключает сервер.
19
+
20
+ ## Как это работает
21
+
22
+ - Свой SSH-сервер (`ssh2`) с эфемерными host-key и токеном — живут только пока запущен процесс.
23
+ - Токен передаётся в `user=` диплинка; сервер пускает только с верным токеном.
24
+ - Диплинк cmux намеренно не несёт паролей/ключей, поэтому секрет — это сам токен.
25
+ - Полноценный shell через PTY (`node-pty`), с поддержкой размера окна.
26
+
27
+ ## Требования
28
+
29
+ - Node 18+.
30
+ - [cmux](https://cmux.com) на устройстве, с которого открываешь ссылку.
31
+ - macOS или Linux (на хосте, который раздаёт shell). Windows-хост не поддержан.
32
+
33
+ ## Безопасность
34
+
35
+ ⚠️ **Токен в ссылке — bearer-секрет, дающий shell под твоим пользователем.** Только для доверенной локальной сети. Не публикуй ссылку и не шли по недоверенным каналам. Закрыл терминал — токен и host-key мертвы.
36
+
37
+ ## Опции
38
+
39
+ - `PORT=2222 npx cmux-ssh-here` — фиксированный порт (по умолчанию случайный свободный).
package/bin.js ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ // npx cmux-ssh-here
3
+ // Поднимает эфемерный SSH-сервер с авторизацией по токену и печатает cmux deep link.
4
+ // Любой в LAN, открывший ссылку, попадает в shell под текущим пользователем.
5
+ // ponytail: токен = bearer-секрет. Для доверенной локальной сети, не для интернета.
6
+ import ssh2 from "ssh2"; // ponytail: ssh2 — CJS, именованного экспорта нет
7
+ const { Server } = ssh2;
8
+ import { generateKeyPairSync, randomBytes } from "node:crypto";
9
+ import os from "node:os";
10
+ import { chmodSync } from "node:fs";
11
+ import { createRequire } from "node:module";
12
+ import { dirname, join } from "node:path";
13
+
14
+ // ponytail: prebuilt spawn-helper иногда распаковывается без +x (баг упаковки node-pty) — чиним до import
15
+ if (process.platform !== "win32") {
16
+ try {
17
+ const root = dirname(dirname(createRequire(import.meta.url).resolve("node-pty")));
18
+ chmodSync(join(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"), 0o755);
19
+ } catch {}
20
+ }
21
+ const { default: pty } = await import("node-pty");
22
+
23
+ const token = randomBytes(9).toString("base64url"); // секрет в ссылке (user=<token>)
24
+ const { privateKey } = generateKeyPairSync("rsa", {
25
+ // ponytail: rsa PEM — ssh2 парсит host-key без сюрпризов (ed25519 PKCS8 ssh2 берёт не всегда)
26
+ modulusLength: 2048,
27
+ privateKeyEncoding: { type: "pkcs1", format: "pem" },
28
+ publicKeyEncoding: { type: "spki", format: "pem" },
29
+ });
30
+
31
+ const lanIP = () =>
32
+ Object.values(os.networkInterfaces())
33
+ .flat()
34
+ .find((i) => i?.family === "IPv4" && !i.internal && !i.address.startsWith("169.254"))?.address;
35
+
36
+ const server = new Server({ hostKeys: [privateKey] }, (client) => {
37
+ client.on("authentication", (ctx) =>
38
+ ctx.username === token ? ctx.accept() : ctx.reject()
39
+ );
40
+ client.on("session", (accept) => {
41
+ const session = accept();
42
+ let info = { term: "xterm-256color", cols: 80, rows: 24 };
43
+ session.on("pty", (a, _r, i) => {
44
+ info = i;
45
+ a?.();
46
+ });
47
+ session.on("shell", (acc) => {
48
+ const stream = acc();
49
+ const shell = pty.spawn(process.env.SHELL || "/bin/zsh", [], {
50
+ name: info.term,
51
+ cols: info.cols,
52
+ rows: info.rows,
53
+ cwd: os.homedir(),
54
+ env: process.env,
55
+ });
56
+ shell.onData((d) => stream.write(d));
57
+ stream.on("data", (d) => shell.write(d));
58
+ session.on("window-change", (a, _r, i) => {
59
+ shell.resize(i.cols, i.rows);
60
+ a?.();
61
+ });
62
+ shell.onExit(() => stream.end());
63
+ });
64
+ });
65
+ });
66
+
67
+ server.on("error", (e) => {
68
+ console.error(`Ошибка сервера: ${e.message}`);
69
+ process.exit(1);
70
+ });
71
+
72
+ const PORT = Number(process.env.PORT) || 0; // 0 = случайный свободный порт
73
+ server.listen(PORT, "0.0.0.0", function () {
74
+ const ip = lanIP();
75
+ if (!ip) {
76
+ console.error("Нет LAN IPv4-адреса");
77
+ process.exit(1);
78
+ }
79
+ const port = this.address().port;
80
+ const params = new URLSearchParams({
81
+ host: ip,
82
+ port: String(port),
83
+ user: token,
84
+ "host-key-policy": "accept-new",
85
+ title: os.hostname(),
86
+ });
87
+ console.log(`\n Подключение к этой машине через cmux по LAN`);
88
+ console.log(` (shell под пользователем ${os.userInfo().username}, Ctrl-C тут — выключить):\n`);
89
+ console.log(` https://cmux.com/deeplink/ssh?${params}\n`);
90
+ });
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "cmux-ssh-here",
3
+ "version": "0.1.0",
4
+ "description": "Spin up a token-auth SSH server and print a cmux deep link to connect over LAN",
5
+ "type": "module",
6
+ "bin": {
7
+ "cmux-ssh-here": "bin.js"
8
+ },
9
+ "files": [
10
+ "bin.js",
11
+ "README.md"
12
+ ],
13
+ "keywords": [
14
+ "cmux",
15
+ "ssh",
16
+ "deep-link",
17
+ "lan",
18
+ "remote"
19
+ ],
20
+ "license": "MIT",
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "dependencies": {
25
+ "node-pty": "^1.0.0",
26
+ "ssh2": "^1.16.0"
27
+ }
28
+ }