"""Base Host interface for both local and remote command execution.""" import logging from abc import abstractmethod from collections.abc import Iterator from contextlib import contextmanager from typing import Protocol from clan_lib.cmd import CmdOut, RunOpts cmdlog = logging.getLogger(__name__) class Host(Protocol): """ Abstract base class for host command execution. This provides a common interface for both local and remote hosts. """ @property def address(self) -> str: """Return the address of the host.""" msg = "Subclasses must implement address property" raise NotImplementedError(msg) @property def command_prefix(self) -> str | None: ... @property @abstractmethod def target(self) -> str: """Return a descriptive target string for this host.""" @property @abstractmethod def user(self) -> str: """Return the user for this host.""" @abstractmethod def run( self, cmd: list[str], opts: RunOpts | None = None, extra_env: dict[str, str] | None = None, tty: bool = False, verbose_ssh: bool = False, quiet: bool = False, control_master: bool = True, ) -> CmdOut: """ Run a command on the host. Args: cmd: Command to execute opts: Run options extra_env: Additional environment variables tty: Whether to allocate a TTY (for remote hosts) verbose_ssh: Enable verbose SSH output (for remote hosts) quiet: Suppress command logging control_master: Use SSH ControlMaster (for remote hosts) Returns: Command output """ @contextmanager @abstractmethod def become_root(self) -> Iterator["Host"]: """ Context manager to execute commands as root. """ @contextmanager @abstractmethod def host_connection(self) -> Iterator["Host"]: """ Context manager to manage host connections. For remote hosts, this manages SSH ControlMaster connections. For local hosts, this is a no-op that returns self. """ @abstractmethod def nix_ssh_env( self, env: dict[str, str] | None = None, control_master: bool = True, ) -> dict[str, str]: """ Get environment variables for Nix operations. Remote hosts will add NIX_SSHOPTS, local hosts won't. """