diff --git a/pmb/init/run.py b/pmb/init/run.py new file mode 100644 index 00000000..4ee83204 --- /dev/null +++ b/pmb/init/run.py @@ -0,0 +1,27 @@ +# Copyright 2025 Casey Connolly +# SPDX-License-Identifier: GPL-3.0-or-later + +# Wrappers for running commands prior to pmbootstrap init/unshare + +from pmb.init.sudo import which_sudo +import subprocess +import shlex + + +def sudo(cmd: list[str]) -> list[str]: + """ + Prefix with "sudo --" unless already running as root + """ + sudo = which_sudo() + if not sudo: + return cmd + + return [sudo, "--", *[shlex.quote(x) for x in cmd]] + + +def run_root(cmd: list[str]) -> tuple[str, str]: + """ + Run a command as root and get stdout/stderr result + """ + proc = subprocess.run(sudo(cmd), capture_output=True) + return (proc.stdout, proc.stderr) diff --git a/pmb/init/sudo.py b/pmb/init/sudo.py new file mode 100644 index 00000000..e01b5908 --- /dev/null +++ b/pmb/init/sudo.py @@ -0,0 +1,38 @@ +# Copyright 2023 Anjandev Momi +# SPDX-License-Identifier: GPL-3.0-or-later +import os +import shutil +from functools import lru_cache + + +@lru_cache +def which_sudo() -> str | None: + """Return a command required to run commands as root, if any. + + Find whether sudo or doas is installed for commands that require root. + Allows user to override preferred sudo with PMB_SUDO env variable. + """ + if os.getuid() == 0: + return None + + supported_sudos = ["doas", "sudo"] + + user_set_sudo = os.getenv("PMB_SUDO") + if user_set_sudo is not None: + if shutil.which(user_set_sudo) is None: + raise RuntimeError( + "PMB_SUDO environmental variable is set to" + f" {user_set_sudo} but pmbootstrap cannot find" + " this command on your system." + ) + return user_set_sudo + + for sudo in supported_sudos: + if shutil.which(sudo) is not None: + return sudo + + raise RuntimeError( + "Can't find sudo or doas required to run pmbootstrap." + " Please install sudo, doas, or specify your own sudo" + " with the PMB_SUDO environmental variable." + )