diff --git a/CHANGELOG.md b/CHANGELOG.md index 73989fa3..497f7ebc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.9.23] +### Features +- Arch + - allowing AUR packages to be installed when bauh is launched by the root user [#196](https://github.com/vinifmor/bauh/issues/196) + - it creates a non-root user called **bauh-aur** for building the packages (`useradd` and `runuser` commands must be installed) + + ## [0.9.22] 2021-11-30 ### Improvements - General diff --git a/README.md b/README.md index 503bb38a..987ea0d3 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,9 @@ suggestions: - If you have AUR added as a repository on you pacman configuration, make sure to disable bauh's support (through the settings described below) - AUR package compilation may require additional installed packages to work properly. Some of them are defined on the field `optdepends` of the [PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=bauh) + - for a **root** user the following additional applications must be installed: + - `useradd`: required to create a simple user named **bauh-aur** (since **makepkg** does not allow building packages as the **root** user) + - `runuser`: required to run commands as another user - **Repository packages currently do not support the following actions: Downgrade and History** - If some of your installed packages are not categorized, open a PullRequest to the **bauh-files** repository changing [categories.txt](https://github.com/vinifmor/bauh-files/blob/master/arch/categories.txt) - During bauh initialization a full AUR normalized index is saved at `~/.cache/bauh/arch/aur/index.txt` diff --git a/bauh/api/paths.py b/bauh/api/paths.py index fd7b5deb..a27f32a8 100644 --- a/bauh/api/paths.py +++ b/bauh/api/paths.py @@ -4,11 +4,16 @@ from pathlib import Path from bauh import __app_name__ from bauh.api import user + +def get_temp_dir(username: str) -> str: + return f'/tmp/{__app_name__}@{username}' + + CACHE_DIR = f'/var/cache/{__app_name__}' if user.is_root() else f'{Path.home()}/.cache/{__app_name__}' CONFIG_DIR = f'/etc/{__app_name__}' if user.is_root() else f'{Path.home()}/.config/{__app_name__}' USER_THEMES_DIR = f'/usr/share/{__app_name__}/themes' if user.is_root() else f'{Path.home()}/.local/share/{__app_name__}/themes' DESKTOP_ENTRIES_DIR = '/usr/share/applications' if user.is_root() else f'{Path.home()}/.local/share/applications' -TEMP_DIR = f'/tmp/{__app_name__}@{getuser()}' +TEMP_DIR = get_temp_dir(getuser()) LOGS_DIR = f'{TEMP_DIR}/logs' AUTOSTART_DIR = f'/etc/xdg/autostart' if user.is_root() else f'{Path.home()}/.config/autostart' BINARIES_DIR = f'/usr/local/bin' if user.is_root() else f'{Path.home()}/.local/bin' diff --git a/bauh/commons/system.py b/bauh/commons/system.py index eff0068b..9aa3e4f4 100644 --- a/bauh/commons/system.py +++ b/bauh/commons/system.py @@ -67,12 +67,15 @@ class SimpleProcess: def __init__(self, cmd: List[str], cwd: str = '.', expected_code: int = 0, global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: str = DEFAULT_LANG, root_password: str = None, extra_paths: Set[str] = None, error_phrases: Set[str] = None, wrong_error_phrases: Set[str] = None, - shell: bool = False, - success_phrases: Set[str] = None, extra_env: Optional[Dict[str, str]] = None): + shell: bool = False, success_phrases: Set[str] = None, extra_env: Optional[Dict[str, str]] = None, + custom_user: Optional[str] = None): pwdin, final_cmd = None, [] self.shell = shell - if root_password is not None: + + if custom_user: + final_cmd.extend(['runuser', '-u', custom_user, '--']) + elif root_password is not None: final_cmd.extend(['sudo', '-S']) pwdin = self._new(['echo', root_password], cwd, global_interpreter, lang).stdout @@ -219,14 +222,10 @@ class ProcessHandler: def run_cmd(cmd: str, expected_code: int = 0, ignore_return_code: bool = False, print_error: bool = True, - cwd: str = '.', global_interpreter: bool = USE_GLOBAL_INTERPRETER, extra_paths: Set[str] = None) -> str: + cwd: str = '.', global_interpreter: bool = USE_GLOBAL_INTERPRETER, extra_paths: Set[str] = None, + custom_user: Optional[str] = None) -> str: """ runs a given command and returns its default output - :param cmd: - :param expected_code: - :param ignore_return_code: - :param print_error: - :param global_interpreter :return: """ args = { @@ -239,13 +238,14 @@ def run_cmd(cmd: str, expected_code: int = 0, ignore_return_code: bool = False, if not print_error: args["stderr"] = subprocess.DEVNULL - res = subprocess.run(cmd, **args) + final_cmd = f"runuser -u {custom_user} -- {cmd}" if custom_user else cmd + res = subprocess.run(final_cmd, **args) return res.stdout.decode() if ignore_return_code or res.returncode == expected_code else None def new_subprocess(cmd: List[str], cwd: str = '.', shell: bool = False, stdin = None, global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: str = DEFAULT_LANG, - extra_paths: Set[str] = None) -> subprocess.Popen: + extra_paths: Set[str] = None, custom_user: Optional[str] = None) -> subprocess.Popen: args = { "stdout": PIPE, "stderr": PIPE, @@ -255,7 +255,8 @@ def new_subprocess(cmd: List[str], cwd: str = '.', shell: bool = False, stdin = "stdin": stdin if stdin else subprocess.DEVNULL } - return subprocess.Popen(cmd, **args) + final_cmd = ['runuser', '-u', custom_user, '--', *cmd] if custom_user else cmd + return subprocess.Popen(final_cmd, **args) def new_root_subprocess(cmd: List[str], root_password: str, cwd: str = '.', @@ -302,8 +303,9 @@ def get_human_size_str(size) -> str: return str(int_size) -def run(cmd: List[str], success_code: int = 0) -> Tuple[bool, str]: - p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) +def run(cmd: List[str], success_code: int = 0, custom_user: Optional[str] = None) -> Tuple[bool, str]: + final_cmd = ['runuser', '-u', custom_user, '--', *cmd] if custom_user else cmd + p = subprocess.run(final_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) return p.returncode == success_code, p.stdout.decode() @@ -327,9 +329,13 @@ def check_enabled_services(*names: str) -> Dict[str, bool]: return {s: status[i].strip().lower() == 'enabled' for i, s in enumerate(names) if s} -def execute(cmd: str, shell: bool = False, cwd: Optional[str] = None, output: bool = True, custom_env: Optional[dict] = None, stdin: bool = True) -> Tuple[int, Optional[str]]: +def execute(cmd: str, shell: bool = False, cwd: Optional[str] = None, output: bool = True, custom_env: Optional[dict] = None, + stdin: bool = True, custom_user: Optional[str] = None) -> Tuple[int, Optional[str]]: + + final_cmd = f"runuser -u {custom_user} -- {cmd}" if custom_user else cmd + params = { - 'args': cmd.split(' ') if not shell else [cmd], + 'args': final_cmd.split(' ') if not shell else [final_cmd], 'stdout': subprocess.PIPE if output else subprocess.DEVNULL, 'stderr': subprocess.STDOUT if output else subprocess.DEVNULL, 'shell': shell diff --git a/bauh/gems/arch/__init__.py b/bauh/gems/arch/__init__.py index 05169a09..5fafbeaa 100644 --- a/bauh/gems/arch/__init__.py +++ b/bauh/gems/arch/__init__.py @@ -1,11 +1,11 @@ import os +from typing import Optional from bauh import __app_name__ -from bauh.api.paths import CONFIG_DIR, TEMP_DIR, CACHE_DIR +from bauh.api.paths import CONFIG_DIR, TEMP_DIR, CACHE_DIR, get_temp_dir from bauh.commons import resource ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -BUILD_DIR = f'{TEMP_DIR}/arch' ARCH_CACHE_DIR = f'{CACHE_DIR}/arch' CATEGORIES_FILE_PATH = f'{ARCH_CACHE_DIR}/categories.txt' URL_CATEGORIES_FILE = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/arch/categories.txt' @@ -21,6 +21,10 @@ EDITABLE_PKGBUILDS_FILE = f'{ARCH_CONFIG_DIR}/aur/editable_pkgbuilds.txt' IGNORED_REBUILD_CHECK_FILE = f'{ARCH_CONFIG_DIR}/aur/ignored_rebuild_check.txt' +def get_pkgbuild_dir(user: Optional[str] = None) -> str: + return f'{get_temp_dir(user) if user else TEMP_DIR}/arch' + + def get_icon_path() -> str: return resource.get_path('img/arch.svg', ROOT_DIR) diff --git a/bauh/gems/arch/aur.py b/bauh/gems/arch/aur.py index d002dc28..5716f346 100644 --- a/bauh/gems/arch/aur.py +++ b/bauh/gems/arch/aur.py @@ -42,10 +42,6 @@ KNOWN_LIST_FIELDS = ('validpgpkeys', 'conflicts') -def map_pkgbuild(pkgbuild: str) -> dict: - return {attr: val.replace('"', '').replace("'", '').replace('(', '').replace(')', '') for attr, val in re.findall(r'\n(\w+)=(.+)', pkgbuild)} - - def map_srcinfo(string: str, pkgname: Optional[str], fields: Set[str] = None) -> dict: subinfos, subinfo = [], {} @@ -260,3 +256,4 @@ class AURClient: def is_supported(arch_config: dict) -> bool: return arch_config['aur'] and git.is_installed() + diff --git a/bauh/gems/arch/config.py b/bauh/gems/arch/config.py index 72280920..5a88fd14 100644 --- a/bauh/gems/arch/config.py +++ b/bauh/gems/arch/config.py @@ -1,16 +1,15 @@ -from pathlib import Path +from typing import Optional from bauh.commons.config import YAMLConfigManager -from bauh.gems.arch import CONFIG_FILE, BUILD_DIR +from bauh.gems.arch import CONFIG_FILE, get_pkgbuild_dir -def get_build_dir(arch_config: dict) -> str: +def get_build_dir(arch_config: dict, user: Optional[str]) -> str: build_dir = arch_config.get('aur_build_dir') if not build_dir: - build_dir = BUILD_DIR + build_dir = get_pkgbuild_dir(user) - Path(build_dir).mkdir(parents=True, exist_ok=True) return build_dir diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 1e8329ed..c0331f3d 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -10,12 +10,14 @@ import traceback from datetime import datetime from math import floor from pathlib import Path +from pwd import getpwnam from threading import Thread from typing import List, Set, Type, Tuple, Dict, Iterable, Optional, Collection import requests from dateutil.parser import parse as parse_date +from bauh import __app_name__ from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ TransactionResult, SoftwareAction from bauh.api.abstract.disk import DiskCacheLoader @@ -25,20 +27,19 @@ from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePacka from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \ ViewComponent, PanelComponent, MultipleSelectComponent, TextInputComponent, TextInputType, \ FileChooserComponent, TextComponent -from bauh.api.paths import TEMP_DIR from bauh.api.exception import NoInternetException +from bauh.api.paths import TEMP_DIR from bauh.commons import system -from bauh.api import user from bauh.commons.boot import CreateConfigFile from bauh.commons.category import CategoriesDownloader from bauh.commons.html import bold from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, SimpleProcess from bauh.commons.util import datetime_as_milis from bauh.commons.view_utils import new_select -from bauh.gems.arch import aur, pacman, makepkg, message, confirmation, disk, git, \ +from bauh.gems.arch import aur, pacman, message, confirmation, disk, git, \ gpg, URL_CATEGORIES_FILE, CATEGORIES_FILE_PATH, CUSTOM_MAKEPKG_FILE, SUGGESTIONS_FILE, \ get_icon_path, database, mirrors, sorting, cpu_manager, UPDATES_IGNORED_FILE, \ - ARCH_CONFIG_DIR, EDITABLE_PKGBUILDS_FILE, URL_GPG_SERVERS, BUILD_DIR, rebuild_detector + ARCH_CONFIG_DIR, EDITABLE_PKGBUILDS_FILE, URL_GPG_SERVERS, rebuild_detector, makepkg, sshell from bauh.gems.arch.aur import AURClient from bauh.gems.arch.config import get_build_dir, ArchConfigManager from bauh.gems.arch.dependencies import DependenciesAnalyser @@ -48,6 +49,7 @@ from bauh.gems.arch.mapper import AURDataMapper from bauh.gems.arch.model import ArchPackage from bauh.gems.arch.output import TransactionStatusHandler from bauh.gems.arch.pacman import RE_DEP_OPERATORS +from bauh.gems.arch.proc_util import write_as_user from bauh.gems.arch.updates import UpdatesSummarizer from bauh.gems.arch.worker import AURIndexUpdater, ArchDiskCacheUpdater, ArchCompilationOptimizer, RefreshMirrors, \ SyncDatabases @@ -247,6 +249,7 @@ class ArchManager(SoftwareManager): self.index_aur = None self.re_file_conflict = re.compile(r'[\w\d\-_.]+:') self.disk_cache_updater = disk_cache_updater + self.pkgbuilder_user: Optional[str] = f'{__app_name__}-aur' if context.root_user else None @staticmethod def get_aur_semantic_search_map() -> Dict[str, str]: @@ -673,32 +676,39 @@ class ArchManager(SoftwareManager): return SearchResult(pkgs, None, len(pkgs)) - def _downgrade_aur_pkg(self, context: TransactionContext): + def _downgrade_aur_pkg(self, context: TransactionContext) -> bool: + if not self.add_package_builder_user(context.handler): + return False + if context.commit: self.logger.info("Package '{}' current commit {}".format(context.name, context.commit)) else: self.logger.warning("Package '{}' has no commit associated with it. Downgrading will only compare versions.".format(context.name)) - context.build_dir = '{}/build_{}'.format(get_build_dir(context.config), int(time.time())) + context.build_dir = f'{get_build_dir(context.config, self.pkgbuilder_user)}/build_{int(time.time())}' try: if not os.path.exists(context.build_dir): - build_dir = context.handler.handle(SystemProcess(new_subprocess(['mkdir', '-p', context.build_dir]))) + build_dir, build_dir_error = sshell.mkdir(dir_path=context.build_dir, custom_user=self.pkgbuilder_user) - if build_dir: + if not build_dir: + context.watcher.print(build_dir_error) + else: context.handler.watcher.change_progress(10) base_name = context.get_base_name() context.watcher.change_substatus(self.i18n['arch.clone'].format(bold(context.name))) - cloned, _ = context.handler.handle_simple(git.clone_as_process(url=URL_GIT.format(base_name), cwd=context.build_dir)) + clone_dir = f'{context.build_dir}/{base_name}' + cloned, _ = context.handler.handle_simple(git.clone(url=URL_GIT.format(base_name), + target_dir=clone_dir, + custom_user=self.pkgbuilder_user)) context.watcher.change_progress(30) if cloned: context.watcher.change_substatus(self.i18n['arch.downgrade.reading_commits']) - clone_path = '{}/{}'.format(context.build_dir, base_name) - context.project_dir = clone_path - srcinfo_path = '{}/.SRCINFO'.format(clone_path) + context.project_dir = clone_dir + srcinfo_path = f'{clone_dir}/.SRCINFO' - logs = git.log_shas_and_timestamps(clone_path) + logs = git.log_shas_and_timestamps(clone_dir) context.watcher.change_progress(40) if not logs or len(logs) == 1: @@ -719,7 +729,7 @@ class ArchManager(SoftwareManager): self.logger.warning("Could not find '{}' target commit to revert to".format(context.name)) else: context.watcher.change_substatus(self.i18n['arch.downgrade.version_found']) - checkout_proc = new_subprocess(['git', 'checkout', target_commit], cwd=clone_path) + checkout_proc = new_subprocess(['git', 'checkout', target_commit], cwd=clone_dir, custom_user=self.pkgbuilder_user) if not context.handler.handle(SystemProcess(checkout_proc, check_error_output=False)): context.watcher.print("Could not rollback to current version's commit") return False @@ -738,7 +748,7 @@ class ArchManager(SoftwareManager): with open(srcinfo_path) as f: pkgsrc = aur.map_srcinfo(string=f.read(), pkgname=context.name, fields=srcfields) - reset_proc = new_subprocess(['git', 'reset', '--hard', commit], cwd=clone_path) + reset_proc = new_subprocess(['git', 'reset', '--hard', commit], cwd=clone_dir, custom_user=self.pkgbuilder_user) if not context.handler.handle(SystemProcess(reset_proc, check_error_output=False)): context.handler.watcher.print('Could not downgrade anymore. Aborting...') return False @@ -752,18 +762,18 @@ class ArchManager(SoftwareManager): if commit_found: context.watcher.change_substatus(self.i18n['arch.downgrade.version_found']) - checkout_proc = new_subprocess(['git', 'checkout', commit_found], cwd=clone_path) + checkout_proc = new_subprocess(['git', 'checkout', commit_found], cwd=clone_dir, custom_user=self.pkgbuilder_user) if not context.handler.handle(SystemProcess(checkout_proc, check_error_output=False)): context.watcher.print("Could not rollback to current version's commit") return False - reset_proc = new_subprocess(['git', 'reset', '--hard', commit_found], cwd=clone_path) + reset_proc = new_subprocess(['git', 'reset', '--hard', commit_found], cwd=clone_dir, custom_user=self.pkgbuilder_user) if not context.handler.handle(SystemProcess(reset_proc, check_error_output=False)): context.watcher.print("Could not downgrade to previous commit of '{}'. Aborting...".format(commit_found)) return False break - elif current_version == context.get_version(): # current version found: + elif current_version == context.get_version(): commit_found, commit_date = commit, date context.watcher.change_substatus(self.i18n['arch.downgrade.install_older']) @@ -825,10 +835,11 @@ class ArchManager(SoftwareManager): return self._install(context) def downgrade(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher) -> bool: - self.aur_client.clean_caches() - if not self._check_action_allowed(pkg, watcher): + if not self.check_action_allowed(pkg, watcher): return False + self.aur_client.clean_caches() + handler = ProcessHandler(watcher) if self._is_database_locked(handler, root_password): @@ -857,14 +868,6 @@ class ArchManager(SoftwareManager): if os.path.exists(pkg.get_disk_cache_path()): shutil.rmtree(pkg.get_disk_cache_path()) - def _check_action_allowed(self, pkg: ArchPackage, watcher: ProcessWatcher) -> bool: - if user.is_root() and pkg.repository == 'aur': - watcher.show_message(title=self.i18n['arch.install.aur.root_error.title'], - body=self.i18n['arch.install.aur.root_error.body'], - type_=MessageType.ERROR) - return False - return True - def _is_database_locked(self, handler: ProcessHandler, root_password: str) -> bool: if os.path.exists('/var/lib/pacman/db.lck'): handler.watcher.print('pacman database is locked') @@ -1155,16 +1158,13 @@ class ArchManager(SoftwareManager): pkg_sizes[req.pkg.name] = req.required_size - if aur_pkgs and not self._check_action_allowed(aur_pkgs[0], watcher): - return False - arch_config = self.configman.get_config() aur_supported = bool(aur_pkgs) or aur.is_supported(arch_config) self._sync_databases(arch_config=arch_config, aur_supported=aur_supported, root_password=root_password, handler=handler) - if repo_pkgs: + if repo_pkgs and self.check_action_allowed(repo_pkgs[0], watcher): if not self._upgrade_repo_pkgs(to_upgrade=[p.name for p in repo_pkgs], to_remove={r.pkg.name for r in requirements.to_remove} if requirements.to_remove else None, handler=handler, @@ -1177,7 +1177,7 @@ class ArchManager(SoftwareManager): elif requirements.to_remove and not self._remove_transaction_packages({r.pkg.name for r in requirements.to_remove}, handler, root_password): return False - if aur_pkgs: + if aur_pkgs and self.check_action_allowed(aur_pkgs[0], watcher) and self.add_package_builder_user(handler): watcher.change_status('{}...'.format(self.i18n['arch.upgrade.upgrade_aur_pkgs'])) self.logger.info("Retrieving the 'last_modified' field for each package to upgrade") @@ -1581,21 +1581,21 @@ class ArchManager(SoftwareManager): self.logger.warning("Package '{}' has no commit associated with it. Current history status may not be correct.".format(pkg.name)) arch_config = self.configman.get_config() - temp_dir = '{}/build_{}'.format(get_build_dir(arch_config), int(time.time())) + temp_dir = f'{get_build_dir(arch_config, self.pkgbuilder_user)}/build_{int(time.time())}' try: Path(temp_dir).mkdir(parents=True) base_name = pkg.get_base_name() run_cmd('git clone ' + URL_GIT.format(base_name), print_error=False, cwd=temp_dir) - clone_path = '{}/{}'.format(temp_dir, base_name) + clone_dir = f'{temp_dir}/{base_name}' - srcinfo_path = '{}/.SRCINFO'.format(clone_path) + srcinfo_path = f'{clone_dir}/.SRCINFO' if not os.path.exists(srcinfo_path): return PackageHistory.empyt(pkg) - logs = git.log_shas_and_timestamps(clone_path) + logs = git.log_shas_and_timestamps(clone_dir) if logs: srcfields = {'epoch', 'pkgver', 'pkgrel'} @@ -1622,7 +1622,7 @@ class ArchManager(SoftwareManager): '3_date': datetime.fromtimestamp(timestamp)}) # the number prefix is to ensure the rendering order if idx + 1 < len(logs): - if not run_cmd('git reset --hard ' + logs[idx + 1][0], cwd=clone_path): + if not run_cmd('git reset --hard ' + logs[idx + 1][0], cwd=clone_dir): break return PackageHistory(pkg=pkg, history=history, pkg_status_idx=status_idx) @@ -1738,7 +1738,7 @@ class ArchManager(SoftwareManager): context.restabilish_progress() return res - def _install_deps(self, context: TransactionContext, deps: List[Tuple[str, str]]) -> Iterable[str]: + def _install_deps(self, context: TransactionContext, deps: List[Tuple[str, str]]) -> Optional[Iterable[str]]: progress_increment = int(100 / len(deps)) progress = 0 self._update_progress(context, 1) @@ -1785,7 +1785,7 @@ class ArchManager(SoftwareManager): pkg_sizes = pacman.map_download_sizes(repo_dep_names) downloaded = self._download_packages(repo_dep_names, context.handler, context.root_password, pkg_sizes, multithreaded=True) except ArchDownloadException: - return False + return None status_handler = TransactionStatusHandler(watcher=context.watcher, i18n=self.i18n, names={*repo_dep_names}, logger=self.logger, percentage=len(repo_deps) > 1, downloading=downloaded) @@ -1827,7 +1827,8 @@ class ArchManager(SoftwareManager): return pkg_repos def _pre_download_source(self, pkgname: str, project_dir: str, watcher: ProcessWatcher) -> bool: - if self.context.file_downloader.is_multithreaded(): + # TODO: multi-threaded download client cannot be run as another user at the moment + if not self.context.root_user and self.context.file_downloader.is_multithreaded(): with open('{}/.SRCINFO'.format(project_dir)) as f: srcinfo = aur.map_srcinfo(string=f.read(), pkgname=pkgname) @@ -1839,7 +1840,7 @@ class ArchManager(SoftwareManager): continue else: for f in srcinfo[attr]: - if RE_PRE_DOWNLOAD_WL_PROTOCOLS.match(f) and not RE_PRE_DOWNLOAD_BL_EXT.match(f): + if RE_PRE_DOWNLOAD_WL_PROTOCOLS.match(f) and not RE_PRE_DOWNLOAD_BL_EXT.match(f): pre_download_files.append(f) if pre_download_files: @@ -1872,10 +1873,11 @@ class ArchManager(SoftwareManager): deny_button=False) if pkgbuild_input.get_value() != pkgbuild: - with open(pkgbuild_path, 'w+') as f: - f.write(pkgbuild_input.get_value()) + if not write_as_user(content=pkgbuild_input.get_value(), file_path=pkgbuild_path, user=self.pkgbuilder_user): + return False - return makepkg.update_srcinfo('/'.join(pkgbuild_path.split('/')[0:-1])) + return makepkg.update_srcinfo(project_dir='/'.join(pkgbuild_path.split('/')[0:-1]), + custom_user=self.pkgbuilder_user) return False @@ -1897,9 +1899,10 @@ class ArchManager(SoftwareManager): if self._ask_for_pkgbuild_edition(pkgname=context.name, arch_config=context.config, watcher=context.watcher, - pkgbuild_path='{}/PKGBUILD'.format(context.project_dir)): + pkgbuild_path=f'{context.project_dir}/PKGBUILD'): context.pkgbuild_edited = True - srcinfo = aur.map_srcinfo(string=makepkg.gen_srcinfo(context.project_dir), pkgname=context.name) + srcinfo = aur.map_srcinfo(string=makepkg.gen_srcinfo(build_dir=context.project_dir, custom_user=self.pkgbuilder_user), + pkgname=context.name) if srcinfo: context.name = srcinfo['pkgname'] @@ -1915,12 +1918,14 @@ class ArchManager(SoftwareManager): setattr(context.pkg, pkgattr, srcinfo.get(srcattr, getattr(context.pkg, pkgattr))) def _read_srcinfo(self, context: TransactionContext) -> str: - src_path = '{}/.SRCINFO'.format(context.project_dir) - if not os.path.exists(src_path): - srcinfo = makepkg.gen_srcinfo(context.project_dir, context.custom_pkgbuild_path) + src_path = f'{context.project_dir}/.SRCINFO' - with open(src_path, 'w+') as f: - f.write(srcinfo) + if not os.path.exists(src_path): + srcinfo = makepkg.gen_srcinfo(build_dir=context.project_dir, + custom_pkgbuild_path=context.custom_pkgbuild_path, + custom_user=self.pkgbuilder_user) + + write_as_user(content=srcinfo, file_path=src_path, user=self.pkgbuilder_user) else: with open(src_path) as f: srcinfo = f.read() @@ -1947,10 +1952,11 @@ class ArchManager(SoftwareManager): cpus_changed, cpu_prev_governors = cpu_manager.set_all_cpus_to('performance', context.root_password, self.logger) try: - pkgbuilt, output = makepkg.make(pkgdir=context.project_dir, - optimize=optimize, - handler=context.handler, - custom_pkgbuild=context.custom_pkgbuild_path) + pkgbuilt, output = makepkg.build(pkgdir=context.project_dir, + optimize=optimize, + handler=context.handler, + custom_pkgbuild=context.custom_pkgbuild_path, + custom_user=self.pkgbuilder_user) finally: if cpus_changed and cpu_prev_governors: self.logger.info("Restoring CPU governors") @@ -1998,7 +2004,10 @@ class ArchManager(SoftwareManager): def __fill_aur_output_files(self, context: TransactionContext): self.logger.info("Determining output files of '{}'".format(context.name)) context.watcher.change_substatus(self.i18n['arch.aur.build.list_output']) - output_files = {f for f in makepkg.list_output_files(context.project_dir, context.custom_pkgbuild_path) if os.path.isfile(f)} + + output_files = {f for f in makepkg.list_output_files(project_dir=context.project_dir, + custom_pkgbuild_path=context.custom_pkgbuild_path, + custom_user=self.pkgbuilder_user) if os.path.isfile(f)} if output_files: context.install_files = output_files @@ -2013,7 +2022,8 @@ class ArchManager(SoftwareManager): file_to_install = gen_file[0] if len(gen_file) > 1: - srcinfo = aur.map_srcinfo(string=makepkg.gen_srcinfo(context.project_dir), pkgname=context.name) + srcinfo = aur.map_srcinfo(string=makepkg.gen_srcinfo(build_dir=context.project_dir, custom_user=self.pkgbuilder_user), + pkgname=context.name) pkgver = '-{}'.format(srcinfo['pkgver']) if srcinfo.get('pkgver') else '' pkgrel = '-{}'.format(srcinfo['pkgrel']) if srcinfo.get('pkgrel') else '' arch = '-{}'.format(srcinfo['arch']) if srcinfo.get('arch') else '' @@ -2134,11 +2144,12 @@ class ArchManager(SoftwareManager): if not handled_deps: return False - check_res = makepkg.check(context.project_dir, + check_res = makepkg.check(project_dir=context.project_dir, optimize=bool(context.config['optimize']), missing_deps=False, handler=context.handler, - custom_pkgbuild=context.custom_pkgbuild_path) + custom_pkgbuild=context.custom_pkgbuild_path, + custom_user=self.pkgbuilder_user) if check_res: if check_res.get('gpg_key'): @@ -2502,23 +2513,30 @@ class ArchManager(SoftwareManager): return False def _install_from_aur(self, context: TransactionContext) -> bool: + if not context.dependency and not self.add_package_builder_user(context.handler): + return False + self._optimize_makepkg(context.config, context.watcher) - context.build_dir = '{}/build_{}'.format(get_build_dir(context.config), int(time.time())) + context.build_dir = f'{get_build_dir(context.config, self.pkgbuilder_user)}/build_{int(time.time())}' try: if not os.path.exists(context.build_dir): - build_dir = context.handler.handle(SystemProcess(new_subprocess(['mkdir', '-p', context.build_dir]))) + build_dir, build_dir_error = sshell.mkdir(dir_path=context.build_dir, custom_user=self.pkgbuilder_user) self._update_progress(context, 10) - if build_dir: + if not build_dir: + context.watcher.print(build_dir_error) + else: base_name = context.get_base_name() context.watcher.change_substatus(self.i18n['arch.clone'].format(bold(base_name))) - cloned = context.handler.handle_simple(git.clone_as_process(url=URL_GIT.format(base_name), cwd=context.build_dir, depth=1)) + clone_dir = f'{context.build_dir}/{base_name}' + cloned = context.handler.handle_simple(git.clone(url=URL_GIT.format(base_name), target_dir=clone_dir, + depth=1, custom_user=self.pkgbuilder_user)) if cloned: self._update_progress(context, 40) - context.project_dir = '{}/{}'.format(context.build_dir, base_name) + context.project_dir = clone_dir return self._build(context) finally: if os.path.exists(context.build_dir) and context.config['aur_remove_build_dir']: @@ -2544,10 +2562,10 @@ class ArchManager(SoftwareManager): ArchCompilationOptimizer(i18n=self.i18n, logger=self.context.logger, taskman=TaskManager()).optimize() def install(self, pkg: ArchPackage, root_password: str, disk_loader: Optional[DiskCacheLoader], watcher: ProcessWatcher, context: TransactionContext = None) -> TransactionResult: - self.aur_client.clean_caches() + if not self.check_action_allowed(pkg, watcher): + return TransactionResult.fail() - if not self._check_action_allowed(pkg, watcher): - return TransactionResult(success=False, installed=[], removed=[]) + self.aur_client.clean_caches() handler = ProcessHandler(watcher) if not context else context.handler @@ -2936,7 +2954,7 @@ class ArchManager(SoftwareManager): capitalize_label=False), FileChooserComponent(id_='aur_build_dir', label=self.i18n['arch.config.aur_build_dir'], - tooltip=self.i18n['arch.config.aur_build_dir.tip'].format(BUILD_DIR), + tooltip=self.i18n['arch.config.aur_build_dir.tip'].format(get_build_dir(arch_config, self.pkgbuilder_user)), max_width=max_width, file_path=arch_config['aur_build_dir'], capitalize_label=False, @@ -3397,7 +3415,8 @@ class ArchManager(SoftwareManager): deny_label=self.i18n['arch.aur.sync.several_names.popup.bt_selected']): context.pkgs_to_build = {context.name, *select.get_selected_values()} - pkgbuild_path = '{}/PKGBUILD'.format(context.project_dir) + pkgbuild_path = f'{context.project_dir}/PKGBUILD' + with open(pkgbuild_path) as f: current_pkgbuild = f.read() @@ -3407,16 +3426,22 @@ class ArchManager(SoftwareManager): names = context.name context.pkgs_to_build = {context.name} - new_pkgbuild = RE_PKGBUILD_PKGNAME.sub("pkgname={}".format(names), current_pkgbuild) - custom_pkgbuild_path = pkgbuild_path + '_CUSTOM' + new_pkgbuild = RE_PKGBUILD_PKGNAME.sub(f"pkgname={names}", current_pkgbuild) + custom_pkgbuild_path = f'{pkgbuild_path}_CUSTOM' - with open(custom_pkgbuild_path, 'w+') as f: - f.write(new_pkgbuild) + if not write_as_user(content=new_pkgbuild, + file_path=custom_pkgbuild_path, + user=self.pkgbuilder_user): + self.logger.error(f"Could not write edited PKGBUILD to '{custom_pkgbuild_path}'") + return - new_srcinfo = makepkg.gen_srcinfo(context.project_dir, custom_pkgbuild_path) + new_srcinfo = makepkg.gen_srcinfo(build_dir=context.project_dir, + custom_pkgbuild_path=custom_pkgbuild_path, + custom_user=self.pkgbuilder_user) - with open('{}/.SRCINFO'.format(context.project_dir), 'w+') as f: - f.write(new_srcinfo) + srcinfo_path = f'{context.project_dir}/.SRCINFO' + if not write_as_user(content=new_srcinfo, file_path=srcinfo_path, user=self.pkgbuilder_user): + self.logger.warning(f"Could not write the updated .SRCINFO content to '{srcinfo_path}'") return custom_pkgbuild_path @@ -3499,3 +3524,47 @@ class ArchManager(SoftwareManager): pkg.update_state() return True + + def check_action_allowed(self, pkg: ArchPackage, watcher: Optional[ProcessWatcher]) -> bool: + if self.context.root_user and pkg.repository == 'aur': + if not shutil.which('useradd'): + if watcher: + watcher.show_message(title=self.i18n['error'].capitalize(), + type_=MessageType.ERROR, + body=self.i18n['arch.aur.error.missing_root_dep'].format(dep=bold('useradd'), + aur=bold('AUR'), + root=bold('root'))) + return False + + if not shutil.which('runuser'): + if watcher: + watcher.show_message(title=self.i18n['error'].capitalize(), + type_=MessageType.ERROR, + body=self.i18n['arch.aur.error.missing_root_dep'].format(dep=bold('runuser'), + aur=bold('AUR'), + root=bold('root'))) + return False + + return True + + def add_package_builder_user(self, handler: ProcessHandler) -> bool: + if self.context.root_user and self.pkgbuilder_user: + try: + getpwnam(self.pkgbuilder_user) + return True + except KeyError: + self.logger.warning(f"Package builder user '{self.pkgbuilder_user}' does not exist") + self.logger.info(f"Adding the package builder user '{self.pkgbuilder_user}'") + added, output = handler.handle_simple(SimpleProcess(cmd=['useradd', self.pkgbuilder_user], shell=True)) + + if not added: + output_log = "Command output: {}".format(output.replace('\n', ' ') if output else '(no output)') + self.logger.error(f"Could not add the package builder user '{self.pkgbuilder_user}'. {output_log}") + handler.watcher.show_message(title=self.i18n['error'].capitalize(), + type_=MessageType.ERROR, + body=self.i18n['arch.aur.error.add_builder_user'].format(user=bold(self.pkgbuilder_user), + aur=bold('AUR'))) + + return added + + return True diff --git a/bauh/gems/arch/git.py b/bauh/gems/arch/git.py index 086e8112..8320002d 100644 --- a/bauh/gems/arch/git.py +++ b/bauh/gems/arch/git.py @@ -53,10 +53,13 @@ def log_shas_and_timestamps(repo_path: str) -> Optional[List[Tuple[str, int]]]: return logs -def clone_as_process(url: str, cwd: Optional[str], depth: int = -1) -> SimpleProcess: +def clone(url: str, target_dir: Optional[str], depth: int = -1, custom_user: Optional[str] = None) -> SimpleProcess: cmd = ['git', 'clone', url] if depth > 0: - cmd.append('--depth={}'.format(depth)) + cmd.append(f'--depth={depth}') - return SimpleProcess(cmd=cmd, cwd=cwd) + if target_dir: + cmd.append(target_dir) + + return SimpleProcess(cmd=cmd, custom_user=custom_user) diff --git a/bauh/gems/arch/makepkg.py b/bauh/gems/arch/makepkg.py index 424697ef..3c59bb7b 100644 --- a/bauh/gems/arch/makepkg.py +++ b/bauh/gems/arch/makepkg.py @@ -1,20 +1,61 @@ import os import re -from typing import Tuple, Optional, Set +from typing import Optional, Set, Tuple -from bauh.commons.system import SimpleProcess, ProcessHandler, run_cmd +from bauh.commons import system +from bauh.commons.system import ProcessHandler, SimpleProcess from bauh.gems.arch import CUSTOM_MAKEPKG_FILE +from bauh.gems.arch.proc_util import write_as_user -RE_DEPS_PATTERN = re.compile(r'\n?\s+->\s(.+)\n') RE_UNKNOWN_GPG_KEY = re.compile(r'\(unknown public key (\w+)\)') +RE_DEPS_PATTERN = re.compile(r'\n?\s+->\s(.+)\n') -def gen_srcinfo(build_dir: str, custom_pkgbuild_path: Optional[str] = None) -> str: - return run_cmd('makepkg --printsrcinfo{}'.format(' -p {}'.format(custom_pkgbuild_path) if custom_pkgbuild_path else ''), - cwd=build_dir) +def gen_srcinfo(build_dir: str, custom_pkgbuild_path: Optional[str] = None, custom_user: Optional[str] = None) -> str: + cmd = f"makepkg --printsrcinfo{' -p {}'.format(custom_pkgbuild_path) if custom_pkgbuild_path else ''}" + return system.run_cmd(cmd, cwd=build_dir, custom_user=custom_user) -def check(pkgdir: str, optimize: bool, missing_deps: bool, handler: ProcessHandler, custom_pkgbuild: Optional[str] = None) -> dict: +def update_srcinfo(project_dir: str, custom_user: Optional[str] = None) -> bool: + updated_src = system.run_cmd('makepkg --printsrcinfo', cwd=project_dir, custom_user=custom_user) + + if updated_src: + return write_as_user(content=updated_src, file_path=f"{project_dir}/.SRCINFO", user=custom_user) + + return False + + +def list_output_files(project_dir: str, custom_pkgbuild_path: Optional[str] = None, + custom_user: Optional[str] = None) -> Set[str]: + cmd = f"makepkg --packagelist{' -p {}'.format(custom_pkgbuild_path) if custom_pkgbuild_path else ''}" + output = system.run_cmd(cmd=cmd, print_error=False, cwd=project_dir, custom_user=custom_user) + + if output: + return {p.strip() for p in output.split('\n') if p} + + return set() + + +def build(pkgdir: str, optimize: bool, handler: ProcessHandler, custom_pkgbuild: Optional[str] = None, + custom_user: Optional[str] = None) -> Tuple[bool, str]: + cmd = ['makepkg', '-ALcsmf', '--skipchecksums', '--nodeps'] + + if custom_pkgbuild: + cmd.append('-p') + cmd.append(custom_pkgbuild) + + if optimize: + if os.path.exists(CUSTOM_MAKEPKG_FILE): + handler.watcher.print(f'Using custom makepkg.conf -> {CUSTOM_MAKEPKG_FILE}') + cmd.append(f'--config={CUSTOM_MAKEPKG_FILE}') + else: + handler.watcher.print(f'Custom optimized makepkg.conf ({CUSTOM_MAKEPKG_FILE}) not found') + + return handler.handle_simple(SimpleProcess(cmd, cwd=pkgdir, shell=True, custom_user=custom_user)) + + +def check(project_dir: str, optimize: bool, missing_deps: bool, handler: ProcessHandler, + custom_pkgbuild: Optional[str] = None, custom_user: Optional[str] = None) -> dict: res = {} cmd = ['makepkg', '-ALcfm', '--check', '--noarchive', '--nobuild', '--noprepare'] @@ -28,12 +69,12 @@ def check(pkgdir: str, optimize: bool, missing_deps: bool, handler: ProcessHandl if optimize: if os.path.exists(CUSTOM_MAKEPKG_FILE): - handler.watcher.print('Using custom makepkg.conf -> {}'.format(CUSTOM_MAKEPKG_FILE)) - cmd.append('--config={}'.format(CUSTOM_MAKEPKG_FILE)) + handler.watcher.print(f'Using custom makepkg.conf -> {CUSTOM_MAKEPKG_FILE}') + cmd.append(f'--config={CUSTOM_MAKEPKG_FILE}') else: - handler.watcher.print('Custom optimized makepkg.conf ( {} ) not found'.format(CUSTOM_MAKEPKG_FILE)) + handler.watcher.print(f'Custom optimized makepkg.conf ({CUSTOM_MAKEPKG_FILE}) not found') - success, output = handler.handle_simple(SimpleProcess(cmd, cwd=pkgdir, shell=True)) + success, output = handler.handle_simple(SimpleProcess(cmd, cwd=project_dir, shell=True, custom_user=custom_user)) if missing_deps and 'Missing dependencies' in output: res['missing_deps'] = RE_DEPS_PATTERN.findall(output) @@ -47,42 +88,3 @@ def check(pkgdir: str, optimize: bool, missing_deps: bool, handler: ProcessHandl res['validity_check'] = True return res - - -def make(pkgdir: str, optimize: bool, handler: ProcessHandler, custom_pkgbuild: Optional[str] = None) -> Tuple[bool, str]: - cmd = ['makepkg', '-ALcsmf', '--skipchecksums', '--nodeps'] - - if custom_pkgbuild: - cmd.append('-p') - cmd.append(custom_pkgbuild) - - if optimize: - if os.path.exists(CUSTOM_MAKEPKG_FILE): - handler.watcher.print('Using custom makepkg.conf -> {}'.format(CUSTOM_MAKEPKG_FILE)) - cmd.append('--config={}'.format(CUSTOM_MAKEPKG_FILE)) - else: - handler.watcher.print('Custom optimized makepkg.conf ( {} ) not found'.format(CUSTOM_MAKEPKG_FILE)) - - return handler.handle_simple(SimpleProcess(cmd, cwd=pkgdir, shell=True)) - - -def update_srcinfo(project_dir: str) -> bool: - updated_src = run_cmd('makepkg --printsrcinfo', cwd=project_dir) - - if updated_src: - with open('{}/.SRCINFO'.format(project_dir), 'w+') as f: - f.write(updated_src) - return True - - return False - - -def list_output_files(project_dir: str, custom_pkgbuild_path: Optional[str] = None) -> Set[str]: - output = run_cmd(cmd='makepkg --packagelist{}'.format(' -p {}'.format(custom_pkgbuild_path) if custom_pkgbuild_path else ''), - print_error=False, - cwd=project_dir) - - if output: - return {p.strip() for p in output.split('\n') if p} - - return set() diff --git a/bauh/gems/arch/proc_util.py b/bauh/gems/arch/proc_util.py new file mode 100644 index 00000000..4ef77ce8 --- /dev/null +++ b/bauh/gems/arch/proc_util.py @@ -0,0 +1,51 @@ +import multiprocessing +import os +import traceback +from pwd import getpwnam +from typing import Callable, Optional, TypeVar + +R = TypeVar('R') + + +class CallAsUser: + + def __init__(self, target: Callable[[], R], user: str): + self._target = target + self._user = user + + def __call__(self, *args, **kwargs) -> R: + try: + os.setuid(getpwnam(self._user).pw_uid) + return self._target() + except: + traceback.print_exc() + + +class WriteToFile: + + def __init__(self, file_path: str, content: str): + self._file_path = file_path + self._content = content + + def __call__(self, *args, **kwargs) -> bool: + try: + with open(self._file_path, 'w+') as f: + f.write(self._content) + + return True + except: + traceback.print_exc() + return False + + +def exec_as_user(target: Callable[[], R], user: Optional[str] = None) -> R: + if user: + with multiprocessing.Pool(1) as pool: + return pool.apply(CallAsUser(target, user)) + else: + return target() + + +def write_as_user(content: str, file_path: str, user: Optional[str] = None) -> bool: + return exec_as_user(target=WriteToFile(file_path=file_path, content=content), + user=user) diff --git a/bauh/gems/arch/resources/locale/ca b/bauh/gems/arch/resources/locale/ca index 4b4f9337..ec8381d5 100644 --- a/bauh/gems/arch/resources/locale/ca +++ b/bauh/gems/arch/resources/locale/ca @@ -126,6 +126,8 @@ arch.downgrade.reading_commits=S’estan llegint les revisions del dipòsit arch.downgrade.repo_pkg.no_versions=No s'ha trobat cap versió antiga al disc arch.downgrade.searching_stored=Cerqueu versions antigues al disc arch.downgrade.version_found=S’ha trobat la versió actual del paquet +arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user +arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages arch.info.00_pkg_build=PKGBUILD arch.info.01_id=identificació arch.info.02_name=nom diff --git a/bauh/gems/arch/resources/locale/de b/bauh/gems/arch/resources/locale/de index 4f92a835..f46459cc 100644 --- a/bauh/gems/arch/resources/locale/de +++ b/bauh/gems/arch/resources/locale/de @@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Repository Commits lesen arch.downgrade.repo_pkg.no_versions=No old version found on disk arch.downgrade.searching_stored=Looking for old versions on disk arch.downgrade.version_found=Aktuelle Paketversion gefunden +arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user +arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages arch.info.00_pkg_build=pkgbuild arch.info.01_id=Ich würde arch.info.02_name=Name diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en index 26935450..a3137e06 100644 --- a/bauh/gems/arch/resources/locale/en +++ b/bauh/gems/arch/resources/locale/en @@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Reading the repository commits arch.downgrade.repo_pkg.no_versions=No old version found on disk arch.downgrade.searching_stored=Looking for old versions on disk arch.downgrade.version_found=Current package version found +arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user +arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages arch.info.00_pkg_build=pkgbuild arch.info.01_id=id arch.info.02_name=name diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es index 674343ab..951cf926 100644 --- a/bauh/gems/arch/resources/locale/es +++ b/bauh/gems/arch/resources/locale/es @@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Leyendo los commits del repositorio arch.downgrade.repo_pkg.no_versions=No se encontró una versión anterior en el disco arch.downgrade.searching_stored=Buscando versiones antiguas en el disco arch.downgrade.version_found=Version actual del paquete encontrada +arch.aur.error.missing_root_dep={dep} no está instalado y es necesario para la instalación de paquetes del {aur} como el usuario {root} +arch.aur.error.add_builder_user=No fue posible crear el usuario {user} para construir paquetes del {aur} arch.info.00_pkg_build=pkgbuild arch.info.01_id=id arch.info.02_name=nombre diff --git a/bauh/gems/arch/resources/locale/fr b/bauh/gems/arch/resources/locale/fr index 23db1bee..b7fe4b39 100644 --- a/bauh/gems/arch/resources/locale/fr +++ b/bauh/gems/arch/resources/locale/fr @@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Lecture des commits du dépôt arch.downgrade.repo_pkg.no_versions=Aucune version antérieure trouvée sur le disque arch.downgrade.searching_stored=Recherche de version antérieure sur le disque arch.downgrade.version_found=Version actuelle du paquet trouvée +arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user +arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages arch.info.00_pkg_build=pkgbuild arch.info.01_id=id arch.info.02_name=nom diff --git a/bauh/gems/arch/resources/locale/it b/bauh/gems/arch/resources/locale/it index 8af43cc5..eb106094 100644 --- a/bauh/gems/arch/resources/locale/it +++ b/bauh/gems/arch/resources/locale/it @@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Reading the repository commits arch.downgrade.repo_pkg.no_versions=Nessuna versione precedente trovata sul disco arch.downgrade.searching_stored=Ricerca di versioni precedenti su disco arch.downgrade.version_found=Trovata la versione del pacchetto corrente +arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user +arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages arch.info.00_pkg_build=pkgbuild arch.info.01_id=id arch.info.02_name=nome diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt index 4037df26..da9a8c5d 100644 --- a/bauh/gems/arch/resources/locale/pt +++ b/bauh/gems/arch/resources/locale/pt @@ -125,6 +125,8 @@ arch.downgrade.reading_commits=Lendo os commits do repositório arch.downgrade.repo_pkg.no_versions=Nenhuma versão antiga encontrada no disco arch.downgrade.searching_stored=Procurando versões antigas no disco arch.downgrade.version_found=Versão atual do pacote encontrada +arch.aur.error.missing_root_dep={dep} não está instalado e é necessário para a instalação de pacotes do {aur} como o usuário {root} +arch.aur.error.add_builder_user=Não foi possível criar o usuário {user} para a construção de pacotes do {aur} arch.info.00_pkg_build=pkgbuild arch.info.01_id=id arch.info.02_name=nome diff --git a/bauh/gems/arch/resources/locale/ru b/bauh/gems/arch/resources/locale/ru index 36b9f4d2..107fb6bc 100644 --- a/bauh/gems/arch/resources/locale/ru +++ b/bauh/gems/arch/resources/locale/ru @@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Чтение коммитов репозитор arch.downgrade.repo_pkg.no_versions=No old version found on disk arch.downgrade.searching_stored=Looking for old versions on disk arch.downgrade.version_found=Найдена текущая версия пакета +arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user +arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages arch.info.00_pkg_build=PKGBUILD arch.info.01_id=Идентификатор arch.info.02_name=Имя diff --git a/bauh/gems/arch/resources/locale/tr b/bauh/gems/arch/resources/locale/tr index b90b42de..a6520a28 100644 --- a/bauh/gems/arch/resources/locale/tr +++ b/bauh/gems/arch/resources/locale/tr @@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Depo çalışmalarını oku arch.downgrade.repo_pkg.no_versions=Diskte eski sürüm bulunamadı arch.downgrade.searching_stored=Diskteki eski sürümlere bakılıyor arch.downgrade.version_found=Geçerli paket sürümü bulundu +arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user +arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages arch.info.00_pkg_build=pkgbuild arch.info.01_id=kimlik arch.info.02_name=isim diff --git a/bauh/gems/arch/sshell.py b/bauh/gems/arch/sshell.py new file mode 100644 index 00000000..7e4852c1 --- /dev/null +++ b/bauh/gems/arch/sshell.py @@ -0,0 +1,8 @@ +from typing import Optional, Tuple + +from bauh.commons.system import execute + + +def mkdir(dir_path: str, parent: bool = True, custom_user: Optional[str] = None) -> Tuple[bool, Optional[str]]: + code, output = execute(f'mkdir {"-p " if parent else ""}"{dir_path}"', shell=True, custom_user=custom_user) + return code == 0, output