mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-04-07 11:03:37 +00:00
Compare commits
7 Commits
875e9cf58a
...
release/2.
| Author | SHA1 | Date | |
|---|---|---|---|
| d99b61d8c0 | |||
| 34d0b8cf73 | |||
| cc082dbd6f | |||
| 945ddb2942 | |||
| 9cd0926588 | |||
| f5c12d03ec | |||
| 606ee2fb0e |
77
docs/_static/architecture.dot
vendored
77
docs/_static/architecture.dot
vendored
@@ -138,11 +138,12 @@ digraph G {
|
||||
ahriman_core_http [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nhttp",shape="box"];
|
||||
ahriman_core_http_sync_ahriman_client [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nhttp\.\nsync_ahriman_client",shape="box"];
|
||||
ahriman_core_http_sync_http_client [fillcolor="#933f24",fontcolor="#ffffff",label="ahriman\.\ncore\.\nhttp\.\nsync_http_client"];
|
||||
ahriman_core_log [fillcolor="#e53b05",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog"];
|
||||
ahriman_core_log [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nlog",shape="box"];
|
||||
ahriman_core_log_http_log_handler [fillcolor="#914730",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nhttp_log_handler"];
|
||||
ahriman_core_log_journal_handler [fillcolor="#ac6149",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\njournal_handler"];
|
||||
ahriman_core_log_lazy_logging [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nlazy_logging"];
|
||||
ahriman_core_log_log_loader [fillcolor="#82402b",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nlog_loader"];
|
||||
ahriman_core_log_lazy_logging [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nlog\.\nlazy_logging",shape="box"];
|
||||
ahriman_core_log_log_context [fillcolor="#db582f",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nlog_context"];
|
||||
ahriman_core_log_log_loader [fillcolor="#7a3c28",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nlog_loader"];
|
||||
ahriman_core_module_loader [fillcolor="#ce5e3b",fontcolor="#ffffff",label="ahriman\.\ncore\.\nmodule_loader"];
|
||||
ahriman_core_report [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nreport",shape="box"];
|
||||
ahriman_core_report_console [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nreport\.\nconsole",shape="box"];
|
||||
@@ -242,10 +243,11 @@ digraph G {
|
||||
ahriman_web_apispec_info [fillcolor="#a14f35",fontcolor="#ffffff",label="ahriman\.\nweb\.\napispec\.\ninfo"];
|
||||
ahriman_web_cors [fillcolor="#b0573a",fontcolor="#ffffff",label="ahriman\.\nweb\.\ncors"];
|
||||
ahriman_web_keys [fillcolor="#823017",fontcolor="#ffffff",label="ahriman\.\nweb\.\nkeys"];
|
||||
ahriman_web_middlewares [fillcolor="#e9410c",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares"];
|
||||
ahriman_web_middlewares [fillcolor="#ef3e06",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares"];
|
||||
ahriman_web_middlewares_auth_handler [fillcolor="#733826",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nauth_handler"];
|
||||
ahriman_web_middlewares_exception_handler [fillcolor="#994b33",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nexception_handler"];
|
||||
ahriman_web_middlewares_metrics_handler [fillcolor="#a34628",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nmetrics_handler"];
|
||||
ahriman_web_middlewares_request_id_handler [fillcolor="#8a442e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nrequest_id_handler"];
|
||||
ahriman_web_routes [fillcolor="#8a442e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nroutes"];
|
||||
ahriman_web_schemas [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas",shape="box"];
|
||||
ahriman_web_schemas_any_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nany_schema"];
|
||||
@@ -493,6 +495,7 @@ digraph G {
|
||||
ahriman_core -> ahriman_models_worker [fillcolor="#ef3e06",minlen="2"];
|
||||
ahriman_core -> ahriman_web_keys [fillcolor="#ef3e06",minlen="2"];
|
||||
ahriman_core -> ahriman_web_middlewares_auth_handler [fillcolor="#ef3e06",minlen="3"];
|
||||
ahriman_core -> ahriman_web_middlewares_request_id_handler [fillcolor="#ef3e06",minlen="3"];
|
||||
ahriman_core -> ahriman_web_routes [fillcolor="#ef3e06",minlen="2"];
|
||||
ahriman_core -> ahriman_web_server_info [fillcolor="#ef3e06",minlen="2"];
|
||||
ahriman_core -> ahriman_web_views_api_docs [fillcolor="#ef3e06",minlen="3"];
|
||||
@@ -848,35 +851,39 @@ digraph G {
|
||||
ahriman_core_http_sync_ahriman_client -> ahriman_core_http [fillcolor="blue",weight="3"];
|
||||
ahriman_core_http_sync_http_client -> ahriman_core_http [fillcolor="#933f24",weight="3"];
|
||||
ahriman_core_http_sync_http_client -> ahriman_core_http_sync_ahriman_client [fillcolor="#933f24",weight="3"];
|
||||
ahriman_core_log -> ahriman_application_application_application_properties [fillcolor="#e53b05",minlen="3"];
|
||||
ahriman_core_log -> ahriman_application_application_workers_updater [fillcolor="#e53b05",minlen="3"];
|
||||
ahriman_core_log -> ahriman_application_handlers_handler [fillcolor="#e53b05",minlen="3"];
|
||||
ahriman_core_log -> ahriman_application_lock [fillcolor="#e53b05",minlen="2"];
|
||||
ahriman_core_log -> ahriman_core_alpm_pacman [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_alpm_repo [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_archive_archive_tree [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_auth_auth [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_build_tools_package_version [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_build_tools_sources [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_build_tools_task [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_database_migrations [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_database_operations_operations [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_distributed_workers_cache [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_gitremote_remote_pull [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_gitremote_remote_push [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_http_sync_http_client [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_report_report [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_repository_repository_properties [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_spawn [fillcolor="#e53b05",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_status_watcher [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_triggers_trigger [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_triggers_trigger_loader [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_upload_upload [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_models_package [fillcolor="#e53b05",minlen="2"];
|
||||
ahriman_core_log -> ahriman_models_repository_paths [fillcolor="#e53b05",minlen="2"];
|
||||
ahriman_core_log -> ahriman_application_application_application_properties [fillcolor="blue",minlen="3"];
|
||||
ahriman_core_log -> ahriman_application_application_workers_updater [fillcolor="blue",minlen="3"];
|
||||
ahriman_core_log -> ahriman_application_handlers_handler [fillcolor="blue",minlen="3"];
|
||||
ahriman_core_log -> ahriman_application_lock [fillcolor="blue",minlen="2"];
|
||||
ahriman_core_log -> ahriman_core_alpm_pacman [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_alpm_repo [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_archive_archive_tree [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_auth_auth [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_build_tools_package_version [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_build_tools_sources [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_build_tools_task [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_database_migrations [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_database_operations_operations [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_distributed_workers_cache [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_gitremote_remote_pull [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_gitremote_remote_push [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_http_sync_http_client [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_report_report [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_repository_repository_properties [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_spawn [fillcolor="blue",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_status_watcher [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_triggers_trigger [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_triggers_trigger_loader [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_core_upload_upload [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_core_log -> ahriman_models_package [fillcolor="blue",minlen="2"];
|
||||
ahriman_core_log -> ahriman_models_repository_paths [fillcolor="blue",minlen="2"];
|
||||
ahriman_core_log -> ahriman_web_middlewares_request_id_handler [fillcolor="blue",minlen="3"];
|
||||
ahriman_core_log_http_log_handler -> ahriman_core_log_log_loader [fillcolor="#914730",weight="3"];
|
||||
ahriman_core_log_lazy_logging -> ahriman_core_log [fillcolor="#b85a3d",weight="3"];
|
||||
ahriman_core_log_log_loader -> ahriman_application_handlers_handler [fillcolor="#82402b",minlen="3"];
|
||||
ahriman_core_log_lazy_logging -> ahriman_core_log [fillcolor="blue",weight="3"];
|
||||
ahriman_core_log_log_context -> ahriman_core_log_lazy_logging [fillcolor="#db582f",weight="3"];
|
||||
ahriman_core_log_log_context -> ahriman_core_log_log_loader [fillcolor="#db582f",weight="3"];
|
||||
ahriman_core_log_log_context -> ahriman_web_middlewares_request_id_handler [fillcolor="#db582f",minlen="3"];
|
||||
ahriman_core_log_log_loader -> ahriman_application_handlers_handler [fillcolor="#7a3c28",minlen="3"];
|
||||
ahriman_core_module_loader -> ahriman_application_ahriman [fillcolor="#ce5e3b",minlen="2"];
|
||||
ahriman_core_module_loader -> ahriman_web_routes [fillcolor="#ce5e3b",minlen="2"];
|
||||
ahriman_core_report_console -> ahriman_core_report_report [fillcolor="blue",weight="3"];
|
||||
@@ -1779,12 +1786,13 @@ digraph G {
|
||||
ahriman_web_keys -> ahriman_web_apispec_info [fillcolor="#823017",minlen="2",weight="2"];
|
||||
ahriman_web_keys -> ahriman_web_views_base [fillcolor="#823017",minlen="2",weight="2"];
|
||||
ahriman_web_keys -> ahriman_web_web [fillcolor="#823017",weight="2"];
|
||||
ahriman_web_middlewares -> ahriman_web_views_v1_status_metrics [fillcolor="#e9410c",minlen="2",weight="2"];
|
||||
ahriman_web_middlewares -> ahriman_web_web [fillcolor="#e9410c",weight="2"];
|
||||
ahriman_web_middlewares -> ahriman_web_views_v1_status_metrics [fillcolor="#ef3e06",minlen="2",weight="2"];
|
||||
ahriman_web_middlewares -> ahriman_web_web [fillcolor="#ef3e06",weight="2"];
|
||||
ahriman_web_middlewares_auth_handler -> ahriman_web_web [fillcolor="#733826",minlen="2",weight="2"];
|
||||
ahriman_web_middlewares_exception_handler -> ahriman_web_web [fillcolor="#994b33",minlen="2",weight="2"];
|
||||
ahriman_web_middlewares_metrics_handler -> ahriman_web_views_v1_status_metrics [fillcolor="#a34628",minlen="2",weight="2"];
|
||||
ahriman_web_middlewares_metrics_handler -> ahriman_web_web [fillcolor="#a34628",minlen="2",weight="2"];
|
||||
ahriman_web_middlewares_request_id_handler -> ahriman_web_web [fillcolor="#8a442e",minlen="2",weight="2"];
|
||||
ahriman_web_routes -> ahriman_web_web [fillcolor="#8a442e",weight="2"];
|
||||
ahriman_web_schemas -> ahriman_web_apispec_decorators [fillcolor="blue",minlen="2",weight="2"];
|
||||
ahriman_web_schemas -> ahriman_web_views_v1_auditlog_events [fillcolor="blue",minlen="2",weight="2"];
|
||||
@@ -1939,6 +1947,7 @@ digraph G {
|
||||
aiohttp -> ahriman_web_middlewares_auth_handler [fillcolor="#f9b506",minlen="3"];
|
||||
aiohttp -> ahriman_web_middlewares_exception_handler [fillcolor="#f9b506",minlen="3"];
|
||||
aiohttp -> ahriman_web_middlewares_metrics_handler [fillcolor="#f9b506",minlen="3"];
|
||||
aiohttp -> ahriman_web_middlewares_request_id_handler [fillcolor="#f9b506",minlen="3"];
|
||||
aiohttp -> ahriman_web_routes [fillcolor="#f9b506",minlen="2"];
|
||||
aiohttp -> ahriman_web_views_api_swagger [fillcolor="#f9b506",minlen="3"];
|
||||
aiohttp -> ahriman_web_views_base [fillcolor="#f9b506",minlen="3"];
|
||||
|
||||
@@ -9,7 +9,8 @@ Packages have strict rules of importing:
|
||||
* ``ahriman.application`` package must not be used outside of this package.
|
||||
* ``ahriman.core`` and ``ahriman.models`` packages don't have any import restriction. Actually we would like to totally restrict importing of ``core`` package from ``models``, but it is impossible at the moment.
|
||||
* ``ahriman.web`` package is allowed to be imported from ``ahriman.application`` (web handler only, only ``ahriman.web.web`` methods).
|
||||
* The idea remains the same for all imports, if an package requires some specific dependencies, it must be imported locally to keep dependencies optional.
|
||||
|
||||
The idea remains the same for all imports, if a package requires some specific dependencies, it must be imported locally to keep dependencies optional.
|
||||
|
||||
Full dependency diagram:
|
||||
|
||||
@@ -42,7 +43,7 @@ This package contains everything required for the most of application actions an
|
||||
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
||||
* ``ahriman.core.housekeeping`` package provides few triggers for removing old data.
|
||||
* ``ahriman.core.http`` package provides HTTP clients which can be used later by other classes.
|
||||
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger and some wrappers.
|
||||
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger, log context for injecting context variables into log records and some wrappers.
|
||||
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
||||
* ``ahriman.core.repository`` contains several traits and base repository (``ahriman.core.repository.Repository`` class) implementation.
|
||||
* ``ahriman.core.sign`` package provides sign feature (only gpg calls are available).
|
||||
@@ -85,6 +86,7 @@ Application run
|
||||
#. Call ``Handler.execute`` method.
|
||||
#. Define list of architectures to run. In case if there is more than one architecture specified run several subprocesses or continue in current process otherwise. Class attribute ``ALLOW_MULTI_ARCHITECTURE_RUN`` controls whether the application can be run in multiple processes or not - this feature is required for some handlers (e.g. ``Config``, which utilizes stdout to print messages).
|
||||
#. In each child process call lock functions.
|
||||
#. Load configuration and install logging.
|
||||
#. After success checks pass control to ``Handler.run`` method defined by specific handler class.
|
||||
#. Return result (success or failure) of each subprocess and exit from application.
|
||||
#. Some handlers may override their status and throw ``ExitCode`` exception. This exception is just silently suppressed and changes application exit code to ``1``.
|
||||
@@ -159,12 +161,12 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
|
||||
├── aur.files -> aur.files.tar.gz
|
||||
└── aur.files.tar.gz
|
||||
|
||||
There are multiple subdirectories, some of them are commons for any repository, but some of them are not.
|
||||
There are multiple subdirectories, some of them are common for any repository, but some of them are not.
|
||||
|
||||
* ``archive`` is the package archive directory. It is common for all repositories and architectures and contains two subdirectories:
|
||||
|
||||
* ``archive/packages/{first_letter}/{package_base}`` stores the actual built package files and their signatures.
|
||||
* ``archive/repos/{YYYY}/{MM}/{DD}/{repository}/{architecture}`` contains daily repository snapshots. Each snapshot is a repository database with symlinks pointing to the corresponding packages in the ``archive/packages`` tree.
|
||||
* ``archive/repos/{YYYY}/{MM}/{DD}/{repository}/{architecture}`` contains daily repository snapshots. Each snapshot is a repository database with symlinks pointing to the corresponding packages in the ``archive/packages`` tree. These directories only appear if ``ahriman.core.archive.ArchiveTrigger`` is enabled.
|
||||
|
||||
The archive also allows the build process to skip rebuilding a package if a matching version already exists.
|
||||
|
||||
@@ -239,9 +241,9 @@ Check outdated packages
|
||||
There are few ways for packages to be marked as out-of-date and hence requiring rebuild. Those are following:
|
||||
|
||||
#. User requested update of the package. It can be caused by calling ``package-add`` subcommand (or ``package-update`` with arguments).
|
||||
#. The most common way for packages to be marked as out-of-dated is that the version in AUR (or the official repositories) is newer than in the repository.
|
||||
#. The most common way for packages to be marked as out-of-date is that the version in AUR (or the official repositories) is newer than in the repository.
|
||||
#. In addition to the above, if package is named as VCS (e.g. has suffix ``-git``) and the last update was more than specified threshold ago, the service will also try to fetch sources and check if the revision is newer than the built one.
|
||||
#. In addition, there is ability to check if the dependencies of the package have been updated (e.g. if linked library has been renamed or the modules directory - e.g. in case of python and ruby packages - has been changed). And if so, the package will be marked as out-of-dated as well.
|
||||
#. In addition, there is ability to check if the dependencies of the package have been updated (e.g. if linked library has been renamed or the modules directory - e.g. in case of python and ruby packages - has been changed). And if so, the package will be marked as out-of-date as well.
|
||||
|
||||
Update packages
|
||||
^^^^^^^^^^^^^^^
|
||||
@@ -255,6 +257,7 @@ This feature is divided into the following stages: check AUR for updates and run
|
||||
|
||||
#. Download package data from AUR.
|
||||
#. Bump ``pkgrel`` if there is duplicate version in the local repository (see explanation below).
|
||||
#. Check if there is already built package of the same version in archive (cross-repository support). If so, then just copy built archives and skip steps below.
|
||||
#. Build every package in clean chroot.
|
||||
#. Sign packages if required.
|
||||
#. Add packages to database and sign database if required.
|
||||
@@ -313,7 +316,7 @@ Having the initial dependencies tree, the application is looking for packages wh
|
||||
|
||||
Those paths are also filtered by regular expressions set in the configuration.
|
||||
|
||||
All those implicit dependencies are stored in the database and extracted on each check. In case if any of the repository packages doesn't contain any entry anymore (e.g. so version has been changed or modules directory has been changed), the dependent package will be marked as out-of-dated.
|
||||
All those implicit dependencies are stored in the database and extracted on each check. In case if any of the repository packages doesn't contain any entry anymore (e.g. so version has been changed or modules directory has been changed), the dependent package will be marked as out-of-date.
|
||||
|
||||
Core functions reference
|
||||
------------------------
|
||||
@@ -344,6 +347,8 @@ The ``_Context`` class itself mimics default collection interface (as is ``Mappi
|
||||
|
||||
In order to provide statically typed interface, the ``ahriman.models.context_key.ContextKey`` class is used for both ``_Content.get`` and ``_Content.set`` methods; the context instance itself, however, does not store information about types.
|
||||
|
||||
Logging module has its own context variables, which are required to be registered in advance to avoid possible race conditions.
|
||||
|
||||
Submodules
|
||||
^^^^^^^^^^
|
||||
|
||||
@@ -370,7 +375,7 @@ Passwords must be stored in database as ``hash(password + salt)``, where ``passw
|
||||
|
||||
means that there is user ``username`` with ``read`` access and password ``password`` hashed by ``sha512`` with salt ``salt``.
|
||||
|
||||
OAuth provider uses library definitions (``aioauth-client``) in order *authenticate* users. It still requires user permission to be set in database, thus it inherits mapping provider without any changes. Whereas we could override ``check_credentials`` (authentication method) by something custom, OAuth flow is a bit more complex than just forward request, thus we have to implement the flow in login form.
|
||||
OAuth provider uses library definitions (``aioauth-client``) in order to *authenticate* users. It still requires user permission to be set in database, thus it inherits mapping provider without any changes. Whereas we could override ``check_credentials`` (authentication method) by something custom, OAuth flow is a bit more complex than just forward request, thus we have to implement the flow in login form.
|
||||
|
||||
OAuth's implementation also allows authenticating users via username + password (in the same way as mapping does) though it is not recommended for end-users and password must be left blank. In particular this feature can be used by service reporting (aka robots).
|
||||
|
||||
@@ -383,7 +388,7 @@ Triggers
|
||||
|
||||
Triggers are extensions which can be used in order to perform any actions on application start, after the update process and, finally, before the application exit.
|
||||
|
||||
The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.UploadTrigger``) by using ``importlib``: get the last part of the import and treat it as class name, join remain part by ``.`` and interpret as module path, import module and extract attribute from it.
|
||||
The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.UploadTrigger``) by using ``importlib``: get the last part of the import and treat it as class name, join the remaining part by ``.`` and interpret as module path, import module and extract attribute from it.
|
||||
|
||||
The loaded triggers will be called with ``ahriman.models.result.Result`` and ``list[Packages]`` arguments, which describes the process result and current repository packages respectively. Any exception raised will be suppressed and will generate an exception message in logs.
|
||||
|
||||
@@ -407,7 +412,7 @@ PKGBUILD parsing
|
||||
|
||||
The application provides a house-made shell parser ``ahriman.core.alpm.pkgbuild_parser.PkgbuildParser`` to process PKGBUILDs and extract package data from them. It relies on the ``shlex.shlex`` parser with some configuration tweaks and adds some token post-processing.
|
||||
|
||||
#. During the parser process, firstly, it extract next token from the source file (basically, the word) and tries to match it to the variable assignment. If so, then just processes accordingly.
|
||||
#. During the parser process, firstly, it extracts the next token from the source file (basically, the word) and tries to match it to the variable assignment. If so, then just processes accordingly.
|
||||
#. If it is not an assignment, the parser checks if the token was quoted.
|
||||
#. If it wasn't quoted then the parser tries to match the array starts (two consecutive tokens like ``array=`` and ``(``) or it is function (``function``, ``()`` and ``{``).
|
||||
#. The arrays are processed until the next closing bracket ``)``. After extraction, the parser tries to expand an array according to bash rules (``prefix{first,second}suffix`` constructions).
|
||||
@@ -420,6 +425,15 @@ The PKGBUILD class also provides some additional functions on top of that:
|
||||
* Ability to extract fields defined inside ``package*()`` functions, which are in particular used for the multi-packages.
|
||||
* Shell substitution, which supports constructions ``$var`` (including ``${var}``), ``${var#(#)pattern}``, ``${var%(%)pattern}`` and ``${var/(/)pattern/replacement}`` (including ``#pattern`` and ``%pattern``).
|
||||
|
||||
HTTP client
|
||||
^^^^^^^^^^^
|
||||
|
||||
The ``ahriman.core.http`` package provides a HTTP client built on top of the ``requests`` library.
|
||||
|
||||
The base class ``ahriman.core.http.SyncHttpClient`` wraps ``requests.Session`` and provides common features for all HTTP interactions: configurable timeouts, retry policies with exponential backoff (using ``urllib3.util.retry.Retry``), basic authentication, custom User-Agent header, error processing, and ``make_request`` method. The session is lazily created (via ``cached_property``).
|
||||
|
||||
On top of that, ``ahriman.core.http.SyncAhrimanClient`` extends the base client for communication with the ahriman web service specifically. It adds automatic login on session creation (using configured credentials), ``X-Request-ID`` header injection and Unix socket transport support (via ``requests-unixsocket2``) if required.
|
||||
|
||||
Additional features
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -445,7 +459,7 @@ Web application requires the following python packages to be installed:
|
||||
Middlewares
|
||||
^^^^^^^^^^^
|
||||
|
||||
Service provides some custom middlewares, e.g. logging every exception (except for user ones) and user authorization.
|
||||
Service provides some custom middlewares, e.g. logging every exception (except for user ones), user authorization and request tracing via ``X-Request-ID`` header.
|
||||
|
||||
HEAD and OPTIONS requests
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -457,7 +471,7 @@ On the other side, ``OPTIONS`` method is implemented in the ``ahriman.web.middle
|
||||
Web views
|
||||
^^^^^^^^^
|
||||
|
||||
All web views are defined in separated package and derived from ``ahriman.web.views.base.Base`` class which provides typed interfaces for web application.
|
||||
All web views are defined in a separate package and derived from ``ahriman.web.views.base.Base`` class which provides typed interfaces for web application.
|
||||
|
||||
REST API supports only JSON data.
|
||||
|
||||
@@ -476,7 +490,7 @@ The views are also divided by supporting API versions (e.g. ``v1``, ``v2``).
|
||||
Templating
|
||||
^^^^^^^^^^
|
||||
|
||||
Package provides base jinja templates which can be overridden by settings. Vanilla templates actively use bootstrap library.
|
||||
Package provides base jinja templates which can be overridden by settings. The default web interface is a React application. The classic bootstrap-based template is still available as ``build-status-classic.jinja2`` and can be enabled via the ``web.template`` configuration option.
|
||||
|
||||
Requests and scopes
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "ahriman-frontend",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "2.20.0-rc6",
|
||||
"version": "2.20.1",
|
||||
"scripts": {
|
||||
"build": "tsc && vite build",
|
||||
"dev": "vite",
|
||||
|
||||
@@ -17,16 +17,14 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import AppLayout from "components/layout/AppLayout";
|
||||
import { AuthProvider } from "contexts/AuthProvider";
|
||||
import { ClientProvider } from "contexts/ClientProvider";
|
||||
import { NotificationProvider } from "contexts/NotificationProvider";
|
||||
import { RepositoryProvider } from "contexts/RepositoryProvider";
|
||||
import { ThemeProvider } from "contexts/ThemeProvider";
|
||||
import type React from "react";
|
||||
import Theme from "theme/Theme";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -39,8 +37,7 @@ const queryClient = new QueryClient({
|
||||
|
||||
export default function App(): React.JSX.Element {
|
||||
return <QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider theme={Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<NotificationProvider>
|
||||
<ClientProvider>
|
||||
<AuthProvider>
|
||||
|
||||
@@ -40,7 +40,7 @@ export class Client {
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/json",
|
||||
"X-Request-ID": crypto.randomUUID(),
|
||||
"X-Request-ID": crypto.randomUUID?.() ?? Date.now().toString(),
|
||||
};
|
||||
if (json !== undefined) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { blue } from "@mui/material/colors";
|
||||
import type { Event } from "models/Event";
|
||||
import type React from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
@@ -33,6 +34,8 @@ export default function EventDurationLineChart({ events }: EventDurationLineChar
|
||||
{
|
||||
label: "update duration, s",
|
||||
data: updateEvents.map(event => event.data?.took ?? 0),
|
||||
borderColor: blue[500],
|
||||
backgroundColor: blue[200],
|
||||
cubicInterpolationMode: "monotone" as const,
|
||||
tension: 0.4,
|
||||
},
|
||||
|
||||
@@ -47,6 +47,7 @@ export default function AutoRefreshControl({
|
||||
<Tooltip title="Auto-refresh">
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label="Auto-refresh"
|
||||
onClick={event => setAnchorEl(event.currentTarget)}
|
||||
color={enabled ? "primary" : "default"}
|
||||
>
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function CodeBlock({
|
||||
component="pre"
|
||||
onScroll={onScroll}
|
||||
sx={{
|
||||
backgroundColor: "grey.100",
|
||||
backgroundColor: "action.hover",
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
overflow: "auto",
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { Box, Container } from "@mui/material";
|
||||
import Brightness4Icon from "@mui/icons-material/Brightness4";
|
||||
import Brightness7Icon from "@mui/icons-material/Brightness7";
|
||||
import { Box, Container, IconButton, Tooltip } from "@mui/material";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import LoginDialog from "components/dialogs/LoginDialog";
|
||||
import Footer from "components/layout/Footer";
|
||||
@@ -27,6 +29,7 @@ import { QueryKeys } from "hooks/QueryKeys";
|
||||
import { useAuth } from "hooks/useAuth";
|
||||
import { useClient } from "hooks/useClient";
|
||||
import { useRepository } from "hooks/useRepository";
|
||||
import { useThemeMode } from "hooks/useThemeMode";
|
||||
import type { InfoResponse } from "models/InfoResponse";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
@@ -34,6 +37,7 @@ export default function AppLayout(): React.JSX.Element {
|
||||
const client = useClient();
|
||||
const { setAuthState } = useAuth();
|
||||
const { setRepositories } = useRepository();
|
||||
const { mode, toggleTheme } = useThemeMode();
|
||||
const [loginOpen, setLoginOpen] = useState(false);
|
||||
|
||||
const { data: info } = useQuery<InfoResponse>({
|
||||
@@ -58,6 +62,11 @@ export default function AppLayout(): React.JSX.Element {
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Navbar />
|
||||
</Box>
|
||||
<Tooltip title="Toggle theme">
|
||||
<IconButton aria-label="Toggle theme" onClick={toggleTheme}>
|
||||
{mode === "dark" ? <Brightness7Icon /> : <Brightness4Icon />}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
<PackageTable
|
||||
|
||||
@@ -22,12 +22,13 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import CopyButton from "components/common/CopyButton";
|
||||
import { QueryKeys } from "hooks/QueryKeys";
|
||||
import { useClient } from "hooks/useClient";
|
||||
import { useThemeMode } from "hooks/useThemeMode";
|
||||
import type { Changes } from "models/Changes";
|
||||
import type { RepositoryId } from "models/RepositoryId";
|
||||
import React from "react";
|
||||
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import diff from "react-syntax-highlighter/dist/esm/languages/hljs/diff";
|
||||
import { githubGist } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
import { githubGist, vs2015 } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
|
||||
SyntaxHighlighter.registerLanguage("diff", diff);
|
||||
|
||||
@@ -38,6 +39,7 @@ interface ChangesTabProps {
|
||||
|
||||
export default function ChangesTab({ packageBase, repository }: ChangesTabProps): React.JSX.Element {
|
||||
const client = useClient();
|
||||
const { mode } = useThemeMode();
|
||||
|
||||
const { data } = useQuery<Changes>({
|
||||
queryKey: QueryKeys.changes(packageBase, repository),
|
||||
@@ -50,7 +52,7 @@ export default function ChangesTab({ packageBase, repository }: ChangesTabProps)
|
||||
return <Box sx={{ position: "relative", mt: 1 }}>
|
||||
<SyntaxHighlighter
|
||||
language="diff"
|
||||
style={githubGist}
|
||||
style={mode === "dark" ? vs2015 : githubGist}
|
||||
customStyle={{
|
||||
padding: "16px",
|
||||
borderRadius: "4px",
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function PackagePatchesList({
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
{editable &&
|
||||
<IconButton size="small" color="error" onClick={() => onDelete(patch.key)}>
|
||||
<IconButton size="small" color="error" aria-label="Remove patch" onClick={() => onDelete(patch.key)}>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
}
|
||||
|
||||
27
frontend/src/contexts/ThemeContext.ts
Normal file
27
frontend/src/contexts/ThemeContext.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2026 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { createContext } from "react";
|
||||
|
||||
export interface ThemeContextValue {
|
||||
mode: "light" | "dark";
|
||||
toggleTheme: () => void;
|
||||
}
|
||||
|
||||
export const ThemeContext = createContext<ThemeContextValue | null>(null);
|
||||
56
frontend/src/contexts/ThemeProvider.tsx
Normal file
56
frontend/src/contexts/ThemeProvider.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2026 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import { ThemeProvider as MuiThemeProvider } from "@mui/material/styles";
|
||||
import { defaults as chartDefaults } from "chart.js";
|
||||
import { ThemeContext } from "contexts/ThemeContext";
|
||||
import { useLocalStorage } from "hooks/useLocalStorage";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
import { createAppTheme } from "theme/Theme";
|
||||
|
||||
function systemPreference(): "light" | "dark" {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }): React.JSX.Element {
|
||||
const [mode, setMode] = useLocalStorage<"light" | "dark">("theme-mode", systemPreference());
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
setMode(prev => prev === "light" ? "dark" : "light");
|
||||
}, [setMode]);
|
||||
|
||||
const theme = useMemo(() => createAppTheme(mode), [mode]);
|
||||
|
||||
useEffect(() => {
|
||||
const textColor = mode === "dark" ? "rgba(255,255,255,0.7)" : "rgba(0,0,0,0.7)";
|
||||
const gridColor = mode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
||||
chartDefaults.color = textColor;
|
||||
chartDefaults.borderColor = gridColor;
|
||||
}, [mode]);
|
||||
|
||||
const value = useMemo(() => ({ mode, toggleTheme }), [mode, toggleTheme]);
|
||||
|
||||
return <ThemeContext.Provider value={value}>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</MuiThemeProvider>
|
||||
</ThemeContext.Provider>;
|
||||
}
|
||||
25
frontend/src/hooks/useThemeMode.ts
Normal file
25
frontend/src/hooks/useThemeMode.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2026 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { ThemeContext, type ThemeContextValue } from "contexts/ThemeContext";
|
||||
import { useContextNotNull } from "hooks/useContextNotNull";
|
||||
|
||||
export function useThemeMode(): ThemeContextValue {
|
||||
return useContextNotNull(ThemeContext);
|
||||
}
|
||||
@@ -21,15 +21,15 @@ import { amber, green, grey, orange, red } from "@mui/material/colors";
|
||||
import type { BuildStatus } from "models/BuildStatus";
|
||||
|
||||
const base: Record<BuildStatus, string> = {
|
||||
unknown: grey[800],
|
||||
pending: amber[900],
|
||||
building: orange[900],
|
||||
failed: red[900],
|
||||
success: green[800],
|
||||
unknown: grey[600],
|
||||
pending: amber[700],
|
||||
building: orange[800],
|
||||
failed: red[700],
|
||||
success: green[700],
|
||||
};
|
||||
|
||||
const headerBase: Record<BuildStatus, string> = {
|
||||
unknown: grey[800],
|
||||
unknown: grey[600],
|
||||
pending: amber[700],
|
||||
building: orange[600],
|
||||
failed: red[500],
|
||||
|
||||
@@ -17,9 +17,13 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { createTheme } from "@mui/material/styles";
|
||||
import { createTheme, type Theme } from "@mui/material/styles";
|
||||
|
||||
const Theme = createTheme({
|
||||
export function createAppTheme(mode: "light" | "dark"): Theme {
|
||||
return createTheme({
|
||||
palette: {
|
||||
mode,
|
||||
},
|
||||
components: {
|
||||
MuiDialog: {
|
||||
defaultProps: {
|
||||
@@ -28,6 +32,5 @@ const Theme = createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default Theme;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
pkgbase='ahriman'
|
||||
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
|
||||
pkgver=2.20.0rc6
|
||||
pkgver=2.20.1
|
||||
pkgrel=1
|
||||
pkgdesc="ArcH linux ReposItory MANager"
|
||||
arch=('any')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.TH AHRIMAN "1" "2026\-03\-06" "ahriman 2.20.0rc6" "ArcH linux ReposItory MANager"
|
||||
.TH AHRIMAN "1" "2026\-03\-13" "ahriman 2.20.1" "ArcH linux ReposItory MANager"
|
||||
.SH NAME
|
||||
ahriman \- ArcH linux ReposItory MANager
|
||||
.SH SYNOPSIS
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "2.20.0rc6"
|
||||
__version__ = "2.20.1"
|
||||
|
||||
Reference in New Issue
Block a user