diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py
index 218b04f9..0d1e48d6 100644
--- a/src/ahriman/application/ahriman.py
+++ b/src/ahriman/application/ahriman.py
@@ -31,12 +31,8 @@ def _parser() -> argparse.ArgumentParser:
:return: command line parser for the application
"""
parser = argparse.ArgumentParser(prog="ahriman", description="ArcHlinux ReposItory MANager")
- parser.add_argument(
- "-a",
- "--architecture",
- help="target architectures (can be used multiple times)",
- action="append",
- required=True)
+ parser.add_argument("-a", "--architecture", help="target architectures (can be used multiple times)",
+ action="append", required=True)
parser.add_argument("-c", "--config", help="configuration path", default="/etc/ahriman.ini")
parser.add_argument("--force", help="force run, remove file lock", action="store_true")
parser.add_argument("--lock", help="lock file", default="/tmp/ahriman.lock")
@@ -60,12 +56,10 @@ def _parser() -> argparse.ArgumentParser:
clean_parser.add_argument("--no-build", help="do not clear directory with package sources", action="store_true")
clean_parser.add_argument("--no-cache", help="do not clear directory with package caches", action="store_true")
clean_parser.add_argument("--no-chroot", help="do not clear build chroot", action="store_true")
- clean_parser.add_argument(
- "--no-manual",
- help="do not clear directory with manually added packages",
- action="store_true")
+ clean_parser.add_argument("--no-manual", help="do not clear directory with manually added packages",
+ action="store_true")
clean_parser.add_argument("--no-packages", help="do not clear directory with built packages", action="store_true")
- clean_parser.set_defaults(handler=handlers.Clean)
+ clean_parser.set_defaults(handler=handlers.Clean, unsafe=True)
config_parser = subparsers.add_parser("config", description="dump configuration for specified architecture")
config_parser.set_defaults(handler=handlers.Dump, lock=None, no_report=True, unsafe=True)
@@ -81,6 +75,15 @@ def _parser() -> argparse.ArgumentParser:
report_parser.add_argument("target", help="target to generate report", nargs="*")
report_parser.set_defaults(handler=handlers.Report)
+ setup_parser = subparsers.add_parser("setup", description="create initial service configuration, requires root")
+ setup_parser.add_argument("--build-command", help="build command prefix", default="ahriman")
+ setup_parser.add_argument("--from-config", help="path to default devtools pacman configuration",
+ default="/usr/share/devtools/pacman-extra.conf")
+ setup_parser.add_argument("--no-multilib", help="do not add multilib repository", action="store_true")
+ setup_parser.add_argument("--packager", help="packager name and email", required=True)
+ setup_parser.add_argument("--repository", help="repository name", default="aur-clone")
+ setup_parser.set_defaults(handler=handlers.Setup, lock=None, no_report=True, unsafe=True)
+
sign_parser = subparsers.add_parser("sign", description="(re-)sign packages and repository database")
sign_parser.add_argument("package", help="sign only specified packages", nargs="*")
sign_parser.set_defaults(handler=handlers.Sign)
@@ -96,8 +99,8 @@ def _parser() -> argparse.ArgumentParser:
update_parser = subparsers.add_parser("update", description="run updates")
update_parser.add_argument("package", help="filter check by package base", nargs="*")
- update_parser.add_argument(
- "--dry-run", help="just perform check for updates, same as check command", action="store_true")
+ update_parser.add_argument("--dry-run", help="just perform check for updates, same as check command",
+ action="store_true")
update_parser.add_argument("--no-aur", help="do not check for AUR updates. Implies --no-vcs", action="store_true")
update_parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
update_parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
diff --git a/src/ahriman/application/handlers/__init__.py b/src/ahriman/application/handlers/__init__.py
index 7691c927..d64f241b 100644
--- a/src/ahriman/application/handlers/__init__.py
+++ b/src/ahriman/application/handlers/__init__.py
@@ -25,6 +25,7 @@ from ahriman.application.handlers.dump import Dump
from ahriman.application.handlers.rebuild import Rebuild
from ahriman.application.handlers.remove import Remove
from ahriman.application.handlers.report import Report
+from ahriman.application.handlers.setup import Setup
from ahriman.application.handlers.sign import Sign
from ahriman.application.handlers.status import Status
from ahriman.application.handlers.sync import Sync
diff --git a/src/ahriman/application/handlers/setup.py b/src/ahriman/application/handlers/setup.py
new file mode 100644
index 00000000..4cfcfb7a
--- /dev/null
+++ b/src/ahriman/application/handlers/setup.py
@@ -0,0 +1,148 @@
+#
+# Copyright (c) 2021 ahriman team.
+#
+# This file is part of ahriman
+# (see https://github.com/arcan1s/ahriman).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+import argparse
+import configparser
+
+from pathlib import Path
+from typing import Type
+
+from ahriman.application.application import Application
+from ahriman.application.handlers.handler import Handler
+from ahriman.core.configuration import Configuration
+from ahriman.models.repository_paths import RepositoryPaths
+
+
+class Setup(Handler):
+ """
+ setup handler
+ :cvar ARCHBUILD_COMMAND_PATH: default devtools command
+ :cvar BIN_DIR_PATH: directory for custom binaries
+ :cvar MIRRORLIST_PATH: path to pacman default mirrorlist (used by multilib repository)
+ :cvar SUDOERS_PATH: path to sudoers.d include configuration
+ """
+
+ ARCHBUILD_COMMAND_PATH = Path("/usr/bin/archbuild")
+ BIN_DIR_PATH = Path("/usr/local/bin")
+ MIRRORLIST_PATH = Path("/etc/pacman.d/mirrorlist")
+ SUDOERS_PATH = Path("/etc/sudoers.d/ahriman")
+
+ @classmethod
+ def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, config: Configuration) -> None:
+ """
+ callback for command line
+ :param args: command line args
+ :param architecture: repository architecture
+ :param config: configuration instance
+ """
+ application = Application(architecture, config)
+ Setup.create_makepkg_configuration(args.packager, application.repository.paths)
+ Setup.create_executable(args.build_command, architecture)
+ Setup.create_devtools_configuration(args.build_command, architecture, Path(args.from_config), args.no_multilib,
+ application.repository.name, application.repository.paths)
+ Setup.create_ahriman_configuration(args.build_command, architecture, config.include)
+ Setup.create_sudo_configuration(args.build_command, architecture)
+
+ @staticmethod
+ def build_command(prefix: str, architecture: str) -> Path:
+ """
+ generate build command name
+ :param prefix: command prefix in {prefix}-{architecture}-build
+ :param architecture: repository architecture
+ :return: valid devtools command name
+ """
+ return Setup.BIN_DIR_PATH / f"{prefix}-{architecture}-build"
+
+ @staticmethod
+ def create_ahriman_configuration(prefix: str, architecture: str, include_path: Path) -> None:
+ """
+ create service specific configuration
+ :param prefix: command prefix in {prefix}-{architecture}-build
+ :param architecture: repository architecture
+ :param include_path: path to directory with configuration includes
+ """
+ config = configparser.RawConfigParser()
+ config.add_section("build")
+ config.set("build", "build_command", str(Setup.build_command(prefix, architecture)))
+
+ target = include_path / "build.ini"
+ with target.open("w") as ahriman_config:
+ config.write(ahriman_config)
+
+ @staticmethod
+ def create_devtools_configuration(prefix: str, architecture: str, source: Path,
+ no_multilib: bool, repository: str, paths: RepositoryPaths) -> None:
+ """
+ create configuration for devtools based on `source` configuration
+ :param prefix: command prefix in {prefix}-{architecture}-build
+ :param architecture: repository architecture
+ :param source: path to source configuration file
+ :param no_multilib: do not add multilib repository
+ :param repository: repository name
+ :param paths: repository paths instance
+ """
+ config = configparser.RawConfigParser()
+
+ # include base configuration
+ config.add_section("options")
+ config.set("options", "Include", str(source))
+ config.set("options", "Architecture", architecture)
+
+ # add multilib
+ if not no_multilib:
+ config.add_section("multilib")
+ config.set("multilib", "Include", str(Setup.MIRRORLIST_PATH))
+
+ # add repository itself
+ config.add_section(repository)
+ config.set(repository, "SigLevel", "Optional TrustAll") # we don't care
+ config.set(repository, "Server", f"file://{paths.repository}")
+
+ target = source.parent / f"pacman-{prefix}.conf"
+ with target.open("w") as devtools_config:
+ config.write(devtools_config)
+
+ @staticmethod
+ def create_makepkg_configuration(packager: str, paths: RepositoryPaths) -> None:
+ """
+ create configuration for makepkg
+ :param packager: packager identifier (e.g. name, email)
+ :param paths: repository paths instance
+ """
+ (paths.root / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n")
+
+ @staticmethod
+ def create_sudo_configuration(prefix: str, architecture: str) -> None:
+ """
+ create configuration to run build command with sudo without password
+ :param prefix: command prefix in {prefix}-{architecture}-build
+ :param architecture: repository architecture
+ """
+ command = Setup.build_command(prefix, architecture)
+ Setup.SUDOERS_PATH.write_text(f"ahriman ALL=(ALL) NOPASSWD: {command} *\n")
+ Setup.SUDOERS_PATH.chmod(0o400) # security!
+
+ @staticmethod
+ def create_executable(prefix: str, architecture: str) -> None:
+ """
+ create executable for the service
+ :param prefix: command prefix in {prefix}-{architecture}-build
+ :param architecture: repository architecture
+ """
+ Setup.build_command(prefix, architecture).symlink_to(Setup.BIN_DIR_PATH)
diff --git a/tests/ahriman/application/handlers/test_handler_add.py b/tests/ahriman/application/handlers/test_handler_add.py
index 671ff6ee..4a6f0a4e 100644
--- a/tests/ahriman/application/handlers/test_handler_add.py
+++ b/tests/ahriman/application/handlers/test_handler_add.py
@@ -6,12 +6,17 @@ from ahriman.application.handlers import Add
from ahriman.core.configuration import Configuration
+def _default_args(args: argparse.Namespace) -> argparse.Namespace:
+ args.package = []
+ args.without_dependencies = False
+ return args
+
+
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
- args.package = []
- args.without_dependencies = False
+ args = _default_args(args)
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.add")
diff --git a/tests/ahriman/application/handlers/test_handler_clean.py b/tests/ahriman/application/handlers/test_handler_clean.py
index 50555798..db2c0e06 100644
--- a/tests/ahriman/application/handlers/test_handler_clean.py
+++ b/tests/ahriman/application/handlers/test_handler_clean.py
@@ -6,15 +6,20 @@ from ahriman.application.handlers import Clean
from ahriman.core.configuration import Configuration
-def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
- """
- must run command
- """
+def _default_args(args: argparse.Namespace) -> argparse.Namespace:
args.no_build = False
args.no_cache = False
args.no_chroot = False
args.no_manual = False
args.no_packages = False
+ return args
+
+
+def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
+ """
+ must run command
+ """
+ args = _default_args(args)
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.clean")
diff --git a/tests/ahriman/application/handlers/test_handler_remove.py b/tests/ahriman/application/handlers/test_handler_remove.py
index 5f9a22b2..df990cbc 100644
--- a/tests/ahriman/application/handlers/test_handler_remove.py
+++ b/tests/ahriman/application/handlers/test_handler_remove.py
@@ -6,11 +6,16 @@ from ahriman.application.handlers import Remove
from ahriman.core.configuration import Configuration
+def _default_args(args: argparse.Namespace) -> argparse.Namespace:
+ args.package = []
+ return args
+
+
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
- args.package = []
+ args = _default_args(args)
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.remove")
diff --git a/tests/ahriman/application/handlers/test_handler_report.py b/tests/ahriman/application/handlers/test_handler_report.py
index 9dbba316..7deaa60d 100644
--- a/tests/ahriman/application/handlers/test_handler_report.py
+++ b/tests/ahriman/application/handlers/test_handler_report.py
@@ -6,11 +6,16 @@ from ahriman.application.handlers import Report
from ahriman.core.configuration import Configuration
+def _default_args(args: argparse.Namespace) -> argparse.Namespace:
+ args.target = []
+ return args
+
+
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
- args.target = []
+ args = _default_args(args)
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.report")
diff --git a/tests/ahriman/application/handlers/test_handler_setup.py b/tests/ahriman/application/handlers/test_handler_setup.py
new file mode 100644
index 00000000..0203fc79
--- /dev/null
+++ b/tests/ahriman/application/handlers/test_handler_setup.py
@@ -0,0 +1,121 @@
+import argparse
+
+from pathlib import Path
+from pytest_mock import MockerFixture
+from unittest import mock
+
+from ahriman.application.handlers import Setup
+from ahriman.core.configuration import Configuration
+from ahriman.models.repository_paths import RepositoryPaths
+
+
+def _default_args(args: argparse.Namespace) -> argparse.Namespace:
+ args.build_command = "ahriman"
+ args.from_config = "/usr/share/devtools/pacman-extra.conf"
+ args.no_multilib = False
+ args.packager = "John Doe "
+ args.repository = "aur-clone"
+ return args
+
+
+def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
+ """
+ must run command
+ """
+ args = _default_args(args)
+ mocker.patch("pathlib.Path.mkdir")
+ ahriman_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.create_ahriman_configuration")
+ devtools_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.create_devtools_configuration")
+ makepkg_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.create_makepkg_configuration")
+ sudo_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.create_sudo_configuration")
+ executable_mock = mocker.patch("ahriman.application.handlers.setup.Setup.create_executable")
+
+ Setup.run(args, "x86_64", configuration)
+ ahriman_configuration_mock.assert_called_once()
+ devtools_configuration_mock.assert_called_once()
+ makepkg_configuration_mock.assert_called_once()
+ sudo_configuration_mock.assert_called_once()
+ executable_mock.assert_called_once()
+
+
+def test_build_command(args: argparse.Namespace) -> None:
+ """
+ must generate correct build command name
+ """
+ args = _default_args(args)
+ assert Setup.build_command(args.build_command, "x86_64").name == f"{args.build_command}-x86_64-build"
+
+
+def test_create_ahriman_configuration(args: argparse.Namespace, configuration: Configuration,
+ mocker: MockerFixture) -> None:
+ """
+ must create configuration for the service
+ """
+ args = _default_args(args)
+ mocker.patch("pathlib.Path.open")
+ add_section_mock = mocker.patch("configparser.RawConfigParser.add_section")
+ set_mock = mocker.patch("configparser.RawConfigParser.set")
+ write_mock = mocker.patch("configparser.RawConfigParser.write")
+
+ command = Setup.build_command(args.build_command, "x86_64")
+ Setup.create_ahriman_configuration(args.build_command, "x86_64", configuration.include)
+ add_section_mock.assert_called_once()
+ set_mock.assert_called_with("build", "build_command", str(command))
+ write_mock.assert_called_once()
+
+
+def test_create_devtools_configuration(args: argparse.Namespace, repository_paths: RepositoryPaths,
+ mocker: MockerFixture) -> None:
+ """
+ must create configuration for the service
+ """
+ args = _default_args(args)
+ mocker.patch("pathlib.Path.open")
+ mocker.patch("configparser.RawConfigParser.set")
+ add_section_mock = mocker.patch("configparser.RawConfigParser.add_section")
+ write_mock = mocker.patch("configparser.RawConfigParser.write")
+
+ Setup.create_devtools_configuration(args.build_command, "x86_64", Path(args.from_config), args.no_multilib,
+ args.repository, repository_paths)
+ add_section_mock.assert_has_calls([
+ mock.call("options"),
+ mock.call("multilib"),
+ mock.call(args.repository)
+ ])
+ write_mock.assert_called_once()
+
+
+def test_create_makepkg_configuration(args: argparse.Namespace, repository_paths: RepositoryPaths,
+ mocker: MockerFixture) -> None:
+ """
+ must create makepkg configuration
+ """
+ args = _default_args(args)
+ write_text_mock = mocker.patch("pathlib.Path.write_text")
+
+ Setup.create_makepkg_configuration(args.packager, repository_paths)
+ write_text_mock.assert_called_once()
+
+
+def test_create_sudo_configuration(args: argparse.Namespace, mocker: MockerFixture) -> None:
+ """
+ must create sudo configuration
+ """
+ args = _default_args(args)
+ chmod_text_mock = mocker.patch("pathlib.Path.chmod")
+ write_text_mock = mocker.patch("pathlib.Path.write_text")
+
+ Setup.create_sudo_configuration(args.build_command, "x86_64")
+ chmod_text_mock.assert_called_with(0o400)
+ write_text_mock.assert_called_once()
+
+
+def test_create_executable(args: argparse.Namespace, mocker: MockerFixture) -> None:
+ """
+ must create sudo configuration
+ """
+ args = _default_args(args)
+ symlink_text_mock = mocker.patch("pathlib.Path.symlink_to")
+
+ Setup.create_executable(args.build_command, "x86_64")
+ symlink_text_mock.assert_called_once()
diff --git a/tests/ahriman/application/handlers/test_handler_sign.py b/tests/ahriman/application/handlers/test_handler_sign.py
index 38f906f4..0b16f01a 100644
--- a/tests/ahriman/application/handlers/test_handler_sign.py
+++ b/tests/ahriman/application/handlers/test_handler_sign.py
@@ -6,11 +6,16 @@ from ahriman.application.handlers import Sign
from ahriman.core.configuration import Configuration
+def _default_args(args: argparse.Namespace) -> argparse.Namespace:
+ args.package = []
+ return args
+
+
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
- args.package = []
+ args = _default_args(args)
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.sign")
diff --git a/tests/ahriman/application/handlers/test_handler_status.py b/tests/ahriman/application/handlers/test_handler_status.py
index 8ded654e..a1cac924 100644
--- a/tests/ahriman/application/handlers/test_handler_status.py
+++ b/tests/ahriman/application/handlers/test_handler_status.py
@@ -6,13 +6,17 @@ from ahriman.application.handlers import Status
from ahriman.core.configuration import Configuration
+def _default_args(args: argparse.Namespace) -> argparse.Namespace:
+ args.ahriman = True
+ args.package = []
+ return args
+
+
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
- args.ahriman = True
- args.package = []
- args.without_dependencies = False
+ args = _default_args(args)
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.core.status.client.Client.get_self")
packages_mock = mocker.patch("ahriman.core.status.client.Client.get")
diff --git a/tests/ahriman/application/handlers/test_handler_sync.py b/tests/ahriman/application/handlers/test_handler_sync.py
index 4926976e..28dac940 100644
--- a/tests/ahriman/application/handlers/test_handler_sync.py
+++ b/tests/ahriman/application/handlers/test_handler_sync.py
@@ -6,11 +6,16 @@ from ahriman.application.handlers import Sync
from ahriman.core.configuration import Configuration
+def _default_args(args: argparse.Namespace) -> argparse.Namespace:
+ args.target = []
+ return args
+
+
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
- args.target = []
+ args = _default_args(args)
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.sync")
diff --git a/tests/ahriman/application/handlers/test_handler_update.py b/tests/ahriman/application/handlers/test_handler_update.py
index 3de20521..a4bbafd2 100644
--- a/tests/ahriman/application/handlers/test_handler_update.py
+++ b/tests/ahriman/application/handlers/test_handler_update.py
@@ -6,15 +6,20 @@ from ahriman.application.handlers import Update
from ahriman.core.configuration import Configuration
-def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
- """
- must run command
- """
+def _default_args(args: argparse.Namespace) -> argparse.Namespace:
args.package = []
args.dry_run = False
args.no_aur = False
args.no_manual = False
args.no_vcs = False
+ return args
+
+
+def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
+ """
+ must run command
+ """
+ args = _default_args(args)
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.update")
updates_mock = mocker.patch("ahriman.application.application.Application.get_updates")
@@ -28,11 +33,8 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, moc
"""
must run simplified command
"""
- args.package = []
+ args = _default_args(args)
args.dry_run = True
- args.no_aur = False
- args.no_manual = False
- args.no_vcs = False
mocker.patch("pathlib.Path.mkdir")
updates_mock = mocker.patch("ahriman.application.application.Application.get_updates")
diff --git a/tests/ahriman/application/test_ahriman.py b/tests/ahriman/application/test_ahriman.py
index ea87dd59..0b4153fd 100644
--- a/tests/ahriman/application/test_ahriman.py
+++ b/tests/ahriman/application/test_ahriman.py
@@ -26,6 +26,14 @@ def test_subparsers_check(parser: argparse.ArgumentParser) -> None:
assert args.dry_run
+def test_subparsers_clean(parser: argparse.ArgumentParser) -> None:
+ """
+ clean command must imply unsafe
+ """
+ args = parser.parse_args(["-a", "x86_64", "clean"])
+ assert args.unsafe
+
+
def test_subparsers_config(parser: argparse.ArgumentParser) -> None:
"""
config command must imply lock, no_report and unsafe
@@ -36,6 +44,16 @@ def test_subparsers_config(parser: argparse.ArgumentParser) -> None:
assert args.unsafe
+def test_subparsers_setup(parser: argparse.ArgumentParser) -> None:
+ """
+ setup command must imply lock, no_report and unsafe
+ """
+ args = parser.parse_args(["-a", "x86_64", "setup", "--packager", "John Doe "])
+ assert args.lock is None
+ assert args.no_report
+ assert args.unsafe
+
+
def test_subparsers_status(parser: argparse.ArgumentParser) -> None:
"""
status command must imply lock, no_report and unsafe
diff --git a/tests/testresources/core/ahriman.ini b/tests/testresources/core/ahriman.ini
index b918f520..e7faa097 100644
--- a/tests/testresources/core/ahriman.ini
+++ b/tests/testresources/core/ahriman.ini
@@ -1,4 +1,5 @@
[settings]
+include = .
logging = logging.ini
[alpm]