remote-dsh 0.5.0 → 0.7.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/README.md +3 -2
- package/dist/bin.js +119 -18
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,10 +4,11 @@ Secure remote access for [DeepSeek Harness](https://github.com/deepseek-ai/deeps
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install -g remote-dsh
|
|
7
|
-
rdsh
|
|
7
|
+
rdsh host setup lan # write ~/.rdsh/host.json (LAN gateway: pair auth)
|
|
8
|
+
rdsh host serve # run the gateway in the foreground
|
|
8
9
|
```
|
|
9
10
|
|
|
10
|
-
>
|
|
11
|
+
> See `doc/overview/usage.md` for the full manual (`rdsh host setup lan|cloud`, `rdsh host join <hub>`, `rdsh hub serve`, ...).
|
|
11
12
|
|
|
12
13
|
## License
|
|
13
14
|
|
package/dist/bin.js
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { createInterface } from "node:readline";
|
|
12
12
|
import { rm } from "node:fs/promises";
|
|
13
|
-
import { hostname as osHostname } from "node:os";
|
|
14
13
|
import { dirname } from "node:path";
|
|
15
14
|
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";
|
|
16
15
|
import { loadHubConfig, resolveHubConfigPath, HubDb, hashPassword, serveHub, } from "rdsh-hub";
|
|
@@ -25,7 +24,8 @@ Usage:
|
|
|
25
24
|
rdsh host leave Unregister this machine from the hub
|
|
26
25
|
rdsh host user ... Manage gateway users (LAN/cloud password auth)
|
|
27
26
|
rdsh hub serve Start the hub server (cloud, multi-host)
|
|
28
|
-
rdsh hub user add|passwd|rm|ls
|
|
27
|
+
rdsh hub user add|passwd|rm|ls|unlock|reset-2fa|ban|unban
|
|
28
|
+
rdsh hub audit ls Audit log (login/2FA/sharing/email events)
|
|
29
29
|
rdsh hub host ls|revoke List / revoke hosts (revoke drops the tunnel instantly)
|
|
30
30
|
rdsh hub service ... Run hub as a systemd/launchd service
|
|
31
31
|
|
|
@@ -60,7 +60,8 @@ Run the hub server or manage it.
|
|
|
60
60
|
|
|
61
61
|
Subcommands:
|
|
62
62
|
serve Start the hub server (TLS required)
|
|
63
|
-
user add|passwd|rm|ls
|
|
63
|
+
user add|passwd|rm|ls|unlock|reset-2fa|ban|unban Manage users (admin creates accounts; unlock / reset-2fa / ban / unban)
|
|
64
|
+
audit ls [--user <n>] [--event <e>] [--since 24h|7d] Query the audit log
|
|
64
65
|
host ls|revoke List / revoke hosts (revoke drops the tunnel instantly)
|
|
65
66
|
service install|status|uninstall Run hub as a systemd/launchd service
|
|
66
67
|
|
|
@@ -284,7 +285,7 @@ async function handleHostJoin(args, configPath) {
|
|
|
284
285
|
const config = await loadConfig(target);
|
|
285
286
|
config.mode = "join";
|
|
286
287
|
config.hub = hubUrl;
|
|
287
|
-
config.name =
|
|
288
|
+
config.name = outcome.name;
|
|
288
289
|
config.insecure = outcome.insecure;
|
|
289
290
|
config.dshPath = opts.dshPath ?? findDsh() ?? config.dshPath;
|
|
290
291
|
await saveConfig(target, config);
|
|
@@ -356,7 +357,7 @@ async function maybeRegisterForServiceInstall(args, target) {
|
|
|
356
357
|
const cfg = await loadConfig(target);
|
|
357
358
|
cfg.mode = "join";
|
|
358
359
|
cfg.hub = maybeHub;
|
|
359
|
-
cfg.name =
|
|
360
|
+
cfg.name = outcome.name;
|
|
360
361
|
cfg.insecure = outcome.insecure;
|
|
361
362
|
cfg.dshPath = joinOpts.dshPath ?? findDsh() ?? cfg.dshPath;
|
|
362
363
|
await saveConfig(target, cfg);
|
|
@@ -436,6 +437,56 @@ async function openHubDb(configPath) {
|
|
|
436
437
|
const config = await loadHubConfig(hubConfigPath);
|
|
437
438
|
return new HubDb(config.dbPath);
|
|
438
439
|
}
|
|
440
|
+
/** `rdsh hub audit ls [--user <name>] [--event <e>] [--since 24h|7d]`。 */
|
|
441
|
+
async function handleHubAudit(args, configPath) {
|
|
442
|
+
const db = await openHubDb(configPath);
|
|
443
|
+
try {
|
|
444
|
+
// 兼容 `audit ls` 与 `audit`(ls 为可选位置参数)
|
|
445
|
+
const opts = args[0] === "ls" ? args.slice(1) : args;
|
|
446
|
+
const filter = {};
|
|
447
|
+
for (let i = 0; i < opts.length; i++) {
|
|
448
|
+
const flag = opts[i];
|
|
449
|
+
if (flag === "--user") {
|
|
450
|
+
const name = opts[++i];
|
|
451
|
+
if (name === undefined)
|
|
452
|
+
throw new Error("missing value for --user");
|
|
453
|
+
const u = db.getUserByName(name);
|
|
454
|
+
if (u === null)
|
|
455
|
+
throw new Error(`user '${name}' not found`);
|
|
456
|
+
filter.userId = u.id;
|
|
457
|
+
}
|
|
458
|
+
else if (flag === "--event") {
|
|
459
|
+
filter.event = opts[++i];
|
|
460
|
+
if (filter.event === undefined)
|
|
461
|
+
throw new Error("missing value for --event");
|
|
462
|
+
}
|
|
463
|
+
else if (flag === "--since") {
|
|
464
|
+
const v = opts[++i];
|
|
465
|
+
if (v === undefined)
|
|
466
|
+
throw new Error("missing value for --since");
|
|
467
|
+
const m = /^(\d+)([hd])$/.exec(v);
|
|
468
|
+
if (m === null)
|
|
469
|
+
throw new Error(`invalid --since '${v}' (use like 24h or 7d)`);
|
|
470
|
+
filter.since = Date.now() - Number(m[1]) * (m[2] === "h" ? 3_600_000 : 86_400_000);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
throw new Error(`unknown option '${flag}'`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const events = db.listAudit(filter);
|
|
477
|
+
if (events.length === 0) {
|
|
478
|
+
console.log("(no events)");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
for (const e of events) {
|
|
482
|
+
const user = e.userId !== null ? db.getUserById(e.userId) : null;
|
|
483
|
+
console.log(`${new Date(e.createdAt).toISOString()} ${user?.name ?? "-"} ${e.event} ip=${e.ip} ${e.detailJson}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
finally {
|
|
487
|
+
db.close();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
439
490
|
async function handleHub(args, configPath) {
|
|
440
491
|
const sub = args[0];
|
|
441
492
|
const rest = args.slice(1);
|
|
@@ -457,6 +508,10 @@ async function handleHub(args, configPath) {
|
|
|
457
508
|
await handleHubHost(rest, configPath);
|
|
458
509
|
return;
|
|
459
510
|
}
|
|
511
|
+
case "audit": {
|
|
512
|
+
await handleHubAudit(rest, configPath);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
460
515
|
case "service": {
|
|
461
516
|
const action = rest[0];
|
|
462
517
|
const hubConfigPath = resolveHubConfigPath(configPath);
|
|
@@ -476,7 +531,7 @@ async function handleHub(args, configPath) {
|
|
|
476
531
|
}
|
|
477
532
|
}
|
|
478
533
|
default:
|
|
479
|
-
throw new Error("usage: rdsh hub serve|user|host|service");
|
|
534
|
+
throw new Error("usage: rdsh hub serve|user|host|audit|service");
|
|
480
535
|
}
|
|
481
536
|
}
|
|
482
537
|
function parseHubServeArgs(args, configPath) {
|
|
@@ -528,20 +583,21 @@ async function handleHubUser(args, configPath) {
|
|
|
528
583
|
case "add": {
|
|
529
584
|
const name = args[1];
|
|
530
585
|
if (name === undefined)
|
|
531
|
-
throw new Error("usage: rdsh hub user add <name> [--no-password]");
|
|
586
|
+
throw new Error("usage: rdsh hub user add <name> [--no-password] [--role <user|readonly|operator|admin>]");
|
|
532
587
|
const noPassword = args.includes("--no-password");
|
|
588
|
+
const roleIdx = args.indexOf("--role");
|
|
589
|
+
const role = roleIdx >= 0 && args[roleIdx + 1] !== undefined ? args[roleIdx + 1] : "user";
|
|
590
|
+
if (!["user", "readonly", "operator", "admin"].includes(role))
|
|
591
|
+
throw new Error(`invalid role '${role}' (user|readonly|operator|admin)`);
|
|
533
592
|
const hubConfigPath = resolveHubConfigPath(configPath);
|
|
534
593
|
if (db.getUserByName(name) !== null)
|
|
535
594
|
throw new Error(`user '${name}' already exists`);
|
|
536
|
-
|
|
537
|
-
db.createUser(name, "scrypt:disabled", new Date().toISOString(), true)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
db.createUser(name, await hashPassword(password));
|
|
543
|
-
console.log(`rdsh: user '${name}' created (hub db: ${db.path})`);
|
|
544
|
-
}
|
|
595
|
+
const user = noPassword
|
|
596
|
+
? db.createUser(name, "scrypt:disabled", new Date().toISOString(), true)
|
|
597
|
+
: db.createUser(name, await hashPassword(await promptPasswordTwice(`password for ${name}: `)));
|
|
598
|
+
if (role !== "user")
|
|
599
|
+
db.setRole(user.id, role);
|
|
600
|
+
console.log(`rdsh: user '${name}' created (role: ${role}${noPassword ? " — must set password on first sign-in" : ""})`);
|
|
545
601
|
return;
|
|
546
602
|
}
|
|
547
603
|
case "passwd": {
|
|
@@ -558,7 +614,7 @@ async function handleHubUser(args, configPath) {
|
|
|
558
614
|
}
|
|
559
615
|
case "ls": {
|
|
560
616
|
const users = db.listUsers();
|
|
561
|
-
console.log(users.length > 0 ? users.map((u) => u.name).join("\n") : "(no users)");
|
|
617
|
+
console.log(users.length > 0 ? users.map((u) => `${u.name}\t${u.role}`).join("\n") : "(no users)");
|
|
562
618
|
return;
|
|
563
619
|
}
|
|
564
620
|
case "rm": {
|
|
@@ -572,8 +628,53 @@ async function handleHubUser(args, configPath) {
|
|
|
572
628
|
console.log(`rdsh: user '${name}' removed (their hosts removed)`);
|
|
573
629
|
return;
|
|
574
630
|
}
|
|
631
|
+
case "unlock": {
|
|
632
|
+
const name = args[1];
|
|
633
|
+
if (name === undefined)
|
|
634
|
+
throw new Error("usage: rdsh hub user unlock <name>");
|
|
635
|
+
const user = db.getUserByName(name);
|
|
636
|
+
if (user === null)
|
|
637
|
+
throw new Error(`user '${name}' not found`);
|
|
638
|
+
db.unlockAccount(user.id);
|
|
639
|
+
console.log(`rdsh: user '${name}' unlocked`);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
case "reset-2fa": {
|
|
643
|
+
const name = args[1];
|
|
644
|
+
if (name === undefined)
|
|
645
|
+
throw new Error("usage: rdsh hub user reset-2fa <name>");
|
|
646
|
+
const user = db.getUserByName(name);
|
|
647
|
+
if (user === null)
|
|
648
|
+
throw new Error(`user '${name}' not found`);
|
|
649
|
+
db.clearTotpSecret(user.id);
|
|
650
|
+
db.bumpVersion(user.id);
|
|
651
|
+
console.log(`rdsh: 2FA reset for '${name}' (their sessions revoked)`);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
case "ban": {
|
|
655
|
+
const name = args[1];
|
|
656
|
+
if (name === undefined)
|
|
657
|
+
throw new Error("usage: rdsh hub user ban <name>");
|
|
658
|
+
const user = db.getUserByName(name);
|
|
659
|
+
if (user === null)
|
|
660
|
+
throw new Error(`user '${name}' not found`);
|
|
661
|
+
db.setAccountStatus(user.id, "banned");
|
|
662
|
+
console.log(`rdsh: user '${name}' banned (login + host access blocked)`);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
case "unban": {
|
|
666
|
+
const name = args[1];
|
|
667
|
+
if (name === undefined)
|
|
668
|
+
throw new Error("usage: rdsh hub user unban <name>");
|
|
669
|
+
const user = db.getUserByName(name);
|
|
670
|
+
if (user === null)
|
|
671
|
+
throw new Error(`user '${name}' not found`);
|
|
672
|
+
db.setAccountStatus(user.id, "active");
|
|
673
|
+
console.log(`rdsh: user '${name}' unbanned (plan restored)`);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
575
676
|
default:
|
|
576
|
-
throw new Error("usage: rdsh hub user add|passwd|ls|rm");
|
|
677
|
+
throw new Error("usage: rdsh hub user add|passwd|ls|rm|unlock|reset-2fa|ban|unban");
|
|
577
678
|
}
|
|
578
679
|
}
|
|
579
680
|
finally {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remote-dsh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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.5.0",
|
|
27
|
+
"rdsh-hub": "0.5.0"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsc -p tsconfig.json",
|