Skip to content

feat: start with the system on Linux and macOS - #96

Merged
httpsphl merged 6 commits into
mainfrom
feat/unix-autostart
Oct 1, 2026
Merged

httpsphl merged 6 commits into
mainfrom
feat/unix-autostart

Conversation

@httpsphl

@httpsphl httpsphl commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Third slice of Linux/macOS support (spec 18, item 8): botloftd service install|status|restart|stop|uninstall work off Windows, so the daemon starts at sign-in and comes back after a crash.

Linux: systemd user service

  • The unit is ~/.config/systemd/user/botloft.service (botloft-<hash>.service for another data folder, like the Windows task name).
  • It has Type=exec, Restart=on-failure (RestartSec=5), KillMode=control-group, TimeoutStopSec=20 and WantedBy=default.target.
  • Starting with the system is systemctl --user enable (or disable).
  • After a crash, systemd brings the daemon back. A stop asked for (logout, service stop) exits cleanly and stays stopped.
  • KillMode=control-group ends the bots and browsers with the daemon, which closes the orphan gap left by slice 2 on Linux.

macOS: launch agent

  • The agent is ~/Library/LaunchAgents/io.github.httpsphl.botloft.daemon[-<hash>].plist, in gui/<uid>.
  • RunAtLoad follows the owner's choice.
  • KeepAlive is PathState on <home>/run/keep-alive, because a plain KeepAlive (or SuccessfulExit) would also start it at every sign-in:
    • starting creates the file: install, restart, and the daemon started by the agent;
    • stopping and a clean exit delete it.
  • Starting reloads the agent from its file (bootout, bootstrap, kickstart). Stopping deletes the file and sends SIGTERM.

Shared

  • TaskDefinition.arguments is a list now; Windows quotes it for its command line (same output as before, tested).
  • Messages say "systemd user service" or "launch agent" instead of "scheduled task".
  • sign_in_id stays empty off Windows: neither systemd nor launchd starts the daemon at a new sign-in unless the owner chose it, so no new-sign-in check is needed.

Tests

  • Unit tests for the unit file and the plist: quoting, % and $, XML escaping, labels. Both modules build on every Unix, so the launchd tests also run on Linux.
  • tests/service.rs runs against the real systemd and launchd in CI only (BOTLOFT_SERVICE_TEST=1):
    1. install and check the daemon answers;
    2. kill -9 it and see it come back;
    3. service stop, then check it stays down for 15 s;
    4. restart, then uninstall.
  • The Ubuntu runner needs loginctl enable-linger for a user manager.
  • Windows: fmt, clippy, lib and settings tests. Linux (Docker): clippy and tests.

Still missing

On macOS, launchd only kills the daemon's own process group, so a bot's browser outlives a daemon crash. A sweep at start comes next.

Manual test (not done yet)

  • Linux desktop:
    1. service install
    2. Sign out and in with "start with the system" on: Botloft is running.
    3. Turn it off, sign out and in: Botloft is not running.
  • macOS: the same steps. Then kill -9 the daemon and see it back within about 10 s.

What the real service managers showed (fixed here)

  • The daemon's copy in <home>/bin had no execute bit off Windows. It was written with fs::write, so systemd answered 203/EXEC and launchd never started it. It is now 0755, with a test.
  • A launchctl call hung the install for 10 minutes on the macOS runner. Every systemctl and launchctl call now gives up after 30 s and says which one it was.
  • The service test now passes on both runners: install, back after kill -9, stays down after stop, restart, uninstall.
  • macOS tabs test: it failed on the way here because of a separate browser race, fixed in fix(browser): app state before the waiting tool answers #97.

- Linux: a systemd user service. Enabled when Botloft starts with the
  system; Restart=on-failure brings the daemon back after a crash, and
  KillMode=control-group ends its bots and browsers with it.
- macOS: a launch agent. RunAtLoad follows the owner's choice, and
  KeepAlive watches a keep-alive file that starting creates and stopping
  (or a clean exit) deletes, since a plain KeepAlive would also start it
  at every sign-in.
- Task arguments are a list; Windows quotes them for its command line.
- Messages name what starts the daemon on each system.

Spec 14.1 describes both.
…rvice manager

Runs only with BOTLOFT_SERVICE_TEST=1, which CI sets on its Ubuntu runner
(with a lingering user manager) and its macOS runner.
…hen it fails

The step also stops after 10 minutes instead of hanging the runner.
On the macOS runner a launchctl call hung the install for 10 minutes.
Each call now gives up after 30 s and says which one it was. The service
test keeps its data folder under the home folder: the Linux runner's
/tmp is its own, and systemd answered 203/EXEC for a binary there.
It was written with fs::write, which leaves no execute bit: systemd
answered 203/EXEC and launchd never started it.
@httpsphl
httpsphl merged commit d9c1a77 into main Oct 1, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant