@naviyra/cloud-panel 0.0.3-beta2 → 0.0.3-beta3

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.
@@ -1,164 +1,164 @@
1
- import { spawn } from "node:child_process";
2
- import { createWriteStream } from "node:fs";
3
- import { mkdir, stat, rename, writeFile, unlink } from "node:fs/promises";
4
- import { fileURLToPath } from "node:url";
5
- import net from "node:net";
6
- import path from "node:path";
7
- import { setTimeout as delay } from "node:timers/promises";
8
-
9
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
-
11
- function portOpen(port) {
12
- return new Promise((resolve) => {
13
- const socket = net.createConnection({ host: "127.0.0.1", port });
14
- const finish = (open) => {
15
- socket.destroy();
16
- resolve(open);
17
- };
18
- socket.once("connect", () => finish(true));
19
- socket.once("error", () => finish(false));
20
- socket.setTimeout(1500, () => finish(false));
21
- });
22
- }
23
-
24
- async function stackUp() {
25
- try {
26
- process.loadEnvFile(path.join(root, ".env"));
27
- } catch {
28
- // The launcher creates .env when it is missing.
29
- }
30
- const panelPort = Number(process.env.PANEL_PORT || 3000);
31
- const agentPort = Number(process.env.AGENT_PORT || 4001);
32
- if (
33
- !Number.isInteger(panelPort) ||
34
- !Number.isInteger(agentPort) ||
35
- panelPort < 1 ||
36
- agentPort < 1
37
- )
38
- return false;
39
- return (await portOpen(panelPort)) && (await portOpen(agentPort));
40
- }
41
-
42
- function killTree(pid) {
43
- if (!pid) return;
44
- if (process.platform === "win32") {
45
- const killer = spawn("taskkill.exe", ["/PID", String(pid), "/T", "/F"], {
46
- windowsHide: true,
47
- stdio: "ignore",
48
- });
49
- killer.once("error", () => {});
50
- killer.unref();
51
- return;
52
- }
53
- try {
54
- process.kill(pid, "SIGTERM");
55
- } catch {
56
- // The child has already exited.
57
- }
58
- }
59
-
60
- export async function supervise(options = {}) {
61
- const log = options.log ?? (() => {});
62
- const spawnChild = options.spawnChild;
63
- const healthy = options.stackUp ?? stackUp;
64
- const sleep =
65
- options.sleep ??
66
- (async (ms, shouldStop) => {
67
- const step = 200;
68
- for (let waited = 0; waited < ms && !shouldStop(); waited += step)
69
- await delay(Math.min(step, ms - waited));
70
- });
71
- let stopping = false;
72
- const shouldStop = options.shouldStop ?? (() => stopping);
73
- let current = null;
74
- if (options.hookSignals !== false && !options.shouldStop) {
75
- for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"])
76
- process.on(signal, () => {
77
- stopping = true;
78
- current?.kill();
79
- });
80
- }
81
- let backoff = 3000;
82
- log("Naviyra service supervisor started.");
83
- while (!shouldStop()) {
84
- const started = Date.now();
85
- current = spawnChild();
86
- const code = await current.exited;
87
- current = null;
88
- if (shouldStop()) break;
89
- const ran = Date.now() - started;
90
- if (code === 0 && (await healthy())) {
91
- log("Launcher exited cleanly. Watching until the panel or agent stops.");
92
- while (!shouldStop() && (await healthy())) await sleep(5000, shouldStop);
93
- if (shouldStop()) break;
94
- backoff = 3000;
95
- log("Panel or agent is not listening. Starting again in 3s.");
96
- await sleep(3000, shouldStop);
97
- continue;
98
- }
99
- if (ran > 60000) backoff = 3000;
100
- const wait = code === 0 ? Math.max(backoff, 3000) : backoff;
101
- log(
102
- `Launcher exited with code ${code}. Restarting in ${Math.round(wait / 1000)}s.`,
103
- );
104
- await sleep(wait, shouldStop);
105
- backoff = Math.min(Math.max(wait, 3000) * 2, 60000);
106
- }
107
- log("Naviyra service supervisor stopped.");
108
- return 0;
109
- }
110
-
111
- function spawnLauncher(logStream) {
112
- const child = spawn(
113
- process.execPath,
114
- ["scripts/start-naviyra.mjs", "--no-browser"],
115
- {
116
- cwd: root,
117
- windowsHide: true,
118
- stdio: ["ignore", "pipe", "pipe"],
119
- },
120
- );
121
- child.stdout?.on("data", (chunk) => logStream.write(chunk));
122
- child.stderr?.on("data", (chunk) => logStream.write(chunk));
123
- return {
124
- exited: new Promise((resolve) => {
125
- child.once("error", () => resolve(1));
126
- child.once("exit", (code) => resolve(code ?? 1));
127
- }),
128
- kill() {
129
- killTree(child.pid);
130
- },
131
- };
132
- }
133
-
134
- async function main() {
135
- const runtime = path.join(root, ".runtime");
136
- await mkdir(runtime, { recursive: true });
137
- const logPath = path.join(runtime, "service.log");
138
- try {
139
- if ((await stat(logPath)).size > 2_000_000)
140
- await rename(logPath, `${logPath}.1`);
141
- } catch {
142
- // A missing log is created by the write stream.
143
- }
144
- const logStream = createWriteStream(logPath, { flags: "a" });
145
- const log = (line) => {
146
- logStream.write(`[${new Date().toISOString()}] ${line}\n`);
147
- };
148
- const pidFile = path.join(runtime, "service.pid");
149
- await writeFile(pidFile, String(process.pid));
150
- try {
151
- await supervise({ log, spawnChild: () => spawnLauncher(logStream), stackUp });
152
- } finally {
153
- await unlink(pidFile).catch(() => {});
154
- logStream.end();
155
- }
156
- }
157
-
158
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
159
- process.chdir(root);
160
- main().catch((error) => {
161
- console.error(error.message);
162
- process.exitCode = 1;
163
- });
164
- }
1
+ import { spawn } from "node:child_process";
2
+ import { createWriteStream } from "node:fs";
3
+ import { mkdir, stat, rename, writeFile, unlink } from "node:fs/promises";
4
+ import { fileURLToPath } from "node:url";
5
+ import net from "node:net";
6
+ import path from "node:path";
7
+ import { setTimeout as delay } from "node:timers/promises";
8
+
9
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
+
11
+ function portOpen(port) {
12
+ return new Promise((resolve) => {
13
+ const socket = net.createConnection({ host: "127.0.0.1", port });
14
+ const finish = (open) => {
15
+ socket.destroy();
16
+ resolve(open);
17
+ };
18
+ socket.once("connect", () => finish(true));
19
+ socket.once("error", () => finish(false));
20
+ socket.setTimeout(1500, () => finish(false));
21
+ });
22
+ }
23
+
24
+ async function stackUp() {
25
+ try {
26
+ process.loadEnvFile(path.join(root, ".env"));
27
+ } catch {
28
+ // The launcher creates .env when it is missing.
29
+ }
30
+ const panelPort = Number(process.env.PANEL_PORT || 3000);
31
+ const agentPort = Number(process.env.AGENT_PORT || 4001);
32
+ if (
33
+ !Number.isInteger(panelPort) ||
34
+ !Number.isInteger(agentPort) ||
35
+ panelPort < 1 ||
36
+ agentPort < 1
37
+ )
38
+ return false;
39
+ return (await portOpen(panelPort)) && (await portOpen(agentPort));
40
+ }
41
+
42
+ function killTree(pid) {
43
+ if (!pid) return;
44
+ if (process.platform === "win32") {
45
+ const killer = spawn("taskkill.exe", ["/PID", String(pid), "/T", "/F"], {
46
+ windowsHide: true,
47
+ stdio: "ignore",
48
+ });
49
+ killer.once("error", () => {});
50
+ killer.unref();
51
+ return;
52
+ }
53
+ try {
54
+ process.kill(pid, "SIGTERM");
55
+ } catch {
56
+ // The child has already exited.
57
+ }
58
+ }
59
+
60
+ export async function supervise(options = {}) {
61
+ const log = options.log ?? (() => {});
62
+ const spawnChild = options.spawnChild;
63
+ const healthy = options.stackUp ?? stackUp;
64
+ const sleep =
65
+ options.sleep ??
66
+ (async (ms, shouldStop) => {
67
+ const step = 200;
68
+ for (let waited = 0; waited < ms && !shouldStop(); waited += step)
69
+ await delay(Math.min(step, ms - waited));
70
+ });
71
+ let stopping = false;
72
+ const shouldStop = options.shouldStop ?? (() => stopping);
73
+ let current = null;
74
+ if (options.hookSignals !== false && !options.shouldStop) {
75
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"])
76
+ process.on(signal, () => {
77
+ stopping = true;
78
+ current?.kill();
79
+ });
80
+ }
81
+ let backoff = 3000;
82
+ log("Naviyra service supervisor started.");
83
+ while (!shouldStop()) {
84
+ const started = Date.now();
85
+ current = spawnChild();
86
+ const code = await current.exited;
87
+ current = null;
88
+ if (shouldStop()) break;
89
+ const ran = Date.now() - started;
90
+ if (code === 0 && (await healthy())) {
91
+ log("Launcher exited cleanly. Watching until the panel or agent stops.");
92
+ while (!shouldStop() && (await healthy())) await sleep(5000, shouldStop);
93
+ if (shouldStop()) break;
94
+ backoff = 3000;
95
+ log("Panel or agent is not listening. Starting again in 3s.");
96
+ await sleep(3000, shouldStop);
97
+ continue;
98
+ }
99
+ if (ran > 60000) backoff = 3000;
100
+ const wait = code === 0 ? Math.max(backoff, 3000) : backoff;
101
+ log(
102
+ `Launcher exited with code ${code}. Restarting in ${Math.round(wait / 1000)}s.`,
103
+ );
104
+ await sleep(wait, shouldStop);
105
+ backoff = Math.min(Math.max(wait, 3000) * 2, 60000);
106
+ }
107
+ log("Naviyra service supervisor stopped.");
108
+ return 0;
109
+ }
110
+
111
+ function spawnLauncher(logStream) {
112
+ const child = spawn(
113
+ process.execPath,
114
+ ["scripts/start-naviyra.mjs", "--no-browser"],
115
+ {
116
+ cwd: root,
117
+ windowsHide: true,
118
+ stdio: ["ignore", "pipe", "pipe"],
119
+ },
120
+ );
121
+ child.stdout?.on("data", (chunk) => logStream.write(chunk));
122
+ child.stderr?.on("data", (chunk) => logStream.write(chunk));
123
+ return {
124
+ exited: new Promise((resolve) => {
125
+ child.once("error", () => resolve(1));
126
+ child.once("exit", (code) => resolve(code ?? 1));
127
+ }),
128
+ kill() {
129
+ killTree(child.pid);
130
+ },
131
+ };
132
+ }
133
+
134
+ async function main() {
135
+ const runtime = path.join(root, ".runtime");
136
+ await mkdir(runtime, { recursive: true });
137
+ const logPath = path.join(runtime, "service.log");
138
+ try {
139
+ if ((await stat(logPath)).size > 2_000_000)
140
+ await rename(logPath, `${logPath}.1`);
141
+ } catch {
142
+ // A missing log is created by the write stream.
143
+ }
144
+ const logStream = createWriteStream(logPath, { flags: "a" });
145
+ const log = (line) => {
146
+ logStream.write(`[${new Date().toISOString()}] ${line}\n`);
147
+ };
148
+ const pidFile = path.join(runtime, "service.pid");
149
+ await writeFile(pidFile, String(process.pid));
150
+ try {
151
+ await supervise({ log, spawnChild: () => spawnLauncher(logStream), stackUp });
152
+ } finally {
153
+ await unlink(pidFile).catch(() => {});
154
+ logStream.end();
155
+ }
156
+ }
157
+
158
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
159
+ process.chdir(root);
160
+ main().catch((error) => {
161
+ console.error(error.message);
162
+ process.exitCode = 1;
163
+ });
164
+ }