← Back to PRs

#19802: feat(daemon): support system-level systemd services via OPENCLAW_SYSTEMD_SYSTEM

by gabrielrinaldi open 2026-02-18 05:42 View on GitHub →
gateway commands size: S
## Summary When running OpenClaw as a system-level systemd service (e.g. on NixOS with a dedicated `openclaw` user), the user D-Bus session is unavailable. This causes `openclaw status` to incorrectly report "systemd not installed" and breaks all daemon management commands. This PR adds a `OPENCLAW_SYSTEMD_SYSTEM=1` environment variable that switches all `systemctl` calls from `--user` to system scope. ## Changes - **`src/daemon/systemd.ts`**: Add `isSystemLevelService()` and `systemctlScope()` helpers. All 14 `--user` call sites now go through `systemctlScope()`. Availability checks handle exit code 3 (no units loaded) as success for system-level services. Unit path resolves to `/etc/systemd/system/` instead of `~/.config/systemd/user/`. - **`src/commands/systemd-linger.ts`**: Skip linger management when running as system service (not applicable). - **`src/daemon/systemd.test.ts`**: Add tests for `isSystemLevelService`, system-level availability, and system-level unit path resolution. ## Usage ```bash # In your systemd service environment or shell profile: export OPENCLAW_SYSTEMD_SYSTEM=1 export OPENCLAW_SYSTEMD_UNIT=openclaw # if unit name differs from default openclaw status # now correctly detects system-level service ``` Works alongside the existing `OPENCLAW_SYSTEMD_UNIT` env var for custom unit names. ## AI assisted Driven and tested by me, but AI assisted on finding all places that needed change. Changes were discussed and reviewed by me. Although this is not my primary coding language. ## Testing - All 18 tests in src/daemon/systemd.test.ts pass - TypeScript typecheck clean (no new errors) - Tested on NixOS with system-level openclaw.service running as dedicated user ## Non related changes I had to include: - apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift - apps/macos/Sources/OpenClawProtocol/GatewayModels.swift So CI would pass, this seems like something is missing upstream and not part of my changes

Most Similar PRs