diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index 8c6dd9b3..c0a44f56 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -60,7 +60,8 @@ config_keys = ["aports", "user", "work", "boot_size", - "extra_space"] + "extra_space", + "sudo_timer"] # Config file/commandline default values # $WORK gets replaced with the actual value for args.work (which may be @@ -96,7 +97,8 @@ defaults = { "user": "user", "work": os.path.expanduser("~") + "/.local/var/pmbootstrap", "boot_size": "128", - "extra_space": "0" + "extra_space": "0", + "sudo_timer": False } diff --git a/pmb/config/init.py b/pmb/config/init.py index 094b4b5f..fa6a9f11 100644 --- a/pmb/config/init.py +++ b/pmb/config/init.py @@ -344,7 +344,8 @@ def ask_for_additional_options(args, cfg): f" extra free space: {args.extra_space} MB," f" boot partition size: {args.boot_size} MB," f" parallel jobs: {args.jobs}," - f" ccache per arch: {args.ccache_size}") + f" ccache per arch: {args.ccache_size}," + f" sudo timer: {args.sudo_timer}") if not pmb.helpers.cli.confirm(args, "Change them?", default=False): @@ -385,6 +386,17 @@ def ask_for_additional_options(args, cfg): lowercase_answer=False, validation_regex=regex) cfg["pmbootstrap"]["ccache_size"] = answer + # Sudo timer + logging.info("pmbootstrap does everything in Alpine Linux chroots, so" + " your host system does not get modified. In order to" + " work with these chroots, pmbootstrap calls 'sudo'" + " internally. For long running operations, it is possible" + " that you'll have to authorize sudo more than once.") + answer = pmb.helpers.cli.confirm(args, "Enable background timer to prevent" + " repeated sudo authorization?", + default=args.sudo_timer) + cfg["pmbootstrap"]["sudo_timer"] = str(answer) + def ask_for_hostname(args, device): while True: diff --git a/pmb/config/pmaports.py b/pmb/config/pmaports.py index a4ee0676..9d69bf66 100644 --- a/pmb/config/pmaports.py +++ b/pmb/config/pmaports.py @@ -23,12 +23,6 @@ def check_legacy_folder(): def clone(args): - # Explain sudo-usage before using it the first time - logging.info("pmbootstrap does everything in Alpine Linux chroots, so your" - " host system does not get modified. In order to work with" - " these chroots, pmbootstrap calls 'sudo' internally. To see" - " the commands it runs, you can run 'pmbootstrap log' in a" - " second terminal.") logging.info("Setting up the native chroot and cloning the package build" " recipes (pmaports)...") diff --git a/pmb/helpers/run_core.py b/pmb/helpers/run_core.py index b0efdb08..e77a5f3e 100644 --- a/pmb/helpers/run_core.py +++ b/pmb/helpers/run_core.py @@ -5,6 +5,7 @@ import logging import selectors import subprocess import sys +import threading import time import os import pmb.helpers.run @@ -217,6 +218,31 @@ def check_return_code(args, code, log_message): raise RuntimeError("Command failed: " + log_message) +def sudo_timer_iterate(): + """ + Run sudo -v and schedule a new timer to repeat the same. + """ + + subprocess.Popen(["sudo", "-v"]).wait() + + timer = threading.Timer(interval=60, function=sudo_timer_iterate) + timer.daemon = True + timer.start() + + +def sudo_timer_start(args): + """ + Start a timer to call sudo -v periodically, so that the password is only + needed once. + """ + + if "sudo_timer_active" in args.cache: + return + args.cache["sudo_timer_active"] = True + + sudo_timer_iterate() + + def core(args, log_message, cmd, working_dir=None, output="log", output_return=False, check=None, sudo=False, disable_timeout=False): """ @@ -277,6 +303,9 @@ def core(args, log_message, cmd, working_dir=None, output="log", """ sanity_checks(output, output_return, check) + if args.sudo_timer and sudo: + sudo_timer_start(args) + # Log simplified and full command (pmbootstrap -v) logging.debug(log_message) logging.verbose("run: " + str(cmd)) diff --git a/test/test_questions.py b/test/test_questions.py index 02d66c23..9e77a507 100644 --- a/test/test_questions.py +++ b/test/test_questions.py @@ -261,12 +261,13 @@ def test_questions_additional_options(args, monkeypatch): assert cfg == {"pmbootstrap": {}} # Answer everything - fake_answers(monkeypatch, ["y", "128", "64", "5", "2G", "n"]) + fake_answers(monkeypatch, ["y", "128", "64", "5", "2G", "n", "n"]) func(args, cfg) assert cfg == {"pmbootstrap": {"extra_space": "128", "boot_size": "64", "jobs": "5", - "ccache_size": "2G"}} + "ccache_size": "2G", + "sudo_timer": "False"}} def test_questions_hostname(args, monkeypatch):