fluxy-bot 0.5.69 → 0.5.71
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/bin/cli.js +11 -9
- package/package.json +1 -1
- package/supervisor/index.ts +24 -38
- package/supervisor/tunnel.ts +23 -9
package/bin/cli.js
CHANGED
|
@@ -152,9 +152,9 @@ function generateLaunchdPlist({ nodePath, dataDir }) {
|
|
|
152
152
|
<key>ThrottleInterval</key>
|
|
153
153
|
<integer>5</integer>
|
|
154
154
|
<key>StandardOutPath</key>
|
|
155
|
-
<string>${LAUNCHD_LOG_DIR}/
|
|
155
|
+
<string>${LAUNCHD_LOG_DIR}/fluxy.log</string>
|
|
156
156
|
<key>StandardErrorPath</key>
|
|
157
|
-
<string>${LAUNCHD_LOG_DIR}/
|
|
157
|
+
<string>${LAUNCHD_LOG_DIR}/fluxy.log</string>
|
|
158
158
|
<key>ProcessType</key>
|
|
159
159
|
<string>Standard</string>
|
|
160
160
|
</dict>
|
|
@@ -545,15 +545,15 @@ function finalMessage(tunnelUrl, relayUrl) {
|
|
|
545
545
|
${c.pink}${c.bold}${link(relayUrl)}${c.reset}`);
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
-
if (
|
|
548
|
+
if (hasDaemonSupport()) {
|
|
549
549
|
console.log(`
|
|
550
550
|
${c.dim}─────────────────────────────────${c.reset}
|
|
551
551
|
|
|
552
552
|
${c.bold}${c.white}Commands:${c.reset}
|
|
553
|
-
${c.dim}Status${c.reset} ${c.pink}fluxy
|
|
554
|
-
${c.dim}Logs${c.reset} ${c.pink}fluxy
|
|
553
|
+
${c.dim}Status${c.reset} ${c.pink}fluxy status${c.reset}
|
|
554
|
+
${c.dim}Logs${c.reset} ${c.pink}fluxy logs${c.reset}
|
|
555
|
+
${c.dim}Stop${c.reset} ${c.pink}fluxy stop${c.reset}
|
|
555
556
|
${c.dim}Restart${c.reset} ${c.pink}fluxy daemon restart${c.reset}
|
|
556
|
-
${c.dim}Stop${c.reset} ${c.pink}fluxy daemon stop${c.reset}
|
|
557
557
|
${c.dim}Update${c.reset} ${c.pink}fluxy update${c.reset}
|
|
558
558
|
`);
|
|
559
559
|
} else {
|
|
@@ -1222,7 +1222,7 @@ async function daemon(sub) {
|
|
|
1222
1222
|
if (PLATFORM === 'darwin') {
|
|
1223
1223
|
switch (action) {
|
|
1224
1224
|
case 'install': {
|
|
1225
|
-
const dataDir =
|
|
1225
|
+
const dataDir = ROOT; // Uses REPO_ROOT in dev, DATA_DIR (~/.fluxy) in production
|
|
1226
1226
|
if (!fs.existsSync(path.join(dataDir, 'supervisor', 'index.ts'))) {
|
|
1227
1227
|
console.log(`\n ${c.red}✗${c.reset} Run ${c.pink}fluxy init${c.reset} first.\n`);
|
|
1228
1228
|
process.exit(1);
|
|
@@ -1301,12 +1301,12 @@ async function daemon(sub) {
|
|
|
1301
1301
|
console.log(`\n ${c.dim}●${c.reset} Fluxy daemon is stopped.`);
|
|
1302
1302
|
}
|
|
1303
1303
|
console.log(` ${c.dim}Plist:${c.reset} ${LAUNCHD_PLIST_PATH}`);
|
|
1304
|
-
console.log(` ${c.dim}Logs:${c.reset} ${LAUNCHD_LOG_DIR}
|
|
1304
|
+
console.log(` ${c.dim}Logs:${c.reset} ${LAUNCHD_LOG_DIR}/fluxy.log\n`);
|
|
1305
1305
|
break;
|
|
1306
1306
|
}
|
|
1307
1307
|
|
|
1308
1308
|
case 'logs': {
|
|
1309
|
-
const logFile = path.join(LAUNCHD_LOG_DIR, '
|
|
1309
|
+
const logFile = path.join(LAUNCHD_LOG_DIR, 'fluxy.log');
|
|
1310
1310
|
if (!fs.existsSync(logFile)) {
|
|
1311
1311
|
console.log(`\n ${c.dim}No logs found at ${logFile}${c.reset}\n`);
|
|
1312
1312
|
break;
|
|
@@ -1548,6 +1548,8 @@ async function tunnel(sub) {
|
|
|
1548
1548
|
switch (command) {
|
|
1549
1549
|
case 'init': init(); break;
|
|
1550
1550
|
case 'start': start(); break;
|
|
1551
|
+
case 'stop': daemon('stop'); break;
|
|
1552
|
+
case 'logs': daemon('logs'); break;
|
|
1551
1553
|
case 'status': status(); break;
|
|
1552
1554
|
case 'update': update(); break;
|
|
1553
1555
|
case 'daemon': daemon(subcommand); break;
|
package/package.json
CHANGED
package/supervisor/index.ts
CHANGED
|
@@ -784,17 +784,18 @@ export async function startSupervisor() {
|
|
|
784
784
|
config.tunnelUrl = tunnelUrl;
|
|
785
785
|
saveConfig(config);
|
|
786
786
|
|
|
787
|
-
// Wait for
|
|
787
|
+
// Wait for local server to be reachable before telling the relay.
|
|
788
|
+
// Probes localhost directly — avoids macOS issue where server can't
|
|
789
|
+
// reach itself through the Cloudflare tunnel URL.
|
|
788
790
|
let tunnelReady = false;
|
|
789
|
-
log.info(
|
|
791
|
+
log.info('Readiness probe: waiting for local server...');
|
|
790
792
|
for (let i = 0; i < 30; i++) {
|
|
791
793
|
try {
|
|
792
|
-
const res = await fetch(
|
|
794
|
+
const res = await fetch(`http://127.0.0.1:${config.port}/api/health?_cb=${Date.now()}`, {
|
|
793
795
|
signal: AbortSignal.timeout(3000),
|
|
794
|
-
headers: { 'Cache-Control': 'no-cache, no-store' },
|
|
795
796
|
});
|
|
796
797
|
log.info(`Readiness probe #${i + 1}: ${res.status}`);
|
|
797
|
-
if (res.
|
|
798
|
+
if (res.ok) {
|
|
798
799
|
tunnelReady = true;
|
|
799
800
|
break;
|
|
800
801
|
}
|
|
@@ -804,7 +805,7 @@ export async function startSupervisor() {
|
|
|
804
805
|
await new Promise(r => setTimeout(r, 1000));
|
|
805
806
|
}
|
|
806
807
|
if (!tunnelReady) {
|
|
807
|
-
log.warn('
|
|
808
|
+
log.warn('Local server readiness probe timed out — updating relay anyway');
|
|
808
809
|
}
|
|
809
810
|
|
|
810
811
|
// Now register tunnel URL with relay and start heartbeats
|
|
@@ -837,17 +838,16 @@ export async function startSupervisor() {
|
|
|
837
838
|
config.tunnelUrl = tunnelUrl;
|
|
838
839
|
saveConfig(config);
|
|
839
840
|
|
|
840
|
-
// Readiness probe
|
|
841
|
+
// Readiness probe — check local server (not tunnel URL, avoids macOS self-reach issue)
|
|
841
842
|
let tunnelReady = false;
|
|
842
|
-
log.info(
|
|
843
|
+
log.info('Readiness probe: waiting for local server...');
|
|
843
844
|
for (let i = 0; i < 30; i++) {
|
|
844
845
|
try {
|
|
845
|
-
const res = await fetch(
|
|
846
|
+
const res = await fetch(`http://127.0.0.1:${config.port}/api/health?_cb=${Date.now()}`, {
|
|
846
847
|
signal: AbortSignal.timeout(3000),
|
|
847
|
-
headers: { 'Cache-Control': 'no-cache, no-store' },
|
|
848
848
|
});
|
|
849
849
|
log.info(`Readiness probe #${i + 1}: ${res.status}`);
|
|
850
|
-
if (res.
|
|
850
|
+
if (res.ok) {
|
|
851
851
|
tunnelReady = true;
|
|
852
852
|
break;
|
|
853
853
|
}
|
|
@@ -857,7 +857,7 @@ export async function startSupervisor() {
|
|
|
857
857
|
await new Promise(r => setTimeout(r, 1000));
|
|
858
858
|
}
|
|
859
859
|
if (!tunnelReady) {
|
|
860
|
-
log.warn('
|
|
860
|
+
log.warn('Local server readiness probe timed out');
|
|
861
861
|
}
|
|
862
862
|
|
|
863
863
|
console.log('__READY__');
|
|
@@ -880,7 +880,7 @@ export async function startSupervisor() {
|
|
|
880
880
|
lastTick = now; // Update immediately so concurrent ticks don't see a stale gap
|
|
881
881
|
|
|
882
882
|
if (wakeGap || periodicCheck) {
|
|
883
|
-
const alive = await isTunnelAlive(tunnelUrl
|
|
883
|
+
const alive = await isTunnelAlive(tunnelUrl!, config.port);
|
|
884
884
|
if (!alive) {
|
|
885
885
|
log.warn('Tunnel dead, restarting...');
|
|
886
886
|
try {
|
|
@@ -892,35 +892,21 @@ export async function startSupervisor() {
|
|
|
892
892
|
// Quick tunnel: restart and get new URL
|
|
893
893
|
const newUrl = await restartTunnel(config.port);
|
|
894
894
|
|
|
895
|
-
//
|
|
896
|
-
|
|
897
|
-
let tunnelReady = false;
|
|
898
|
-
for (let i = 0; i < 30; i++) {
|
|
899
|
-
try {
|
|
900
|
-
const res = await fetch(newUrl + `/api/health?_cb=${Date.now()}`, {
|
|
901
|
-
signal: AbortSignal.timeout(3000),
|
|
902
|
-
headers: { 'Cache-Control': 'no-cache, no-store' },
|
|
903
|
-
});
|
|
904
|
-
if (res.status !== 502 && res.status !== 503) {
|
|
905
|
-
tunnelReady = true;
|
|
906
|
-
log.info(`Tunnel reachable after ${i + 1} probes`);
|
|
907
|
-
break;
|
|
908
|
-
}
|
|
909
|
-
} catch {}
|
|
910
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
911
|
-
}
|
|
912
|
-
if (!tunnelReady) {
|
|
913
|
-
log.warn('Tunnel readiness probe timed out — updating relay anyway');
|
|
914
|
-
}
|
|
895
|
+
// Brief pause to let cloudflared establish the tunnel
|
|
896
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
915
897
|
|
|
916
898
|
tunnelUrl = newUrl;
|
|
917
|
-
config.tunnelUrl = newUrl;
|
|
918
|
-
saveConfig(config);
|
|
919
899
|
|
|
920
|
-
|
|
900
|
+
// Re-read config from disk — relay token may have been added during onboarding
|
|
901
|
+
const latestCfg = loadConfig();
|
|
902
|
+
latestCfg.tunnelUrl = newUrl;
|
|
903
|
+
saveConfig(latestCfg);
|
|
904
|
+
|
|
905
|
+
if (latestCfg.relay?.token) {
|
|
921
906
|
stopHeartbeat();
|
|
922
|
-
startHeartbeat(
|
|
923
|
-
await updateTunnelUrl(
|
|
907
|
+
startHeartbeat(latestCfg.relay.token, newUrl);
|
|
908
|
+
await updateTunnelUrl(latestCfg.relay.token, newUrl);
|
|
909
|
+
log.ok(`Relay updated with new tunnel URL`);
|
|
924
910
|
}
|
|
925
911
|
log.ok(`Tunnel restored: ${newUrl}`);
|
|
926
912
|
}
|
package/supervisor/tunnel.ts
CHANGED
|
@@ -116,19 +116,33 @@ export function stopTunnel(): void {
|
|
|
116
116
|
proc = null;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
/** Check if the cloudflared child process is still running */
|
|
120
|
+
export function isTunnelProcessAlive(): boolean {
|
|
121
|
+
return proc !== null && proc.exitCode === null && proc.signalCode === null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function isTunnelAlive(url: string, localPort?: number): Promise<boolean> {
|
|
125
|
+
// 1. Process check — if cloudflared exited, tunnel is definitely dead
|
|
126
|
+
if (!isTunnelProcessAlive()) return false;
|
|
127
|
+
|
|
128
|
+
// 2. Local reachability check — confirm the local server is still responding.
|
|
129
|
+
// We probe localhost directly instead of the tunnel URL because on macOS
|
|
130
|
+
// the server can't reach itself through the Cloudflare tunnel (DNS/firewall).
|
|
131
|
+
// If localhost responds and cloudflared is running, the tunnel is alive.
|
|
132
|
+
if (localPort) {
|
|
122
133
|
try {
|
|
123
|
-
const res = await fetch(
|
|
124
|
-
signal: AbortSignal.timeout(
|
|
125
|
-
headers: { 'Cache-Control': 'no-cache, no-store' },
|
|
134
|
+
const res = await fetch(`http://127.0.0.1:${localPort}/api/health?_cb=${Date.now()}`, {
|
|
135
|
+
signal: AbortSignal.timeout(3000),
|
|
126
136
|
});
|
|
127
|
-
if (res.
|
|
137
|
+
if (res.ok) return true;
|
|
128
138
|
} catch {}
|
|
129
|
-
|
|
139
|
+
// Local server not responding — but cloudflared is alive, so tunnel is still up.
|
|
140
|
+
// The worker may be restarting. Don't kill the tunnel for a local issue.
|
|
141
|
+
return true;
|
|
130
142
|
}
|
|
131
|
-
|
|
143
|
+
|
|
144
|
+
// No local port provided — process alive is enough
|
|
145
|
+
return true;
|
|
132
146
|
}
|
|
133
147
|
|
|
134
148
|
export async function restartTunnel(port: number): Promise<string> {
|