From 3548b434be7dd4c21ffee9617be1db9d8d8c8bed Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Fri, 14 Aug 2026 13:34:42 +0300 Subject: [PATCH] feat: full rootless setup (#165) * use local devtools configs instead of global ones * make docker image rootless * add migration notes * fix setup command * fix regress jo --- .github/scripts/setup.sh | 2 +- .github/workflows/regress.yml | 12 +-- ahriman-core/package/bin/ahriman-archbuild | 16 +++- .../share/ahriman/settings/ahriman.ini | 4 +- .../src/ahriman/application/handlers/setup.py | 56 +++++++++---- .../src/ahriman/core/build_tools/task.py | 9 +- .../core/configuration/configuration.py | 23 ++--- .../src/ahriman/core/configuration/schema.py | 7 ++ .../src/ahriman/models/repository_paths.py | 13 ++- .../handlers/test_handler_setup.py | 84 ++++++++++++++----- .../handlers/test_handler_validate.py | 2 +- .../tests/ahriman/application/test_ahriman.py | 3 +- .../ahriman/core/build_tools/test_task.py | 16 +++- docker/01-docker.ini | 2 + docker/Dockerfile | 6 +- docker/entrypoint-web.sh | 7 +- docker/entrypoint.sh | 29 +------ docs/architecture.rst | 2 +- docs/faq/docker.rst | 20 +++-- docs/faq/non-x86_64-setup.rst | 11 +++ docs/migrations/2.22.0.rst | 54 ++++++++++++ docs/migrations/index.rst | 1 + recipes/check/compose.yml | 33 ++++++-- recipes/daemon/compose.yml | 23 ++++- recipes/distributed-manual/compose.yml | 31 ++++++- recipes/distributed/compose.yml | 33 ++++++-- recipes/i686/Dockerfile | 4 + recipes/i686/compose.yml | 31 ++++++- recipes/index/compose.yml | 23 ++++- recipes/multirepo/compose.yml | 31 ++++++- recipes/oauth/compose.yml | 31 ++++++- recipes/pam/compose.yml | 29 ++++++- recipes/pull/compose.yml | 23 ++++- recipes/sign/compose.yml | 25 +++++- recipes/web/compose.yml | 31 ++++++- tests/testresources/core/ahriman.ini | 1 + 36 files changed, 581 insertions(+), 147 deletions(-) create mode 100644 docker/01-docker.ini create mode 100644 docs/migrations/2.22.0.rst diff --git a/.github/scripts/setup.sh b/.github/scripts/setup.sh index 0b95bc0c..b768a036 100755 --- a/.github/scripts/setup.sh +++ b/.github/scripts/setup.sh @@ -44,7 +44,7 @@ pacman -Qdtq | pacman -Rscn --noconfirm - # initial setup command as root [[ -z $MINIMAL_INSTALL ]] && WEB_ARGS=("--web-port" "8080") -ahriman -a x86_64 -r "github" service-setup --packager "ahriman bot " "${WEB_ARGS[@]}" +sudo -u ahriman -- ahriman -a x86_64 -r "github" service-setup --packager "ahriman bot " "${WEB_ARGS[@]}" # enable services systemctl enable ahriman@x86_64-github.timer if [[ -z $MINIMAL_INSTALL ]]; then diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index c0ebb3a5..2caebd0c 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -18,8 +18,8 @@ jobs: image: arcan1s/ahriman:edge env: AHRIMAN_PORT: 8080 - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman-web.sock - options: --privileged --entrypoint entrypoint-web + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman-web.sock + options: --privileged --user root --entrypoint entrypoint-web ports: - 8080 volumes: @@ -31,8 +31,8 @@ jobs: AHRIMAN_DEBUG: y AHRIMAN_OUTPUT: console AHRIMAN_PORT: 8080 - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman-web.sock - options: --privileged + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman-web.sock + options: --privileged --user root volumes: - repo:/var/lib/ahriman @@ -40,7 +40,9 @@ jobs: - run: pacman -Sy - name: Init repository - run: entrypoint help + run: | + chown ahriman:ahriman /var/lib/ahriman + sudo -E -u ahriman -- entrypoint help - name: Print configuration run: | diff --git a/ahriman-core/package/bin/ahriman-archbuild b/ahriman-core/package/bin/ahriman-archbuild index e1999e35..acacb055 100755 --- a/ahriman-core/package/bin/ahriman-archbuild +++ b/ahriman-core/package/bin/ahriman-archbuild @@ -7,16 +7,19 @@ usage() { echo "Usage: $cmd [options] -- [archbuild args]" echo " -r Repository name" echo " -a Repository architecture" + echo " -c Read devtools pacman configurations from this directory" exit 1 } repository= architecture= +pacman_config_dir= -while getopts ":r:a:" arg; do +while getopts ":r:a:c:" arg; do case "$arg" in r) repository="$OPTARG" ;; a) architecture="$OPTARG" ;; + c) pacman_config_dir="$OPTARG" ;; *) usage ;; esac done @@ -28,6 +31,17 @@ fi source "/usr/share/devtools/lib/archroot.sh" check_root "SOURCE_DATE_EPOCH,SRCDEST,SRCPKGDEST,PKGDEST,LOGDEST,NPROC,MAKEFLAGS,PACKAGER,GNUPGHOME" "${BASH_SOURCE[0]}" "$@" +# because devtools doesn't allow to read configuration from custom path +# here is a workaround, which uses unshare to bind-mount directory with configuration files +# for specific process +if [[ -n $pacman_config_dir && -z ${AHRIMAN_ARCHBUILD_MOUNTED:-} ]]; then + export AHRIMAN_ARCHBUILD_MOUNTED=1 + exec unshare --mount --propagation private bash -e -c ' + mount --bind "$0" /usr/share/devtools/pacman.conf.d + exec "$@" + ' "$(readlink -f "$pacman_config_dir")" "${BASH_SOURCE[0]}" "$@" +fi + exec bash -c ' source "$1" "${@:2}" ' "${repository}-${architecture}-build" "archbuild" "${@:$OPTIND}" diff --git a/ahriman-core/package/share/ahriman/settings/ahriman.ini b/ahriman-core/package/share/ahriman/settings/ahriman.ini index 67c92b3e..2b2187d6 100644 --- a/ahriman-core/package/share/ahriman/settings/ahriman.ini +++ b/ahriman-core/package/share/ahriman/settings/ahriman.ini @@ -1,6 +1,6 @@ [settings] ; Relative path to directory with configuration files overrides. Overrides will be applied in alphabetic order. -include = ahriman.ini.d $HOME/.config/ahriman.ini.d +include = ahriman.ini.d ${repository:root}/.config/ahriman/ahriman.ini.d ; Relative path to configuration used by logging package. logging = ahriman.ini.d/logging.ini ; Perform database migrations on the application start. Do not touch this option unless you know what you are doing. @@ -34,6 +34,8 @@ retry_backoff = 1.0 [build] ; List of additional flags passed to archbuild command. ;archbuild_flags = +; Path to local directory with devtools configuration files, which will be bind-mounted for devtools. +devtools_configs = ${repository:root}/.config/ahriman/pacman.conf.d ; Path to build command. devtools_wrapper = ahriman-archbuild ; List of packages to be ignored during automatic updates. diff --git a/ahriman-core/src/ahriman/application/handlers/setup.py b/ahriman-core/src/ahriman/application/handlers/setup.py index 40d87c5a..6212c7c7 100644 --- a/ahriman-core/src/ahriman/application/handlers/setup.py +++ b/ahriman-core/src/ahriman/application/handlers/setup.py @@ -28,7 +28,7 @@ from urllib.parse import quote_plus as url_encode from ahriman.application.application import Application from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration -from ahriman.core.exceptions import MissingArchitectureError +from ahriman.core.exceptions import InitializeError, MissingArchitectureError from ahriman.core.utils import enum_values from ahriman.models.repository_id import RepositoryId from ahriman.models.sign_settings import SignSettings @@ -63,18 +63,20 @@ class Setup(Handler): if args.architecture is None or args.repository is None: raise MissingArchitectureError(args.command) - Setup.configuration_create_ahriman(args, repository_id, configuration) + target_directory = Setup.configuration_create_directory(configuration) + Setup.configuration_create_ahriman(args, repository_id, configuration, target_directory) configuration.reload() application = Application(repository_id, configuration, report=report) + paths = application.repository.paths - # basically we create configuration here as root, but it is ok, because those files are only used for reading - 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) + repository_server = f"file://{paths.repository}" if args.server is None else args.server + target_directory = paths.ensure_exists(configuration.getpath("build", "devtools_configs")) + Setup.configuration_create_devtools(repository_id, args.from_configuration, target_directory, args.mirror, + args.multilib, repository_server) # finish initialization - with application.repository.paths.preserve_owner(): + with paths.preserve_owner(): application.repository.repo.init() # lazy database sync application.repository.pacman.handle # pylint: disable=pointless-statement @@ -114,12 +116,12 @@ class Setup(Handler): parser.add_argument("--web-port", help="port of the web service", type=int) parser.add_argument("--web-unix-socket", help="path to unix socket used for interprocess communications", type=Path) - parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True) + parser.set_defaults(lock=None, quiet=True, report=False) return parser @staticmethod def configuration_create_ahriman(args: argparse.Namespace, repository_id: RepositoryId, - root: Configuration) -> None: + root: Configuration, target_directory: Path) -> None: """ create service specific configuration @@ -127,6 +129,7 @@ class Setup(Handler): args(argparse.Namespace): command line args repository_id(RepositoryId): repository unique identifier root(Configuration): root configuration instance + target_directory(Path): path to directory where configuration files will be written """ configuration = Configuration() @@ -164,15 +167,13 @@ class Setup(Handler): if args.generate_salt: configuration.set_option("auth", "salt", User.generate_password(20)) - include_path = next(path for path in root.getpathlist("settings", "include") if os.access(path, os.W_OK)) - (include_path / "00-setup-overrides.ini").unlink(missing_ok=True) # remove old-style configuration - target = include_path / f"00-setup-overrides-{repository_id.id}.ini" + target = target_directory / f"00-setup-overrides-{repository_id.id}.ini" with target.open("w", encoding="utf8") as ahriman_configuration: configuration.write(ahriman_configuration) @staticmethod - def configuration_create_devtools(repository_id: RepositoryId, source: Path, mirror: str | None, - multilib: bool, repository_server: str) -> None: + def configuration_create_devtools(repository_id: RepositoryId, source: Path, target_directory: Path, + mirror: str | None, multilib: bool, repository_server: str) -> None: """ create configuration for devtools based on ``source`` configuration @@ -182,6 +183,7 @@ class Setup(Handler): Args: repository_id(RepositoryId): repository unique identifier source(Path): path to source configuration file + target_directory(Path): path to directory where configuration files will be written mirror(str | None): link to package server mirror multilib(bool): add or do not multilib repository to the configuration repository_server(str): url of the repository @@ -216,8 +218,32 @@ class Setup(Handler): configuration.set_option(repository_id.name, "SigLevel", "Never") # we don't care configuration.set_option(repository_id.name, "Server", repository_server) - target = source.parent / f"{repository_id.name}-{repository_id.architecture}.conf" + target = target_directory / f"{repository_id.name}-{repository_id.architecture}.conf" with target.open("w", encoding="utf8") as devtools_configuration: configuration.write(devtools_configuration) + @staticmethod + def configuration_create_directory(root: Configuration) -> Path: + """ + create directory for includes + + Args: + root(Configuration): root configuration instance + + Returns: + Path: path to first writable directory + + Raises: + InitializeError: if no writable directories have been found + """ + for include_path in root.include: + try: + directory = root.repository_paths.ensure_exists(include_path) + if os.access(directory, os.W_OK | os.X_OK): + return directory + except OSError: + continue + + raise InitializeError("No writable include directory found") + arguments = [_set_service_setup_parser] diff --git a/ahriman-core/src/ahriman/core/build_tools/task.py b/ahriman-core/src/ahriman/core/build_tools/task.py index 3178a556..2cdd22ac 100644 --- a/ahriman-core/src/ahriman/core/build_tools/task.py +++ b/ahriman-core/src/ahriman/core/build_tools/task.py @@ -38,6 +38,7 @@ class Task(LazyLogging): Attributes: archbuild_flags(list[str]): command flags for archbuild command build_command(list[str]): build command + devtools_configs(Path): path to local directory with devtools configuration files include_debug_packages(bool): whether to include debug packages or not make_flags(str): MAKEFLAGS variable for makepkg command makechrootpkg_flags(list[str]): command flags for makechrootpkg command @@ -64,6 +65,7 @@ class Task(LazyLogging): self.archbuild_flags = configuration.getlist("build", "archbuild_flags", fallback=[]) self.build_command = configuration.getlist("build", "devtools_wrapper") + self.devtools_configs = configuration.getpath("build", "devtools_configs") self._legacy_build_command = configuration.getlist("build", "build_command", fallback=[]) self.include_debug_packages = configuration.getboolean("build", "include_debug_packages", fallback=True) # even though this option is declared as list, there is no need to read it as list, @@ -116,7 +118,12 @@ class Task(LazyLogging): """ command = self._legacy_build_command[:] if not command: - command = self.build_command + ["-r", self.repository_id.name, "-a", self.repository_id.architecture, "--"] + command = self.build_command + [ + "-r", self.repository_id.name, + "-a", self.repository_id.architecture, + "-c", str(self.devtools_configs), + "--", + ] command.extend(["-r", str(self.paths.chroot)] + self.archbuild_flags) # archbuild flags command.extend(["--", "-D", str(self.paths.archive)] + self.makechrootpkg_flags) # makechrootpkg flags diff --git a/ahriman-core/src/ahriman/core/configuration/configuration.py b/ahriman-core/src/ahriman/core/configuration/configuration.py index 405c8090..7090c924 100644 --- a/ahriman-core/src/ahriman/core/configuration/configuration.py +++ b/ahriman-core/src/ahriman/core/configuration/configuration.py @@ -108,6 +108,16 @@ class Configuration(configparser.RawConfigParser): _, repository_id = self.check_loaded() return repository_id.architecture + @property + def include(self) -> list[Path]: + """ + get full path to include directory + + Returns: + list[Path]: paths to directories with configuration includes + """ + return self.getpathlist("settings", "include") + @property def logging_path(self) -> Path: """ @@ -322,20 +332,11 @@ class Configuration(configparser.RawConfigParser): self.includes = [] # reset state try: - # raw processing to make sure that options are applied correctly - include_directories = shlex.split(self.get("settings", "include", raw=True)) + include_directories = self.include except (configparser.NoOptionError, configparser.NoSectionError): return - for directory in include_directories: - value = self._interpolation.before_get( # type: ignore[attr-defined] - self, - "settings", - "include", - directory, - self._unify_values("settings", None), # type: ignore[attr-defined] - ) - path = self._convert_path(value) + for path in include_directories: # pylint: disable=not-an-iterable if not path.is_dir(): continue diff --git a/ahriman-core/src/ahriman/core/configuration/schema.py b/ahriman-core/src/ahriman/core/configuration/schema.py index ca7a5bc2..95196fce 100644 --- a/ahriman-core/src/ahriman/core/configuration/schema.py +++ b/ahriman-core/src/ahriman/core/configuration/schema.py @@ -200,6 +200,13 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = { "empty": False, }, }, + "devtools_configs": { + "type": "path", + "coerce": "absolute_path", + "required": True, + "path_exists": True, + "path_type": "dir", + }, "devtools_wrapper": { "type": "list", "coerce": "list", diff --git a/ahriman-core/src/ahriman/models/repository_paths.py b/ahriman-core/src/ahriman/models/repository_paths.py index f723a58c..9585f5ef 100644 --- a/ahriman-core/src/ahriman/models/repository_paths.py +++ b/ahriman-core/src/ahriman/models/repository_paths.py @@ -103,7 +103,7 @@ class RepositoryPaths(LazyLogging): Returns: Path: path to directory in which build process is run """ - uid, _ = owner(self.root) + uid, _ = self.root_owner return self.chroot / f"{self.repository_id.name}-{self.repository_id.architecture}" / getpwuid(uid).pw_name @property @@ -127,6 +127,16 @@ class RepositoryPaths(LazyLogging): # for the chroot directory devtools will create own tree, and we don't have to specify architecture here return self.root / "chroot" / self.repository_id.name + @property + def configs(self) -> Path: + """ + get directory for local configuration files + + Returns: + Path: full path to local configuration directory + """ + return self.root / ".config" / "ahriman" + @property def packages(self) -> Path: """ @@ -323,6 +333,7 @@ class RepositoryPaths(LazyLogging): self.archive, self.cache, self.chroot, + self.configs, self.packages, self.pacman, self.repository, diff --git a/ahriman-core/tests/ahriman/application/handlers/test_handler_setup.py b/ahriman-core/tests/ahriman/application/handlers/test_handler_setup.py index f3e05102..aa1b5765 100644 --- a/ahriman-core/tests/ahriman/application/handlers/test_handler_setup.py +++ b/ahriman-core/tests/ahriman/application/handlers/test_handler_setup.py @@ -1,5 +1,6 @@ import argparse import multiprocessing +import os import pytest from pathlib import Path @@ -11,9 +12,8 @@ from urllib.parse import quote_plus as url_encode from ahriman.application.handlers.setup import Setup from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite -from ahriman.core.exceptions import MissingArchitectureError +from ahriman.core.exceptions import InitializeError, MissingArchitectureError from ahriman.core.repository import Repository -from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.sign_settings import SignSettings @@ -51,8 +51,11 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: must run command """ args = _default_args(args) + local = Path("local") mocker.patch("ahriman.core.database.SQLite.load", return_value=database) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) + mkdir_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_directory", + return_value=local) ahriman_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman") devtools_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_devtools") init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init") @@ -61,9 +64,16 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: _, repository_id = configuration.check_loaded() Setup.run(args, repository_id, configuration, report=False) owner_guard_mock.assert_called_once_with() - ahriman_configuration_mock.assert_called_once_with(args, repository_id, configuration) + mkdir_mock.assert_called_once_with(configuration) + ahriman_configuration_mock.assert_called_once_with(args, repository_id, configuration, local) devtools_configuration_mock.assert_called_once_with( - repository_id, args.from_configuration, args.mirror, args.multilib, f"file://{repository_paths.repository}") + repository_id, + args.from_configuration, + configuration.getpath("build", "devtools_configs"), + args.mirror, + args.multilib, + f"file://{repository_paths.repository}", + ) init_mock.assert_called_once_with() @@ -102,11 +112,17 @@ def test_run_with_server(args: argparse.Namespace, configuration: Configuration, _, repository_id = configuration.check_loaded() Setup.run(args, repository_id, configuration, report=False) devtools_configuration_mock.assert_called_once_with( - repository_id, args.from_configuration, args.mirror, args.multilib, "server") + repository_id, + args.from_configuration, + configuration.getpath("build", "devtools_configs"), + args.mirror, + args.multilib, + "server", + ) -def test_configuration_create_ahriman(args: argparse.Namespace, configuration: Configuration, - repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: +def test_configuration_create_ahriman(args: argparse.Namespace, configuration: Configuration, tmp_path: Path, + mocker: MockerFixture) -> None: """ must create configuration for the service """ @@ -114,10 +130,9 @@ def test_configuration_create_ahriman(args: argparse.Namespace, configuration: C mocker.patch("pathlib.Path.open") set_option_mock = mocker.patch("ahriman.core.configuration.Configuration.set_option") write_mock = mocker.patch("ahriman.core.configuration.Configuration.write") - remove_mock = mocker.patch("pathlib.Path.unlink", autospec=True) _, repository_id = configuration.check_loaded() - Setup.configuration_create_ahriman(args, repository_id, configuration) + Setup.configuration_create_ahriman(args, repository_id, configuration, tmp_path) set_option_mock.assert_has_calls([ MockCall("repository", "name", repository_id.name), MockCall(Configuration.section_name("build", repository_id.name, repository_id.architecture), @@ -139,15 +154,10 @@ def test_configuration_create_ahriman(args: argparse.Namespace, configuration: C MockCall("auth", "salt", pytest.helpers.anyvar(str, strict=True)), ]) write_mock.assert_called_once_with(pytest.helpers.anyvar(int)) - remove_mock.assert_called_once_with( - next( - path for path in configuration.getpathlist("settings", "include")) / - "00-setup-overrides.ini", - missing_ok=True) def test_configuration_create_ahriman_no_multilib(args: argparse.Namespace, configuration: Configuration, - mocker: MockerFixture) -> None: + tmp_path: Path, mocker: MockerFixture) -> None: """ must create configuration for the service without multilib repository """ @@ -158,14 +168,14 @@ def test_configuration_create_ahriman_no_multilib(args: argparse.Namespace, conf set_option_mock = mocker.patch("ahriman.core.configuration.Configuration.set_option") _, repository_id = configuration.check_loaded() - Setup.configuration_create_ahriman(args, repository_id, configuration) + Setup.configuration_create_ahriman(args, repository_id, configuration, tmp_path) set_option_mock.assert_has_calls([ MockCall(Configuration.section_name("alpm", repository_id.name, repository_id.architecture), "mirror", args.mirror), ]) # non-strict check called intentionally -def test_configuration_create_devtools(args: argparse.Namespace, configuration: Configuration, +def test_configuration_create_devtools(args: argparse.Namespace, configuration: Configuration, tmp_path: Path, mocker: MockerFixture) -> None: """ must create configuration for the devtools @@ -177,12 +187,12 @@ def test_configuration_create_devtools(args: argparse.Namespace, configuration: write_mock = mocker.patch("ahriman.core.configuration.Configuration.write") _, repository_id = configuration.check_loaded() - Setup.configuration_create_devtools(repository_id, args.from_configuration, None, args.multilib, "server") + Setup.configuration_create_devtools(repository_id, args.from_configuration, tmp_path, None, args.multilib, "server") add_section_mock.assert_has_calls([MockCall("multilib"), MockCall(repository_id.name)]) write_mock.assert_called_once_with(pytest.helpers.anyvar(int)) -def test_configuration_create_devtools_mirror(args: argparse.Namespace, configuration: Configuration, +def test_configuration_create_devtools_mirror(args: argparse.Namespace, configuration: Configuration, tmp_path: Path, mocker: MockerFixture) -> None: """ must create configuration for the devtools with mirror set explicitly @@ -202,14 +212,21 @@ def test_configuration_create_devtools_mirror(args: argparse.Namespace, configur set_option_mock = mocker.patch("ahriman.core.configuration.Configuration.set_option") _, repository_id = configuration.check_loaded() - Setup.configuration_create_devtools(repository_id, args.from_configuration, args.mirror, args.multilib, "server") + Setup.configuration_create_devtools( + repository_id, + args.from_configuration, + tmp_path, + args.mirror, + args.multilib, + "server", + ) get_mock.assert_has_calls([MockCall("core", "Include", fallback=None), MockCall("extra", "Include", fallback=None)]) remove_option_mock.assert_called_once_with("core", "Include") set_option_mock.assert_has_calls([MockCall("core", "Server", args.mirror)]) # non-strict check called intentionally def test_configuration_create_devtools_no_multilib(args: argparse.Namespace, configuration: Configuration, - mocker: MockerFixture) -> None: + tmp_path: Path, mocker: MockerFixture) -> None: """ must create configuration for the devtools without multilib """ @@ -219,10 +236,33 @@ def test_configuration_create_devtools_no_multilib(args: argparse.Namespace, con write_mock = mocker.patch("ahriman.core.configuration.Configuration.write") _, repository_id = configuration.check_loaded() - Setup.configuration_create_devtools(repository_id, args.from_configuration, args.mirror, False, "server") + Setup.configuration_create_devtools(repository_id, args.from_configuration, tmp_path, args.mirror, False, "server") write_mock.assert_called_once_with(pytest.helpers.anyvar(int)) +def test_configuration_create_directory(configuration: Configuration, mocker: MockerFixture) -> None: + """ + must create writable directory for includes + """ + configuration.set_option("settings", "include", f"/path1 /path2 /path3") + mkdir_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.ensure_exists", + side_effect=[OSError, "/path2", "/path3"]) + access_mock = mocker.patch("os.access", side_effect=[False, True]) + + assert Setup.configuration_create_directory(configuration) == "/path3" + mkdir_mock.assert_has_calls([MockCall(Path("/path1")), MockCall(Path("/path2")), MockCall(Path("/path3"))]) + access_mock.assert_has_calls([MockCall("/path2", os.W_OK | os.X_OK), MockCall("/path3", os.W_OK | os.X_OK)]) + + +def test_configuration_create_directory_no_writable(configuration: Configuration, mocker: MockerFixture) -> None: + """ + must raise InitializeError if no writable directories found + """ + mocker.patch("ahriman.models.repository_paths.RepositoryPaths.ensure_exists", side_effect=OSError) + with pytest.raises(InitializeError): + Setup.configuration_create_directory(configuration) + + def test_disallow_multi_architecture_run() -> None: """ must not allow multi architecture run diff --git a/ahriman-core/tests/ahriman/application/handlers/test_handler_validate.py b/ahriman-core/tests/ahriman/application/handlers/test_handler_validate.py index 076e6dba..358bf7b7 100644 --- a/ahriman-core/tests/ahriman/application/handlers/test_handler_validate.py +++ b/ahriman-core/tests/ahriman/application/handlers/test_handler_validate.py @@ -63,7 +63,7 @@ def test_run_default(args: argparse.Namespace, configuration: Configuration) -> default = Configuration.from_path(Configuration.SYSTEM_CONFIGURATION_PATH, repository_id) # copy autogenerated values - for section, key in (("repository", "root"),): + for section, key in (("repository", "root"), ("build", "devtools_configs")): value = configuration.get(section, key) default.set_option(section, key, value) diff --git a/ahriman-core/tests/ahriman/application/test_ahriman.py b/ahriman-core/tests/ahriman/application/test_ahriman.py index ddbe3220..a6856fae 100644 --- a/ahriman-core/tests/ahriman/application/test_ahriman.py +++ b/ahriman-core/tests/ahriman/application/test_ahriman.py @@ -1488,7 +1488,7 @@ def test_subparsers_service_run_option_repository(parser: argparse.ArgumentParse def test_subparsers_service_setup(parser: argparse.ArgumentParser) -> None: """ - service-setup command must imply lock, quiet, report and unsafe + service-setup command must imply lock, quiet and report """ args = parser.parse_args(["-a", "x86_64", "-r", "repo", "service-setup", "--packager", "ahriman bot "]) @@ -1497,7 +1497,6 @@ def test_subparsers_service_setup(parser: argparse.ArgumentParser) -> None: assert args.quiet assert not args.report assert args.repository == "repo" - assert args.unsafe def test_subparsers_service_setup_option_from_configuration(parser: argparse.ArgumentParser) -> None: diff --git a/ahriman-core/tests/ahriman/core/build_tools/test_task.py b/ahriman-core/tests/ahriman/core/build_tools/test_task.py index 7aa40f14..b4579ec9 100644 --- a/ahriman-core/tests/ahriman/core/build_tools/test_task.py +++ b/ahriman-core/tests/ahriman/core/build_tools/test_task.py @@ -54,7 +54,9 @@ def test_build(task_ahriman: Task, mocker: MockerFixture) -> None: assert task_ahriman.build(local) == [task_ahriman.package.base] check_output_mock.assert_called_once_with( "ahriman-archbuild", - "-r", task_ahriman.repository_id.name, "-a", task_ahriman.repository_id.architecture, + "-r", task_ahriman.repository_id.name, + "-a", task_ahriman.repository_id.architecture, + "-c", str(task_ahriman.devtools_configs), "--", "-r", str(task_ahriman.paths.chroot), "--", "-D", str(task_ahriman.paths.archive), "--", "--skippgpcheck", @@ -81,7 +83,9 @@ def test_build_environment(task_ahriman: Task, mocker: MockerFixture) -> None: task_ahriman.build(local, **environment, empty=None) check_output_mock.assert_called_once_with( "ahriman-archbuild", - "-r", task_ahriman.repository_id.name, "-a", task_ahriman.repository_id.architecture, + "-r", task_ahriman.repository_id.name, + "-a", task_ahriman.repository_id.architecture, + "-c", str(task_ahriman.devtools_configs), "--", "-r", str(task_ahriman.paths.chroot), "--", "-D", str(task_ahriman.paths.archive), "--", "--skippgpcheck", @@ -106,7 +110,9 @@ def test_build_makeflags(task_ahriman: Task, mocker: MockerFixture) -> None: task_ahriman.build(local) check_output_mock.assert_called_once_with( "ahriman-archbuild", - "-r", task_ahriman.repository_id.name, "-a", task_ahriman.repository_id.architecture, + "-r", task_ahriman.repository_id.name, + "-a", task_ahriman.repository_id.architecture, + "-c", str(task_ahriman.devtools_configs), "--", "-r", str(task_ahriman.paths.chroot), "--", "-D", str(task_ahriman.paths.archive), "--", "--skippgpcheck", @@ -130,7 +136,9 @@ def test_build_dry_run(task_ahriman: Task, mocker: MockerFixture) -> None: task_ahriman.build(local, dry_run=True) check_output_mock.assert_called_once_with( "ahriman-archbuild", - "-r", task_ahriman.repository_id.name, "-a", task_ahriman.repository_id.architecture, + "-r", task_ahriman.repository_id.name, + "-a", task_ahriman.repository_id.architecture, + "-c", str(task_ahriman.devtools_configs), "--", "-r", str(task_ahriman.paths.chroot), "--", "-D", str(task_ahriman.paths.archive), "--", "--skippgpcheck", diff --git a/docker/01-docker.ini b/docker/01-docker.ini new file mode 100644 index 00000000..c03ba1d3 --- /dev/null +++ b/docker/01-docker.ini @@ -0,0 +1,2 @@ +[web] +host = $AHRIMAN_HOST diff --git a/docker/Dockerfile b/docker/Dockerfile index fd34a43d..4c8b82f2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -80,7 +80,6 @@ FROM archlinux:base AS ahriman # image configuration ENV AHRIMAN_ARCHITECTURE="x86_64" ENV AHRIMAN_DEBUG="" -ENV AHRIMAN_FORCE_ROOT="" ENV AHRIMAN_HOST="0.0.0.0" ENV AHRIMAN_MULTILIB="yes" ENV AHRIMAN_OUTPUT="" @@ -91,7 +90,6 @@ ENV AHRIMAN_POSTSETUP_COMMAND="" ENV AHRIMAN_PRESETUP_COMMAND="" ENV AHRIMAN_REPOSITORY="aur" ENV AHRIMAN_REPOSITORY_SERVER="" -ENV AHRIMAN_REPOSITORY_ROOT="/var/lib/ahriman/ahriman" ENV AHRIMAN_UNIX_SOCKET="" ENV AHRIMAN_USER="ahriman" ENV AHRIMAN_VALIDATE_CONFIGURATION="yes" @@ -135,11 +133,15 @@ RUN find "/var/cache/pacman/pkg" "/var/lib/pacman/sync" -type "f,l" -delete && \ VOLUME ["/var/lib/ahriman"] # minimal runtime ahriman setup +RUN systemd-machine-id-setup +COPY "docker/01-docker.ini" "/etc/ahriman.ini.d/01-docker.ini" ## FIXME since 1.0.4 devtools requires dbus to be run, which doesn't work now in container COPY "docker/systemd-nspawn.sh" "/usr/local/bin/systemd-nspawn" ## entrypoint setup COPY "docker/entrypoint.sh" "/usr/local/bin/entrypoint" COPY "docker/entrypoint-web.sh" "/usr/local/bin/entrypoint-web" + +USER ahriman ENTRYPOINT ["entrypoint"] # default command CMD ["repo-update", "--refresh"] diff --git a/docker/entrypoint-web.sh b/docker/entrypoint-web.sh index af6532ab..79f331a6 100755 --- a/docker/entrypoint-web.sh +++ b/docker/entrypoint-web.sh @@ -2,4 +2,9 @@ # Special workaround for running web service in github actions, must not be usually used in real environment, # consider running web command explicitly instead -exec entrypoint web "$@" \ No newline at end of file +if (( EUID == 0 )); then + chown ahriman:ahriman /var/lib/ahriman + exec sudo -E -u ahriman -- entrypoint web "$@" +fi + +exec entrypoint web "$@" diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index b791912d..70722cba 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -3,30 +3,15 @@ set -e [ -n "$AHRIMAN_DEBUG" ] && set -x -# configuration tune -cat < "/etc/ahriman.ini.d/01-docker.ini" -[repository] -root = $AHRIMAN_REPOSITORY_ROOT - -[web] -host = $AHRIMAN_HOST - -EOF - AHRIMAN_DEFAULT_ARGS=("--architecture" "$AHRIMAN_ARCHITECTURE") AHRIMAN_DEFAULT_ARGS+=("--repository" "$AHRIMAN_REPOSITORY") if [ -n "$AHRIMAN_OUTPUT" ]; then AHRIMAN_DEFAULT_ARGS+=("--log-handler" "$AHRIMAN_OUTPUT") fi -# create repository root inside the [[mounted]] directory and set correct ownership -[ -d "$AHRIMAN_REPOSITORY_ROOT" ] || mkdir "$AHRIMAN_REPOSITORY_ROOT" -chown "$AHRIMAN_USER":"$AHRIMAN_USER" "$AHRIMAN_REPOSITORY_ROOT" - # create .gnupg directory which is required for keys AHRIMAN_GNUPG_HOME="$(getent passwd "$AHRIMAN_USER" | cut -d : -f 6)/.gnupg" [ -d "$AHRIMAN_GNUPG_HOME" ] || mkdir -m700 "$AHRIMAN_GNUPG_HOME" -chown "$AHRIMAN_USER":"$AHRIMAN_USER" "$AHRIMAN_GNUPG_HOME" # run built-in setup command AHRIMAN_SETUP_ARGS=("--build-as-user" "$AHRIMAN_USER") @@ -54,19 +39,7 @@ ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" service-setup "${AHRIMAN_SETUP_ARGS[@]}" # validate configuration if set [ -n "$AHRIMAN_VALIDATE_CONFIGURATION" ] && ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" service-config-validate --exit-code -# create machine-id which is required by build tools -systemd-machine-id-setup &> /dev/null - # special workaround to emulate /bin/bash entrypoint if first argument starts with / [[ "$1" =~ ^/.* ]] && exec "$@" -# if AHRIMAN_FORCE_ROOT is set or command is unsafe we can run without sudo -# otherwise we prepend executable by sudo command -if [ -n "$AHRIMAN_FORCE_ROOT" ]; then - AHRIMAN_EXECUTABLE=("ahriman") -elif ahriman help-commands-unsafe -- "$@" &> /dev/null; then - AHRIMAN_EXECUTABLE=("sudo" "-E" "-u" "$AHRIMAN_USER" "--" "ahriman") -else - AHRIMAN_EXECUTABLE=("ahriman") -fi -exec "${AHRIMAN_EXECUTABLE[@]}" "${AHRIMAN_DEFAULT_ARGS[@]}" "$@" +exec ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" "$@" diff --git a/docs/architecture.rst b/docs/architecture.rst index 88cff60c..4a473869 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -116,7 +116,7 @@ Filesystem tree The application supports two types of trees, one is for the legacy configuration (when there were no explicit repository name configuration available) and another one is the new-style tree. This document describes only new-style tree in order to avoid deprecated structures. -Having default root as ``/var/lib/ahriman`` (differs from container though), the directory structure is the following: +Having default root as ``/var/lib/ahriman``, the directory structure is the following: .. code-block:: diff --git a/docs/faq/docker.rst b/docs/faq/docker.rst index 1d92ad63..c3cf2ca7 100644 --- a/docs/faq/docker.rst +++ b/docs/faq/docker.rst @@ -22,6 +22,12 @@ In order to make data available outside of container, you would need to mount lo docker run --privileged -v /path/to/local/repo:/var/lib/ahriman -v /path/to/overrides/overrides.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest +The volume must have correct rights, e.g.: + +.. code-block:: shell + + chown 643:643 /path/to/local/repo + The action can be specified during run, e.g.: .. code-block:: shell @@ -59,19 +65,17 @@ The following environment variables are supported: * ``AHRIMAN_ARCHITECTURE`` - architecture of the repository, default is ``x86_64``. * ``AHRIMAN_DEBUG`` - if set all commands will be logged to console. -* ``AHRIMAN_FORCE_ROOT`` - force run ahriman as root instead of guessing by subcommand. * ``AHRIMAN_HOST`` - host for the web interface, default is ``0.0.0.0``. * ``AHRIMAN_MULTILIB`` - if set (default) multilib repository will be used, disabled otherwise. * ``AHRIMAN_OUTPUT`` - controls logging handler, e.g. ``syslog``, ``console``. The name must be found in logging configuration. Note that if ``syslog`` handler is used you will need to mount ``/dev/log`` inside container because it is not available there. * ``AHRIMAN_PACKAGER`` - packager name from which packages will be built, default is ``ahriman bot ``. * ``AHRIMAN_PACMAN_MIRROR`` - override pacman mirror server if set. * ``AHRIMAN_PORT`` - HTTP server port if any, default is empty. -* ``AHRIMAN_POSTSETUP_COMMAND`` - if set, the command which will be called (as root) after the setup command, but before any other actions. -* ``AHRIMAN_PRESETUP_COMMAND`` - if set, the command which will be called (as root) right before the setup command. +* ``AHRIMAN_POSTSETUP_COMMAND`` - if set, the command which will be called after the setup command, but before any other actions. +* ``AHRIMAN_PRESETUP_COMMAND`` - if set, the command which will be called right before the setup command. * ``AHRIMAN_REPOSITORY`` - repository name, default is ``aur``. * ``AHRIMAN_REPOSITORY_SERVER`` - optional override for the repository URL. Useful if you would like to download packages from remote instead of local filesystem. -* ``AHRIMAN_REPOSITORY_ROOT`` - repository root. Because of filesystem rights it is required to override default repository root. By default, it uses ``ahriman`` directory inside ahriman's home, which can be passed as mount volume. -* ``AHRIMAN_UNIX_SOCKET`` - full path to unix socket which is used by web server, default is empty. Note that more likely you would like to put it inside ``AHRIMAN_REPOSITORY_ROOT`` directory (e.g. ``/var/lib/ahriman/ahriman/ahriman-web.sock``) or to ``/run/ahriman``. +* ``AHRIMAN_UNIX_SOCKET`` - full path to unix socket which is used by web server, default is empty. Note that more likely you would like to put it inside repository root directory (e.g. ``/var/lib/ahriman/ahriman-web.sock``) or to ``/run/ahriman``. * ``AHRIMAN_USER`` - ahriman user, usually must not be overwritten, default is ``ahriman``. * ``AHRIMAN_VALIDATE_CONFIGURATION`` - if set (default) validate service configuration. @@ -99,7 +103,7 @@ For that you would need to have web container instance running forever; it can b .. code-block:: shell - docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman/ahriman-web.sock -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest + docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman-web.sock -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest Note about ``AHRIMAN_PORT`` environment variable which is required in order to enable web service. An additional port bind by ``-p 8080:8080`` is required to pass docker port outside of container. @@ -109,7 +113,7 @@ If you are using ``AHRIMAN_UNIX_SOCKET`` variable, for every next container run .. code-block:: shell - docker run --privileged -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman/ahriman-web.sock -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest + docker run --privileged -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman-web.sock -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest Otherwise, you would need to pass ``AHRIMAN_PORT`` and mount container network to the host system (``--net=host``), e.g.: @@ -128,7 +132,7 @@ In order to create configuration for additional repositories, the ``AHRIMAN_POST .. code-block:: shell - docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman/ahriman-web.sock -e AHRIMAN_POSTSETUP_COMMAND="ahriman --architecture x86_64 --repository aur-v2 service-setup --build-as-user ahriman --packager 'ahriman bot '" -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest + docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman-web.sock -e AHRIMAN_POSTSETUP_COMMAND="ahriman --architecture x86_64 --repository aur-v2 service-setup --build-as-user ahriman --packager 'ahriman bot '" -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest The command above will also create configuration for the repository named ``aur-v2``. diff --git a/docs/faq/non-x86_64-setup.rst b/docs/faq/non-x86_64-setup.rst index c5eed091..d2e4c6da 100644 --- a/docs/faq/non-x86_64-setup.rst +++ b/docs/faq/non-x86_64-setup.rst @@ -55,6 +55,13 @@ There are two possible ways to achieve same setup, by using docker container. Th FROM arcan1s/ahriman:latest +#. + Switch to ``root`` user: + + .. code-block:: dockerfile + + USER root + #. Init pacman keys. This command is required in order to populate distribution keys: @@ -78,12 +85,16 @@ There are two possible ways to achieve same setup, by using docker container. Th FROM arcan1s/ahriman:latest + USER root + RUN pacman-key --init RUN pacman --noconfirm -Sy wget RUN wget https://pool.mirror.archlinux32.org/i686/extra/devtools-20221208-1.2-any.pkg.tar.zst && pacman --noconfirm -U devtools-20221208-1.2-any.pkg.tar.zst RUN wget https://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-20230705-1.0-any.pkg.tar.zst && pacman --noconfirm -U archlinux32-keyring-20230705-1.0-any.pkg.tar.zst + USER ahriman + #. After that you can build you own container, e.g.: diff --git a/docs/migrations/2.22.0.rst b/docs/migrations/2.22.0.rst new file mode 100644 index 00000000..d33b98de --- /dev/null +++ b/docs/migrations/2.22.0.rst @@ -0,0 +1,54 @@ +To 2.22.0 +--------- + +This release stores newly generated configuration files in the repository root and changes the docker image to run as the ``ahriman`` user. Existing system-wide configuration files remain supported, so regular installations do not require manual intervention. Docker installations require data migration. + +Regular installation +^^^^^^^^^^^^^^^^^^^^ + +Newly generated ahriman and devtools configuration files are stored below ``/var/lib/ahriman/.config/ahriman`` instead of system-wide configuration directories. Existing configurations continue to work without any changes. + +However, it is recommended to migrate to the new configuration schema by doing the following steps: + +#. + Stop all ahriman services. + +#. + Remove the old generated configuration files. For example, for repository ``aur`` and architecture ``x86_64``: + + .. code-block:: shell + + sudo rm /etc/ahriman.ini.d/00-setup-overrides-x86_64-aur.ini + sudo rm /usr/share/devtools/pacman.conf.d/aur-x86_64.conf + + Repeat this step for every configured repository. Do not remove manually maintained configuration overrides. + +#. + Run setup command (i.e. ``ahriman service-setup``) again with the same arguments as used before as the ``ahriman`` user. + +#. + Start the services again. + +Docker installation +^^^^^^^^^^^^^^^^^^^ + +The repository root inside the Docker image has changed from ``/var/lib/ahriman/ahriman`` to ``/var/lib/ahriman``. Existing repository contents must therefore be moved one directory level up, and the volume must be owned by the ``ahriman`` user. + +#. + Stop all containers using the repository volume and create a backup. + +#. + Run a one-off container which mounts the existing volume. Replace ``VOLUME`` with the named volume or bind mount used by the installation: + + .. code-block:: shell + + docker run --rm --user root --entrypoint bash \ + --volume VOLUME:/var/lib/ahriman \ + arcan1s/ahriman:latest \ + -c 'find /var/lib/ahriman/ahriman -mindepth 1 -maxdepth 1 -exec mv -t /var/lib/ahriman -- {} + && rmdir /var/lib/ahriman/ahriman && chown ahriman:ahriman /var/lib/ahriman' + +#. + Update custom configuration and container arguments which refer to ``/var/lib/ahriman/ahriman``. The new path is ``/var/lib/ahriman``. + +#. + Start the containers again. diff --git a/docs/migrations/index.rst b/docs/migrations/index.rst index 64dafb50..034c43d0 100644 --- a/docs/migrations/index.rst +++ b/docs/migrations/index.rst @@ -12,3 +12,4 @@ Upgrades to breakpoints 2.9.0 2.12.0 2.16.0 + 2.22.0 diff --git a/recipes/check/compose.yml b/recipes/check/compose.yml index 25b127c7..4c156927 100644 --- a/recipes/check/compose.yml +++ b/recipes/check/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -8,9 +25,9 @@ services: AHRIMAN_OUTPUT: console AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD} AHRIMAN_PORT: 8080 - AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full + AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | ahriman user-add demo -R full AHRIMAN_REPOSITORY: ahriman-demo - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman.sock configs: - source: service @@ -22,8 +39,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully healthcheck: test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info @@ -37,6 +56,10 @@ services: ports: - 8080:80 + depends_on: + backend: + condition: service_healthy + configs: - source: nginx target: /etc/nginx/conf.d/default.conf @@ -57,7 +80,7 @@ services: AHRIMAN_DEBUG: yes AHRIMAN_OUTPUT: console AHRIMAN_REPOSITORY: ahriman-demo - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman.sock volumes: - type: volume diff --git a/recipes/daemon/compose.yml b/recipes/daemon/compose.yml index 660e8772..5e694b96 100644 --- a/recipes/daemon/compose.yml +++ b/recipes/daemon/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -12,8 +29,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully command: repo-daemon diff --git a/recipes/distributed-manual/compose.yml b/recipes/distributed-manual/compose.yml index 0e29d267..4e9c5ad4 100644 --- a/recipes/distributed-manual/compose.yml +++ b/recipes/distributed-manual/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -8,9 +25,9 @@ services: AHRIMAN_OUTPUT: console AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD} AHRIMAN_PORT: 8080 - AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full + AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | ahriman user-add demo -R full AHRIMAN_REPOSITORY: ahriman-demo - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman.sock configs: - source: service @@ -22,8 +39,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully healthcheck: test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info @@ -37,6 +56,10 @@ services: ports: - 8080:80 + depends_on: + backend: + condition: service_healthy + configs: - source: nginx target: /etc/nginx/conf.d/default.conf diff --git a/recipes/distributed/compose.yml b/recipes/distributed/compose.yml index fa5cda03..35dbed11 100644 --- a/recipes/distributed/compose.yml +++ b/recipes/distributed/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -8,9 +25,9 @@ services: AHRIMAN_OUTPUT: console AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD} AHRIMAN_PORT: 8080 - AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full + AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | ahriman user-add demo -R full AHRIMAN_REPOSITORY: ahriman-demo - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman.sock configs: - source: service @@ -22,8 +39,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully healthcheck: test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info @@ -37,6 +56,10 @@ services: ports: - 8080:80 + depends_on: + backend: + condition: service_healthy + configs: - source: nginx target: /etc/nginx/conf.d/default.conf @@ -62,7 +85,7 @@ services: AHRIMAN_OUTPUT: console AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD} AHRIMAN_PORT: 8080 - AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full + AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | ahriman user-add demo -R full AHRIMAN_REPOSITORY: ahriman-demo AHRIMAN_REPOSITORY_SERVER: http://frontend/repo/$$repo/$$arch diff --git a/recipes/i686/Dockerfile b/recipes/i686/Dockerfile index e9ca47ee..866a5339 100644 --- a/recipes/i686/Dockerfile +++ b/recipes/i686/Dockerfile @@ -2,8 +2,12 @@ FROM arcan1s/ahriman:edge ENV ARCH32_KEYRING_VERSION="20231126-1.0" +USER root + RUN pacman-key --init RUN pacman -Sy --noconfirm wget && \ wget -nv https://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-${ARCH32_KEYRING_VERSION}-any.pkg.tar.zst && \ pacman -U --noconfirm archlinux32-keyring-${ARCH32_KEYRING_VERSION}-any.pkg.tar.zst + +USER ahriman diff --git a/recipes/i686/compose.yml b/recipes/i686/compose.yml index f457cf02..f0f2db86 100644 --- a/recipes/i686/compose.yml +++ b/recipes/i686/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: ahriman-i686 build: . @@ -12,9 +29,9 @@ services: AHRIMAN_PACMAN_MIRROR: https://de.mirror.archlinux32.org/$$arch/$$repo AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD} AHRIMAN_PORT: 8080 - AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full + AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | ahriman user-add demo -R full AHRIMAN_REPOSITORY: ahriman-demo - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman.sock configs: - source: makepkg @@ -30,8 +47,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully healthcheck: test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info @@ -45,6 +64,10 @@ services: ports: - 8080:80 + depends_on: + backend: + condition: service_healthy + configs: - source: nginx target: /etc/nginx/conf.d/default.conf diff --git a/recipes/index/compose.yml b/recipes/index/compose.yml index 1a508105..4e118df1 100644 --- a/recipes/index/compose.yml +++ b/recipes/index/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -16,8 +33,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully command: repo-report diff --git a/recipes/multirepo/compose.yml b/recipes/multirepo/compose.yml index 1807b740..674fe0bc 100644 --- a/recipes/multirepo/compose.yml +++ b/recipes/multirepo/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -8,10 +25,10 @@ services: AHRIMAN_OUTPUT: console AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD} AHRIMAN_PORT: 8080 - AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full + AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | ahriman user-add demo -R full AHRIMAN_PRESETUP_COMMAND: ahriman --architecture x86_64 --repository another-demo service-setup --build-as-user ahriman --packager 'ahriman bot ' AHRIMAN_REPOSITORY: ahriman-demo - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman.sock configs: - source: service @@ -23,8 +40,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully healthcheck: test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info @@ -38,6 +57,10 @@ services: ports: - 8080:80 + depends_on: + backend: + condition: service_healthy + configs: - source: nginx target: /etc/nginx/conf.d/default.conf diff --git a/recipes/oauth/compose.yml b/recipes/oauth/compose.yml index 328706ec..db7c2452 100644 --- a/recipes/oauth/compose.yml +++ b/recipes/oauth/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -9,9 +26,9 @@ services: AHRIMAN_OAUTH_CLIENT_SECRET: ${AHRIMAN_OAUTH_CLIENT_SECRET} AHRIMAN_OUTPUT: console AHRIMAN_PORT: 8080 - AHRIMAN_POSTSETUP_COMMAND: sudo -u ahriman ahriman user-add ${AHRIMAN_OAUTH_USER} -R full -p "" + AHRIMAN_POSTSETUP_COMMAND: ahriman user-add ${AHRIMAN_OAUTH_USER} -R full -p "" AHRIMAN_REPOSITORY: ahriman-demo - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman.sock configs: - source: service @@ -21,8 +38,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully healthcheck: test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info @@ -36,6 +55,10 @@ services: ports: - 8080:80 + depends_on: + backend: + condition: service_healthy + configs: - source: nginx target: /etc/nginx/conf.d/default.conf diff --git a/recipes/pam/compose.yml b/recipes/pam/compose.yml index 5f61d980..eeca2369 100644 --- a/recipes/pam/compose.yml +++ b/recipes/pam/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -10,7 +27,7 @@ services: AHRIMAN_PORT: 8080 AHRIMAN_PRESETUP_COMMAND: useradd -d / -G wheel -M demo; (cat /run/secrets/password; echo; cat /run/secrets/password) | passwd demo AHRIMAN_REPOSITORY: ahriman-demo - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman.sock configs: - source: service @@ -22,8 +39,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully healthcheck: test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info @@ -37,6 +56,10 @@ services: ports: - 8080:80 + depends_on: + backend: + condition: service_healthy + configs: - source: nginx target: /etc/nginx/conf.d/default.conf diff --git a/recipes/pull/compose.yml b/recipes/pull/compose.yml index d6c7cc08..d1bdca33 100644 --- a/recipes/pull/compose.yml +++ b/recipes/pull/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -16,8 +33,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully frontend: image: nginx diff --git a/recipes/sign/compose.yml b/recipes/sign/compose.yml index 318a07e1..9f3eabd0 100644 --- a/recipes/sign/compose.yml +++ b/recipes/sign/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -6,7 +23,7 @@ services: environment: AHRIMAN_DEBUG: yes AHRIMAN_OUTPUT: console - AHRIMAN_POSTSETUP_COMMAND: sudo -u ahriman gpg --import /run/secrets/key + AHRIMAN_POSTSETUP_COMMAND: gpg --import /run/secrets/key AHRIMAN_REPOSITORY: ahriman-demo configs: @@ -19,8 +36,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully command: repo-daemon diff --git a/recipes/web/compose.yml b/recipes/web/compose.yml index 45247968..ae7d5e0f 100644 --- a/recipes/web/compose.yml +++ b/recipes/web/compose.yml @@ -1,4 +1,21 @@ services: + volume-init: + image: arcan1s/ahriman:edge + user: root + + entrypoint: [ + "chown", + ] + command: [ + "ahriman:ahriman", + "/var/lib/ahriman", + ] + + volumes: + - type: volume + source: repository + target: /var/lib/ahriman + backend: image: arcan1s/ahriman:edge privileged: true @@ -8,9 +25,9 @@ services: AHRIMAN_OUTPUT: console AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD} AHRIMAN_PORT: 8080 - AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full + AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | ahriman user-add demo -R full AHRIMAN_REPOSITORY: ahriman-demo - AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock + AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman.sock configs: - source: service @@ -22,8 +39,10 @@ services: - type: volume source: repository target: /var/lib/ahriman - volume: - nocopy: true + + depends_on: + volume-init: + condition: service_completed_successfully healthcheck: test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info @@ -37,6 +56,10 @@ services: ports: - 8080:80 + depends_on: + backend: + condition: service_healthy + configs: - source: nginx target: /etc/nginx/conf.d/default.conf diff --git a/tests/testresources/core/ahriman.ini b/tests/testresources/core/ahriman.ini index b65987d6..94dd2f15 100644 --- a/tests/testresources/core/ahriman.ini +++ b/tests/testresources/core/ahriman.ini @@ -23,6 +23,7 @@ allow_read_only = no [build] archbuild_flags = +devtools_configs = . devtools_wrapper = ahriman-archbuild ignore_packages = makechrootpkg_flags =