Skip to content

Builders API

Fluent builders for creating systemd unit files. Each builder follows a chain-style API: call methods to set directives, then .build() to validate and render a UnitFile.

ServiceBuilder

systemd_client.builders.ServiceBuilder

Bases: _BaseBuilder

Fluent builder for systemd service unit files.

Source code in src/systemd_client/builders/_service.py
class ServiceBuilder(_BaseBuilder):
    """Fluent builder for systemd service unit files."""

    def __init__(self, name: str, *, template: bool = False) -> None:
        super().__init__(name, UnitType.SERVICE, template=template)

    # ── [Service] section ───────────────────────────────────────

    def type_(self, svc_type: str) -> Self:
        self._set(self._type_section, "Type", svc_type)
        return self

    def exec_start(self, cmd: str) -> Self:
        self._set(self._type_section, "ExecStart", cmd)
        return self

    def exec_start_pre(self, cmd: str) -> Self:
        self._append(self._type_section, "ExecStartPre", cmd)
        return self

    def exec_start_post(self, cmd: str) -> Self:
        self._append(self._type_section, "ExecStartPost", cmd)
        return self

    def exec_stop(self, cmd: str) -> Self:
        self._set(self._type_section, "ExecStop", cmd)
        return self

    def exec_stop_post(self, cmd: str) -> Self:
        self._append(self._type_section, "ExecStopPost", cmd)
        return self

    def exec_reload(self, cmd: str) -> Self:
        self._set(self._type_section, "ExecReload", cmd)
        return self

    def restart(self, policy: str) -> Self:
        self._set(self._type_section, "Restart", policy)
        return self

    def restart_sec(self, seconds: int | float) -> Self:
        self._set(self._type_section, "RestartSec", str(seconds))
        return self

    def timeout_start_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "TimeoutStartSec", str(seconds))
        return self

    def timeout_stop_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "TimeoutStopSec", str(seconds))
        return self

    def watchdog_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "WatchdogSec", str(seconds))
        return self

    def user(self, name: str) -> Self:
        self._set(self._type_section, "User", name)
        return self

    def group(self, name: str) -> Self:
        self._set(self._type_section, "Group", name)
        return self

    def working_directory(self, path: str) -> Self:
        self._set(self._type_section, "WorkingDirectory", path)
        return self

    def environment(self, env: dict[str, str]) -> Self:
        quoted = " ".join(f'"{k}={v}"' for k, v in env.items())
        self._set(self._type_section, "Environment", quoted)
        return self

    def environment_file(self, path: str) -> Self:
        self._append(self._type_section, "EnvironmentFile", path)
        return self

    def standard_output(self, target: str) -> Self:
        self._set(self._type_section, "StandardOutput", target)
        return self

    def standard_error(self, target: str) -> Self:
        self._set(self._type_section, "StandardError", target)
        return self

    def runtime_directory(self, name: str) -> Self:
        self._set(self._type_section, "RuntimeDirectory", name)
        return self

    def state_directory(self, name: str) -> Self:
        self._set(self._type_section, "StateDirectory", name)
        return self

    def syslog_identifier(self, name: str) -> Self:
        self._set(self._type_section, "SyslogIdentifier", name)
        return self

    def remain_after_exit(self, val: bool) -> Self:
        self._set(self._type_section, "RemainAfterExit", "true" if val else "false")
        return self

    def pid_file(self, path: str) -> Self:
        self._set(self._type_section, "PIDFile", path)
        return self

    def nice(self, n: int) -> Self:
        self._set(self._type_section, "Nice", str(n))
        return self

    def limit_nofile(self, n: int) -> Self:
        self._set(self._type_section, "LimitNOFILE", str(n))
        return self

    def private_tmp(self, val: bool) -> Self:
        self._set(self._type_section, "PrivateTmp", "true" if val else "false")
        return self

    def protect_system(self, val: str) -> Self:
        self._set(self._type_section, "ProtectSystem", val)
        return self

    def protect_home(self, val: str) -> Self:
        self._set(self._type_section, "ProtectHome", val)
        return self

    def dynamic_user(self, val: bool) -> Self:
        self._set(self._type_section, "DynamicUser", "true" if val else "false")
        return self

    # ── Validation ──────────────────────────────────────────────

    def _validate(self) -> None:
        errors: list[str] = []

        if "ExecStart" not in self._type_section:
            errors.append("ExecStart is required for service units")

        wd = self._type_section.get("WorkingDirectory")
        if wd:
            err = validate_absolute_path(wd[0], "WorkingDirectory")
            if err:
                errors.append(err)

        self._raise_validation(errors)

__init__(name, *, template=False)

Source code in src/systemd_client/builders/_service.py
def __init__(self, name: str, *, template: bool = False) -> None:
    super().__init__(name, UnitType.SERVICE, template=template)

type_(svc_type)

Source code in src/systemd_client/builders/_service.py
def type_(self, svc_type: str) -> Self:
    self._set(self._type_section, "Type", svc_type)
    return self

exec_start(cmd)

Source code in src/systemd_client/builders/_service.py
def exec_start(self, cmd: str) -> Self:
    self._set(self._type_section, "ExecStart", cmd)
    return self

exec_start_pre(cmd)

Source code in src/systemd_client/builders/_service.py
def exec_start_pre(self, cmd: str) -> Self:
    self._append(self._type_section, "ExecStartPre", cmd)
    return self

exec_start_post(cmd)

Source code in src/systemd_client/builders/_service.py
def exec_start_post(self, cmd: str) -> Self:
    self._append(self._type_section, "ExecStartPost", cmd)
    return self

exec_stop(cmd)

Source code in src/systemd_client/builders/_service.py
def exec_stop(self, cmd: str) -> Self:
    self._set(self._type_section, "ExecStop", cmd)
    return self

exec_reload(cmd)

Source code in src/systemd_client/builders/_service.py
def exec_reload(self, cmd: str) -> Self:
    self._set(self._type_section, "ExecReload", cmd)
    return self

restart(policy)

Source code in src/systemd_client/builders/_service.py
def restart(self, policy: str) -> Self:
    self._set(self._type_section, "Restart", policy)
    return self

restart_sec(seconds)

Source code in src/systemd_client/builders/_service.py
def restart_sec(self, seconds: int | float) -> Self:
    self._set(self._type_section, "RestartSec", str(seconds))
    return self

watchdog_sec(seconds)

Source code in src/systemd_client/builders/_service.py
def watchdog_sec(self, seconds: int) -> Self:
    self._set(self._type_section, "WatchdogSec", str(seconds))
    return self

user(name)

Source code in src/systemd_client/builders/_service.py
def user(self, name: str) -> Self:
    self._set(self._type_section, "User", name)
    return self

group(name)

Source code in src/systemd_client/builders/_service.py
def group(self, name: str) -> Self:
    self._set(self._type_section, "Group", name)
    return self

working_directory(path)

Source code in src/systemd_client/builders/_service.py
def working_directory(self, path: str) -> Self:
    self._set(self._type_section, "WorkingDirectory", path)
    return self

environment(env)

Source code in src/systemd_client/builders/_service.py
def environment(self, env: dict[str, str]) -> Self:
    quoted = " ".join(f'"{k}={v}"' for k, v in env.items())
    self._set(self._type_section, "Environment", quoted)
    return self

environment_file(path)

Source code in src/systemd_client/builders/_service.py
def environment_file(self, path: str) -> Self:
    self._append(self._type_section, "EnvironmentFile", path)
    return self

standard_output(target)

Source code in src/systemd_client/builders/_service.py
def standard_output(self, target: str) -> Self:
    self._set(self._type_section, "StandardOutput", target)
    return self

standard_error(target)

Source code in src/systemd_client/builders/_service.py
def standard_error(self, target: str) -> Self:
    self._set(self._type_section, "StandardError", target)
    return self

runtime_directory(name)

Source code in src/systemd_client/builders/_service.py
def runtime_directory(self, name: str) -> Self:
    self._set(self._type_section, "RuntimeDirectory", name)
    return self

state_directory(name)

Source code in src/systemd_client/builders/_service.py
def state_directory(self, name: str) -> Self:
    self._set(self._type_section, "StateDirectory", name)
    return self

syslog_identifier(name)

Source code in src/systemd_client/builders/_service.py
def syslog_identifier(self, name: str) -> Self:
    self._set(self._type_section, "SyslogIdentifier", name)
    return self

remain_after_exit(val)

Source code in src/systemd_client/builders/_service.py
def remain_after_exit(self, val: bool) -> Self:
    self._set(self._type_section, "RemainAfterExit", "true" if val else "false")
    return self

private_tmp(val)

Source code in src/systemd_client/builders/_service.py
def private_tmp(self, val: bool) -> Self:
    self._set(self._type_section, "PrivateTmp", "true" if val else "false")
    return self

protect_system(val)

Source code in src/systemd_client/builders/_service.py
def protect_system(self, val: str) -> Self:
    self._set(self._type_section, "ProtectSystem", val)
    return self

protect_home(val)

Source code in src/systemd_client/builders/_service.py
def protect_home(self, val: str) -> Self:
    self._set(self._type_section, "ProtectHome", val)
    return self

dynamic_user(val)

Source code in src/systemd_client/builders/_service.py
def dynamic_user(self, val: bool) -> Self:
    self._set(self._type_section, "DynamicUser", "true" if val else "false")
    return self

nice(n)

Source code in src/systemd_client/builders/_service.py
def nice(self, n: int) -> Self:
    self._set(self._type_section, "Nice", str(n))
    return self

limit_nofile(n)

Source code in src/systemd_client/builders/_service.py
def limit_nofile(self, n: int) -> Self:
    self._set(self._type_section, "LimitNOFILE", str(n))
    return self

TimerBuilder

systemd_client.builders.TimerBuilder

Bases: _BaseBuilder

Fluent builder for systemd timer unit files.

Source code in src/systemd_client/builders/_timer.py
class TimerBuilder(_BaseBuilder):
    """Fluent builder for systemd timer unit files."""

    def __init__(self, name: str, *, template: bool = False) -> None:
        super().__init__(name, UnitType.TIMER, template=template)

    # ── [Timer] section ─────────────────────────────────────────

    def on_calendar(self, spec: str) -> Self:
        self._set(self._type_section, "OnCalendar", spec)
        return self

    def on_boot_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "OnBootSec", str(seconds))
        return self

    def on_startup_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "OnStartupSec", str(seconds))
        return self

    def on_unit_active_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "OnUnitActiveSec", str(seconds))
        return self

    def on_unit_inactive_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "OnUnitInactiveSec", str(seconds))
        return self

    def on_active_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "OnActiveSec", str(seconds))
        return self

    def persistent(self, val: bool) -> Self:
        self._set(self._type_section, "Persistent", "true" if val else "false")
        return self

    def accuracy_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "AccuracySec", str(seconds))
        return self

    def randomized_delay_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "RandomizedDelaySec", str(seconds))
        return self

    def unit(self, name: str) -> Self:
        self._set(self._type_section, "Unit", name)
        return self

    # ── Validation ──────────────────────────────────────────────

    def _validate(self) -> None:
        errors: list[str] = []

        has_trigger = any(k in self._type_section for k in _TIME_TRIGGERS)
        if not has_trigger:
            errors.append(
                "At least one time trigger is required "
                "(OnCalendar, OnBootSec, OnStartupSec, etc.)"
            )

        self._raise_validation(errors)

__init__(name, *, template=False)

Source code in src/systemd_client/builders/_timer.py
def __init__(self, name: str, *, template: bool = False) -> None:
    super().__init__(name, UnitType.TIMER, template=template)

on_calendar(spec)

Source code in src/systemd_client/builders/_timer.py
def on_calendar(self, spec: str) -> Self:
    self._set(self._type_section, "OnCalendar", spec)
    return self

on_boot_sec(seconds)

Source code in src/systemd_client/builders/_timer.py
def on_boot_sec(self, seconds: int) -> Self:
    self._set(self._type_section, "OnBootSec", str(seconds))
    return self

on_startup_sec(seconds)

Source code in src/systemd_client/builders/_timer.py
def on_startup_sec(self, seconds: int) -> Self:
    self._set(self._type_section, "OnStartupSec", str(seconds))
    return self

on_unit_active_sec(seconds)

Source code in src/systemd_client/builders/_timer.py
def on_unit_active_sec(self, seconds: int) -> Self:
    self._set(self._type_section, "OnUnitActiveSec", str(seconds))
    return self

on_unit_inactive_sec(seconds)

Source code in src/systemd_client/builders/_timer.py
def on_unit_inactive_sec(self, seconds: int) -> Self:
    self._set(self._type_section, "OnUnitInactiveSec", str(seconds))
    return self

on_active_sec(seconds)

Source code in src/systemd_client/builders/_timer.py
def on_active_sec(self, seconds: int) -> Self:
    self._set(self._type_section, "OnActiveSec", str(seconds))
    return self

persistent(val)

Source code in src/systemd_client/builders/_timer.py
def persistent(self, val: bool) -> Self:
    self._set(self._type_section, "Persistent", "true" if val else "false")
    return self

accuracy_sec(seconds)

Source code in src/systemd_client/builders/_timer.py
def accuracy_sec(self, seconds: int) -> Self:
    self._set(self._type_section, "AccuracySec", str(seconds))
    return self

randomized_delay_sec(seconds)

Source code in src/systemd_client/builders/_timer.py
def randomized_delay_sec(self, seconds: int) -> Self:
    self._set(self._type_section, "RandomizedDelaySec", str(seconds))
    return self

unit(name)

Source code in src/systemd_client/builders/_timer.py
def unit(self, name: str) -> Self:
    self._set(self._type_section, "Unit", name)
    return self

SocketBuilder

systemd_client.builders.SocketBuilder

Bases: _BaseBuilder

Fluent builder for systemd socket unit files.

Source code in src/systemd_client/builders/_socket.py
class SocketBuilder(_BaseBuilder):
    """Fluent builder for systemd socket unit files."""

    def __init__(self, name: str, *, template: bool = False) -> None:
        super().__init__(name, UnitType.SOCKET, template=template)

    # ── [Socket] section ────────────────────────────────────────

    def listen_stream(self, addr: int | str) -> Self:
        self._set(self._type_section, "ListenStream", str(addr))
        return self

    def listen_datagram(self, addr: int | str) -> Self:
        self._set(self._type_section, "ListenDatagram", str(addr))
        return self

    def listen_sequential_packet(self, addr: int | str) -> Self:
        self._set(self._type_section, "ListenSequentialPacket", str(addr))
        return self

    def listen_fifo(self, path: str) -> Self:
        self._set(self._type_section, "ListenFIFO", path)
        return self

    def accept(self, val: bool) -> Self:
        self._set(self._type_section, "Accept", "true" if val else "false")
        return self

    def socket_user(self, name: str) -> Self:
        self._set(self._type_section, "SocketUser", name)
        return self

    def socket_group(self, name: str) -> Self:
        self._set(self._type_section, "SocketGroup", name)
        return self

    def socket_mode(self, mode: str) -> Self:
        self._set(self._type_section, "SocketMode", mode)
        return self

    def service(self, name: str) -> Self:
        self._set(self._type_section, "Service", name)
        return self

    def max_connections(self, n: int) -> Self:
        self._set(self._type_section, "MaxConnections", str(n))
        return self

    def keep_alive(self, val: bool) -> Self:
        self._set(self._type_section, "KeepAlive", "true" if val else "false")
        return self

    # ── Validation ──────────────────────────────────────────────

    def _validate(self) -> None:
        errors: list[str] = []

        has_listen = any(k in self._type_section for k in _LISTEN_KEYS)
        if not has_listen:
            errors.append(
                "At least one listen directive is required "
                "(ListenStream, ListenDatagram, etc.)"
            )

        self._raise_validation(errors)

__init__(name, *, template=False)

Source code in src/systemd_client/builders/_socket.py
def __init__(self, name: str, *, template: bool = False) -> None:
    super().__init__(name, UnitType.SOCKET, template=template)

listen_stream(addr)

Source code in src/systemd_client/builders/_socket.py
def listen_stream(self, addr: int | str) -> Self:
    self._set(self._type_section, "ListenStream", str(addr))
    return self

listen_datagram(addr)

Source code in src/systemd_client/builders/_socket.py
def listen_datagram(self, addr: int | str) -> Self:
    self._set(self._type_section, "ListenDatagram", str(addr))
    return self

listen_sequential_packet(addr)

Source code in src/systemd_client/builders/_socket.py
def listen_sequential_packet(self, addr: int | str) -> Self:
    self._set(self._type_section, "ListenSequentialPacket", str(addr))
    return self

listen_fifo(path)

Source code in src/systemd_client/builders/_socket.py
def listen_fifo(self, path: str) -> Self:
    self._set(self._type_section, "ListenFIFO", path)
    return self

accept(val)

Source code in src/systemd_client/builders/_socket.py
def accept(self, val: bool) -> Self:
    self._set(self._type_section, "Accept", "true" if val else "false")
    return self

socket_user(name)

Source code in src/systemd_client/builders/_socket.py
def socket_user(self, name: str) -> Self:
    self._set(self._type_section, "SocketUser", name)
    return self

socket_group(name)

Source code in src/systemd_client/builders/_socket.py
def socket_group(self, name: str) -> Self:
    self._set(self._type_section, "SocketGroup", name)
    return self

socket_mode(mode)

Source code in src/systemd_client/builders/_socket.py
def socket_mode(self, mode: str) -> Self:
    self._set(self._type_section, "SocketMode", mode)
    return self

service(name)

Source code in src/systemd_client/builders/_socket.py
def service(self, name: str) -> Self:
    self._set(self._type_section, "Service", name)
    return self

max_connections(n)

Source code in src/systemd_client/builders/_socket.py
def max_connections(self, n: int) -> Self:
    self._set(self._type_section, "MaxConnections", str(n))
    return self

keep_alive(val)

Source code in src/systemd_client/builders/_socket.py
def keep_alive(self, val: bool) -> Self:
    self._set(self._type_section, "KeepAlive", "true" if val else "false")
    return self

PathBuilder

systemd_client.builders.PathBuilder

Bases: _BaseBuilder

Fluent builder for systemd path unit files.

Source code in src/systemd_client/builders/_path.py
class PathBuilder(_BaseBuilder):
    """Fluent builder for systemd path unit files."""

    def __init__(self, name: str, *, template: bool = False) -> None:
        super().__init__(name, UnitType.PATH, template=template)

    # ── [Path] section ──────────────────────────────────────────

    def path_exists(self, path: str) -> Self:
        self._append(self._type_section, "PathExists", path)
        return self

    def path_exists_glob(self, pattern: str) -> Self:
        self._append(self._type_section, "PathExistsGlob", pattern)
        return self

    def path_changed(self, path: str) -> Self:
        self._append(self._type_section, "PathChanged", path)
        return self

    def path_modified(self, path: str) -> Self:
        self._append(self._type_section, "PathModified", path)
        return self

    def directory_not_empty(self, path: str) -> Self:
        self._append(self._type_section, "DirectoryNotEmpty", path)
        return self

    def unit(self, name: str) -> Self:
        self._set(self._type_section, "Unit", name)
        return self

    def make_directory(self, val: bool) -> Self:
        self._set(self._type_section, "MakeDirectory", "true" if val else "false")
        return self

    def trigger_limit_interval_sec(self, seconds: int) -> Self:
        self._set(self._type_section, "TriggerLimitIntervalSec", str(seconds))
        return self

    def trigger_limit_burst(self, n: int) -> Self:
        self._set(self._type_section, "TriggerLimitBurst", str(n))
        return self

    # ── Validation ──────────────────────────────────────────────

    def _validate(self) -> None:
        errors: list[str] = []

        has_path = any(k in self._type_section for k in _PATH_DIRECTIVES)
        if not has_path:
            errors.append(
                "At least one path directive is required "
                "(PathExists, PathChanged, PathModified, DirectoryNotEmpty)"
            )

        # Validate absolute paths
        for key in ("PathExists", "PathChanged", "PathModified", "DirectoryNotEmpty"):
            for val in self._type_section.get(key, []):
                err = validate_absolute_path(val, key)
                if err:
                    errors.append(err)

        self._raise_validation(errors)

__init__(name, *, template=False)

Source code in src/systemd_client/builders/_path.py
def __init__(self, name: str, *, template: bool = False) -> None:
    super().__init__(name, UnitType.PATH, template=template)

path_exists(path)

Source code in src/systemd_client/builders/_path.py
def path_exists(self, path: str) -> Self:
    self._append(self._type_section, "PathExists", path)
    return self

path_exists_glob(pattern)

Source code in src/systemd_client/builders/_path.py
def path_exists_glob(self, pattern: str) -> Self:
    self._append(self._type_section, "PathExistsGlob", pattern)
    return self

path_changed(path)

Source code in src/systemd_client/builders/_path.py
def path_changed(self, path: str) -> Self:
    self._append(self._type_section, "PathChanged", path)
    return self

path_modified(path)

Source code in src/systemd_client/builders/_path.py
def path_modified(self, path: str) -> Self:
    self._append(self._type_section, "PathModified", path)
    return self

directory_not_empty(path)

Source code in src/systemd_client/builders/_path.py
def directory_not_empty(self, path: str) -> Self:
    self._append(self._type_section, "DirectoryNotEmpty", path)
    return self

unit(name)

Source code in src/systemd_client/builders/_path.py
def unit(self, name: str) -> Self:
    self._set(self._type_section, "Unit", name)
    return self

make_directory(val)

Source code in src/systemd_client/builders/_path.py
def make_directory(self, val: bool) -> Self:
    self._set(self._type_section, "MakeDirectory", "true" if val else "false")
    return self

trigger_limit_interval_sec(seconds)

Source code in src/systemd_client/builders/_path.py
def trigger_limit_interval_sec(self, seconds: int) -> Self:
    self._set(self._type_section, "TriggerLimitIntervalSec", str(seconds))
    return self

trigger_limit_burst(n)

Source code in src/systemd_client/builders/_path.py
def trigger_limit_burst(self, n: int) -> Self:
    self._set(self._type_section, "TriggerLimitBurst", str(n))
    return self