diff --git a/README.md b/README.md index 2c75a2b0..e7f0f178 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,12 @@ $ pmbootstrap stats --arch=armhf $ pmbootstrap log_distccd ``` +### Use alternative sudo + +pmbootstrap supports `doas` and `sudo`. +If multiple sudo implementations are installed, pmbootstrap will use `doas`. +You can set the `PMB_SUDO` environmental variable to define the sudo implementation you want to use. + ## Development ### Requirements for running tests * [Shellcheck](https://shellcheck.net/) diff --git a/pmb/chroot/root.py b/pmb/chroot/root.py index b8181280..e531c679 100644 --- a/pmb/chroot/root.py +++ b/pmb/chroot/root.py @@ -71,7 +71,7 @@ def root(args, cmd, suffix="native", working_dir="/", output="log", executables = executables_absolute_path() cmd_chroot = [executables["chroot"], chroot, "/bin/sh", "-c", pmb.helpers.run.flat_cmd(cmd, working_dir)] - cmd_sudo = ["sudo", "env", "-i", executables["sh"], "-c", + cmd_sudo = [pmb.config.sudo, "env", "-i", executables["sh"], "-c", pmb.helpers.run.flat_cmd(cmd_chroot, env=env_all)] return pmb.helpers.run_core.core(args, msg, cmd_sudo, None, output, output_return, check, True, diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index 38b2f709..f440f13d 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -11,6 +11,7 @@ import sys from pmb.config.load import load from pmb.config.save import save from pmb.config.merge_with_args import merge_with_args +from pmb.config.sudo import which_sudo # @@ -50,6 +51,7 @@ ondev_min_version = "0.2.0" # in sync with README.md, and try to keep the list as small as possible. The # idea is to run almost everything in Alpine chroots. required_programs = ["git", "openssl", "ps"] +sudo = which_sudo() # Keys saved in the config file (mostly what we ask in 'pmbootstrap init') config_keys = ["aports", diff --git a/pmb/config/sudo.py b/pmb/config/sudo.py new file mode 100644 index 00000000..4512ece0 --- /dev/null +++ b/pmb/config/sudo.py @@ -0,0 +1,28 @@ +# Copyright 2021 Anjandev Momi +# SPDX-License-Identifier: GPL-3.0-or-later +import os +import shutil + + +def which_sudo(): + """ + Find whether sudo or doas is installed for commands that require root. + Allows user to override preferred sudo with PMB_SUDO env variable. + """ + 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.") diff --git a/pmb/helpers/run.py b/pmb/helpers/run.py index 0d6a7bd3..9100c3d7 100644 --- a/pmb/helpers/run.py +++ b/pmb/helpers/run.py @@ -62,7 +62,7 @@ def user(args, cmd, working_dir=None, output="log", output_return=False, def root(args, cmd, working_dir=None, output="log", output_return=False, check=None, env={}): """ - Run a command on the host system as root, with sudo. + Run a command on the host system as root, with sudo or doas. :param env: dict of environment variables to be passed to the command, e.g. {"JOBS": "5"} @@ -72,7 +72,7 @@ def root(args, cmd, working_dir=None, output="log", output_return=False, """ if env: cmd = ["sh", "-c", flat_cmd(cmd, env=env)] - cmd = ["sudo"] + cmd + cmd = [pmb.config.sudo] + cmd return user(args, cmd, working_dir, output, output_return, check, env, True) diff --git a/pmb/helpers/run_core.py b/pmb/helpers/run_core.py index 800b7e86..e42e76b4 100644 --- a/pmb/helpers/run_core.py +++ b/pmb/helpers/run_core.py @@ -224,7 +224,10 @@ def sudo_timer_iterate(): Run sudo -v and schedule a new timer to repeat the same. """ - subprocess.Popen(["sudo", "-v"]).wait() + if pmb.config.sudo == "sudo": + subprocess.Popen(["sudo", "-v"]).wait() + else: + subprocess.Popen([pmb.config.sudo, "true"]).wait() timer = threading.Timer(interval=60, function=sudo_timer_iterate) timer.daemon = True diff --git a/pmb/parse/bootimg.py b/pmb/parse/bootimg.py index a713d47d..b6759592 100644 --- a/pmb/parse/bootimg.py +++ b/pmb/parse/bootimg.py @@ -79,8 +79,9 @@ def bootimg(args, path): if not os.path.exists(path): raise RuntimeError("Could not find file '" + path + "'") - logging.info("NOTE: You will be prompted for your sudo password, so we can" - " set up a chroot to extract and analyze your boot.img file") + logging.info("NOTE: You will be prompted for your sudo/doas password, so" + " we can set up a chroot to extract and analyze your" + " boot.img file") pmb.chroot.apk.install(args, ["file", "unpackbootimg"]) temp_path = pmb.chroot.other.tempfolder(args, "/tmp/bootimg_parser") diff --git a/test/test_run_core.py b/test/test_run_core.py index 06ed1eae..506488d9 100644 --- a/test/test_run_core.py +++ b/test/test_run_core.py @@ -4,6 +4,7 @@ import sys import subprocess import pytest +import time import pmb_test # noqa import pmb.helpers.run_core @@ -81,7 +82,7 @@ def test_foreground_pipe(args): assert ret == (-9, "first\n") # Kill with output timeout as root - cmd = ["sudo", "sh", "-c", "printf first; sleep 2; printf second"] + cmd = [pmb.config.sudo, "sh", "-c", "printf first; sleep 2; printf second"] args.timeout = 0.3 ret = func(args, cmd, output_return=True, output_timeout=True, sudo=True) @@ -97,7 +98,7 @@ def test_foreground_pipe(args): # Check if all child processes are killed after timeout. # The first command uses ps to get its process group id (pgid) and echo it # to stdout. All of the test commands will be running under that pgid. - cmd = ["sudo", "sh", "-c", + cmd = [pmb.config.sudo, "sh", "-c", "pgid=$(ps -o pgid= | grep ^${1:-$$});echo $pgid | tr -d '\n';" + "sleep 10 | sleep 20 | sleep 30"] args.timeout = 0.3 @@ -152,3 +153,14 @@ def test_core(args): with pytest.raises(RuntimeError) as e: func(args, msg, ["sleep", "1"], output="log") assert str(e.value).startswith("Command failed:") + + +@pytest.mark.skip_ci +def test_sudo_timer(args): + pmb.helpers.run.root(args, ["whoami"]) + + time.sleep(300) + + out = pmb.helpers.run.root(args, ["whoami"]) + + assert out == 0