Skip to content

Linux

Alfred runs on Linux. The scheduling layer is systemd --user timers, the Linux analogue of macOS launchd per-user agents. install.sh, deploy.sh, bin/doctor.sh, alfred-status, and the alfred CLI all detect the host OS and pick the right path. Supported distros: Debian and Ubuntu (apt).

Full reference: docs/LINUX.md.

The scheduler is the only OS-specific layer

Section titled “The scheduler is the only OS-specific layer”

Everything above the scheduler is portable Python and Bash. The host-scheduler dependency is isolated to the render scripts, the unit templates, and the lib/scheduler.py abstraction (launchd on macOS, systemd --user on Linux). agents.conf is the single source of truth for both.

flowchart TB
    subgraph portable["Portable core (every host)"]
        runner["lib/agent_runner/"]
        bins["bin/{role}.py"]
        tests["tests/"]
        doctor["bin/doctor.sh"]
        sched["lib/scheduler.py<br/><i>launchd / systemd adapter</i>"]
    end

    subgraph mac["macOS (launchd)"]
        lrender["launchd/render.sh"]
        ltmpl["launchd/_template.plist"]
        lctl["launchctl bootstrap"]
    end

    subgraph linux["Linux (systemd --user)"]
        srender["systemd/render.sh"]
        stmpl["_template.service + .timer"]
        sctl["systemctl --user enable"]
    end

    conf["launchd/agents.conf<br/><i>shared source of truth</i>"]
    conf --> lrender
    conf --> srender
    lrender --> ltmpl --> lctl --> bins
    srender --> stmpl --> sctl --> bins
    bins --> runner
    bins --> sched

The port is a real mapping, not a translation shim:

launchdsystemd —userPurpose
~/Library/LaunchAgents/<label>.plist~/.config/systemd/user/<label>.{service,timer}per-agent unit on disk
StartInterval / StartCalendarIntervalOnUnitActiveSec= / OnCalendar=the schedule
RunAtLoad=false + one-shot exitType=oneshotfire-and-forget, no restart loop
launchctl bootstrap / bootoutsystemctl --user enable --now / disable --nowload / unload
launchctl kickstart -ksystemctl --user stop then startone-shot run, killing any in-flight firing
EnvironmentVariables blockEnvironment= linesper-agent env without polluting the shell

Rendered systemd units use the %h specifier in place of your literal home directory, so a unit file is host-agnostic.

Terminal window
git clone https://github.com/luminik-io/alfred-os.git ~/code/alfred-os
cd ~/code/alfred-os
bash install.sh

On Linux, install.sh confirms the host is Debian/Ubuntu (/etc/os-release), apt-get installs the prerequisites, installs uv from the official installer (and uses it to provision Python 3.11 if the distro default is newer), runs npm install -g @anthropic-ai/claude-code, then seeds $ALFRED_HOME, $WORKSPACE_ROOT, and ~/.alfredrc.

AWS CLI v2 is not auto-installed; apt ships v1.x. Install it from Amazon if your fleet jobs touch AWS.

Terminal window
bash deploy.sh

On Linux, deploy.sh copies lib/ and bin/ into $ALFRED_HOME, renders systemd/ units from agents.conf, reaps units for removed rows, copies the units into ~/.config/systemd/user/, runs daemon-reload, and enable --nows each timer, skipping any agent whose pause marker is set.

The alfred CLI is OS-agnostic; the same verbs work on Linux:

Terminal window
alfred agents # roster, with a systemd-load column
alfred pause lucius # disable the timer, write the pause marker
alfred resume lucius # clear the marker, re-enable the timer
alfred run lucius # one-shot run now
alfred status # health snapshot from the systemd timer roster
bash bin/doctor.sh --dev # preflight; --dev tolerates host-config gaps

Raw systemctl --user list-timers and journalctl --user -u <label> still work if you prefer them.

linger: keeping the fleet alive across logout

Section titled “linger: keeping the fleet alive across logout”

systemd --user units only run while the user has an active session unless linger is enabled. For an always-on agent host, enable it once:

Terminal window
sudo loginctl enable-linger "$USER"

This is the one piece deploy.sh does not do for you. It needs sudo, so Alfred leaves it as an explicit decision.

  • WSL2: same path as Linux on distros that enable systemd (recent Ubuntu does by default). Keep WORKSPACE_ROOT on a Linux path, not /mnt/c/.... Cross-filesystem worktrees are slow and Windows file-locking confuses git worktree.
  • Docker: still not container-friendly. The host-scheduler assumption means hosting the scheduler outside the container. Running individual agents inside containers is fine, though: point your codename’s bin/<name>.py at docker run --rm ... claude -p ....

alfred claude works on Linux too. It switches the Claude Code account by setting CLAUDE_CONFIG_DIR in the systemd --user manager environment with systemctl --user set-environment. Already-running services keep the environment they started with, so restart the affected timer or service after a switch:

Terminal window
alfred claude secondary
systemctl --user restart my.fleet.lucius.timer

If you prefer static routing, set CLAUDE_CONFIG_DIR directly in ~/.alfredrc; it flows into rendered units through agent-launch.