workstation.md 0.1.1 → 0.3.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/bin/workstation.d.ts +1 -13
- package/bin/workstation.js +68 -5
- package/package.json +1 -1
package/bin/workstation.d.ts
CHANGED
|
@@ -1,14 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
interface WorkstationInfo {
|
|
4
|
-
id: string;
|
|
5
|
-
host: string;
|
|
6
|
-
port: number;
|
|
7
|
-
web: string;
|
|
8
|
-
}
|
|
9
|
-
declare function apiRequest(path: string, options?: RequestInit): Promise<any>;
|
|
10
|
-
declare function create(args: string[]): Promise<void>;
|
|
11
|
-
declare function destroy(wsId: string): Promise<void>;
|
|
12
|
-
declare function list(): Promise<void>;
|
|
13
|
-
declare function usage(): void;
|
|
14
|
-
declare function main(): Promise<void>;
|
|
2
|
+
export {};
|
package/bin/workstation.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { writeFileSync, mkdtempSync, readFileSync } from "fs";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
3
6
|
const API_URL = process.env.WORKSTATION_API_URL || "https://api.workstation.md";
|
|
4
7
|
async function apiRequest(path, options = {}) {
|
|
5
8
|
const res = await fetch(`${API_URL}${path}`, {
|
|
@@ -16,21 +19,65 @@ async function apiRequest(path, options = {}) {
|
|
|
16
19
|
}
|
|
17
20
|
return res.json();
|
|
18
21
|
}
|
|
22
|
+
function findKeyPath(args) {
|
|
23
|
+
for (let i = 0; i < args.length; i++) {
|
|
24
|
+
if (args[i] === "--key" && args[i + 1]) {
|
|
25
|
+
return args[i + 1];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Default: try common key paths
|
|
29
|
+
const home = process.env.HOME || "~";
|
|
30
|
+
const defaults = [
|
|
31
|
+
join(home, ".ssh", "id_ed25519"),
|
|
32
|
+
join(home, ".ssh", "id_rsa"),
|
|
33
|
+
];
|
|
34
|
+
for (const p of defaults) {
|
|
35
|
+
try {
|
|
36
|
+
readFileSync(p);
|
|
37
|
+
return p;
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
}
|
|
41
|
+
console.error("No SSH private key found. Use --key <path> to specify.");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
function signWithSSHKey(keyPath, data) {
|
|
45
|
+
const tmp = mkdtempSync(join(tmpdir(), "ws-"));
|
|
46
|
+
const dataPath = join(tmp, "data");
|
|
47
|
+
writeFileSync(dataPath, data);
|
|
48
|
+
try {
|
|
49
|
+
execSync(`ssh-keygen -Y sign -f "${keyPath}" -n workstation.md "${dataPath}"`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
50
|
+
const sigPath = dataPath + ".sig";
|
|
51
|
+
return readFileSync(sigPath, "utf-8");
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
console.error("Failed to sign with SSH key:", e.stderr?.toString() || e.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
19
58
|
async function create(args) {
|
|
20
59
|
let pubkey;
|
|
60
|
+
let name;
|
|
21
61
|
for (let i = 0; i < args.length; i++) {
|
|
22
62
|
if (args[i] === "--pubkey" && args[i + 1]) {
|
|
23
63
|
pubkey = args[i + 1];
|
|
24
64
|
i++;
|
|
25
65
|
}
|
|
66
|
+
else if (args[i] === "--name" && args[i + 1]) {
|
|
67
|
+
name = args[i + 1];
|
|
68
|
+
i++;
|
|
69
|
+
}
|
|
26
70
|
}
|
|
27
71
|
if (!pubkey) {
|
|
28
|
-
console.error("Usage: workstation create --pubkey <public_key>");
|
|
72
|
+
console.error("Usage: workstation create --pubkey <public_key> [--name <name>]");
|
|
29
73
|
process.exit(1);
|
|
30
74
|
}
|
|
75
|
+
const body = { pubkey };
|
|
76
|
+
if (name)
|
|
77
|
+
body.name = name;
|
|
31
78
|
const info = await apiRequest("/create", {
|
|
32
79
|
method: "POST",
|
|
33
|
-
body: JSON.stringify(
|
|
80
|
+
body: JSON.stringify(body),
|
|
34
81
|
});
|
|
35
82
|
console.log(JSON.stringify(info, null, 2));
|
|
36
83
|
}
|
|
@@ -38,6 +85,15 @@ async function destroy(wsId) {
|
|
|
38
85
|
const result = await apiRequest(`/${wsId}`, { method: "DELETE" });
|
|
39
86
|
console.log(JSON.stringify(result, null, 2));
|
|
40
87
|
}
|
|
88
|
+
async function extend(wsId, args) {
|
|
89
|
+
const keyPath = findKeyPath(args);
|
|
90
|
+
const signature = signWithSSHKey(keyPath, wsId);
|
|
91
|
+
const result = await apiRequest(`/${wsId}/extend`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
body: JSON.stringify({ signature }),
|
|
94
|
+
});
|
|
95
|
+
console.log(JSON.stringify(result, null, 2));
|
|
96
|
+
}
|
|
41
97
|
async function list() {
|
|
42
98
|
const items = await apiRequest("/list");
|
|
43
99
|
if (items.length === 0) {
|
|
@@ -45,13 +101,17 @@ async function list() {
|
|
|
45
101
|
return;
|
|
46
102
|
}
|
|
47
103
|
for (const ws of items) {
|
|
48
|
-
|
|
104
|
+
const ttl = ws.expires - Math.floor(Date.now() / 1000);
|
|
105
|
+
const hours = Math.max(0, Math.floor(ttl / 3600));
|
|
106
|
+
const mins = Math.max(0, Math.floor((ttl % 3600) / 60));
|
|
107
|
+
console.log(`${ws.id}\tssh -p ${ws.port} root@${ws.host}\t${ws.web}\t${hours}h${mins}m remaining`);
|
|
49
108
|
}
|
|
50
109
|
}
|
|
51
110
|
function usage() {
|
|
52
111
|
console.log(`Usage:
|
|
53
|
-
workstation create --pubkey <
|
|
112
|
+
workstation create --pubkey <key> [--name <name>] Create a workstation (24h TTL)
|
|
54
113
|
workstation <id> destroy Destroy a workstation
|
|
114
|
+
workstation <id> extend [--key <path>] Extend TTL by 24h (proves key ownership)
|
|
55
115
|
workstation list List active workstations`);
|
|
56
116
|
}
|
|
57
117
|
async function main() {
|
|
@@ -70,6 +130,9 @@ async function main() {
|
|
|
70
130
|
else if (args.length >= 2 && args[1] === "destroy") {
|
|
71
131
|
await destroy(command);
|
|
72
132
|
}
|
|
133
|
+
else if (args.length >= 2 && args[1] === "extend") {
|
|
134
|
+
await extend(command, args.slice(2));
|
|
135
|
+
}
|
|
73
136
|
else {
|
|
74
137
|
usage();
|
|
75
138
|
process.exit(1);
|