[arch] feature: allowing AUR packages to be installed when bauh is launched by the root user

This commit is contained in:
Vinicius Moreira
2021-12-07 15:41:02 -03:00
parent afcc94b934
commit 6d3917ee99
21 changed files with 330 additions and 158 deletions

View File

@@ -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'

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Sestan 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=Sha 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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=Имя

View File

@@ -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

8
bauh/gems/arch/sshell.py Normal file
View File

@@ -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