Compare commits

..

60 Commits

Author SHA1 Message Date
4d009cba6d Release 2.20.0rc3 2026-02-20 20:56:05 +02:00
f6defbf90d fix: rollback samesite option to Lax, because of broken OAuth 2026-02-20 20:54:37 +02:00
c4fefa6071 Release 2.20.0rc2 2026-02-20 16:30:37 +02:00
b0f1828ae7 feat: add retry policy 2026-02-20 16:24:14 +02:00
dec025b45a feat: raise OptionError on missing OAuth provider class instead of generic AttributeError 2026-02-19 10:19:03 +02:00
89008e5350 fix: use context manager for selector and smtp session 2026-02-19 10:19:03 +02:00
422196d413 fix: force data filter for tar archive extraction
(python3.14 default anyway)
2026-02-19 10:19:03 +02:00
6fe2eade26 feat: (more) secure cookies 2026-02-19 10:19:03 +02:00
5266f54257 fix: speedup table reload by updating only changed statuses
it has been found that on big (>100) repos it starts lagging on reload.
This commit adds guard to avoid updating rows whose package statuses
were not changed
2026-02-19 10:19:03 +02:00
bbf9e38fda Release 2.20.0rc1 2026-02-18 13:34:08 +02:00
ba80a91d95 feat: implement CSRF protection 2026-02-18 13:34:08 +02:00
536d040a6a feat: handle only unknownpackageerror on aur load 2026-02-18 13:34:08 +02:00
bed8752f3a fix: filter logs by repository (twice) before rotation 2026-02-18 13:34:05 +02:00
4093ca8986 fix: do not clear queue on queue fetch failures 2026-02-18 13:34:03 +02:00
f027155885 docs: correct docstring for list_flatmap method 2026-02-18 13:34:03 +02:00
443d4ae667 fix: correct vcs definition for cvs packages 2026-02-18 13:34:00 +02:00
c8f7fa8c51 fix: load gitremote triggers configuration schema from non-standard
paths
2026-02-18 13:33:57 +02:00
93c36fb429 docs: update documentation for the lasts archive feature 2026-02-16 22:07:34 +02:00
2d6d42f969 feat: archive package tree implementation (#153)
* store built packages in archive tree instead of repository

* write tests to support new changes

* implement atomic_move method, move files only with lock

* use generic packages tree for all repos

* lookup through archive packages before build

* add archive trigger

* add archive trigger

* regenerate docs

* gpg loader fix

* support requires repostory flag

* drop excess REQUIRES_REPOSITORY

* simplify symlionk creation

* remove generators

* fix sttyle

* add separate function for symlinks creation

* fix rebase

* add note about slicing

* smol refactoring of archive_tree class

* remove duplicate code

* fix typos

* few review fixes

* monor fixes and typos

* clean empty directories

* remove side effect from getter

* drop recursive remove

* ensure_exists now accepts only argument

* add package like guard to symlinks fix

* speedup archive_lookup processing by iterrupting cycle

* remove custom filelock

* fix naming

* remove remove flag from repo

* review fixes

* restore wrapper around filelock

* extract repository explorer to separate class

* docs update

* fix ide findings
2026-02-16 00:12:51 +02:00
6a2454548d refactor: drop some methods from package class into separated wrappers 2026-02-11 03:05:49 +02:00
389bad6725 fix: use effective uid instead of uid 2026-02-03 16:38:13 +02:00
5738b8b911 fix: rewrite preserver_owner method complitely
Previous implementation was somewhat working in the most) scenarios, but
was super slow to handle permissions. However, it is actually very
limited operations in which the application can do anything, so it is
much easier to just drop privileged user to normal one
2026-02-03 15:27:19 +02:00
5ac2e3de19 fix: handle permissionerror during walking over tree
Previously it tried to look into 700 directories (e.g. .gnupg) which
breaks running as non-ahriman user
2026-02-02 22:16:15 +02:00
799dc73d8a feat: update package status to success in case if package is up-to-date 2026-01-26 01:56:30 +02:00
4e79cbf71a fix: fallback to package name for missing bases in archive
package zoom is being generated without base, leading to None there

Closes #155
2026-01-22 16:34:19 +02:00
4fa5d55317 type: replace generator return type with iterator 2026-01-15 15:22:03 +02:00
a7fa3b90e4 type: fix typing in some modules 2026-01-14 14:28:31 +02:00
ce07cda8ab fix: pass underlying exception on extensionerror 2026-01-14 14:06:34 +02:00
00c4f32294 fix: correct exception type on repository id comparison 2026-01-14 13:59:07 +02:00
49cf91ea52 chore: copyright update 2026-01-08 02:50:37 +02:00
4a8430dc67 build: allow to rebuild images manually 2026-01-08 02:45:31 +02:00
46af782db2 build: drop shtab wrapper after their release 2025-11-18 20:47:20 +02:00
6443e02352 type: use as keyword in case match 2025-10-26 09:36:54 +02:00
999ad39d6f feat: add trigger loader guard 2025-09-17 14:45:09 +03:00
dfab5f56b2 feat: use atexit instead of del for triggers 2025-08-11 14:53:10 +03:00
10798b9ba3 fix: correctly process trigger repo specific settings in validator (see #154) 2025-08-01 16:53:15 +03:00
358e3dc4d2 feat: expose repository name and architecure in configuration if available
In some cases there are reference to current repository settings. In
order to handle it correctly two ro options have been added

Related to #154
2025-07-31 14:14:22 +03:00
c13cd029bc feat: fully readable configuration from environment 2025-07-23 14:49:38 +03:00
ae32cc8fbb type: use custom comparable for comparable functions 2025-07-15 21:20:49 +03:00
dff5b775a9 refactor: move logs rotation to separated trigger which is enabled by default
Previous solution, well, worked kinda fine-ish, though we have much
better mechanisms to do so
2025-07-15 11:26:00 +03:00
db3f20546e fix: do not update datalist if search substring hasn't changed 2025-07-14 21:30:27 +03:00
53368468a4 fix: block autoupdate on any modal opened 2025-07-14 21:12:33 +03:00
228c2cce51 style: use parebtgeses-less exceptions in side effects (tests only) 2025-07-14 20:33:54 +03:00
f5aec4e5c1 fix: fix search result sorting based if there is exact match or
starts with (closes #152)
2025-07-14 01:12:27 +03:00
9217c8c759 feat: add reload command and api endpoints 2025-07-13 15:35:49 +03:00
6392520e06 style: reorder schemas properties to alphabet order 2025-07-13 15:34:22 +03:00
c6306631e6 fix: careful handling of file permissions during initialization
It has been found that during cold start (e.g. in docker container),
some permissions are invalid. In order to handle that, some operations
are not guarded with RepositoryPaths.preserve_root guard

In addition, it has been also found that in some cases (e.g. web server
start) migrations are performed on empty repository identifier which may
lead to wrong data (see also 435375721d),
as well as some unexpected results during database operations. In order
to handle that, now all watcher instances have their own databases (and
configurations)
2025-07-11 17:13:37 +03:00
97b906c536 revert: type: fix broken types in dependencies
This reverts commit bd770aac2f.
2025-07-11 03:10:32 +03:00
435375721d fix: fix migrations on empty repositories 2025-07-08 16:46:34 +03:00
4c5caba6b7 fix: trim provides/depends versions and lookup provides through pkgname
(#150)

Current implementation did it in wrong way. First of all, there was a
lookup through pkgbase instead of pkgname, which lead to errors, because
aur api doesn't allow to search by pkgbase (as well as provides is
basically pkgname instead)

It also was found that dependencies resolution lookup has been performed
by using raw packages array, which can include versions, descriptions
etc
2025-07-08 16:22:41 +03:00
b83df9d2c5 refactor: reorganize js methods 2025-07-07 20:33:01 +03:00
f2ea76aab9 feat: add silent logs reload 2025-07-07 17:02:08 +03:00
471b1c1331 feat: add cookies support and improve autorefresh UX
This commit also includes changing of load logic to update row by row
instead of full table toggle. It also changes behaviour on openned
dropdowns blocking refresh
2025-07-07 12:48:00 +03:00
bd770aac2f type: fix broken types in dependencies
aiosignal 1.4.0 has been released including feature https://github.com/aio-libs/aiosignal/pull/699
However, aiohttp still uses old types which were not merged as well as
were not even implemented (the closest is
https://github.com/aio-libs/aiohttp/pull/11160 and related issue
https://github.com/aio-libs/aiohttp/issues/11036, whoever it still
doesn't change signatures for _AppSignal)

Let's just mark those calls as ignore
2025-07-04 13:03:56 +03:00
6abe35ef8c fix: separate ua by spaces 2025-07-02 14:50:21 +03:00
fdc27a9ebf feat: filter out obvious duplicates from multisearch 2025-07-01 09:59:29 +03:00
b729096a25 feat: block refresh upon opening dashboard or changing table position 2025-07-01 06:58:03 +03:00
390b9da29e feat: allow to use 0 as auto refresh interval with special meaning (#148) 2025-07-01 03:37:49 +03:00
256376df85 feat: add autorefresh button to the main page (#149)
* also add configuration options and change behaviour accordingly
2025-07-01 03:22:01 +03:00
939a94d889 feat: add autoupdate button to package info (#148) 2025-06-29 22:22:54 +03:00
467 changed files with 6775 additions and 3528 deletions

View File

@@ -7,6 +7,7 @@ on:
tags:
- '*'
- '!*rc*'
workflow_dispatch:
permissions:
contents: read

View File

@@ -10,7 +10,7 @@ echo -e '[arcanisrepo]\nServer = https://repo.arcanis.me/$arch\nSigLevel = Never
# refresh the image
pacman -Syyu --noconfirm
# main dependencies
pacman -S --noconfirm devtools git pyalpm python-bcrypt python-inflection python-pyelftools python-requests python-systemd sudo
pacman -S --noconfirm devtools git pyalpm python-bcrypt python-filelock python-inflection python-pyelftools python-requests python-systemd sudo
# make dependencies
pacman -S --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel
# optional dependencies

View File

@@ -165,6 +165,11 @@ Again, the most checks can be performed by `tox` command, though some additional
# Blank line again and package imports
from ahriman.core.configuration import Configuration
# Multiline import example
from ahriman.core.database.operations import (
AuthOperations,
BuildOperations,
)
```
* One file should define only one class, exception is class satellites in case if file length remains less than 400 lines.
@@ -215,6 +220,7 @@ Again, the most checks can be performed by `tox` command, though some additional
* It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced.
* On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release.
* Enumerations (`Enum` classes) are allowed and recommended. However, it is recommended to use `StrEnum` class if there are from/to string conversions and `IntEnum` otherwise.
* `Generator` return type is not allowed. Generator functions must return generic `Iterator` object. Documentation should be described as `Yields`, however, because of pylint checks. Unfortunately, `Iterable` return type is not available for generators also, because of specific `contextlib.contextmanager` case.
### Other checks

View File

@@ -24,7 +24,8 @@ RUN pacman -S --noconfirm --asdeps \
devtools \
git \
pyalpm \
python-bcrypt \
python-bcrypt \
python-filelock \
python-inflection \
python-pyelftools \
python-requests \

File diff suppressed because it is too large Load Diff

View File

@@ -100,6 +100,14 @@ ahriman.application.handlers.rebuild module
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.reload module
------------------------------------------
.. automodule:: ahriman.application.handlers.reload
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.remove module
------------------------------------------

View File

@@ -0,0 +1,29 @@
ahriman.core.archive package
============================
Submodules
----------
ahriman.core.archive.archive\_tree module
-----------------------------------------
.. automodule:: ahriman.core.archive.archive_tree
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.archive.archive\_trigger module
--------------------------------------------
.. automodule:: ahriman.core.archive.archive_trigger
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.archive
:members:
:no-undoc-members:
:show-inheritance:

View File

@@ -12,6 +12,14 @@ ahriman.core.build\_tools.package\_archive module
:no-undoc-members:
:show-inheritance:
ahriman.core.build\_tools.package\_version module
-------------------------------------------------
.. automodule:: ahriman.core.build_tools.package_version
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.build\_tools.sources module
----------------------------------------

View File

@@ -132,6 +132,14 @@ ahriman.core.database.migrations.m015\_logs\_process\_id module
:no-undoc-members:
:show-inheritance:
ahriman.core.database.migrations.m016\_archive module
-----------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m016_archive
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------

View File

@@ -0,0 +1,29 @@
ahriman.core.housekeeping package
=================================
Submodules
----------
ahriman.core.housekeeping.archive\_rotation\_trigger module
-----------------------------------------------------------
.. automodule:: ahriman.core.housekeeping.archive_rotation_trigger
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.housekeeping.logs\_rotation\_trigger module
--------------------------------------------------------
.. automodule:: ahriman.core.housekeeping.logs_rotation_trigger
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.housekeeping
:members:
:no-undoc-members:
:show-inheritance:

View File

@@ -28,6 +28,14 @@ ahriman.core.repository.executor module
:no-undoc-members:
:show-inheritance:
ahriman.core.repository.explorer module
---------------------------------------
.. automodule:: ahriman.core.repository.explorer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.repository.package\_info module
--------------------------------------------

View File

@@ -8,6 +8,7 @@ Subpackages
:maxdepth: 4
ahriman.core.alpm
ahriman.core.archive
ahriman.core.auth
ahriman.core.build_tools
ahriman.core.configuration
@@ -15,6 +16,7 @@ Subpackages
ahriman.core.distributed
ahriman.core.formatters
ahriman.core.gitremote
ahriman.core.housekeeping
ahriman.core.http
ahriman.core.log
ahriman.core.report

View File

@@ -44,6 +44,14 @@ ahriman.web.schemas.changes\_schema module
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.configuration\_schema module
------------------------------------------------
.. automodule:: ahriman.web.schemas.configuration_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.counters\_schema module
-------------------------------------------
@@ -140,6 +148,14 @@ ahriman.web.schemas.logs\_schema module
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.logs\_search\_schema module
-----------------------------------------------
.. automodule:: ahriman.web.schemas.logs_search_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.oauth2\_schema module
-----------------------------------------

View File

@@ -12,6 +12,14 @@ ahriman.web.views.v1.service.add module
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.service.config module
------------------------------------------
.. automodule:: ahriman.web.views.v1.service.config
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.service.logs module
----------------------------------------

View File

@@ -40,6 +40,7 @@ This package contains everything required for the most of application actions an
* ``ahriman.core.distributed`` package with triggers and helpers for distributed build system.
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
* ``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.report`` is a package with reporting triggers. Should not be called directly.
@@ -119,6 +120,20 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
/var/lib/ahriman/
├── ahriman.db
├── archive
│ ├── packages
│ │ └── a
│ │ └── ahriman
│ │ └── ahriman-2.0.0-1-any.pkg.tar.zst
│ └── repos
│ └── 2026
│ └── 01
│ └── 01
│ └── aur
│ └── x86_64
│ ├── ahriman-2.0.0-1-any.pkg.tar.zst -> ../../../../../../packages/a/ahriman/ahriman-2.0.0-1-any.pkg.tar.zst
│ ├── aur.db -> aur.db.tar.gz
│ └── aur.db.tar.gz
├── cache
├── chroot
│ └── aur
@@ -138,6 +153,7 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
└── repository
└── aur
└── x86_64
├── ahriman-2.0.0-1-any.pkg.tar.zst -> ../../../archive/packages/a/ahriman/ahriman-2.0.0-1-any.pkg.tar.zst
├── aur.db -> aur.db.tar.gz
├── aur.db.tar.gz
├── aur.files -> aur.files.tar.gz
@@ -145,11 +161,18 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
There are multiple subdirectories, some of them are commons 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.
The archive also allows the build process to skip rebuilding a package if a matching version already exists.
* ``cache`` is a directory with locally stored PKGBUILD's and VCS packages. It is common for all repositories and architectures.
* ``chroot/{repository}`` is a chroot directory for ``devtools``. It is specific for each repository, but shared for different architectures inside (the ``devtools`` handles architectures automatically).
* ``packages/{repository}/{architecture}`` is a directory with prebuilt packages. When a package is built, first it will be uploaded to this directory and later will be handled by update process. It is architecture and repository specific.
* ``pacman/{repository}/{architecture}`` is the repository and architecture specific caches for pacman's databases.
* ``repository/{repository}/{architecture}`` is a repository packages directory.
* ``repository/{repository}/{architecture}`` is a repository packages directory. Package files in this directory are symlinks to the archive.
Normally you should avoid direct interaction with the application tree. For tree migration process refer to the :doc:`migration notes <migrations/index>`.

View File

@@ -65,6 +65,8 @@ will try to read value from ``SECRET`` environment variable. In case if the requ
will eventually lead ``key`` option in section ``section1`` to be set to the value of ``HOME`` environment variable (if available).
Moreover, configuration can be read from environment variables directly by following the same naming convention, e.g. in the example above, one can have environment variable named ``section1:key`` (e.g. ``section1:key=$HOME``) and it will be substituted to the configuration with the highest priority.
There is also additional subcommand which will allow to validate configuration and print found errors. In order to do so, run ``service-config-validate`` subcommand, e.g.:
.. code-block:: shell
@@ -81,7 +83,6 @@ Base configuration settings.
* ``apply_migrations`` - perform database migrations on the application start, boolean, optional, default ``yes``. Useful if you are using git version. Note, however, that this option must be changed only if you know what to do and going to handle migrations manually.
* ``database`` - path to the application SQLite database, string, required.
* ``include`` - path to directory with configuration files overrides, string, optional. Files will be read in alphabetical order.
* ``keep_last_logs`` - amount of build logs to be kept for each package, integer, optional ,default ``0``. Logs will be cleared at the end of each process.
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
``alpm:*`` groups
@@ -96,6 +97,15 @@ libalpm and AUR related configuration. Group name can refer to architecture, e.g
* ``sync_files_database`` - download files database from mirror, boolean, required.
* ``use_ahriman_cache`` - use local pacman package cache instead of system one, boolean, required. With this option enabled you might want to refresh database periodically (available as additional flag for some subcommands). If set to ``no``, databases must be synchronized manually.
``aur`` group
-------------
Archlinux User Repository related configuration.
* ``max_retries`` - maximum amount of retries of HTTP requests, integer, optional, default ``0``.
* ``retry_backoff`` - retry exponential backoff, float, optional, default ``0.0``.
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
``auth`` group
--------------
@@ -157,7 +167,9 @@ Reporting to web service related settings. In most cases there is fallback to we
* ``enabled`` - enable reporting to web service, boolean, optional, default ``yes`` for backward compatibility.
* ``address`` - remote web service address with protocol, string, optional. In case of websocket, the ``http+unix`` scheme and URL encoded address (e.g. ``%2Fvar%2Flib%2Fahriman`` for ``/var/lib/ahriman``) must be used, e.g. ``http+unix://%2Fvar%2Flib%2Fahriman%2Fsocket``. In case if none set, it will be guessed from ``web`` section.
* ``max_retries`` - maximum amount of retries of HTTP requests, integer, optional, default ``0``.
* ``password`` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
* ``retry_backoff`` - retry exponential backoff, float, optional, default ``0.0``.
* ``suppress_http_log_errors`` - suppress HTTP log errors, boolean, optional, default ``no``. If set to ``yes``, any HTTP log errors (e.g. if web server is not available, but HTTP logging is enabled) will be suppressed.
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
* ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.
@@ -168,6 +180,7 @@ Reporting to web service related settings. In most cases there is fallback to we
Web server settings. This feature requires ``aiohttp`` libraries to be installed.
* ``address`` - optional address in form ``proto://host:port`` (``port`` can be omitted in case of default ``proto`` ports), will be used instead of ``http://{host}:{port}`` in case if set, string, optional. This option is required in case if ``OAuth`` provider is used.
* ``autorefresh_intervals`` - enable page auto refresh options, space separated list of integers, optional. The first defined interval will be used as default. If no intervals set, the auto refresh buttons will be disabled. If first element of the list equals ``0``, auto refresh will be disabled by default.
* ``enable_archive_upload`` - allow to upload packages via HTTP (i.e. call of ``/api/v1/service/upload`` uri), boolean, optional, default ``no``.
* ``host`` - host to bind, string, optional.
* ``index_url`` - full URL of the repository index page, string, optional.
@@ -180,8 +193,15 @@ Web server settings. This feature requires ``aiohttp`` libraries to be installed
* ``unix_socket_unsafe`` - set unsafe (o+w) permissions to unix socket, boolean, optional, default ``yes``. This option is enabled by default, because it is supposed that unix socket is created in safe environment (only web service is supposed to be used in unsafe), but it can be disabled by configuration.
* ``wait_timeout`` - wait timeout in seconds, maximum amount of time to be waited before lock will be free, integer, optional.
``archive`` group
-----------------
Describes settings for packages archives management extensions.
* ``keep_built_packages`` - keep this amount of built packages with different versions, integer, required. ``0`` will effectively disable archives removal.
``keyring`` group
--------------------
-----------------
Keyring package generator plugin.
@@ -199,6 +219,13 @@ Keyring generator plugin
* ``revoked`` - list of revoked packagers keys, space separated list of strings, optional.
* ``trusted`` - list of master keys, space separated list of strings, optional, if not set, the ``key`` option from ``sign`` group will be used.
``logs-rotation`` group
-----------------------
This section describes settings for the ``ahriman.core.housekeeping.LogsRotationTrigger`` plugin.
* ``keep_last_logs`` - amount of build logs to be kept for each package, integer, required. Logs will be cleared at the end of each process.
``mirrorlist`` group
--------------------
@@ -234,6 +261,7 @@ Available options are:
Remote pull trigger
^^^^^^^^^^^^^^^^^^^
* ``type`` - type of the pull, string, optional, must be set to ``gitremote`` if exists.
* ``pull_url`` - URL of the remote repository from which PKGBUILDs can be pulled before build process, string, required.
* ``pull_branch`` - branch of the remote repository from which PKGBUILDs can be pulled before build process, string, optional, default is ``master``.
@@ -254,6 +282,7 @@ Available options are:
Remote push trigger
^^^^^^^^^^^^^^^^^^^
* ``type`` - type of the push, string, optional, must be set to ``gitremote`` if exists.
* ``commit_email`` - git commit email, string, optional, default is ``ahriman@localhost``.
* ``commit_user`` - git commit user, string, optional, default is ``ahriman``.
* ``push_url`` - URL of the remote repository to which PKGBUILDs should be pushed after build process, string, required.
@@ -349,6 +378,8 @@ Section name must be either ``telegram`` (plus optional architecture name, e.g.
* ``chat_id`` - telegram chat id, either string with ``@`` or integer value, required.
* ``homepage`` - link to homepage, string, optional.
* ``link_path`` - prefix for HTML links, string, required.
* ``max_retries`` - maximum amount of retries of HTTP requests, integer, optional, default ``0``.
* ``retry_backoff`` - retry exponential backoff, float, optional, default ``0.0``.
* ``rss_url`` - link to RSS feed, string, optional.
* ``template`` - Jinja2 template name, string, required.
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
@@ -374,6 +405,7 @@ Type will be read from several sources:
This feature requires GitHub key creation (see below). Section name must be either ``github`` (plus optional architecture name, e.g. ``github:x86_64``) or random name with ``type`` set.
* ``type`` - type of the upload, string, optional, must be set to ``github`` if exists.
* ``max_retries`` - maximum amount of retries of HTTP requests, integer, optional, default ``0``.
* ``owner`` - GitHub repository owner, string, required.
* ``password`` - created GitHub API key. In order to create it do the following:
@@ -383,6 +415,7 @@ This feature requires GitHub key creation (see below). Section name must be eith
#. Generate new token. Required scope is ``public_repo`` (or ``repo`` for private repository support).
* ``repository`` - GitHub repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme).
* ``retry_backoff`` - retry exponential backoff, float, optional, default ``0.0``.
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
* ``use_full_release_name`` - if set to ``yes``, the release will contain both repository name and architecture, and only architecture otherwise, boolean, optional, default ``no`` (legacy behavior).
* ``username`` - GitHub authorization user, string, required. Basically the same as ``owner``.
@@ -393,6 +426,8 @@ This feature requires GitHub key creation (see below). Section name must be eith
Section name must be either ``remote-service`` (plus optional architecture name, e.g. ``remote-service:x86_64``) or random name with ``type`` set.
* ``type`` - type of the report, string, optional, must be set to ``remote-service`` if exists.
* ``max_retries`` - maximum amount of retries of HTTP requests, integer, optional, default ``0``.
* ``retry_backoff`` - retry exponential backoff, float, optional, default ``0.0``.
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
``rsync`` type

View File

@@ -40,6 +40,8 @@ docutils==0.21.2
# sphinx
# sphinx-argparse
# sphinx-rtd-theme
filelock==3.24.0
# via ahriman (pyproject.toml)
frozenlist==1.6.0
# via
# aiohttp

View File

@@ -1,7 +1,7 @@
Triggers
========
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides three types of extensions - reporting, files uploading and PKGBUILD synchronization. Each extension must derive from the ``ahriman.core.triggers.Trigger`` class and should implement at least one of the abstract methods:
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides several types of extensions - reporting, files uploading, PKGBUILD synchronization, repository archiving, housekeeping and distributed builds support. Each extension must derive from the ``ahriman.core.triggers.Trigger`` class and should implement at least one of the abstract methods:
* ``on_result`` - trigger action which will be called after build process, the build result and the list of repository packages will be supplied as arguments.
* ``on_start`` - trigger action which will be called right before the start of the application process.
@@ -14,6 +14,11 @@ Built-in triggers
For the configuration details and settings explanation kindly refer to the :doc:`documentation <configuration>`.
``ahriman.core.archive.ArchiveTrigger``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This trigger provides date-based snapshots of the repository. It organizes packages into a daily directory tree (``repos/YYYY/MM/DD``) with its own pacman database. On each run it creates symlinks from the daily snapshot to the actual package archives and maintains the database accordingly. It also takes care of cleaning up broken symlinks and empty directories for packages which have been removed.
``ahriman.core.distributed.WorkerLoaderTrigger``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -36,6 +41,16 @@ In order to update those packages you would need to clone your repository separa
This trigger will be called right after build process (``on_result``). It will pick PKGBUILDs for the updated packages, pull them (together with any other files) and commit and push changes to remote repository. No real use cases, but the most of user repositories do it.
``ahriman.core.housekeeping.ArchiveRotationTrigger``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This trigger removes old package versions from the archive directory. It implements ``on_result`` and, after each build, compares available versions for updated packages and removes the older ones, keeping only the last N versions as configured by ``keep_built_packages`` option.
``ahriman.core.housekeeping.LogsRotationTrigger``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Simple trigger to rotate build logs. It implements ``on_result`` and removes old log records after each build process, keeping only the last N records as configured by ``keep_last_logs`` option.
``ahriman.core.report.ReportTrigger``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -2,13 +2,13 @@
pkgbase='ahriman'
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
pkgver=2.19.3
pkgver=2.20.0rc3
pkgrel=1
pkgdesc="ArcH linux ReposItory MANager"
arch=('any')
url="https://ahriman.readthedocs.io/"
license=('GPL-3.0-or-later')
depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-bcrypt' 'python-inflection' 'python-pyelftools' 'python-requests')
depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-bcrypt' 'python-filelock' 'python-inflection' 'python-pyelftools' 'python-requests')
makedepends=('python-build' 'python-flit' 'python-installer' 'python-wheel')
source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgbase-$pkgver.tar.gz"
"$pkgbase.sysusers"
@@ -40,6 +40,7 @@ package_ahriman-core() {
'rsync: sync by using rsync')
install="$pkgbase.install"
backup=('etc/ahriman.ini'
'etc/ahriman.ini.d/00-housekeeping.ini'
'etc/ahriman.ini.d/logging.ini')
cd "$pkgbase-$pkgver"
@@ -49,6 +50,7 @@ package_ahriman-core() {
# keep usr/share configs as reference and copy them to /etc
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini" "$pkgdir/etc/ahriman.ini"
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/00-housekeeping.ini" "$pkgdir/etc/ahriman.ini.d/00-housekeeping.ini"
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/logging.ini" "$pkgdir/etc/ahriman.ini.d/logging.ini"
install -Dm644 "$srcdir/$pkgbase.sysusers" "$pkgdir/usr/lib/sysusers.d/$pkgbase.conf"

View File

@@ -5,6 +5,7 @@ After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/ahriman web
ExecReload=/usr/bin/ahriman web-reload
User=ahriman
Group=ahriman

View File

@@ -7,8 +7,6 @@ logging = ahriman.ini.d/logging.ini
;apply_migrations = yes
; Path to the application SQLite database.
database = ${repository:root}/ahriman.db
; Keep last build logs for each package
keep_last_logs = 5
[alpm]
; Path to pacman system database cache.
@@ -25,6 +23,14 @@ sync_files_database = yes
; as additional option for some subcommands). If set to no, databases must be synchronized manually.
use_ahriman_cache = yes
[aur]
; Maximum amount of retries of HTTP requests.
max_retries = 3
; Retry exponential backoff.
retry_backoff = 1.0
; HTTP request timeout in seconds.
;timeout = 30
[build]
; List of additional flags passed to archbuild command.
;archbuild_flags =
@@ -45,9 +51,13 @@ triggers[] = ahriman.core.gitremote.RemotePullTrigger
triggers[] = ahriman.core.report.ReportTrigger
triggers[] = ahriman.core.upload.UploadTrigger
triggers[] = ahriman.core.gitremote.RemotePushTrigger
triggers[] = ahriman.core.housekeeping.LogsRotationTrigger
triggers[] = ahriman.core.housekeeping.ArchiveRotationTrigger
; List of well-known triggers. Used only for configuration purposes.
triggers_known[] = ahriman.core.gitremote.RemotePullTrigger
triggers_known[] = ahriman.core.gitremote.RemotePushTrigger
triggers_known[] = ahriman.core.housekeeping.ArchiveRotationTrigger
triggers_known[] = ahriman.core.housekeeping.LogsRotationTrigger
triggers_known[] = ahriman.core.report.ReportTrigger
triggers_known[] = ahriman.core.upload.UploadTrigger
; Maximal age in seconds of the VCS packages before their version will be updated with its remote source.
@@ -71,8 +81,12 @@ enabled = yes
; In case if unix sockets are used, it might point to the valid socket with encoded path, e.g.:
; address = http+unix://%2Fvar%2Flib%2Fahriman%2Fsocket
;address = http://${web:host}:${web:port}
; Maximum amount of retries of HTTP requests.
;max_retries = 0
; Optional password for authentication (if enabled).
;password =
; Retry exponential backoff.
;retry_backoff = 0.0
; Do not log HTTP errors if occurs.
suppress_http_log_errors = yes
; HTTP request timeout in seconds.
@@ -214,6 +228,10 @@ templates[] = ${prefix}/share/ahriman/templates
;homepage=
; Prefix for packages links. Link to a package will be formed as link_path / filename.
;link_path =
; Maximum amount of retries of HTTP requests.
;max_retries = 0
; Retry exponential backoff.
;retry_backoff = 0.0
; Optional link to the RSS feed.
;rss_url =
; Template name to be used.
@@ -234,12 +252,16 @@ target =
[github]
; Trigger type name.
;type = github
; Maximum amount of retries of HTTP requests.
;max_retries = 0
; GitHub repository owner username.
;owner =
; GitHub API key. public_repo (repo) scope is required.
;password =
; GitHub repository name.
;repository =
; Retry exponential backoff.
;retry_backoff = 0.0
; HTTP request timeout in seconds.
;timeout = 30
; Include repository name to release name (recommended).
@@ -251,6 +273,10 @@ target =
[remote-service]
; Trigger type name.
;type = remote-service
; Maximum amount of retries of HTTP requests.
;max_retries = 0
; Retry exponential backoff.
;retry_backoff = 0.0
; HTTP request timeout in seconds.
;timeout = 30

View File

@@ -0,0 +1,7 @@
[archive]
; Keep amount of last built packages in archive. 0 means keep all packages
keep_built_packages = 1
[logs-rotation]
; Keep last build logs for each package
keep_last_logs = 5

View File

@@ -1,5 +1,6 @@
[build]
; List of well-known triggers. Used only for configuration purposes.
triggers_known[] = ahriman.core.archive.ArchiveTrigger
triggers_known[] = ahriman.core.distributed.WorkerLoaderTrigger
triggers_known[] = ahriman.core.distributed.WorkerTrigger
triggers_known[] = ahriman.core.support.KeyringTrigger

View File

@@ -28,6 +28,10 @@ allow_read_only = yes
; External address of the web service. Will be used for some features like OAuth. If none set will be generated as
; address = http://${web:host}:${web:port}
;address = http://${web:host}:${web:port}
; Enable page auto refresh. Intervals are given in seconds. Default interval is always the first element of the list.
; If no intervals set, auto refresh will be disabled. 0 can only be the first element and will disable auto refresh
; by default.
autorefresh_intervals = 5 1 10 30 60
; Enable file upload endpoint used by some triggers.
;enable_archive_upload = no
; Address to bind the server.

View File

@@ -80,10 +80,28 @@
<button type="button" class="btn btn-secondary" onclick="reload()">
<i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span>
</button>
{% if autorefresh_intervals %}
<div class="btn-group">
<input id="table-autoreload-button" type="checkbox" class="btn-check" autocomplete="off" onclick="toggleTableAutoReload()" checked>
<label for="table-autoreload-button" class="btn btn-outline-secondary" title="toggle auto reload"><i class="bi bi-clock"></i></label>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">select interval</span>
</button>
<ul id="table-autoreload-input" class="dropdown-menu">
{% for interval in autorefresh_intervals %}
<li><a class="dropdown-item {{ "active" if interval.is_active }}" onclick="toggleTableAutoReload({{ interval.interval }})" data-interval="{{ interval.interval }}">{{ interval.text }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
<table id="packages"
data-classes="table table-hover"
data-cookie="true"
data-cookie-id-table="ahriman-packages"
data-cookie-storage="localStorage"
data-export-options='{"fileName": "packages"}'
data-filter-control="true"
data-filter-control-visible="false"
@@ -102,8 +120,8 @@
data-sortable="true"
data-sort-name="base"
data-sort-order="asc"
data-toggle="table"
data-toolbar="#toolbar">
data-toolbar="#toolbar"
data-unique-id="id">
<thead class="table-primary">
<tr>
<th data-checkbox="true"></th>

View File

@@ -3,7 +3,9 @@
function createAlert(title, message, clz, action, id) {
id ??= md5(title + message); // MD5 id from the content
if (alertPlaceholder.querySelector(`#alert-${id}`)) return; // check if there are duplicates
if (alertPlaceholder.querySelector(`#alert-${id}`)) {
return; // check if there are duplicates
}
const wrapper = document.createElement("div");
wrapper.id = `alert-${id}`;

View File

@@ -51,6 +51,87 @@
const dashboardPackagesCountChartCanvas = document.getElementById("dashboard-packages-count-chart");
let dashboardPackagesCountChart = null;
function statusLoad() {
const badgeClass = status => {
if (status === "pending") return "btn-outline-warning";
if (status === "building") return "btn-outline-warning";
if (status === "failed") return "btn-outline-danger";
if (status === "success") return "btn-outline-success";
return "btn-outline-secondary";
};
makeRequest(
"/api/v1/status",
{
query: {
architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
},
data => {
versionBadge.innerHTML = `<i class="bi bi-github"></i> ahriman ${safe(data.version)}`;
dashboardButton.classList.remove(...dashboardButton.classList);
dashboardButton.classList.add("btn");
dashboardButton.classList.add(badgeClass(data.status.status));
dashboardModalHeader.classList.remove(...dashboardModalHeader.classList);
dashboardModalHeader.classList.add("modal-header");
headerClass(data.status.status).forEach(clz => dashboardModalHeader.classList.add(clz));
dashboardName.textContent = data.repository;
dashboardArchitecture.textContent = data.architecture;
dashboardStatus.textContent = data.status.status;
dashboardStatusTimestamp.textContent = new Date(1000 * data.status.timestamp).toISOStringShort();
if (dashboardPackagesStatusesChart) {
const labels = [
"unknown",
"pending",
"building",
"failed",
"success",
];
dashboardPackagesStatusesChart.config.data = {
labels: labels,
datasets: [{
label: "packages in status",
data: labels.map(label => data.packages[label]),
backgroundColor: [
"rgb(55, 58, 60)",
"rgb(255, 117, 24)",
"rgb(255, 117, 24)",
"rgb(255, 0, 57)",
"rgb(63, 182, 24)", // copy-paste from current style
],
}],
};
dashboardPackagesStatusesChart.update();
}
if (dashboardPackagesCountChart) {
dashboardPackagesCountChart.config.data = {
labels: ["packages"],
datasets: [
{
label: "archives",
data: [data.stats.packages],
},
{
label: "bases",
data: [data.stats.bases],
},
],
};
dashboardPackagesCountChart.update();
}
dashboardCanvas.hidden = data.status.total > 0;
},
);
}
ready(_ => {
dashboardPackagesStatusesChart = new Chart(dashboardPackagesStatusesChartCanvas, {
type: "pie",

View File

@@ -148,8 +148,19 @@
packageAddInput.addEventListener("keyup", _ => {
clearTimeout(packageAddInput.requestTimeout);
// do not update datalist if search string didn't change yet
const value = packageAddInput.value;
const previousValue = packageAddInput.dataset.previousValue;
if (value === previousValue) {
return;
}
// store current search string in attributes
packageAddInput.dataset.previousValue = value;
// perform data list update
packageAddInput.requestTimeout = setTimeout(_ => {
const value = packageAddInput.value;
if (value.length >= 3) {
makeRequest(

View File

@@ -80,8 +80,7 @@
data-classes="table table-hover"
data-sortable="true"
data-sort-name="timestamp"
data-sort-order="desc"
data-toggle="table">
data-sort-order="desc">
<thead class="table-primary">
<tr>
<th data-align="right" data-field="timestamp">date</th>
@@ -98,10 +97,24 @@
<input id="package-info-refresh-input" type="checkbox" class="form-check-input" value="" checked>
<label for="package-info-refresh-input" class="form-check-label">update pacman databases</label>
<button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()" data-bs-dismiss="modal"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
<button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
<button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button>
{% endif %}
<button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button>
{% if autorefresh_intervals %}
<button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button>
<div class="btn-group dropup">
<input id="package-info-autoreload-button" type="checkbox" class="btn-check" autocomplete="off" onclick="togglePackageInfoAutoReload()" checked>
<label for="package-info-autoreload-button" class="btn btn-outline-secondary" title="toggle auto reload"><i class="bi bi-clock"></i></label>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">select interval</span>
</button>
<ul id="package-info-autoreload-input" class="dropdown-menu">
{% for interval in autorefresh_intervals %}
<li><a class="dropdown-item {{ "active" if interval.is_active }}" onclick="togglePackageInfoAutoReload({{ interval.interval }})" data-interval="{{ interval.interval }}">{{ interval.text }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i><span class="d-none d-sm-inline"> close</span></button>
</div>
</div>
@@ -140,6 +153,10 @@
const packageInfoRefreshInput = document.getElementById("package-info-refresh-input");
const packageInfoAutoReloadButton = document.getElementById("package-info-autoreload-button");
const packageInfoAutoReloadInput = document.getElementById("package-info-autoreload-input");
let packageInfoAutoReloadTask = null;
function clearChart() {
packageInfoEventsUpdateChartCanvas.hidden = true;
if (packageInfoEventsUpdateChart) {
@@ -148,6 +165,13 @@
}
}
function convertLogs(data, filter) {
return data
.filter((filter || Boolean))
.map(log_record => `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`)
.join("\n");
}
async function copyChanges() {
const changes = packageInfoChangesInput.textContent;
await copyToClipboard(changes, packageInfoChangesCopyButton);
@@ -291,6 +315,69 @@
}
function loadLogs(packageBase, onFailure) {
const sortFn = (left, right) => left.process_id.localeCompare(right.process_id) || left.version.localeCompare(right.version);
const compareFn = (left, right) => left.process_id === right.process_id && left.version === right.version;
makeRequest(
`/api/v2/packages/${packageBase}/logs`,
{
query: {
architecture: repository.architecture,
head: true,
repository: repository.repository,
},
convert: response => response.json(),
},
data => {
const currentVersions = Array.from(packageInfoLogsVersions.children)
.map(el => {
return {
process_id: el.dataset.processId,
version: el.dataset.version,
};
})
.sort(sortFn);
const newVersions = data
.map(el => {
return {
process_id: el.process_id,
version: el.version,
};
})
.sort(sortFn);
if (currentVersions.equals(newVersions, compareFn))
loadLogsActive(packageBase);
else
loadLogsAll(packageBase, onFailure);
},
)
}
function loadLogsActive(packageBase) {
const activeLogSelector = packageInfoLogsVersions.querySelector(".active");
if (activeLogSelector) {
makeRequest(
`/api/v2/packages/${packageBase}/logs`,
{
query: {
architecture: repository.architecture,
repository: repository.repository,
version: activeLogSelector.dataset.version,
process_id: activeLogSelector.dataset.processId,
},
convert: response => response.json(),
},
data => {
activeLogSelector.dataset.logs = convertLogs(data);
activeLogSelector.click();
},
);
}
}
function loadLogsAll(packageBase, onFailure) {
makeRequest(
`/api/v2/packages/${packageBase}/logs`,
{
@@ -319,15 +406,19 @@
const link = document.createElement("a");
link.classList.add("dropdown-item");
link.dataset.version = version.version;
link.dataset.processId = version.process_id;
link.dataset.logs = convertLogs(data, log_record => log_record.version === version.version && log_record.process_id === version.process_id);
link.textContent = new Date(1000 * version.created).toISOStringShort();
link.href = "#";
link.onclick = _ => {
const logs = data
.filter(log_record => log_record.version === version.version && log_record.process_id === version.process_id)
.map(log_record => `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`);
packageInfoLogsInput.textContent = logs.join("\n");
// check if we are at the bottom of the code block
const isScrolledToBottom = packageInfoLogsInput.scrollTop + packageInfoLogsInput.clientHeight >= packageInfoLogsInput.scrollHeight;
packageInfoLogsInput.textContent = link.dataset.logs;
highlight(packageInfoLogsInput);
if (isScrolledToBottom)
packageInfoLogsInput.scrollTop = packageInfoLogsInput.scrollHeight; // scroll to the new end
Array.from(packageInfoLogsVersions.children).forEach(el => el.classList.remove("active"));
link.classList.add("active");
@@ -403,12 +494,12 @@
}
function packageInfoRemove() {
const packageBase = packageInfoModal.package;
const packageBase = packageInfoModal.dataset.package;
packagesRemove([packageBase]);
}
function packageInfoUpdate() {
const packageBase = packageInfoModal.package;
const packageBase = packageInfoModal.dataset.package;
packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked});
}
@@ -416,10 +507,10 @@
const isPackageBaseSet = packageBase !== undefined;
if (isPackageBaseSet) {
// set package base as currently used
packageInfoModal.package = packageBase;
packageInfoModal.dataset.package = packageBase;
} else {
// read package base from the current window attribute
packageBase = packageInfoModal.package;
packageBase = packageInfoModal.dataset.package;
}
const onFailure = error => {
@@ -438,10 +529,27 @@
if (isPackageBaseSet) {
bootstrap.Modal.getOrCreateInstance(packageInfoModal).show();
{% if autorefresh_intervals %}
togglePackageInfoAutoReload();
{% endif %}
}
}
function togglePackageInfoAutoReload(interval) {
clearInterval(packageInfoAutoReloadTask);
packageInfoAutoReloadTask = toggleAutoReload(packageInfoAutoReloadButton, interval, packageInfoAutoReloadInput, _ => {
if (!hasActiveSelection()) {
const packageBase = packageInfoModal.dataset.package;
// we only poll status and logs here
loadPackage(packageBase);
loadLogs(packageBase);
}
});
}
ready(_ => {
packageInfoEventsTable.bootstrapTable({});
packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, {
type: "line",
data: {},
@@ -468,6 +576,11 @@
packageInfoChangesInput.textContent = "";
packageInfoEventsTable.bootstrapTable("load", []);
clearChart();
clearInterval(packageInfoAutoReloadTask);
packageInfoAutoReloadTask = null; // not really required (?) but lets clear everything
});
restoreAutoReloadSettings(packageInfoAutoReloadButton, packageInfoAutoReloadInput);
});
</script>

View File

@@ -10,6 +10,10 @@
const dashboardButton = document.getElementById("dashboard-button");
const versionBadge = document.getElementById("badge-version");
const tableAutoReloadButton = document.getElementById("table-autoreload-button");
const tableAutoReloadInput = document.getElementById("table-autoreload-input");
let tableAutoReloadTask = null;
function doPackageAction(uri, packages, repository, successText, failureText, data) {
makeRequest(
uri,
@@ -55,6 +59,41 @@
return table.bootstrapTable("getSelections").map(row => row.id);
}
function packagesLoad(onFailure) {
makeRequest(
"/api/v1/packages",
{
query: {
architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
},
data => {
const payload = data
.map(description => {
const package_base = description.package.base;
const web_url = description.package.remote.web_url;
return {
id: package_base,
base: web_url ? safeLink(web_url, package_base, package_base).outerHTML : safe(package_base),
version: safe(description.package.version),
packager: description.package.packager ? safe(description.package.packager) : "",
packages: listToTable(Object.keys(description.package.packages)),
groups: listToTable(extractListProperties(description.package, "groups")),
licenses: listToTable(extractListProperties(description.package, "licenses")),
timestamp: new Date(1000 * description.status.timestamp).toISOStringShort(),
status: description.status.status,
};
});
updateTable(table, payload, row => row.timestamp);
table.bootstrapTable("hideLoading");
},
onFailure,
);
}
function packagesRemove(packages) {
packages = packages ?? getSelection();
const onSuccess = update => `Packages ${update} have been removed`;
@@ -88,130 +127,22 @@
function reload() {
table.bootstrapTable("showLoading");
const badgeClass = status => {
if (status === "pending") return "btn-outline-warning";
if (status === "building") return "btn-outline-warning";
if (status === "failed") return "btn-outline-danger";
if (status === "success") return "btn-outline-success";
return "btn-outline-secondary";
const onFailure = error => {
if ((error.status === 401) || (error.status === 403)) {
// authorization error
const text = "In order to see statuses you must login first.";
table.find("tr.unauthorized").remove();
table.find("tbody").append(`<tr class="unauthorized"><td colspan="100%">${safe(text)}</td></tr>`);
table.bootstrapTable("hideLoading");
} else {
// other errors
const message = details => `Could not load list of packages: ${details}`;
showFailure("Load failure", message, error);
}
};
makeRequest(
"/api/v1/packages",
{
query: {
architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
},
data => {
const payload = data.map(description => {
const package_base = description.package.base;
const web_url = description.package.remote.web_url;
return {
id: package_base,
base: web_url ? safeLink(web_url, package_base, package_base).outerHTML : safe(package_base),
version: safe(description.package.version),
packager: description.package.packager ? safe(description.package.packager) : "",
packages: listToTable(Object.keys(description.package.packages)),
groups: listToTable(extractListProperties(description.package, "groups")),
licenses: listToTable(extractListProperties(description.package, "licenses")),
timestamp: new Date(1000 * description.status.timestamp).toISOStringShort(),
status: description.status.status,
};
});
table.bootstrapTable("load", payload);
table.bootstrapTable("uncheckAll");
table.bootstrapTable("hideLoading");
},
error => {
if ((error.status === 401) || (error.status === 403)) {
// authorization error
const text = "In order to see statuses you must login first.";
table.find("tr.unauthorized").remove();
table.find("tbody").append(`<tr class="unauthorized"><td colspan="100%">${safe(text)}</td></tr>`);
table.bootstrapTable("hideLoading");
} else {
// other errors
const message = details => `Could not load list of packages: ${details}`;
showFailure("Load failure", message, error);
}
},
);
makeRequest(
"/api/v1/status",
{
query: {
architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
},
data => {
versionBadge.innerHTML = `<i class="bi bi-github"></i> ahriman ${safe(data.version)}`;
dashboardButton.classList.remove(...dashboardButton.classList);
dashboardButton.classList.add("btn");
dashboardButton.classList.add(badgeClass(data.status.status));
dashboardModalHeader.classList.remove(...dashboardModalHeader.classList);
dashboardModalHeader.classList.add("modal-header");
headerClass(data.status.status).forEach(clz => dashboardModalHeader.classList.add(clz));
dashboardName.textContent = data.repository;
dashboardArchitecture.textContent = data.architecture;
dashboardStatus.textContent = data.status.status;
dashboardStatusTimestamp.textContent = new Date(1000 * data.status.timestamp).toISOStringShort();
if (dashboardPackagesStatusesChart) {
const labels = [
"unknown",
"pending",
"building",
"failed",
"success",
];
dashboardPackagesStatusesChart.config.data = {
labels: labels,
datasets: [{
label: "packages in status",
data: labels.map(label => data.packages[label]),
backgroundColor: [
"rgb(55, 58, 60)",
"rgb(255, 117, 24)",
"rgb(255, 117, 24)",
"rgb(255, 0, 57)",
"rgb(63, 182, 24)", // copy-paste from current style
],
}],
};
dashboardPackagesStatusesChart.update();
}
if (dashboardPackagesCountChart) {
dashboardPackagesCountChart.config.data = {
labels: ["packages"],
datasets: [
{
label: "archives",
data: [data.stats.packages],
},
{
label: "bases",
data: [data.stats.bases],
},
],
};
dashboardPackagesCountChart.update();
}
dashboardCanvas.hidden = data.status.total > 0;
},
);
packagesLoad(onFailure);
statusLoad();
}
function selectRepository() {
@@ -230,7 +161,24 @@
return {classes: cellClass(value)};
}
function toggleTableAutoReload(interval) {
clearInterval(tableAutoReloadTask);
tableAutoReloadTask = toggleAutoReload(tableAutoReloadButton, interval, tableAutoReloadInput, _ => {
if (!hasActiveModal() &&
!hasActiveDropdown()) {
packagesLoad();
statusLoad();
}
});
}
ready(_ => {
const onCheckFunction = function () {
if (packageRemoveButton) {
packageRemoveButton.disabled = !getSelection().length;
}
};
document.querySelectorAll("#repositories a").forEach(element => {
element.onclick = _ => {
repository = {
@@ -245,49 +193,55 @@
};
});
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", _ => {
if (packageRemoveButton) {
packageRemoveButton.disabled = !table.bootstrapTable("getSelections").length;
}
});
table.on("click-row.bs.table", (self, data, row, cell) => {
if (0 === cell || "base" === cell) {
const method = data[0] === true ? "uncheckBy" : "checkBy"; // fck javascript
table.bootstrapTable(method, {field: "id", values: [data.id]});
} else showPackageInfo(data.id);
});
table.on("created-controls.bs.table", _ => {
new easepick.create({
element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
css: [
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
],
grid: 2,
calendars: 2,
autoApply: false,
locale: {
cancel: "Clear",
},
RangePlugin: {
tooltip: false,
},
plugins: [
"RangePlugin",
],
setup: picker => {
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
// replace "Cancel" behaviour to "Clear"
picker.onClickCancelButton = element => {
if (picker.isCancelButton(element)) {
picker.clear();
picker.hide();
table.bootstrapTable("triggerSearch");
}
};
},
});
table.bootstrapTable({
onCheck: onCheckFunction,
onCheckAll: onCheckFunction,
onClickRow: (data, row, cell) => {
if (0 === cell || "base" === cell) {
const method = data[0] === true ? "uncheckBy" : "checkBy"; // fck javascript
table.bootstrapTable(method, {field: "id", values: [data.id]});
} else showPackageInfo(data.id);
},
onCreatedControls: _ => {
new easepick.create({
element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
css: [
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
],
grid: 2,
calendars: 2,
autoApply: false,
locale: {
cancel: "Clear",
},
RangePlugin: {
tooltip: false,
},
plugins: [
"RangePlugin",
],
setup: picker => {
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
// replace "Cancel" behaviour to "Clear"
picker.onClickCancelButton = element => {
if (picker.isCancelButton(element)) {
picker.clear();
picker.hide();
table.bootstrapTable("triggerSearch");
}
};
},
});
},
onUncheck: onCheckFunction,
onUncheckAll: onCheckFunction,
});
restoreAutoReloadSettings(tableAutoReloadButton, tableAutoReloadInput);
selectRepository();
{% if autorefresh_intervals %}
toggleTableAutoReload();
{% endif %}
});
</script>

View File

@@ -53,8 +53,7 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
data-show-search-clear-button="true"
data-sortable="true"
data-sort-name="base"
data-sort-order="asc"
data-toggle="table">
data-sort-order="asc">
<thead class="table-primary">
<tr>
<th data-sortable="true" data-switchable="false" data-field="name" data-filter-control="input" data-filter-control-placeholder="(any package)">package</th>
@@ -128,36 +127,38 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
}
ready(_ => {
table.on("created-controls.bs.table", _ => {
new easepick.create({
element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
css: [
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
],
grid: 2,
calendars: 2,
autoApply: false,
locale: {
cancel: "Clear",
},
RangePlugin: {
tooltip: false,
},
plugins: [
"RangePlugin",
],
setup: picker => {
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
// replace "Cancel" behaviour to "Clear"
picker.onClickCancelButton = element => {
if (picker.isCancelButton(element)) {
picker.clear();
picker.hide();
table.bootstrapTable("triggerSearch");
}
};
},
});
table.bootstrapTable({
onCreatedControls: _ => {
new easepick.create({
element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
css: [
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
],
grid: 2,
calendars: 2,
autoApply: false,
locale: {
cancel: "Clear",
},
RangePlugin: {
tooltip: false,
},
plugins: [
"RangePlugin",
],
setup: picker => {
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
// replace "Cancel" behaviour to "Clear"
picker.onClickCancelButton = element => {
if (picker.isCancelButton(element)) {
picker.clear();
picker.hide();
table.bootstrapTable("triggerSearch");
}
};
},
});
},
});
});
</script>

View File

@@ -1,23 +1,24 @@
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/js-md5@0.8.3/src/md5.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.30.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.33.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/cookie/bootstrap-table-cookie.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.10.0/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script>
async function copyToClipboard(text, button) {
@@ -58,6 +59,20 @@
return value.includes(dataList[index].toLowerCase());
}
function hasActiveSelection() {
return !document.getSelection().isCollapsed; // not sure if it is a valid way, but I guess so
}
function hasActiveDropdown() {
return Array.from(document.querySelectorAll(".dropdown-menu"))
.some(el => el.classList.contains("show"));
}
function hasActiveModal() {
return Array.from(document.querySelectorAll(".modal"))
.some(el => el.classList.contains("show"));
}
function headerClass(status) {
if (status === "pending") return ["bg-warning"];
if (status === "building") return ["bg-warning"];
@@ -106,6 +121,12 @@
.catch(error => onFailure && onFailure(error));
}
function readOptional(extractor, callback) {
for (let value = extractor(); !!value; value = null) {
callback(value);
}
}
function ready(fn) {
if (document.readyState === "complete" || document.readyState === "interactive") {
setTimeout(fn, 1);
@@ -114,6 +135,11 @@
}
}
function restoreAutoReloadSettings(toggle, intervalSelector) {
readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-enabled`), value => toggle.checked = value === "true");
readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-interval`), value => toggleActiveElement(intervalSelector, "interval", value));
}
function safe(string) {
return String(string)
.replace(/&/g, "&amp;")
@@ -133,7 +159,89 @@
return element;
}
Date.prototype.toISOStringShort = function() {
function toggleActiveElement(selector, dataType, value) {
const targetElement = selector.querySelector(`a[data-${dataType}="${value}"]`);
if (targetElement?.classList?.contains("active")) {
return; // element is already active, skip processing
}
Array.from(selector.children).forEach(il => {
Array.from(il.children).forEach(el => el.classList.remove("active"));
});
targetElement?.classList?.add("active");
}
function toggleAutoReload(toggle, interval, intervalSelector, callback) {
if (interval) {
toggle.checked = true; // toggle reload
} else {
interval = intervalSelector.querySelector(".active")?.dataset?.interval; // find active element
}
let intervalId = null;
if (interval) {
if (toggle.checked) {
// refresh UI
toggleActiveElement(intervalSelector, "interval", interval);
// finally create timer task
intervalId = setInterval(callback, interval);
}
} else {
toggle.checked = false; // no active interval found, disable toggle
}
localStorage.setItem(`ahriman-${toggle.id}-refresh-enabled`, toggle.checked);
localStorage.setItem(`ahriman-${toggle.id}-refresh-interval`, interval);
return intervalId;
}
function updateTable(table, rows, rowChangedKey) {
// instead of using load method here, we just update rows manually to avoid table reinitialization
const currentData = table.bootstrapTable("getData").reduce((accumulator, row) => {
accumulator[row.id] = {state: row["0"], key: rowChangedKey(row)};
return accumulator;
}, {});
// insert or update rows, skipping ones whose status hasn't changed
rows.forEach(row => {
if (Object.hasOwn(currentData, row.id)) {
if (rowChangedKey(row) === currentData[row.id].key) {
return;
}
row["0"] = currentData[row.id].state; // copy checkbox state
table.bootstrapTable("updateByUniqueId", {
id: row.id,
row: row,
replace: true,
});
} else {
table.bootstrapTable("insertRow", {index: 0, row: row});
}
});
// remove old rows
const newData = rows.map(value => value.id);
Object.keys(currentData).forEach(id => {
if (!newData.includes(id)) {
table.bootstrapTable("removeByUniqueId", id);
}
});
}
Array.prototype.equals = function (right, comparator) {
let index = this.length;
if (index !== right.length) {
return false;
}
while (index--) {
if (!comparator(this[index], right[index])) {
return false;
}
}
return true;
}
Date.prototype.toISOStringShort = function () {
const pad = number => String(number).padStart(2, "0");
return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())} ${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`;
}

View File

@@ -1,15 +1,15 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/bootstrap-table.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/bootstrap-table.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/filter-control/bootstrap-table-filter-control.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/filter-control/bootstrap-table-filter-control.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.3/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.7/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.10.0/build/styles/github.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/github.min.css" crossorigin="anonymous" type="text/css">
<style>
.pre-scrollable {

View File

@@ -1,6 +1,6 @@
# AUTOMATICALLY GENERATED by `shtab`
_shtab_ahriman_subparsers=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web')
_shtab_ahriman_subparsers=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web' 'web-reload')
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '-V' '--version' '--wait-timeout')
_shtab_ahriman_add_option_strings=('-h' '--help' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable')
@@ -78,10 +78,11 @@ _shtab_ahriman_user_list_option_strings=('-h' '--help' '-e' '--exit-code' '-R' '
_shtab_ahriman_user_remove_option_strings=('-h' '--help')
_shtab_ahriman_version_option_strings=('-h' '--help')
_shtab_ahriman_web_option_strings=('-h' '--help')
_shtab_ahriman_web_reload_option_strings=('-h' '--help')
_shtab_ahriman_pos_0_choices=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web')
_shtab_ahriman_pos_0_choices=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web' 'web-reload')
_shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald')
_shtab_ahriman_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
_shtab_ahriman_add___source_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
@@ -572,6 +573,8 @@ _shtab_ahriman_version__h_nargs=0
_shtab_ahriman_version___help_nargs=0
_shtab_ahriman_web__h_nargs=0
_shtab_ahriman_web___help_nargs=0
_shtab_ahriman_web_reload__h_nargs=0
_shtab_ahriman_web_reload___help_nargs=0
# $1=COMP_WORDS[1]

View File

@@ -1,9 +1,9 @@
.TH AHRIMAN "1" "2026\-01\-25" "ahriman 2.19.3" "ArcH linux ReposItory MANager"
.TH AHRIMAN "1" "2026\-02\-20" "ahriman 2.20.0rc3" "ArcH linux ReposItory MANager"
.SH NAME
ahriman \- ArcH linux ReposItory MANager
.SH SYNOPSIS
.B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {add,aur-search,check,clean,config,config-validate,copy,daemon,help,help-commands-unsafe,help-updates,help-version,init,key-import,package-add,package-changes,package-changes-remove,package-copy,package-remove,package-status,package-status-remove,package-status-update,package-update,patch-add,patch-list,patch-remove,patch-set-add,rebuild,remove,remove-unknown,repo-backup,repo-check,repo-clean,repo-config,repo-config-validate,repo-create-keyring,repo-create-mirrorlist,repo-daemon,repo-init,repo-rebuild,repo-remove-unknown,repo-report,repo-restore,repo-setup,repo-sign,repo-statistics,repo-status-update,repo-sync,repo-tree,repo-triggers,repo-update,report,run,search,service-clean,service-config,service-config-validate,service-key-import,service-repositories,service-run,service-setup,service-shell,service-tree-migrate,setup,shell,sign,status,status-update,sync,update,user-add,user-list,user-remove,version,web} ...
.B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {add,aur-search,check,clean,config,config-validate,copy,daemon,help,help-commands-unsafe,help-updates,help-version,init,key-import,package-add,package-changes,package-changes-remove,package-copy,package-remove,package-status,package-status-remove,package-status-update,package-update,patch-add,patch-list,patch-remove,patch-set-add,rebuild,remove,remove-unknown,repo-backup,repo-check,repo-clean,repo-config,repo-config-validate,repo-create-keyring,repo-create-mirrorlist,repo-daemon,repo-init,repo-rebuild,repo-remove-unknown,repo-report,repo-restore,repo-setup,repo-sign,repo-statistics,repo-status-update,repo-sync,repo-tree,repo-triggers,repo-update,report,run,search,service-clean,service-config,service-config-validate,service-key-import,service-repositories,service-run,service-setup,service-shell,service-tree-migrate,setup,shell,sign,status,status-update,sync,update,user-add,user-list,user-remove,version,web,web-reload} ...
.SH DESCRIPTION
ArcH linux ReposItory MANager
@@ -193,11 +193,14 @@ remove user
.TP
\fBahriman\fR \fI\,web\/\fR
web server
.TP
\fBahriman\fR \fI\,web\-reload\/\fR
reload configuration
.SH COMMAND \fI\,'ahriman aur\-search'\/\fR
usage: ahriman aur\-search [\-h] [\-e] [\-\-info | \-\-no\-info]
[\-\-sort\-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}]
search [search ...]
usage: ahriman aur\-search [\-h] [\-e] [\-\-info | \-\-no\-info]
[\-\-sort\-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}]
search [search ...]
search for package in AUR using API
@@ -220,7 +223,7 @@ sort field by this field. In case if two packages have the same value of the spe
by name
.SH COMMAND \fI\,'ahriman help'\/\fR
usage: ahriman help [\-h] [subcommand]
usage: ahriman help [\-h] [subcommand]
show help message for application or command and exit
@@ -229,7 +232,7 @@ show help message for application or command and exit
show help message for specific command
.SH COMMAND \fI\,'ahriman help\-commands\-unsafe'\/\fR
usage: ahriman help\-commands\-unsafe [\-h] [subcommand ...]
usage: ahriman help\-commands\-unsafe [\-h] [subcommand ...]
list unsafe commands as defined in default args
@@ -239,7 +242,7 @@ instead of showing commands, just test command line for unsafe subcommand and re
otherwise
.SH COMMAND \fI\,'ahriman help\-updates'\/\fR
usage: ahriman help\-updates [\-h] [\-e]
usage: ahriman help\-updates [\-h] [\-e]
request AUR for current version and compare with current service version
@@ -249,15 +252,15 @@ request AUR for current version and compare with current service version
return non\-zero exit code if updates available
.SH COMMAND \fI\,'ahriman help\-version'\/\fR
usage: ahriman help\-version [\-h]
usage: ahriman help\-version [\-h]
print application and its dependencies versions
.SH COMMAND \fI\,'ahriman package\-add'\/\fR
usage: ahriman package\-add [\-h] [\-\-changes | \-\-no\-changes] [\-\-dependencies | \-\-no\-dependencies] [\-e]
[\-\-increment | \-\-no\-increment] [\-n] [\-y]
[\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME] [\-v VARIABLE]
package [package ...]
usage: ahriman package\-add [\-h] [\-\-changes | \-\-no\-changes] [\-\-dependencies | \-\-no\-dependencies] [\-e]
[\-\-increment | \-\-no\-increment] [\-n] [\-y]
[\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME] [\-v VARIABLE]
package [package ...]
add existing or new package to the build queue
@@ -303,7 +306,7 @@ build as user
apply specified makepkg variables to the next build
.SH COMMAND \fI\,'ahriman package\-changes'\/\fR
usage: ahriman package\-changes [\-h] [\-e] package
usage: ahriman package\-changes [\-h] [\-e] package
retrieve package changes stored in database
@@ -317,7 +320,7 @@ package base
return non\-zero exit status if result is empty
.SH COMMAND \fI\,'ahriman package\-changes\-remove'\/\fR
usage: ahriman package\-changes\-remove [\-h] package
usage: ahriman package\-changes\-remove [\-h] package
remove the package changes stored remotely
@@ -326,7 +329,7 @@ remove the package changes stored remotely
package base
.SH COMMAND \fI\,'ahriman package\-copy'\/\fR
usage: ahriman package\-copy [\-h] [\-e] [\-\-remove] source package [package ...]
usage: ahriman package\-copy [\-h] [\-e] [\-\-remove] source package [package ...]
copy package and its metadata from another repository
@@ -348,7 +351,7 @@ return non\-zero exit status if result is empty
remove package from the source repository after
.SH COMMAND \fI\,'ahriman package\-remove'\/\fR
usage: ahriman package\-remove [\-h] package [package ...]
usage: ahriman package\-remove [\-h] package [package ...]
remove package from the repository
@@ -357,8 +360,8 @@ remove package from the repository
package name or base
.SH COMMAND \fI\,'ahriman package\-status'\/\fR
usage: ahriman package\-status [\-h] [\-\-ahriman] [\-e] [\-\-info | \-\-no\-info] [\-s {unknown,pending,building,failed,success}]
[package ...]
usage: ahriman package\-status [\-h] [\-\-ahriman] [\-e] [\-\-info | \-\-no\-info] [\-s {unknown,pending,building,failed,success}]
[package ...]
request status of the package
@@ -384,7 +387,7 @@ show additional package information
filter packages by status
.SH COMMAND \fI\,'ahriman package\-status\-remove'\/\fR
usage: ahriman package\-status\-remove [\-h] package [package ...]
usage: ahriman package\-status\-remove [\-h] package [package ...]
remove the package from the status page
@@ -393,7 +396,7 @@ remove the package from the status page
remove specified packages from status page
.SH COMMAND \fI\,'ahriman package\-status\-update'\/\fR
usage: ahriman package\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}] [package ...]
usage: ahriman package\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}] [package ...]
update package status on the status page
@@ -407,7 +410,7 @@ set status for specified packages. If no packages supplied, service status will
new package build status
.SH COMMAND \fI\,'ahriman patch\-add'\/\fR
usage: ahriman patch\-add [\-h] package variable [patch]
usage: ahriman patch\-add [\-h] package variable [patch]
create or update patched PKGBUILD function or variable
@@ -424,7 +427,7 @@ PKGBUILD variable or function name. If variable is a function, it must end with
path to file which contains function or variable value. If not set, the value will be read from stdin
.SH COMMAND \fI\,'ahriman patch\-list'\/\fR
usage: ahriman patch\-list [\-h] [\-e] [\-v VARIABLE] package
usage: ahriman patch\-list [\-h] [\-e] [\-v VARIABLE] package
list available patches for the package
@@ -442,7 +445,7 @@ return non\-zero exit status if result is empty
if set, show only patches for specified PKGBUILD variables
.SH COMMAND \fI\,'ahriman patch\-remove'\/\fR
usage: ahriman patch\-remove [\-h] [\-v VARIABLE] package
usage: ahriman patch\-remove [\-h] [\-v VARIABLE] package
remove patches for the package
@@ -457,7 +460,7 @@ should be used for single\-function patches in case if you wold like to remove o
if not set, it will remove all patches related to the package
.SH COMMAND \fI\,'ahriman patch\-set\-add'\/\fR
usage: ahriman patch\-set\-add [\-h] [\-t TRACK] package
usage: ahriman patch\-set\-add [\-h] [\-t TRACK] package
create or update source patches
@@ -471,7 +474,7 @@ path to directory with changed files for patch addition/update
files which has to be tracked
.SH COMMAND \fI\,'ahriman repo\-backup'\/\fR
usage: ahriman repo\-backup [\-h] path
usage: ahriman repo\-backup [\-h] path
backup repository settings and database
@@ -480,9 +483,9 @@ backup repository settings and database
path of the output archive
.SH COMMAND \fI\,'ahriman repo\-check'\/\fR
usage: ahriman repo\-check [\-h] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files] [\-e] [\-\-vcs | \-\-no\-vcs]
[\-y]
[package ...]
usage: ahriman repo\-check [\-h] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files] [\-e] [\-\-vcs | \-\-no\-vcs]
[\-y]
[package ...]
check for packages updates. Same as repo\-update \-\-dry\-run \-\-no\-manual
@@ -512,20 +515,20 @@ fetch actual version of VCS packages
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman repo\-create\-keyring'\/\fR
usage: ahriman repo\-create\-keyring [\-h]
usage: ahriman repo\-create\-keyring [\-h]
create package which contains list of trusted keys as set by configuration. Note, that this action will only create package, the package itself has to be built manually
.SH COMMAND \fI\,'ahriman repo\-create\-mirrorlist'\/\fR
usage: ahriman repo\-create\-mirrorlist [\-h]
usage: ahriman repo\-create\-mirrorlist [\-h]
create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually
.SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR
usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes]
[\-\-check\-files | \-\-no\-check\-files] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run]
[\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual]
[\-\-partitions | \-\-no\-partitions] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes]
[\-\-check\-files | \-\-no\-check\-files] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run]
[\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual]
[\-\-partitions | \-\-no\-partitions] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
start process which periodically will run update process
@@ -583,8 +586,8 @@ fetch actual version of VCS packages
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman repo\-rebuild'\/\fR
usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-\-increment | \-\-no\-increment]
[\-e] [\-s {unknown,pending,building,failed,success}] [\-u USERNAME]
usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-\-increment | \-\-no\-increment]
[\-e] [\-s {unknown,pending,building,failed,success}] [\-u USERNAME]
force rebuild whole repository
@@ -620,7 +623,7 @@ filter packages by status. Requires \-\-from\-database to be set
build as user
.SH COMMAND \fI\,'ahriman repo\-remove\-unknown'\/\fR
usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run]
usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run]
remove packages which are missing in AUR and do not have local PKGBUILDs
@@ -630,12 +633,12 @@ remove packages which are missing in AUR and do not have local PKGBUILDs
just perform check for packages without removal
.SH COMMAND \fI\,'ahriman repo\-report'\/\fR
usage: ahriman repo\-report [\-h]
usage: ahriman repo\-report [\-h]
generate repository report according to current settings
.SH COMMAND \fI\,'ahriman repo\-restore'\/\fR
usage: ahriman repo\-restore [\-h] [\-o OUTPUT] path
usage: ahriman repo\-restore [\-h] [\-o OUTPUT] path
restore settings and database
@@ -649,7 +652,7 @@ path of the input archive
root path of the extracted files
.SH COMMAND \fI\,'ahriman repo\-sign'\/\fR
usage: ahriman repo\-sign [\-h] [package ...]
usage: ahriman repo\-sign [\-h] [package ...]
(re\-)sign packages and repository database according to current settings
@@ -658,10 +661,10 @@ usage: ahriman repo\-sign [\-h] [package ...]
sign only specified packages
.SH COMMAND \fI\,'ahriman repo\-statistics'\/\fR
usage: ahriman repo\-statistics [\-h] [\-\-chart CHART]
[\-e {package\-outdated,package\-removed,package\-update\-failed,package\-updated}]
[\-\-from\-date FROM_DATE] [\-\-limit LIMIT] [\-\-offset OFFSET] [\-\-to\-date TO_DATE]
[package]
usage: ahriman repo\-statistics [\-h] [\-\-chart CHART]
[\-e {package\-outdated,package\-removed,package\-update\-failed,package\-updated}]
[\-\-from\-date FROM_DATE] [\-\-limit LIMIT] [\-\-offset OFFSET] [\-\-to\-date TO_DATE]
[package]
fetch repository statistics
@@ -695,7 +698,7 @@ skip specified amount of events
only fetch events which are older than the date
.SH COMMAND \fI\,'ahriman repo\-status\-update'\/\fR
usage: ahriman repo\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}]
usage: ahriman repo\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}]
update repository status on the status page
@@ -705,12 +708,12 @@ update repository status on the status page
new status
.SH COMMAND \fI\,'ahriman repo\-sync'\/\fR
usage: ahriman repo\-sync [\-h]
usage: ahriman repo\-sync [\-h]
sync repository files to remote server according to current settings
.SH COMMAND \fI\,'ahriman repo\-tree'\/\fR
usage: ahriman repo\-tree [\-h] [\-p PARTITIONS]
usage: ahriman repo\-tree [\-h] [\-p PARTITIONS]
dump repository tree based on packages dependencies
@@ -720,7 +723,7 @@ dump repository tree based on packages dependencies
also divide packages by independent partitions
.SH COMMAND \fI\,'ahriman repo\-triggers'\/\fR
usage: ahriman repo\-triggers [\-h] [trigger ...]
usage: ahriman repo\-triggers [\-h] [trigger ...]
run triggers on empty build result as configured by settings
@@ -729,10 +732,10 @@ run triggers on empty build result as configured by settings
instead of running all triggers as set by configuration, just process specified ones in order of mention
.SH COMMAND \fI\,'ahriman repo\-update'\/\fR
usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files]
[\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e] [\-\-increment | \-\-no\-increment]
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
[package ...]
usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files]
[\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e] [\-\-increment | \-\-no\-increment]
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
[package ...]
check for packages updates and run build process if requested
@@ -790,8 +793,8 @@ fetch actual version of VCS packages
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman service\-clean'\/\fR
usage: ahriman service\-clean [\-h] [\-\-cache | \-\-no\-cache] [\-\-chroot | \-\-no\-chroot] [\-\-manual | \-\-no\-manual]
[\-\-packages | \-\-no\-packages] [\-\-pacman | \-\-no\-pacman]
usage: ahriman service\-clean [\-h] [\-\-cache | \-\-no\-cache] [\-\-chroot | \-\-no\-chroot] [\-\-manual | \-\-no\-manual]
[\-\-packages | \-\-no\-packages] [\-\-pacman | \-\-no\-pacman]
remove local caches
@@ -817,7 +820,7 @@ clear directory with built packages
clear directory with pacman local database cache
.SH COMMAND \fI\,'ahriman service\-config'\/\fR
usage: ahriman service\-config [\-h] [\-\-info | \-\-no\-info] [\-\-secure | \-\-no\-secure] [section] [key]
usage: ahriman service\-config [\-h] [\-\-info | \-\-no\-info] [\-\-secure | \-\-no\-secure] [section] [key]
dump configuration for the specified architecture
@@ -839,7 +842,7 @@ show additional information, e.g. configuration files
hide passwords and secrets from output
.SH COMMAND \fI\,'ahriman service\-config\-validate'\/\fR
usage: ahriman service\-config\-validate [\-h] [\-e]
usage: ahriman service\-config\-validate [\-h] [\-e]
validate configuration and print found errors
@@ -849,7 +852,7 @@ validate configuration and print found errors
return non\-zero exit status if configuration is invalid
.SH COMMAND \fI\,'ahriman service\-key\-import'\/\fR
usage: ahriman service\-key\-import [\-h] [\-\-key\-server KEY_SERVER] key
usage: ahriman service\-key\-import [\-h] [\-\-key\-server KEY_SERVER] key
import PGP key from public sources to the repository user
@@ -863,7 +866,7 @@ PGP key to import from public server
key server for key import
.SH COMMAND \fI\,'ahriman service\-repositories'\/\fR
usage: ahriman service\-repositories [\-h] [\-\-id\-only | \-\-no\-id\-only]
usage: ahriman service\-repositories [\-h] [\-\-id\-only | \-\-no\-id\-only]
list all available repositories
@@ -873,7 +876,7 @@ list all available repositories
show machine readable identifier instead
.SH COMMAND \fI\,'ahriman service\-run'\/\fR
usage: ahriman service\-run [\-h] command [command ...]
usage: ahriman service\-run [\-h] command [command ...]
run multiple commands on success run of the previous command
@@ -882,11 +885,11 @@ run multiple commands on success run of the previous command
command to be run (quoted) without ``ahriman``
.SH COMMAND \fI\,'ahriman service\-setup'\/\fR
usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-from\-configuration FROM_CONFIGURATION]
[\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs]
[\-\-mirror MIRROR] [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER [\-\-server SERVER]
[\-\-sign\-key SIGN_KEY] [\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT]
[\-\-web\-unix\-socket WEB_UNIX_SOCKET]
usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-from\-configuration FROM_CONFIGURATION]
[\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs]
[\-\-mirror MIRROR] [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER [\-\-server SERVER]
[\-\-sign\-key SIGN_KEY] [\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT]
[\-\-web\-unix\-socket WEB_UNIX_SOCKET]
create initial service configuration, requires root
@@ -940,7 +943,7 @@ port of the web service
path to unix socket used for interprocess communications
.SH COMMAND \fI\,'ahriman service\-shell'\/\fR
usage: ahriman service\-shell [\-h] [\-o OUTPUT] [code]
usage: ahriman service\-shell [\-h] [\-o OUTPUT] [code]
drop into python shell
@@ -954,13 +957,13 @@ instead of dropping into shell, just execute the specified code
output commands and result to the file
.SH COMMAND \fI\,'ahriman service\-tree\-migrate'\/\fR
usage: ahriman service\-tree\-migrate [\-h]
usage: ahriman service\-tree\-migrate [\-h]
migrate repository tree between versions
.SH COMMAND \fI\,'ahriman user\-add'\/\fR
usage: ahriman user\-add [\-h] [\-\-key KEY] [\-\-packager PACKAGER] [\-p PASSWORD] [\-R {unauthorized,read,reporter,full}]
username
usage: ahriman user\-add [\-h] [\-\-key KEY] [\-\-packager PACKAGER] [\-p PASSWORD] [\-R {unauthorized,read,reporter,full}]
username
update user for web services with the given password and role. In case if password was not entered it will be asked interactively
@@ -987,7 +990,7 @@ authorization type.
user access level
.SH COMMAND \fI\,'ahriman user\-list'\/\fR
usage: ahriman user\-list [\-h] [\-e] [\-R {unauthorized,read,reporter,full}] [username]
usage: ahriman user\-list [\-h] [\-e] [\-R {unauthorized,read,reporter,full}] [username]
list users from the user mapping and their roles
@@ -1005,7 +1008,7 @@ return non\-zero exit status if result is empty
filter users by role
.SH COMMAND \fI\,'ahriman user\-remove'\/\fR
usage: ahriman user\-remove [\-h] username
usage: ahriman user\-remove [\-h] username
remove user from the user mapping and update the configuration
@@ -1014,10 +1017,15 @@ remove user from the user mapping and update the configuration
username for web service
.SH COMMAND \fI\,'ahriman web'\/\fR
usage: ahriman web [\-h]
usage: ahriman web [\-h]
start web server
.SH COMMAND \fI\,'ahriman web\-reload'\/\fR
usage: ahriman web\-reload [\-h]
reload web server configuration
.SH COMMENTS
Quick setup command (replace repository name, architecture and packager as needed):

View File

@@ -80,6 +80,7 @@ _shtab_ahriman_commands() {
"user-remove:remove user from the user mapping and update the configuration"
"version:print application and its dependencies versions"
"web:start web server"
"web-reload:reload web server configuration"
)
_describe 'ahriman commands' _commands
}
@@ -911,6 +912,13 @@ _shtab_ahriman_web_options=(
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_web_defaults_added=0
_shtab_ahriman_web_reload_options=(
"(- : *)"{-h,--help}"[show this help message and exit]"
)
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_web_reload_defaults_added=0
_shtab_ahriman() {
local context state line curcontext="$curcontext" one_or_more='(*)' remainder='(-)*' default='*::: :->ahriman'
@@ -1005,6 +1013,7 @@ _shtab_ahriman() {
user-remove) _arguments -C -s $_shtab_ahriman_user_remove_options ;;
version) _arguments -C -s $_shtab_ahriman_version_options ;;
web) _arguments -C -s $_shtab_ahriman_web_options ;;
web-reload) _arguments -C -s $_shtab_ahriman_web_reload_options ;;
esac
esac
}

View File

@@ -18,6 +18,7 @@ authors = [
dependencies = [
"bcrypt",
"filelock",
"inflection",
"pyelftools",
"requests",

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -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.19.3"
__version__ = "2.20.0rc3"

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -22,6 +22,7 @@ from collections.abc import Iterable
from ahriman.application.application.application_properties import ApplicationProperties
from ahriman.application.application.workers import Updater
from ahriman.core.build_tools.sources import Sources
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.package import Package
from ahriman.models.packagers import Packagers
from ahriman.models.result import Result
@@ -116,7 +117,7 @@ class ApplicationRepository(ApplicationProperties):
for single in probe.packages:
try:
_ = Package.from_aur(single, None)
except Exception:
except UnknownPackageError:
packages.append(single)
return packages

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -81,11 +81,13 @@ class Backup(Handler):
Returns:
set[Path]: map of the filesystem paths
"""
paths = set(configuration.include.glob("*.ini"))
# configuration files
root, _ = configuration.check_loaded()
paths.add(root) # the configuration itself
paths.add(SQLite.database_path(configuration)) # database
paths = set(configuration.includes)
paths.add(root)
# database
paths.add(SQLite.database_path(configuration))
# local caches
repository_paths = configuration.repository_paths

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -20,7 +20,7 @@
import argparse
import logging
from collections.abc import Callable, Iterable
from collections.abc import Callable
from multiprocessing import Pool
from typing import ClassVar, TypeVar
@@ -28,9 +28,9 @@ from ahriman.application.lock import Lock
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import ExitCode, MissingArchitectureError, MultipleArchitecturesError
from ahriman.core.log.log_loader import LogLoader
from ahriman.core.repository import Explorer
from ahriman.core.types import ExplicitBool
from ahriman.models.repository_id import RepositoryId
from ahriman.models.repository_paths import RepositoryPaths
# this workaround is for several things
@@ -169,11 +169,6 @@ class Handler:
Raises:
MissingArchitectureError: if no architecture set and automatic detection is not allowed or failed
"""
configuration = Configuration()
configuration.load(args.configuration)
# pylint, wtf???
root = configuration.getpath("repository", "root") # pylint: disable=assignment-from-no-return
# preparse systemd repository-id argument
# we are using unescaped values, so / is not allowed here, because it is impossible to separate if from dashes
if args.repository_id is not None:
@@ -184,27 +179,10 @@ class Handler:
if repository_parts:
args.repository = "-".join(repository_parts) # replace slash with dash
# extract repository names first
if (from_args := args.repository) is not None:
repositories: Iterable[str] = [from_args]
elif from_filesystem := RepositoryPaths.known_repositories(root):
repositories = from_filesystem
else: # try to read configuration now
repositories = [configuration.get("repository", "name")]
configuration = Configuration()
configuration.load(args.configuration)
repositories = Explorer.repositories_extract(configuration, args.repository, args.architecture)
# extract architecture names
if (architecture := args.architecture) is not None:
parsed = set(
RepositoryId(architecture, repository)
for repository in repositories
)
else: # try to read from file system
parsed = set(
RepositoryId(architecture, repository)
for repository in repositories
for architecture in RepositoryPaths.known_architectures(root, repository)
)
if not parsed:
if not repositories:
raise MissingArchitectureError(args.command)
return sorted(parsed)
return sorted(repositories)

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -0,0 +1,70 @@
#
# 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 argparse
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration
from ahriman.models.repository_id import RepositoryId
class Reload(Handler):
"""
web server reload handler
"""
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
application = Application(repository_id, configuration, report=True)
client = application.repository.reporter
client.configuration_reload()
@staticmethod
def _set_web_reload_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for web reload subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("web-reload", help="reload configuration",
description="reload web server configuration",
epilog="This method forces the web server to reload its configuration. "
"Note, however, that this method does not apply all configuration changes "
"(like ports, authentication, etc)")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return parser
arguments = [_set_web_reload_parser]

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -47,7 +47,7 @@ class Restore(Handler):
report(bool): force enable or disable reporting
"""
with tarfile.open(args.path) as archive:
archive.extractall(path=args.output) # nosec
archive.extractall(path=args.output, filter="data")
@staticmethod
def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -28,6 +28,7 @@ from ahriman.core.alpm.remote import AUR, Official
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import OptionError
from ahriman.core.formatters import AurPrinter
from ahriman.core.types import Comparable
from ahriman.models.aur_package import AURPackage
from ahriman.models.repository_id import RepositoryId
@@ -115,7 +116,7 @@ class Search(Handler):
raise OptionError(sort_by)
# always sort by package name at the last
# well technically it is not a string, but we can deal with it
comparator: Callable[[AURPackage], tuple[str, str]] =\
comparator: Callable[[AURPackage], Comparable] = \
lambda package: (getattr(package, sort_by), package.name)
return sorted(packages, key=comparator)

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -72,14 +72,16 @@ class Setup(Handler):
application = Application(repository_id, configuration, report=report)
with application.repository.paths.preserve_owner():
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
Setup.executable_create(application.repository.paths, repository_id)
repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server
Setup.configuration_create_devtools(
repository_id, args.from_configuration, args.mirror, args.multilib, repository_server)
Setup.configuration_create_sudo(application.repository.paths, repository_id)
# basically we create configuration here as root, but it is ok, because those files are only used for reading
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
Setup.executable_create(application.repository.paths, repository_id)
repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server
Setup.configuration_create_devtools(
repository_id, args.from_configuration, args.mirror, args.multilib, repository_server)
Setup.configuration_create_sudo(application.repository.paths, repository_id)
# finish initialization
with application.repository.paths.preserve_owner():
application.repository.repo.init()
# lazy database sync
application.repository.pacman.handle # pylint: disable=pointless-statement

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -25,6 +25,7 @@ from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration
from ahriman.core.formatters import PackagePrinter, StatusPrinter
from ahriman.core.types import Comparable
from ahriman.core.utils import enum_values
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package
@@ -64,8 +65,8 @@ class Status(Handler):
Status.check_status(args.exit_code, packages)
comparator: Callable[[tuple[Package, BuildStatus]], str] = lambda item: item[0].base
filter_fn: Callable[[tuple[Package, BuildStatus]], bool] =\
comparator: Callable[[tuple[Package, BuildStatus]], Comparable] = lambda item: item[0].base
filter_fn: Callable[[tuple[Package, BuildStatus]], bool] = \
lambda item: args.status is None or item[1].status == args.status
for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
PackagePrinter(package, package_status)(verbose=args.info)

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -21,6 +21,7 @@ import argparse
from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration
from ahriman.core.utils import symlink_relative, walk
from ahriman.models.repository_id import RepositoryId
from ahriman.models.repository_paths import RepositoryPaths
@@ -49,6 +50,7 @@ class TreeMigrate(Handler):
target_tree.tree_create()
# perform migration
TreeMigrate.tree_move(current_tree, target_tree)
TreeMigrate.symlinks_fix(target_tree)
@staticmethod
def _set_service_tree_migrate_parser(root: SubParserAction) -> argparse.ArgumentParser:
@@ -66,6 +68,22 @@ class TreeMigrate(Handler):
parser.set_defaults(lock=None, quiet=True, report=False)
return parser
@staticmethod
def symlinks_fix(paths: RepositoryPaths) -> None:
"""
fix package archive symlinks
Args:
paths(RepositoryPaths): new repository paths
"""
archives = {path.name: path for path in walk(paths.archive)}
for symlink in walk(paths.repository):
if symlink.exists(): # no need to check for symlinks as we have just walked through the tree
continue
if (source_archive := archives.get(symlink.name)) is not None:
symlink.unlink()
symlink_relative(symlink, source_archive)
@staticmethod
def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None:
"""

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -21,7 +21,7 @@ import argparse
import re
import sys
from collections.abc import Generator
from collections.abc import Iterator
from importlib import metadata
from typing import ClassVar
@@ -77,7 +77,7 @@ class Versions(Handler):
return parser
@staticmethod
def package_dependencies(root: str) -> Generator[tuple[str, str], None, None]:
def package_dependencies(root: str) -> Iterator[tuple[str, str]]:
"""
extract list of ahriman package dependencies installed into system with their versions
@@ -87,7 +87,7 @@ class Versions(Handler):
Yields:
tuple[str, str]: map of installed dependency to its version
"""
def dependencies_by_key(key: str) -> Generator[str, None, None]:
def dependencies_by_key(key: str) -> Iterator[str]:
# in importlib it returns requires in the following format
# ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
try:

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -19,7 +19,7 @@
#
import argparse
from collections.abc import Generator
from collections.abc import Iterator
from pathlib import Path
from ahriman.application.handlers.handler import Handler, SubParserAction
@@ -86,7 +86,7 @@ class Web(Handler):
return parser
@staticmethod
def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Generator[str, None, None]:
def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Iterator[str]:
"""
extract list of arguments used for current command, except for command specific ones

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -158,7 +158,7 @@ class Lock(LazyLogging):
"""
check if current user is actually owner of ahriman root
"""
check_user(self.paths, unsafe=self.unsafe)
check_user(self.paths.root, unsafe=self.unsafe)
self.paths.tree_create()
def check_version(self) -> None:

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -21,7 +21,7 @@ import itertools
import shutil
import tarfile
from collections.abc import Generator, Iterable
from collections.abc import Iterable, Iterator
from functools import cached_property
from pathlib import Path
from pyalpm import DB, Handle, Package, SIG_DATABASE_OPTIONAL, SIG_PACKAGE_OPTIONAL # type: ignore[import-not-found]
@@ -130,7 +130,7 @@ class Pacman(LazyLogging):
return # database for some reason deos not exist
self.logger.info("copy pacman database %s from operating system root to ahriman's home %s", src, dst)
with self.repository_paths.preserve_owner(dst.parent):
with self.repository_paths.preserve_owner():
shutil.copy(src, dst)
def database_init(self, handle: Handle, repository: str, architecture: str) -> DB:
@@ -188,7 +188,7 @@ class Pacman(LazyLogging):
Returns:
dict[str, set[str]]: map of package name to its list of files
"""
def extract(tar: tarfile.TarFile, versions: dict[str, str]) -> Generator[tuple[str, set[str]], None, None]:
def extract(tar: tarfile.TarFile, versions: dict[str, str]) -> Iterator[tuple[str, set[str]]]:
for package_name, version in versions.items():
path = Path(f"{package_name}-{version}") / "files"
try:
@@ -223,7 +223,7 @@ class Pacman(LazyLogging):
return result
def package(self, package_name: str) -> Generator[Package, None, None]:
def package(self, package_name: str) -> Iterator[Package]:
"""
retrieve list of the packages from the repository by name
@@ -256,7 +256,7 @@ class Pacman(LazyLogging):
return result
def provided_by(self, package_name: str) -> Generator[Package, None, None]:
def provided_by(self, package_name: str) -> Iterator[Package]:
"""
search through databases and emit packages which provides the ``package_name``

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -21,7 +21,7 @@ import itertools
import re
import shlex
from collections.abc import Generator
from collections.abc import Iterator
from enum import StrEnum
from typing import IO
@@ -209,7 +209,7 @@ class PkgbuildParser(shlex.shlex):
Raises:
PkgbuildParserError: if array is not closed
"""
def extract() -> Generator[str, None, None]:
def extract() -> Iterator[str]:
while token := self.get_token():
match token:
case _ if self._is_escaped():
@@ -276,7 +276,7 @@ class PkgbuildParser(shlex.shlex):
return content
def _parse_token(self, token: str) -> Generator[PkgbuildPatch, None, None]:
def _parse_token(self, token: str) -> Iterator[PkgbuildPatch]:
"""
parse single token to the PKGBUILD field
@@ -360,7 +360,7 @@ class PkgbuildParser(shlex.shlex):
raise PkgbuildParserError("reached starting position, no valid symbols found")
def parse(self) -> Generator[PkgbuildPatch, None, None]:
def parse(self) -> Iterator[PkgbuildPatch]:
"""
parse source stream and yield parsed entries

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -44,7 +44,7 @@ class AUR(Remote):
"""
generate remote git url from the package base
Args
Args:
package_base(str): package base
repository(str): repository name
@@ -58,7 +58,7 @@ class AUR(Remote):
"""
generate remote web url from the package base
Args
Args:
package_base(str): package base
Returns:

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -46,7 +46,7 @@ class Official(Remote):
"""
generate remote git url from the package base
Args
Args:
package_base(str): package base
repository(str): repository name
@@ -60,7 +60,7 @@ class Official(Remote):
"""
generate remote web url from the package base
Args
Args:
package_base(str): package base
Returns:

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2025 ahriman team.
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@@ -94,6 +94,15 @@ class Remote(SyncHttpClient):
for package in portion
if package.name in packages or not packages
}
# simple check for duplicates. This method will remove all packages under base if there is
# a package named exactly as its base
packages = {
package.name: package
for package in packages.values()
if package.package_base not in packages or package.package_base == package.name
}
return list(packages.values())
@classmethod
@@ -101,7 +110,7 @@ class Remote(SyncHttpClient):
"""
generate remote git url from the package base
Args
Args:
package_base(str): package base
repository(str): repository name
@@ -118,7 +127,7 @@ class Remote(SyncHttpClient):
"""
generate remote web url from the package base
Args
Args:
package_base(str): package base
Returns:

Some files were not shown because too many files have changed in this diff Show More