@memtensor/memos-local-openclaw-plugin 1.0.4-beta.15 → 1.0.4-beta.17
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 +94 -27
- package/dist/hub/server.d.ts +1 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +74 -7
- package/dist/hub/server.js.map +1 -1
- package/dist/ingest/providers/index.js +2 -2
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +2 -1
- package/dist/shared/llm-call.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +214 -67
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +6 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +152 -42
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +7 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/hub/server.ts +70 -8
- package/src/ingest/providers/index.ts +2 -2
- package/src/shared/llm-call.ts +2 -1
- package/src/viewer/html.ts +214 -67
- package/src/viewer/server.ts +145 -41
package/src/viewer/server.ts
CHANGED
|
@@ -34,6 +34,7 @@ export interface ViewerServerOptions {
|
|
|
34
34
|
log: Logger;
|
|
35
35
|
dataDir: string;
|
|
36
36
|
ctx?: PluginContext;
|
|
37
|
+
defaultHubPort?: number;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
interface AuthState {
|
|
@@ -51,6 +52,8 @@ export class ViewerServer {
|
|
|
51
52
|
private readonly authFile: string;
|
|
52
53
|
private readonly auth: AuthState;
|
|
53
54
|
private readonly ctx?: PluginContext;
|
|
55
|
+
private readonly cookieName: string;
|
|
56
|
+
private readonly defaultHubPort: number;
|
|
54
57
|
|
|
55
58
|
private static readonly SESSION_TTL = 24 * 60 * 60 * 1000;
|
|
56
59
|
private static readonly PLUGIN_VERSION: string = (() => {
|
|
@@ -99,17 +102,31 @@ export class ViewerServer {
|
|
|
99
102
|
this.ctx = opts.ctx;
|
|
100
103
|
this.authFile = path.join(opts.dataDir, "viewer-auth.json");
|
|
101
104
|
this.auth = { passwordHash: null, sessions: new Map() };
|
|
105
|
+
this.cookieName = `memos_token_${opts.port}`;
|
|
106
|
+
this.defaultHubPort = opts.defaultHubPort ?? 18800;
|
|
102
107
|
this.resetToken = crypto.randomBytes(16).toString("hex");
|
|
103
108
|
this.loadAuth();
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
private getHubPort(): number {
|
|
112
|
+
const configured = this.ctx?.config?.sharing?.hub?.port;
|
|
113
|
+
if (configured && configured !== 18800) return configured;
|
|
114
|
+
return this.defaultHubPort;
|
|
115
|
+
}
|
|
116
|
+
|
|
106
117
|
start(): Promise<string> {
|
|
118
|
+
const MAX_PORT_RETRIES = 5;
|
|
107
119
|
return new Promise((resolve, reject) => {
|
|
120
|
+
let retries = 0;
|
|
108
121
|
this.server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
109
122
|
this.server.on("error", (err: NodeJS.ErrnoException) => {
|
|
110
|
-
if (err.code === "EADDRINUSE") {
|
|
111
|
-
|
|
112
|
-
this.
|
|
123
|
+
if (err.code === "EADDRINUSE" && retries < MAX_PORT_RETRIES) {
|
|
124
|
+
retries++;
|
|
125
|
+
const nextPort = this.port + retries;
|
|
126
|
+
this.log.warn(`Viewer port ${this.port + retries - 1} in use, trying ${nextPort}`);
|
|
127
|
+
this.server!.listen(nextPort, "0.0.0.0");
|
|
128
|
+
} else if (err.code === "EADDRINUSE") {
|
|
129
|
+
reject(new Error(`Viewer failed to find open port after ${MAX_PORT_RETRIES} retries (tried ${this.port}–${this.port + MAX_PORT_RETRIES})`));
|
|
113
130
|
} else {
|
|
114
131
|
reject(err);
|
|
115
132
|
}
|
|
@@ -187,7 +204,8 @@ export class ViewerServer {
|
|
|
187
204
|
|
|
188
205
|
private isValidSession(req: http.IncomingMessage): boolean {
|
|
189
206
|
const cookie = req.headers.cookie ?? "";
|
|
190
|
-
const
|
|
207
|
+
const re = new RegExp(`${this.cookieName}=([a-f0-9]+)`);
|
|
208
|
+
const match = cookie.match(re);
|
|
191
209
|
if (!match) return false;
|
|
192
210
|
const expiry = this.auth.sessions.get(match[1]);
|
|
193
211
|
if (!expiry) return false;
|
|
@@ -270,6 +288,7 @@ export class ViewerServer {
|
|
|
270
288
|
else if (p === "/api/sharing/remove-user" && req.method === "POST") this.handleSharingRemoveUser(req, res);
|
|
271
289
|
else if (p === "/api/sharing/change-role" && req.method === "POST") this.handleSharingChangeRole(req, res);
|
|
272
290
|
else if (p === "/api/sharing/retry-join" && req.method === "POST") this.handleRetryJoin(req, res);
|
|
291
|
+
else if (p === "/api/sharing/leave" && req.method === "POST") this.handleLeaveTeam(req, res);
|
|
273
292
|
else if (p === "/api/sharing/search/memories" && req.method === "POST") this.handleSharingMemorySearch(req, res);
|
|
274
293
|
else if (p === "/api/sharing/memories/list" && req.method === "GET") this.serveSharingMemoryList(res, url);
|
|
275
294
|
else if (p === "/api/sharing/tasks/list" && req.method === "GET") this.serveSharingTaskList(res, url);
|
|
@@ -350,7 +369,7 @@ export class ViewerServer {
|
|
|
350
369
|
const token = this.createSession();
|
|
351
370
|
res.writeHead(200, {
|
|
352
371
|
"Content-Type": "application/json",
|
|
353
|
-
"Set-Cookie":
|
|
372
|
+
"Set-Cookie": `${this.cookieName}=${token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400`,
|
|
354
373
|
});
|
|
355
374
|
res.end(JSON.stringify({ ok: true, message: "Password set successfully" }));
|
|
356
375
|
} catch (err) {
|
|
@@ -372,7 +391,7 @@ export class ViewerServer {
|
|
|
372
391
|
const token = this.createSession();
|
|
373
392
|
res.writeHead(200, {
|
|
374
393
|
"Content-Type": "application/json",
|
|
375
|
-
"Set-Cookie":
|
|
394
|
+
"Set-Cookie": `${this.cookieName}=${token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400`,
|
|
376
395
|
});
|
|
377
396
|
res.end(JSON.stringify({ ok: true }));
|
|
378
397
|
} catch (err) {
|
|
@@ -384,11 +403,12 @@ export class ViewerServer {
|
|
|
384
403
|
|
|
385
404
|
private handleLogout(req: http.IncomingMessage, res: http.ServerResponse): void {
|
|
386
405
|
const cookie = req.headers.cookie ?? "";
|
|
387
|
-
const
|
|
406
|
+
const re = new RegExp(`${this.cookieName}=([a-f0-9]+)`);
|
|
407
|
+
const match = cookie.match(re);
|
|
388
408
|
if (match) this.auth.sessions.delete(match[1]);
|
|
389
409
|
res.writeHead(200, {
|
|
390
410
|
"Content-Type": "application/json",
|
|
391
|
-
"Set-Cookie":
|
|
411
|
+
"Set-Cookie": `${this.cookieName}=; Path=/; HttpOnly; Max-Age=0`,
|
|
392
412
|
});
|
|
393
413
|
res.end(JSON.stringify({ ok: true }));
|
|
394
414
|
}
|
|
@@ -415,7 +435,7 @@ export class ViewerServer {
|
|
|
415
435
|
const sessionToken = this.createSession();
|
|
416
436
|
res.writeHead(200, {
|
|
417
437
|
"Content-Type": "application/json",
|
|
418
|
-
"Set-Cookie":
|
|
438
|
+
"Set-Cookie": `${this.cookieName}=${sessionToken}; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400`,
|
|
419
439
|
});
|
|
420
440
|
res.end(JSON.stringify({ ok: true, message: "Password reset successfully" }));
|
|
421
441
|
} catch (err) {
|
|
@@ -1522,6 +1542,7 @@ export class ViewerServer {
|
|
|
1522
1542
|
// ─── Config API ───
|
|
1523
1543
|
|
|
1524
1544
|
private getOpenClawConfigPath(): string {
|
|
1545
|
+
if (process.env.OPENCLAW_CONFIG_PATH) return process.env.OPENCLAW_CONFIG_PATH;
|
|
1525
1546
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
1526
1547
|
const ocHome = process.env.OPENCLAW_STATE_DIR || path.join(home, ".openclaw");
|
|
1527
1548
|
return path.join(ocHome, "openclaw.json");
|
|
@@ -1611,7 +1632,20 @@ export class ViewerServer {
|
|
|
1611
1632
|
base.connection.teamName = info?.teamName ?? sharing.hub?.teamName ?? null;
|
|
1612
1633
|
base.connection.apiVersion = info?.apiVersion ?? null;
|
|
1613
1634
|
} catch { /* ignore */ }
|
|
1614
|
-
|
|
1635
|
+
|
|
1636
|
+
const hubStats: any = { totalMembers: 0, onlineMembers: 0, pendingMembers: 0 };
|
|
1637
|
+
try {
|
|
1638
|
+
const activeUsers = this.store.listHubUsers("active");
|
|
1639
|
+
const pendingUsers = this.store.listHubUsers("pending");
|
|
1640
|
+
const now = Date.now();
|
|
1641
|
+
const OFFLINE_THRESHOLD = 120_000;
|
|
1642
|
+
hubStats.totalMembers = activeUsers.length;
|
|
1643
|
+
hubStats.onlineMembers = activeUsers.filter(u =>
|
|
1644
|
+
u.lastActiveAt && (now - u.lastActiveAt < OFFLINE_THRESHOLD),
|
|
1645
|
+
).length;
|
|
1646
|
+
hubStats.pendingMembers = pendingUsers.length;
|
|
1647
|
+
} catch { /* best-effort */ }
|
|
1648
|
+
this.jsonResponse(res, { ...base, hubStats });
|
|
1615
1649
|
return;
|
|
1616
1650
|
}
|
|
1617
1651
|
|
|
@@ -1776,15 +1810,6 @@ export class ViewerServer {
|
|
|
1776
1810
|
}
|
|
1777
1811
|
try {
|
|
1778
1812
|
const hubUrl = normalizeHubUrl(hubAddress);
|
|
1779
|
-
const localIPs = this.getLocalIPs();
|
|
1780
|
-
localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
|
|
1781
|
-
try {
|
|
1782
|
-
const u = new URL(hubUrl);
|
|
1783
|
-
const targetPort = u.port || (u.protocol === "https:" ? "443" : "80");
|
|
1784
|
-
if (localIPs.includes(u.hostname) && targetPort === String(this.port)) {
|
|
1785
|
-
return this.jsonResponse(res, { ok: false, error: "cannot_join_self" });
|
|
1786
|
-
}
|
|
1787
|
-
} catch {}
|
|
1788
1813
|
const os = await import("os");
|
|
1789
1814
|
const nickname = sharing.client?.nickname;
|
|
1790
1815
|
const username = nickname || os.userInfo().username || "user";
|
|
@@ -2213,7 +2238,7 @@ export class ViewerServer {
|
|
|
2213
2238
|
// Hub 模式:连接自己,用 bootstrap admin token
|
|
2214
2239
|
const sharing = this.ctx.config.sharing;
|
|
2215
2240
|
if (sharing?.role === "hub") {
|
|
2216
|
-
const hubPort =
|
|
2241
|
+
const hubPort = this.getHubPort();
|
|
2217
2242
|
const hubUrl = `http://127.0.0.1:${hubPort}`;
|
|
2218
2243
|
try {
|
|
2219
2244
|
const authPath = path.join(this.dataDir, "hub-auth.json");
|
|
@@ -2607,13 +2632,14 @@ export class ViewerServer {
|
|
|
2607
2632
|
if (merged.role === "client" && merged.client) {
|
|
2608
2633
|
const clientCfg = merged.client as Record<string, unknown>;
|
|
2609
2634
|
const addr = String(clientCfg.hubAddress || "");
|
|
2610
|
-
if (addr) {
|
|
2635
|
+
if (addr && oldSharingRole === "hub" && oldSharingEnabled) {
|
|
2636
|
+
const selfHubPort = (oldSharing?.hub as Record<string, unknown>)?.port ?? 18800;
|
|
2611
2637
|
const localIPs = this.getLocalIPs();
|
|
2612
2638
|
localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
|
|
2613
2639
|
try {
|
|
2614
2640
|
const u = new URL(addr.startsWith("http") ? addr : `http://${addr}`);
|
|
2615
2641
|
const targetPort = u.port || (u.protocol === "https:" ? "443" : "80");
|
|
2616
|
-
if (localIPs.includes(u.hostname) && targetPort === String(
|
|
2642
|
+
if (localIPs.includes(u.hostname) && targetPort === String(selfHubPort)) {
|
|
2617
2643
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2618
2644
|
res.end(JSON.stringify({ error: "cannot_join_self" }));
|
|
2619
2645
|
return;
|
|
@@ -2638,12 +2664,9 @@ export class ViewerServer {
|
|
|
2638
2664
|
const wasClient = oldSharingEnabled && oldSharingRole === "client";
|
|
2639
2665
|
const isClient = newEnabled && newRole === "client";
|
|
2640
2666
|
if (wasClient && !isClient) {
|
|
2641
|
-
this.
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
this.store.setClientHubConnection({ ...oldConn, userToken: "", lastKnownStatus: "left" });
|
|
2645
|
-
}
|
|
2646
|
-
this.log.info("Client hub connection token cleared (sharing disabled or role changed), identity preserved");
|
|
2667
|
+
await this.withdrawOrLeaveHub();
|
|
2668
|
+
this.store.clearClientHubConnection();
|
|
2669
|
+
this.log.info("Client hub connection cleared (sharing disabled or role changed)");
|
|
2647
2670
|
}
|
|
2648
2671
|
|
|
2649
2672
|
if (wasClient && isClient) {
|
|
@@ -2661,7 +2684,7 @@ export class ViewerServer {
|
|
|
2661
2684
|
if (merged.role === "hub") {
|
|
2662
2685
|
merged.client = { hubAddress: "", userToken: "", teamToken: "" };
|
|
2663
2686
|
} else if (merged.role === "client") {
|
|
2664
|
-
merged.hub = {
|
|
2687
|
+
merged.hub = { teamName: "", teamToken: "" };
|
|
2665
2688
|
}
|
|
2666
2689
|
config.sharing = merged;
|
|
2667
2690
|
}
|
|
@@ -2684,7 +2707,12 @@ export class ViewerServer {
|
|
|
2684
2707
|
}
|
|
2685
2708
|
}
|
|
2686
2709
|
|
|
2687
|
-
this.jsonResponse(res, { ok: true, joinStatus });
|
|
2710
|
+
this.jsonResponse(res, { ok: true, joinStatus, restart: true });
|
|
2711
|
+
|
|
2712
|
+
setTimeout(() => {
|
|
2713
|
+
this.log.info("config-save: triggering gateway restart via SIGUSR1...");
|
|
2714
|
+
try { process.kill(process.pid, "SIGUSR1"); } catch (sig) { this.log.warn(`SIGUSR1 failed: ${sig}`); }
|
|
2715
|
+
}, 500);
|
|
2688
2716
|
} catch (e) {
|
|
2689
2717
|
this.log.warn(`handleSaveConfig error: ${e}`);
|
|
2690
2718
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -2727,6 +2755,41 @@ export class ViewerServer {
|
|
|
2727
2755
|
return result.status;
|
|
2728
2756
|
}
|
|
2729
2757
|
|
|
2758
|
+
private handleLeaveTeam(_req: http.IncomingMessage, res: http.ServerResponse): void {
|
|
2759
|
+
this.readBody(_req, async () => {
|
|
2760
|
+
try {
|
|
2761
|
+
await this.withdrawOrLeaveHub();
|
|
2762
|
+
this.store.clearClientHubConnection();
|
|
2763
|
+
|
|
2764
|
+
const configPath = this.getOpenClawConfigPath();
|
|
2765
|
+
if (configPath && fs.existsSync(configPath)) {
|
|
2766
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
2767
|
+
const pluginKey = Object.keys(raw.plugins?.entries ?? {}).find(k => k.includes("memos-local"));
|
|
2768
|
+
if (pluginKey) {
|
|
2769
|
+
const cfg = raw.plugins.entries[pluginKey].config ?? {};
|
|
2770
|
+
if (cfg.sharing) {
|
|
2771
|
+
cfg.sharing.enabled = false;
|
|
2772
|
+
cfg.sharing.client = { hubAddress: "", userToken: "", teamToken: "" };
|
|
2773
|
+
}
|
|
2774
|
+
raw.plugins.entries[pluginKey].config = cfg;
|
|
2775
|
+
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
2776
|
+
this.log.info("handleLeaveTeam: config updated, sharing disabled");
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
this.jsonResponse(res, { ok: true, restart: true });
|
|
2781
|
+
|
|
2782
|
+
setTimeout(() => {
|
|
2783
|
+
this.log.info("handleLeaveTeam: triggering gateway restart via SIGUSR1...");
|
|
2784
|
+
try { process.kill(process.pid, "SIGUSR1"); } catch (sig) { this.log.warn(`SIGUSR1 failed: ${sig}`); }
|
|
2785
|
+
}, 500);
|
|
2786
|
+
} catch (e) {
|
|
2787
|
+
this.log.warn(`handleLeaveTeam error: ${e}`);
|
|
2788
|
+
this.jsonResponse(res, { ok: false, error: String(e) });
|
|
2789
|
+
}
|
|
2790
|
+
});
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2730
2793
|
private async notifyHubLeave(): Promise<void> {
|
|
2731
2794
|
try {
|
|
2732
2795
|
const hub = this.resolveHubConnection();
|
|
@@ -2745,11 +2808,49 @@ export class ViewerServer {
|
|
|
2745
2808
|
}
|
|
2746
2809
|
}
|
|
2747
2810
|
|
|
2811
|
+
private async withdrawOrLeaveHub(): Promise<void> {
|
|
2812
|
+
try {
|
|
2813
|
+
const persisted = this.store.getClientHubConnection();
|
|
2814
|
+
const sharing = this.ctx?.config?.sharing;
|
|
2815
|
+
|
|
2816
|
+
if (persisted?.userToken && persisted?.hubUrl) {
|
|
2817
|
+
await hubRequestJson(persisted.hubUrl, persisted.userToken, "/api/v1/hub/leave", { method: "POST" });
|
|
2818
|
+
this.log.info("Notified Hub of voluntary leave (had token)");
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
const hub = this.resolveHubConnection();
|
|
2823
|
+
if (hub?.userToken) {
|
|
2824
|
+
await hubRequestJson(hub.hubUrl, hub.userToken, "/api/v1/hub/leave", { method: "POST" });
|
|
2825
|
+
this.log.info("Notified Hub of voluntary leave (resolved connection)");
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
const hubUrl = persisted?.hubUrl || (sharing?.client?.hubAddress ? normalizeHubUrl(sharing.client.hubAddress) : null);
|
|
2830
|
+
const userId = persisted?.userId;
|
|
2831
|
+
const teamToken = sharing?.client?.teamToken;
|
|
2832
|
+
if (hubUrl && userId && teamToken) {
|
|
2833
|
+
const withdrawUrl = `${normalizeHubUrl(hubUrl)}/api/v1/hub/withdraw-pending`;
|
|
2834
|
+
await fetch(withdrawUrl, {
|
|
2835
|
+
method: "POST",
|
|
2836
|
+
headers: { "content-type": "application/json" },
|
|
2837
|
+
body: JSON.stringify({ teamToken, userId }),
|
|
2838
|
+
});
|
|
2839
|
+
this.log.info("Withdrew pending application from Hub");
|
|
2840
|
+
return;
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
this.log.info("No hub connection to clean up (no token, no pending)");
|
|
2844
|
+
} catch (e) {
|
|
2845
|
+
this.log.warn(`Failed to withdraw/leave Hub: ${e}`);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2748
2849
|
private async notifyHubShutdown(): Promise<void> {
|
|
2749
2850
|
try {
|
|
2750
2851
|
const sharing = this.ctx?.config.sharing;
|
|
2751
2852
|
if (!sharing || sharing.role !== "hub") return;
|
|
2752
|
-
const hubPort =
|
|
2853
|
+
const hubPort = this.getHubPort();
|
|
2753
2854
|
const authPath = path.join(this.dataDir, "hub-auth.json");
|
|
2754
2855
|
let adminToken: string | undefined;
|
|
2755
2856
|
try {
|
|
@@ -2834,13 +2935,17 @@ export class ViewerServer {
|
|
|
2834
2935
|
const { hubUrl } = JSON.parse(body);
|
|
2835
2936
|
if (!hubUrl) { this.jsonResponse(res, { ok: false, error: "hubUrl is required" }); return; }
|
|
2836
2937
|
try {
|
|
2837
|
-
const
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2938
|
+
const sharing = this.ctx?.config?.sharing;
|
|
2939
|
+
if (sharing?.enabled && sharing.role === "hub") {
|
|
2940
|
+
const selfHubPort = this.getHubPort();
|
|
2941
|
+
const localIPs = this.getLocalIPs();
|
|
2942
|
+
localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
|
|
2943
|
+
const parsed = new URL(hubUrl.startsWith("http") ? hubUrl : `http://${hubUrl}`);
|
|
2944
|
+
const targetPort = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
|
|
2945
|
+
if (localIPs.includes(parsed.hostname) && targetPort === String(selfHubPort)) {
|
|
2946
|
+
this.jsonResponse(res, { ok: false, error: "cannot_join_self" });
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2844
2949
|
}
|
|
2845
2950
|
} catch {}
|
|
2846
2951
|
const url = hubUrl.replace(/\/+$/, "") + "/api/v1/hub/info";
|
|
@@ -3078,10 +3183,9 @@ export class ViewerServer {
|
|
|
3078
3183
|
this.log.info(`update-install: success! Updated to ${newVersion}`);
|
|
3079
3184
|
this.jsonResponse(res, { ok: true, version: newVersion });
|
|
3080
3185
|
|
|
3081
|
-
// Trigger Gateway restart after response is sent
|
|
3082
3186
|
setTimeout(() => {
|
|
3083
|
-
this.log.info(`update-install: triggering gateway restart...`);
|
|
3084
|
-
process.kill(process.pid, "SIGUSR1");
|
|
3187
|
+
this.log.info(`update-install: triggering gateway restart via SIGUSR1...`);
|
|
3188
|
+
try { process.kill(process.pid, "SIGUSR1"); } catch (sig) { this.log.warn(`SIGUSR1 failed: ${sig}`); }
|
|
3085
3189
|
}, 500);
|
|
3086
3190
|
});
|
|
3087
3191
|
});
|