feat: start-at-login is a Settings switch, off by default (v2.5.0)
- Settings → About → "Start AgentHub automatically" (AutostartSettings.tsx,
GET/PUT /api/settings/autostart, server/lib/autostart.js). The switch is
the systemd --user unit's / LaunchAgent's enabled state: on writes a unit
for the running server when none exists and enables it, off disables it;
neither starts or stops the server. A foreign unit is never touched
(409 EFOREIGN)
- The plugin bootstrap no longer installs a unit on every install: it
installs or starts one only while the user has it enabled, and a rewrite
keeps the enabled state (writeUserService({ enable })). An install that
already had the unit enabled stays on. A stale-but-enabled unit beside a
live detached server is repointed, no longer started onto the bound port
- unitIsEnabled / setUnitEnabled in systemd-service.js and launchd-service.js
Also:
- fix(workspace): status bar no longer covers the chat box below md (#61)
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* @file Tests server/lib/autostart.js (Settings → About's "Start AgentHub
|
||||
* automatically"): off by default, on writes + enables a unit for this server
|
||||
* without starting it, off disables without stopping, and a foreign unit is
|
||||
* never touched. Also the bootstrap's half: `writeUserService({enable:false})`
|
||||
* leaves the enabled state alone. systemctl is faked; HOME is a temp dir.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
const { describe, it, beforeEach, after } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
|
||||
const TMP_HOME = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-autostart-"));
|
||||
const prevHome = process.env.HOME;
|
||||
process.env.HOME = TMP_HOME;
|
||||
|
||||
const svc = require("../../scripts/systemd-service");
|
||||
const autostart = require("../lib/autostart");
|
||||
svc.enableLinger = () => true; // the real one runs loginctl, outside the seam
|
||||
|
||||
let calls = [];
|
||||
let enabled = false;
|
||||
|
||||
beforeEach(() => {
|
||||
calls = [];
|
||||
enabled = false;
|
||||
fs.rmSync(svc.unitPath("agenthub"), { force: true });
|
||||
svc.__setExecImpl((args) => {
|
||||
if (args[0] === "--version") return { status: 0 };
|
||||
calls.push(args.join(" "));
|
||||
if (args[0] === "is-enabled")
|
||||
return { status: 0, stdout: enabled ? "enabled\n" : "disabled\n" };
|
||||
if (args[0] === "enable") enabled = true;
|
||||
if (args[0] === "disable") enabled = false;
|
||||
return { status: 0 };
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
svc.__reset();
|
||||
if (prevHome === undefined) delete process.env.HOME;
|
||||
else process.env.HOME = prevHome;
|
||||
fs.rmSync(TMP_HOME, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const linuxOnly = { skip: process.platform !== "linux" ? "systemd is Linux-only" : false };
|
||||
const startsOrStops = (c) => /^(start|stop|restart)\b|--now/.test(c);
|
||||
|
||||
describe("autostart", linuxOnly, () => {
|
||||
it("is off with no unit, and turning it off writes nothing", () => {
|
||||
assert.equal(autostart.status().enabled, false);
|
||||
autostart.setEnabled(false);
|
||||
assert.equal(fs.existsSync(svc.unitPath("agenthub")), false);
|
||||
});
|
||||
|
||||
it("on writes a unit for this server and enables it, starting nothing", () => {
|
||||
const res = autostart.setEnabled(true);
|
||||
assert.equal(res.enabled, true);
|
||||
const body = fs.readFileSync(svc.unitPath("agenthub"), "utf8");
|
||||
assert.match(
|
||||
body,
|
||||
new RegExp(`^WorkingDirectory=${path.resolve(__dirname, "..", "..")}$`, "m")
|
||||
);
|
||||
assert.ok(calls.includes("enable agenthub.service"));
|
||||
assert.ok(!calls.some(startsOrStops), JSON.stringify(calls));
|
||||
});
|
||||
|
||||
it("off disables our unit and keeps it, stopping nothing", () => {
|
||||
autostart.setEnabled(true);
|
||||
calls = [];
|
||||
const res = autostart.setEnabled(false);
|
||||
assert.equal(res.enabled, false);
|
||||
assert.ok(calls.includes("disable agenthub.service"));
|
||||
assert.ok(!calls.some(startsOrStops), JSON.stringify(calls));
|
||||
assert.ok(fs.existsSync(svc.unitPath("agenthub")));
|
||||
});
|
||||
|
||||
it("refuses a unit it did not write", () => {
|
||||
fs.mkdirSync(path.dirname(svc.unitPath("agenthub")), { recursive: true });
|
||||
fs.writeFileSync(svc.unitPath("agenthub"), "[Service]\nExecStart=/opt/mine\n", "utf8");
|
||||
assert.equal(autostart.status().foreign, true);
|
||||
assert.throws(() => autostart.setEnabled(true), { code: "EFOREIGN" });
|
||||
assert.equal(
|
||||
fs.readFileSync(svc.unitPath("agenthub"), "utf8"),
|
||||
"[Service]\nExecStart=/opt/mine\n"
|
||||
);
|
||||
});
|
||||
|
||||
it("writeUserService({enable:false}) repoints without enabling — the bootstrap's off path", () => {
|
||||
svc.writeUserService({
|
||||
name: "agenthub",
|
||||
description: "x",
|
||||
workingDir: "/plugin/root",
|
||||
execStart: "/usr/bin/node /plugin/root/server/index.js",
|
||||
enable: false,
|
||||
});
|
||||
assert.ok(!calls.some((c) => c.startsWith("enable")), JSON.stringify(calls));
|
||||
assert.equal(svc.unitIsEnabled("agenthub"), false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user