import { existsSync, mkdirSync, writeFileSync, unlinkSync, rmSync, readFileSync } from "node:fs"; import { join, dirname } from "node:path"; import { homedir } from "node:os"; import { run, cliEntry, type AutostartManager, type AutostartInstallOpts } from "./index.js"; import { userConfigPath } from "../config.js"; const UNIT_NAME = "claude-mailbox.service"; function unitPath(): string { const xdg = process.env["XDG_CONFIG_HOME"] || join(homedir(), ".config"); return join(xdg, "systemd", "user", UNIT_NAME); } function ensureConfigSeeded(opts: AutostartInstallOpts): string { const path = userConfigPath(); if (!existsSync(path)) { mkdirSync(dirname(path), { recursive: true }); const seed: Record = {}; if (opts.port !== undefined) seed.port = opts.port; if (opts.bind !== undefined) seed.bind = opts.bind; if (opts.dbPath !== undefined) seed.dbPath = opts.dbPath; writeFileSync(path, JSON.stringify(seed, null, 2) + "\n", "utf8"); } return path; } function shellQuote(s: string): string { return `'${s.replace(/'/g, `'\\''`)}'`; } function buildUnit(node: string, script: string, configPath: string): string { const exec = `${shellQuote(node)} ${shellQuote(script)} serve --config ${shellQuote(configPath)}`; return `[Unit] Description=ClaudeMailbox MCP mail daemon After=network.target [Service] Type=simple ExecStart=${exec} Restart=on-failure RestartSec=2 [Install] WantedBy=default.target `; } function systemctl(args: string[]): { status: number; stdout: string; stderr: string } { return run("systemctl", ["--user", ...args]); } export function linuxManager(): AutostartManager { return { mode: "default", async install(opts) { const configPath = ensureConfigSeeded(opts); const { node, script } = cliEntry(); const path = unitPath(); mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, buildUnit(node, script, configPath), "utf8"); const reload = systemctl(["daemon-reload"]); if (reload.status !== 0) { throw new Error(`systemctl daemon-reload failed: ${reload.stderr || reload.stdout}`); } const enable = systemctl(["enable", "--now", UNIT_NAME]); if (enable.status !== 0) { throw new Error(`systemctl enable --now failed: ${enable.stderr || enable.stdout}`); } }, async uninstall(purge) { systemctl(["disable", "--now", UNIT_NAME]); const path = unitPath(); if (existsSync(path)) unlinkSync(path); systemctl(["daemon-reload"]); if (purge) { const cfg = userConfigPath(); if (existsSync(cfg)) { try { const parsed = JSON.parse(readFileSync(cfg, "utf8")) as { dbPath?: string }; if (parsed.dbPath && existsSync(parsed.dbPath)) { rmSync(parsed.dbPath, { force: true }); } } catch { // ignore } unlinkSync(cfg); } } }, async start() { const r = systemctl(["start", UNIT_NAME]); if (r.status !== 0) throw new Error(`systemctl start failed: ${r.stderr || r.stdout}`); }, async stop() { systemctl(["stop", UNIT_NAME]); }, async status() { if (!existsSync(unitPath())) return "NotInstalled"; const r = systemctl(["is-active", UNIT_NAME]); if (r.stdout.trim() === "active") return "Running"; return "Stopped"; }, }; }