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