From b1c7dfa3c9b9b57ec00727c1acf8e7696e455d1e Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Thu, 10 Jul 2025 18:41:23 +0300 Subject: [PATCH] fix perms --- src/ahriman/application/handlers/setup.py | 20 +++--- src/ahriman/core/alpm/pacman.py | 4 +- src/ahriman/core/database/sqlite.py | 4 +- src/ahriman/models/repository_paths.py | 77 ++++++++++++++++------- src/ahriman/web/web.py | 9 ++- 5 files changed, 76 insertions(+), 38 deletions(-) diff --git a/src/ahriman/application/handlers/setup.py b/src/ahriman/application/handlers/setup.py index a00114fd..a7aa9129 100644 --- a/src/ahriman/application/handlers/setup.py +++ b/src/ahriman/application/handlers/setup.py @@ -72,16 +72,17 @@ class Setup(Handler): application = Application(repository_id, configuration, report=report) - Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths) - Setup.executable_create(application.repository.paths, repository_id) - repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server - Setup.configuration_create_devtools( - repository_id, args.from_configuration, args.mirror, args.multilib, repository_server) - Setup.configuration_create_sudo(application.repository.paths, repository_id) + with application.repository.paths.preserve_owner(): + Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths) + Setup.executable_create(application.repository.paths, repository_id) + repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server + Setup.configuration_create_devtools( + repository_id, args.from_configuration, args.mirror, args.multilib, repository_server) + Setup.configuration_create_sudo(application.repository.paths, repository_id) - application.repository.repo.init() - # lazy database sync - application.repository.pacman.handle # pylint: disable=pointless-statement + application.repository.repo.init() + # lazy database sync + application.repository.pacman.handle # pylint: disable=pointless-statement @staticmethod def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser: @@ -280,6 +281,5 @@ class Setup(Handler): command = Setup.build_command(paths.root, repository_id) command.unlink(missing_ok=True) command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH) - paths.chown(command) # we would like to keep owner inside ahriman's home arguments = [_set_service_setup_parser] diff --git a/src/ahriman/core/alpm/pacman.py b/src/ahriman/core/alpm/pacman.py index e7f6cd0a..d5ab1753 100644 --- a/src/ahriman/core/alpm/pacman.py +++ b/src/ahriman/core/alpm/pacman.py @@ -130,8 +130,8 @@ class Pacman(LazyLogging): return # database for some reason deos not exist self.logger.info("copy pacman database %s from operating system root to ahriman's home %s", src, dst) - shutil.copy(src, dst) - self.repository_paths.chown(dst) + with self.repository_paths.preserve_owner(dst.parent): + shutil.copy(src, dst) def database_init(self, handle: Handle, repository: str, architecture: str) -> DB: """ diff --git a/src/ahriman/core/database/sqlite.py b/src/ahriman/core/database/sqlite.py index e9093625..2c6051c6 100644 --- a/src/ahriman/core/database/sqlite.py +++ b/src/ahriman/core/database/sqlite.py @@ -95,8 +95,8 @@ class SQLite( sqlite3.register_converter("json", json.loads) if self._configuration.getboolean("settings", "apply_migrations", fallback=True): - self.with_connection(lambda connection: Migrations.migrate(connection, self._configuration)) - self._repository_paths.chown(self.path) + with self._repository_paths.preserve_owner(): + self.with_connection(lambda connection: Migrations.migrate(connection, self._configuration)) def package_clear(self, package_base: str, repository_id: RepositoryId | None = None) -> None: """ diff --git a/src/ahriman/models/repository_paths.py b/src/ahriman/models/repository_paths.py index d20dff70..5a9dfebb 100644 --- a/src/ahriman/models/repository_paths.py +++ b/src/ahriman/models/repository_paths.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +import contextlib import os import shutil @@ -221,22 +222,14 @@ class RepositoryPaths(LazyLogging): stat = path.stat() return stat.st_uid, stat.st_gid - def cache_for(self, package_base: str) -> Path: - """ - get path to cached PKGBUILD and package sources for the package base - - Args: - package_base(str): package base name - - Returns: - Path: full path to directory for specified package base cache - """ - return self.cache / package_base - - def chown(self, path: Path) -> None: + def _chown(self, path: Path) -> None: """ set owner of path recursively (from root) to root owner + Notes: + More likely you don't want to call this method explicitly, consider using :func:`preserve_owner` + as context manager + Args: path(Path): path to be chown @@ -256,6 +249,45 @@ class RepositoryPaths(LazyLogging): set_owner(path) path = path.parent + def cache_for(self, package_base: str) -> Path: + """ + get path to cached PKGBUILD and package sources for the package base + + Args: + package_base(str): package base name + + Returns: + Path: full path to directory for specified package base cache + """ + return self.cache / package_base + + @contextlib.contextmanager + def preserve_owner(self, path: Path | None = None) -> Generator[None, None, None]: + """ + perform any action preserving owner for any newly created file or directory + + Args: + path(Path, optional): use this path as root instead of repository root (Default value = None) + """ + path = path or self.root + + def walk(root: Path) -> Generator[Path, None, None]: + for child in root.iterdir(): + yield child + if child in (self.chroot.parent,): + yield from child.iterdir() + elif child.is_dir(): + yield from walk(child) + + # get current filesystem and run action + previous_snapshot = set(walk(path)) + yield + + # get newly created files and directories and chown them + new_entries = set(walk(path)).difference(previous_snapshot) + for entry in new_entries: + self._chown(entry) + def tree_clear(self, package_base: str) -> None: """ clear package specific files @@ -274,12 +306,13 @@ class RepositoryPaths(LazyLogging): """ if self.repository_id.is_empty: return # do not even try to create tree in case if no repository id set - for directory in ( - self.cache, - self.chroot, - self.packages, - self.pacman, - self.repository, - ): - directory.mkdir(mode=0o755, parents=True, exist_ok=True) - self.chown(directory) + + with self.preserve_owner(): + for directory in ( + self.cache, + self.chroot, + self.packages, + self.pacman, + self.repository, + ): + directory.mkdir(mode=0o755, parents=True, exist_ok=True) diff --git a/src/ahriman/web/web.py b/src/ahriman/web/web.py index 547023d3..b1a05a6b 100644 --- a/src/ahriman/web/web.py +++ b/src/ahriman/web/web.py @@ -166,11 +166,16 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis # package cache if not repositories: raise InitializeError("No repositories configured, exiting") - database = SQLite.load(configuration) watchers: dict[RepositoryId, Watcher] = {} + configuration_path, _ = configuration.check_loaded() for repository_id in repositories: application.logger.info("load repository %s", repository_id) - client = Client.load(repository_id, configuration, database, report=False) # explicitly load local client + # load settings explicitly for architecture if any + repository_configuration = Configuration.from_path(configuration_path, repository_id) + # load database instance, because it holds identifier + database = SQLite.load(repository_configuration) + # explicitly load local client + client = Client.load(repository_id, repository_configuration, database, report=False) watchers[repository_id] = Watcher(client) application[WatcherKey] = watchers # workers cache