botsync 0.2.0 → 0.2.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/dist/commands/stop.js +2 -0
- package/dist/heartbeat-daemon.d.ts +11 -0
- package/dist/heartbeat-daemon.js +99 -0
- package/dist/heartbeat.d.ts +8 -9
- package/dist/heartbeat.js +47 -54
- package/package.json +1 -1
package/dist/commands/stop.js
CHANGED
|
@@ -40,8 +40,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
exports.stop = stop;
|
|
42
42
|
const syncthing_js_1 = require("../syncthing.js");
|
|
43
|
+
const heartbeat_js_1 = require("../heartbeat.js");
|
|
43
44
|
const ui = __importStar(require("../ui.js"));
|
|
44
45
|
async function stop() {
|
|
46
|
+
(0, heartbeat_js_1.stopHeartbeat)();
|
|
45
47
|
const killed = (0, syncthing_js_1.stopDaemon)();
|
|
46
48
|
if (killed) {
|
|
47
49
|
ui.stopped();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* heartbeat-daemon.ts — Standalone background heartbeat process.
|
|
4
|
+
*
|
|
5
|
+
* Spawned as a detached child by init/join. Sends heartbeats every 60s
|
|
6
|
+
* until the Syncthing daemon dies (checked via API ping), then exits.
|
|
7
|
+
*
|
|
8
|
+
* Usage: node heartbeat-daemon.js
|
|
9
|
+
* (Not meant to be run directly — spawned by init/join)
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* heartbeat-daemon.ts — Standalone background heartbeat process.
|
|
5
|
+
*
|
|
6
|
+
* Spawned as a detached child by init/join. Sends heartbeats every 60s
|
|
7
|
+
* until the Syncthing daemon dies (checked via API ping), then exits.
|
|
8
|
+
*
|
|
9
|
+
* Usage: node heartbeat-daemon.js
|
|
10
|
+
* (Not meant to be run directly — spawned by init/join)
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const os_1 = require("os");
|
|
14
|
+
const config_js_1 = require("./config.js");
|
|
15
|
+
const fs_1 = require("fs");
|
|
16
|
+
const path_1 = require("path");
|
|
17
|
+
const RELAY_URL = "https://relay.botsync.io";
|
|
18
|
+
const HEARTBEAT_INTERVAL_MS = 60_000;
|
|
19
|
+
const HEALTH_CHECK_INTERVAL_MS = 120_000; // Check if Syncthing is alive every 2 min
|
|
20
|
+
const PID_FILE = (0, path_1.join)(config_js_1.BOTSYNC_DIR, "heartbeat.pid");
|
|
21
|
+
function getVersion() {
|
|
22
|
+
try {
|
|
23
|
+
return require("../package.json").version || "0.0.0";
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return "0.0.0";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function sendHeartbeat() {
|
|
30
|
+
const config = (0, config_js_1.readConfig)();
|
|
31
|
+
const networkId = (0, config_js_1.readNetworkId)();
|
|
32
|
+
if (!config?.deviceId || !networkId)
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(`${RELAY_URL}/network/${encodeURIComponent(networkId)}/heartbeat`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
deviceId: config.deviceId,
|
|
40
|
+
name: (0, os_1.hostname)(),
|
|
41
|
+
os: process.platform,
|
|
42
|
+
version: getVersion(),
|
|
43
|
+
}),
|
|
44
|
+
signal: AbortSignal.timeout(5000),
|
|
45
|
+
});
|
|
46
|
+
return res.ok;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function isSyncthingAlive() {
|
|
53
|
+
const config = (0, config_js_1.readConfig)();
|
|
54
|
+
if (!config)
|
|
55
|
+
return false;
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch(`http://127.0.0.1:${config.apiPort}/rest/system/ping`, {
|
|
58
|
+
headers: { "X-API-Key": config.apiKey },
|
|
59
|
+
signal: AbortSignal.timeout(3000),
|
|
60
|
+
});
|
|
61
|
+
return res.ok;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function main() {
|
|
68
|
+
// Write our PID so stop can kill us
|
|
69
|
+
(0, fs_1.writeFileSync)(PID_FILE, String(process.pid));
|
|
70
|
+
// Cleanup PID file on exit
|
|
71
|
+
const cleanup = () => {
|
|
72
|
+
try {
|
|
73
|
+
(0, fs_1.unlinkSync)(PID_FILE);
|
|
74
|
+
}
|
|
75
|
+
catch { }
|
|
76
|
+
};
|
|
77
|
+
process.on("exit", cleanup);
|
|
78
|
+
process.on("SIGTERM", () => {
|
|
79
|
+
cleanup();
|
|
80
|
+
process.exit(0);
|
|
81
|
+
});
|
|
82
|
+
process.on("SIGINT", () => {
|
|
83
|
+
cleanup();
|
|
84
|
+
process.exit(0);
|
|
85
|
+
});
|
|
86
|
+
// Initial heartbeat
|
|
87
|
+
await sendHeartbeat();
|
|
88
|
+
// Heartbeat loop
|
|
89
|
+
setInterval(sendHeartbeat, HEARTBEAT_INTERVAL_MS);
|
|
90
|
+
// Health check loop — exit if Syncthing is gone
|
|
91
|
+
setInterval(async () => {
|
|
92
|
+
const alive = await isSyncthingAlive();
|
|
93
|
+
if (!alive) {
|
|
94
|
+
cleanup();
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
}, HEALTH_CHECK_INTERVAL_MS);
|
|
98
|
+
}
|
|
99
|
+
main().catch(() => process.exit(1));
|
package/dist/heartbeat.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* heartbeat.ts —
|
|
2
|
+
* heartbeat.ts — Spawn/stop the background heartbeat daemon.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* The heartbeat runs in the background and never throws — a failed
|
|
9
|
-
* heartbeat just means we're invisible on the dashboard for a cycle.
|
|
4
|
+
* Instead of running in-process (which dies when init/join exits),
|
|
5
|
+
* we spawn heartbeat-daemon.js as a detached child process that
|
|
6
|
+
* outlives the parent CLI command.
|
|
10
7
|
*/
|
|
11
|
-
/**
|
|
8
|
+
/** Spawn the heartbeat daemon as a detached background process. */
|
|
12
9
|
export declare function startHeartbeat(): void;
|
|
13
|
-
/** Stop the heartbeat
|
|
10
|
+
/** Stop the heartbeat daemon if running. */
|
|
14
11
|
export declare function stopHeartbeat(): void;
|
|
12
|
+
/** Check if the heartbeat daemon is still alive. */
|
|
13
|
+
export declare function isHeartbeatRunning(): boolean;
|
package/dist/heartbeat.js
CHANGED
|
@@ -1,73 +1,66 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* heartbeat.ts —
|
|
3
|
+
* heartbeat.ts — Spawn/stop the background heartbeat daemon.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* The heartbeat runs in the background and never throws — a failed
|
|
10
|
-
* heartbeat just means we're invisible on the dashboard for a cycle.
|
|
5
|
+
* Instead of running in-process (which dies when init/join exits),
|
|
6
|
+
* we spawn heartbeat-daemon.js as a detached child process that
|
|
7
|
+
* outlives the parent CLI command.
|
|
11
8
|
*/
|
|
12
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
10
|
exports.startHeartbeat = startHeartbeat;
|
|
14
11
|
exports.stopHeartbeat = stopHeartbeat;
|
|
15
|
-
|
|
12
|
+
exports.isHeartbeatRunning = isHeartbeatRunning;
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const fs_1 = require("fs");
|
|
15
|
+
const path_1 = require("path");
|
|
16
16
|
const config_js_1 = require("./config.js");
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const config = (0, config_js_1.readConfig)();
|
|
23
|
-
const networkId = (0, config_js_1.readNetworkId)();
|
|
24
|
-
if (!config?.deviceId || !networkId)
|
|
17
|
+
const HEARTBEAT_PID_FILE = (0, path_1.join)(config_js_1.BOTSYNC_DIR, "heartbeat.pid");
|
|
18
|
+
/** Spawn the heartbeat daemon as a detached background process. */
|
|
19
|
+
function startHeartbeat() {
|
|
20
|
+
// Don't double-spawn
|
|
21
|
+
if (isHeartbeatRunning())
|
|
25
22
|
return;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
name: (0, os_1.hostname)(),
|
|
33
|
-
os: process.platform,
|
|
34
|
-
version: getVersion(),
|
|
35
|
-
}),
|
|
36
|
-
signal: AbortSignal.timeout(5000),
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
// Silent fail — dashboard visibility is best-effort
|
|
41
|
-
}
|
|
23
|
+
const daemonScript = (0, path_1.join)((0, path_1.dirname)(__filename), "heartbeat-daemon.js");
|
|
24
|
+
const child = (0, child_process_1.spawn)(process.execPath, [daemonScript], {
|
|
25
|
+
detached: true,
|
|
26
|
+
stdio: "ignore",
|
|
27
|
+
});
|
|
28
|
+
child.unref();
|
|
42
29
|
}
|
|
43
|
-
/**
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (heartbeatTimer.unref) {
|
|
54
|
-
heartbeatTimer.unref();
|
|
30
|
+
/** Stop the heartbeat daemon if running. */
|
|
31
|
+
function stopHeartbeat() {
|
|
32
|
+
const pid = getHeartbeatPid();
|
|
33
|
+
if (pid) {
|
|
34
|
+
try {
|
|
35
|
+
process.kill(pid, "SIGTERM");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Already dead
|
|
39
|
+
}
|
|
55
40
|
}
|
|
56
41
|
}
|
|
57
|
-
/**
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
42
|
+
/** Check if the heartbeat daemon is still alive. */
|
|
43
|
+
function isHeartbeatRunning() {
|
|
44
|
+
const pid = getHeartbeatPid();
|
|
45
|
+
if (!pid)
|
|
46
|
+
return false;
|
|
47
|
+
try {
|
|
48
|
+
process.kill(pid, 0); // Signal 0 = just check if alive
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
62
53
|
}
|
|
63
54
|
}
|
|
64
|
-
|
|
65
|
-
function getVersion() {
|
|
55
|
+
function getHeartbeatPid() {
|
|
66
56
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
57
|
+
if (!(0, fs_1.existsSync)(HEARTBEAT_PID_FILE))
|
|
58
|
+
return null;
|
|
59
|
+
const raw = (0, fs_1.readFileSync)(HEARTBEAT_PID_FILE, "utf-8").trim();
|
|
60
|
+
const pid = parseInt(raw, 10);
|
|
61
|
+
return isNaN(pid) ? null : pid;
|
|
69
62
|
}
|
|
70
63
|
catch {
|
|
71
|
-
return
|
|
64
|
+
return null;
|
|
72
65
|
}
|
|
73
66
|
}
|