remote-dsh 0.4.9 → 0.5.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/dist/bin.js +288 -194
- package/package.json +3 -3
package/dist/bin.js
CHANGED
|
@@ -2,84 +2,57 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* rdsh CLI 入口(手写参数解析,零依赖)。
|
|
4
4
|
*
|
|
5
|
-
* rdsh
|
|
6
|
-
* rdsh join <hub-url> [--token <t>] [--
|
|
5
|
+
* rdsh host setup lan|cloud
|
|
6
|
+
* rdsh host join <hub-url> [--token <t>] [--name <n>] [--dsh <p>] [--insecure]
|
|
7
|
+
* rdsh host serve | service install|status|uninstall | leave | user ...
|
|
7
8
|
* rdsh hub serve|user|host|service ... [--config <path>]
|
|
8
|
-
* rdsh user add|passwd <name> | ls | rm <name> [--config <path>]
|
|
9
|
-
* rdsh service install|uninstall|status [--config <path>]
|
|
10
9
|
* rdsh --version | --help
|
|
11
10
|
*/
|
|
12
11
|
import { createInterface } from "node:readline";
|
|
13
|
-
import {
|
|
12
|
+
import { rm } from "node:fs/promises";
|
|
13
|
+
import { hostname as osHostname } from "node:os";
|
|
14
|
+
import { dirname } from "node:path";
|
|
15
|
+
import { serve, join, registerJoin, findDsh, selfRevoke, readPersistedToken, clearPersistedToken, UserManager, resolveConfigPath, loadConfig, saveConfig, installService, uninstallService, serviceStatus, HOST_SERVICE_NAME, JOIN_SERVICE_NAME, HUB_SERVICE_NAME, } from "rdsh-gateway";
|
|
14
16
|
import { loadHubConfig, resolveHubConfigPath, HubDb, hashPassword, serveHub, } from "rdsh-hub";
|
|
15
17
|
import { VERSION } from "./index.js";
|
|
16
18
|
const HELP = `rdsh — secure remote access for DeepSeek Harness
|
|
17
19
|
|
|
18
20
|
Usage:
|
|
19
|
-
rdsh
|
|
20
|
-
rdsh join <hub-url>
|
|
21
|
-
rdsh
|
|
22
|
-
rdsh
|
|
23
|
-
rdsh
|
|
24
|
-
rdsh
|
|
25
|
-
rdsh hub
|
|
26
|
-
rdsh hub
|
|
27
|
-
rdsh
|
|
28
|
-
rdsh service
|
|
21
|
+
rdsh host setup lan|cloud Configure this machine (LAN / cloud HTTPS gateway)
|
|
22
|
+
rdsh host join <hub-url> Connect this machine to a hub (outbound tunnel)
|
|
23
|
+
rdsh host serve Run the configured mode in the foreground
|
|
24
|
+
rdsh host service ... Run as a systemd/launchd service
|
|
25
|
+
rdsh host leave Unregister this machine from the hub
|
|
26
|
+
rdsh host user ... Manage gateway users (LAN/cloud password auth)
|
|
27
|
+
rdsh hub serve Start the hub server (cloud, multi-host)
|
|
28
|
+
rdsh hub user add|passwd|rm|ls
|
|
29
|
+
rdsh hub host ls|revoke List / revoke hosts (revoke drops the tunnel instantly)
|
|
30
|
+
rdsh hub service ... Run hub as a systemd/launchd service
|
|
29
31
|
|
|
30
32
|
Global:
|
|
31
|
-
--config <path>
|
|
32
|
-
|
|
33
|
-
--version, -v
|
|
34
|
-
--help, -h
|
|
35
|
-
|
|
36
|
-
Options (serve):
|
|
37
|
-
--port <n> Listen port (overrides config; default 8443)
|
|
38
|
-
--host <ip> Bind address (overrides config; default 0.0.0.0)
|
|
39
|
-
--pair-code <code> Preset pairing code (overrides config)
|
|
40
|
-
--session-ttl <sec> Session cookie lifetime (overrides config; default 43200)
|
|
41
|
-
--dsh <path> dsh executable path (overrides config)
|
|
42
|
-
--reset Rotate session keys (all devices must re-auth)
|
|
43
|
-
--no-code Disable auth (trusted network ONLY!)
|
|
44
|
-
|
|
45
|
-
Options (join):
|
|
46
|
-
--token <t> Use an existing host token (skip pairing-code flow)
|
|
47
|
-
--reset Forget the persisted host token and re-pair
|
|
48
|
-
--dsh <path> dsh executable path
|
|
49
|
-
|
|
50
|
-
Options (hub serve):
|
|
51
|
-
--port <n> Listen port (overrides hub config; default 8443)
|
|
52
|
-
--host <ip> Bind address (overrides hub config; default 0.0.0.0)
|
|
53
|
-
|
|
54
|
-
Config (persistent): host, port, sessionTtlSeconds, tls{cert,key}, behindProxy,
|
|
55
|
-
allowFrom[], auth{mode: pair|password|none, pairCode, users[]}, dshPath.
|
|
56
|
-
Hub config (~/.rdsh/hub.json): host, port, tls{cert,key}, dbPath, jwtKeyPath.
|
|
33
|
+
--config <path> Host config (default ~/.rdsh/host.json; also $RDSH_CONFIG)
|
|
34
|
+
For hub commands: hub config (default ~/.rdsh/hub.json; also $RDSH_HUB_CONFIG)
|
|
35
|
+
--version, -v Print version
|
|
36
|
+
--help, -h Print this help
|
|
57
37
|
`;
|
|
58
38
|
/** 子命令级用法(`rdsh <cmd> --help`)。 */
|
|
59
39
|
const SUB_HELP = {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Start the gateway (LAN auth or cloud HTTPS service).
|
|
40
|
+
host: `Usage: rdsh host <subcommand>
|
|
63
41
|
|
|
64
|
-
|
|
65
|
-
--config <path> Config file (default ~/.rdsh/config.json; also $RDSH_CONFIG)
|
|
66
|
-
--port <n> Listen port (overrides config; default 8443)
|
|
67
|
-
--host <ip> Bind address (overrides config; default 0.0.0.0)
|
|
68
|
-
--pair-code <code> Preset pairing code (overrides config)
|
|
69
|
-
--session-ttl <sec> Session cookie lifetime (overrides config; default 43200)
|
|
70
|
-
--dsh <path> dsh executable path (overrides config)
|
|
71
|
-
--reset Rotate session keys (all devices must re-auth)
|
|
72
|
-
--no-code Disable auth (trusted network ONLY!)
|
|
73
|
-
`,
|
|
74
|
-
join: `Usage: rdsh join <hub-url> [options]
|
|
42
|
+
Configure and run this machine (the DSH host).
|
|
75
43
|
|
|
76
|
-
|
|
44
|
+
Subcommands:
|
|
45
|
+
setup lan Configure a LAN gateway (pair auth, plain http)
|
|
46
|
+
setup cloud Configure a cloud HTTPS gateway (password + TLS + allowFrom)
|
|
47
|
+
join <hub-url> Connect to a hub (interactive token paste; --token for scripts)
|
|
48
|
+
serve Run the configured mode in the foreground
|
|
49
|
+
service install|status|uninstall Run as a systemd/launchd service
|
|
50
|
+
leave Unregister this machine from the hub
|
|
51
|
+
user add|passwd|ls|rm Manage gateway users
|
|
77
52
|
|
|
78
|
-
Options:
|
|
79
|
-
--
|
|
80
|
-
|
|
81
|
-
--dsh <path> dsh executable path
|
|
82
|
-
--insecure Skip TLS certificate verification (self-signed hub)
|
|
53
|
+
Options (setup lan): --port <n> [--pair-code <code>]
|
|
54
|
+
Options (setup cloud): --tls-cert <path> --tls-key <path> [--port <n>] [--allow-from <cidr,...>]
|
|
55
|
+
Options (join): --token <t> --name <n> --dsh <path> --insecure
|
|
83
56
|
`,
|
|
84
57
|
hub: `Usage: rdsh hub <subcommand> [options]
|
|
85
58
|
|
|
@@ -95,31 +68,6 @@ Options (serve):
|
|
|
95
68
|
--config <path> Hub config (default ~/.rdsh/hub.json; also $RDSH_HUB_CONFIG)
|
|
96
69
|
--port <n> Listen port (overrides hub config; default 8443)
|
|
97
70
|
--host <ip> Bind address (overrides hub config; default 0.0.0.0)
|
|
98
|
-
`,
|
|
99
|
-
user: `Usage: rdsh user <action> [name]
|
|
100
|
-
|
|
101
|
-
Manage gateway users (LAN / cloud HTTPS service).
|
|
102
|
-
|
|
103
|
-
Actions:
|
|
104
|
-
add <name> Add a user (interactive password)
|
|
105
|
-
passwd <name> Change a user's password (revokes all their sessions)
|
|
106
|
-
ls List users
|
|
107
|
-
rm <name> Remove a user
|
|
108
|
-
|
|
109
|
-
Options:
|
|
110
|
-
--config <path> Config file (default ~/.rdsh/config.json; also $RDSH_CONFIG)
|
|
111
|
-
`,
|
|
112
|
-
service: `Usage: rdsh service <action>
|
|
113
|
-
|
|
114
|
-
Manage the gateway as a systemd/launchd service.
|
|
115
|
-
|
|
116
|
-
Actions:
|
|
117
|
-
install Install and start (auto-start on boot, restart on crash)
|
|
118
|
-
status Show service status
|
|
119
|
-
uninstall Stop and remove the service
|
|
120
|
-
|
|
121
|
-
Options:
|
|
122
|
-
--config <path> Config file (default ~/.rdsh/config.json; also $RDSH_CONFIG)
|
|
123
71
|
`,
|
|
124
72
|
};
|
|
125
73
|
/** 参数里是否有 --help / -h。 */
|
|
@@ -151,28 +99,14 @@ async function main() {
|
|
|
151
99
|
return;
|
|
152
100
|
}
|
|
153
101
|
switch (command) {
|
|
154
|
-
case "
|
|
155
|
-
|
|
156
|
-
await serve(opts);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
case "join": {
|
|
160
|
-
const opts = parseJoinArgs(rest);
|
|
161
|
-
await join(opts);
|
|
102
|
+
case "host": {
|
|
103
|
+
await handleHost(rest, cli.configPath);
|
|
162
104
|
return;
|
|
163
105
|
}
|
|
164
106
|
case "hub": {
|
|
165
107
|
await handleHub(rest, cli.configPath);
|
|
166
108
|
return;
|
|
167
109
|
}
|
|
168
|
-
case "user": {
|
|
169
|
-
await handleUser(rest, cli.configPath);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
case "service": {
|
|
173
|
-
await handleService(rest, cli.configPath);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
110
|
default:
|
|
177
111
|
console.error(`rdsh: unknown command '${command}'. Run 'rdsh --help'.`);
|
|
178
112
|
process.exitCode = 1;
|
|
@@ -198,59 +132,118 @@ function parseGlobal(argv) {
|
|
|
198
132
|
flags.push(argv[i]);
|
|
199
133
|
}
|
|
200
134
|
}
|
|
201
|
-
// 返回原始值(undefined 或显式 --config);gateway/hub 子命令分别按自己的默认路径 resolve
|
|
202
135
|
return { configPath, flags };
|
|
203
136
|
}
|
|
204
|
-
function
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
137
|
+
function parsePort(v) {
|
|
138
|
+
const n = Number(v);
|
|
139
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535)
|
|
140
|
+
throw new Error(`invalid port '${v}'`);
|
|
141
|
+
return n;
|
|
142
|
+
}
|
|
143
|
+
/** 服务 unit 的子进程 PATH:node 目录(nvm/自装)+ 系统路径,保证 spawn dsh 时 shebang 能解析 node。 */
|
|
144
|
+
function nodePathEnv() {
|
|
145
|
+
return `${dirname(process.execPath)}:/usr/local/bin:/usr/bin:/bin`;
|
|
146
|
+
}
|
|
147
|
+
async function handleHost(args, configPath) {
|
|
148
|
+
const sub = args[0];
|
|
149
|
+
const rest = args.slice(1);
|
|
150
|
+
if (sub === undefined || hasHelp(rest)) {
|
|
151
|
+
console.log(SUB_HELP["host"] ?? HELP);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
switch (sub) {
|
|
155
|
+
case "setup":
|
|
156
|
+
await handleHostSetup(rest, configPath);
|
|
157
|
+
return;
|
|
158
|
+
case "join":
|
|
159
|
+
await handleHostJoin(rest, configPath);
|
|
160
|
+
return;
|
|
161
|
+
case "serve":
|
|
162
|
+
await handleHostServe(rest, configPath);
|
|
163
|
+
return;
|
|
164
|
+
case "service":
|
|
165
|
+
await handleHostService(rest, configPath);
|
|
166
|
+
return;
|
|
167
|
+
case "leave":
|
|
168
|
+
await handleHostLeave(rest, configPath);
|
|
169
|
+
return;
|
|
170
|
+
case "user":
|
|
171
|
+
await handleHostUser(rest, configPath);
|
|
172
|
+
return;
|
|
173
|
+
default:
|
|
174
|
+
throw new Error("usage: rdsh host setup|join|serve|service|leave|user");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** `rdsh host setup lan|cloud`:写 host.json(mode + 预设字段),配置完退出。 */
|
|
178
|
+
async function handleHostSetup(args, configPath) {
|
|
179
|
+
const which = args[0];
|
|
180
|
+
const target = resolveConfigPath(configPath);
|
|
181
|
+
const config = await loadConfig(target);
|
|
182
|
+
if (which === "lan") {
|
|
183
|
+
let port = config.port;
|
|
184
|
+
let pairCode = config.auth.pairCode;
|
|
185
|
+
for (let i = 1; i < args.length; i++) {
|
|
186
|
+
const flag = args[i];
|
|
187
|
+
if (flag === "--port")
|
|
188
|
+
port = parsePort(args[++i]);
|
|
189
|
+
else if (flag === "--pair-code")
|
|
190
|
+
pairCode = args[++i];
|
|
191
|
+
else
|
|
245
192
|
throw new Error(`unknown option '${flag}'`);
|
|
246
193
|
}
|
|
194
|
+
config.mode = "lan";
|
|
195
|
+
config.port = port;
|
|
196
|
+
config.tls = undefined;
|
|
197
|
+
config.behindProxy = false;
|
|
198
|
+
config.allowFrom = [];
|
|
199
|
+
config.auth.mode = "pair";
|
|
200
|
+
config.auth.pairCode = pairCode;
|
|
201
|
+
config.dshPath = config.dshPath ?? findDsh() ?? undefined;
|
|
202
|
+
await saveConfig(target, config);
|
|
203
|
+
console.log(`rdsh: host 配置为 LAN 网关(pair,端口 ${port})→ ${target}`);
|
|
204
|
+
console.log("rdsh: 运行 `rdsh host serve` 前台启动,或 `rdsh host service install` 常驻。");
|
|
205
|
+
return;
|
|
247
206
|
}
|
|
248
|
-
|
|
207
|
+
if (which === "cloud") {
|
|
208
|
+
let port = config.port;
|
|
209
|
+
let cert;
|
|
210
|
+
let key;
|
|
211
|
+
let allowFrom = [];
|
|
212
|
+
for (let i = 1; i < args.length; i++) {
|
|
213
|
+
const flag = args[i];
|
|
214
|
+
if (flag === "--port")
|
|
215
|
+
port = parsePort(args[++i]);
|
|
216
|
+
else if (flag === "--tls-cert")
|
|
217
|
+
cert = args[++i];
|
|
218
|
+
else if (flag === "--tls-key")
|
|
219
|
+
key = args[++i];
|
|
220
|
+
else if (flag === "--allow-from")
|
|
221
|
+
allowFrom = (args[++i] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
222
|
+
else
|
|
223
|
+
throw new Error(`unknown option '${flag}'`);
|
|
224
|
+
}
|
|
225
|
+
if (cert === undefined || key === undefined) {
|
|
226
|
+
throw new Error("usage: rdsh host setup cloud --tls-cert <path> --tls-key <path> [--port <n>] [--allow-from <cidr,...>]");
|
|
227
|
+
}
|
|
228
|
+
config.mode = "cloud";
|
|
229
|
+
config.port = port;
|
|
230
|
+
config.tls = { cert, key };
|
|
231
|
+
config.behindProxy = false;
|
|
232
|
+
config.allowFrom = allowFrom;
|
|
233
|
+
config.auth.mode = "password";
|
|
234
|
+
config.dshPath = config.dshPath ?? findDsh() ?? undefined;
|
|
235
|
+
await saveConfig(target, config);
|
|
236
|
+
console.log(`rdsh: host 配置为云 HTTPS 网关(password,端口 ${port})→ ${target}`);
|
|
237
|
+
console.log("rdsh: 用 `rdsh host user add <name>` 建用户,再 `rdsh host serve` / `rdsh host service install`。");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
throw new Error("usage: rdsh host setup lan|cloud");
|
|
249
241
|
}
|
|
250
|
-
|
|
242
|
+
/** `rdsh host join <hub>`:注册(join token)→ 写 host.json + session,配置完退出。 */
|
|
243
|
+
async function handleHostJoin(args, configPath) {
|
|
251
244
|
const hubUrl = args[0];
|
|
252
245
|
if (hubUrl === undefined || !/^https?:\/\//.test(hubUrl)) {
|
|
253
|
-
throw new Error("usage: rdsh join <hub-url> [--token <t>] [--dsh <
|
|
246
|
+
throw new Error("usage: rdsh host join <hub-url> [--token <t>] [--name <n>] [--dsh <p>] [--insecure]");
|
|
254
247
|
}
|
|
255
248
|
const opts = { hubUrl };
|
|
256
249
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -261,45 +254,153 @@ function parseJoinArgs(args) {
|
|
|
261
254
|
throw new Error(`missing value for ${name}`);
|
|
262
255
|
return args[i] ?? "";
|
|
263
256
|
};
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
257
|
+
if (flag === "--token")
|
|
258
|
+
opts.token = value(flag);
|
|
259
|
+
else if (flag === "--name")
|
|
260
|
+
opts.name = value(flag);
|
|
261
|
+
else if (flag === "--dsh")
|
|
262
|
+
opts.dshPath = value(flag);
|
|
263
|
+
else if (flag === "--insecure")
|
|
264
|
+
opts.insecure = true;
|
|
265
|
+
else if (flag === "--reset")
|
|
266
|
+
opts.reset = true;
|
|
267
|
+
else
|
|
268
|
+
throw new Error(`unknown option '${flag}'`);
|
|
269
|
+
}
|
|
270
|
+
if (opts.token === undefined && readPersistedToken(hubUrl) === null) {
|
|
271
|
+
// 无 --token 且无持久化 session:TTY 交互粘贴 token;非 TTY 报错不 hang
|
|
272
|
+
if (process.stdin.isTTY) {
|
|
273
|
+
const pasted = (await promptLine("Paste your join token (hub portal → Add host): ")).trim();
|
|
274
|
+
if (pasted === "")
|
|
275
|
+
throw new Error("需要 join token(hub portal → 添加主机 生成)");
|
|
276
|
+
opts.token = pasted;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
throw new Error("无持久化 session 且未提供 --token;交互式运行粘贴 token");
|
|
279
280
|
}
|
|
280
281
|
}
|
|
281
|
-
|
|
282
|
+
const outcome = await registerJoin(opts);
|
|
283
|
+
const target = resolveConfigPath(configPath);
|
|
284
|
+
const config = await loadConfig(target);
|
|
285
|
+
config.mode = "join";
|
|
286
|
+
config.hub = hubUrl;
|
|
287
|
+
config.name = opts.name ?? osHostname();
|
|
288
|
+
config.insecure = outcome.insecure;
|
|
289
|
+
config.dshPath = opts.dshPath ?? findDsh() ?? config.dshPath;
|
|
290
|
+
await saveConfig(target, config);
|
|
291
|
+
console.log(`rdsh: host 已接入 ${hubUrl}(${config.name})—— session 已保存`);
|
|
292
|
+
console.log("rdsh: 运行 `rdsh host serve` 前台启动,或 `rdsh host service install` 常驻。");
|
|
293
|
+
}
|
|
294
|
+
/** `rdsh host serve`:前台常驻,读 host.json 按 mode 分发(join→隧道,lan/cloud→网关)。 */
|
|
295
|
+
async function handleHostServe(_args, configPath) {
|
|
296
|
+
const target = resolveConfigPath(configPath);
|
|
297
|
+
const config = await loadConfig(target);
|
|
298
|
+
if (config.mode === "join") {
|
|
299
|
+
if (config.hub === undefined)
|
|
300
|
+
throw new Error("host.json 缺 hub(join 模式);先 `rdsh host join <hub>`");
|
|
301
|
+
await join({ hubUrl: config.hub, name: config.name, insecure: config.insecure, dshPath: config.dshPath });
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
await serve({ configPath: target });
|
|
305
|
+
}
|
|
306
|
+
/** `rdsh host service install|status|uninstall`:读 host.json mode 装对应服务(unit 不含 token)。 */
|
|
307
|
+
async function handleHostService(args, configPath) {
|
|
308
|
+
const action = args[0];
|
|
309
|
+
const target = resolveConfigPath(configPath);
|
|
310
|
+
switch (action) {
|
|
311
|
+
case "install": {
|
|
312
|
+
await maybeRegisterForServiceInstall(args, target);
|
|
313
|
+
const config = await loadConfig(target);
|
|
314
|
+
const name = config.mode === "join" ? JOIN_SERVICE_NAME : HOST_SERVICE_NAME;
|
|
315
|
+
// host 服务会 spawn dsh(shebang `#!/usr/bin/env node`),nvm/自装 node 下需补 node 目录到 PATH(防 127)
|
|
316
|
+
console.log(await installService({ name, args: ["host", "serve"], configPath: target, pathEnv: nodePathEnv() }));
|
|
317
|
+
console.log(`rdsh: host service installed (${name}) —— 开机自启 + 崩溃重启。`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
case "status": {
|
|
321
|
+
const config = await loadConfig(target);
|
|
322
|
+
const name = config.mode === "join" ? JOIN_SERVICE_NAME : HOST_SERVICE_NAME;
|
|
323
|
+
console.log(`rdsh host service (${name}): ${await serviceStatus(name)}`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
case "uninstall": {
|
|
327
|
+
const config = await loadConfig(target);
|
|
328
|
+
const name = config.mode === "join" ? JOIN_SERVICE_NAME : HOST_SERVICE_NAME;
|
|
329
|
+
console.log(await uninstallService(name));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
default:
|
|
333
|
+
throw new Error("usage: rdsh host service install|status|uninstall");
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/** 一行接入:`host service install <hub-url> --token <t> [--name <n>]` → 注册 + 写 host.json(再装服务)。 */
|
|
337
|
+
async function maybeRegisterForServiceInstall(args, target) {
|
|
338
|
+
const maybeHub = args[1];
|
|
339
|
+
if (maybeHub === undefined || !/^https?:\/\//.test(maybeHub))
|
|
340
|
+
return; // 无 <hub>,走读 host.json 路径
|
|
341
|
+
const joinOpts = { hubUrl: maybeHub };
|
|
342
|
+
for (let i = 2; i < args.length; i++) {
|
|
343
|
+
const flag = args[i];
|
|
344
|
+
if (flag === "--token")
|
|
345
|
+
joinOpts.token = args[++i];
|
|
346
|
+
else if (flag === "--name")
|
|
347
|
+
joinOpts.name = args[++i];
|
|
348
|
+
else if (flag === "--dsh")
|
|
349
|
+
joinOpts.dshPath = args[++i];
|
|
350
|
+
else if (flag === "--insecure")
|
|
351
|
+
joinOpts.insecure = true;
|
|
352
|
+
else
|
|
353
|
+
throw new Error(`unknown option '${flag}'`);
|
|
354
|
+
}
|
|
355
|
+
const outcome = await registerJoin(joinOpts);
|
|
356
|
+
const cfg = await loadConfig(target);
|
|
357
|
+
cfg.mode = "join";
|
|
358
|
+
cfg.hub = maybeHub;
|
|
359
|
+
cfg.name = joinOpts.name ?? osHostname();
|
|
360
|
+
cfg.insecure = outcome.insecure;
|
|
361
|
+
cfg.dshPath = joinOpts.dshPath ?? findDsh() ?? cfg.dshPath;
|
|
362
|
+
await saveConfig(target, cfg);
|
|
363
|
+
}
|
|
364
|
+
/** `rdsh host leave`:self-revoke + 清 session + 删 host.json → 未配置。 */
|
|
365
|
+
async function handleHostLeave(_args, configPath) {
|
|
366
|
+
const target = resolveConfigPath(configPath);
|
|
367
|
+
const config = await loadConfig(target);
|
|
368
|
+
if (config.mode !== "join" || config.hub === undefined) {
|
|
369
|
+
throw new Error("当前不是 join 模式(或缺 hub),无需 leave");
|
|
370
|
+
}
|
|
371
|
+
const token = readPersistedToken(config.hub);
|
|
372
|
+
if (token === null) {
|
|
373
|
+
console.log("rdsh: 本地无 session 文件,仅清理配置");
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
await selfRevoke(config.hub, token, config.insecure === true);
|
|
377
|
+
console.log("rdsh: 已在 hub 自吊销本机");
|
|
378
|
+
}
|
|
379
|
+
clearPersistedToken(config.hub);
|
|
380
|
+
await rm(target, { force: true });
|
|
381
|
+
console.log(`rdsh: host 已注销(清理 ${target})—— 回到未配置状态`);
|
|
282
382
|
}
|
|
283
|
-
|
|
383
|
+
/** `rdsh host user add|passwd|ls|rm`:本机网关用户(写 host.json auth.users)。 */
|
|
384
|
+
async function handleHostUser(args, configPath) {
|
|
284
385
|
const action = args[0];
|
|
285
386
|
const um = new UserManager(resolveConfigPath(configPath));
|
|
286
387
|
switch (action) {
|
|
287
388
|
case "add": {
|
|
288
389
|
const name = args[1];
|
|
289
390
|
if (name === undefined)
|
|
290
|
-
throw new Error("usage: rdsh user add <name>");
|
|
391
|
+
throw new Error("usage: rdsh host user add <name>");
|
|
291
392
|
const password = await promptPassword(`password for ${name}: `);
|
|
292
393
|
const again = await promptPassword("confirm: ");
|
|
293
394
|
if (password !== again)
|
|
294
395
|
throw new Error("passwords do not match");
|
|
295
396
|
await um.add(name, password);
|
|
296
|
-
console.log(`rdsh: user '${name}' added
|
|
397
|
+
console.log(`rdsh: user '${name}' added`);
|
|
297
398
|
return;
|
|
298
399
|
}
|
|
299
400
|
case "passwd": {
|
|
300
401
|
const name = args[1];
|
|
301
402
|
if (name === undefined)
|
|
302
|
-
throw new Error("usage: rdsh user passwd <name>");
|
|
403
|
+
throw new Error("usage: rdsh host user passwd <name>");
|
|
303
404
|
const password = await promptPassword(`new password for ${name}: `);
|
|
304
405
|
const again = await promptPassword("confirm: ");
|
|
305
406
|
if (password !== again)
|
|
@@ -318,7 +419,7 @@ async function handleUser(args, configPath) {
|
|
|
318
419
|
case "rm": {
|
|
319
420
|
const name = args[1];
|
|
320
421
|
if (name === undefined)
|
|
321
|
-
throw new Error("usage: rdsh user rm <name>");
|
|
422
|
+
throw new Error("usage: rdsh host user rm <name>");
|
|
322
423
|
const ok = await um.remove(name);
|
|
323
424
|
if (!ok)
|
|
324
425
|
throw new Error(`user '${name}' not found`);
|
|
@@ -326,24 +427,7 @@ async function handleUser(args, configPath) {
|
|
|
326
427
|
return;
|
|
327
428
|
}
|
|
328
429
|
default:
|
|
329
|
-
throw new Error("usage: rdsh user add|passwd|ls|rm");
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
async function handleService(args, configPath) {
|
|
333
|
-
const action = args[0];
|
|
334
|
-
switch (action) {
|
|
335
|
-
case "install":
|
|
336
|
-
console.log(await installService(resolveConfigPath(configPath)));
|
|
337
|
-
console.log("rdsh: service installed — it starts on boot and restarts on crash.");
|
|
338
|
-
return;
|
|
339
|
-
case "status":
|
|
340
|
-
console.log(`rdsh service: ${await serviceStatus()}`);
|
|
341
|
-
return;
|
|
342
|
-
case "uninstall":
|
|
343
|
-
console.log(await uninstallService());
|
|
344
|
-
return;
|
|
345
|
-
default:
|
|
346
|
-
throw new Error("usage: rdsh service install|status|uninstall");
|
|
430
|
+
throw new Error("usage: rdsh host user add|passwd|ls|rm");
|
|
347
431
|
}
|
|
348
432
|
}
|
|
349
433
|
/** 打开 hub 数据库(读 hub config 定位 dbPath)。 */
|
|
@@ -378,14 +462,14 @@ async function handleHub(args, configPath) {
|
|
|
378
462
|
const hubConfigPath = resolveHubConfigPath(configPath);
|
|
379
463
|
switch (action) {
|
|
380
464
|
case "install":
|
|
381
|
-
console.log(await installService(
|
|
382
|
-
console.log(
|
|
465
|
+
console.log(await installService({ name: HUB_SERVICE_NAME, args: ["hub", "serve"], configPath: hubConfigPath }));
|
|
466
|
+
console.log(`rdsh: hub service installed (${HUB_SERVICE_NAME}) —— 开机自启 + 崩溃重启。`);
|
|
383
467
|
return;
|
|
384
468
|
case "status":
|
|
385
|
-
console.log(`rdsh hub service: ${await serviceStatus()}`);
|
|
469
|
+
console.log(`rdsh hub service (${HUB_SERVICE_NAME}): ${await serviceStatus(HUB_SERVICE_NAME)}`);
|
|
386
470
|
return;
|
|
387
471
|
case "uninstall":
|
|
388
|
-
console.log(await uninstallService());
|
|
472
|
+
console.log(await uninstallService(HUB_SERVICE_NAME));
|
|
389
473
|
return;
|
|
390
474
|
default:
|
|
391
475
|
throw new Error("usage: rdsh hub service install|status|uninstall");
|
|
@@ -450,7 +534,6 @@ async function handleHubUser(args, configPath) {
|
|
|
450
534
|
if (db.getUserByName(name) !== null)
|
|
451
535
|
throw new Error(`user '${name}' already exists`);
|
|
452
536
|
if (noPassword) {
|
|
453
|
-
// 首次登录自设密码(must_change=1,密码占位不可用)
|
|
454
537
|
db.createUser(name, "scrypt:disabled", new Date().toISOString(), true);
|
|
455
538
|
console.log(`rdsh: user '${name}' created — they must set a password on first sign-in (${hubConfigPath})`);
|
|
456
539
|
}
|
|
@@ -494,7 +577,7 @@ async function handleHubUser(args, configPath) {
|
|
|
494
577
|
}
|
|
495
578
|
}
|
|
496
579
|
finally {
|
|
497
|
-
db.close();
|
|
580
|
+
db.close();
|
|
498
581
|
}
|
|
499
582
|
}
|
|
500
583
|
async function handleHubHost(args, configPath) {
|
|
@@ -562,11 +645,21 @@ function promptPassword(prompt) {
|
|
|
562
645
|
});
|
|
563
646
|
}
|
|
564
647
|
// 非 TTY(管道/重定向):一次性读完 stdin(EOF),逐行消费。
|
|
565
|
-
// 注意:不能每次调用新建 readline —— 第二个 interface 会读不到剩余行。
|
|
566
648
|
process.stdout.write(prompt);
|
|
567
649
|
return nextPipeLine();
|
|
568
650
|
}
|
|
569
651
|
let pipeLines = null;
|
|
652
|
+
/** 读取一行(echo,用于粘贴 token 等非敏感输入)。 */
|
|
653
|
+
function promptLine(prompt) {
|
|
654
|
+
process.stdout.write(prompt);
|
|
655
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
656
|
+
return new Promise((resolve) => {
|
|
657
|
+
rl.question("", (answer) => {
|
|
658
|
+
rl.close();
|
|
659
|
+
resolve(answer);
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
}
|
|
570
663
|
function nextPipeLine() {
|
|
571
664
|
if (pipeLines !== null) {
|
|
572
665
|
return Promise.resolve(pipeLines.shift() ?? "");
|
|
@@ -578,6 +671,7 @@ function nextPipeLine() {
|
|
|
578
671
|
rl.on("close", () => resolve(pipeLines.shift() ?? ""));
|
|
579
672
|
});
|
|
580
673
|
}
|
|
581
|
-
// 管理命令(
|
|
674
|
+
// 管理命令(host setup/join/leave/service/user 完成后)显式退出:
|
|
675
|
+
// 常驻命令(host serve / hub serve)不 resolve,靠信号退出。
|
|
582
676
|
void main().then(() => process.exit(process.exitCode ?? 0));
|
|
583
677
|
//# sourceMappingURL=bin.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remote-dsh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Secure remote access for DeepSeek Harness: rdsh serve / rdsh join / rdsh hub",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"node": ">=22"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"rdsh-gateway": "0.
|
|
27
|
-
"rdsh-hub": "0.
|
|
26
|
+
"rdsh-gateway": "0.3.0",
|
|
27
|
+
"rdsh-hub": "0.3.0"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsc -p tsconfig.json",
|