rovloom 0.5.0 → 0.6.1
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 +9 -1
- package/dist/src/admin/index.js +1 -1
- package/dist/src/{chunk-emcefrc1.js → chunk-0e9gg4sg.js} +3 -4
- package/dist/src/chunk-1xer1ent.js +102 -0
- package/dist/src/{chunk-863tk45b.js → chunk-41fny9qm.js} +5 -5
- package/dist/src/{chunk-zjzrcc59.js → chunk-59h200cr.js} +139 -7
- package/dist/src/chunk-5w5srj3b.js +93 -0
- package/dist/src/{chunk-d4ejrtjq.js → chunk-6n6b1xpy.js} +1 -1
- package/dist/src/{chunk-ympzv86c.js → chunk-6w4g9fw8.js} +16 -4
- package/dist/src/{chunk-smpevv7n.js → chunk-73aq2qkz.js} +9 -16
- package/dist/src/{chunk-0ake7e4r.js → chunk-8c9yc0ge.js} +9 -17
- package/dist/src/{chunk-57ayxqnb.js → chunk-8fy8w7ds.js} +2 -23
- package/dist/src/{chunk-64ftwe9b.js → chunk-8ykrqhxh.js} +8 -2
- package/dist/src/{chunk-2tn0r64b.js → chunk-9h9x79bx.js} +5 -5
- package/dist/src/{chunk-nyn75b8v.js → chunk-av31bx1e.js} +31 -2
- package/dist/src/{chunk-0xgpxdgc.js → chunk-bbdtsg4b.js} +7 -5
- package/dist/src/{chunk-q5pgbwb3.js → chunk-e7jgpxkf.js} +2 -2
- package/dist/src/{chunk-zj48jvfe.js → chunk-efty77sb.js} +3 -40
- package/dist/src/{chunk-abd4envz.js → chunk-enj7s8cv.js} +71 -78
- package/dist/src/{chunk-4etdq0cp.js → chunk-ftpy8xx3.js} +313 -28
- package/dist/src/{chunk-zdxww7ms.js → chunk-h3xyq7zk.js} +1 -1
- package/dist/src/{chunk-ttsrctxg.js → chunk-hxmq28pg.js} +9 -7
- package/dist/src/{chunk-r0nfthsf.js → chunk-j59ac0xs.js} +2 -2
- package/dist/src/{chunk-5s2emvww.js → chunk-j6svdqr8.js} +10 -302
- package/dist/src/{chunk-9y36gntb.js → chunk-jkzfck98.js} +10 -5
- package/dist/src/{chunk-sj24fpz3.js → chunk-m76cmkfy.js} +28 -30
- package/dist/src/{chunk-5n8csngz.js → chunk-m96mbzcw.js} +816 -305
- package/dist/src/{chunk-xhq21g24.js → chunk-m9c1pnv9.js} +3 -3
- package/dist/src/{chunk-fnakrk0a.js → chunk-n3z3jaxy.js} +39 -36
- package/dist/src/{chunk-e7x51y0t.js → chunk-ndj0ay2p.js} +19 -18
- package/dist/src/chunk-pgs9jp6t.js +320 -0
- package/dist/src/chunk-pksaqt9a.js +25 -0
- package/dist/src/{chunk-ebm3kvnq.js → chunk-pnep7yhj.js} +31 -100
- package/dist/src/chunk-qn5xae7g.js +66 -0
- package/dist/src/{chunk-0pewfj89.js → chunk-qp8tgms2.js} +1 -1
- package/dist/src/{chunk-7k9g0x5e.js → chunk-rmgw8h27.js} +10 -8
- package/dist/src/{chunk-gbsbwssz.js → chunk-sf4ztxba.js} +3 -3
- package/dist/src/{chunk-7q96e161.js → chunk-shez7hkc.js} +6 -12
- package/dist/src/chunk-sqz614bh.js +30 -0
- package/dist/src/{chunk-09f9q26d.js → chunk-vdwzb63d.js} +2 -2
- package/dist/src/chunk-xtvgm6kf.js +48 -0
- package/dist/src/chunk-y8zm75jt.js +157 -0
- package/dist/src/chunk-yasqw2vj.js +756 -0
- package/dist/src/{chunk-ybte82d3.js → chunk-z0qmfqvd.js} +4 -3
- package/dist/src/chunk-zm705snh.js +53 -0
- package/dist/src/cli/index.js +39 -30
- package/dist/src/cli/main.js +14 -9
- package/dist/src/connector/herdr/installation.js +4 -4
- package/dist/src/connector/herdr/provider.js +9 -9
- package/dist/src/connector/herdr/runtime.js +7 -7
- package/dist/src/connector/luvus/index.js +7 -6
- package/dist/src/mcp/index.js +16 -14
- package/dist/src/runtime/index.js +16 -15
- package/dist/src/service/index.js +3 -4
- package/dist/src/store.js +4 -4
- package/dist/src/transport/index.js +9 -9
- package/dist/src/webui/index.js +975 -834
- package/package.json +1 -1
- package/dist/src/chunk-5j0gj6w2.js +0 -7
- package/dist/src/chunk-9yg6x3gt.js +0 -24
- package/dist/src/chunk-erdq0rn8.js +0 -1300
- package/dist/src/chunk-n073xnr1.js +0 -1117
- package/dist/src/chunk-rwpynx2j.js +0 -51
- package/dist/src/chunk-vwcntax3.js +0 -228
|
@@ -1,1300 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
windowsCurrentUserSid
|
|
4
|
-
} from "./chunk-09f9q26d.js";
|
|
5
|
-
import {
|
|
6
|
-
digest2
|
|
7
|
-
} from "./chunk-d71r315c.js";
|
|
8
|
-
import {
|
|
9
|
-
parseNativeJson2
|
|
10
|
-
} from "./chunk-7crhmqn2.js";
|
|
11
|
-
import {
|
|
12
|
-
object2,
|
|
13
|
-
fields2,
|
|
14
|
-
id2,
|
|
15
|
-
uint2,
|
|
16
|
-
boundedJson2,
|
|
17
|
-
hash2,
|
|
18
|
-
validatePolicies2
|
|
19
|
-
} from "./chunk-qt9m2kg2.js";
|
|
20
|
-
import {
|
|
21
|
-
observedHerdrSource
|
|
22
|
-
} from "./chunk-9tt13dm0.js";
|
|
23
|
-
import"./chunk-g7pnjzkk.js";
|
|
24
|
-
import"./chunk-fnakrk0a.js";
|
|
25
|
-
import"./chunk-mqyq5jh6.js";
|
|
26
|
-
import"./chunk-zj48jvfe.js";
|
|
27
|
-
import {
|
|
28
|
-
setupTarget,
|
|
29
|
-
selectedSetupPolicies,
|
|
30
|
-
setupReference,
|
|
31
|
-
setupSessionsFor,
|
|
32
|
-
GatewaySetup
|
|
33
|
-
} from "./chunk-n073xnr1.js";
|
|
34
|
-
import {
|
|
35
|
-
compileHerdrSchemas2
|
|
36
|
-
} from "./chunk-5h82060k.js";
|
|
37
|
-
import"./chunk-d4ejrtjq.js";
|
|
38
|
-
import {
|
|
39
|
-
LuvusAdapterError2
|
|
40
|
-
} from "./chunk-57ayxqnb.js";
|
|
41
|
-
import {
|
|
42
|
-
HerdrLocalError,
|
|
43
|
-
measureHerdrInstallation,
|
|
44
|
-
verifyHerdrInstallation
|
|
45
|
-
} from "./chunk-q5pgbwb3.js";
|
|
46
|
-
import {
|
|
47
|
-
isStoppedHerdrCommandCancellation,
|
|
48
|
-
herdrDeadline,
|
|
49
|
-
runHerdrCommand
|
|
50
|
-
} from "./chunk-863tk45b.js";
|
|
51
|
-
import"./chunk-zb641qa3.js";
|
|
52
|
-
import"./chunk-ttsrctxg.js";
|
|
53
|
-
import"./chunk-5svt6rwr.js";
|
|
54
|
-
import {
|
|
55
|
-
parseHerdrProviderRefConfig2
|
|
56
|
-
} from "./chunk-2tn0r64b.js";
|
|
57
|
-
import {
|
|
58
|
-
localLimits,
|
|
59
|
-
verifyInstallation,
|
|
60
|
-
runFixedProcess
|
|
61
|
-
} from "./chunk-0xgpxdgc.js";
|
|
62
|
-
import"./chunk-hz8pm6hz.js";
|
|
63
|
-
import"./chunk-y4hksha9.js";
|
|
64
|
-
import {
|
|
65
|
-
initializeManagedLuvusRoot2,
|
|
66
|
-
discoverLuvusInstallation,
|
|
67
|
-
MANAGED_LUVUS_REVISION2,
|
|
68
|
-
MANAGED_LUVUS_CLASSIFICATION2
|
|
69
|
-
} from "./chunk-5s2emvww.js";
|
|
70
|
-
import {
|
|
71
|
-
setupFileStamp,
|
|
72
|
-
writeSetupFile,
|
|
73
|
-
readSetupJson,
|
|
74
|
-
writeSetupJson
|
|
75
|
-
} from "./chunk-ebm3kvnq.js";
|
|
76
|
-
import {
|
|
77
|
-
readRuntimeConfig2
|
|
78
|
-
} from "./chunk-bspzg8er.js";
|
|
79
|
-
import {
|
|
80
|
-
privateDirectory
|
|
81
|
-
} from "./chunk-64ftwe9b.js";
|
|
82
|
-
import"./chunk-1bf0wmgk.js";
|
|
83
|
-
import {
|
|
84
|
-
runtimeReference
|
|
85
|
-
} from "./chunk-z7kkfrgz.js";
|
|
86
|
-
import {
|
|
87
|
-
herdrMode,
|
|
88
|
-
luvusMode,
|
|
89
|
-
selectedNativeProviders,
|
|
90
|
-
CliError2,
|
|
91
|
-
CliCancelled,
|
|
92
|
-
parseConfig2,
|
|
93
|
-
configDocument2,
|
|
94
|
-
readConfig2
|
|
95
|
-
} from "./chunk-hpzr94hm.js";
|
|
96
|
-
import {
|
|
97
|
-
createFileCredentialSink2,
|
|
98
|
-
loadCredential2
|
|
99
|
-
} from "./chunk-vwcntax3.js";
|
|
100
|
-
import {
|
|
101
|
-
LuvusDispatchClaims2
|
|
102
|
-
} from "./chunk-gbsbwssz.js";
|
|
103
|
-
import {
|
|
104
|
-
effectiveBinDirectories
|
|
105
|
-
} from "./chunk-3nnahj0j.js";
|
|
106
|
-
import"./chunk-zdxww7ms.js";
|
|
107
|
-
import"./chunk-0pewfj89.js";
|
|
108
|
-
import"./chunk-0y5542k8.js";
|
|
109
|
-
|
|
110
|
-
// src/cli/setup-terminal.ts
|
|
111
|
-
import { isatty } from "tty";
|
|
112
|
-
import { userInfo } from "os";
|
|
113
|
-
import { Writable } from "stream";
|
|
114
|
-
import { createInterface } from "readline/promises";
|
|
115
|
-
function hasLocalTerminal(io) {
|
|
116
|
-
const input = io.stdin.fd;
|
|
117
|
-
const output = io.stderr.fd;
|
|
118
|
-
return Number.isSafeInteger(input) && Number.isSafeInteger(output) && isatty(input) && isatty(output);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
class SetupTerminal {
|
|
122
|
-
configPath;
|
|
123
|
-
config;
|
|
124
|
-
io;
|
|
125
|
-
actor;
|
|
126
|
-
uid;
|
|
127
|
-
sid;
|
|
128
|
-
#reader;
|
|
129
|
-
#output;
|
|
130
|
-
#muted = false;
|
|
131
|
-
#closed = false;
|
|
132
|
-
#questionsClosed = false;
|
|
133
|
-
#queue = Promise.resolve();
|
|
134
|
-
constructor(configPath, config, io) {
|
|
135
|
-
this.configPath = configPath;
|
|
136
|
-
this.config = config;
|
|
137
|
-
this.io = io;
|
|
138
|
-
this.uid = process.platform === "win32" ? 0 : userInfo().uid;
|
|
139
|
-
this.sid = process.platform === "win32" ? windowsCurrentUserSid() : undefined;
|
|
140
|
-
this.actor = "operator_" + (this.sid ?? this.uid);
|
|
141
|
-
this.assertOwner();
|
|
142
|
-
this.#output = new Writable({
|
|
143
|
-
write: (chunk, _encoding, callback) => {
|
|
144
|
-
if (!this.#muted) {
|
|
145
|
-
io.stderr.write(chunk);
|
|
146
|
-
}
|
|
147
|
-
callback();
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
this.#reader = createInterface({ input: io.stdin, output: this.#output, terminal: true });
|
|
151
|
-
this.#reader.on("SIGINT", this.#interrupt);
|
|
152
|
-
io.signal.addEventListener("abort", this.#abort, { once: true });
|
|
153
|
-
}
|
|
154
|
-
static open(path, config, io) {
|
|
155
|
-
return hasLocalTerminal(io) ? new SetupTerminal(path, config, io) : undefined;
|
|
156
|
-
}
|
|
157
|
-
assertOwner() {
|
|
158
|
-
if (this.io.signal.aborted) {
|
|
159
|
-
throw new CliCancelled;
|
|
160
|
-
}
|
|
161
|
-
if (this.#closed || !hasLocalTerminal(this.io) || !this.#ownerUnchanged()) {
|
|
162
|
-
throw new CliError2("configuration_required");
|
|
163
|
-
}
|
|
164
|
-
const current = readConfig2(this.configPath);
|
|
165
|
-
if (current.role !== this.config.role || current.state_dir !== this.config.state_dir) {
|
|
166
|
-
throw new CliError2("configuration_required");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
#ownerUnchanged() {
|
|
170
|
-
if (process.platform === "win32") {
|
|
171
|
-
return this.sid !== undefined && windowsCurrentUserSid() === this.sid;
|
|
172
|
-
}
|
|
173
|
-
const user = userInfo();
|
|
174
|
-
return (process.platform === "linux" || process.platform === "darwin") && user.uid >= 0 && user.uid === this.uid && process.getuid?.() === this.uid && process.geteuid?.() === this.uid;
|
|
175
|
-
}
|
|
176
|
-
ask(prompt, secret = false, signal = this.io.signal) {
|
|
177
|
-
const active = AbortSignal.any([signal, this.io.signal]);
|
|
178
|
-
const operation = this.#queue.then(async () => {
|
|
179
|
-
this.assertOwner();
|
|
180
|
-
if (this.#questionsClosed || active.aborted) {
|
|
181
|
-
throw new CliError2("configuration_required");
|
|
182
|
-
}
|
|
183
|
-
try {
|
|
184
|
-
if (secret) {
|
|
185
|
-
this.io.stderr.write(prompt);
|
|
186
|
-
this.#muted = true;
|
|
187
|
-
}
|
|
188
|
-
let answer;
|
|
189
|
-
try {
|
|
190
|
-
answer = await this.#reader.question(secret ? "" : prompt, { signal: active });
|
|
191
|
-
} catch (error) {
|
|
192
|
-
if (this.io.signal.aborted && (error === active.reason || error instanceof Error && error.name === "AbortError")) {
|
|
193
|
-
throw new CliCancelled;
|
|
194
|
-
}
|
|
195
|
-
throw error;
|
|
196
|
-
}
|
|
197
|
-
this.assertOwner();
|
|
198
|
-
if (active.aborted) {
|
|
199
|
-
throw new CliError2("configuration_required");
|
|
200
|
-
}
|
|
201
|
-
return answer.trim();
|
|
202
|
-
} finally {
|
|
203
|
-
this.#muted = false;
|
|
204
|
-
if (secret) {
|
|
205
|
-
this.io.stderr.write(`
|
|
206
|
-
`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
this.#queue = operation.catch(() => {});
|
|
211
|
-
return operation;
|
|
212
|
-
}
|
|
213
|
-
async confirm(description, signal = this.io.signal) {
|
|
214
|
-
const answer = (await this.ask(description + " [y/N] ", false, signal)).toLowerCase();
|
|
215
|
-
return answer === "y" || answer === "yes";
|
|
216
|
-
}
|
|
217
|
-
show(text) {
|
|
218
|
-
this.assertOwner();
|
|
219
|
-
this.io.stderr.write(text);
|
|
220
|
-
}
|
|
221
|
-
#abort = () => this.close();
|
|
222
|
-
#interrupt = () => {
|
|
223
|
-
process.kill(process.pid, "SIGINT");
|
|
224
|
-
};
|
|
225
|
-
finishQuestions() {
|
|
226
|
-
if (!this.#questionsClosed) {
|
|
227
|
-
this.#questionsClosed = true;
|
|
228
|
-
this.#reader.off("SIGINT", this.#interrupt);
|
|
229
|
-
this.#reader.close();
|
|
230
|
-
this.#queue.then(() => {
|
|
231
|
-
this.#output.end();
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
close() {
|
|
236
|
-
if (this.#closed) {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
this.#closed = true;
|
|
240
|
-
this.io.signal.removeEventListener("abort", this.#abort);
|
|
241
|
-
this.finishQuestions();
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// src/cli/setup-native.ts
|
|
246
|
-
import { existsSync, mkdirSync } from "fs";
|
|
247
|
-
import { homedir } from "os";
|
|
248
|
-
import { isAbsolute, join } from "path";
|
|
249
|
-
|
|
250
|
-
// src/cli/setup-prompts.ts
|
|
251
|
-
function connectorNativePrompt(enabled, kind = "Luvus", attach = false) {
|
|
252
|
-
if (attach) {
|
|
253
|
-
return enabled === true ? `Attach to your running ${kind} session (or start one in your own config directory if none is running); remote callers will act on your REAL workspaces (terminal execution ENABLED in user config)? / \u9644\u7740\u5230\u4F60\u6B63\u5728\u8FD0\u884C\u7684 ${kind} \u4F1A\u8BDD\uFF08\u6CA1\u6709\u5219\u5728\u4F60\u81EA\u5DF1\u7684\u914D\u7F6E\u76EE\u5F55\u91CC\u542F\u52A8\u4E00\u4E2A\uFF09\uFF0C\u8FDC\u7A0B\u8C03\u7528\u5C06\u76F4\u63A5\u64CD\u4F5C\u4F60\u7684\u771F\u5B9E\u5DE5\u4F5C\u533A\uFF08\u7528\u6237\u914D\u7F6E\u5DF2\u5F00\u542F\u7EC8\u7AEF\u6267\u884C\uFF09\uFF1F` : `Attach to your running ${kind} session (or start one in your own config directory if none is running); remote callers will act on your REAL workspaces (terminal execution disabled)? / \u9644\u7740\u5230\u4F60\u6B63\u5728\u8FD0\u884C\u7684 ${kind} \u4F1A\u8BDD\uFF08\u6CA1\u6709\u5219\u5728\u4F60\u81EA\u5DF1\u7684\u914D\u7F6E\u76EE\u5F55\u91CC\u542F\u52A8\u4E00\u4E2A\uFF09\uFF0C\u8FDC\u7A0B\u8C03\u7528\u5C06\u76F4\u63A5\u64CD\u4F5C\u4F60\u7684\u771F\u5B9E\u5DE5\u4F5C\u533A\uFF08\u5173\u95ED\u7EC8\u7AEF\u6267\u884C\uFF09\uFF1F`;
|
|
254
|
-
}
|
|
255
|
-
return enabled === true ? `Discover ${kind} and create isolated state (terminal execution ENABLED in user config)? / \u53D1\u73B0 ${kind} \u5E76\u521B\u5EFA\u9694\u79BB\u72B6\u6001\uFF08\u7528\u6237\u914D\u7F6E\u5DF2\u5F00\u542F\u7EC8\u7AEF\u6267\u884C\uFF09\uFF1F` : `Discover this ${kind} installation and create its isolated local state (terminal execution disabled)? / \u53D1\u73B0 ${kind} \u5E76\u521B\u5EFA\u9694\u79BB\u72B6\u6001\uFF08\u5173\u95ED\u7EC8\u7AEF\u6267\u884C\uFF09\uFF1F`;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// src/cli/setup-native.ts
|
|
259
|
-
function assertSetupNativePath(config, terminal) {
|
|
260
|
-
if (selectedNativeProviders(config).includes("luvus") && luvusMode(config) === "managed" && Buffer.byteLength(join(config.state_dir, "n")) > 52) {
|
|
261
|
-
terminal.show(`native_state_path_too_long: choose a shorter data.directory before initialization (Luvus state path maximum 52 UTF-8 bytes). / \u521D\u59CB\u5316\u524D\u8BF7\u7F29\u77ED data.directory\uFF08Luvus \u72B6\u6001\u8DEF\u5F84\u6700\u591A52\u4E2AUTF-8\u5B57\u8282\uFF09\u3002
|
|
262
|
-
`);
|
|
263
|
-
throw new CliError2("configuration_required");
|
|
264
|
-
}
|
|
265
|
-
if (selectedNativeProviders(config).includes("herdr") && herdrMode(config) === "managed" && process.platform !== "win32" && Buffer.byteLength(join(config.state_dir, "hr/c/herdr/herdr-client.sock")) > 107) {
|
|
266
|
-
terminal.show(`native_state_path_too_long: choose a shorter data.directory for the private Herdr sockets (herdr.sock and herdr-client.sock). / \u8BF7\u7F29\u77ED data.directory \u4EE5\u5BB9\u7EB3\u9694\u79BB Herdr \u7684 socket\uFF08\u542B herdr-client.sock\uFF09\u3002
|
|
267
|
-
`);
|
|
268
|
-
throw new CliError2("configuration_required");
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
function setupBinary(config, kind = "luvus") {
|
|
272
|
-
const fallback = process.platform === "win32" ? [] : ["/usr/bin", "/bin"];
|
|
273
|
-
const name = process.platform === "win32" ? kind + ".exe" : kind;
|
|
274
|
-
for (const directory of [...effectiveBinDirectories(config.native?.runtime_bin_dirs), ...fallback]) {
|
|
275
|
-
const path = join(directory, name);
|
|
276
|
-
if (existsSync(path)) {
|
|
277
|
-
return path;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
throw new CliError2("configuration_required");
|
|
281
|
-
}
|
|
282
|
-
function setupAgentDirectories(config) {
|
|
283
|
-
const directories = effectiveBinDirectories(config.native?.agent_bin_dirs);
|
|
284
|
-
return directories.length > 0 ? { agent_bin_dirs: directories } : {};
|
|
285
|
-
}
|
|
286
|
-
function userLuvusHome(config) {
|
|
287
|
-
const configured = config.native?.luvus?.state_root;
|
|
288
|
-
const environment = process.env.LUVUS_HOME;
|
|
289
|
-
const path = configured ?? (environment && isAbsolute(environment) ? environment : join(homedir(), ".luvus"));
|
|
290
|
-
mkdirSync(path, { recursive: true, mode: 448 });
|
|
291
|
-
return path;
|
|
292
|
-
}
|
|
293
|
-
function prepared(value, attempt, machine, config) {
|
|
294
|
-
const row = object2(value);
|
|
295
|
-
fields2(row, ["schema_version", "attempt_id", "machine_id", "phase", "settings", "reference", "local", "remote"]);
|
|
296
|
-
if (row.schema_version !== 1 || row.attempt_id !== attempt || row.machine_id !== machine || row.phase !== "prepared" || row.reference !== setupReference(attempt) || boundedJson2(row.settings) !== boundedJson2(config.native ?? {})) {
|
|
297
|
-
throw new CliError2("configuration_required");
|
|
298
|
-
}
|
|
299
|
-
return row;
|
|
300
|
-
}
|
|
301
|
-
async function prepareSetupNative(config, terminal, credential, attempt, authorization) {
|
|
302
|
-
terminal.assertOwner();
|
|
303
|
-
id2(attempt);
|
|
304
|
-
id2(authorization.principal_id);
|
|
305
|
-
validatePolicies2(authorization.policies);
|
|
306
|
-
const path = runtimeReference(config.state_dir, "native-setup");
|
|
307
|
-
const existing = readSetupJson(path, 1048576);
|
|
308
|
-
if (existing !== undefined) {
|
|
309
|
-
return prepared(existing, attempt, credential.machine_id, config);
|
|
310
|
-
}
|
|
311
|
-
const binary = setupBinary(config);
|
|
312
|
-
const attach = luvusMode(config) === "attach";
|
|
313
|
-
if (!attach && process.platform === "win32") {
|
|
314
|
-
terminal.show(`luvus_managed_unsupported_on_windows: set native.luvus.mode to "attach". / Windows \u4E0A\u6682\u4E0D\u652F\u6301 managed\uFF0C\u8BF7\u628A native.luvus.mode \u8BBE\u4E3A "attach"\u3002
|
|
315
|
-
`);
|
|
316
|
-
throw new CliError2("configuration_required");
|
|
317
|
-
}
|
|
318
|
-
if (!attach && process.platform === "darwin") {
|
|
319
|
-
terminal.show(`luvus_managed_unsupported_on_macos: set native.luvus.mode to "attach". / macOS \u4E0A\u6682\u4E0D\u652F\u6301 managed\uFF0C\u8BF7\u628A native.luvus.mode \u8BBE\u4E3A "attach"\u3002
|
|
320
|
-
`);
|
|
321
|
-
throw new CliError2("configuration_required");
|
|
322
|
-
}
|
|
323
|
-
if (!await terminal.confirm(connectorNativePrompt(config.allow_terminal_execution, "Luvus", attach))) {
|
|
324
|
-
throw new CliError2("configuration_required");
|
|
325
|
-
}
|
|
326
|
-
const control = join(config.state_dir, "control");
|
|
327
|
-
const stateRoot = attach ? userLuvusHome(config) : join(config.state_dir, "n");
|
|
328
|
-
const home = join(config.state_dir, "h");
|
|
329
|
-
const work = join(config.state_dir, "w");
|
|
330
|
-
assertSetupNativePath(config, terminal);
|
|
331
|
-
for (const directory of [
|
|
332
|
-
control,
|
|
333
|
-
...attach ? [] : [stateRoot],
|
|
334
|
-
home,
|
|
335
|
-
work,
|
|
336
|
-
...["config", "data", "cache", "runtime"].map((name) => join(home, name))
|
|
337
|
-
]) {
|
|
338
|
-
privateDirectory(directory);
|
|
339
|
-
}
|
|
340
|
-
const reference = setupReference(attempt);
|
|
341
|
-
const target = setupTarget(credential.machine_id, "luvus", setupSessionsFor(config));
|
|
342
|
-
const discovered = await discoverLuvusInstallation({
|
|
343
|
-
target,
|
|
344
|
-
config_ref: reference,
|
|
345
|
-
binding_revision: 1,
|
|
346
|
-
binary,
|
|
347
|
-
state_root: stateRoot,
|
|
348
|
-
home,
|
|
349
|
-
cwd: work,
|
|
350
|
-
transport: "ipc",
|
|
351
|
-
allow_terminal_execution: false,
|
|
352
|
-
...attach ? { attach: true, user_home: homedir() } : {},
|
|
353
|
-
...setupAgentDirectories(config)
|
|
354
|
-
}, { revision: MANAGED_LUVUS_REVISION2, signal: terminal.io.signal });
|
|
355
|
-
const schema = await runFixedProcess(discovered.config, localLimits(discovered.config.limits), "schema", terminal.io.signal);
|
|
356
|
-
if (schema.exit_code !== 0 || hash2(schema.stdout) !== discovered.compiled.identity.schema_sha256) {
|
|
357
|
-
throw new CliError2("configuration_required");
|
|
358
|
-
}
|
|
359
|
-
await verifyInstallation(discovered.config);
|
|
360
|
-
const remote = {
|
|
361
|
-
kind: "luvus",
|
|
362
|
-
authentication: "remote_connector",
|
|
363
|
-
binding: {
|
|
364
|
-
machine_id: target.machine_id,
|
|
365
|
-
provider_id: target.provider_id,
|
|
366
|
-
kind: "luvus",
|
|
367
|
-
config_ref: reference,
|
|
368
|
-
binding_revision: 1,
|
|
369
|
-
enabled: true
|
|
370
|
-
},
|
|
371
|
-
target,
|
|
372
|
-
contract: {
|
|
373
|
-
binary_version: discovered.binaryVersion,
|
|
374
|
-
binary_sha256: discovered.config.binary_sha256,
|
|
375
|
-
schema_sha256: discovered.compiled.identity.schema_sha256,
|
|
376
|
-
adapter_revision: MANAGED_LUVUS_REVISION2,
|
|
377
|
-
classification_revision: MANAGED_LUVUS_CLASSIFICATION2,
|
|
378
|
-
schema_bundle: schema.stdout.toString("utf8")
|
|
379
|
-
},
|
|
380
|
-
peer_policy_revision: authorization.policies.local.revision,
|
|
381
|
-
raw_grant: {
|
|
382
|
-
principal_id: authorization.principal_id,
|
|
383
|
-
methods: [...authorization.policies.caller.grants[0].methods]
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
boundedJson2(remote, 1048576);
|
|
387
|
-
terminal.assertOwner();
|
|
388
|
-
writeSetupJson(path, {
|
|
389
|
-
schema_version: 1,
|
|
390
|
-
attempt_id: attempt,
|
|
391
|
-
machine_id: credential.machine_id,
|
|
392
|
-
phase: "initializing"
|
|
393
|
-
});
|
|
394
|
-
if (attach) {
|
|
395
|
-
const ledgerId = hash2(boundedJson2([reference, "user", target]));
|
|
396
|
-
new LuvusDispatchClaims2({
|
|
397
|
-
path: join(control, "luvus-claims.db"),
|
|
398
|
-
mode: "create",
|
|
399
|
-
ledger_id: ledgerId,
|
|
400
|
-
limits: { entries: 1024, bytes: 4194304 },
|
|
401
|
-
now: Date.now
|
|
402
|
-
}).close();
|
|
403
|
-
const result = {
|
|
404
|
-
schema_version: 1,
|
|
405
|
-
attempt_id: attempt,
|
|
406
|
-
machine_id: credential.machine_id,
|
|
407
|
-
phase: "prepared",
|
|
408
|
-
settings: config.native ?? {},
|
|
409
|
-
reference,
|
|
410
|
-
local: {
|
|
411
|
-
kind: "luvus",
|
|
412
|
-
authentication: "local_owner",
|
|
413
|
-
native: { mode: "user", config: discovered.config },
|
|
414
|
-
peer_policy_revision: remote.peer_policy_revision,
|
|
415
|
-
principal_id: authorization.principal_id,
|
|
416
|
-
policies: authorization.policies,
|
|
417
|
-
storage_mode: "open"
|
|
418
|
-
},
|
|
419
|
-
remote
|
|
420
|
-
};
|
|
421
|
-
writeSetupJson(path, result, true, 1048576);
|
|
422
|
-
return result;
|
|
423
|
-
}
|
|
424
|
-
const rootRecord = join(control, "luvus-root.json");
|
|
425
|
-
const root = await initializeManagedLuvusRoot2({
|
|
426
|
-
record_path: rootRecord,
|
|
427
|
-
config: discovered.config
|
|
428
|
-
});
|
|
429
|
-
try {
|
|
430
|
-
const ledgerId = hash2(boundedJson2([reference, root.root_id, root.root_revision, root.config.target]));
|
|
431
|
-
const claims = new LuvusDispatchClaims2({
|
|
432
|
-
path: join(control, "luvus-claims.db"),
|
|
433
|
-
mode: "create",
|
|
434
|
-
ledger_id: ledgerId,
|
|
435
|
-
limits: { entries: 1024, bytes: 4194304 },
|
|
436
|
-
now: Date.now
|
|
437
|
-
});
|
|
438
|
-
claims.close();
|
|
439
|
-
const result = {
|
|
440
|
-
schema_version: 1,
|
|
441
|
-
attempt_id: attempt,
|
|
442
|
-
machine_id: credential.machine_id,
|
|
443
|
-
phase: "prepared",
|
|
444
|
-
settings: config.native ?? {},
|
|
445
|
-
reference,
|
|
446
|
-
local: {
|
|
447
|
-
kind: "luvus",
|
|
448
|
-
authentication: "local_owner",
|
|
449
|
-
native: {
|
|
450
|
-
mode: "managed",
|
|
451
|
-
root_record: rootRecord,
|
|
452
|
-
expected_root_id: root.root_id,
|
|
453
|
-
expected_root_revision: root.root_revision
|
|
454
|
-
},
|
|
455
|
-
peer_policy_revision: remote.peer_policy_revision,
|
|
456
|
-
principal_id: authorization.principal_id,
|
|
457
|
-
policies: authorization.policies,
|
|
458
|
-
storage_mode: "open"
|
|
459
|
-
},
|
|
460
|
-
remote
|
|
461
|
-
};
|
|
462
|
-
writeSetupJson(path, result, true, 1048576);
|
|
463
|
-
return result;
|
|
464
|
-
} finally {
|
|
465
|
-
root.close();
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// src/cli/setup-herdr.ts
|
|
470
|
-
import { mkdirSync as mkdirSync2 } from "fs";
|
|
471
|
-
import { homedir as homedir2 } from "os";
|
|
472
|
-
import { isAbsolute as isAbsolute2, join as join2 } from "path";
|
|
473
|
-
function attachedDirectories(config) {
|
|
474
|
-
const home = homedir2();
|
|
475
|
-
if (process.platform === "win32") {
|
|
476
|
-
const roaming = config.native?.herdr?.config_home ?? process.env.APPDATA ?? join2(home, "AppData", "Roaming");
|
|
477
|
-
const local = process.env.LOCALAPPDATA ?? join2(home, "AppData", "Local");
|
|
478
|
-
for (const path of [roaming, local]) {
|
|
479
|
-
mkdirSync2(path, { recursive: true });
|
|
480
|
-
}
|
|
481
|
-
return { home, config_home: roaming, state_home: local, cache_home: local, cwd: home };
|
|
482
|
-
}
|
|
483
|
-
const xdg = (variable, fallback) => {
|
|
484
|
-
const value = process.env[variable];
|
|
485
|
-
return value && isAbsolute2(value) ? value : join2(home, fallback);
|
|
486
|
-
};
|
|
487
|
-
const directories = {
|
|
488
|
-
home,
|
|
489
|
-
config_home: config.native?.herdr?.config_home ?? xdg("XDG_CONFIG_HOME", ".config"),
|
|
490
|
-
state_home: xdg("XDG_STATE_HOME", ".local/state"),
|
|
491
|
-
cache_home: xdg("XDG_CACHE_HOME", ".cache"),
|
|
492
|
-
cwd: home
|
|
493
|
-
};
|
|
494
|
-
for (const path of [directories.config_home, directories.state_home, directories.cache_home]) {
|
|
495
|
-
mkdirSync2(path, { recursive: true, mode: 448 });
|
|
496
|
-
}
|
|
497
|
-
return directories;
|
|
498
|
-
}
|
|
499
|
-
async function version(config, signal) {
|
|
500
|
-
const scope = herdrDeadline(signal, 1000);
|
|
501
|
-
try {
|
|
502
|
-
const raw = await runHerdrCommand(config, "version", scope.signal);
|
|
503
|
-
const label = raw.toString("utf8").trim().replace(/^herdr\s+/i, "");
|
|
504
|
-
return label.length > 0 && label.length <= 128 && [...label].every((character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127) ? label : "unavailable";
|
|
505
|
-
} catch (error) {
|
|
506
|
-
if (error instanceof HerdrLocalError && (error.code === "herdr_cli_exit" || error.code === "herdr_timeout" && !signal.aborted)) {
|
|
507
|
-
return "unavailable";
|
|
508
|
-
}
|
|
509
|
-
throw error;
|
|
510
|
-
} finally {
|
|
511
|
-
scope.close();
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
function prepared2(value, config, attempt, machine) {
|
|
515
|
-
const row = object2(value);
|
|
516
|
-
fields2(row, ["schema_version", "attempt_id", "machine_id", "phase", "settings", "reference", "local", "remote"]);
|
|
517
|
-
if (row.schema_version !== 1 || row.attempt_id !== attempt || row.machine_id !== machine || row.phase !== "prepared" || row.reference !== setupReference(attempt, "herdr") || boundedJson2(row.settings) !== boundedJson2(config.native ?? {})) {
|
|
518
|
-
throw new CliError2("configuration_required");
|
|
519
|
-
}
|
|
520
|
-
return row;
|
|
521
|
-
}
|
|
522
|
-
async function prepareSetupHerdr(config, terminal, credential, attempt, authorization) {
|
|
523
|
-
terminal.assertOwner();
|
|
524
|
-
const path = runtimeReference(config.state_dir, "herdr-setup");
|
|
525
|
-
const existing = readSetupJson(path, 1048576);
|
|
526
|
-
if (existing !== undefined) {
|
|
527
|
-
return prepared2(existing, config, attempt, credential.machine_id);
|
|
528
|
-
}
|
|
529
|
-
const binary = setupBinary(config, "herdr");
|
|
530
|
-
const attach = herdrMode(config) === "attach";
|
|
531
|
-
if (!attach && process.platform === "win32") {
|
|
532
|
-
terminal.show(`herdr_managed_unsupported_on_windows: set native.herdr.mode to "attach". / Windows \u4E0A\u6682\u4E0D\u652F\u6301 managed\uFF0C\u8BF7\u628A native.herdr.mode \u8BBE\u4E3A "attach"\u3002
|
|
533
|
-
`);
|
|
534
|
-
throw new CliError2("configuration_required");
|
|
535
|
-
}
|
|
536
|
-
if (!attach && process.platform === "darwin") {
|
|
537
|
-
terminal.show(`herdr_managed_unsupported_on_macos: set native.herdr.mode to "attach". / macOS \u4E0A\u6682\u4E0D\u652F\u6301 managed\uFF0C\u8BF7\u628A native.herdr.mode \u8BBE\u4E3A "attach"\u3002
|
|
538
|
-
`);
|
|
539
|
-
throw new CliError2("configuration_required");
|
|
540
|
-
}
|
|
541
|
-
if (!await terminal.confirm(connectorNativePrompt(config.allow_terminal_execution, "Herdr", attach))) {
|
|
542
|
-
throw new CliError2("configuration_required");
|
|
543
|
-
}
|
|
544
|
-
assertSetupNativePath(config, terminal);
|
|
545
|
-
const root = join2(config.state_dir, "hr");
|
|
546
|
-
privateDirectory(root);
|
|
547
|
-
for (const name of ["h", "c", "s", "k", "t", "w", "control"]) {
|
|
548
|
-
privateDirectory(join2(root, name));
|
|
549
|
-
}
|
|
550
|
-
const target = setupTarget(credential.machine_id, "herdr");
|
|
551
|
-
const reference = setupReference(attempt, "herdr");
|
|
552
|
-
const local = {
|
|
553
|
-
target,
|
|
554
|
-
binding_revision: 1,
|
|
555
|
-
config_ref: reference,
|
|
556
|
-
binary,
|
|
557
|
-
...attach ? attachedDirectories(config) : {
|
|
558
|
-
home: join2(root, "h"),
|
|
559
|
-
config_home: join2(root, "c"),
|
|
560
|
-
state_home: join2(root, "s"),
|
|
561
|
-
cache_home: join2(root, "k"),
|
|
562
|
-
cwd: join2(root, "w")
|
|
563
|
-
},
|
|
564
|
-
temporary_home: join2(root, "t"),
|
|
565
|
-
allow_terminal_execution: false,
|
|
566
|
-
...attach ? { attach: true } : {},
|
|
567
|
-
...setupAgentDirectories(config)
|
|
568
|
-
};
|
|
569
|
-
const measured = measureHerdrInstallation(local);
|
|
570
|
-
const scope = herdrDeadline(terminal.io.signal, 1e4);
|
|
571
|
-
let raw;
|
|
572
|
-
let source;
|
|
573
|
-
try {
|
|
574
|
-
const label = await version(local, scope.signal);
|
|
575
|
-
raw = await runHerdrCommand(local, "schema", scope.signal);
|
|
576
|
-
const compiled = await compileHerdrSchemas2(raw, scope.signal);
|
|
577
|
-
if (verifyHerdrInstallation(local, measured.sha256) !== measured.stamp || compiled.schema_sha256 !== hash2(raw)) {
|
|
578
|
-
throw new CliError2("configuration_required");
|
|
579
|
-
}
|
|
580
|
-
source = observedHerdrSource(label, measured.sha256, compiled.schema_sha256, compiled.protocol);
|
|
581
|
-
} catch (error) {
|
|
582
|
-
if (terminal.io.signal.aborted && isStoppedHerdrCommandCancellation(error)) {
|
|
583
|
-
throw new CliCancelled;
|
|
584
|
-
}
|
|
585
|
-
throw error;
|
|
586
|
-
} finally {
|
|
587
|
-
scope.close();
|
|
588
|
-
}
|
|
589
|
-
const shared = {
|
|
590
|
-
version: 1,
|
|
591
|
-
target,
|
|
592
|
-
binding_revision: 1,
|
|
593
|
-
config_ref: reference,
|
|
594
|
-
expected_source: source,
|
|
595
|
-
peer_policy_revision: authorization.policies.local.revision
|
|
596
|
-
};
|
|
597
|
-
const control = join2(root, "control");
|
|
598
|
-
const schemaPath = join2(control, "schema.json");
|
|
599
|
-
const limits = { entries: 1024, bytes: 4194304 };
|
|
600
|
-
const claimsRef = {
|
|
601
|
-
path: join2(control, "claims.db"),
|
|
602
|
-
mode: "open",
|
|
603
|
-
ledger_id: hash2(boundedJson2([reference, target, source, "claims"])),
|
|
604
|
-
limits
|
|
605
|
-
};
|
|
606
|
-
const provider = parseHerdrProviderRefConfig2({
|
|
607
|
-
...shared,
|
|
608
|
-
schema_path: schemaPath,
|
|
609
|
-
connector: {
|
|
610
|
-
local,
|
|
611
|
-
principal_id: authorization.principal_id,
|
|
612
|
-
claims: claimsRef
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
terminal.assertOwner();
|
|
616
|
-
writeSetupJson(path, {
|
|
617
|
-
schema_version: 1,
|
|
618
|
-
attempt_id: attempt,
|
|
619
|
-
machine_id: credential.machine_id,
|
|
620
|
-
phase: "initializing"
|
|
621
|
-
});
|
|
622
|
-
writeSetupFile(schemaPath, raw);
|
|
623
|
-
const claims = new LuvusDispatchClaims2({ ...claimsRef, mode: "create", now: Date.now });
|
|
624
|
-
try {} finally {
|
|
625
|
-
claims.close();
|
|
626
|
-
}
|
|
627
|
-
const result = {
|
|
628
|
-
schema_version: 1,
|
|
629
|
-
attempt_id: attempt,
|
|
630
|
-
machine_id: credential.machine_id,
|
|
631
|
-
phase: "prepared",
|
|
632
|
-
settings: config.native ?? {},
|
|
633
|
-
reference,
|
|
634
|
-
local: {
|
|
635
|
-
kind: "herdr",
|
|
636
|
-
config: provider,
|
|
637
|
-
native: { mode: attach ? "attach" : "managed" },
|
|
638
|
-
policies: authorization.policies
|
|
639
|
-
},
|
|
640
|
-
remote: { kind: "herdr", config: shared, schema_bundle: raw.toString("utf8") }
|
|
641
|
-
};
|
|
642
|
-
writeSetupJson(path, result, true, 1048576);
|
|
643
|
-
return result;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// src/cli/setup-connector.ts
|
|
647
|
-
import { join as join3 } from "path";
|
|
648
|
-
async function responseJson(response) {
|
|
649
|
-
if (!response.body || response.status !== 200) {
|
|
650
|
-
await response.body?.cancel().catch(() => {});
|
|
651
|
-
throw new CliError2("configuration_required");
|
|
652
|
-
}
|
|
653
|
-
const reader = response.body.getReader();
|
|
654
|
-
const chunks = [];
|
|
655
|
-
let bytes = 0;
|
|
656
|
-
try {
|
|
657
|
-
while (true) {
|
|
658
|
-
const value = await reader.read();
|
|
659
|
-
if (value.done) {
|
|
660
|
-
break;
|
|
661
|
-
}
|
|
662
|
-
bytes += value.value.length;
|
|
663
|
-
if (bytes > 16384) {
|
|
664
|
-
throw new CliError2("configuration_required");
|
|
665
|
-
}
|
|
666
|
-
chunks.push(value.value);
|
|
667
|
-
}
|
|
668
|
-
const parsed = parseNativeJson2(Buffer.concat(chunks, bytes), { object_bytes: 16384 });
|
|
669
|
-
if (!parsed.ok) {
|
|
670
|
-
throw new CliError2("configuration_required");
|
|
671
|
-
}
|
|
672
|
-
return object2(parsed.value);
|
|
673
|
-
} finally {
|
|
674
|
-
await reader.cancel().catch(() => {});
|
|
675
|
-
reader.releaseLock();
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
async function post(config, path, value, signal, timeout = 5000) {
|
|
679
|
-
const origin = new URL(config.gateway_url);
|
|
680
|
-
origin.protocol = origin.protocol === "wss:" ? "https:" : "http:";
|
|
681
|
-
return await responseJson(await fetch(new URL(path, origin), {
|
|
682
|
-
method: "POST",
|
|
683
|
-
redirect: "error",
|
|
684
|
-
headers: { "content-type": "application/json" },
|
|
685
|
-
body: boundedJson2(value, path === "/pairing/native" || path === "/pairing/claim/activation/native" ? 1048576 : 16384),
|
|
686
|
-
signal: AbortSignal.any([signal, AbortSignal.timeout(timeout)])
|
|
687
|
-
}));
|
|
688
|
-
}
|
|
689
|
-
function attempt(value, config) {
|
|
690
|
-
const row = object2(value);
|
|
691
|
-
fields2(row, ["schema_version", "attempt_id", "machine_id", "gateway_url", "phase"]);
|
|
692
|
-
id2(row.attempt_id);
|
|
693
|
-
id2(row.machine_id);
|
|
694
|
-
if (row.schema_version !== 1 || row.gateway_url !== config.gateway_url || !["requesting", "credential_saved", "paired", "configured"].includes(String(row.phase))) {
|
|
695
|
-
throw new CliError2("configuration_required");
|
|
696
|
-
}
|
|
697
|
-
return row;
|
|
698
|
-
}
|
|
699
|
-
async function pair(config, terminal) {
|
|
700
|
-
const path = runtimeReference(config.state_dir, "pairing");
|
|
701
|
-
const credentialPath = runtimeReference(config.state_dir, "device");
|
|
702
|
-
const previous = readSetupJson(path);
|
|
703
|
-
let current;
|
|
704
|
-
let credential;
|
|
705
|
-
if (previous === undefined) {
|
|
706
|
-
const value = await terminal.ask("One-time pairing code / \u4E00\u6B21\u6027\u914D\u5BF9\u7801: ", true);
|
|
707
|
-
const parts = value.split(".");
|
|
708
|
-
if (parts.length !== 3) {
|
|
709
|
-
throw new CliError2("configuration_required");
|
|
710
|
-
}
|
|
711
|
-
const [machine, original, enrollment] = parts;
|
|
712
|
-
id2(machine);
|
|
713
|
-
id2(original);
|
|
714
|
-
if (!enrollment || !/^[A-Za-z0-9_-]{32,256}$/.test(enrollment)) {
|
|
715
|
-
throw new CliError2("configuration_required");
|
|
716
|
-
}
|
|
717
|
-
if (!await terminal.confirm("Trust this Gateway address for one-time device enrollment and reviewed native setup? / \u4FE1\u4EFB\u6B64 Gateway \u5730\u5740\u5E76\u5151\u6362\u4E00\u6B21\u6027\u8BBE\u5907\u914D\u5BF9\uFF1F")) {
|
|
718
|
-
throw new CliError2("configuration_required");
|
|
719
|
-
}
|
|
720
|
-
current = {
|
|
721
|
-
schema_version: 1,
|
|
722
|
-
attempt_id: original,
|
|
723
|
-
machine_id: machine,
|
|
724
|
-
gateway_url: config.gateway_url,
|
|
725
|
-
phase: "requesting"
|
|
726
|
-
};
|
|
727
|
-
writeSetupJson(path, current);
|
|
728
|
-
const delivery = await post(config, "/pairing/redeem", { attempt_id: original, machine_id: machine, enrollment }, terminal.io.signal);
|
|
729
|
-
fields2(delivery, [
|
|
730
|
-
"attempt_id",
|
|
731
|
-
"delivery_id",
|
|
732
|
-
"revision",
|
|
733
|
-
"machine_id",
|
|
734
|
-
"recovery_generation",
|
|
735
|
-
"credential",
|
|
736
|
-
"state",
|
|
737
|
-
"expires_at"
|
|
738
|
-
]);
|
|
739
|
-
id2(delivery.delivery_id);
|
|
740
|
-
id2(delivery.recovery_generation);
|
|
741
|
-
uint2(delivery.revision, true);
|
|
742
|
-
if (delivery.attempt_id !== original || delivery.machine_id !== machine || delivery.state !== "persist_and_acknowledge" || typeof delivery.credential !== "string" || !/^[A-Za-z0-9_-]{32,256}$/.test(delivery.credential) || !Number.isSafeInteger(delivery.expires_at) || Number(delivery.expires_at) <= Date.now()) {
|
|
743
|
-
throw new CliError2("configuration_required");
|
|
744
|
-
}
|
|
745
|
-
credential = {
|
|
746
|
-
machine_id: machine,
|
|
747
|
-
recovery_generation: delivery.recovery_generation,
|
|
748
|
-
credential: delivery.credential
|
|
749
|
-
};
|
|
750
|
-
const sink = createFileCredentialSink2(credentialPath, credential);
|
|
751
|
-
try {
|
|
752
|
-
const ack = await sink.deliver(credential, terminal.io.signal, {
|
|
753
|
-
delivery_id: delivery.delivery_id,
|
|
754
|
-
revision: delivery.revision
|
|
755
|
-
});
|
|
756
|
-
current = { ...current, phase: "credential_saved" };
|
|
757
|
-
writeSetupJson(path, current, true);
|
|
758
|
-
try {
|
|
759
|
-
const receipt = await post(config, "/pairing/ack", { attempt_id: original, authentication: credential, acknowledgement: ack }, terminal.io.signal);
|
|
760
|
-
fields2(receipt, ["machine_id", "delivery_id", "state", "attempt_id"]);
|
|
761
|
-
if (receipt.machine_id !== machine || receipt.delivery_id !== delivery.delivery_id || receipt.state !== "delivered" || receipt.attempt_id !== original) {
|
|
762
|
-
throw new CliError2("configuration_required");
|
|
763
|
-
}
|
|
764
|
-
} catch {
|
|
765
|
-
terminal.assertOwner();
|
|
766
|
-
}
|
|
767
|
-
} finally {
|
|
768
|
-
sink.close();
|
|
769
|
-
}
|
|
770
|
-
} else {
|
|
771
|
-
current = attempt(previous, config);
|
|
772
|
-
if (current.phase === "requesting") {
|
|
773
|
-
throw new CliError2("configuration_required");
|
|
774
|
-
}
|
|
775
|
-
credential = loadCredential2(credentialPath);
|
|
776
|
-
if (credential.machine_id !== current.machine_id) {
|
|
777
|
-
throw new CliError2("configuration_required");
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
const status = await post(config, "/pairing/status", {
|
|
781
|
-
attempt_id: current.attempt_id,
|
|
782
|
-
authentication: credential,
|
|
783
|
-
providers: selectedNativeProviders(config),
|
|
784
|
-
sessions: setupSessionsFor(config)
|
|
785
|
-
}, terminal.io.signal);
|
|
786
|
-
fields2(status, ["attempt_id", "machine_id", "recovery_generation", "phase", "authorization"], ["lineage"]);
|
|
787
|
-
if (status.lineage !== undefined) {
|
|
788
|
-
id2(status.lineage);
|
|
789
|
-
}
|
|
790
|
-
if (status.attempt_id !== current.attempt_id || status.machine_id !== credential.machine_id || status.recovery_generation !== credential.recovery_generation || !["paired", "native_approved", "configured"].includes(String(status.phase))) {
|
|
791
|
-
throw new CliError2("configuration_required");
|
|
792
|
-
}
|
|
793
|
-
const authorization = object2(status.authorization);
|
|
794
|
-
fields2(authorization, ["principal_id", "policies"]);
|
|
795
|
-
id2(authorization.principal_id);
|
|
796
|
-
const expected = selectedSetupPolicies({
|
|
797
|
-
attempt_id: current.attempt_id,
|
|
798
|
-
actor: authorization.principal_id,
|
|
799
|
-
...status.lineage ? { lineage: status.lineage } : {}
|
|
800
|
-
}, credential.machine_id, selectedNativeProviders(config), setupSessionsFor(config));
|
|
801
|
-
if (boundedJson2(authorization) !== boundedJson2(expected)) {
|
|
802
|
-
throw new CliError2("configuration_required");
|
|
803
|
-
}
|
|
804
|
-
current = { ...current, phase: "paired" };
|
|
805
|
-
writeSetupJson(path, current, true);
|
|
806
|
-
return { current, credential, authorization: expected };
|
|
807
|
-
}
|
|
808
|
-
async function setupConnector(config, terminal) {
|
|
809
|
-
if (readRuntimeConfig2(config).providers.length > 0) {
|
|
810
|
-
terminal.finishQuestions();
|
|
811
|
-
return config;
|
|
812
|
-
}
|
|
813
|
-
if (!config.gateway_url) {
|
|
814
|
-
const previous = setupFileStamp(terminal.configPath);
|
|
815
|
-
if (boundedJson2(configDocument2(readConfig2(terminal.configPath))) !== boundedJson2(configDocument2(config))) {
|
|
816
|
-
throw new CliError2("config_changed");
|
|
817
|
-
}
|
|
818
|
-
const address = await terminal.ask("Gateway address (ws://host:port or wss://host) / Gateway \u5730\u5740: ");
|
|
819
|
-
config = parseConfig2({ ...configDocument2(config), gateway: { url: address } });
|
|
820
|
-
writeSetupFile(terminal.configPath, JSON.stringify(configDocument2(config), null, 2) + `
|
|
821
|
-
`, true, previous);
|
|
822
|
-
}
|
|
823
|
-
assertSetupNativePath(config, terminal);
|
|
824
|
-
privateDirectory(config.state_dir);
|
|
825
|
-
privateDirectory(join3(config.state_dir, "managed"));
|
|
826
|
-
const paired = await pair(config, terminal);
|
|
827
|
-
terminal.show(`pairing_completed / \u8BBE\u5907\u8EAB\u4EFD\u5DF2\u6301\u4E45\u914D\u5BF9\uFF1B\u63A5\u4E0B\u6765\u53D1\u73B0\u6240\u9009\u539F\u751F\u5B89\u88C5\u3002
|
|
828
|
-
`);
|
|
829
|
-
const prepared = [];
|
|
830
|
-
for (const kind of selectedNativeProviders(config)) {
|
|
831
|
-
prepared.push(await (kind === "luvus" ? prepareSetupNative : prepareSetupHerdr)(config, terminal, paired.credential, paired.current.attempt_id, paired.authorization));
|
|
832
|
-
}
|
|
833
|
-
const installations = prepared.map((native) => ({
|
|
834
|
-
reference: native.reference,
|
|
835
|
-
value: native.remote
|
|
836
|
-
}));
|
|
837
|
-
const installation = installations.length === 1 && selectedNativeProviders(config)[0] === "luvus" ? installations[0] : { installations };
|
|
838
|
-
const expected = hash2(boundedJson2(installation, 1048576));
|
|
839
|
-
try {
|
|
840
|
-
const applied = await post(config, "/pairing/native", { attempt_id: paired.current.attempt_id, authentication: paired.credential, installation }, terminal.io.signal, 300000);
|
|
841
|
-
fields2(applied, ["state", "fingerprint"]);
|
|
842
|
-
if (applied.state !== "configured" || applied.fingerprint !== expected) {
|
|
843
|
-
throw new CliError2("configuration_required");
|
|
844
|
-
}
|
|
845
|
-
} catch {
|
|
846
|
-
terminal.show(`Native setup response unavailable; retain the original setup and rerun Connector to check it. / \u539F\u751F\u8BBE\u7F6E\u54CD\u5E94\u4E0D\u53EF\u7528\uFF1B\u4FDD\u7559\u539F\u72B6\u6001\uFF0C\u91CD\u65B0\u8FD0\u884C Connector \u6838\u5BF9\u3002
|
|
847
|
-
`);
|
|
848
|
-
throw new CliError2("configuration_required");
|
|
849
|
-
}
|
|
850
|
-
writeSetupJson(runtimeReference(config.state_dir, "providers"), {
|
|
851
|
-
schema_version: 1,
|
|
852
|
-
role: "connector",
|
|
853
|
-
providers: prepared.map((native) => ({
|
|
854
|
-
reference: native.reference,
|
|
855
|
-
value: native.local
|
|
856
|
-
}))
|
|
857
|
-
}, false, 1048576);
|
|
858
|
-
writeSetupJson(runtimeReference(config.state_dir, "pairing"), { ...paired.current, phase: "configured" }, true);
|
|
859
|
-
terminal.show(`Native state prepared; starting the real Connector. Watch Gateway for connector_ready. / \u539F\u751F\u72B6\u6001\u5DF2\u51C6\u5907\uFF1B\u542F\u52A8\u6B63\u5F0F Connector\uFF0C\u4EE5 Gateway \u7684\u5B9E\u9645\u5C31\u7EEA\u63D0\u793A\u4E3A\u51C6\u3002
|
|
860
|
-
`);
|
|
861
|
-
terminal.finishQuestions();
|
|
862
|
-
return config;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
// src/cli/setup-repair.ts
|
|
866
|
-
async function repairConnector(config, terminal) {
|
|
867
|
-
const credentialPath = runtimeReference(config.state_dir, "device");
|
|
868
|
-
if (!config.gateway_url || readRuntimeConfig2(config).providers.length === 0) {
|
|
869
|
-
throw new CliError2("configuration_required");
|
|
870
|
-
}
|
|
871
|
-
const old = loadCredential2(credentialPath);
|
|
872
|
-
terminal.show("This Connector is paired as " + old.machine_id + ". Re-pairing replaces only its Gateway credential; native setup is kept. Stop any other running Connector for this device first. / \u6B64 Connector \u5DF2\u914D\u5BF9\u4E3A " + old.machine_id + `\u3002\u91CD\u65B0\u914D\u5BF9\u53EA\u66F4\u6362\u4E0E Gateway \u7684\u51ED\u636E\uFF0C\u4FDD\u7559\u539F\u751F\u8BBE\u7F6E\uFF1B\u8BF7\u5148\u505C\u6B62\u8BE5\u8BBE\u5907\u4E0A\u5176\u5B83\u6B63\u5728\u8FD0\u884C\u7684 Connector\u3002
|
|
873
|
-
`);
|
|
874
|
-
const value = await terminal.ask("One-time re-pairing code from rovloom gateway --repair-device / \u6765\u81EA rovloom gateway --repair-device \u7684\u4E00\u6B21\u6027\u7801: ", true);
|
|
875
|
-
const parts = value.split(".");
|
|
876
|
-
if (parts.length !== 3) {
|
|
877
|
-
throw new CliError2("configuration_required");
|
|
878
|
-
}
|
|
879
|
-
const [machine, attempt, enrollment] = parts;
|
|
880
|
-
id2(machine);
|
|
881
|
-
id2(attempt);
|
|
882
|
-
if (machine !== old.machine_id || !enrollment || !/^[A-Za-z0-9_-]{32,256}$/.test(enrollment)) {
|
|
883
|
-
throw new CliError2("configuration_required");
|
|
884
|
-
}
|
|
885
|
-
if (!await terminal.confirm("Trust this Gateway address and replace this device credential with a new one? / \u4FE1\u4EFB\u6B64 Gateway \u5730\u5740\u5E76\u7528\u65B0\u51ED\u636E\u66FF\u6362\u672C\u673A\u8BBE\u5907\u51ED\u636E\uFF1F")) {
|
|
886
|
-
throw new CliError2("configuration_required");
|
|
887
|
-
}
|
|
888
|
-
const delivery = await post(config, "/pairing/redeem", { attempt_id: attempt, machine_id: machine, enrollment }, terminal.io.signal);
|
|
889
|
-
fields2(delivery, [
|
|
890
|
-
"attempt_id",
|
|
891
|
-
"delivery_id",
|
|
892
|
-
"revision",
|
|
893
|
-
"machine_id",
|
|
894
|
-
"recovery_generation",
|
|
895
|
-
"credential",
|
|
896
|
-
"state",
|
|
897
|
-
"expires_at"
|
|
898
|
-
]);
|
|
899
|
-
id2(delivery.delivery_id);
|
|
900
|
-
id2(delivery.recovery_generation);
|
|
901
|
-
uint2(delivery.revision, true);
|
|
902
|
-
if (delivery.attempt_id !== attempt || delivery.machine_id !== machine || delivery.state !== "persist_and_acknowledge" || typeof delivery.credential !== "string" || !/^[A-Za-z0-9_-]{32,256}$/.test(delivery.credential) || !Number.isSafeInteger(delivery.expires_at) || Number(delivery.expires_at) <= Date.now()) {
|
|
903
|
-
throw new CliError2("configuration_required");
|
|
904
|
-
}
|
|
905
|
-
const credential = {
|
|
906
|
-
machine_id: machine,
|
|
907
|
-
recovery_generation: delivery.recovery_generation,
|
|
908
|
-
credential: delivery.credential
|
|
909
|
-
};
|
|
910
|
-
const sink = createFileCredentialSink2(credentialPath, credential, true);
|
|
911
|
-
try {
|
|
912
|
-
const ack = await sink.deliver(credential, terminal.io.signal, {
|
|
913
|
-
delivery_id: delivery.delivery_id,
|
|
914
|
-
revision: delivery.revision
|
|
915
|
-
});
|
|
916
|
-
try {
|
|
917
|
-
const receipt = await post(config, "/pairing/ack", { attempt_id: attempt, authentication: credential, acknowledgement: ack }, terminal.io.signal);
|
|
918
|
-
fields2(receipt, ["machine_id", "delivery_id", "state", "attempt_id"]);
|
|
919
|
-
if (receipt.machine_id !== machine || receipt.delivery_id !== delivery.delivery_id || receipt.state !== "delivered" || receipt.attempt_id !== attempt) {
|
|
920
|
-
throw new CliError2("configuration_required");
|
|
921
|
-
}
|
|
922
|
-
} catch {
|
|
923
|
-
terminal.assertOwner();
|
|
924
|
-
terminal.show(`Gateway confirmation unavailable; if this Connector cannot authenticate, run the re-pairing again. / \u672A\u6536\u5230 Gateway \u7684\u786E\u8BA4\uFF1B\u82E5 Connector \u4E4B\u540E\u65E0\u6CD5\u8BA4\u8BC1\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C\u91CD\u65B0\u914D\u5BF9\u3002
|
|
925
|
-
`);
|
|
926
|
-
}
|
|
927
|
-
} finally {
|
|
928
|
-
sink.close();
|
|
929
|
-
}
|
|
930
|
-
terminal.show(`device_repaired / \u8BBE\u5907\u51ED\u636E\u5DF2\u66F4\u6362\uFF1B\u6B63\u5728\u542F\u52A8 Connector\u3002
|
|
931
|
-
`);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
// src/cli/online-claim.ts
|
|
935
|
-
import { randomBytes } from "crypto";
|
|
936
|
-
import { existsSync as existsSync2 } from "fs";
|
|
937
|
-
import { join as join4 } from "path";
|
|
938
|
-
function record(value, config) {
|
|
939
|
-
const row = object2(value);
|
|
940
|
-
fields2(row, ["schema_version", "claim_id", "machine_id", "pairing_id", "gateway_url", "phase"], ["secret", "expires_at", "credential_hash", "recovery_generation"]);
|
|
941
|
-
id2(row.claim_id);
|
|
942
|
-
id2(row.machine_id);
|
|
943
|
-
id2(row.pairing_id);
|
|
944
|
-
if (row.expires_at !== undefined && (!Number.isSafeInteger(row.expires_at) || Number(row.expires_at) < 0) || (row.credential_hash !== undefined || row.recovery_generation !== undefined) && (typeof row.credential_hash !== "string" || !/^[0-9a-f]{64}$/.test(row.credential_hash) || typeof row.recovery_generation !== "string") || row.schema_version !== 1 || row.gateway_url !== config.gateway_url || !["waiting", "credential_saved", "paired", "configured"].includes(String(row.phase)) || row.phase !== "paired" && row.phase !== "configured" && (typeof row.secret !== "string" || !/^[A-Za-z0-9_-]{16,256}$/.test(row.secret))) {
|
|
945
|
-
throw new CliError2("configuration_required");
|
|
946
|
-
}
|
|
947
|
-
if (row.recovery_generation !== undefined) {
|
|
948
|
-
id2(row.recovery_generation);
|
|
949
|
-
}
|
|
950
|
-
return row;
|
|
951
|
-
}
|
|
952
|
-
var POLL_BUDGET_MS = 600000;
|
|
953
|
-
var POLL_INTERVAL_MS = 2000;
|
|
954
|
-
async function claimConnector(config, terminal) {
|
|
955
|
-
const credentialPath = runtimeReference(config.state_dir, "device");
|
|
956
|
-
if (!config.gateway_url) {
|
|
957
|
-
const previous = setupFileStamp(terminal.configPath);
|
|
958
|
-
if (boundedJson2(configDocument2(readConfig2(terminal.configPath))) !== boundedJson2(configDocument2(config))) {
|
|
959
|
-
throw new CliError2("config_changed");
|
|
960
|
-
}
|
|
961
|
-
const address = await terminal.ask("Gateway address (ws://host:port or wss://host) / Gateway \u5730\u5740: ");
|
|
962
|
-
config = parseConfig2({ ...configDocument2(config), gateway: { url: address } });
|
|
963
|
-
writeSetupFile(terminal.configPath, JSON.stringify(configDocument2(config), null, 2) + `
|
|
964
|
-
`, true, previous);
|
|
965
|
-
}
|
|
966
|
-
privateDirectory(config.state_dir);
|
|
967
|
-
privateDirectory(join4(config.state_dir, "managed"));
|
|
968
|
-
const path = runtimeReference(config.state_dir, "pairing-claim");
|
|
969
|
-
const previous = readSetupJson(path);
|
|
970
|
-
if (previous === undefined && (readRuntimeConfig2(config).providers.length > 0 || existsSync2(credentialPath))) {
|
|
971
|
-
throw new CliError2("configuration_required");
|
|
972
|
-
}
|
|
973
|
-
let current = previous === undefined ? undefined : record(previous, config);
|
|
974
|
-
if (existsSync2(credentialPath)) {
|
|
975
|
-
if (!current || !matchesSavedCredential(current, credentialPath)) {
|
|
976
|
-
throw new CliError2("configuration_required");
|
|
977
|
-
}
|
|
978
|
-
if (current.phase === "configured") {
|
|
979
|
-
terminal.show(`pairing_completed / \u8BBE\u5907\u8EAB\u4EFD\u5DF2\u6301\u4E45\u914D\u5BF9\uFF0C\u539F\u751F\u6FC0\u6D3B\u5DF2\u5B8C\u6210\u3002
|
|
980
|
-
`);
|
|
981
|
-
return config;
|
|
982
|
-
}
|
|
983
|
-
if (current.phase === "paired") {
|
|
984
|
-
return await activate(config, terminal, current);
|
|
985
|
-
}
|
|
986
|
-
if (current.phase !== "credential_saved") {
|
|
987
|
-
current = { ...current, phase: "credential_saved" };
|
|
988
|
-
writeSetupJson(path, current, true);
|
|
989
|
-
}
|
|
990
|
-
const paired = await acknowledge(config, terminal, current);
|
|
991
|
-
return await activate(config, terminal, paired);
|
|
992
|
-
}
|
|
993
|
-
if (current && current.phase !== "waiting") {
|
|
994
|
-
throw new CliError2("configuration_required");
|
|
995
|
-
}
|
|
996
|
-
current ??= await startClaim(config, terminal, path, false);
|
|
997
|
-
let deadline = Date.now() + POLL_BUDGET_MS;
|
|
998
|
-
for (;; ) {
|
|
999
|
-
if (terminal.io.signal.aborted) {
|
|
1000
|
-
throw new CliError2("configuration_required");
|
|
1001
|
-
}
|
|
1002
|
-
if (current.expires_at !== undefined && Date.now() >= current.expires_at) {
|
|
1003
|
-
current = await restartClaim(config, terminal, path, credentialPath);
|
|
1004
|
-
deadline = Date.now() + POLL_BUDGET_MS;
|
|
1005
|
-
continue;
|
|
1006
|
-
}
|
|
1007
|
-
if (Date.now() > deadline) {
|
|
1008
|
-
terminal.show(`Approval wait ended; rerun the same command to resume this claim. / \u7B49\u5F85\u6279\u51C6\u5DF2\u7ED3\u675F\uFF1B\u91CD\u65B0\u8FD0\u884C\u540C\u4E00\u547D\u4EE4\u53EF\u7EE7\u7EED\u539F\u8BA4\u9886\u3002
|
|
1009
|
-
`);
|
|
1010
|
-
throw new CliError2("configuration_required");
|
|
1011
|
-
}
|
|
1012
|
-
let status;
|
|
1013
|
-
try {
|
|
1014
|
-
status = await post(config, "/pairing/claim/status", { claim_id: current.claim_id, secret: current.secret }, terminal.io.signal);
|
|
1015
|
-
} catch {
|
|
1016
|
-
status = undefined;
|
|
1017
|
-
}
|
|
1018
|
-
if (status?.status === "approved") {
|
|
1019
|
-
fields2(status, ["status", "credential", "machine_id", "recovery_generation", "revision"]);
|
|
1020
|
-
id2(status.recovery_generation);
|
|
1021
|
-
uint2(status.revision, true);
|
|
1022
|
-
if (status.machine_id !== current.machine_id || typeof status.credential !== "string" || !/^[A-Za-z0-9_-]{32,256}$/.test(status.credential)) {
|
|
1023
|
-
throw new CliError2("configuration_required");
|
|
1024
|
-
}
|
|
1025
|
-
const credential = {
|
|
1026
|
-
machine_id: current.machine_id,
|
|
1027
|
-
recovery_generation: status.recovery_generation,
|
|
1028
|
-
credential: status.credential
|
|
1029
|
-
};
|
|
1030
|
-
current = {
|
|
1031
|
-
...current,
|
|
1032
|
-
credential_hash: digest2(credential.credential),
|
|
1033
|
-
recovery_generation: credential.recovery_generation
|
|
1034
|
-
};
|
|
1035
|
-
writeSetupJson(path, current, true);
|
|
1036
|
-
const sink = createFileCredentialSink2(credentialPath, credential);
|
|
1037
|
-
try {
|
|
1038
|
-
await sink.deliver(credential, terminal.io.signal, { delivery_id: current.claim_id, revision: status.revision });
|
|
1039
|
-
} finally {
|
|
1040
|
-
sink.close();
|
|
1041
|
-
}
|
|
1042
|
-
current = { ...current, phase: "credential_saved" };
|
|
1043
|
-
writeSetupJson(path, current, true);
|
|
1044
|
-
const paired = await acknowledge(config, terminal, current);
|
|
1045
|
-
return await activate(config, terminal, paired);
|
|
1046
|
-
}
|
|
1047
|
-
if (status?.status === "delivered") {
|
|
1048
|
-
if (!existsSync2(credentialPath) || !matchesSavedCredential(current, credentialPath)) {
|
|
1049
|
-
throw new CliError2("configuration_required");
|
|
1050
|
-
}
|
|
1051
|
-
const paired = await acknowledge(config, terminal, current);
|
|
1052
|
-
return await activate(config, terminal, paired);
|
|
1053
|
-
}
|
|
1054
|
-
if (status?.status === "failed" || status?.status === "cancelled" || status?.status === "expired") {
|
|
1055
|
-
current = await restartClaim(config, terminal, path, credentialPath);
|
|
1056
|
-
deadline = Date.now() + POLL_BUDGET_MS;
|
|
1057
|
-
continue;
|
|
1058
|
-
}
|
|
1059
|
-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
function matchesSavedCredential(current, path) {
|
|
1063
|
-
const credential = loadCredential2(path);
|
|
1064
|
-
return credential.machine_id === current.machine_id && credential.recovery_generation === current.recovery_generation && digest2(credential.credential) === current.credential_hash;
|
|
1065
|
-
}
|
|
1066
|
-
async function restartClaim(config, terminal, path, credentialPath) {
|
|
1067
|
-
if (terminal.io.signal.aborted || existsSync2(credentialPath) || !await terminal.confirm("Start a new claim with a new pairing application? / \u539F\u8BA4\u9886\u5DF2\u7EC8\u6B62\u6216\u8FC7\u671F\uFF0C\u662F\u5426\u4F7F\u7528\u65B0\u7684\u914D\u5BF9\u7533\u8BF7\u91CD\u65B0\u8BA4\u9886\uFF1F")) {
|
|
1068
|
-
throw new CliError2("configuration_required");
|
|
1069
|
-
}
|
|
1070
|
-
return startClaim(config, terminal, path, true);
|
|
1071
|
-
}
|
|
1072
|
-
async function startClaim(config, terminal, path, replace) {
|
|
1073
|
-
const pairing_id = await terminal.ask("Pairing application ID / \u914D\u5BF9\u7533\u8BF7 ID: ");
|
|
1074
|
-
id2(pairing_id);
|
|
1075
|
-
if (!await terminal.confirm("Trust this Gateway address for online device enrollment? / \u4FE1\u4EFB\u6B64 Gateway \u5730\u5740\u5E76\u5728\u7EBF\u8BA4\u9886\u8BBE\u5907\u914D\u5BF9\uFF1F")) {
|
|
1076
|
-
throw new CliError2("configuration_required");
|
|
1077
|
-
}
|
|
1078
|
-
const secret = randomBytes(32).toString("base64url");
|
|
1079
|
-
const created = await post(config, "/pairing/claim", { pairing_id, secret_hash: digest2(secret) }, terminal.io.signal);
|
|
1080
|
-
fields2(created, ["claim_id", "machine_id", "user_code", "expires_at"]);
|
|
1081
|
-
id2(created.claim_id);
|
|
1082
|
-
id2(created.machine_id);
|
|
1083
|
-
if (typeof created.user_code !== "string" || !/^[A-Z0-9]{8}$/.test(created.user_code) || !Number.isSafeInteger(created.expires_at)) {
|
|
1084
|
-
throw new CliError2("configuration_required");
|
|
1085
|
-
}
|
|
1086
|
-
const current = {
|
|
1087
|
-
schema_version: 1,
|
|
1088
|
-
claim_id: created.claim_id,
|
|
1089
|
-
machine_id: created.machine_id,
|
|
1090
|
-
pairing_id,
|
|
1091
|
-
gateway_url: config.gateway_url,
|
|
1092
|
-
secret,
|
|
1093
|
-
expires_at: Number(created.expires_at),
|
|
1094
|
-
phase: "waiting"
|
|
1095
|
-
};
|
|
1096
|
-
writeSetupJson(path, current, replace);
|
|
1097
|
-
terminal.show("Claim code / \u6838\u5BF9\u7801: " + created.user_code + `
|
|
1098
|
-
Approve this exact code for machine ` + current.machine_id + ` in the management page. / \u8BF7\u5728\u7BA1\u7406\u9875\u4E3A\u8BE5\u8BBE\u5907\u6838\u5BF9\u5E76\u6279\u51C6\u6B64\u7801\u3002
|
|
1099
|
-
`);
|
|
1100
|
-
return current;
|
|
1101
|
-
}
|
|
1102
|
-
async function acknowledge(config, terminal, current) {
|
|
1103
|
-
try {
|
|
1104
|
-
const receipt = await post(config, "/pairing/claim/ack", { claim_id: current.claim_id, secret: current.secret }, terminal.io.signal);
|
|
1105
|
-
fields2(receipt, ["status"]);
|
|
1106
|
-
if (receipt.status !== "delivered") {
|
|
1107
|
-
throw new CliError2("configuration_required");
|
|
1108
|
-
}
|
|
1109
|
-
} catch {
|
|
1110
|
-
const status = await post(config, "/pairing/claim/status", { claim_id: current.claim_id, secret: current.secret }, terminal.io.signal);
|
|
1111
|
-
if (status.status !== "delivered") {
|
|
1112
|
-
throw new CliError2("configuration_required");
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
const { secret: _secret, ...rest } = current;
|
|
1116
|
-
const paired = { ...rest, phase: "paired" };
|
|
1117
|
-
writeSetupJson(runtimeReference(config.state_dir, "pairing-claim"), paired, true);
|
|
1118
|
-
terminal.show(`pairing_completed / \u8BBE\u5907\u8EAB\u4EFD\u5DF2\u6301\u4E45\u914D\u5BF9\uFF1B\u63A5\u4E0B\u6765\u53D1\u73B0\u6240\u9009\u539F\u751F\u5B89\u88C5\u3002
|
|
1119
|
-
`);
|
|
1120
|
-
return paired;
|
|
1121
|
-
}
|
|
1122
|
-
async function activate(config, terminal, current) {
|
|
1123
|
-
assertSetupNativePath(config, terminal);
|
|
1124
|
-
const credential = loadCredential2(runtimeReference(config.state_dir, "device"));
|
|
1125
|
-
if (credential.machine_id !== current.machine_id) {
|
|
1126
|
-
throw new CliError2("configuration_required");
|
|
1127
|
-
}
|
|
1128
|
-
const status = await post(config, "/pairing/claim/activation/status", {
|
|
1129
|
-
attempt_id: current.claim_id,
|
|
1130
|
-
authentication: credential,
|
|
1131
|
-
providers: selectedNativeProviders(config),
|
|
1132
|
-
sessions: setupSessionsFor(config)
|
|
1133
|
-
}, terminal.io.signal);
|
|
1134
|
-
fields2(status, ["attempt_id", "machine_id", "recovery_generation", "phase", "authorization"], ["lineage"]);
|
|
1135
|
-
if (status.lineage !== undefined) {
|
|
1136
|
-
id2(status.lineage);
|
|
1137
|
-
}
|
|
1138
|
-
if (status.attempt_id !== current.claim_id || status.machine_id !== credential.machine_id || status.recovery_generation !== credential.recovery_generation || !["paired", "configured"].includes(String(status.phase))) {
|
|
1139
|
-
throw new CliError2("configuration_required");
|
|
1140
|
-
}
|
|
1141
|
-
const authorization = object2(status.authorization);
|
|
1142
|
-
fields2(authorization, ["principal_id", "policies"]);
|
|
1143
|
-
id2(authorization.principal_id);
|
|
1144
|
-
const expected = selectedSetupPolicies({
|
|
1145
|
-
attempt_id: current.claim_id,
|
|
1146
|
-
actor: authorization.principal_id,
|
|
1147
|
-
...status.lineage ? { lineage: status.lineage } : {}
|
|
1148
|
-
}, credential.machine_id, selectedNativeProviders(config), setupSessionsFor(config));
|
|
1149
|
-
if (boundedJson2(authorization) !== boundedJson2(expected)) {
|
|
1150
|
-
throw new CliError2("configuration_required");
|
|
1151
|
-
}
|
|
1152
|
-
const prepared = [];
|
|
1153
|
-
for (const kind of selectedNativeProviders(config)) {
|
|
1154
|
-
prepared.push(await (kind === "luvus" ? prepareSetupNative : prepareSetupHerdr)(config, terminal, credential, current.claim_id, expected));
|
|
1155
|
-
}
|
|
1156
|
-
const installations = prepared.map((native) => ({ reference: native.reference, value: native.remote }));
|
|
1157
|
-
const installation = installations.length === 1 && selectedNativeProviders(config)[0] === "luvus" ? installations[0] : { installations };
|
|
1158
|
-
const fingerprint = hash2(boundedJson2(installation, 1048576));
|
|
1159
|
-
try {
|
|
1160
|
-
const applied = await post(config, "/pairing/claim/activation/native", { attempt_id: current.claim_id, authentication: credential, installation }, terminal.io.signal, 300000);
|
|
1161
|
-
fields2(applied, ["state", "fingerprint"]);
|
|
1162
|
-
if (applied.state !== "configured" || applied.fingerprint !== fingerprint) {
|
|
1163
|
-
throw new CliError2("configuration_required");
|
|
1164
|
-
}
|
|
1165
|
-
} catch {
|
|
1166
|
-
terminal.show(`Native activation response unavailable; rerun the same command to check the original claim. / \u539F\u751F\u6FC0\u6D3B\u54CD\u5E94\u4E0D\u53EF\u7528\uFF1B\u91CD\u65B0\u8FD0\u884C\u540C\u4E00\u547D\u4EE4\u6309\u539F\u8BA4\u9886\u6838\u5BF9\u3002
|
|
1167
|
-
`);
|
|
1168
|
-
throw new CliError2("configuration_required");
|
|
1169
|
-
}
|
|
1170
|
-
const providersPath = runtimeReference(config.state_dir, "providers");
|
|
1171
|
-
const providersDocument = {
|
|
1172
|
-
schema_version: 1,
|
|
1173
|
-
role: "connector",
|
|
1174
|
-
providers: prepared.map((native) => ({ reference: native.reference, value: native.local }))
|
|
1175
|
-
};
|
|
1176
|
-
const existingProviders = readSetupJson(providersPath, 1048576);
|
|
1177
|
-
if (existingProviders === undefined) {
|
|
1178
|
-
writeSetupJson(providersPath, providersDocument, false, 1048576);
|
|
1179
|
-
} else if (boundedJson2(existingProviders, 1048576) !== boundedJson2(providersDocument, 1048576)) {
|
|
1180
|
-
throw new CliError2("configuration_required");
|
|
1181
|
-
}
|
|
1182
|
-
const pairingPath = runtimeReference(config.state_dir, "pairing");
|
|
1183
|
-
const pairingDocument = {
|
|
1184
|
-
schema_version: 1,
|
|
1185
|
-
attempt_id: current.claim_id,
|
|
1186
|
-
machine_id: current.machine_id,
|
|
1187
|
-
gateway_url: config.gateway_url,
|
|
1188
|
-
phase: "configured"
|
|
1189
|
-
};
|
|
1190
|
-
const existingPairing = readSetupJson(pairingPath);
|
|
1191
|
-
if (existingPairing === undefined) {
|
|
1192
|
-
writeSetupJson(pairingPath, pairingDocument);
|
|
1193
|
-
} else if (boundedJson2(existingPairing) !== boundedJson2(pairingDocument)) {
|
|
1194
|
-
throw new CliError2("configuration_required");
|
|
1195
|
-
}
|
|
1196
|
-
writeSetupJson(runtimeReference(config.state_dir, "pairing-claim"), { ...current, phase: "configured" }, true);
|
|
1197
|
-
terminal.show(`Native state prepared; starting the real Connector. / \u539F\u751F\u72B6\u6001\u5DF2\u51C6\u5907\uFF1B\u542F\u52A8\u6B63\u5F0F Connector\u3002
|
|
1198
|
-
`);
|
|
1199
|
-
terminal.finishQuestions();
|
|
1200
|
-
return config;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
// src/cli/setup-runtime.ts
|
|
1204
|
-
class CliSetup {
|
|
1205
|
-
#terminal;
|
|
1206
|
-
#gateway;
|
|
1207
|
-
#observation;
|
|
1208
|
-
async prepare(config, path, io, options) {
|
|
1209
|
-
const terminal = SetupTerminal.open(path, config, io);
|
|
1210
|
-
if (!terminal) {
|
|
1211
|
-
if (options?.repair || options?.addDevice || options?.claim) {
|
|
1212
|
-
throw new CliError2("configuration_required");
|
|
1213
|
-
}
|
|
1214
|
-
return config;
|
|
1215
|
-
}
|
|
1216
|
-
this.#terminal = terminal;
|
|
1217
|
-
if (config.role === "connector") {
|
|
1218
|
-
if (options?.repair) {
|
|
1219
|
-
await repairConnector(config, terminal);
|
|
1220
|
-
terminal.finishQuestions();
|
|
1221
|
-
return config;
|
|
1222
|
-
}
|
|
1223
|
-
if (options?.claim) {
|
|
1224
|
-
const claimed = await claimConnector(config, terminal);
|
|
1225
|
-
terminal.finishQuestions();
|
|
1226
|
-
return claimed;
|
|
1227
|
-
}
|
|
1228
|
-
return await setupConnector(config, terminal);
|
|
1229
|
-
}
|
|
1230
|
-
if (options?.addDevice) {
|
|
1231
|
-
if (readSetupJson(runtimeReference(config.state_dir, "setup-active")) === undefined) {
|
|
1232
|
-
throw new CliError2("configuration_required");
|
|
1233
|
-
}
|
|
1234
|
-
this.#gateway = new GatewaySetup(config, terminal, { addDevice: true });
|
|
1235
|
-
return config;
|
|
1236
|
-
}
|
|
1237
|
-
if (options?.repair) {
|
|
1238
|
-
if (readSetupJson(runtimeReference(config.state_dir, "setup-active")) === undefined) {
|
|
1239
|
-
throw new CliError2("configuration_required");
|
|
1240
|
-
}
|
|
1241
|
-
this.#gateway = new GatewaySetup(config, terminal, { repair: true });
|
|
1242
|
-
return config;
|
|
1243
|
-
}
|
|
1244
|
-
if (readRuntimeConfig2(config).providers.length && readSetupJson(runtimeReference(config.state_dir, "setup-active")) === undefined) {
|
|
1245
|
-
terminal.finishQuestions();
|
|
1246
|
-
return config;
|
|
1247
|
-
}
|
|
1248
|
-
this.#gateway = new GatewaySetup(config, terminal);
|
|
1249
|
-
return config;
|
|
1250
|
-
}
|
|
1251
|
-
gateway(config) {
|
|
1252
|
-
return this.#gateway?.config === config ? this.#gateway : undefined;
|
|
1253
|
-
}
|
|
1254
|
-
nativeFailed(error) {
|
|
1255
|
-
if ((error instanceof LuvusAdapterError2 || error instanceof HerdrLocalError) && /^[a-z_]{1,80}$/.test(error.code)) {
|
|
1256
|
-
this.#terminal?.show("native_unavailable " + error.code + `
|
|
1257
|
-
`);
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
observe(running) {
|
|
1261
|
-
if (!running.transport || !this.#terminal) {
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
let previous = "";
|
|
1265
|
-
this.#observation = setInterval(() => {
|
|
1266
|
-
try {
|
|
1267
|
-
const status = running.transport.status();
|
|
1268
|
-
const ready = status.providers.some((provider) => provider.ready);
|
|
1269
|
-
const current = status.phase + ":" + ready + ":" + (status.failure ?? "");
|
|
1270
|
-
if (current !== previous) {
|
|
1271
|
-
previous = current;
|
|
1272
|
-
this.#terminal.show("connector_transport " + status.phase + " provider_ready=" + String(ready) + (status.capacity ? " capacity=" + status.capacity.resource + " reclaim_at=" + String(status.capacity.reclaim_at) : "") + (status.failure ? " reason=" + status.failure : "") + `
|
|
1273
|
-
`);
|
|
1274
|
-
}
|
|
1275
|
-
} catch {
|
|
1276
|
-
clearInterval(this.#observation);
|
|
1277
|
-
this.#observation = undefined;
|
|
1278
|
-
}
|
|
1279
|
-
}, 250);
|
|
1280
|
-
running.closed.then(() => {
|
|
1281
|
-
clearInterval(this.#observation);
|
|
1282
|
-
this.#observation = undefined;
|
|
1283
|
-
}, () => {
|
|
1284
|
-
clearInterval(this.#observation);
|
|
1285
|
-
this.#observation = undefined;
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
close() {
|
|
1289
|
-
clearInterval(this.#observation);
|
|
1290
|
-
this.#observation = undefined;
|
|
1291
|
-
this.#terminal?.close();
|
|
1292
|
-
this.#terminal = undefined;
|
|
1293
|
-
this.#gateway = undefined;
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
export {
|
|
1297
|
-
CliSetup
|
|
1298
|
-
};
|
|
1299
|
-
|
|
1300
|
-
//# debugId=C698A949CB8251CE64756E2164756E21
|