@seedvault/cli 0.1.3 → 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/bin/sv.mjs +2 -7
- package/dist/sv.js +385 -82
- package/package.json +1 -1
package/bin/sv.mjs
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
import { execFileSync } from "child_process";
|
|
1
|
+
#!/usr/bin/env bun
|
|
3
2
|
import { join, dirname } from "path";
|
|
4
3
|
import { fileURLToPath } from "url";
|
|
5
4
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
|
|
7
|
-
execFileSync("bun", [join(__dirname, "..", "dist", "sv.js"), ...process.argv.slice(2)], { stdio: "inherit" });
|
|
8
|
-
} catch (e) {
|
|
9
|
-
process.exit(e.status || 1);
|
|
10
|
-
}
|
|
5
|
+
await import(join(__dirname, "..", "dist", "sv.js"));
|
package/dist/sv.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
// src/commands/init.ts
|
|
5
5
|
import * as readline from "readline/promises";
|
|
6
|
-
import {
|
|
6
|
+
import { stdout } from "process";
|
|
7
|
+
import * as fs from "fs";
|
|
7
8
|
|
|
8
9
|
// src/config.ts
|
|
9
10
|
import { join, resolve, relative, isAbsolute } from "path";
|
|
@@ -12,12 +13,25 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
|
12
13
|
var CONFIG_DIR = join(homedir(), ".config", "seedvault");
|
|
13
14
|
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
14
15
|
var PID_PATH = join(CONFIG_DIR, "daemon.pid");
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
var DAEMON_LOG_PATH = join(CONFIG_DIR, "daemon.log");
|
|
17
|
+
var LAUNCHD_PLIST_PATH = join(homedir(), "Library", "LaunchAgents", "ai.seedvault.daemon.plist");
|
|
18
|
+
var SYSTEMD_UNIT_PATH = join(homedir(), ".config", "systemd", "user", "seedvault.service");
|
|
19
|
+
var SCHTASKS_XML_PATH = join(CONFIG_DIR, "seedvault-task.xml");
|
|
18
20
|
function getPidPath() {
|
|
19
21
|
return PID_PATH;
|
|
20
22
|
}
|
|
23
|
+
function getDaemonLogPath() {
|
|
24
|
+
return DAEMON_LOG_PATH;
|
|
25
|
+
}
|
|
26
|
+
function getLaunchdPlistPath() {
|
|
27
|
+
return LAUNCHD_PLIST_PATH;
|
|
28
|
+
}
|
|
29
|
+
function getSystemdUnitPath() {
|
|
30
|
+
return SYSTEMD_UNIT_PATH;
|
|
31
|
+
}
|
|
32
|
+
function getSchtasksXmlPath() {
|
|
33
|
+
return SCHTASKS_XML_PATH;
|
|
34
|
+
}
|
|
21
35
|
function ensureConfigDir() {
|
|
22
36
|
if (!existsSync(CONFIG_DIR)) {
|
|
23
37
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
@@ -258,7 +272,14 @@ async function init(args) {
|
|
|
258
272
|
console.log(` Token: ${result.token}`);
|
|
259
273
|
return;
|
|
260
274
|
}
|
|
261
|
-
|
|
275
|
+
let input;
|
|
276
|
+
try {
|
|
277
|
+
const fd = fs.openSync("/dev/tty", "r");
|
|
278
|
+
input = fs.createReadStream("", { fd });
|
|
279
|
+
} catch {
|
|
280
|
+
input = process.stdin;
|
|
281
|
+
}
|
|
282
|
+
const rl = readline.createInterface({ input, output: stdout });
|
|
262
283
|
try {
|
|
263
284
|
console.log(`Seedvault Setup
|
|
264
285
|
`);
|
|
@@ -402,7 +423,7 @@ async function collections() {
|
|
|
402
423
|
}
|
|
403
424
|
|
|
404
425
|
// src/commands/start.ts
|
|
405
|
-
import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
|
|
426
|
+
import { writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
406
427
|
|
|
407
428
|
// ../node_modules/.bun/chokidar@4.0.3/node_modules/chokidar/esm/index.js
|
|
408
429
|
import { stat as statcb } from "fs";
|
|
@@ -2271,13 +2292,350 @@ async function walkDirRecursive(dir, results) {
|
|
|
2271
2292
|
}
|
|
2272
2293
|
}
|
|
2273
2294
|
|
|
2295
|
+
// src/daemon/service.ts
|
|
2296
|
+
import { resolve as resolve5, dirname as dirname3 } from "path";
|
|
2297
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
2298
|
+
var TASK_NAME = "SeedvaultDaemon";
|
|
2299
|
+
function detectPlatform() {
|
|
2300
|
+
if (process.platform === "darwin")
|
|
2301
|
+
return "macos";
|
|
2302
|
+
if (process.platform === "linux")
|
|
2303
|
+
return "linux";
|
|
2304
|
+
if (process.platform === "win32")
|
|
2305
|
+
return "windows";
|
|
2306
|
+
throw new Error(`Unsupported platform: ${process.platform}. Only macOS, Linux, and Windows are supported.`);
|
|
2307
|
+
}
|
|
2308
|
+
function resolveBunPath() {
|
|
2309
|
+
const which = Bun.which("bun");
|
|
2310
|
+
if (which)
|
|
2311
|
+
return which;
|
|
2312
|
+
if (process.platform === "win32") {
|
|
2313
|
+
const fallback = resolve5(process.env.USERPROFILE || process.env.HOME || "~", ".bun", "bin", "bun.exe");
|
|
2314
|
+
if (existsSync3(fallback))
|
|
2315
|
+
return fallback;
|
|
2316
|
+
} else {
|
|
2317
|
+
const fallback = resolve5(process.env.HOME || "~", ".bun", "bin", "bun");
|
|
2318
|
+
if (existsSync3(fallback))
|
|
2319
|
+
return fallback;
|
|
2320
|
+
}
|
|
2321
|
+
throw new Error("Cannot find bun executable. Ensure bun is in your PATH.");
|
|
2322
|
+
}
|
|
2323
|
+
function resolveSvPath() {
|
|
2324
|
+
return resolve5(process.argv[1]);
|
|
2325
|
+
}
|
|
2326
|
+
async function runCommand(cmd, args) {
|
|
2327
|
+
const proc = Bun.spawn([cmd, ...args], {
|
|
2328
|
+
stdout: "pipe",
|
|
2329
|
+
stderr: "pipe"
|
|
2330
|
+
});
|
|
2331
|
+
const [stdout2, stderr] = await Promise.all([
|
|
2332
|
+
new Response(proc.stdout).text(),
|
|
2333
|
+
new Response(proc.stderr).text()
|
|
2334
|
+
]);
|
|
2335
|
+
const exitCode = await proc.exited;
|
|
2336
|
+
if (exitCode !== 0) {
|
|
2337
|
+
throw new Error(`Command failed: ${cmd} ${args.join(" ")}
|
|
2338
|
+
${stderr.trim() || stdout2.trim()}`);
|
|
2339
|
+
}
|
|
2340
|
+
return { stdout: stdout2.trim(), stderr: stderr.trim() };
|
|
2341
|
+
}
|
|
2342
|
+
function generatePlist(bunPath, svPath, logPath) {
|
|
2343
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2344
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2345
|
+
<plist version="1.0">
|
|
2346
|
+
<dict>
|
|
2347
|
+
<key>Label</key>
|
|
2348
|
+
<string>ai.seedvault.daemon</string>
|
|
2349
|
+
<key>ProgramArguments</key>
|
|
2350
|
+
<array>
|
|
2351
|
+
<string>${bunPath}</string>
|
|
2352
|
+
<string>${svPath}</string>
|
|
2353
|
+
<string>start</string>
|
|
2354
|
+
<string>--foreground</string>
|
|
2355
|
+
</array>
|
|
2356
|
+
<key>KeepAlive</key>
|
|
2357
|
+
<dict>
|
|
2358
|
+
<key>SuccessfulExit</key>
|
|
2359
|
+
<false/>
|
|
2360
|
+
</dict>
|
|
2361
|
+
<key>RunAtLoad</key>
|
|
2362
|
+
<true/>
|
|
2363
|
+
<key>ThrottleInterval</key>
|
|
2364
|
+
<integer>5</integer>
|
|
2365
|
+
<key>StandardOutPath</key>
|
|
2366
|
+
<string>${logPath}</string>
|
|
2367
|
+
<key>StandardErrorPath</key>
|
|
2368
|
+
<string>${logPath}</string>
|
|
2369
|
+
</dict>
|
|
2370
|
+
</plist>
|
|
2371
|
+
`;
|
|
2372
|
+
}
|
|
2373
|
+
function generateUnit(bunPath, svPath, logPath) {
|
|
2374
|
+
return `[Unit]
|
|
2375
|
+
Description=Seedvault Sync Daemon
|
|
2376
|
+
|
|
2377
|
+
[Service]
|
|
2378
|
+
ExecStart=${bunPath} ${svPath} start --foreground
|
|
2379
|
+
Restart=on-failure
|
|
2380
|
+
RestartSec=5
|
|
2381
|
+
StandardOutput=append:${logPath}
|
|
2382
|
+
StandardError=append:${logPath}
|
|
2383
|
+
|
|
2384
|
+
[Install]
|
|
2385
|
+
WantedBy=default.target
|
|
2386
|
+
`;
|
|
2387
|
+
}
|
|
2388
|
+
function escapeXml(s) {
|
|
2389
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2390
|
+
}
|
|
2391
|
+
function generateTaskXml(bunPath, svPath, logPath) {
|
|
2392
|
+
const eBun = escapeXml(bunPath);
|
|
2393
|
+
const eSv = escapeXml(svPath);
|
|
2394
|
+
const eLog = escapeXml(logPath);
|
|
2395
|
+
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
2396
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
2397
|
+
<RegistrationInfo>
|
|
2398
|
+
<Description>Seedvault Sync Daemon</Description>
|
|
2399
|
+
</RegistrationInfo>
|
|
2400
|
+
<Triggers>
|
|
2401
|
+
<LogonTrigger>
|
|
2402
|
+
<Enabled>true</Enabled>
|
|
2403
|
+
</LogonTrigger>
|
|
2404
|
+
</Triggers>
|
|
2405
|
+
<Settings>
|
|
2406
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
2407
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
2408
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
2409
|
+
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
2410
|
+
<RestartOnFailure>
|
|
2411
|
+
<Interval>PT5S</Interval>
|
|
2412
|
+
<Count>999</Count>
|
|
2413
|
+
</RestartOnFailure>
|
|
2414
|
+
</Settings>
|
|
2415
|
+
<Actions>
|
|
2416
|
+
<Exec>
|
|
2417
|
+
<Command>cmd</Command>
|
|
2418
|
+
<Arguments>/c "${eBun}" "${eSv}" start --foreground >> "${eLog}" 2>&1</Arguments>
|
|
2419
|
+
</Exec>
|
|
2420
|
+
</Actions>
|
|
2421
|
+
</Task>
|
|
2422
|
+
`;
|
|
2423
|
+
}
|
|
2424
|
+
async function installService() {
|
|
2425
|
+
const platform = detectPlatform();
|
|
2426
|
+
const bunPath = resolveBunPath();
|
|
2427
|
+
const svPath = resolveSvPath();
|
|
2428
|
+
const logPath = getDaemonLogPath();
|
|
2429
|
+
ensureConfigDir();
|
|
2430
|
+
if (platform === "macos") {
|
|
2431
|
+
await installMacos(bunPath, svPath, logPath);
|
|
2432
|
+
} else if (platform === "linux") {
|
|
2433
|
+
await installLinux(bunPath, svPath, logPath);
|
|
2434
|
+
} else {
|
|
2435
|
+
await installWindows(bunPath, svPath, logPath);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
async function installMacos(bunPath, svPath, logPath) {
|
|
2439
|
+
const plistPath = getLaunchdPlistPath();
|
|
2440
|
+
const plistDir = dirname3(plistPath);
|
|
2441
|
+
if (!existsSync3(plistDir)) {
|
|
2442
|
+
mkdirSync2(plistDir, { recursive: true });
|
|
2443
|
+
}
|
|
2444
|
+
if (existsSync3(plistPath)) {
|
|
2445
|
+
try {
|
|
2446
|
+
await runCommand("launchctl", ["unload", plistPath]);
|
|
2447
|
+
} catch {}
|
|
2448
|
+
}
|
|
2449
|
+
await Bun.write(plistPath, generatePlist(bunPath, svPath, logPath));
|
|
2450
|
+
await runCommand("launchctl", ["load", plistPath]);
|
|
2451
|
+
console.log("Seedvault daemon registered with launchd.");
|
|
2452
|
+
console.log(` Service: ai.seedvault.daemon`);
|
|
2453
|
+
console.log(` Log: ${logPath}`);
|
|
2454
|
+
console.log(` The daemon will auto-restart on crash and start on login.`);
|
|
2455
|
+
console.log(` Run 'sv status' to check, 'sv stop' to unregister.`);
|
|
2456
|
+
}
|
|
2457
|
+
async function installLinux(bunPath, svPath, logPath) {
|
|
2458
|
+
const unitPath = getSystemdUnitPath();
|
|
2459
|
+
const unitDir = dirname3(unitPath);
|
|
2460
|
+
if (!existsSync3(unitDir)) {
|
|
2461
|
+
mkdirSync2(unitDir, { recursive: true });
|
|
2462
|
+
}
|
|
2463
|
+
if (existsSync3(unitPath)) {
|
|
2464
|
+
try {
|
|
2465
|
+
await runCommand("systemctl", ["--user", "disable", "--now", "seedvault.service"]);
|
|
2466
|
+
} catch {}
|
|
2467
|
+
}
|
|
2468
|
+
await Bun.write(unitPath, generateUnit(bunPath, svPath, logPath));
|
|
2469
|
+
await runCommand("systemctl", ["--user", "daemon-reload"]);
|
|
2470
|
+
await runCommand("systemctl", ["--user", "enable", "--now", "seedvault.service"]);
|
|
2471
|
+
console.log("Seedvault daemon registered with systemd.");
|
|
2472
|
+
console.log(` Service: seedvault.service`);
|
|
2473
|
+
console.log(` Log: ${logPath}`);
|
|
2474
|
+
console.log(` The daemon will auto-restart on crash and start on login.`);
|
|
2475
|
+
console.log(` Run 'sv status' to check, 'sv stop' to unregister.`);
|
|
2476
|
+
}
|
|
2477
|
+
async function installWindows(bunPath, svPath, logPath) {
|
|
2478
|
+
const xmlPath = getSchtasksXmlPath();
|
|
2479
|
+
try {
|
|
2480
|
+
await runCommand("schtasks", ["/Delete", "/TN", TASK_NAME, "/F"]);
|
|
2481
|
+
} catch {}
|
|
2482
|
+
await Bun.write(xmlPath, generateTaskXml(bunPath, svPath, logPath));
|
|
2483
|
+
await runCommand("schtasks", ["/Create", "/TN", TASK_NAME, "/XML", xmlPath, "/F"]);
|
|
2484
|
+
await runCommand("schtasks", ["/Run", "/TN", TASK_NAME]);
|
|
2485
|
+
console.log("Seedvault daemon registered with Windows Task Scheduler.");
|
|
2486
|
+
console.log(` Task: ${TASK_NAME}`);
|
|
2487
|
+
console.log(` Log: ${logPath}`);
|
|
2488
|
+
console.log(` The daemon will auto-restart on failure and start on login.`);
|
|
2489
|
+
console.log(` Run 'sv status' to check, 'sv stop' to unregister.`);
|
|
2490
|
+
}
|
|
2491
|
+
async function uninstallService() {
|
|
2492
|
+
const platform = detectPlatform();
|
|
2493
|
+
if (platform === "macos") {
|
|
2494
|
+
await uninstallMacos();
|
|
2495
|
+
} else if (platform === "linux") {
|
|
2496
|
+
await uninstallLinux();
|
|
2497
|
+
} else {
|
|
2498
|
+
await uninstallWindows();
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
async function uninstallMacos() {
|
|
2502
|
+
const plistPath = getLaunchdPlistPath();
|
|
2503
|
+
if (!existsSync3(plistPath)) {
|
|
2504
|
+
console.log("No daemon service is registered.");
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
try {
|
|
2508
|
+
await runCommand("launchctl", ["unload", plistPath]);
|
|
2509
|
+
} catch {}
|
|
2510
|
+
unlinkSync(plistPath);
|
|
2511
|
+
cleanupPidFile();
|
|
2512
|
+
console.log("Daemon stopped and service unregistered.");
|
|
2513
|
+
}
|
|
2514
|
+
async function uninstallLinux() {
|
|
2515
|
+
const unitPath = getSystemdUnitPath();
|
|
2516
|
+
if (!existsSync3(unitPath)) {
|
|
2517
|
+
console.log("No daemon service is registered.");
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
try {
|
|
2521
|
+
await runCommand("systemctl", ["--user", "disable", "--now", "seedvault.service"]);
|
|
2522
|
+
} catch {}
|
|
2523
|
+
unlinkSync(unitPath);
|
|
2524
|
+
try {
|
|
2525
|
+
await runCommand("systemctl", ["--user", "daemon-reload"]);
|
|
2526
|
+
} catch {}
|
|
2527
|
+
cleanupPidFile();
|
|
2528
|
+
console.log("Daemon stopped and service unregistered.");
|
|
2529
|
+
}
|
|
2530
|
+
async function uninstallWindows() {
|
|
2531
|
+
let taskExists = false;
|
|
2532
|
+
try {
|
|
2533
|
+
await runCommand("schtasks", ["/Query", "/TN", TASK_NAME]);
|
|
2534
|
+
taskExists = true;
|
|
2535
|
+
} catch {}
|
|
2536
|
+
if (!taskExists) {
|
|
2537
|
+
console.log("No daemon service is registered.");
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
try {
|
|
2541
|
+
await runCommand("schtasks", ["/End", "/TN", TASK_NAME]);
|
|
2542
|
+
} catch {}
|
|
2543
|
+
try {
|
|
2544
|
+
await runCommand("schtasks", ["/Delete", "/TN", TASK_NAME, "/F"]);
|
|
2545
|
+
} catch {}
|
|
2546
|
+
const xmlPath = getSchtasksXmlPath();
|
|
2547
|
+
if (existsSync3(xmlPath)) {
|
|
2548
|
+
try {
|
|
2549
|
+
unlinkSync(xmlPath);
|
|
2550
|
+
} catch {}
|
|
2551
|
+
}
|
|
2552
|
+
cleanupPidFile();
|
|
2553
|
+
console.log("Daemon stopped and service unregistered.");
|
|
2554
|
+
}
|
|
2555
|
+
function cleanupPidFile() {
|
|
2556
|
+
const pidPath = getPidPath();
|
|
2557
|
+
if (existsSync3(pidPath)) {
|
|
2558
|
+
try {
|
|
2559
|
+
unlinkSync(pidPath);
|
|
2560
|
+
} catch {}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
async function getServiceStatus() {
|
|
2564
|
+
const platform = detectPlatform();
|
|
2565
|
+
if (platform === "macos") {
|
|
2566
|
+
return getStatusMacos();
|
|
2567
|
+
} else if (platform === "linux") {
|
|
2568
|
+
return getStatusLinux();
|
|
2569
|
+
} else {
|
|
2570
|
+
return getStatusWindows();
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
async function getStatusMacos() {
|
|
2574
|
+
const plistPath = getLaunchdPlistPath();
|
|
2575
|
+
if (!existsSync3(plistPath)) {
|
|
2576
|
+
return { installed: false, running: false, pid: null };
|
|
2577
|
+
}
|
|
2578
|
+
try {
|
|
2579
|
+
const { stdout: stdout2 } = await runCommand("launchctl", ["list", "ai.seedvault.daemon"]);
|
|
2580
|
+
const pidMatch = stdout2.match(/"PID"\s*=\s*(\d+)/);
|
|
2581
|
+
const pid = pidMatch ? parseInt(pidMatch[1], 10) : null;
|
|
2582
|
+
return { installed: true, running: pid !== null, pid };
|
|
2583
|
+
} catch {
|
|
2584
|
+
return { installed: true, running: false, pid: null };
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
async function getStatusLinux() {
|
|
2588
|
+
const unitPath = getSystemdUnitPath();
|
|
2589
|
+
if (!existsSync3(unitPath)) {
|
|
2590
|
+
return { installed: false, running: false, pid: null };
|
|
2591
|
+
}
|
|
2592
|
+
let running = false;
|
|
2593
|
+
try {
|
|
2594
|
+
const { stdout: stdout2 } = await runCommand("systemctl", ["--user", "is-active", "seedvault.service"]);
|
|
2595
|
+
running = stdout2 === "active";
|
|
2596
|
+
} catch {}
|
|
2597
|
+
let pid = null;
|
|
2598
|
+
if (running) {
|
|
2599
|
+
try {
|
|
2600
|
+
const { stdout: stdout2 } = await runCommand("systemctl", ["--user", "show", "--property=MainPID", "seedvault.service"]);
|
|
2601
|
+
const match = stdout2.match(/MainPID=(\d+)/);
|
|
2602
|
+
if (match) {
|
|
2603
|
+
const parsed = parseInt(match[1], 10);
|
|
2604
|
+
if (parsed > 0)
|
|
2605
|
+
pid = parsed;
|
|
2606
|
+
}
|
|
2607
|
+
} catch {}
|
|
2608
|
+
}
|
|
2609
|
+
return { installed: true, running, pid };
|
|
2610
|
+
}
|
|
2611
|
+
async function getStatusWindows() {
|
|
2612
|
+
try {
|
|
2613
|
+
const { stdout: stdout2 } = await runCommand("schtasks", ["/Query", "/TN", TASK_NAME, "/FO", "CSV", "/NH"]);
|
|
2614
|
+
const installed = true;
|
|
2615
|
+
const running = stdout2.toLowerCase().includes("running");
|
|
2616
|
+
let pid = null;
|
|
2617
|
+
const pidPath = getPidPath();
|
|
2618
|
+
if (running && existsSync3(pidPath)) {
|
|
2619
|
+
try {
|
|
2620
|
+
const content = await Bun.file(pidPath).text();
|
|
2621
|
+
const parsed = parseInt(content.trim(), 10);
|
|
2622
|
+
if (!isNaN(parsed) && parsed > 0)
|
|
2623
|
+
pid = parsed;
|
|
2624
|
+
} catch {}
|
|
2625
|
+
}
|
|
2626
|
+
return { installed, running, pid };
|
|
2627
|
+
} catch {
|
|
2628
|
+
return { installed: false, running: false, pid: null };
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2274
2632
|
// src/commands/start.ts
|
|
2275
2633
|
async function start(args) {
|
|
2276
|
-
const
|
|
2277
|
-
if (
|
|
2278
|
-
return
|
|
2634
|
+
const foreground = args.includes("-f") || args.includes("--foreground");
|
|
2635
|
+
if (foreground) {
|
|
2636
|
+
return startForeground();
|
|
2279
2637
|
}
|
|
2280
|
-
return
|
|
2638
|
+
return installService();
|
|
2281
2639
|
}
|
|
2282
2640
|
async function startForeground() {
|
|
2283
2641
|
let config = loadConfig();
|
|
@@ -2398,31 +2756,13 @@ async function startForeground() {
|
|
|
2398
2756
|
watcher.close();
|
|
2399
2757
|
syncer.stop();
|
|
2400
2758
|
try {
|
|
2401
|
-
|
|
2759
|
+
unlinkSync2(getPidPath());
|
|
2402
2760
|
} catch {}
|
|
2403
2761
|
process.exit(0);
|
|
2404
2762
|
};
|
|
2405
2763
|
process.on("SIGINT", shutdown);
|
|
2406
2764
|
process.on("SIGTERM", shutdown);
|
|
2407
2765
|
}
|
|
2408
|
-
async function startBackground() {
|
|
2409
|
-
loadConfig();
|
|
2410
|
-
const entryPoint = import.meta.dir + "/../index.ts";
|
|
2411
|
-
const logPath = getConfigDir() + "/daemon.log";
|
|
2412
|
-
const child = Bun.spawn({
|
|
2413
|
-
cmd: ["bun", "run", entryPoint, "start"],
|
|
2414
|
-
stdin: "ignore",
|
|
2415
|
-
stdout: Bun.file(logPath),
|
|
2416
|
-
stderr: Bun.file(logPath),
|
|
2417
|
-
env: { ...process.env }
|
|
2418
|
-
});
|
|
2419
|
-
const pid = child.pid;
|
|
2420
|
-
writeFileSync2(getPidPath(), String(pid));
|
|
2421
|
-
console.log(`Daemon started in background (PID ${pid}).`);
|
|
2422
|
-
console.log(` Log: ${logPath}`);
|
|
2423
|
-
console.log(` Run 'sv status' to check, 'sv stop' to stop.`);
|
|
2424
|
-
child.unref();
|
|
2425
|
-
}
|
|
2426
2766
|
function keyByName(collections2) {
|
|
2427
2767
|
const map = new Map;
|
|
2428
2768
|
for (const collection of collections2) {
|
|
@@ -2455,46 +2795,11 @@ function reconcileCollections(prev, next) {
|
|
|
2455
2795
|
}
|
|
2456
2796
|
|
|
2457
2797
|
// src/commands/stop.ts
|
|
2458
|
-
import { readFileSync as readFileSync2, unlinkSync as unlinkSync2, existsSync as existsSync3 } from "fs";
|
|
2459
2798
|
async function stop() {
|
|
2460
|
-
|
|
2461
|
-
if (!existsSync3(pidPath)) {
|
|
2462
|
-
console.log("No daemon is running (no PID file found).");
|
|
2463
|
-
return;
|
|
2464
|
-
}
|
|
2465
|
-
const pid = parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
|
|
2466
|
-
if (isNaN(pid)) {
|
|
2467
|
-
console.error("Invalid PID file. Removing it.");
|
|
2468
|
-
unlinkSync2(pidPath);
|
|
2469
|
-
return;
|
|
2470
|
-
}
|
|
2471
|
-
try {
|
|
2472
|
-
process.kill(pid, 0);
|
|
2473
|
-
} catch {
|
|
2474
|
-
console.log(`Daemon (PID ${pid}) is not running. Cleaning up PID file.`);
|
|
2475
|
-
unlinkSync2(pidPath);
|
|
2476
|
-
return;
|
|
2477
|
-
}
|
|
2478
|
-
try {
|
|
2479
|
-
process.kill(pid, "SIGTERM");
|
|
2480
|
-
console.log(`Sent SIGTERM to daemon (PID ${pid}).`);
|
|
2481
|
-
await Bun.sleep(500);
|
|
2482
|
-
try {
|
|
2483
|
-
process.kill(pid, 0);
|
|
2484
|
-
console.log("Daemon still running. Send SIGKILL with: kill -9 " + pid);
|
|
2485
|
-
} catch {
|
|
2486
|
-
console.log("Daemon stopped.");
|
|
2487
|
-
try {
|
|
2488
|
-
unlinkSync2(pidPath);
|
|
2489
|
-
} catch {}
|
|
2490
|
-
}
|
|
2491
|
-
} catch (e) {
|
|
2492
|
-
console.error(`Failed to stop daemon: ${e.message}`);
|
|
2493
|
-
}
|
|
2799
|
+
await uninstallService();
|
|
2494
2800
|
}
|
|
2495
2801
|
|
|
2496
2802
|
// src/commands/status.ts
|
|
2497
|
-
import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
|
|
2498
2803
|
async function status() {
|
|
2499
2804
|
if (!configExists()) {
|
|
2500
2805
|
console.log("Not configured. Run 'sv init' first.");
|
|
@@ -2505,21 +2810,19 @@ async function status() {
|
|
|
2505
2810
|
`);
|
|
2506
2811
|
console.log(` Server: ${config.server}`);
|
|
2507
2812
|
console.log(` Contributor: ${config.contributorId}`);
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
const
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
if (alive) {
|
|
2517
|
-
console.log(` Daemon: running (PID ${pid})`);
|
|
2813
|
+
try {
|
|
2814
|
+
const platform = detectPlatform();
|
|
2815
|
+
const serviceName = platform === "macos" ? "launchd" : platform === "linux" ? "systemd" : "Task Scheduler";
|
|
2816
|
+
const svc = await getServiceStatus();
|
|
2817
|
+
if (!svc.installed) {
|
|
2818
|
+
console.log(` Daemon: not registered (run 'sv start' to register)`);
|
|
2819
|
+
} else if (svc.running) {
|
|
2820
|
+
console.log(` Daemon: running via ${serviceName}${svc.pid ? ` (PID ${svc.pid})` : ""}`);
|
|
2518
2821
|
} else {
|
|
2519
|
-
console.log(` Daemon:
|
|
2822
|
+
console.log(` Daemon: registered via ${serviceName} but not running`);
|
|
2520
2823
|
}
|
|
2521
|
-
}
|
|
2522
|
-
console.log(" Daemon:
|
|
2824
|
+
} catch {
|
|
2825
|
+
console.log(" Daemon: unsupported platform");
|
|
2523
2826
|
}
|
|
2524
2827
|
if (config.collections.length === 0) {
|
|
2525
2828
|
console.log(" Collections: none configured");
|
|
@@ -2644,9 +2947,9 @@ Collections:
|
|
|
2644
2947
|
collections List configured collections
|
|
2645
2948
|
|
|
2646
2949
|
Daemon:
|
|
2647
|
-
start
|
|
2648
|
-
start -
|
|
2649
|
-
stop Stop
|
|
2950
|
+
start Register OS service and start syncing
|
|
2951
|
+
start -f Start syncing in foreground (debug)
|
|
2952
|
+
stop Stop daemon and unregister service
|
|
2650
2953
|
status Show sync status
|
|
2651
2954
|
|
|
2652
2955
|
Files:
|
|
@@ -2664,7 +2967,7 @@ async function main() {
|
|
|
2664
2967
|
return;
|
|
2665
2968
|
}
|
|
2666
2969
|
if (cmd === "--version" || cmd === "-v") {
|
|
2667
|
-
console.log("0.1
|
|
2970
|
+
console.log("0.2.1");
|
|
2668
2971
|
return;
|
|
2669
2972
|
}
|
|
2670
2973
|
try {
|