Skip to content

installer

installer

Install the clearance hub + verdict helper systemd user units.

The clearance flow splits across two units:

  • terok-clearance-hub.service — varlink server, subscriber fan-out, authz binding. Hardened (NNP + seccomp + mount-ns isolation).
  • terok-clearance-verdict.service — stateless helper, execs terok-shield allow|deny. Unhardened (podman setns requires it).

Both run the same terok-clearance-hub launcher with different subcommands (serve vs serve-verdict), so install_service takes one bin_path and writes both units.

Legacy migration: earlier releases shipped one monolithic terok-dbus.service. On first post-split install the legacy unit is disabled + unlinked before the new pair goes down, so operators don't end up running two hubs against the same socket.

HUB_UNIT_NAME = 'terok-clearance-hub.service' module-attribute

VERDICT_UNIT_NAME = 'terok-clearance-verdict.service' module-attribute

NOTIFIER_UNIT_NAME = 'terok-clearance-notifier.service' module-attribute

UNIT_NAME = HUB_UNIT_NAME module-attribute

install_service(bin_path=None)

Render + write both unit files into the user systemd directory.

Also disables + unlinks any leftover pre-split terok-dbus.service so the operator ends up with exactly the new pair running. Calls systemctl --user daemon-reload once at the end.

Parameters:

Name Type Description Default
bin_path Path | list[str] | None

Path to the terok-clearance-hub launcher, or a list[str] argv. None (the default) renders python -m terok_clearance.cli.main against the running interpreter — the shape pipx installs use — so callers don't need to spell clearance's own module layout.

None

Returns:

Type Description
Path

(hub_path, verdict_path) — the on-disk paths of the two

Path

unit files.

Source code in src/terok_clearance/runtime/installer.py
def install_service(bin_path: Path | list[str] | None = None) -> tuple[Path, Path]:
    """Render + write both unit files into the user systemd directory.

    Also disables + unlinks any leftover pre-split ``terok-dbus.service``
    so the operator ends up with exactly the new pair running.  Calls
    ``systemctl --user daemon-reload`` once at the end.

    Args:
        bin_path: ``Path`` to the ``terok-clearance-hub`` launcher, or
            a ``list[str]`` argv.  ``None`` (the default) renders
            ``python -m terok_clearance.cli.main`` against the running
            interpreter — the shape pipx installs use — so callers
            don't need to spell clearance's own module layout.

    Returns:
        ``(hub_path, verdict_path)`` — the on-disk paths of the two
        unit files.
    """
    _uninstall_legacy()
    bin_rendered = _render_exec_start(bin_path if bin_path is not None else list(_DEFAULT_HUB_ARGV))
    dest_dir = _user_systemd_dir()
    dest_dir.mkdir(parents=True, exist_ok=True)
    paths: list[Path] = []
    for unit_name, _marker in _UNITS:
        template = _read_template(unit_name)
        rendered = template.replace("{{UNIT_VERSION}}", str(_PAIR_UNIT_VERSION)).replace(
            "{{BIN}}", bin_rendered
        )
        dest = dest_dir / unit_name
        dest.write_text(rendered)
        paths.append(dest)
    _daemon_reload()
    return paths[0], paths[1]

uninstall_service()

Disable + unlink both new units + any pre-split legacy leftover.

Symmetric teardown for install_serviceterok uninstall calls this instead of rolling its own systemctl + unlink sequence. Daemon-reloads once at the end so systemd's in-memory registry drops the now-missing units. All individual steps soft-fail so a half-installed tree still ends up clean.

Source code in src/terok_clearance/runtime/installer.py
def uninstall_service() -> None:
    """Disable + unlink both new units + any pre-split legacy leftover.

    Symmetric teardown for [`install_service`][terok_clearance.runtime.installer.install_service] — ``terok uninstall``
    calls this instead of rolling its own systemctl + unlink sequence.
    Daemon-reloads once at the end so systemd's in-memory registry
    drops the now-missing units.  All individual steps soft-fail so a
    half-installed tree still ends up clean.
    """
    for name in (HUB_UNIT_NAME, VERDICT_UNIT_NAME, _LEGACY_UNIT_NAME):
        _disable_and_unlink(name)
    _daemon_reload()

install_notifier_service(bin_path=None)

Render + write the notifier unit into the user systemd directory.

Paired with install_service: headless hosts that installed the hub + verdict pair can opt into the desktop notifier later by calling only this function. Daemon-reloads once at the end.

Parameters:

Name Type Description Default
bin_path Path | list[str] | None

Path to the notifier launcher, or a list[str] argv. None (the default) renders python -m terok_clearance.notifier.app against the running interpreter — same rationale as install_service.

None

Returns:

Type Description
Path

The on-disk path of the written unit file.

Source code in src/terok_clearance/runtime/installer.py
def install_notifier_service(bin_path: Path | list[str] | None = None) -> Path:
    """Render + write the notifier unit into the user systemd directory.

    Paired with [`install_service`][terok_clearance.runtime.installer.install_service]: headless hosts that installed
    the hub + verdict pair can opt into the desktop notifier later by
    calling only this function.  Daemon-reloads once at the end.

    Args:
        bin_path: ``Path`` to the notifier launcher, or a ``list[str]``
            argv.  ``None`` (the default) renders
            ``python -m terok_clearance.notifier.app`` against the
            running interpreter — same rationale as [`install_service`][terok_clearance.runtime.installer.install_service].

    Returns:
        The on-disk path of the written unit file.
    """
    bin_rendered = _render_exec_start(
        bin_path if bin_path is not None else list(_DEFAULT_NOTIFIER_ARGV)
    )
    dest_dir = _user_systemd_dir()
    dest_dir.mkdir(parents=True, exist_ok=True)
    template = _read_template(NOTIFIER_UNIT_NAME)
    rendered = template.replace("{{UNIT_VERSION}}", str(_NOTIFIER_UNIT_VERSION)).replace(
        "{{BIN}}", bin_rendered
    )
    dest = dest_dir / NOTIFIER_UNIT_NAME
    dest.write_text(rendered)
    _daemon_reload()
    return dest

uninstall_notifier_service()

Disable + unlink the notifier unit; daemon-reload once.

Symmetric teardown for install_notifier_service. Soft-fail on every step so a half-installed tree still ends up clean.

Source code in src/terok_clearance/runtime/installer.py
def uninstall_notifier_service() -> None:
    """Disable + unlink the notifier unit; daemon-reload once.

    Symmetric teardown for [`install_notifier_service`][terok_clearance.runtime.installer.install_notifier_service].  Soft-fail
    on every step so a half-installed tree still ends up clean.
    """
    _disable_and_unlink(NOTIFIER_UNIT_NAME)
    _daemon_reload()

read_installed_unit()

Return the hub unit's file contents, or None if absent.

Kept for backwards compatibility with out-of-tree callers that grew used to the pre-split single-unit API — reads the hub unit (the one that was formerly terok-dbus.service).

Source code in src/terok_clearance/runtime/installer.py
def read_installed_unit() -> str | None:
    """Return the hub unit's file contents, or ``None`` if absent.

    Kept for backwards compatibility with out-of-tree callers that
    grew used to the pre-split single-unit API — reads the hub unit
    (the one that was formerly ``terok-dbus.service``).
    """
    path = _user_systemd_dir() / HUB_UNIT_NAME
    try:
        return path.read_text()
    except OSError:
        return None

read_installed_unit_version()

Return the hub unit's # terok-clearance-hub-version: stamp, or None.

None is either "unit not installed" or "unit installed without a marker" (the pre-split legacy unit) — check_units_outdated differentiates between those in its operator-facing message.

Source code in src/terok_clearance/runtime/installer.py
def read_installed_unit_version() -> int | None:
    """Return the hub unit's ``# terok-clearance-hub-version:`` stamp, or ``None``.

    ``None`` is either "unit not installed" or "unit installed without
    a marker" (the pre-split legacy unit) — ``check_units_outdated``
    differentiates between those in its operator-facing message.
    """
    return _version_for(HUB_UNIT_NAME, _HUB[1])

check_units_outdated()

Return a one-line drift warning if any installed unit is stale, else None.

Checks hub + verdict together (they're installed as a pair by install_service) plus the notifier independently (headless hosts may install it later, or not at all). None is returned when neither pair nor notifier is installed (headless host, or no setup command has run yet); a one-sided hub/verdict pair is reported as stale so the operator is prompted to restore it. A legacy terok-dbus.service on disk counts as "stale" so the operator is prompted to rerun setup and get the split pair.

Source code in src/terok_clearance/runtime/installer.py
def check_units_outdated() -> str | None:
    """Return a one-line drift warning if any installed unit is stale, else ``None``.

    Checks hub + verdict together (they're installed as a pair by
    [`install_service`][terok_clearance.runtime.installer.install_service]) plus the notifier independently (headless
    hosts may install it later, or not at all).  ``None`` is returned
    when neither pair nor notifier is installed (headless host, or
    no setup command has run yet); a one-sided hub/verdict pair is
    reported as stale so the operator is prompted to restore it.  A
    legacy ``terok-dbus.service`` on disk counts as "stale" so the
    operator is prompted to rerun setup and get the split pair.
    """
    legacy = _user_systemd_dir() / _LEGACY_UNIT_NAME
    if legacy.is_file():
        return (
            f"{_LEGACY_UNIT_NAME} is from a pre-split release — "
            f"{_RERUN_HINT} to migrate to the hub/verdict pair."
        )
    if (verdict := _check_pair_outdated()) is not None:
        return verdict
    return _check_notifier_outdated()