@jamiexiongr/panda-hub 0.1.18 → 0.1.20
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 +24 -13
- package/dist/chunk-72Z5Y3ZR.mjs +455 -0
- package/dist/{chunk-N5CXOFMD.mjs → chunk-I2346PMO.mjs} +307 -27
- package/dist/cli.mjs +17 -8
- package/dist/index.mjs +4 -2
- package/dist/{src-EGC2EU26.mjs → src-YGFHF7S3.mjs} +1 -1
- package/dist/web/assets/{diagnostics-page-DOiGXWzy.js → diagnostics-page-BKp7IiZk.js} +1 -1
- package/dist/web/assets/index-DhjIbKkn.js +142 -0
- package/dist/web/assets/index-QBVYFqgo.css +1 -0
- package/dist/web/assets/{session-diff-preview-C3XBmV-3.js → session-diff-preview-BU4XO0fT.js} +1 -1
- package/dist/web/assets/{web-D7zt3JQj.js → web-BqX5x67V.js} +1 -1
- package/dist/web/assets/{web-DYKkl2e-.js → web-C3EZUi0V.js} +1 -1
- package/dist/web/assets/{web-QNQSrxyj.js → web-CIHpZyzh.js} +1 -1
- package/dist/web/assets/{web-C9kVcOd-.js → web-Ck4th-0Z.js} +1 -1
- package/dist/web/assets/{web-AIORsbBk.js → web-HzS7kaoe.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +2 -1
- package/dist/chunk-AZ5AYF2R.mjs +0 -109
- package/dist/web/assets/index-CJigScOb.js +0 -142
- package/dist/web/assets/index-CtgLzUhf.css +0 -1
package/README.md
CHANGED
|
@@ -2,19 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
Published hub runtime for Panda, including the built web UI.
|
|
4
4
|
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
panda-hub
|
|
9
|
-
panda-hub tailscareserv
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
`tailscareserv` and `--tailscale-serve` both enable automatic `tailscale serve` publishing.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
panda-hub
|
|
9
|
+
panda-hub tailscareserv
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`tailscareserv` and `--tailscale-serve` both enable automatic `tailscale serve` publishing.
|
|
13
|
+
|
|
14
|
+
Windows service management:
|
|
15
|
+
|
|
16
|
+
```powershell
|
|
17
|
+
panda-hub service install --name=PandaHub tailscareserv
|
|
18
|
+
panda-hub service status
|
|
19
|
+
panda-hub service restart
|
|
20
|
+
panda-hub service uninstall
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`service install` stores the startup args and current `PANDA_*` environment values in the Windows service definition. If you change them later, run `service install` again to update the service.
|
|
24
|
+
|
|
25
|
+
When enabled, hub startup will:
|
|
26
|
+
|
|
27
|
+
- detect whether `tailscale` is available and online
|
|
28
|
+
- run `tailscale serve --bg`
|
|
18
29
|
- print the generated Tailscale HTTPS URL in the startup log
|
|
19
30
|
|
|
20
31
|
## Environment
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configureTailscaleServe,
|
|
3
|
+
ensurePandaHubApiKey,
|
|
4
|
+
printTerminalQr,
|
|
5
|
+
resolveTailscalePublicationMode,
|
|
6
|
+
resolveTailscaleServePort,
|
|
7
|
+
startPandaSessionService
|
|
8
|
+
} from "./chunk-I2346PMO.mjs";
|
|
9
|
+
|
|
10
|
+
// release/panda-hub/src/index.ts
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path2 from "path";
|
|
13
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14
|
+
|
|
15
|
+
// release/panda-hub/package.json
|
|
16
|
+
var package_default = {
|
|
17
|
+
name: "@jamiexiongr/panda-hub",
|
|
18
|
+
version: "0.1.20",
|
|
19
|
+
type: "module",
|
|
20
|
+
private: false,
|
|
21
|
+
description: "Panda hub runtime",
|
|
22
|
+
dependencies: {
|
|
23
|
+
"@fastify/compress": "^8.3.1",
|
|
24
|
+
"@fastify/cors": "^10.0.2",
|
|
25
|
+
"@fastify/websocket": "^11.0.2",
|
|
26
|
+
fastify: "^5.2.1",
|
|
27
|
+
"node-windows": "^1.0.0-beta.8",
|
|
28
|
+
"web-push": "^3.6.7"
|
|
29
|
+
},
|
|
30
|
+
bin: {
|
|
31
|
+
"panda-hub": "./bin/panda-hub.cjs"
|
|
32
|
+
},
|
|
33
|
+
exports: {
|
|
34
|
+
".": "./dist/index.mjs"
|
|
35
|
+
},
|
|
36
|
+
files: [
|
|
37
|
+
"bin",
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
publishConfig: {
|
|
41
|
+
access: "public",
|
|
42
|
+
registry: "https://registry.npmjs.org/"
|
|
43
|
+
},
|
|
44
|
+
engines: {
|
|
45
|
+
node: ">=20.19.0"
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// release/panda-hub/src/service.ts
|
|
50
|
+
import path from "path";
|
|
51
|
+
import { fileURLToPath } from "url";
|
|
52
|
+
|
|
53
|
+
// release/shared/src/windows-service.ts
|
|
54
|
+
import { spawnSync } from "child_process";
|
|
55
|
+
import { createRequire } from "module";
|
|
56
|
+
var require2 = createRequire(import.meta.url);
|
|
57
|
+
var cachedServiceConstructor = null;
|
|
58
|
+
var WINDOWS_SERVICE_TIMEOUT_MS = 2e4;
|
|
59
|
+
var ensureWindows = () => {
|
|
60
|
+
if (process.platform !== "win32") {
|
|
61
|
+
throw new Error("Windows \u670D\u52A1\u7BA1\u7406\u5F53\u524D\u53EA\u652F\u6301 Windows\u3002");
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var trimToNull = (value) => {
|
|
65
|
+
const normalized = value?.trim() ?? "";
|
|
66
|
+
return normalized || null;
|
|
67
|
+
};
|
|
68
|
+
var escapePowerShellString = (value) => value.replace(/'/g, "''");
|
|
69
|
+
var quoteForSc = (value) => value.trim();
|
|
70
|
+
var describeCommandFailure = (output, fallback) => {
|
|
71
|
+
const normalized = trimToNull(output);
|
|
72
|
+
return normalized ?? fallback;
|
|
73
|
+
};
|
|
74
|
+
var runPowerShell = (script) => spawnSync("powershell.exe", ["-NoProfile", "-Command", script], {
|
|
75
|
+
encoding: "utf8",
|
|
76
|
+
windowsHide: true
|
|
77
|
+
});
|
|
78
|
+
var runSc = (args) => {
|
|
79
|
+
ensureWindows();
|
|
80
|
+
const command = ["sc.exe", ...args.map((value) => /[\s"]/u.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value)].join(" ");
|
|
81
|
+
return spawnSync(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", command], {
|
|
82
|
+
encoding: "utf8",
|
|
83
|
+
windowsHide: true
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
var parseWindowsServiceState = (output) => {
|
|
87
|
+
const normalized = output.trim().toLowerCase();
|
|
88
|
+
if (normalized === "__missing__") {
|
|
89
|
+
return "missing";
|
|
90
|
+
}
|
|
91
|
+
if (normalized === "running") {
|
|
92
|
+
return "running";
|
|
93
|
+
}
|
|
94
|
+
if (normalized === "stopped") {
|
|
95
|
+
return "stopped";
|
|
96
|
+
}
|
|
97
|
+
return "unknown";
|
|
98
|
+
};
|
|
99
|
+
var queryWindowsServiceStatus = (name) => {
|
|
100
|
+
ensureWindows();
|
|
101
|
+
const result = runPowerShell(
|
|
102
|
+
[
|
|
103
|
+
`$service = Get-Service -Name '${escapePowerShellString(name)}' -ErrorAction SilentlyContinue`,
|
|
104
|
+
`if ($null -eq $service) { '__MISSING__' } else { $service.Status.ToString() }`
|
|
105
|
+
].join("; ")
|
|
106
|
+
);
|
|
107
|
+
const rawOutput = `${result.stdout ?? ""}
|
|
108
|
+
${result.stderr ?? ""}`.trim();
|
|
109
|
+
const state = parseWindowsServiceState(rawOutput);
|
|
110
|
+
return {
|
|
111
|
+
name,
|
|
112
|
+
exists: state !== "missing",
|
|
113
|
+
state,
|
|
114
|
+
rawOutput
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
var wait = (ms) => new Promise((resolve) => {
|
|
118
|
+
setTimeout(resolve, ms);
|
|
119
|
+
});
|
|
120
|
+
var waitForWindowsServiceState = async (name, desiredState, timeoutMs = WINDOWS_SERVICE_TIMEOUT_MS) => {
|
|
121
|
+
const startedAt = Date.now();
|
|
122
|
+
let latest = queryWindowsServiceStatus(name);
|
|
123
|
+
while (latest.state !== desiredState && Date.now() - startedAt < timeoutMs) {
|
|
124
|
+
await wait(750);
|
|
125
|
+
latest = queryWindowsServiceStatus(name);
|
|
126
|
+
}
|
|
127
|
+
return latest;
|
|
128
|
+
};
|
|
129
|
+
var startWindowsService = async (name) => {
|
|
130
|
+
ensureWindows();
|
|
131
|
+
const current = queryWindowsServiceStatus(name);
|
|
132
|
+
if (!current.exists) {
|
|
133
|
+
throw new Error(`Windows \u670D\u52A1 ${name} \u4E0D\u5B58\u5728\u3002`);
|
|
134
|
+
}
|
|
135
|
+
if (current.state === "running") {
|
|
136
|
+
return current;
|
|
137
|
+
}
|
|
138
|
+
const result = runPowerShell(`Start-Service -Name '${escapePowerShellString(name)}' -ErrorAction Stop`);
|
|
139
|
+
const output = `${result.stdout ?? ""}
|
|
140
|
+
${result.stderr ?? ""}`.trim();
|
|
141
|
+
const latest = await waitForWindowsServiceState(name, "running");
|
|
142
|
+
if (latest.state !== "running") {
|
|
143
|
+
throw new Error(describeCommandFailure(output || latest.rawOutput, `\u542F\u52A8\u670D\u52A1 ${name} \u5931\u8D25\u3002`));
|
|
144
|
+
}
|
|
145
|
+
return latest;
|
|
146
|
+
};
|
|
147
|
+
var stopWindowsService = async (name) => {
|
|
148
|
+
ensureWindows();
|
|
149
|
+
const current = queryWindowsServiceStatus(name);
|
|
150
|
+
if (!current.exists || current.state === "stopped") {
|
|
151
|
+
return current;
|
|
152
|
+
}
|
|
153
|
+
const result = runPowerShell(`Stop-Service -Name '${escapePowerShellString(name)}' -Force -ErrorAction Stop`);
|
|
154
|
+
const output = `${result.stdout ?? ""}
|
|
155
|
+
${result.stderr ?? ""}`.trim();
|
|
156
|
+
const latest = await waitForWindowsServiceState(name, "stopped");
|
|
157
|
+
if (latest.state !== "stopped") {
|
|
158
|
+
throw new Error(describeCommandFailure(output || latest.rawOutput, `\u505C\u6B62\u670D\u52A1 ${name} \u5931\u8D25\u3002`));
|
|
159
|
+
}
|
|
160
|
+
return latest;
|
|
161
|
+
};
|
|
162
|
+
var restartWindowsService = async (name) => {
|
|
163
|
+
await stopWindowsService(name);
|
|
164
|
+
return await startWindowsService(name);
|
|
165
|
+
};
|
|
166
|
+
var collectServiceEnvironment = (env) => Object.entries(env ?? process.env).filter(([key, value]) => key.startsWith("PANDA_") && typeof value === "string" && value.trim()).map(([name, value]) => ({
|
|
167
|
+
name,
|
|
168
|
+
value: value.trim()
|
|
169
|
+
}));
|
|
170
|
+
var getNodeWindowsServiceConstructor = () => {
|
|
171
|
+
if (cachedServiceConstructor) {
|
|
172
|
+
return cachedServiceConstructor;
|
|
173
|
+
}
|
|
174
|
+
const mod = require2("node-windows");
|
|
175
|
+
if (!mod.Service) {
|
|
176
|
+
throw new Error("\u7F3A\u5C11 node-windows \u4F9D\u8D56\uFF0C\u65E0\u6CD5\u6CE8\u518C Windows \u670D\u52A1\u3002");
|
|
177
|
+
}
|
|
178
|
+
cachedServiceConstructor = mod.Service;
|
|
179
|
+
return cachedServiceConstructor;
|
|
180
|
+
};
|
|
181
|
+
var createNodeWindowsService = (definition) => {
|
|
182
|
+
ensureWindows();
|
|
183
|
+
const Service = getNodeWindowsServiceConstructor();
|
|
184
|
+
return new Service({
|
|
185
|
+
name: definition.name,
|
|
186
|
+
description: definition.description,
|
|
187
|
+
script: definition.scriptPath,
|
|
188
|
+
scriptOptions: trimToNull(definition.scriptOptions) ?? void 0,
|
|
189
|
+
workingDirectory: trimToNull(definition.workingDirectory) ?? void 0,
|
|
190
|
+
execPath: trimToNull(definition.execPath) ?? process.execPath,
|
|
191
|
+
env: collectServiceEnvironment(definition.env)
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
var waitForNodeWindowsAction = async (service, action) => await new Promise((resolve, reject) => {
|
|
195
|
+
let settled = false;
|
|
196
|
+
const finish = (error) => {
|
|
197
|
+
if (settled) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
settled = true;
|
|
201
|
+
if (error) {
|
|
202
|
+
reject(error);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
resolve();
|
|
206
|
+
};
|
|
207
|
+
const fail = (error) => {
|
|
208
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
209
|
+
};
|
|
210
|
+
service.once(action, () => finish());
|
|
211
|
+
service.once(action === "install" ? "alreadyinstalled" : "alreadyuninstalled", () => finish());
|
|
212
|
+
service.once(
|
|
213
|
+
"invalidinstallation",
|
|
214
|
+
() => fail(new Error(`Windows \u670D\u52A1 ${service.exists ? "\u5B89\u88C5" : "\u5378\u8F7D"}\u72B6\u6001\u65E0\u6548\u3002`))
|
|
215
|
+
);
|
|
216
|
+
service.once("error", fail);
|
|
217
|
+
try {
|
|
218
|
+
service[action]();
|
|
219
|
+
} catch (error) {
|
|
220
|
+
fail(error);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
var uninstallWindowsService = async (definition) => {
|
|
224
|
+
ensureWindows();
|
|
225
|
+
const current = queryWindowsServiceStatus(definition.name);
|
|
226
|
+
if (!current.exists) {
|
|
227
|
+
return current;
|
|
228
|
+
}
|
|
229
|
+
if (current.state === "running") {
|
|
230
|
+
await stopWindowsService(definition.name);
|
|
231
|
+
}
|
|
232
|
+
const service = createNodeWindowsService(definition);
|
|
233
|
+
await waitForNodeWindowsAction(service, "uninstall");
|
|
234
|
+
return queryWindowsServiceStatus(definition.name);
|
|
235
|
+
};
|
|
236
|
+
var installOrUpdateWindowsService = async (definition, options) => {
|
|
237
|
+
ensureWindows();
|
|
238
|
+
const existing = queryWindowsServiceStatus(definition.name);
|
|
239
|
+
if (existing.exists) {
|
|
240
|
+
await uninstallWindowsService(definition);
|
|
241
|
+
}
|
|
242
|
+
const service = createNodeWindowsService(definition);
|
|
243
|
+
await waitForNodeWindowsAction(service, "install");
|
|
244
|
+
runSc(["config", quoteForSc(definition.name), "start=", "auto"]);
|
|
245
|
+
let started = false;
|
|
246
|
+
let startError = null;
|
|
247
|
+
if (options?.start !== false) {
|
|
248
|
+
try {
|
|
249
|
+
const startedStatus = await startWindowsService(definition.name);
|
|
250
|
+
started = startedStatus.state === "running";
|
|
251
|
+
} catch (error) {
|
|
252
|
+
startError = error instanceof Error ? error.message : String(error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
status: queryWindowsServiceStatus(definition.name),
|
|
257
|
+
started,
|
|
258
|
+
startError
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// release/panda-hub/src/service.ts
|
|
263
|
+
var currentDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
264
|
+
var packageRoot = path.resolve(currentDirectory, "..");
|
|
265
|
+
var defaultServiceName = "PandaHub";
|
|
266
|
+
var trimToNull2 = (value) => {
|
|
267
|
+
const normalized = value?.trim() ?? "";
|
|
268
|
+
return normalized || null;
|
|
269
|
+
};
|
|
270
|
+
var normalizeServiceName = (value) => trimToNull2(value) ?? defaultServiceName;
|
|
271
|
+
var joinScriptOptions = (argv) => argv.map((value) => value.trim()).filter(Boolean).join(" ");
|
|
272
|
+
var buildHubServiceDefinition = (options) => ({
|
|
273
|
+
name: normalizeServiceName(options?.name),
|
|
274
|
+
description: "Panda Hub Windows service",
|
|
275
|
+
scriptPath: path.join(packageRoot, "bin", "panda-hub.cjs"),
|
|
276
|
+
scriptOptions: trimToNull2(options?.scriptOptions),
|
|
277
|
+
workingDirectory: packageRoot,
|
|
278
|
+
env: options?.env ?? process.env
|
|
279
|
+
});
|
|
280
|
+
var parseHubServiceCommand = (argv) => {
|
|
281
|
+
let action = null;
|
|
282
|
+
let name = null;
|
|
283
|
+
let shouldStart = true;
|
|
284
|
+
const runtimeArgs = [];
|
|
285
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
286
|
+
const candidate = argv[index]?.trim() ?? "";
|
|
287
|
+
if (!candidate) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (!action) {
|
|
291
|
+
action = candidate.toLowerCase();
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const normalized = candidate.toLowerCase();
|
|
295
|
+
if (normalized === "--name" || normalized === "name" || normalized === "--service-name" || normalized === "service-name") {
|
|
296
|
+
name = trimToNull2(argv[index + 1]);
|
|
297
|
+
index += 1;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (normalized.startsWith("--name=") || normalized.startsWith("name=") || normalized.startsWith("--service-name=") || normalized.startsWith("service-name=")) {
|
|
301
|
+
name = trimToNull2(candidate.slice(candidate.indexOf("=") + 1));
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (normalized === "--no-start") {
|
|
305
|
+
shouldStart = false;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
runtimeArgs.push(candidate);
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
action: action ?? "status",
|
|
312
|
+
name,
|
|
313
|
+
shouldStart,
|
|
314
|
+
runtimeArgs
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
var manageJamiexiongrHubService = async (options) => {
|
|
318
|
+
const logger = options?.logger ?? console;
|
|
319
|
+
const parsed = parseHubServiceCommand(options?.argv ?? []);
|
|
320
|
+
const action = parsed.action;
|
|
321
|
+
const definition = buildHubServiceDefinition({
|
|
322
|
+
name: parsed.name,
|
|
323
|
+
scriptOptions: joinScriptOptions(parsed.runtimeArgs),
|
|
324
|
+
env: options?.env
|
|
325
|
+
});
|
|
326
|
+
if (action === "install" || action === "update" || action === "upsert" || action === "sync") {
|
|
327
|
+
const result = await installOrUpdateWindowsService(definition, {
|
|
328
|
+
start: parsed.shouldStart
|
|
329
|
+
});
|
|
330
|
+
if (result.startError) {
|
|
331
|
+
logger.warn(
|
|
332
|
+
`Windows \u670D\u52A1 ${definition.name} \u5DF2\u6CE8\u518C\uFF0C\u4F46\u542F\u52A8\u5931\u8D25\uFF1A${result.startError}`
|
|
333
|
+
);
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
logger.info(
|
|
337
|
+
result.started ? `Windows \u670D\u52A1 ${definition.name} \u5DF2\u6CE8\u518C\u5E76\u542F\u52A8\u3002` : `Windows \u670D\u52A1 ${definition.name} \u5DF2\u6CE8\u518C\u3002`
|
|
338
|
+
);
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
if (action === "uninstall" || action === "remove" || action === "delete") {
|
|
342
|
+
const status = await uninstallWindowsService(definition);
|
|
343
|
+
logger.info(
|
|
344
|
+
status.exists ? `Windows \u670D\u52A1 ${definition.name} \u5378\u8F7D\u540E\u4ECD\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5\u7CFB\u7EDF\u670D\u52A1\u7BA1\u7406\u5668\u3002` : `Windows \u670D\u52A1 ${definition.name} \u5DF2\u5378\u8F7D\u3002`
|
|
345
|
+
);
|
|
346
|
+
return {
|
|
347
|
+
status,
|
|
348
|
+
started: false,
|
|
349
|
+
startError: null
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
if (action === "start") {
|
|
353
|
+
const status = await startWindowsService(definition.name);
|
|
354
|
+
logger.info(`Windows \u670D\u52A1 ${definition.name} \u5F53\u524D\u72B6\u6001\uFF1A${status.state}`);
|
|
355
|
+
return {
|
|
356
|
+
status,
|
|
357
|
+
started: status.state === "running",
|
|
358
|
+
startError: null
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (action === "stop") {
|
|
362
|
+
const status = await stopWindowsService(definition.name);
|
|
363
|
+
logger.info(`Windows \u670D\u52A1 ${definition.name} \u5F53\u524D\u72B6\u6001\uFF1A${status.state}`);
|
|
364
|
+
return {
|
|
365
|
+
status,
|
|
366
|
+
started: false,
|
|
367
|
+
startError: null
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (action === "restart") {
|
|
371
|
+
const status = await restartWindowsService(definition.name);
|
|
372
|
+
logger.info(`Windows \u670D\u52A1 ${definition.name} \u5F53\u524D\u72B6\u6001\uFF1A${status.state}`);
|
|
373
|
+
return {
|
|
374
|
+
status,
|
|
375
|
+
started: status.state === "running",
|
|
376
|
+
startError: null
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
if (action === "status") {
|
|
380
|
+
const status = queryWindowsServiceStatus(definition.name);
|
|
381
|
+
logger.info(
|
|
382
|
+
status.exists ? `Windows \u670D\u52A1 ${definition.name} \u5F53\u524D\u72B6\u6001\uFF1A${status.state}` : `Windows \u670D\u52A1 ${definition.name} \u5C1A\u672A\u6CE8\u518C\u3002`
|
|
383
|
+
);
|
|
384
|
+
return {
|
|
385
|
+
status,
|
|
386
|
+
started: status.state === "running",
|
|
387
|
+
startError: null
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
throw new Error(`Unknown hub service action: ${action}`);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// release/panda-hub/src/index.ts
|
|
394
|
+
var currentDirectory2 = path2.dirname(fileURLToPath2(import.meta.url));
|
|
395
|
+
var resolveBundledWebUiDir = () => {
|
|
396
|
+
const candidates = [
|
|
397
|
+
path2.resolve(currentDirectory2, "web"),
|
|
398
|
+
path2.resolve(currentDirectory2, "../dist/web"),
|
|
399
|
+
path2.resolve(currentDirectory2, "../web")
|
|
400
|
+
];
|
|
401
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0];
|
|
402
|
+
};
|
|
403
|
+
var startJamiexiongrHub = async (options) => {
|
|
404
|
+
const publishMode = options?.tailscalePublicationMode ?? resolveTailscalePublicationMode({
|
|
405
|
+
envPrefix: "PANDA_HUB"
|
|
406
|
+
});
|
|
407
|
+
const port = Number(process.env.PANDA_HUB_PORT ?? 4343);
|
|
408
|
+
const tailscaleServe = configureTailscaleServe({
|
|
409
|
+
enabled: publishMode !== "disabled",
|
|
410
|
+
mode: publishMode === "disabled" ? void 0 : publishMode,
|
|
411
|
+
serviceName: "panda-hub",
|
|
412
|
+
localPort: port,
|
|
413
|
+
servePort: resolveTailscaleServePort({
|
|
414
|
+
envPrefix: "PANDA_HUB",
|
|
415
|
+
defaultPort: 443
|
|
416
|
+
}),
|
|
417
|
+
logger: console
|
|
418
|
+
});
|
|
419
|
+
const hubApiKey = await ensurePandaHubApiKey({
|
|
420
|
+
configuredApiKey: process.env.PANDA_HUB_API_KEY ?? null,
|
|
421
|
+
codexHome: process.env.PANDA_CODEX_HOME ?? null,
|
|
422
|
+
logger: console
|
|
423
|
+
});
|
|
424
|
+
if (hubApiKey.apiKey) {
|
|
425
|
+
process.env.PANDA_HUB_API_KEY = hubApiKey.apiKey;
|
|
426
|
+
}
|
|
427
|
+
const webUiDir = resolveBundledWebUiDir();
|
|
428
|
+
const app = await startPandaSessionService({
|
|
429
|
+
serviceName: "panda-hub",
|
|
430
|
+
mode: "hub",
|
|
431
|
+
port,
|
|
432
|
+
transport: "hub-routed",
|
|
433
|
+
version: package_default.version,
|
|
434
|
+
webUiDir
|
|
435
|
+
});
|
|
436
|
+
if (tailscaleServe.active && tailscaleServe.baseUrl) {
|
|
437
|
+
if (tailscaleServe.mode === "funnel") {
|
|
438
|
+
console.info(`Panda hub Public HTTPS URL: ${tailscaleServe.baseUrl}`);
|
|
439
|
+
console.info(`Public PWA install URL: ${tailscaleServe.baseUrl}`);
|
|
440
|
+
} else {
|
|
441
|
+
console.info(`Panda hub Tailscale HTTPS URL: ${tailscaleServe.baseUrl}`);
|
|
442
|
+
console.info(`Agent hub URL env: PANDA_HUB_URL=${tailscaleServe.baseUrl}`);
|
|
443
|
+
}
|
|
444
|
+
printTerminalQr(tailscaleServe.baseUrl, {
|
|
445
|
+
logger: console,
|
|
446
|
+
label: tailscaleServe.mode === "funnel" ? "Scan this QR code to open the public Panda hub on your phone:" : "Scan this QR code to open Panda hub on your phone:"
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return app;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
export {
|
|
453
|
+
manageJamiexiongrHubService,
|
|
454
|
+
startJamiexiongrHub
|
|
455
|
+
};
|