mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-06-27 14:22:10 +00:00
Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
9c1e9ecbdc | |||
4b2f6bbee9 | |||
fd8c8a00d0 | |||
eaf1984eb3 | |||
794dddccd9 | |||
7bd7f95f76 | |||
375374c396 | |||
d1ad5ecc11 | |||
1eb4d8e47f | |||
0861548b56 | |||
e9e4172063 | |||
beb6156795 | |||
dbfb460557 | |||
f7f76c4119 | |||
88ee300b9e | |||
6f30c687c2 | |||
c023ebe165 | |||
54b99cacfd | |||
4f5166ff25 | |||
c8afcbf36a | |||
2b9880bd3c | |||
3be5cdafe8 | |||
668be41c3e | |||
3353daec6d | |||
eef4d2dd98 | |||
b15161554e | |||
bb4a0d75fc | |||
bca0df41d1 | |||
07b77be6b8 | |||
2b33510ada | |||
6d05389639 | |||
daf9841717 | |||
0d243a781a | |||
cf2e66a934 | |||
f01f35238d | |||
d30d512eb6 | |||
0437f90e5a | |||
3cab65855a | |||
ecfb615f97 | |||
243983ee64 | |||
812c03d1eb | |||
01597c531b | |||
4fec42eac8 |
11
.github/workflows/setup.sh
vendored
11
.github/workflows/setup.sh
vendored
@ -10,9 +10,9 @@ echo -e '[arcanisrepo]\nServer = https://repo.arcanis.me/$arch\nSigLevel = Never
|
||||
# refresh the image
|
||||
pacman -Syu --noconfirm
|
||||
# main dependencies
|
||||
pacman -Sy --noconfirm devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo python-systemd sudo
|
||||
pacman -Sy --noconfirm devtools git pyalpm python-cerberus python-inflection python-passlib python-pyelftools python-requests python-srcinfo python-systemd sudo
|
||||
# make dependencies
|
||||
pacman -Sy --noconfirm python-build python-flit python-installer python-tox python-wheel
|
||||
pacman -Sy --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel
|
||||
# optional dependencies
|
||||
if [[ -z $MINIMAL_INSTALL ]]; then
|
||||
# VCS support
|
||||
@ -32,10 +32,13 @@ mv dist/ahriman-*.tar.gz package/archlinux
|
||||
chmod +777 package/archlinux # because fuck you that's why
|
||||
cd package/archlinux
|
||||
sudo -u nobody -- makepkg -cf --skipchecksums --noconfirm
|
||||
sudo -u nobody -- makepkg --packagelist | pacman -U --noconfirm -
|
||||
sudo -u nobody -- makepkg --packagelist | grep -v -- -debug- | pacman -U --noconfirm -
|
||||
# create machine-id which is required by build tools
|
||||
systemd-machine-id-setup
|
||||
|
||||
# remove unused dependencies
|
||||
pacman -Qdtq | pacman -Rscn --noconfirm -
|
||||
|
||||
# initial setup command as root
|
||||
[[ -z $MINIMAL_INSTALL ]] && WEB_ARGS=("--web-port" "8080")
|
||||
ahriman -a x86_64 -r "github" service-setup --packager "ahriman bot <ahriman@example.com>" "${WEB_ARGS[@]}"
|
||||
@ -50,7 +53,7 @@ if [[ -z $MINIMAL_INSTALL ]]; then
|
||||
WEB_PID=$!
|
||||
fi
|
||||
# add the first package
|
||||
sudo -u ahriman -- ahriman package-add --now ahriman
|
||||
sudo -u ahriman -- ahriman --log-handler console package-add --now ahriman
|
||||
# check if package was actually installed
|
||||
test -n "$(find "/var/lib/ahriman/repository/github/x86_64" -name "ahriman*pkg*")"
|
||||
# run package check
|
||||
|
@ -82,6 +82,7 @@ limit-inference-results=100
|
||||
# List of plugins (as comma separated values of python module names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=pylint.extensions.docparams,
|
||||
pylint.extensions.bad_builtin,
|
||||
definition_order,
|
||||
import_order,
|
||||
|
||||
@ -131,6 +132,8 @@ attr-naming-style=snake_case
|
||||
# style.
|
||||
#attr-rgx=
|
||||
|
||||
bad-functions=print,
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma.
|
||||
bad-names=foo,
|
||||
bar,
|
||||
|
@ -3,7 +3,7 @@ version: 2
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
python: "3.12"
|
||||
|
||||
python:
|
||||
install:
|
||||
|
@ -132,7 +132,7 @@ Again, the most checks can be performed by `tox` command, though some additional
|
||||
* For any path interactions `pathlib.Path` must be used.
|
||||
* Configuration interactions must go through `ahriman.core.configuration.Configuration` class instance.
|
||||
* In case if class load requires some actions, it is recommended to create class method which can be used for class instantiating.
|
||||
* The code must follow the exception safety, unless it is explicitly asked by end user. It means that most exceptions must be handled and printed to log, no other actions must be done (e.g. raising another exception).
|
||||
* The most (expected) exceptions must be handled and printed to log, allowing service to continue work. However, fatal and (in some cases) unexpected exceptions may lead to the application termination.
|
||||
* Exceptions without parameters should be raised without parentheses, e.g.:
|
||||
|
||||
```python
|
||||
|
10
Dockerfile
10
Dockerfile
@ -32,11 +32,11 @@ RUN useradd -m -d "/home/build" -s "/usr/bin/nologin" build && \
|
||||
COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
|
||||
## install package dependencies
|
||||
## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size
|
||||
RUN pacman -Sy --noconfirm --asdeps devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo && \
|
||||
pacman -Sy --noconfirm --asdeps python-build python-flit python-installer python-wheel && \
|
||||
pacman -Sy --noconfirm --asdeps breezy git mercurial python-aiohttp python-boto3 python-cryptography python-jinja python-requests-unixsocket python-systemd rsync subversion && \
|
||||
runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-apispec-git python-aiohttp-cors \
|
||||
python-aiohttp-jinja2 python-aiohttp-session python-aiohttp-security
|
||||
RUN pacman -Sy --noconfirm --asdeps devtools git pyalpm python-cerberus python-inflection python-passlib python-pyelftools python-requests python-srcinfo && \
|
||||
pacman -Sy --noconfirm --asdeps base-devel python-build python-flit python-installer python-wheel && \
|
||||
pacman -Sy --noconfirm --asdeps breezy git mercurial python-aiohttp python-boto3 python-cryptography python-jinja python-systemd rsync subversion && \
|
||||
runuser -u build -- install-aur-package python-aioauth-client python-webargs python-aiohttp-apispec-git python-aiohttp-cors \
|
||||
python-aiohttp-jinja2 python-aiohttp-session python-aiohttp-security python-requests-unixsocket2
|
||||
|
||||
## FIXME since 1.0.4 devtools requires dbus to be run, which doesn't work now in container
|
||||
COPY "docker/systemd-nspawn.sh" "/usr/local/bin/systemd-nspawn"
|
||||
|
@ -33,10 +33,12 @@ Every available option is described in the [documentation](https://ahriman.readt
|
||||
|
||||
The application provides reasonable defaults which allow to use it out-of-box; however additional steps (like configuring build toolchain and sudoers) are recommended and can be easily achieved by following install instructions.
|
||||
|
||||
## [FAQ](https://ahriman.readthedocs.io/en/stable/faq.html)
|
||||
## [FAQ](https://ahriman.readthedocs.io/en/stable/faq/index.html)
|
||||
|
||||
## Live demos
|
||||
|
||||
* [Build status page](https://ahriman-demo.arcanis.me). You can log in as `demo` user by using `demo` password. However, you will not be able to run tasks. [HTTP API documentation](https://ahriman-demo.arcanis.me/api-docs) is also available.
|
||||
* [Repository index](https://repo.arcanis.me/arcanisrepo/x86_64/).
|
||||
* [Telegram feed](https://t.me/arcanisrepo).
|
||||
|
||||
Do you have any success story? You can [share it](https://github.com/arcan1s/ahriman/issues/new?template=04-discussion.md)!
|
||||
|
@ -6,7 +6,7 @@ for PACKAGE in "$@"; do
|
||||
BUILD_DIR="$(mktemp -d)"
|
||||
git clone https://aur.archlinux.org/"$PACKAGE".git "$BUILD_DIR"
|
||||
cd "$BUILD_DIR"
|
||||
makepkg --noconfirm --install --rmdeps --syncdeps
|
||||
makepkg --nocheck --noconfirm --install --rmdeps --syncdeps
|
||||
cd /
|
||||
rm -r "$BUILD_DIR"
|
||||
done
|
||||
|
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
@ -20,6 +20,14 @@ ahriman.core.alpm.pacman module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.alpm.pacman\_database module
|
||||
-----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.alpm.pacman_database
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.alpm.repo module
|
||||
-----------------------------
|
||||
|
||||
|
@ -36,6 +36,14 @@ ahriman.core.auth.oauth module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.auth.pam module
|
||||
----------------------------
|
||||
|
||||
.. automodule:: ahriman.core.auth.pam
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
@ -108,6 +108,14 @@ ahriman.core.database.migrations.m012\_last\_commit\_sha module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.migrations.m013\_dependencies module
|
||||
----------------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.database.migrations.m013_dependencies
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
@ -28,6 +28,14 @@ ahriman.core.database.operations.changes\_operations module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.operations.dependencies\_operations module
|
||||
----------------------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.database.operations.dependencies_operations
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.operations.logs\_operations module
|
||||
--------------------------------------------------------
|
||||
|
||||
|
@ -60,6 +60,14 @@ ahriman.core.util module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.utils module
|
||||
-------------------------
|
||||
|
||||
.. automodule:: ahriman.core.utils
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
@ -12,6 +12,14 @@ ahriman.core.status.client module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.status.local\_client module
|
||||
----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.status.local_client
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.status.watcher module
|
||||
----------------------------------
|
||||
|
||||
|
@ -60,6 +60,22 @@ ahriman.models.counters module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.dependencies module
|
||||
----------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.dependencies
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.filesystem\_package module
|
||||
-----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.filesystem_package
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.internal\_status module
|
||||
--------------------------------------
|
||||
|
||||
@ -108,6 +124,14 @@ ahriman.models.package module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.package\_archive module
|
||||
--------------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.package_archive
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.package\_description module
|
||||
------------------------------------------
|
||||
|
||||
@ -204,6 +228,14 @@ ahriman.models.result module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.scan\_paths module
|
||||
---------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.scan_paths
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.sign\_settings module
|
||||
------------------------------------
|
||||
|
||||
|
@ -44,6 +44,14 @@ ahriman.web.schemas.counters\_schema module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.dependencies\_schema module
|
||||
-----------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.dependencies_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.error\_schema module
|
||||
----------------------------------------
|
||||
|
||||
@ -156,6 +164,14 @@ ahriman.web.schemas.package\_status\_schema module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.package\_version\_schema module
|
||||
---------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.package_version_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.pagination\_schema module
|
||||
---------------------------------------------
|
||||
|
||||
|
69
docs/ahriman.web.views.v1.packages.rst
Normal file
69
docs/ahriman.web.views.v1.packages.rst
Normal file
@ -0,0 +1,69 @@
|
||||
ahriman.web.views.v1.packages package
|
||||
=====================================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
ahriman.web.views.v1.packages.changes module
|
||||
--------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.packages.changes
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.packages.dependencies module
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.packages.dependencies
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.packages.logs module
|
||||
-----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.packages.logs
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.packages.package module
|
||||
--------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.packages.package
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.packages.packages module
|
||||
---------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.packages.packages
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.packages.patch module
|
||||
------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.packages.patch
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.packages.patches module
|
||||
--------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.packages.patches
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.packages
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
@ -8,6 +8,7 @@ Subpackages
|
||||
:maxdepth: 4
|
||||
|
||||
ahriman.web.views.v1.distributed
|
||||
ahriman.web.views.v1.packages
|
||||
ahriman.web.views.v1.service
|
||||
ahriman.web.views.v1.status
|
||||
ahriman.web.views.v1.user
|
||||
|
@ -4,14 +4,6 @@ ahriman.web.views.v1.status package
|
||||
Submodules
|
||||
----------
|
||||
|
||||
ahriman.web.views.v1.status.changes module
|
||||
------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.status.changes
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.info module
|
||||
---------------------------------------
|
||||
|
||||
@ -20,46 +12,6 @@ ahriman.web.views.v1.status.info module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.logs module
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.status.logs
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.package module
|
||||
------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.status.package
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.packages module
|
||||
-------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.status.packages
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.patch module
|
||||
----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.status.patch
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.patches module
|
||||
------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.status.patches
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.repositories module
|
||||
-----------------------------------------------
|
||||
|
||||
|
21
docs/ahriman.web.views.v2.packages.rst
Normal file
21
docs/ahriman.web.views.v2.packages.rst
Normal file
@ -0,0 +1,21 @@
|
||||
ahriman.web.views.v2.packages package
|
||||
=====================================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
ahriman.web.views.v2.packages.logs module
|
||||
-----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v2.packages.logs
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v2.packages
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
@ -7,7 +7,7 @@ Subpackages
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
ahriman.web.views.v2.status
|
||||
ahriman.web.views.v2.packages
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
@ -1,21 +0,0 @@
|
||||
ahriman.web.views.v2.status package
|
||||
===================================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
ahriman.web.views.v2.status.logs module
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v2.status.logs
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v2.status
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
@ -192,6 +192,7 @@ Idea is to add package to a build queue from which it will be handled automatica
|
||||
|
||||
* If supplied argument is file, then application moves the file to the directory with built packages. Same rule applies for directory, but in this case it copies every package-like file from the specified directory.
|
||||
* If supplied argument is directory and there is ``PKGBUILD`` file there, it will be treated as local package. In this case it will queue this package to build and copy source files (``PKGBUILD`` and ``.SRCINFO``) to caches.
|
||||
* If supplied argument looks like URL (i.e. it has scheme - e.g. ``http://`` which is neither ``data`` nor ``file``), it tries to download the package from the specified remote source.
|
||||
* If supplied argument is not file then application tries to lookup for the specified name in AUR and clones it into the directory with manual updates. This scenario can also handle package dependencies which are missing in repositories.
|
||||
|
||||
This logic can be overwritten by specifying the ``source`` parameter, which is partially useful if you would like to add package from AUR, but there is local directory cloned from AUR. Also official repositories calls are hidden behind explicit source definition.
|
||||
@ -206,10 +207,20 @@ Remove packages
|
||||
|
||||
This flow removes package from filesystem, updates repository database and also runs synchronization and reporting methods.
|
||||
|
||||
Check outdated packages
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There are few ways for packages to be marked as out-of-date and hence requiring rebuild. Those are following:
|
||||
|
||||
#. User requested update of the package. It can be caused by calling ``package-add`` subcommand (or ``package-update`` with arguments).
|
||||
#. The most common way for packages to be marked as out-of-dated is that the version in AUR (or the official repositories) is newer than in the repository.
|
||||
#. In addition to the above, if package is named as VCS (e.g. has suffix ``-git``) and the last update was more than specified threshold ago, the service will also try to fetch sources and check if the revision is newer than the built one.
|
||||
#. In addition, there is ability to check if the dependencies of the package have been updated (e.g. if linked library has been renamed or the modules directory - e.g. in case of python and ruby packages - has been changed). And if so, the package will be marked as out-of-dated as well.
|
||||
|
||||
Update packages
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
This feature is divided into to the following stages: check AUR for updates and run rebuild for required packages. Whereas check does not do anything except for check itself, update flow is the following:
|
||||
This feature is divided into to the following stages: check AUR for updates and run rebuild for required packages. The package update flow is the following:
|
||||
|
||||
#. Process every built package first. Those packages are usually added manually.
|
||||
#. Run sync and report methods.
|
||||
@ -259,6 +270,24 @@ The application is able to automatically bump package release (``pkgrel``) durin
|
||||
#. If it has ``major.minor`` notation (e.g. ``1.1``), then increment last part by 1, e.g. ``1.1 -> 1.2``, ``1.0.1 -> 1.0.2``.
|
||||
#. If ``pkgrel`` is a number (e.g. ``1``), then append 1 to the end of the string, e.g. ``1 -> 1.1``.
|
||||
|
||||
Implicit dependencies resolution
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In addition to the depends/optional/make/check depends lists the server also handles implicit dependencies. After success build, the application traverse through the build tree and finds
|
||||
|
||||
* Libraries to which the binaries (ELF-files) are linked. To do so, the ``NEEDED`` section of the ELF-files are read.
|
||||
* Directories which contains files of the package, but do not belong to this package. This case covers, for example, python and ruby submodules.
|
||||
|
||||
Having the initial dependencies tree, the application is looking for packages which contains those (both files and directories) paths and creates the initial packages list. After that, the packages list is reduced in the following way:
|
||||
|
||||
* From any leaf exclude the package itself and possible debug packages.
|
||||
* If the entry (i.e. file or directory) belongs to the package which is in base group, it will be removed.
|
||||
* If there is a package which depends on the another package which provide the same entry, the package will be removed.
|
||||
* After that, if there is a package which *optionally* depends on the another package in the remaining list, the package will be removed.
|
||||
* And finally, if there is any path, which is the child of the entry, and it contains the same package, the package from the smaller entry will be removed.
|
||||
|
||||
All those implicit dependencies are stored in the database and extracted on each check. In case if any of the repository packages doesn't contain any entry anymore (e.g. so version has been changed or modules directory has been changed), the dependent package will be marked as out-of-dated.
|
||||
|
||||
Core functions reference
|
||||
------------------------
|
||||
|
||||
@ -366,7 +395,7 @@ Web application requires the following python packages to be installed:
|
||||
* Additional web features also require ``aiohttp-apispec`` (autogenerated documentation), ``aiohttp_cors`` (CORS support, required by documentation).
|
||||
* In addition, authorization feature requires ``aiohttp_security``, ``aiohttp_session`` and ``cryptography``.
|
||||
* In addition to base authorization dependencies, OAuth2 also requires ``aioauth-client`` library.
|
||||
* In addition if you would like to disable authorization for local access (recommended way in order to run the application itself with reporting support), the ``requests-unixsocket`` library is required.
|
||||
* In addition if you would like to disable authorization for local access (recommended way in order to run the application itself with reporting support), the ``requests-unixsocket2`` library is required.
|
||||
|
||||
Middlewares
|
||||
^^^^^^^^^^^
|
||||
|
@ -53,6 +53,7 @@ libalpm and AUR related configuration. Group name can refer to architecture, e.g
|
||||
* ``mirror`` - package database mirror used by pacman for synchronization, string, required. This option supports standard pacman substitutions with ``$arch`` and ``$repo``. Note that the mentioned mirror should contain all repositories which are set by ``alpm.repositories`` option.
|
||||
* ``repositories`` - list of pacman repositories, used for package search, space separated list of strings, required.
|
||||
* ``root`` - root for alpm library, string, required. In the most cases it must point to the system root.
|
||||
* ``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.
|
||||
|
||||
``auth`` group
|
||||
@ -60,15 +61,17 @@ libalpm and AUR related configuration. Group name can refer to architecture, e.g
|
||||
|
||||
Base authorization settings. ``OAuth`` provider requires ``aioauth-client`` library to be installed.
|
||||
|
||||
* ``target`` - specifies authorization provider, string, optional, default ``disabled``. Allowed values are ``disabled``, ``configuration``, ``oauth``.
|
||||
* ``target`` - specifies authorization provider, string, optional, default ``disabled``. Allowed values are ``disabled``, ``configuration``, ``oauth``, ``pam``.
|
||||
* ``allow_read_only`` - allow requesting status APIs without authorization, boolean, required.
|
||||
* ``client_id`` - OAuth2 application client ID, string, required in case if ``oauth`` is used.
|
||||
* ``client_secret`` - OAuth2 application client secret key, string, required in case if ``oauth`` is used.
|
||||
* ``cookie_secret_key`` - secret key which will be used for cookies encryption, string, optional. It must be 32 bytes URL-safe base64-encoded and can be generated as following ``base64.urlsafe_b64encode(os.urandom(32)).decode("utf8")``. If not set, it will be generated automatically; note, however, that in this case, all sessions will be automatically invalidated during the service restart.
|
||||
* ``full_access_group`` - name of the secondary group (e.g. ``wheel``) to be used as admin group in the service, string, required in case if ``pam`` is used.
|
||||
* ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service in seconds, integer, optional, default is 7 days.
|
||||
* ``oauth_icon`` - OAuth2 login button icon, string, optional, default is ``google``. Must be valid `Bootstrap icon <https://icons.getbootstrap.com/>`__ name.
|
||||
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used.
|
||||
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used.
|
||||
* ``permit_root_login`` - allow login as root user, boolean, optional, default ``no``.
|
||||
* ``salt`` - additional password hash salt, string, optional.
|
||||
|
||||
Authorized users are stored inside internal database, if any of external providers (e.g. ``oauth``) are used, the password field for non-service users must be empty.
|
||||
@ -78,9 +81,12 @@ Authorized users are stored inside internal database, if any of external provide
|
||||
|
||||
Build related configuration. Group name can refer to architecture, e.g. ``build:x86_64`` can be used for x86_64 architecture specific settings.
|
||||
|
||||
* ``allowed_scan_paths`` - paths to be used for implicit dependencies scan, scape separated list of paths, optional.
|
||||
* ``archbuild_flags`` - additional flags passed to ``archbuild`` command, space separated list of strings, optional.
|
||||
* ``blacklisted_scan_paths`` - paths to be excluded for implicit dependencies scan, scape separated list of paths, optional. Normally all elements of this option must be child paths of any of ``allowed_scan_paths`` element.
|
||||
* ``build_command`` - default build command, string, required.
|
||||
* ``ignore_packages`` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional.
|
||||
* ``include_debug_packages`` - distribute debug packages, boolean, optional, default ``yes``.
|
||||
* ``makepkg_flags`` - additional flags passed to ``makepkg`` command, space separated list of strings, optional.
|
||||
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
|
||||
* ``triggers`` - list of ``ahriman.core.triggers.Trigger`` class implementation (e.g. ``ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger``) which will be loaded and run at the end of processing, space separated list of strings, optional. You can also specify triggers by their paths, e.g. ``/usr/lib/python3.10/site-packages/ahriman/core/report/report.py.ReportTrigger``. Triggers are run in the order of definition.
|
||||
@ -128,7 +134,7 @@ Web server settings. This feature requires ``aiohttp`` libraries to be installed
|
||||
* ``port`` - port to bind, integer, optional.
|
||||
* ``service_only`` - disable status routes (including logs), boolean, optional, default ``no``.
|
||||
* ``static_path`` - path to directory with static files, string, required.
|
||||
* ``templates`` - path to templates directories, space separated list of strings, required.
|
||||
* ``templates`` - path to templates directories, space separated list of paths, required.
|
||||
* ``unix_socket`` - path to the listening unix socket, string, optional. If set, server will create the socket on the specified address which can (and will) be used by application. Note, that unlike usual host/port configuration, unix socket allows to perform requests without authorization.
|
||||
* ``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.
|
||||
@ -250,7 +256,7 @@ Section name must be either ``email`` (plus optional architecture name, e.g. ``e
|
||||
* ``ssl`` - SSL mode for SMTP connection, one of ``ssl``, ``starttls``, ``disabled``, optional, default ``disabled``.
|
||||
* ``template`` - Jinja2 template name, string, required.
|
||||
* ``template_full`` - Jinja2 template name for full package description index, string, optional.
|
||||
* ``templates`` - path to templates directories, space separated list of strings, required.
|
||||
* ``templates`` - path to templates directories, space separated list of paths, required.
|
||||
* ``user`` - SMTP user to authenticate, string, optional.
|
||||
|
||||
``html`` type
|
||||
@ -263,7 +269,7 @@ Section name must be either ``html`` (plus optional architecture name, e.g. ``ht
|
||||
* ``link_path`` - prefix for HTML links, string, required.
|
||||
* ``path`` - path to html report file, string, required.
|
||||
* ``template`` - Jinja2 template name, string, required.
|
||||
* ``templates`` - path to templates directories, space separated list of strings, required.
|
||||
* ``templates`` - path to templates directories, space separated list of paths, required.
|
||||
|
||||
``remote-call`` type
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
@ -288,7 +294,7 @@ Section name must be either ``telegram`` (plus optional architecture name, e.g.
|
||||
* ``link_path`` - prefix for HTML links, string, required.
|
||||
* ``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``.
|
||||
* ``templates`` - path to templates directories, space separated list of strings, required.
|
||||
* ``templates`` - path to templates directories, space separated list of paths, required.
|
||||
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
||||
|
||||
``upload`` group
|
||||
|
1539
docs/faq.rst
1539
docs/faq.rst
File diff suppressed because it is too large
Load Diff
35
docs/faq/backup.rst
Normal file
35
docs/faq/backup.rst
Normal file
@ -0,0 +1,35 @@
|
||||
Backup and restore
|
||||
------------------
|
||||
|
||||
The service provides several commands aim to do easy repository backup and restore. If you would like to move repository from the server ``server1.example.com`` to another ``server2.example.com`` you have to perform the following steps:
|
||||
|
||||
#.
|
||||
On the source server ``server1.example.com`` run ``repo-backup`` command, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
ahriman repo-backup /tmp/repo.tar.gz
|
||||
|
||||
This command will pack all configuration files together with database file into the archive specified as command line argument (i.e. ``/tmp/repo.tar.gz``). In addition it will also archive ``cache`` directory (the one which contains local clones used by e.g. local packages) and ``.gnupg`` of the ``ahriman`` user.
|
||||
|
||||
#.
|
||||
Copy created archive from source server ``server1.example.com`` to target ``server2.example.com``.
|
||||
|
||||
#.
|
||||
Install package as usual on the target server ``server2.example.com`` if you didn't yet.
|
||||
|
||||
#.
|
||||
Extract archive e.g. by using subcommand:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
ahriman repo-restore /tmp/repo.tar.gz
|
||||
|
||||
An additional argument ``-o``/``--output`` can be used to specify extraction root (``/`` by default).
|
||||
|
||||
#.
|
||||
Rebuild repository:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman repo-rebuild --from-database
|
320
docs/faq/distributed.rst
Normal file
320
docs/faq/distributed.rst
Normal file
@ -0,0 +1,320 @@
|
||||
Distributed builds
|
||||
------------------
|
||||
|
||||
The service allows to run build on multiple machines and collect packages on main node. There are several ways to achieve it, this section describes officially supported methods.
|
||||
|
||||
Remote synchronization and remote server call
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This setup requires at least two instances of the service:
|
||||
|
||||
#. Web service (with opt-in authorization enabled), later will be referenced as ``master`` node.
|
||||
#. Application instances responsible for build, later will be referenced as ``worker`` nodes.
|
||||
|
||||
In this example the following settings are assumed:
|
||||
|
||||
* Repository architecture is ``x86_64``.
|
||||
* Master node address is ``master.example.com``.
|
||||
|
||||
Master node configuration
|
||||
"""""""""""""""""""""""""
|
||||
|
||||
The only requirements for the master node is that API must be available for worker nodes to call (e.g. port must be exposed to internet, or local network in case of VPN, etc) and file upload must be enabled:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[web]
|
||||
enable_archive_upload = yes
|
||||
|
||||
In addition, the following settings are recommended for the master node:
|
||||
|
||||
*
|
||||
As it has been mentioned above, it is recommended to enable authentication (see :doc:`How to enable basic authorization <web>`) and create system user which will be used later. Later this user (if any) will be referenced as ``worker-user``.
|
||||
|
||||
*
|
||||
In order to be able to spawn multiple processes at the same time, wait timeout must be configured:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[web]
|
||||
wait_timeout = 0
|
||||
|
||||
Worker nodes configuration
|
||||
""""""""""""""""""""""""""
|
||||
|
||||
#.
|
||||
First of all, in this setup you need to split your repository into chunks manually, e.g. if you have repository on master node with packages ``A``, ``B`` and ``C``, you need to split them between all available workers, as example:
|
||||
|
||||
* Worker #1: ``A``.
|
||||
* Worker #2: ``B`` and ``C``.
|
||||
|
||||
Hint: ``repo-tree`` subcommand provides ``--partitions`` argument.
|
||||
|
||||
#.
|
||||
Each worker must be configured to upload files to master node:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[upload]
|
||||
target = remote-service
|
||||
|
||||
[remote-service]
|
||||
|
||||
#.
|
||||
Worker must be configured to access web on master node:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[status]
|
||||
address = https://master.example.com
|
||||
username = worker-user
|
||||
password = very-secure-password
|
||||
|
||||
As it has been mentioned above, ``status.address`` must be available for workers. In case if unix socket is used, it can be passed in the same option as usual. Optional ``status.username``/``status.password`` can be supplied in case if authentication was enabled on master node.
|
||||
|
||||
#.
|
||||
Each worker must call master node on success:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[report]
|
||||
target = remote-call
|
||||
|
||||
[remote-call]
|
||||
manual = yes
|
||||
|
||||
After success synchronization (see above), the built packages will be put into directory, from which they will be read during manual update, thus ``remote-call.manual`` flag is required.
|
||||
|
||||
#.
|
||||
Change order of trigger runs. This step is required, because by default the report trigger is called before the upload trigger and we would like to achieve the opposite:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[build]
|
||||
triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.upload.UploadTrigger ahriman.core.report.ReportTrigger ahriman.core.gitremote.RemotePushTrigger
|
||||
|
||||
In addition, the following settings are recommended for workers:
|
||||
|
||||
*
|
||||
You might want to wait until report trigger will be completed; in this case the following option must be set:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[remote-call]
|
||||
wait_timeout = 0
|
||||
|
||||
Dependency management
|
||||
"""""""""""""""""""""
|
||||
|
||||
By default worker nodes don't know anything about master nodes packages, thus it will try to build each dependency by its own. However, using ``AHRIMAN_REPOSITORY_SERVER`` docker variable (or ``--server`` flag for setup command), it is possible to specify address of the master node for devtools configuration.
|
||||
|
||||
Repository and packages signing
|
||||
"""""""""""""""""""""""""""""""
|
||||
|
||||
You can sign packages on worker nodes and then signatures will be synced to master node. In order to do so, you need to configure worker node as following, e.g.:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[sign]
|
||||
target = package
|
||||
key = 8BE91E5A773FB48AC05CC1EDBED105AED6246B39
|
||||
|
||||
Note, however, that in this case, signatures will not be validated on master node and just will be copied to repository tree.
|
||||
|
||||
If you would like to sign only database files (aka repository sign), it has to be configured only on master node as usual, e.g.:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[sign]
|
||||
target = repository
|
||||
key = 8BE91E5A773FB48AC05CC1EDBED105AED6246B39
|
||||
|
||||
Double node minimal docker example
|
||||
""""""""""""""""""""""""""""""""""
|
||||
|
||||
Master node config (``master.ini``) as:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[auth]
|
||||
target = configuration
|
||||
|
||||
[web]
|
||||
enable_archive_upload = yes
|
||||
wait_timeout = 0
|
||||
|
||||
|
||||
Command to run master node:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -v master.ini:/etc/ahriman.ini.d/overrides.ini arcan1s/ahriman:latest web
|
||||
|
||||
The user ``worker-user`` has been created additionally. Worker node config (``worker.ini``) as:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[status]
|
||||
address = http://172.17.0.1:8080
|
||||
username = worker-user
|
||||
password = very-secure-password
|
||||
|
||||
[upload]
|
||||
target = remote-service
|
||||
|
||||
[remote-service]
|
||||
|
||||
[report]
|
||||
target = remote-call
|
||||
|
||||
[remote-call]
|
||||
manual = yes
|
||||
wait_timeout = 0
|
||||
|
||||
[build]
|
||||
triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.upload.UploadTrigger ahriman.core.report.ReportTrigger ahriman.core.gitremote.RemotePushTrigger
|
||||
|
||||
The address above (``http://172.17.0.1:8080``) is somewhat available for worker container.
|
||||
|
||||
Command to run worker node:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -v worker.ini:/etc/ahriman.ini.d/overrides.ini -it arcan1s/ahriman:latest package-add ahriman --now
|
||||
|
||||
The command above will successfully build ``ahriman`` package, upload it on master node and, finally, will update master node repository.
|
||||
|
||||
Check proof-of-concept setup `here <https://github.com/arcan1s/ahriman/tree/master/recipes/distributed-manual>`__.
|
||||
|
||||
Addition of new package and repository update
|
||||
"""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Just run on worker command as usual, the built packages will be automatically uploaded to master node. Note that automatic update process must be disabled on master node.
|
||||
|
||||
Package removal
|
||||
"""""""""""""""
|
||||
|
||||
This action must be done in two steps:
|
||||
|
||||
#. Remove package on worker.
|
||||
#. Remove package on master node.
|
||||
|
||||
Delegate builds to remote workers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This setup heavily uses upload feature described above and, in addition, also delegates build process automatically to build machines. Same as above, there must be at least two instances available (``master`` and ``worker``), however, all ``worker`` nodes must be run in the web service mode.
|
||||
|
||||
Master node configuration
|
||||
"""""""""""""""""""""""""
|
||||
|
||||
In addition to the configuration above, the worker list must be defined in configuration file (``build.workers`` option), i.e.:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[build]
|
||||
workers = https://worker1.example.com https://worker2.example.com
|
||||
|
||||
[web]
|
||||
enable_archive_upload = yes
|
||||
wait_timeout = 0
|
||||
|
||||
In the example above, ``https://worker1.example.com`` and ``https://worker2.example.com`` are remote ``worker`` node addresses available for ``master`` node.
|
||||
|
||||
In case if authentication is required (which is recommended way to setup it), it can be set by using ``status`` section as usual.
|
||||
|
||||
Worker nodes configuration
|
||||
""""""""""""""""""""""""""
|
||||
|
||||
It is required to point to the master node repository, otherwise internal dependencies will not be handled correctly. In order to do so, the ``--server`` argument (or ``AHRIMAN_REPOSITORY_SERVER`` environment variable for docker images) can be used.
|
||||
|
||||
Also, in case if authentication is enabled, the same user with the same password must be created for all workers.
|
||||
|
||||
It is also recommended to set ``web.wait_timeout`` to infinite in case of multiple conflicting runs and ``service_only`` to ``yes`` in order to disable status endpoints.
|
||||
|
||||
Other settings are the same as mentioned above.
|
||||
|
||||
Triple node minimal docker example
|
||||
""""""""""""""""""""""""""""""""""
|
||||
|
||||
In this example, all instances are run on the same machine with address ``172.17.0.1`` with ports available outside of container. Master node config (``master.ini``) as:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[auth]
|
||||
target = configuration
|
||||
|
||||
[status]
|
||||
username = builder-user
|
||||
password = very-secure-password
|
||||
|
||||
[build]
|
||||
workers = http://172.17.0.1:8081 http://172.17.0.1:8082
|
||||
|
||||
[web]
|
||||
enable_archive_upload = yes
|
||||
wait_timeout = 0
|
||||
|
||||
Command to run master node:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -v master.ini:/etc/ahriman.ini.d/overrides.ini arcan1s/ahriman:latest web
|
||||
|
||||
Worker nodes (applicable for all workers) config (``worker.ini``) as:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[auth]
|
||||
target = configuration
|
||||
|
||||
[status]
|
||||
address = http://172.17.0.1:8080
|
||||
username = builder-user
|
||||
password = very-secure-password
|
||||
|
||||
[upload]
|
||||
target = remote-service
|
||||
|
||||
[remote-service]
|
||||
|
||||
[report]
|
||||
target = remote-call
|
||||
|
||||
[remote-call]
|
||||
manual = yes
|
||||
wait_timeout = 0
|
||||
|
||||
[web]
|
||||
service_only = yes
|
||||
|
||||
[build]
|
||||
triggers = ahriman.core.upload.UploadTrigger ahriman.core.report.ReportTrigger
|
||||
|
||||
Command to run worker nodes (considering there will be two workers, one is on ``8081`` port and other is on ``8082``):
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
docker run --privileged -p 8081:8081 -e AHRIMAN_PORT=8081 -v worker.ini:/etc/ahriman.ini.d/overrides.ini arcan1s/ahriman:latest web
|
||||
docker run --privileged -p 8082:8082 -e AHRIMAN_PORT=8082 -v worker.ini:/etc/ahriman.ini.d/overrides.ini arcan1s/ahriman:latest web
|
||||
|
||||
Unlike the previous setup, it doesn't require to mount repository root for ``worker`` nodes, because they don't use it anyway.
|
||||
|
||||
Check proof-of-concept setup `here <https://github.com/arcan1s/ahriman/tree/master/recipes/distributed>`__.
|
||||
|
||||
Addition of new package, package removal, repository update
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
In all scenarios, update process must be run only on ``master`` node. Unlike the manually distributed packages described above, automatic update must be enabled only for ``master`` node.
|
||||
|
||||
Automatic worker nodes discovery
|
||||
""""""""""""""""""""""""""""""""
|
||||
|
||||
Instead of setting ``build.workers`` option it is also possible to configure services to load worker list dynamically. To do so, the ``ahriman.core.distributed.WorkerLoaderTrigger`` and ``ahriman.core.distributed.WorkerTrigger`` must be used for ``master`` and ``worker`` nodes repsectively. See recipes for more details.
|
||||
|
||||
Known limitations
|
||||
"""""""""""""""""
|
||||
|
||||
* Workers don't support local packages. However, it is possible to build custom packages by providing sources by using ``ahriman.core.gitremote.RemotePullTrigger`` trigger.
|
||||
* No dynamic nodes discovery. In case if one of worker nodes is unavailable, the build process will fail.
|
||||
* No pkgrel bump on conflicts.
|
||||
* The identical user must be created for all workers. However, the ``master`` node user can be different from this one.
|
115
docs/faq/docker.rst
Normal file
115
docs/faq/docker.rst
Normal file
@ -0,0 +1,115 @@
|
||||
Docker image
|
||||
------------
|
||||
|
||||
We provide official images which can be found under:
|
||||
|
||||
* docker registry ``arcan1s/ahriman``;
|
||||
* ghcr.io registry ``ghcr.io/arcan1s/ahriman``.
|
||||
|
||||
These images are totally identical.
|
||||
|
||||
Docker image is being updated on each commit to master as well as on each version. If you would like to use last (probably unstable) build you can use ``edge`` tag or ``latest`` for any tagged versions; otherwise you can use any version tag available.
|
||||
|
||||
The default action (in case if no arguments provided) is ``repo-update``. Basically the idea is to run container, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
``--privileged`` flag is required to make mount possible inside container. In order to make data available outside of container, you would need to mount local (parent) directory inside container by using ``-v /path/to/local/repo:/var/lib/ahriman`` argument, where ``/path/to/local/repo`` is a path to repository on local machine. In addition, you can pass own configuration overrides by using the same ``-v`` flag, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -v /path/to/local/repo:/var/lib/ahriman -v /path/to/overrides/overrides.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest
|
||||
|
||||
The action can be specified during run, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest package-add ahriman --now
|
||||
|
||||
For more details please refer to the docker FAQ.
|
||||
|
||||
Environment variables
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following environment variables are supported:
|
||||
|
||||
* ``AHRIMAN_ARCHITECTURE`` - architecture of the repository, default is ``x86_64``.
|
||||
* ``AHRIMAN_DEBUG`` - if set all commands will be logged to console.
|
||||
* ``AHRIMAN_FORCE_ROOT`` - force run ahriman as root instead of guessing by subcommand.
|
||||
* ``AHRIMAN_HOST`` - host for the web interface, default is ``0.0.0.0``.
|
||||
* ``AHRIMAN_MULTILIB`` - if set (default) multilib repository will be used, disabled otherwise.
|
||||
* ``AHRIMAN_OUTPUT`` - controls logging handler, e.g. ``syslog``, ``console``. The name must be found in logging configuration. Note that if ``syslog`` handler is used you will need to mount ``/dev/log`` inside container because it is not available there.
|
||||
* ``AHRIMAN_PACKAGER`` - packager name from which packages will be built, default is ``ahriman bot <ahriman@example.com>``.
|
||||
* ``AHRIMAN_PACMAN_MIRROR`` - override pacman mirror server if set.
|
||||
* ``AHRIMAN_PORT`` - HTTP server port if any, default is empty.
|
||||
* ``AHRIMAN_POSTSETUP_COMMAND`` - if set, the command which will be called (as root) after the setup command, but before any other actions.
|
||||
* ``AHRIMAN_PRESETUP_COMMAND`` - if set, the command which will be called (as root) right before the setup command.
|
||||
* ``AHRIMAN_REPOSITORY`` - repository name, default is ``aur-clone``.
|
||||
* ``AHRIMAN_REPOSITORY_SERVER`` - optional override for the repository URL. Useful if you would like to download packages from remote instead of local filesystem.
|
||||
* ``AHRIMAN_REPOSITORY_ROOT`` - repository root. Because of filesystem rights it is required to override default repository root. By default, it uses ``ahriman`` directory inside ahriman's home, which can be passed as mount volume.
|
||||
* ``AHRIMAN_UNIX_SOCKET`` - full path to unix socket which is used by web server, default is empty. Note that more likely you would like to put it inside ``AHRIMAN_REPOSITORY_ROOT`` directory (e.g. ``/var/lib/ahriman/ahriman/ahriman-web.sock``) or to ``/run/ahriman``.
|
||||
* ``AHRIMAN_USER`` - ahriman user, usually must not be overwritten, default is ``ahriman``.
|
||||
* ``AHRIMAN_VALIDATE_CONFIGURATION`` - if set (default) validate service configuration.
|
||||
|
||||
You can pass any of these variables by using ``-e`` argument, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -e AHRIMAN_PORT=8080 -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
Daemon service
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
There is special ``repo-daemon`` subcommand which emulates systemd timer and will perform repository update periodically:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest repo-daemon
|
||||
|
||||
This command uses same rules as ``repo-update``, thus, e.g. requires ``--privileged`` flag. Check also `examples <https://github.com/arcan1s/ahriman/tree/master/recipes/daemon>`__.
|
||||
|
||||
Web service setup
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
For that you would need to have web container instance running forever; it can be achieved by the following command:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman/ahriman-web.sock -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
Note about ``AHRIMAN_PORT`` environment variable which is required in order to enable web service. An additional port bind by ``-p 8080:8080`` is required to pass docker port outside of container.
|
||||
|
||||
The ``AHRIMAN_UNIX_SOCKET`` variable is not required, however, highly recommended as it can be used for interprocess communications. If you set this variable you would like to be sure that this path is available outside of container if you are going to use multiple docker instances.
|
||||
|
||||
If you are using ``AHRIMAN_UNIX_SOCKET`` variable, for every next container run it has to be passed also, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman/ahriman-web.sock -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
Otherwise, you would need to pass ``AHRIMAN_PORT`` and mount container network to the host system (``--net=host``), e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged --net=host -e AHRIMAN_PORT=8080 -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
Simple server with authentication can be found in `examples <https://github.com/arcan1s/ahriman/tree/master/recipes/web>`__ too.
|
||||
|
||||
Mutli-repository web service
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
Idea is pretty same as to just run web service. However, it is required to run setup commands for each repository, except for one which is specified by ``AHRIMAN_REPOSITORY`` and ``AHRIMAN_ARCHITECTURE`` variables.
|
||||
|
||||
In order to create configuration for additional repositories, the ``AHRIMAN_POSTSETUP_COMMAND`` variable should be used, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman/ahriman-web.sock -e AHRIMAN_POSTSETUP_COMMAND="ahriman --architecture x86_64 --repository aur-clone-v2 service-setup --build-as-user ahriman --packager 'ahriman bot <ahriman@example.com>'" -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
The command above will also create configuration for the repository named ``aur-clone-v2``.
|
||||
|
||||
Note, however, that the command above is only required in case if the service is going to be used to run subprocesses. Otherwise, everything else (web interface, status, etc) will be handled as usual.
|
||||
|
||||
Configuration `example <https://github.com/arcan1s/ahriman/tree/master/recipes/multirepo>`__.
|
12
docs/faq/examples.rst
Normal file
12
docs/faq/examples.rst
Normal file
@ -0,0 +1,12 @@
|
||||
Use cases
|
||||
---------
|
||||
|
||||
There is a collection of some specific recipes which can be found in `the repository <https://github.com/arcan1s/ahriman/tree/master/recipes>`__.
|
||||
|
||||
Most of them can be run (``AHRIMAN_PASSWORD`` environment variable is required in the most setups) as simple as:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
AHRIMAN_PASSWORD=demo docker compose up
|
||||
|
||||
Note, however, they are just an examples of specific configuration for specific cases and they are never intended to be used as is in real environment.
|
431
docs/faq/general.rst
Normal file
431
docs/faq/general.rst
Normal file
@ -0,0 +1,431 @@
|
||||
General topics
|
||||
--------------
|
||||
|
||||
What is the purpose of the project
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This project has been created in order to maintain self-hosted Arch Linux user repository without manual intervention - checking for updates and building packages.
|
||||
|
||||
How to install ahriman
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TL;DR
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S ahriman
|
||||
ahriman -a x86_64 -r aur-clone service-setup --packager "ahriman bot <ahriman@example.com>"
|
||||
systemctl enable --now ahriman@x86_64-aur-clone.timer
|
||||
|
||||
Long answer
|
||||
"""""""""""
|
||||
|
||||
The idea is to install the package as usual, create working directory tree, create configuration for ``sudo`` and ``devtools``. Detailed description of the setup instruction can be found :doc:`here </setup>`.
|
||||
|
||||
Run as daemon
|
||||
"""""""""""""
|
||||
|
||||
The alternative way (though not recommended) is to run service instead of timer:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
systemctl enable --now ahriman-daemon@x86_64-aur-clone
|
||||
|
||||
How to validate settings
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There is special command which can be used in order to validate current configuration:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
ahriman service-config-validate --exit-code
|
||||
|
||||
This command will print found errors, based on `cerberus <https://docs.python-cerberus.org/>`__, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
auth
|
||||
ssalt: unknown field
|
||||
target: none or more than one rule validate
|
||||
oneof definition 0: unallowed value mapping
|
||||
oneof definition 1: field 'salt' is required
|
||||
oneof definition 2: unallowed value mapping
|
||||
oneof definition 2: field 'salt' is required
|
||||
oneof definition 2: field 'client_id' is required
|
||||
oneof definition 2: field 'client_secret' is required
|
||||
gitremote
|
||||
pull_url: unknown field
|
||||
|
||||
If an additional flag ``--exit-code`` is supplied, the application will return non-zero exit code, which can be used partially in scripts.
|
||||
|
||||
What does "architecture specific" mean / How to configure for different architectures
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Some sections can be configured per architecture. The service will merge architecture specific values into common settings. In order to specify settings for specific architecture you must point it in section name.
|
||||
|
||||
For example, the section
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[build]
|
||||
build_command = extra-x86_64-build
|
||||
|
||||
states that default build command is ``extra-x86_64-build``. But if there is section
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[build:i686]
|
||||
build_command = extra-i686-build
|
||||
|
||||
the ``extra-i686-build`` command will be used for ``i686`` architecture. You can also override settings for different repositories and architectures; in this case section names will be ``build:aur-clone`` (repository name only) and ``build:aur-clone:i686`` (both repository name and architecture).
|
||||
|
||||
How to generate build reports
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Normally you would probably like to generate only one report for the specific type, e.g. only one email report. In order to do so you will need to have the following configuration:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[report]
|
||||
target = email
|
||||
|
||||
[email]
|
||||
...
|
||||
|
||||
or in case of multiple architectures and *different* reporting settings:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[report]
|
||||
target = email
|
||||
|
||||
[email:i686]
|
||||
...
|
||||
|
||||
[email:x86_64]
|
||||
...
|
||||
|
||||
But for some cases you would like to have multiple different reports with the same type (e.g. sending different templates to different addresses). For these cases you will need to specify section name in target and type in section, e.g. the following configuration can be used:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[report]
|
||||
target = email_1 email_2
|
||||
|
||||
[email_1]
|
||||
type = email
|
||||
...
|
||||
|
||||
[email_2]
|
||||
type = email
|
||||
...
|
||||
|
||||
How to add new package
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman package-add ahriman --now
|
||||
|
||||
``--now`` flag is totally optional and just run ``repo-update`` subcommand after the registering the new package. Thus the extended flow is the following:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman package-add ahriman
|
||||
sudo -u ahriman ahriman repo-update
|
||||
|
||||
How to build package from local PKGBUILD
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TL;DR
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman package-add /path/to/local/directory/with/PKGBUILD --now
|
||||
|
||||
Before using this command you will need to create local directory, put ``PKGBUILD`` there and generate ``.SRCINFO`` by using ``makepkg --printsrcinfo > .SRCINFO`` command. These packages will be stored locally and *will be ignored* during automatic update; in order to update the package you will need to run ``package-add`` command again.
|
||||
|
||||
How to copy package from another repository
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
As simple as add package from archive. Considering case when you would like to copy package ``package`` with version ``ver-rel`` from repository ``source-repository`` to ``target-respository`` (same architecture), the command will be following:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman -r target-repository package-add /var/lib/ahriman/repository/source-repository/x86_64/package-ver-rel-x86_64.pkg.tar.zst
|
||||
|
||||
In addition, you can remove source package as usual later.
|
||||
|
||||
This feature in particular useful if for managing multiple repositories like ``[testing]`` and ``[extra]``.
|
||||
|
||||
How to fetch PKGBUILDs from remote repository
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For that purpose you could use ``RemotePullTrigger`` trigger. To do so you will need to configure trigger as following:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[remote-pull]
|
||||
target = gitremote
|
||||
|
||||
[gitremote]
|
||||
pull_url = https://github.com/username/repository
|
||||
|
||||
During the next application run it will fetch repository from the specified URL and will try to find packages there which can be used as local sources.
|
||||
|
||||
This feature can be also used to build packages which are not listed in AUR, the example of the feature use can be found `here <https://github.com/arcan1s/ahriman/tree/master/recipes/pull>`__.
|
||||
|
||||
How to push updated PKGBUILDs to remote repository
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For that purpose you'd need to use another trigger called ``RemotePushTrigger``. Configure trigger as following:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[remote-push]
|
||||
target = gitremote
|
||||
|
||||
[gitremote]
|
||||
push_url = https://github.com/username/repository
|
||||
|
||||
Unlike ``RemotePullTrigger`` trigger, the ``RemotePushTrigger`` more likely will require authorization. It is highly recommended to use application tokens for that instead of using your password (e.g. for GitHub you can generate tokens `here <https://github.com/settings/tokens>`__ with scope ``public_repo``). Authorization can be supplied by using authorization part of the URL, e.g. ``https://key:token@github.com/username/repository``.
|
||||
|
||||
How to change PKGBUILDs before build
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Well it is supported also. The recommended way is to patch specific function, e.g. by running
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman patch-add ahriman version
|
||||
|
||||
This command will prompt for new value of the PKGBUILD variable ``version``. You can also write it to file and read from it:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman patch-add ahriman version version.patch
|
||||
|
||||
The command also supports arrays, but in this case you need to specify full array, e.g.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman patch-add ahriman depends
|
||||
|
||||
Post new function or variable value below. Press Ctrl-D to finish:
|
||||
(python python-aiohttp)
|
||||
^D
|
||||
|
||||
will set depends PKGBUILD variable (exactly) to array ``["python", "python-aiohttp"]``.
|
||||
|
||||
Alternatively you can create full-diff patches, which are calculated by using ``git diff`` from current PKGBUILD master branch:
|
||||
|
||||
#.
|
||||
Clone sources from AUR.
|
||||
|
||||
#.
|
||||
Make changes you would like to (e.g. edit ``PKGBUILD``, add external patches).
|
||||
|
||||
#.
|
||||
Run command
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman patch-set-add /path/to/local/directory/with/PKGBUILD
|
||||
|
||||
The last command will calculate diff from current tree to the ``HEAD`` and will store it locally. Patches will be applied on any package actions (e.g. it can be used for dependency management).
|
||||
|
||||
It is also possible to create simple patch during package addition, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman package-add ahriman --variable PKGEXT=.pkg.tar.xz
|
||||
|
||||
The ``--variable`` argument accepts variables in shell like format: quotation and lists are supported as usual, but functions are not. This feature is useful in particular in order to override specific makepkg variables during build.
|
||||
|
||||
How to build package from official repository
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It is the same as adding any other package, but due to restrictions you must specify source explicitly, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman package-add pacman --source repository
|
||||
|
||||
This feature is heavily depends on local pacman cache. In order to use this feature it is recommended to either run ``pacman -Sy`` before the interaction or use internal application cache with ``--refresh`` flag.
|
||||
|
||||
Package build fails because it cannot validate PGP signature of source files
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TL;DR
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman service-key-import ...
|
||||
|
||||
How to update VCS packages
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Normally the service handles VCS packages correctly, however it requires additional dependencies:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
pacman -S breezy darcs mercurial subversion
|
||||
|
||||
How to review changes before build
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In this scenario, the update process must be separated into several stages. First, it is required to check updates:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman repo-check
|
||||
|
||||
During the check process, the service will generate changes from the last known commit and will send it to remote service. In order to verify source files changes, the web interface or special subcommand can be used:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
ahriman package-changes ahriman
|
||||
|
||||
After validation, the operator can run update process with approved list of packages, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman repo-update ahriman
|
||||
|
||||
How to remove package
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman package-remove ahriman
|
||||
|
||||
Also, there is command ``repo-remove-unknown`` which checks packages in AUR and local storage and removes ones which have been removed.
|
||||
|
||||
Remove commands also remove any package files (patches, caches etc).
|
||||
|
||||
How to sign repository
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Repository sign feature is available in several configurations. The recommended way is just to sign repository database file by single key instead of trying to sign each package. However, the steps are pretty same, just configuration is a bit different. For more details about options kindly refer to :doc:`configuration reference </configuration>`.
|
||||
|
||||
#.
|
||||
First you would need to create the key on your local machine:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
gpg --full-generate-key
|
||||
|
||||
This command will prompt you for several questions. Most of them may be left default, but you will need to fill real name and email address with some data. Because at the moment the service doesn't support passphrases, it must be left blank.
|
||||
|
||||
#.
|
||||
The command above will generate key and print its fingerprint, something like ``8BE91E5A773FB48AC05CC1EDBED105AED6246B39``. Copy it.
|
||||
|
||||
#.
|
||||
Export your private key by using the fingerprint above:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
gpg --export-secret-keys -a 8BE91E5A773FB48AC05CC1EDBED105AED6246B39 > repository-key.gpg
|
||||
|
||||
#.
|
||||
|
||||
Copy the specified key to the build machine (i.e. where the service is running).
|
||||
|
||||
#.
|
||||
Import the specified key to the service user:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman gpg --import repository-key.gpg
|
||||
|
||||
Don't forget to remove the key from filesystem after import.
|
||||
|
||||
#.
|
||||
Change trust level to ``ultimate``:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman gpg --edit-key 8BE91E5A773FB48AC05CC1EDBED105AED6246B39
|
||||
|
||||
The command above will drop you into gpg shell, in which you will need to type ``trust``, choose ``5 = I trust ultimately``, confirm and exit ``quit``.
|
||||
|
||||
#.
|
||||
Proceed with service configuration according to the :doc:`configuration </configuration>`:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[sign]
|
||||
target = repository
|
||||
key = 8BE91E5A773FB48AC05CC1EDBED105AED6246B39
|
||||
|
||||
|
||||
How to rebuild packages after library update
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TL;DR
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman repo-rebuild --depends-on python
|
||||
|
||||
You can even rebuild the whole repository (which is particular useful in case if you would like to change packager) if you do not supply ``--depends-on`` option. This action will automatically increment ``pkgrel`` value; in case if you don't want to, the ``--no-increment`` option has to be supplied.
|
||||
|
||||
However, note that you do not need to rebuild repository in case if you just changed signing option, just use ``repo-sign`` command instead.
|
||||
|
||||
Automated broken dependencies detection
|
||||
"""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
After the success build the application extracts all linked libraries and used directories and stores them in database. During the check process, the application extracts pacman databases and checks if file names have been changed (e.g. new python release caused ``/usr/lib/python3.x`` directory renaming to ``/usr/lib/python3.y`` or soname for a linked library has been changed). In case if broken dependencies have been detected, the package will be added to the rebuild queue.
|
||||
|
||||
In order to disable this check completely, the ``--no-check-files`` flag can be used.
|
||||
|
||||
In addition, there is possibility to control paths which will be used for checking, by using options ``build.allowed_scan_paths`` and ``build.blacklisted_scan_paths``. Leaving ``build.allowed_scan_paths`` blank will effectively disable any check too.
|
||||
|
||||
How to install built packages
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Add the following lines to your ``pacman.conf``:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[repository]
|
||||
Server = file:///var/lib/ahriman/repository/$repo/$arch
|
||||
|
||||
(You might need to add ``SigLevel`` option according to the pacman documentation.)
|
||||
|
||||
How to serve repository
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Easy. For example, nginx configuration (without SSL) will look like:
|
||||
|
||||
.. code-block::
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name repo.example.com;
|
||||
|
||||
location / {
|
||||
autoindex on;
|
||||
root /var/lib/ahriman/repository;
|
||||
}
|
||||
}
|
||||
|
||||
Example of the status page configuration is the following (status service is using 8080 port):
|
||||
|
||||
.. code-block::
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name builds.example.com;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarder-Proto $scheme;
|
||||
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
}
|
||||
|
||||
Some more examples can be found in configuration `recipes <https://github.com/arcan1s/ahriman/tree/master/recipes>`__.
|
17
docs/faq/index.rst
Normal file
17
docs/faq/index.rst
Normal file
@ -0,0 +1,17 @@
|
||||
FAQ
|
||||
===
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
general
|
||||
docker
|
||||
non-x86_64-setup
|
||||
synchronization
|
||||
reporting
|
||||
distributed
|
||||
maintenance-packages
|
||||
web
|
||||
backup
|
||||
examples
|
||||
misc
|
73
docs/faq/maintenance-packages.rst
Normal file
73
docs/faq/maintenance-packages.rst
Normal file
@ -0,0 +1,73 @@
|
||||
Maintenance packages
|
||||
--------------------
|
||||
|
||||
Generate keyring package
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The application provides special plugin which generates keyring package. This plugin heavily depends on ``sign`` group settings, however it is possible to override them. The minimal package can be generated in the following way:
|
||||
|
||||
#.
|
||||
Edit configuration:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[keyring]
|
||||
target = keyring-generator
|
||||
|
||||
By default it will use ``sign.key`` as trusted key and all other keys as packagers ones. For all available options refer to :doc:`configuration </configuration>`.
|
||||
|
||||
#.
|
||||
Create package source files:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman repo-create-keyring
|
||||
|
||||
This command will generate PKGBUILD, revoked and trusted listings and keyring itself and will register the package in database.
|
||||
|
||||
#.
|
||||
Build new package as usual:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman package-add aur-clone-keyring --source local --now
|
||||
|
||||
where ``aur-clone`` is your repository name.
|
||||
|
||||
This plugin might have some issues, in case of any of them, kindly create `new issue <https://github.com/arcan1s/ahriman/issues/new/choose>`__.
|
||||
|
||||
Generate mirrorlist package
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The application provides special plugin which generates mirrorlist package also. It is possible to distribute this package as usual later. The package can be generated in the following way:
|
||||
|
||||
#.
|
||||
Edit configuration:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[mirrorlist]
|
||||
target = mirrorlist-generator
|
||||
|
||||
[mirrorlist-generator]
|
||||
servers = https://repo.example.com/$arch
|
||||
|
||||
The ``mirrorlist-generator.servers`` must contain list of available mirrors, the ``$arch`` and ``$repo`` variables are supported. For more options kindly refer to :doc:`configuration </configuration>`.
|
||||
|
||||
#.
|
||||
Create package source files:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman repo-create-mirrorlist
|
||||
|
||||
This command will generate PKGBUILD and mirrorlist file and will register the package in database.
|
||||
|
||||
#.
|
||||
Build new package as usual:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman package-add aur-clone-mirrorlist --source local --now
|
||||
|
||||
where ``aur-clone`` is your repository name.
|
100
docs/faq/misc.rst
Normal file
100
docs/faq/misc.rst
Normal file
@ -0,0 +1,100 @@
|
||||
Other topics
|
||||
------------
|
||||
|
||||
How does it differ from %another-manager%?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Short answer - I do not know. Also for some references credits to `Alad <https://github.com/AladW>`__, he `did <https://wiki.archlinux.org/title/User:Alad/Local_repo_tools>`__ really good investigation of existing alternatives.
|
||||
|
||||
`arch-repo-manager <https://github.com/Martchus/arch-repo-manager>`__
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Looks actually pretty good, in case if I would find it, I would probably didn't start this project; the most of features (like web interface or additional helpers) are already implemented or planned to be. However, this project seems to be at early alpha stage (as for Nov 2022), written in C++ (not pro or con) and misses documentation.
|
||||
|
||||
`archrepo2 <https://github.com/lilydjwg/archrepo2>`__
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Don't know, haven't tried it. But it lacks of documentation at least.
|
||||
|
||||
* ``ahriman`` has web interface.
|
||||
* ``archrepo2`` doesn't have synchronization and reporting.
|
||||
* ``archrepo2`` actively uses direct shell calls and ``yaourt`` components.
|
||||
* ``archrepo2`` has constantly running process instead of timer process (it is not pro or con).
|
||||
|
||||
`repoctl <https://github.com/cassava/repoctl>`__
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
* ``ahriman`` has web interface.
|
||||
* ``repoctl`` does not have reporting feature.
|
||||
* ``repoctl`` does not support local packages and patches.
|
||||
* Some actions are not fully automated in ``repoctl`` (e.g. package update still requires manual intervention for the build itself).
|
||||
* ``repoctl`` has better AUR interaction features. With colors!
|
||||
* ``repoctl`` has much easier configuration and even completion.
|
||||
* ``repoctl`` is able to store old packages.
|
||||
* Ability to host repository from same command in ``repoctl`` vs external services (e.g. nginx) in ``ahriman``.
|
||||
|
||||
`repod <https://gitlab.archlinux.org/archlinux/repod>`__
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Official tool provided by distribution, has clean logic, but it is just a helper for ``repo-add``, e.g. it doesn't work with AUR and all packages builds have to be handled separately.
|
||||
|
||||
`repo-scripts <https://github.com/arcan1s/repo-scripts>`__
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Though originally I've created ahriman by trying to improve the project, it still lacks a lot of features:
|
||||
|
||||
* ``ahriman`` has web interface.
|
||||
* ``ahriman`` has better reporting with template support.
|
||||
* ``ahriman`` has more synchronization features (there was only ``rsync`` based).
|
||||
* ``ahriman`` supports local packages and patches.
|
||||
* ``repo-scripts`` doesn't have dependency management.
|
||||
|
||||
...and so on. ``repo-scripts`` also has bad architecture and bad quality code and uses out-of-dated ``yaourt`` and ``package-query``.
|
||||
|
||||
`toolbox <https://github.com/chaotic-aur/toolbox>`__
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
It is automation tools for ``repoctl`` mentioned above. Except for using shell it looks pretty cool and also offers some additional features like patches, remote synchronization (isn't it?) and reporting.
|
||||
|
||||
How to check service logs
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
By default, the service writes logs to ``journald`` which can be accessed by using ``journalctl`` command (logs are written to the journal of the user under which command is run). In order to retrieve logs for the process you can use the following command:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo journalctl SYSLOG_IDENTIFIER=ahriman
|
||||
|
||||
You can also ask to forward logs to ``stderr``, just set ``--log-handler`` flag, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
ahriman --log-handler console ...
|
||||
|
||||
You can even configure logging as you wish, but kindly refer to python ``logging`` module `configuration <https://docs.python.org/3/library/logging.config.html>`__.
|
||||
|
||||
The application uses java concept to log messages, e.g. class ``Application`` imported from ``ahriman.application.application`` package will have logger called ``ahriman.application.application.Application``. In order to e.g. change logger name for whole application package it is possible to change values for ``ahriman.application`` package; thus editing ``ahriman`` logger configuration will change logging for whole application (unless there are overrides for another logger).
|
||||
|
||||
Html customization
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It is possible to customize html templates. In order to do so, create files somewhere (refer to Jinja2 documentation and the service source code for available parameters) and prepend ``templates`` with value pointing to this directory.
|
||||
|
||||
In addition, default html templates supports style customization out-of-box. In order to customize style, just put file named ``user-style.jinja2`` to the templates directory.
|
||||
|
||||
Web API extension
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The application loads web views dynamically, so it is possible relatively easy extend its API. In order to do so:
|
||||
|
||||
#. Create view class which is derived from ``ahriman.web.views.base.BaseView`` class.
|
||||
#. Create implementation for this class.
|
||||
#. Put file into ``ahriman.web.views`` package.
|
||||
#. Restart application.
|
||||
|
||||
For more details about implementation and possibilities, kindly refer to module documentation and source code and `aiohttp documentation <https://docs.aiohttp.org/en/stable/>`__.
|
||||
|
||||
I did not find my question
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
`Create an issue <https://github.com/arcan1s/ahriman/issues>`__ with type **Question**.
|
99
docs/faq/non-x86_64-setup.rst
Normal file
99
docs/faq/non-x86_64-setup.rst
Normal file
@ -0,0 +1,99 @@
|
||||
Non-x86_64 architecture setup
|
||||
-----------------------------
|
||||
|
||||
The following section describes how to setup ahriman with architecture different from x86_64, as example i686. For most cases you have base repository available, e.g. archlinux32 repositories for i686 architecture; in case if base repository is not available, steps are a bit different, however, idea remains the same.
|
||||
|
||||
The example of setup with docker compose can be found `here <https://github.com/arcan1s/ahriman/tree/master/recipes/i686>`__.
|
||||
|
||||
Physical server setup
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In this example we are going to use files and packages which are provided by official repositories of the used architecture. Note, that versions might be different, thus you need to find correct versions on the distribution web site, e.g. `archlinux32 packages <https://www.archlinux32.org/packages/>`__.
|
||||
|
||||
#.
|
||||
First, considering having base Arch Linux system, we need to install keyring for the specified repositories, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
wget https://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-20230705-1.0-any.pkg.tar.zst
|
||||
pacman -U archlinux32-keyring-20230705-1.0-any.pkg.tar.zst
|
||||
|
||||
#.
|
||||
In order to run ``devtools`` scripts for custom architecture they also need specific ``makepkg`` configuration, it can be retrieved by installing the ``devtools`` package of the distribution, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
wget https://pool.mirror.archlinux32.org/i686/extra/devtools-20221208-1.2-any.pkg.tar.zst
|
||||
pacman -U devtools-20221208-1.2-any.pkg.tar.zst
|
||||
|
||||
Alternatively, you can create your own ``makepkg`` configuration and save it as ``/usr/share/devtools/makepkg.conf.d/i686.conf``.
|
||||
|
||||
#.
|
||||
Setup repository as usual:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
ahriman -a i686 service-setup --mirror 'https://de.mirror.archlinux32.org/$arch/$repo'--no-multilib ...
|
||||
|
||||
In addition to usual options, you need to specify the following options:
|
||||
|
||||
* ``--mirror`` - link to the mirrors which will be used instead of official repositories.
|
||||
* ``--no-multilib`` - in the example we are using i686 architecture for which multilib repository doesn't exist.
|
||||
|
||||
#.
|
||||
That's all Folks!
|
||||
|
||||
Docker container setup
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There are two possible ways to achieve same setup, by using docker container. The first one is just mount required files inside container and run it as usual (with specific environment variables). Another one is to create own container based on official one:
|
||||
|
||||
#.
|
||||
Clone official container as base:
|
||||
|
||||
.. code-block:: dockerfile
|
||||
|
||||
FROM arcan1s/ahriman:latest
|
||||
|
||||
#.
|
||||
Init pacman keys. This command is required in order to populate distribution keys:
|
||||
|
||||
.. code-block:: dockerfile
|
||||
|
||||
RUN pacman-key --init
|
||||
|
||||
#.
|
||||
Install packages as it was described above:
|
||||
|
||||
.. code-block:: dockerfile
|
||||
|
||||
RUN pacman --noconfirm -Sy wget
|
||||
RUN wget https://pool.mirror.archlinux32.org/i686/extra/devtools-20221208-1.2-any.pkg.tar.zst && pacman --noconfirm -U devtools-20221208-1.2-any.pkg.tar.zst
|
||||
RUN wget https://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-20230705-1.0-any.pkg.tar.zst && pacman --noconfirm -U archlinux32-keyring-20230705-1.0-any.pkg.tar.zst
|
||||
|
||||
#.
|
||||
At that point you should have full ``Dockerfile`` like:
|
||||
|
||||
.. code-block:: dockerfile
|
||||
|
||||
FROM arcan1s/ahriman:latest
|
||||
|
||||
RUN pacman-key --init
|
||||
|
||||
RUN pacman --noconfirm -Sy wget
|
||||
RUN wget https://pool.mirror.archlinux32.org/i686/extra/devtools-20221208-1.2-any.pkg.tar.zst && pacman --noconfirm -U devtools-20221208-1.2-any.pkg.tar.zst
|
||||
RUN wget https://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-20230705-1.0-any.pkg.tar.zst && pacman --noconfirm -U archlinux32-keyring-20230705-1.0-any.pkg.tar.zst
|
||||
|
||||
#.
|
||||
After that you can build you own container, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker build --tag ahriman-i686:latest
|
||||
|
||||
#.
|
||||
Now you can run locally built container as usual with passing environment variables for setup command:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -p 8080:8080 -e AHRIMAN_ARCHITECTURE=i686 -e AHRIMAN_PACMAN_MIRROR='https://de.mirror.archlinux32.org/$arch/$repo' -e AHRIMAN_MULTILIB= ahriman-i686:latest
|
99
docs/faq/reporting.rst
Normal file
99
docs/faq/reporting.rst
Normal file
@ -0,0 +1,99 @@
|
||||
Reporting
|
||||
---------
|
||||
|
||||
How to report by email
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#.
|
||||
Install dependencies:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S --asdeps python-jinja
|
||||
|
||||
#.
|
||||
Configure the service:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[report]
|
||||
target = email
|
||||
|
||||
[email]
|
||||
host = smtp.example.com
|
||||
link_path = http://example.com/aur-clone/x86_64
|
||||
password = ...
|
||||
port = 465
|
||||
receivers = me@example.com
|
||||
sender = me@example.com
|
||||
user = me@example.com
|
||||
|
||||
How to generate index page for S3
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#.
|
||||
Install dependencies:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S --asdeps python-jinja
|
||||
|
||||
#.
|
||||
Configure the service:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[report]
|
||||
target = html
|
||||
|
||||
[html]
|
||||
path = /var/lib/ahriman/repository/aur-clone/x86_64/index.html
|
||||
link_path = http://example.com/aur-clone/x86_64
|
||||
|
||||
After these steps ``index.html`` file will be automatically synced to S3.
|
||||
|
||||
How to post build report to telegram
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#.
|
||||
It still requires additional dependencies:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S --asdeps python-jinja
|
||||
|
||||
#.
|
||||
Register bot in telegram. You can do it by starting chat with `@BotFather <https://t.me/botfather>`__. For more details please refer to `official documentation <https://core.telegram.org/bots>`__.
|
||||
|
||||
#.
|
||||
Optionally (if you want to post message in chat):
|
||||
|
||||
#. Create telegram channel.
|
||||
#. Invite your bot into the channel.
|
||||
#. Make your channel public
|
||||
|
||||
#.
|
||||
Get chat id if you want to use by numerical id or just use id prefixed with ``@`` (e.g. ``@ahriman``). If you are not using chat the chat id is your user id. If you don't want to make channel public you can use `this guide <https://stackoverflow.com/a/33862907>`__.
|
||||
|
||||
#.
|
||||
Configure the service:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[report]
|
||||
target = telegram
|
||||
|
||||
[telegram]
|
||||
api_key = aaAAbbBBccCC
|
||||
chat_id = @ahriman
|
||||
link_path = http://example.com/aur-clone/x86_64
|
||||
|
||||
``api_key`` is the one sent by `@BotFather <https://t.me/botfather>`__, ``chat_id`` is the value retrieved from previous step.
|
||||
|
||||
If you did everything fine you should receive the message with the next update. Quick credentials check can be done by using the following command:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl 'https://api.telegram.org/bot{api_key}/sendMessage?chat_id={chat_id}&text=hello'
|
||||
|
||||
(replace ``{chat_id}`` and ``{api_key}`` with the values from configuration).
|
131
docs/faq/synchronization.rst
Normal file
131
docs/faq/synchronization.rst
Normal file
@ -0,0 +1,131 @@
|
||||
Remote synchronization
|
||||
----------------------
|
||||
|
||||
How to sync repository to another server
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There are several choices:
|
||||
|
||||
#.
|
||||
Easy and cheap, just share your local files through the internet, e.g. for ``nginx``:
|
||||
|
||||
.. code-block::
|
||||
|
||||
server {
|
||||
location / {
|
||||
autoindex on;
|
||||
root /var/lib/ahriman/repository/;
|
||||
}
|
||||
}
|
||||
|
||||
#.
|
||||
You can also upload your packages using ``rsync`` to any available server. In order to use it you would need to configure ahriman first:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[upload]
|
||||
target = rsync
|
||||
|
||||
[rsync]
|
||||
remote = 192.168.0.1:/srv/repo
|
||||
|
||||
After that just add ``/srv/repo`` to the ``pacman.conf`` as usual. You can also upload to S3 (``Server = https://s3.eu-central-1.amazonaws.com/repository/aur-clone/x86_64``) or to GitHub (``Server = https://github.com/ahriman/repository/releases/download/aur-clone-x86_64``).
|
||||
|
||||
How to sync to S3
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
#.
|
||||
Install dependencies:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
pacman -S python-boto3
|
||||
|
||||
#.
|
||||
Create a bucket (e.g. ``repository``).
|
||||
|
||||
#.
|
||||
Create an user with write access to the bucket:
|
||||
|
||||
.. code-block::
|
||||
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "ListObjectsInBucket",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::repository"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sid": "AllObjectActions",
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:*Object",
|
||||
"Resource": [
|
||||
"arn:aws:s3:::repository/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
#.
|
||||
Create an API key for the user and store it.
|
||||
|
||||
#.
|
||||
Configure the service as following:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[upload]
|
||||
target = s3
|
||||
|
||||
[s3]
|
||||
access_key = ...
|
||||
bucket = repository
|
||||
region = eu-central-1
|
||||
secret_key = ...
|
||||
|
||||
S3 with SSL
|
||||
"""""""""""
|
||||
|
||||
In order to configure S3 on custom domain with SSL (and some other features, like redirects), the CloudFront should be used.
|
||||
|
||||
#. Configure S3 as described above.
|
||||
#. In bucket properties, enable static website hosting with hosting type "Host a static website".
|
||||
#. Go to AWS Certificate Manager and create public certificate on your domain. Validate domain as suggested.
|
||||
#. Go to CloudFront and create distribution. The following settings are required:
|
||||
|
||||
* Origin domain choose S3 bucket.
|
||||
* Tick use website endpoint.
|
||||
* Disable caching.
|
||||
* Select issued certificate.
|
||||
|
||||
#. Point DNS record to CloudFront address.
|
||||
|
||||
How to sync to GitHub releases
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#.
|
||||
Create a repository.
|
||||
|
||||
#.
|
||||
`Create API key <https://github.com/settings/tokens>`__ with scope ``public_repo``.
|
||||
|
||||
#.
|
||||
Configure the service as following:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[upload]
|
||||
target = github
|
||||
|
||||
[github]
|
||||
owner = ahriman
|
||||
password = ...
|
||||
repository = repository
|
||||
username = ahriman
|
145
docs/faq/web.rst
Normal file
145
docs/faq/web.rst
Normal file
@ -0,0 +1,145 @@
|
||||
Web service
|
||||
-----------
|
||||
|
||||
How to setup web service
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#.
|
||||
Install dependencies:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S --asdeps python-aiohttp python-aiohttp-jinja2 python-aiohttp-apispec>=3.0.0 python-aiohttp-cors
|
||||
|
||||
#.
|
||||
Configure service:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[web]
|
||||
port = 8080
|
||||
|
||||
#.
|
||||
Start the web service ``systemctl enable --now ahriman-web``.
|
||||
|
||||
How to enable basic authorization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#.
|
||||
Install dependencies 😊:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S --asdeps python-aiohttp-security python-aiohttp-session python-cryptography
|
||||
|
||||
#.
|
||||
Configure the service to enable authorization:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[auth]
|
||||
target = configuration
|
||||
salt = somerandomstring
|
||||
|
||||
The ``salt`` parameter is optional, but recommended, and can be set to any (random) string.
|
||||
|
||||
#.
|
||||
In order to provide access for reporting from application instances you can (the recommended way) use unix sockets by the following configuration (note, that it requires ``python-requests-unixsocket2`` package to be installed):
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[web]
|
||||
unix_socket = /run/ahriman/ahriman-web.sock
|
||||
|
||||
This socket path must be available for web service instance and must be available for all application instances (e.g. in case if you are using docker container - see above - you need to make sure that the socket is passed to the root filesystem).
|
||||
|
||||
By the way, unix socket variable will be automatically set in case if ``--web-unix-socket`` argument is supplied to the ``setup`` subcommand.
|
||||
|
||||
Alternatively, you need to create user for the service:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman user-add -r full api
|
||||
|
||||
This command will ask for the password, just type it in stdin; **do not** leave the field blank, user will not be able to authorize, and finally configure the application:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[status]
|
||||
username = api
|
||||
password = pa55w0rd
|
||||
|
||||
#.
|
||||
Create end-user with password:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman user-add -r full my-first-user
|
||||
|
||||
#.
|
||||
Restart web service ``systemctl restart ahriman-web``.
|
||||
|
||||
Using PAM authentication
|
||||
""""""""""""""""""""""""
|
||||
|
||||
There is also ability to allow system users to log in. To do so, the following configuration have to be set:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[auth]
|
||||
target = pam
|
||||
full_access_group = wheel
|
||||
|
||||
With this setup, every user (except root) will be able to log in by using system password. If user belongs to the ``wheel`` group, the full access will be automatically granted. It is also possible to manually add, block user or change user rights via usual user management process.
|
||||
|
||||
How to enable OAuth authorization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#.
|
||||
Create OAuth web application, download its ``client_id`` and ``client_secret``.
|
||||
|
||||
#.
|
||||
Guess what? Install dependencies:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S --asdeps python-aiohttp-security python-aiohttp-session python-cryptography python-aioauth-client
|
||||
|
||||
#.
|
||||
Configure the service:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[auth]
|
||||
target = oauth
|
||||
client_id = ...
|
||||
client_secret = ...
|
||||
|
||||
[web]
|
||||
address = https://example.com
|
||||
|
||||
Configure ``oauth_provider`` and ``oauth_scopes`` in case if you would like to use different from Google provider. Scope must grant access to user email. ``web.address`` is required to make callback URL available from internet.
|
||||
|
||||
#.
|
||||
If you are not going to use unix socket, you also need to create service user (remember to set ``auth.salt`` option before if required):
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman user-add --as-service -r full api
|
||||
|
||||
#.
|
||||
Create end-user:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo -u ahriman ahriman user-add -r full my-first-user
|
||||
|
||||
When it will ask for the password leave it blank.
|
||||
|
||||
#.
|
||||
Restart web service ``systemctl restart ahriman-web``.
|
||||
|
||||
How to implement own interface
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can write your own interface by using API which is provided by the web service. Full autogenerated API documentation is available at ``http://localhost:8080/api-docs``.
|
@ -33,7 +33,7 @@ Contents
|
||||
setup
|
||||
configuration
|
||||
command-line
|
||||
faq
|
||||
faq/index
|
||||
migration
|
||||
architecture
|
||||
advanced-usage
|
||||
|
@ -1,13 +1,13 @@
|
||||
# Maintainer: Evgeniy Alekseev
|
||||
|
||||
pkgname='ahriman'
|
||||
pkgver=2.13.3
|
||||
pkgver=2.14.1
|
||||
pkgrel=1
|
||||
pkgdesc="ArcH linux ReposItory MANager"
|
||||
arch=('any')
|
||||
url="https://github.com/arcan1s/ahriman"
|
||||
license=('GPL3')
|
||||
depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-cerberus' 'python-inflection' 'python-passlib' 'python-requests' 'python-srcinfo')
|
||||
depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-cerberus' 'python-inflection' 'python-passlib' 'python-pyelftools' 'python-requests' 'python-srcinfo')
|
||||
makedepends=('python-build' 'python-flit' 'python-installer' 'python-wheel')
|
||||
optdepends=('breezy: -bzr packages support'
|
||||
'darcs: -darcs packages support'
|
||||
@ -21,7 +21,7 @@ optdepends=('breezy: -bzr packages support'
|
||||
'python-aiohttp-session: web server with authorization'
|
||||
'python-boto3: sync to s3'
|
||||
'python-cryptography: web server with authorization'
|
||||
'python-requests-unixsocket: client report to web server by unix socket'
|
||||
'python-requests-unixsocket2: client report to web server by unix socket'
|
||||
'python-jinja: html report generation'
|
||||
'python-systemd: journal support'
|
||||
'rsync: sync by using rsync'
|
||||
|
@ -1 +1,2 @@
|
||||
d /var/lib/ahriman 0755 ahriman ahriman
|
||||
d /var/lib/ahriman 0755 ahriman ahriman
|
||||
d /run/ahriman 0755 ahriman ahriman
|
@ -17,6 +17,8 @@ mirror = https://geo.mirror.pkgbuild.com/$repo/os/$arch
|
||||
repositories = core extra multilib
|
||||
; Pacman's root directory. In the most cases it must point to the system root.
|
||||
root = /
|
||||
; Sync files databases too, which is required by deep dependencies check
|
||||
sync_files_database = yes
|
||||
; Use local packages cache. If this option is enabled, the service will be able to synchronize databases (available
|
||||
; as additional option for some subcommands). If set to no, databases must be synchronized manually.
|
||||
use_ahriman_cache = yes
|
||||
@ -32,6 +34,8 @@ allow_read_only = yes
|
||||
; Cookie secret key to be used for cookies encryption. Must be valid 32 bytes URL-safe base64-encoded string.
|
||||
; If not set, it will be generated automatically.
|
||||
;cookie_secret_key =
|
||||
; Name of the secondary group to be used as admin group in the service.
|
||||
;full_access_group = wheel
|
||||
; Authentication cookie expiration in seconds.
|
||||
;max_age = 604800
|
||||
; OAuth2 provider icon for the web interface.
|
||||
@ -40,14 +44,24 @@ allow_read_only = yes
|
||||
;oauth_provider = GoogleClient
|
||||
; Scopes list for OAuth2 provider. Required if oauth is used.
|
||||
;oauth_scopes = https://www.googleapis.com/auth/userinfo.email
|
||||
; Allow login as root user (only if PAM is used).
|
||||
;permit_root_login = no
|
||||
; Optional password salt.
|
||||
;salt =
|
||||
|
||||
[build]
|
||||
; List of paths to be used for implicit dependency scan
|
||||
allowed_scan_paths = /usr/lib
|
||||
; List of additional flags passed to archbuild command.
|
||||
;archbuild_flags =
|
||||
; List of paths to be excluded for implicit dependency scan. Usually they should be subpaths of allowed_scan_paths
|
||||
blacklisted_scan_paths = /usr/lib/cmake
|
||||
; Path to build command
|
||||
;build_command =
|
||||
; List of packages to be ignored during automatic updates.
|
||||
;ignore_packages =
|
||||
; Include debug packages
|
||||
;include_debug_packages = yes
|
||||
; List of additional flags passed to makechrootpkg command.
|
||||
;makechrootpkg_flags =
|
||||
; List of additional flags passed to makepkg command.
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<!-- Embed elements Elements via Web Component -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@stoplight/elements@7.13.7/web-components.min.js" integrity="sha384-aKMPitODat9Dqj3Mva9Rs9jS5Z3KPSW0sFlAOazuULJMFYhAfmORI5SlH9aWIst8" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@stoplight/elements@7.13.7/styles.min.css" integrity="sha384-wPzTs1aFAoGq9gqp9NAs2YVTkFXcU2d6Bx11aKRFhVw2B7o1bCwaV9pGHTlUfD2+" crossorigin="anonymous" type="text/css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/@stoplight/elements@7.13.7/web-components.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@stoplight/elements@7.13.7/styles.min.css" crossorigin="anonymous" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
alertPlaceholder.append(wrapper);
|
||||
const toast = new bootstrap.Toast(wrapper);
|
||||
wrapper.addEventListener("hidden.bs.toast", () => {
|
||||
wrapper.addEventListener("hidden.bs.toast", _ => {
|
||||
wrapper.remove(); // bootstrap doesn't remove elements
|
||||
(action || reload)();
|
||||
});
|
||||
|
@ -38,10 +38,6 @@
|
||||
<script>
|
||||
const keyImportModal = $("#key-import-modal");
|
||||
const keyImportForm = $("#key-import-form");
|
||||
keyImportModal.on("hidden.bs.modal", () => {
|
||||
keyImportBodyInput.text("");
|
||||
keyImportForm.trigger("reset");
|
||||
});
|
||||
|
||||
const keyImportBodyInput = $("#key-import-body-input");
|
||||
const keyImportCopyButton = $("#key-import-copy-button");
|
||||
@ -90,4 +86,11 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(_ => {
|
||||
keyImportModal.on("hidden.bs.modal", _ => {
|
||||
keyImportBodyInput.text("");
|
||||
keyImportForm.trigger("reset");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -36,9 +36,6 @@
|
||||
<script>
|
||||
const loginModal = $("#login-modal");
|
||||
const loginForm = $("#login-form");
|
||||
loginModal.on("hidden.bs.modal", () => {
|
||||
loginForm.trigger("reset");
|
||||
});
|
||||
|
||||
const loginPasswordInput = $("#login-password");
|
||||
const loginUsernameInput = $("#login-username");
|
||||
@ -56,10 +53,13 @@
|
||||
contentType: "application/json",
|
||||
success: _ => {
|
||||
loginModal.modal("hide");
|
||||
showSuccess("Logged in", `Successfully logged in as ${username}`, () => location.href = "/");
|
||||
showSuccess("Logged in", `Successfully logged in as ${username}`, _ => location.href = "/");
|
||||
},
|
||||
error: (jqXHR, _, errorThrown) => {
|
||||
const message = _ => `Could not login as ${username}`;
|
||||
const message = _ =>
|
||||
username === "admin" && password === "admin"
|
||||
? "You've entered a password for user \"root\", did you make a typo in username?"
|
||||
: `Could not login as ${username}`;
|
||||
showFailure("Login error", message, jqXHR, errorThrown);
|
||||
},
|
||||
});
|
||||
@ -77,4 +77,10 @@
|
||||
showHidePasswordButton.addClass("bi-eye");
|
||||
}
|
||||
}
|
||||
|
||||
$(_ => {
|
||||
loginModal.on("hidden.bs.modal", _ => {
|
||||
loginForm.trigger("reset");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -43,41 +43,10 @@
|
||||
<script>
|
||||
const packageAddModal = $("#package-add-modal");
|
||||
const packageAddForm = $("#package-add-form");
|
||||
packageAddModal.on("shown.bs.modal", () => {
|
||||
$(`#package-add-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
|
||||
});
|
||||
packageAddModal.on("hidden.bs.modal", () => {
|
||||
packageAddVariablesDiv.empty();
|
||||
packageAddForm.trigger("reset");
|
||||
});
|
||||
|
||||
const packageAddInput = $("#package-add-input");
|
||||
const packageAddRepositoryInput = $("#package-add-repository-input");
|
||||
const packageAddKnownPackagesList = $("#package-add-known-packages-dlist");
|
||||
packageAddInput.keyup(() => {
|
||||
clearTimeout(packageAddInput.data("timeout"));
|
||||
packageAddInput.data("timeout", setTimeout($.proxy(() => {
|
||||
const value = packageAddInput.val();
|
||||
|
||||
if (value.length >= 3) {
|
||||
$.ajax({
|
||||
url: "/api/v1/service/search",
|
||||
data: {"for": value},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
const options = response.map(pkg => {
|
||||
const option = document.createElement("option");
|
||||
option.value = pkg.package;
|
||||
option.innerText = `${pkg.package} (${pkg.description})`;
|
||||
return option;
|
||||
});
|
||||
packageAddKnownPackagesList.empty().append(options);
|
||||
},
|
||||
});
|
||||
}
|
||||
}, this), 500));
|
||||
});
|
||||
|
||||
const packageAddVariablesDiv = $("#package-add-variables-div");
|
||||
|
||||
@ -156,4 +125,39 @@
|
||||
doPackageAction("/api/v1/service/request", [packages], repository, onSuccess, onFailure, patches);
|
||||
}
|
||||
}
|
||||
|
||||
$(_ => {
|
||||
packageAddModal.on("shown.bs.modal", _ => {
|
||||
$(`#package-add-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
|
||||
});
|
||||
packageAddModal.on("hidden.bs.modal", _ => {
|
||||
packageAddVariablesDiv.empty();
|
||||
packageAddForm.trigger("reset");
|
||||
});
|
||||
|
||||
packageAddInput.keyup(_ => {
|
||||
clearTimeout(packageAddInput.data("timeout"));
|
||||
packageAddInput.data("timeout", setTimeout($.proxy(_ => {
|
||||
const value = packageAddInput.val();
|
||||
|
||||
if (value.length >= 3) {
|
||||
$.ajax({
|
||||
url: "/api/v1/service/search",
|
||||
data: {"for": value},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
const options = response.map(pkg => {
|
||||
const option = document.createElement("option");
|
||||
option.value = pkg.package;
|
||||
option.innerText = `${pkg.package} (${pkg.description})`;
|
||||
return option;
|
||||
});
|
||||
packageAddKnownPackagesList.empty().append(options);
|
||||
},
|
||||
});
|
||||
}
|
||||
}, this), 500));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -72,26 +72,6 @@
|
||||
const packageInfoModal = $("#package-info-modal");
|
||||
const packageInfoModalHeader = $("#package-info-modal-header");
|
||||
const packageInfo = $("#package-info");
|
||||
packageInfoModal.on("hidden.bs.modal", () => {
|
||||
packageInfoAurUrl.empty();
|
||||
packageInfoDepends.empty();
|
||||
packageInfoGroups.empty();
|
||||
packageInfoLicenses.empty();
|
||||
packageInfoPackager.empty();
|
||||
packageInfoPackages.empty();
|
||||
packageInfoUpstreamUrl.empty();
|
||||
packageInfoVersion.empty();
|
||||
|
||||
packageInfoVariablesBlock.attr("hidden", true);
|
||||
packageInfoVariablesDiv.empty();
|
||||
|
||||
packageInfoLogsInput.empty();
|
||||
packageInfoChangesInput.empty();
|
||||
|
||||
packageInfoModal.trigger("reset");
|
||||
|
||||
hideInfoControls(true);
|
||||
});
|
||||
|
||||
const packageInfoLogsInput = $("#package-info-logs-input");
|
||||
const packageInfoLogsCopyButton = $("#package-info-logs-copy-button");
|
||||
@ -309,4 +289,27 @@
|
||||
|
||||
if (isPackageBaseSet) packageInfoModal.modal("show");
|
||||
}
|
||||
|
||||
$(_ => {
|
||||
packageInfoModal.on("hidden.bs.modal", _ => {
|
||||
packageInfoAurUrl.empty();
|
||||
packageInfoDepends.empty();
|
||||
packageInfoGroups.empty();
|
||||
packageInfoLicenses.empty();
|
||||
packageInfoPackager.empty();
|
||||
packageInfoPackages.empty();
|
||||
packageInfoUpstreamUrl.empty();
|
||||
packageInfoVersion.empty();
|
||||
|
||||
packageInfoVariablesBlock.attr("hidden", true);
|
||||
packageInfoVariablesDiv.empty();
|
||||
|
||||
packageInfoLogsInput.empty();
|
||||
packageInfoChangesInput.empty();
|
||||
|
||||
packageInfoModal.trigger("reset");
|
||||
|
||||
hideInfoControls(true);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -35,11 +35,6 @@
|
||||
<script>
|
||||
const packageRebuildModal = $("#package-rebuild-modal");
|
||||
const packageRebuildForm = $("#package-rebuild-form");
|
||||
packageRebuildModal.on("shown.bs.modal", () => {
|
||||
$(`#package-rebuild-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
|
||||
|
||||
});
|
||||
packageRebuildModal.on("hidden.bs.modal", () => { packageRebuildForm.trigger("reset"); });
|
||||
|
||||
const packageRebuildDependencyInput = $("#package-rebuild-dependency-input");
|
||||
const packageRebuildRepositoryInput = $("#package-rebuild-repository-input");
|
||||
@ -54,4 +49,12 @@
|
||||
doPackageAction("/api/v1/service/rebuild", [packages], repository, onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
|
||||
$(_ => {
|
||||
packageRebuildModal.on("shown.bs.modal", _ => {
|
||||
$(`#package-rebuild-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
|
||||
|
||||
});
|
||||
packageRebuildModal.on("hidden.bs.modal", _ => { packageRebuildForm.trigger("reset"); });
|
||||
});
|
||||
</script>
|
||||
|
@ -9,46 +9,8 @@
|
||||
const packageInfoUpdateButton = $("#package-info-update-button");
|
||||
|
||||
let repository = null;
|
||||
$("#repositories a").on("click", (event) => {
|
||||
const element = event.target;
|
||||
repository = {
|
||||
architecture: element.dataset.architecture,
|
||||
repository: element.dataset.repository,
|
||||
};
|
||||
packageUpdateButton.html(`<i class="bi bi-play"></i> update<span class="d-none d-sm-inline"> ${safe(repository.repository)} (${safe(repository.architecture)})</span>`);
|
||||
$(`#${element.id}`).tab("show");
|
||||
reload();
|
||||
});
|
||||
|
||||
const table = $("#packages");
|
||||
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", () => {
|
||||
packageRemoveButton.prop("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", () => {
|
||||
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
|
||||
pickerInput.daterangepicker({
|
||||
autoUpdateInput: false,
|
||||
locale: {
|
||||
cancelLabel: "Clear",
|
||||
},
|
||||
});
|
||||
|
||||
pickerInput.on("apply.daterangepicker", (event, picker) => {
|
||||
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
|
||||
table.bootstrapTable("triggerSearch");
|
||||
});
|
||||
|
||||
pickerInput.on("cancel.daterangepicker", () => {
|
||||
pickerInput.val("");
|
||||
table.bootstrapTable("triggerSearch");
|
||||
});
|
||||
});
|
||||
|
||||
const statusBadge = $("#badge-status");
|
||||
const versionBadge = $("#badge-version");
|
||||
@ -220,9 +182,48 @@
|
||||
return {classes: cellClass(value)};
|
||||
}
|
||||
|
||||
$(() => {
|
||||
table.bootstrapTable({});
|
||||
$(_ => {
|
||||
$("#repositories a").on("click", event => {
|
||||
const element = event.target;
|
||||
repository = {
|
||||
architecture: element.dataset.architecture,
|
||||
repository: element.dataset.repository,
|
||||
};
|
||||
packageUpdateButton.html(`<i class="bi bi-play"></i> update<span class="d-none d-sm-inline"> ${safe(repository.repository)} (${safe(repository.architecture)})</span>`);
|
||||
$(`#${element.id}`).tab("show");
|
||||
reload();
|
||||
});
|
||||
|
||||
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", _ => {
|
||||
packageRemoveButton.prop("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", _ => {
|
||||
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
|
||||
pickerInput.daterangepicker({
|
||||
autoUpdateInput: false,
|
||||
locale: {
|
||||
cancelLabel: "Clear",
|
||||
},
|
||||
});
|
||||
|
||||
pickerInput.on("apply.daterangepicker", (event, picker) => {
|
||||
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
|
||||
table.bootstrapTable("triggerSearch");
|
||||
});
|
||||
|
||||
pickerInput.on("cancel.daterangepicker", _ => {
|
||||
pickerInput.val("");
|
||||
table.bootstrapTable("triggerSearch");
|
||||
});
|
||||
});
|
||||
|
||||
statusBadge.popover();
|
||||
selectRepository();
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
@ -102,25 +102,6 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
||||
|
||||
<script>
|
||||
const table = $("#packages");
|
||||
table.on("created-controls.bs.table", () => {
|
||||
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
|
||||
pickerInput.daterangepicker({
|
||||
autoUpdateInput: false,
|
||||
locale: {
|
||||
cancelLabel: "Clear",
|
||||
},
|
||||
});
|
||||
|
||||
pickerInput.on("apply.daterangepicker", (event, picker) => {
|
||||
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
|
||||
table.bootstrapTable("triggerSearch");
|
||||
});
|
||||
|
||||
pickerInput.on("cancel.daterangepicker", () => {
|
||||
pickerInput.val("");
|
||||
table.bootstrapTable("triggerSearch");
|
||||
});
|
||||
});
|
||||
|
||||
const pacmanConf = $("#pacman-conf");
|
||||
const pacmanConfCopyButton = $("#copy-btn");
|
||||
@ -141,6 +122,28 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
||||
function filterListLicenses() {
|
||||
return extractDataList(table.bootstrapTable("getData"), "licenses");
|
||||
}
|
||||
|
||||
$(_ => {
|
||||
table.on("created-controls.bs.table", _ => {
|
||||
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
|
||||
pickerInput.daterangepicker({
|
||||
autoUpdateInput: false,
|
||||
locale: {
|
||||
cancelLabel: "Clear",
|
||||
},
|
||||
});
|
||||
|
||||
pickerInput.on("apply.daterangepicker", (event, picker) => {
|
||||
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
|
||||
table.bootstrapTable("triggerSearch");
|
||||
});
|
||||
|
||||
pickerInput.on("cancel.daterangepicker", _ => {
|
||||
pickerInput.val("");
|
||||
table.bootstrapTable("triggerSearch");
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
@ -1,21 +1,21 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" integrity="sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<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/moment@2.29.4/moment.min.js" integrity="sha384-8hHkOkbWN1TLWwet/jpbJ0zbx3FJDeYJgQ8dX1mRrv/vfCfHCqFSFZYCgaMML3z9" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.min.js" integrity="sha384-u4eJN1VWrTf/FnYYQJo2kqJyVxEQf5UmWY4iUcNAoLenOEtEuCkfwc5bKvZOWBi5" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.28.0/tableExport.min.js" integrity="sha384-1Rz4Kz/y1rSWw+ZsjTcxB684XgofbO8iizY+UFIzCwFeQ+QUyhBNWBMh/STOyomI" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.28.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" integrity="sha384-IazMVNyYoUNx6357fWJoqtHYUWWCNHIXxFVtbpVgvImQNWuRP2WbHPaIb3QF8j97" 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" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/bootstrap-table.min.js" integrity="sha384-GVLHfbEvuGA/RFiQ3MK2ClEJkWYJXABg55t9LpoDPZFGIsSq8xhFlQydm5poV2jW" 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.2/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/export/bootstrap-table-export.min.js" integrity="sha384-g9OAB1Moamcy8+l1Q/tajHlMf6NTkS79ehKLTYbA80aQRbRhFCjrSuezv+FE2Kwe" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/resizable/bootstrap-table-resizable.js" integrity="sha384-wd8Vc6Febikdnsnk9vthRWRvMwffw246vhqiqNO3aSNe1maTEA07Vh3zAQiSyDji" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" integrity="sha384-NIqcjpr/3eZI1iNzz7hgT5rgp70qFUzkZffeCgVva9gi80B5vqcm7gn+8QvlWxko" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.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.22.1/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js" integrity="sha384-F/bZzf7p3Joyp5psL90p/p89AZJsndkSoGwRpXcZhleCWhd8SnRuoYo4d0yirjJp" crossorigin="anonymous" type="application/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||
|
||||
<script>
|
||||
async function copyToClipboard(text, button) {
|
||||
|
@ -1,17 +1,17 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" integrity="sha384-4LISF5TTJX/fLmGSxO53rV4miRxdg84mZsxmO8Rx5jGtp/LbrixFETvWa5a6sESd" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/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-table@1.22.1/dist/bootstrap-table.min.css" integrity="sha384-sN3NwxbjH33ZidqZnPmX+nQ5IF+LoiI7HvZSoZj5wGacmu0/q4RJfsN0xqN+LIa5" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.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" integrity="sha384-1sLxvR8mXzjhvFY9f8mzXl97DNLepeZ0PnRiMMdm/rQsKjsrPZPJxYle2wwT2PMg" 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.22.1/dist/extensions/filter-control/bootstrap-table-filter-control.css" integrity="sha384-4Glx18jZ0Un+yDG6KUpYJ/af8hkssJ02jRASuFv23gfCl0mTXaVMPI9cB4cn3GvE" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.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.2/dist/cosmo/bootstrap.min.css" integrity="sha384-RfV5VNj9uqyOdZbN0hFNmoq56291KK2Y4iKhoRAbcfBfjYlpasjxK6TefPjxiAiN" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.2/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.css" integrity="sha384-zLkQsiLfAQqGeIJeKLC+rcCR1YoYaQFLCL7cLDUoKE1ajKJzySpjzWGfYS2vjSG+" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.css" crossorigin="anonymous" type="text/css">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css" integrity="sha384-eFTL69TLRZTkNfYZOLM+G04821K1qZao/4QLJbet1pP4tcF+fdXq/9CdqAbWRl/L" crossorigin="anonymous" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css" crossorigin="anonymous" type="text/css">
|
||||
|
||||
<style>
|
||||
.pre-scrollable {
|
||||
|
@ -27,12 +27,12 @@ _shtab_ahriman_patch_list_option_strings=('-h' '--help' '-e' '--exit-code' '-v'
|
||||
_shtab_ahriman_patch_remove_option_strings=('-h' '--help' '-v' '--variable')
|
||||
_shtab_ahriman_patch_set_add_option_strings=('-h' '--help' '-t' '--track')
|
||||
_shtab_ahriman_repo_backup_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_check_option_strings=('-h' '--help' '--changes' '--no-changes' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_check_option_strings=('-h' '--help' '--changes' '--no-changes' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_repo_check_option_strings=('-h' '--help' '--changes' '--no-changes' '--check-files' '--no-check-files' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_check_option_strings=('-h' '--help' '--changes' '--no-changes' '--check-files' '--no-check-files' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_repo_create_keyring_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_create_mirrorlist_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '--dry-run' '--increment' '--no-increment' '--local' '--no-local' '--manual' '--no-manual' '--partitions' '--no-partitions' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '--dry-run' '--increment' '--no-increment' '--local' '--no-local' '--manual' '--no-manual' '--partitions' '--no-partitions' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_repo_daemon_option_strings=('-h' '--help' '-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' '--refresh')
|
||||
_shtab_ahriman_daemon_option_strings=('-h' '--help' '-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' '--refresh')
|
||||
_shtab_ahriman_repo_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '--increment' '--no-increment' '-e' '--exit-code' '-s' '--status' '-u' '--username')
|
||||
_shtab_ahriman_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '--increment' '--no-increment' '-e' '--exit-code' '-s' '--status' '-u' '--username')
|
||||
_shtab_ahriman_repo_remove_unknown_option_strings=('-h' '--help' '--dry-run')
|
||||
@ -47,8 +47,8 @@ _shtab_ahriman_repo_sync_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_sync_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_tree_option_strings=('-h' '--help' '-p' '--partitions')
|
||||
_shtab_ahriman_repo_triggers_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_repo_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--increment' '--no-increment' '--local' '--no-local' '--manual' '--no-manual' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--increment' '--no-increment' '--local' '--no-local' '--manual' '--no-manual' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_repo_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--changes' '--no-changes' '--check-files' '--no-check-files' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--increment' '--no-increment' '--local' '--no-local' '--manual' '--no-manual' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--changes' '--no-changes' '--check-files' '--no-check-files' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--increment' '--no-increment' '--local' '--no-local' '--manual' '--no-manual' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh')
|
||||
_shtab_ahriman_service_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_repo_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
@ -243,6 +243,8 @@ _shtab_ahriman_repo_check__h_nargs=0
|
||||
_shtab_ahriman_repo_check___help_nargs=0
|
||||
_shtab_ahriman_repo_check___changes_nargs=0
|
||||
_shtab_ahriman_repo_check___no_changes_nargs=0
|
||||
_shtab_ahriman_repo_check___check_files_nargs=0
|
||||
_shtab_ahriman_repo_check___no_check_files_nargs=0
|
||||
_shtab_ahriman_repo_check__e_nargs=0
|
||||
_shtab_ahriman_repo_check___exit_code_nargs=0
|
||||
_shtab_ahriman_repo_check___vcs_nargs=0
|
||||
@ -254,6 +256,8 @@ _shtab_ahriman_check__h_nargs=0
|
||||
_shtab_ahriman_check___help_nargs=0
|
||||
_shtab_ahriman_check___changes_nargs=0
|
||||
_shtab_ahriman_check___no_changes_nargs=0
|
||||
_shtab_ahriman_check___check_files_nargs=0
|
||||
_shtab_ahriman_check___no_check_files_nargs=0
|
||||
_shtab_ahriman_check__e_nargs=0
|
||||
_shtab_ahriman_check___exit_code_nargs=0
|
||||
_shtab_ahriman_check___vcs_nargs=0
|
||||
@ -270,6 +274,8 @@ _shtab_ahriman_repo_daemon___aur_nargs=0
|
||||
_shtab_ahriman_repo_daemon___no_aur_nargs=0
|
||||
_shtab_ahriman_repo_daemon___changes_nargs=0
|
||||
_shtab_ahriman_repo_daemon___no_changes_nargs=0
|
||||
_shtab_ahriman_repo_daemon___check_files_nargs=0
|
||||
_shtab_ahriman_repo_daemon___no_check_files_nargs=0
|
||||
_shtab_ahriman_repo_daemon___dependencies_nargs=0
|
||||
_shtab_ahriman_repo_daemon___no_dependencies_nargs=0
|
||||
_shtab_ahriman_repo_daemon___dry_run_nargs=0
|
||||
@ -291,6 +297,8 @@ _shtab_ahriman_daemon___aur_nargs=0
|
||||
_shtab_ahriman_daemon___no_aur_nargs=0
|
||||
_shtab_ahriman_daemon___changes_nargs=0
|
||||
_shtab_ahriman_daemon___no_changes_nargs=0
|
||||
_shtab_ahriman_daemon___check_files_nargs=0
|
||||
_shtab_ahriman_daemon___no_check_files_nargs=0
|
||||
_shtab_ahriman_daemon___dependencies_nargs=0
|
||||
_shtab_ahriman_daemon___no_dependencies_nargs=0
|
||||
_shtab_ahriman_daemon___dry_run_nargs=0
|
||||
@ -358,6 +366,8 @@ _shtab_ahriman_repo_update___aur_nargs=0
|
||||
_shtab_ahriman_repo_update___no_aur_nargs=0
|
||||
_shtab_ahriman_repo_update___changes_nargs=0
|
||||
_shtab_ahriman_repo_update___no_changes_nargs=0
|
||||
_shtab_ahriman_repo_update___check_files_nargs=0
|
||||
_shtab_ahriman_repo_update___no_check_files_nargs=0
|
||||
_shtab_ahriman_repo_update___dependencies_nargs=0
|
||||
_shtab_ahriman_repo_update___no_dependencies_nargs=0
|
||||
_shtab_ahriman_repo_update___dry_run_nargs=0
|
||||
@ -380,6 +390,8 @@ _shtab_ahriman_update___aur_nargs=0
|
||||
_shtab_ahriman_update___no_aur_nargs=0
|
||||
_shtab_ahriman_update___changes_nargs=0
|
||||
_shtab_ahriman_update___no_changes_nargs=0
|
||||
_shtab_ahriman_update___check_files_nargs=0
|
||||
_shtab_ahriman_update___no_check_files_nargs=0
|
||||
_shtab_ahriman_update___dependencies_nargs=0
|
||||
_shtab_ahriman_update___no_dependencies_nargs=0
|
||||
_shtab_ahriman_update___dry_run_nargs=0
|
||||
@ -584,7 +596,7 @@ _set_new_action() {
|
||||
current_action_nargs=1
|
||||
fi
|
||||
|
||||
current_action_args_start_index=$(( $word_index + 1 ))
|
||||
current_action_args_start_index=$(( $word_index + 1 - $pos_only ))
|
||||
|
||||
current_action_is_positional=$2
|
||||
}
|
||||
@ -611,6 +623,7 @@ _shtab_ahriman() {
|
||||
|
||||
local prefix=_shtab_ahriman
|
||||
local word_index=0
|
||||
local pos_only=0 # "--" delimeter not encountered yet
|
||||
_set_parser_defaults
|
||||
word_index=1
|
||||
|
||||
@ -619,26 +632,30 @@ _shtab_ahriman() {
|
||||
while [ $word_index -ne $COMP_CWORD ]; do
|
||||
local this_word="${COMP_WORDS[$word_index]}"
|
||||
|
||||
if [[ -n $sub_parsers && " ${sub_parsers[@]} " == *" ${this_word} "* ]]; then
|
||||
# valid subcommand: add it to the prefix & reset the current action
|
||||
prefix="${prefix}_$(_shtab_replace_nonword $this_word)"
|
||||
_set_parser_defaults
|
||||
fi
|
||||
if [[ $pos_only = 1 || " $this_word " != " -- " ]]; then
|
||||
if [[ -n $sub_parsers && " ${sub_parsers[@]} " == *" ${this_word} "* ]]; then
|
||||
# valid subcommand: add it to the prefix & reset the current action
|
||||
prefix="${prefix}_$(_shtab_replace_nonword $this_word)"
|
||||
_set_parser_defaults
|
||||
fi
|
||||
|
||||
if [[ " ${current_option_strings[@]} " == *" ${this_word} "* ]]; then
|
||||
# a new action should be acquired (due to recognised option string or
|
||||
# no more input expected from current action);
|
||||
# the next positional action can fill in here
|
||||
_set_new_action $this_word false
|
||||
fi
|
||||
if [[ " ${current_option_strings[@]} " == *" ${this_word} "* ]]; then
|
||||
# a new action should be acquired (due to recognised option string or
|
||||
# no more input expected from current action);
|
||||
# the next positional action can fill in here
|
||||
_set_new_action $this_word false
|
||||
fi
|
||||
|
||||
if [[ "$current_action_nargs" != "*" ]] && \
|
||||
[[ "$current_action_nargs" != "+" ]] && \
|
||||
[[ "$current_action_nargs" != *"..." ]] && \
|
||||
(( $word_index + 1 - $current_action_args_start_index >= \
|
||||
$current_action_nargs )); then
|
||||
$current_action_is_positional && let "completed_positional_actions += 1"
|
||||
_set_new_action "pos_${completed_positional_actions}" true
|
||||
if [[ "$current_action_nargs" != "*" ]] && \
|
||||
[[ "$current_action_nargs" != "+" ]] && \
|
||||
[[ "$current_action_nargs" != *"..." ]] && \
|
||||
(( $word_index + 1 - $current_action_args_start_index - $pos_only >= \
|
||||
$current_action_nargs )); then
|
||||
$current_action_is_positional && let "completed_positional_actions += 1"
|
||||
_set_new_action "pos_${completed_positional_actions}" true
|
||||
fi
|
||||
else
|
||||
pos_only=1 # "--" delimeter encountered
|
||||
fi
|
||||
|
||||
let "word_index+=1"
|
||||
@ -646,7 +663,7 @@ _shtab_ahriman() {
|
||||
|
||||
# Generate the completions
|
||||
|
||||
if [[ "${completing_word}" == -* ]]; then
|
||||
if [[ $pos_only = 0 && "${completing_word}" == -* ]]; then
|
||||
# optional argument started: use option strings
|
||||
COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") )
|
||||
else
|
||||
|
@ -1,4 +1,4 @@
|
||||
.TH AHRIMAN "1" "2024\-01\-12" "ahriman" "Generated Python Manual"
|
||||
.TH AHRIMAN "1" "2024\-09\-04" "ahriman" "Generated Python Manual"
|
||||
.SH NAME
|
||||
ahriman
|
||||
.SH SYNOPSIS
|
||||
@ -391,7 +391,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
|
||||
|
||||
@ -447,7 +447,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] [\-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
|
||||
|
||||
@ -460,6 +462,10 @@ filter check by package base
|
||||
\fB\-\-changes\fR, \fB\-\-no\-changes\fR
|
||||
calculate changes from the latest known commit if available. Only applicable in dry run mode
|
||||
|
||||
.TP
|
||||
\fB\-\-check\-files\fR, \fB\-\-no\-check\-files\fR
|
||||
enable or disable checking of broken dependencies (e.g. dynamically linked libraries or modules directories)
|
||||
|
||||
.TP
|
||||
\fB\-e\fR, \fB\-\-exit\-code\fR
|
||||
return non\-zero exit status if result is empty
|
||||
@ -484,9 +490,9 @@ create package which contains list of available mirrors as set by configuration.
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR
|
||||
usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes]
|
||||
[\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-\-increment | \-\-no\-increment]
|
||||
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-\-partitions | \-\-no\-partitions]
|
||||
[\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
|
||||
[\-\-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
|
||||
|
||||
@ -503,6 +509,10 @@ enable or disable checking for AUR updates
|
||||
\fB\-\-changes\fR, \fB\-\-no\-changes\fR
|
||||
calculate changes from the latest known commit if available. Only applicable in dry run mode
|
||||
|
||||
.TP
|
||||
\fB\-\-check\-files\fR, \fB\-\-no\-check\-files\fR
|
||||
enable or disable checking of broken dependencies (e.g. dynamically linked libraries or modules directories)
|
||||
|
||||
.TP
|
||||
\fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR
|
||||
process missing package dependencies
|
||||
@ -649,9 +659,9 @@ 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] [\-\-dependencies | \-\-no\-dependencies]
|
||||
[\-\-dry\-run] [\-e] [\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local]
|
||||
[\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
|
||||
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
|
||||
@ -669,6 +679,10 @@ enable or disable checking for AUR updates
|
||||
\fB\-\-changes\fR, \fB\-\-no\-changes\fR
|
||||
calculate changes from the latest known commit if available. Only applicable in dry run mode
|
||||
|
||||
.TP
|
||||
\fB\-\-check\-files\fR, \fB\-\-no\-check\-files\fR
|
||||
enable or disable checking of broken dependencies (e.g. dynamically linked libraries or modules directories)
|
||||
|
||||
.TP
|
||||
\fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR
|
||||
process missing package dependencies
|
||||
|
@ -86,7 +86,7 @@ _shtab_ahriman_options=(
|
||||
{-a,--architecture}"[filter by target architecture (default\: None)]:architecture:"
|
||||
{-c,--configuration}"[configuration path (default\: \/etc\/ahriman.ini)]:configuration:"
|
||||
"--force[force run, remove file lock (default\: False)]"
|
||||
{-l,--lock}"[lock file (default\: \/tmp\/ahriman.lock)]:lock:"
|
||||
{-l,--lock}"[lock file (default\: ahriman.pid)]:lock:"
|
||||
"--log-handler[explicit log handler specification. If none set, the handler will be guessed from environment (default\: None)]:log_handler:(console syslog journald)"
|
||||
{-q,--quiet}"[force disable any logging (default\: False)]"
|
||||
{--report,--no-report}"[force enable or disable reporting to web service (default\: True)]:report:"
|
||||
@ -120,6 +120,7 @@ _shtab_ahriman_aur_search_options=(
|
||||
_shtab_ahriman_check_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
|
||||
{--check-files,--no-check-files}"[enable or disable checking of broken dependencies (e.g. dynamically linked libraries or modules directories) (default\: True)]:check_files:"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: True)]:vcs:"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
|
||||
@ -153,6 +154,7 @@ _shtab_ahriman_daemon_options=(
|
||||
{-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:"
|
||||
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
|
||||
{--check-files,--no-check-files}"[enable or disable checking of broken dependencies (e.g. dynamically linked libraries or modules directories) (default\: True)]:check_files:"
|
||||
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:"
|
||||
"--dry-run[just perform check for updates, same as check command (default\: False)]"
|
||||
{--increment,--no-increment}"[increment package release (pkgrel) on duplicate (default\: True)]:increment:"
|
||||
@ -278,7 +280,7 @@ _shtab_ahriman_patch_list_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
"*"{-v,--variable}"[if set, show only patches for specified PKGBUILD variables (default\: None)]:variable:"
|
||||
":package base (default\: None):"
|
||||
":package base:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_patch_remove_options=(
|
||||
@ -322,6 +324,7 @@ _shtab_ahriman_repo_backup_options=(
|
||||
_shtab_ahriman_repo_check_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
|
||||
{--check-files,--no-check-files}"[enable or disable checking of broken dependencies (e.g. dynamically linked libraries or modules directories) (default\: True)]:check_files:"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: True)]:vcs:"
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
|
||||
@ -363,6 +366,7 @@ _shtab_ahriman_repo_daemon_options=(
|
||||
{-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:"
|
||||
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
|
||||
{--check-files,--no-check-files}"[enable or disable checking of broken dependencies (e.g. dynamically linked libraries or modules directories) (default\: True)]:check_files:"
|
||||
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:"
|
||||
"--dry-run[just perform check for updates, same as check command (default\: False)]"
|
||||
{--increment,--no-increment}"[increment package release (pkgrel) on duplicate (default\: True)]:increment:"
|
||||
@ -460,6 +464,7 @@ _shtab_ahriman_repo_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
|
||||
{--check-files,--no-check-files}"[enable or disable checking of broken dependencies (e.g. dynamically linked libraries or modules directories) (default\: True)]:check_files:"
|
||||
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:"
|
||||
"--dry-run[just perform check for updates, same as check command (default\: False)]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
@ -601,6 +606,7 @@ _shtab_ahriman_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
|
||||
{--check-files,--no-check-files}"[enable or disable checking of broken dependencies (e.g. dynamically linked libraries or modules directories) (default\: True)]:check_files:"
|
||||
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:"
|
||||
"--dry-run[just perform check for updates, same as check command (default\: False)]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
@ -736,4 +742,12 @@ _shtab_ahriman() {
|
||||
|
||||
|
||||
typeset -A opt_args
|
||||
_shtab_ahriman "$@"
|
||||
|
||||
if [[ $zsh_eval_context[-1] == eval ]]; then
|
||||
# eval/source/. command, register function for later
|
||||
compdef _shtab_ahriman -N ahriman
|
||||
else
|
||||
# autoload from fpath, call function directly
|
||||
_shtab_ahriman "$@"
|
||||
fi
|
||||
|
||||
|
@ -20,6 +20,7 @@ dependencies = [
|
||||
"cerberus",
|
||||
"inflection",
|
||||
"passlib",
|
||||
"pyelftools",
|
||||
"requests",
|
||||
"srcinfo",
|
||||
]
|
||||
@ -80,7 +81,8 @@ web = [
|
||||
"aiohttp_session",
|
||||
"aiohttp_security",
|
||||
"cryptography",
|
||||
"requests-unixsocket", # required by unix socket support
|
||||
"requests-unixsocket2", # required by unix socket support
|
||||
"setuptools", # required by aiohttp-apispec
|
||||
]
|
||||
|
||||
[tool.flit.sdist]
|
||||
|
@ -12,6 +12,7 @@ Collection of the examples of docker compose configuration files, which covers s
|
||||
* [Index](index): repository with index page generator enabled.
|
||||
* [Multi repo](multirepo): run web service with two separated repositories.
|
||||
* [OAuth](oauth): web service with OAuth (GitHub provider) authentication enabled.
|
||||
* [PAM](pam): web service with PAM authentication enabled.
|
||||
* [Pull](pull): normal service, but in addition with pulling packages from another source (e.g. GitHub repository).
|
||||
* [Sign](sign): create repository with database signing.
|
||||
* [Web](web): simple web service with authentication enabled.
|
||||
|
6
recipes/pam/README.md
Normal file
6
recipes/pam/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# PAM
|
||||
|
||||
1. Create system user `demo` with password from `AHRIMAN_PASSWORD` environment variable and group `wheel`.
|
||||
2. Setup repository named `ahriman-demo` with architecture `x86_64`.
|
||||
3. Start web server at port `8080`.
|
||||
4. Repository is available at `http://localhost:8080/repo`.
|
63
recipes/pam/compose.yml
Normal file
63
recipes/pam/compose.yml
Normal file
@ -0,0 +1,63 @@
|
||||
services:
|
||||
backend:
|
||||
image: arcan1s/ahriman:edge
|
||||
privileged: true
|
||||
|
||||
environment:
|
||||
AHRIMAN_DEBUG: yes
|
||||
AHRIMAN_OUTPUT: console
|
||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||
AHRIMAN_PORT: 8080
|
||||
AHRIMAN_PRESETUP_COMMAND: useradd -d / -G wheel -M demo; (cat /run/secrets/password; echo; cat /run/secrets/password) | passwd demo
|
||||
AHRIMAN_REPOSITORY: ahriman-demo
|
||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||
|
||||
configs:
|
||||
- source: service
|
||||
target: /etc/ahriman.ini.d/99-settings.ini
|
||||
secrets:
|
||||
- password
|
||||
|
||||
volumes:
|
||||
- type: volume
|
||||
source: repository
|
||||
target: /var/lib/ahriman
|
||||
volume:
|
||||
nocopy: true
|
||||
|
||||
healthcheck:
|
||||
test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info
|
||||
interval: 10s
|
||||
start_period: 30s
|
||||
|
||||
command: web
|
||||
|
||||
frontend:
|
||||
image: nginx
|
||||
ports:
|
||||
- 8080:80
|
||||
|
||||
configs:
|
||||
- source: nginx
|
||||
target: /etc/nginx/conf.d/default.conf
|
||||
|
||||
volumes:
|
||||
- type: volume
|
||||
source: repository
|
||||
target: /srv
|
||||
read_only: true
|
||||
volume:
|
||||
nocopy: true
|
||||
|
||||
configs:
|
||||
nginx:
|
||||
file: nginx.conf
|
||||
service:
|
||||
file: service.ini
|
||||
|
||||
secrets:
|
||||
password:
|
||||
environment: AHRIMAN_PASSWORD
|
||||
|
||||
volumes:
|
||||
repository:
|
18
recipes/pam/nginx.conf
Normal file
18
recipes/pam/nginx.conf
Normal file
@ -0,0 +1,18 @@
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location /repo {
|
||||
rewrite ^/repo/(.*) /$1 break;
|
||||
autoindex on;
|
||||
root /srv/ahriman/repository;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarder-Proto $scheme;
|
||||
|
||||
proxy_pass http://backend:8080;
|
||||
}
|
||||
}
|
3
recipes/pam/service.ini
Normal file
3
recipes/pam/service.ini
Normal file
@ -0,0 +1,3 @@
|
||||
[auth]
|
||||
target = pam
|
||||
full_access_group = wheel
|
@ -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.13.3"
|
||||
__version__ = "2.14.1"
|
||||
|
@ -19,14 +19,13 @@
|
||||
#
|
||||
# pylint: disable=too-many-lines
|
||||
import argparse
|
||||
import tempfile
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TypeVar
|
||||
|
||||
from ahriman import __version__
|
||||
from ahriman.application import handlers
|
||||
from ahriman.core.util import enum_values, extract_user
|
||||
from ahriman.core.utils import enum_values, extract_user
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.log_handler import LogHandler
|
||||
@ -73,8 +72,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("-c", "--configuration", help="configuration path", type=Path,
|
||||
default=Path("/") / "etc" / "ahriman.ini")
|
||||
parser.add_argument("--force", help="force run, remove file lock", action="store_true")
|
||||
parser.add_argument("-l", "--lock", help="lock file", type=Path,
|
||||
default=Path(tempfile.gettempdir()) / "ahriman.lock")
|
||||
parser.add_argument("-l", "--lock", help="lock file", type=Path, default=Path("ahriman.pid"))
|
||||
parser.add_argument("--log-handler", help="explicit log handler specification. If none set, the handler will be "
|
||||
"guessed from environment",
|
||||
type=LogHandler, choices=enum_values(LogHandler))
|
||||
@ -446,7 +444,7 @@ def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
parser = root.add_parser("patch-list", help="list patch sets",
|
||||
description="list available patches for the package", formatter_class=_formatter)
|
||||
parser.add_argument("package", help="package base", nargs="?")
|
||||
parser.add_argument("package", help="package base")
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||
parser.add_argument("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables",
|
||||
action="append")
|
||||
@ -537,6 +535,9 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("--changes", help="calculate changes from the latest known commit if available. "
|
||||
"Only applicable in dry run mode",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--check-files", help="enable or disable checking of broken dependencies "
|
||||
"(e.g. dynamically linked libraries or modules directories)",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||
parser.add_argument("--vcs", help="fetch actual version of VCS packages",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
@ -605,6 +606,9 @@ def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("--changes", help="calculate changes from the latest known commit if available. "
|
||||
"Only applicable in dry run mode",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--check-files", help="enable or disable checking of broken dependencies "
|
||||
"(e.g. dynamically linked libraries or modules directories)",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--dependencies", help="process missing package dependencies",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")
|
||||
@ -622,8 +626,7 @@ def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
|
||||
"-yy to force refresh even if up to date",
|
||||
action="count", default=False)
|
||||
parser.set_defaults(handler=handlers.Daemon, exit_code=False,
|
||||
lock=Path(tempfile.gettempdir()) / "ahriman-daemon.lock", package=[])
|
||||
parser.set_defaults(handler=handlers.Daemon, exit_code=False, lock=Path("ahriman-daemon.pid"), package=[])
|
||||
return parser
|
||||
|
||||
|
||||
@ -826,6 +829,9 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("--changes", help="calculate changes from the latest known commit if available. "
|
||||
"Only applicable in dry run mode",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--check-files", help="enable or disable checking of broken dependencies "
|
||||
"(e.g. dynamically linked libraries or modules directories)",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--dependencies", help="process missing package dependencies",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")
|
||||
@ -871,7 +877,7 @@ def _set_service_clean_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
action=argparse.BooleanOptionalAction, default=False)
|
||||
parser.add_argument("--pacman", help="clear directory with pacman local database cache",
|
||||
action=argparse.BooleanOptionalAction, default=False)
|
||||
parser.set_defaults(handler=handlers.Clean, quiet=True, unsafe=True)
|
||||
parser.set_defaults(handler=handlers.Clean, lock=None, quiet=True, unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -1130,8 +1136,8 @@ def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("web", help="web server", description="start web server", formatter_class=_formatter)
|
||||
parser.set_defaults(handler=handlers.Web, architecture="", lock=Path(tempfile.gettempdir()) / "ahriman-web.lock",
|
||||
report=False, repository="", parser=_parser)
|
||||
parser.set_defaults(handler=handlers.Web, architecture="", lock=Path("ahriman-web.pid"), report=False,
|
||||
repository="", parser=_parser)
|
||||
return parser
|
||||
|
||||
|
||||
|
@ -62,10 +62,13 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
"""
|
||||
known_packages: set[str] = set()
|
||||
# local set
|
||||
# this action is not really needed in case if ``alpm.use_ahriman_cache`` set to yes, because pacman
|
||||
# will eventually contain all the local packages
|
||||
for base in self.repository.packages():
|
||||
for package, properties in base.packages.items():
|
||||
known_packages.add(package)
|
||||
known_packages.update(properties.provides)
|
||||
# known pacman databases
|
||||
known_packages.update(self.repository.pacman.packages())
|
||||
return known_packages
|
||||
|
||||
@ -158,8 +161,7 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
package = Package.from_aur(package_name, username)
|
||||
with_dependencies[package.base] = package
|
||||
|
||||
# register package in local database
|
||||
self.database.package_base_update(package)
|
||||
# register package in the database
|
||||
self.repository.reporter.set_unknown(package)
|
||||
|
||||
return list(with_dependencies.values())
|
||||
|
@ -27,7 +27,7 @@ from typing import Any
|
||||
from ahriman.application.application.application_properties import ApplicationProperties
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.util import package_like
|
||||
from ahriman.core.utils import package_like
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.result import Result
|
||||
@ -65,7 +65,7 @@ class ApplicationPackages(ApplicationProperties):
|
||||
"""
|
||||
package = Package.from_aur(source, username)
|
||||
self.database.build_queue_insert(package)
|
||||
self.database.package_base_update(package)
|
||||
self.reporter.set_unknown(package)
|
||||
|
||||
def _add_directory(self, source: str, *_: Any) -> None:
|
||||
"""
|
||||
@ -139,7 +139,7 @@ class ApplicationPackages(ApplicationProperties):
|
||||
"""
|
||||
package = Package.from_official(source, self.repository.pacman, username)
|
||||
self.database.build_queue_insert(package)
|
||||
self.database.package_base_update(package)
|
||||
self.reporter.set_unknown(package)
|
||||
|
||||
def add(self, names: Iterable[str], source: PackageSource, username: str | None = None) -> None:
|
||||
"""
|
||||
|
@ -21,6 +21,7 @@ from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.core.status import Client
|
||||
from ahriman.models.pacman_synchronization import PacmanSynchronization
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
@ -63,3 +64,13 @@ class ApplicationProperties(LazyLogging):
|
||||
str: repository architecture
|
||||
"""
|
||||
return self.repository_id.architecture
|
||||
|
||||
@property
|
||||
def reporter(self) -> Client:
|
||||
"""
|
||||
instance of the web/database client
|
||||
|
||||
Returns:
|
||||
Client: repository reposter
|
||||
"""
|
||||
return self.repository.reporter
|
||||
|
@ -39,15 +39,13 @@ class ApplicationRepository(ApplicationProperties):
|
||||
Args:
|
||||
packages(Iterable[Package]): list of packages to retrieve changes
|
||||
"""
|
||||
last_commit_hashes = self.database.hashes_get()
|
||||
|
||||
for package in packages:
|
||||
last_commit_sha = last_commit_hashes.get(package.base)
|
||||
last_commit_sha = self.reporter.package_changes_get(package.base).last_commit_sha
|
||||
if last_commit_sha is None:
|
||||
continue # skip check in case if we can't calculate diff
|
||||
|
||||
changes = self.repository.package_changes(package, last_commit_sha)
|
||||
self.repository.reporter.package_changes_set(package.base, changes)
|
||||
self.repository.reporter.package_changes_update(package.base, changes)
|
||||
|
||||
def clean(self, *, cache: bool, chroot: bool, manual: bool, packages: bool, pacman: bool) -> None:
|
||||
"""
|
||||
@ -91,10 +89,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
packages(Iterable[str]): only sign specified packages
|
||||
"""
|
||||
# copy to prebuilt directory
|
||||
for package in self.repository.packages():
|
||||
# no one requested this package
|
||||
if packages and package.base not in packages:
|
||||
continue
|
||||
for package in self.repository.packages(packages):
|
||||
for archive in package.packages.values():
|
||||
if archive.filepath is None:
|
||||
self.logger.warning("filepath is empty for %s", package.base)
|
||||
@ -179,7 +174,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
return result
|
||||
|
||||
def updates(self, filter_packages: Iterable[str], *,
|
||||
aur: bool, local: bool, manual: bool, vcs: bool) -> list[Package]:
|
||||
aur: bool, local: bool, manual: bool, vcs: bool, check_files: bool) -> list[Package]:
|
||||
"""
|
||||
get list of packages to run update process
|
||||
|
||||
@ -189,6 +184,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
local(bool): enable or disable checking of local packages for updates
|
||||
manual(bool): include or exclude manual updates
|
||||
vcs(bool): enable or disable checking of VCS packages
|
||||
check_files(bool): check for broken dependencies
|
||||
|
||||
Returns:
|
||||
list[Package]: list of out-of-dated packages
|
||||
@ -201,5 +197,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
updates.update({package.base: package for package in self.repository.updates_local(vcs=vcs)})
|
||||
if manual:
|
||||
updates.update({package.base: package for package in self.repository.updates_manual()})
|
||||
if check_files:
|
||||
updates.update({package.base: package for package in self.repository.updates_dependencies(filter_packages)})
|
||||
|
||||
return [package for _, package in sorted(updates.items())]
|
||||
|
@ -50,12 +50,13 @@ class Add(Handler):
|
||||
application.add(args.package, args.source, args.username)
|
||||
patches = [PkgbuildPatch.from_env(patch) for patch in args.variable] if args.variable is not None else []
|
||||
for package in args.package: # for each requested package insert patch
|
||||
application.database.patches_insert(package, patches)
|
||||
for patch in patches:
|
||||
application.reporter.package_patches_update(package, patch)
|
||||
|
||||
if not args.now:
|
||||
return
|
||||
|
||||
packages = application.updates(args.package, aur=False, local=False, manual=True, vcs=False)
|
||||
packages = application.updates(args.package, aur=False, local=False, manual=True, vcs=False, check_files=False)
|
||||
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
|
||||
packagers = Packagers(args.username, {package.base: package.packager for package in packages})
|
||||
|
||||
|
@ -18,10 +18,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
import pwd
|
||||
import tarfile
|
||||
|
||||
from pathlib import Path
|
||||
from tarfile import TarFile
|
||||
from pwd import getpwuid
|
||||
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
@ -49,7 +49,7 @@ class Backup(Handler):
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
backup_paths = Backup.get_paths(configuration)
|
||||
with TarFile(args.path, mode="w") as archive: # well we don't actually use compression
|
||||
with tarfile.open(args.path, mode="w") as archive: # well we don't actually use compression
|
||||
for backup_path in backup_paths:
|
||||
archive.add(backup_path)
|
||||
|
||||
@ -77,7 +77,7 @@ class Backup(Handler):
|
||||
|
||||
# gnupg home with imported keys
|
||||
uid, _ = repository_paths.root_owner
|
||||
system_user = pwd.getpwuid(uid)
|
||||
system_user = getpwuid(uid)
|
||||
gnupg_home = Path(system_user.pw_dir) / ".gnupg"
|
||||
if gnupg_home.is_dir():
|
||||
paths.add(gnupg_home)
|
||||
|
@ -56,4 +56,4 @@ class Change(Handler):
|
||||
ChangesPrinter(changes)(verbose=True, separator="")
|
||||
Change.check_if_empty(args.exit_code, changes.is_empty)
|
||||
case Action.Remove:
|
||||
client.package_changes_set(args.package, Changes())
|
||||
client.package_changes_update(args.package, Changes())
|
||||
|
@ -98,12 +98,14 @@ class Patch(Handler):
|
||||
PkgbuildPatch: created patch for the PKGBUILD function
|
||||
"""
|
||||
if patch_path is None:
|
||||
# pylint: disable=bad-builtin
|
||||
print("Post new function or variable value below. Press Ctrl-D to finish:", file=sys.stderr)
|
||||
patch = "".join(list(sys.stdin))
|
||||
else:
|
||||
patch = patch_path.read_text(encoding="utf8")
|
||||
patch = patch.strip() # remove spaces around the patch
|
||||
return PkgbuildPatch(variable, patch)
|
||||
# remove spaces around the patch and parse to correct type
|
||||
parsed = PkgbuildPatch.parse(patch.strip())
|
||||
return PkgbuildPatch(variable, parsed)
|
||||
|
||||
@staticmethod
|
||||
def patch_set_create(application: Application, package_base: str, patch: PkgbuildPatch) -> None:
|
||||
@ -115,25 +117,28 @@ class Patch(Handler):
|
||||
package_base(str): package base
|
||||
patch(PkgbuildPatch): patch descriptor
|
||||
"""
|
||||
application.database.patches_insert(package_base, [patch])
|
||||
application.reporter.package_patches_update(package_base, patch)
|
||||
|
||||
@staticmethod
|
||||
def patch_set_list(application: Application, package_base: str | None, variables: list[str] | None,
|
||||
def patch_set_list(application: Application, package_base: str, variables: list[str] | None,
|
||||
exit_code: bool) -> None:
|
||||
"""
|
||||
list patches available for the package base
|
||||
|
||||
Args:
|
||||
application(Application): application instance
|
||||
package_base(str | None): package base
|
||||
package_base(str): package base
|
||||
variables(list[str] | None): extract patches only for specified PKGBUILD variables
|
||||
exit_code(bool): exit with error on empty search result
|
||||
"""
|
||||
patches = application.database.patches_list(package_base, variables)
|
||||
patches = [
|
||||
patch
|
||||
for patch in application.reporter.package_patches_get(package_base, None)
|
||||
if variables is None or patch.key in variables
|
||||
]
|
||||
Patch.check_if_empty(exit_code, not patches)
|
||||
|
||||
for base, patch in patches.items():
|
||||
PatchPrinter(base, patch)(verbose=True, separator=" = ")
|
||||
PatchPrinter(package_base, patches)(verbose=True, separator=" = ")
|
||||
|
||||
@staticmethod
|
||||
def patch_set_remove(application: Application, package_base: str, variables: list[str] | None) -> None:
|
||||
@ -145,4 +150,8 @@ class Patch(Handler):
|
||||
package_base(str): package base
|
||||
variables(list[str] | None): remove patches only for specified PKGBUILD variables
|
||||
"""
|
||||
application.database.patches_remove(package_base, variables)
|
||||
if variables is not None:
|
||||
for variable in variables: # iterate over single variable
|
||||
application.reporter.package_patches_remove(package_base, variable)
|
||||
else:
|
||||
application.reporter.package_patches_remove(package_base, None) # just pass as is
|
||||
|
@ -76,7 +76,7 @@ class Rebuild(Handler):
|
||||
if from_database:
|
||||
return [
|
||||
package
|
||||
for (package, last_status) in application.database.packages_get()
|
||||
for (package, last_status) in application.reporter.package_get(None)
|
||||
if status is None or last_status.status == status
|
||||
]
|
||||
|
||||
|
@ -18,8 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
|
||||
from tarfile import TarFile
|
||||
import tarfile
|
||||
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
@ -45,5 +44,5 @@ class Restore(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
with TarFile(args.path) as archive:
|
||||
archive.extractall(path=args.output)
|
||||
with tarfile.open(args.path) as archive:
|
||||
archive.extractall(path=args.output) # nosec
|
||||
|
@ -43,7 +43,7 @@ class Search(Handler):
|
||||
SORT_FIELDS = {
|
||||
field.name
|
||||
for field in fields(AURPackage)
|
||||
if field.default_factory is not list # type: ignore[comparison-overlap]
|
||||
if field.default_factory is not list
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -51,12 +51,8 @@ class StatusUpdate(Handler):
|
||||
match args.action:
|
||||
case Action.Update if args.package:
|
||||
# update packages statuses
|
||||
packages = application.repository.packages()
|
||||
for base in args.package:
|
||||
if (local := next((package for package in packages if package.base == base), None)) is not None:
|
||||
client.package_add(local, args.status)
|
||||
else:
|
||||
client.package_update(base, args.status)
|
||||
for package in args.package:
|
||||
client.package_update(package, args.status)
|
||||
case Action.Update:
|
||||
# update service status
|
||||
client.status_update(args.status)
|
||||
|
@ -48,7 +48,8 @@ class Update(Handler):
|
||||
application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh)
|
||||
application.on_start()
|
||||
|
||||
packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs)
|
||||
packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs,
|
||||
check_files=args.check_files)
|
||||
if args.dry_run: # some check specific actions
|
||||
if args.changes: # generate changes if requested
|
||||
application.changes(packages)
|
||||
@ -76,5 +77,5 @@ class Update(Handler):
|
||||
Callable[[str], None]: in case if dry_run is set it will return print, logger otherwise
|
||||
"""
|
||||
def inner(line: str) -> None:
|
||||
return print(line) if dry_run else application.logger.info(line)
|
||||
return print(line) if dry_run else application.logger.info(line) # pylint: disable=bad-builtin
|
||||
return inner
|
||||
|
@ -18,7 +18,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
import fcntl
|
||||
import os
|
||||
|
||||
from io import TextIOWrapper
|
||||
from pathlib import Path
|
||||
from types import TracebackType
|
||||
from typing import Literal, Self
|
||||
@ -27,8 +30,8 @@ from ahriman import __version__
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import DuplicateRunError
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.status.client import Client
|
||||
from ahriman.core.util import check_user
|
||||
from ahriman.core.status import Client
|
||||
from ahriman.core.utils import check_user
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.waiter import Waiter
|
||||
@ -36,7 +39,7 @@ from ahriman.models.waiter import Waiter
|
||||
|
||||
class Lock(LazyLogging):
|
||||
"""
|
||||
wrapper for application lock file
|
||||
wrapper for application lock file. Credits for idea to https://github.com/bmhatfield/python-pidfile.git
|
||||
|
||||
Attributes:
|
||||
force(bool): remove lock file on start if any
|
||||
@ -70,8 +73,15 @@ class Lock(LazyLogging):
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
self.path: Path | None = \
|
||||
args.lock.with_stem(f"{args.lock.stem}_{repository_id.id}") if args.lock is not None else None
|
||||
self.path: Path | None = None
|
||||
if args.lock is not None:
|
||||
self.path = args.lock
|
||||
if not repository_id.is_empty:
|
||||
self.path = self.path.with_stem(f"{args.lock.stem}_{repository_id.id}")
|
||||
if not self.path.is_absolute():
|
||||
# prepend full path to the lock file
|
||||
self.path = Path("/") / "run" / "ahriman" / self.path
|
||||
self._pid_file: TextIOWrapper | None = None
|
||||
|
||||
self.force: bool = args.force
|
||||
self.unsafe: bool = args.unsafe
|
||||
@ -80,6 +90,72 @@ class Lock(LazyLogging):
|
||||
self.paths = configuration.repository_paths
|
||||
self.reporter = Client.load(repository_id, configuration, report=args.report)
|
||||
|
||||
@staticmethod
|
||||
def perform_lock(fd: int) -> bool:
|
||||
"""
|
||||
perform file lock
|
||||
|
||||
Args:
|
||||
fd(int): file descriptor:
|
||||
|
||||
Returns:
|
||||
bool: True in case if file is locked and False otherwise
|
||||
"""
|
||||
try:
|
||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _open(self) -> None:
|
||||
"""
|
||||
create lock file
|
||||
"""
|
||||
if self.path is None:
|
||||
return
|
||||
self._pid_file = self.path.open("a+")
|
||||
|
||||
def _watch(self) -> bool:
|
||||
"""
|
||||
watch until lock disappear
|
||||
|
||||
Returns:
|
||||
bool: True in case if file is locked and False otherwise
|
||||
"""
|
||||
# there are reasons why we are not using inotify here. First of all, if we would use it, it would bring to
|
||||
# race conditions because multiple processes will be notified at the same time. Secondly, it is good library,
|
||||
# but platform-specific, and we only need to check if file exists
|
||||
if self._pid_file is None:
|
||||
return False
|
||||
|
||||
waiter = Waiter(self.wait_timeout)
|
||||
return bool(waiter.wait(lambda fd: not self.perform_lock(fd), self._pid_file.fileno()))
|
||||
|
||||
def _write(self, *, is_locked: bool = False) -> None:
|
||||
"""
|
||||
write pid to the lock file
|
||||
|
||||
Args:
|
||||
is_locked(bool, optional): indicates if file was already locked or not (Default value = False)
|
||||
|
||||
Raises:
|
||||
DuplicateRunError: if it cannot lock PID file
|
||||
"""
|
||||
if self._pid_file is None:
|
||||
return
|
||||
if not is_locked:
|
||||
if not self.perform_lock(self._pid_file.fileno()):
|
||||
raise DuplicateRunError
|
||||
|
||||
self._pid_file.seek(0) # reset position and remove file content if any
|
||||
self._pid_file.truncate()
|
||||
|
||||
self._pid_file.write(str(os.getpid())) # write current pid
|
||||
self._pid_file.flush() # flush data to disk
|
||||
|
||||
self._pid_file.seek(0) # reset position again
|
||||
|
||||
def check_user(self) -> None:
|
||||
"""
|
||||
check if current user is actually owner of ahriman root
|
||||
@ -100,46 +176,33 @@ class Lock(LazyLogging):
|
||||
"""
|
||||
remove lock file
|
||||
"""
|
||||
if self.path is None:
|
||||
return
|
||||
self.path.unlink(missing_ok=True)
|
||||
if self._pid_file is not None: # close file descriptor
|
||||
try:
|
||||
self._pid_file.close()
|
||||
except IOError:
|
||||
pass # suppress any IO errors occur
|
||||
if self.path is not None: # remove lock file
|
||||
self.path.unlink(missing_ok=True)
|
||||
|
||||
def create(self) -> None:
|
||||
def lock(self) -> None:
|
||||
"""
|
||||
create lock file
|
||||
|
||||
Raises:
|
||||
DuplicateRunError: if lock exists and no force flag supplied
|
||||
create pid file
|
||||
"""
|
||||
if self.path is None:
|
||||
return
|
||||
try:
|
||||
self.path.touch(exist_ok=self.force)
|
||||
except FileExistsError:
|
||||
raise DuplicateRunError from None
|
||||
|
||||
def watch(self) -> None:
|
||||
"""
|
||||
watch until lock disappear
|
||||
"""
|
||||
# there are reasons why we are not using inotify here. First of all, if we would use it, it would bring to
|
||||
# race conditions because multiple processes will be notified in the same time. Secondly, it is good library,
|
||||
# but platform-specific, and we only need to check if file exists
|
||||
if self.path is None:
|
||||
return
|
||||
|
||||
waiter = Waiter(self.wait_timeout)
|
||||
waiter.wait(self.path.is_file)
|
||||
if self.force: # remove lock if force flag is set
|
||||
self.clear()
|
||||
self._open()
|
||||
is_locked = self._watch()
|
||||
self._write(is_locked=is_locked)
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
"""
|
||||
default workflow is the following:
|
||||
|
||||
#. Check user UID
|
||||
#. Check if there is lock file
|
||||
#. Check web status watcher status
|
||||
#. Open lock file
|
||||
#. Wait for lock file to be free
|
||||
#. Create lock file and directory tree
|
||||
#. Write current PID to the lock file
|
||||
#. Report to status page if enabled
|
||||
|
||||
Returns:
|
||||
@ -147,8 +210,7 @@ class Lock(LazyLogging):
|
||||
"""
|
||||
self.check_user()
|
||||
self.check_version()
|
||||
self.watch()
|
||||
self.create()
|
||||
self.lock()
|
||||
self.reporter.status_update(BuildStatusEnum.Building)
|
||||
return self
|
||||
|
||||
|
@ -38,12 +38,12 @@ class _Context:
|
||||
"""
|
||||
self._content: dict[str, Any] = {}
|
||||
|
||||
def get(self, key: ContextKey[T]) -> T:
|
||||
def get(self, key: ContextKey[T] | type[T]) -> T:
|
||||
"""
|
||||
get value for the specified key
|
||||
|
||||
Args:
|
||||
key(ContextKey[T]): context key name
|
||||
key(ContextKey[T] | type[T]): context key name
|
||||
|
||||
Returns:
|
||||
T: value associated with the key
|
||||
@ -52,29 +52,37 @@ class _Context:
|
||||
KeyError: in case if the specified context variable was not found
|
||||
ValueError: in case if type of value is not an instance of specified return type
|
||||
"""
|
||||
if not isinstance(key, ContextKey):
|
||||
key = ContextKey.from_type(key)
|
||||
|
||||
if key.key not in self._content:
|
||||
raise KeyError(key.key)
|
||||
value = self._content[key.key]
|
||||
if not isinstance(value, key.return_type):
|
||||
raise ValueError(f"Value {value} is not an instance of {key.return_type}")
|
||||
|
||||
return value
|
||||
|
||||
def set(self, key: ContextKey[T], value: T) -> None:
|
||||
def set(self, key: ContextKey[T] | type[T], value: T) -> None:
|
||||
"""
|
||||
set value for the specified key
|
||||
|
||||
Args:
|
||||
key(ContextKey[T]): context key name
|
||||
key(ContextKey[T] | type[T]): context key name
|
||||
value(T): context value associated with the specified key
|
||||
|
||||
Raises:
|
||||
KeyError: in case if the specified context variable already exists
|
||||
ValueError: in case if type of value is not an instance of specified return type
|
||||
"""
|
||||
if not isinstance(key, ContextKey):
|
||||
key = ContextKey.from_type(key)
|
||||
|
||||
if key.key in self._content:
|
||||
raise KeyError(key.key)
|
||||
if not isinstance(value, key.return_type):
|
||||
raise ValueError(f"Value {value} is not an instance of {key.return_type}")
|
||||
|
||||
self._content[key.key] = value
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
|
@ -17,25 +17,33 @@
|
||||
# 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 itertools
|
||||
import shutil
|
||||
import tarfile
|
||||
|
||||
from collections.abc import Callable, Generator
|
||||
from collections.abc import Generator, Iterable
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore[import-not-found]
|
||||
from pyalpm import DB, Handle, Package, SIG_DATABASE_OPTIONAL, SIG_PACKAGE_OPTIONAL # type: ignore[import-not-found]
|
||||
from string import Template
|
||||
|
||||
from ahriman.core.alpm.pacman_database import PacmanDatabase
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.util import trim_package
|
||||
from ahriman.core.utils import trim_package
|
||||
from ahriman.models.pacman_synchronization import PacmanSynchronization
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
class Pacman(LazyLogging):
|
||||
"""
|
||||
alpm wrapper
|
||||
|
||||
Attributes:
|
||||
configuration(Configuration): configuration instance
|
||||
refresh_database(PacmanSynchronization): synchronize local cache to remote
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
repository_paths(RepositoryPaths): repository paths instance
|
||||
"""
|
||||
|
||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
@ -48,8 +56,11 @@ class Pacman(LazyLogging):
|
||||
configuration(Configuration): configuration instance
|
||||
refresh_database(PacmanSynchronization): synchronize local cache to remote
|
||||
"""
|
||||
self.__create_handle_fn: Callable[[], Handle] = lambda: self.__create_handle(
|
||||
repository_id, configuration, refresh_database=refresh_database)
|
||||
self.configuration = configuration
|
||||
self.repository_id = repository_id
|
||||
self.repository_paths = configuration.repository_paths
|
||||
|
||||
self.refresh_database = refresh_database
|
||||
|
||||
@cached_property
|
||||
def handle(self) -> Handle:
|
||||
@ -59,40 +70,39 @@ class Pacman(LazyLogging):
|
||||
Returns:
|
||||
Handle: generated pyalpm handle instance
|
||||
"""
|
||||
return self.__create_handle_fn()
|
||||
return self.__create_handle(refresh_database=self.refresh_database)
|
||||
|
||||
def __create_handle(self, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
refresh_database: PacmanSynchronization) -> Handle:
|
||||
def __create_handle(self, *, refresh_database: PacmanSynchronization) -> Handle:
|
||||
"""
|
||||
create lazy handle function
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
refresh_database(PacmanSynchronization): synchronize local cache to remote
|
||||
|
||||
Returns:
|
||||
Handle: fully initialized pacman handle
|
||||
"""
|
||||
root = configuration.getpath("alpm", "root")
|
||||
pacman_root = configuration.getpath("alpm", "database")
|
||||
use_ahriman_cache = configuration.getboolean("alpm", "use_ahriman_cache")
|
||||
mirror = configuration.get("alpm", "mirror")
|
||||
paths = configuration.repository_paths
|
||||
database_path = paths.pacman if use_ahriman_cache else pacman_root
|
||||
pacman_root = self.configuration.getpath("alpm", "database")
|
||||
use_ahriman_cache = self.configuration.getboolean("alpm", "use_ahriman_cache")
|
||||
|
||||
database_path = self.repository_paths.pacman if use_ahriman_cache else pacman_root
|
||||
root = self.configuration.getpath("alpm", "root")
|
||||
handle = Handle(str(root), str(database_path))
|
||||
for repository in configuration.getlist("alpm", "repositories"):
|
||||
database = self.database_init(handle, repository, mirror, repository_id.architecture)
|
||||
self.database_copy(handle, database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache)
|
||||
|
||||
for repository in self.configuration.getlist("alpm", "repositories"):
|
||||
database = self.database_init(handle, repository, self.repository_id.architecture)
|
||||
self.database_copy(handle, database, pacman_root, use_ahriman_cache=use_ahriman_cache)
|
||||
|
||||
# install repository database too
|
||||
local_database = self.database_init(handle, self.repository_id.name, self.repository_id.architecture)
|
||||
self.database_copy(handle, local_database, pacman_root, use_ahriman_cache=use_ahriman_cache)
|
||||
|
||||
if use_ahriman_cache and refresh_database:
|
||||
self.database_sync(handle, force=refresh_database == PacmanSynchronization.Force)
|
||||
|
||||
return handle
|
||||
|
||||
def database_copy(self, handle: Handle, database: DB, pacman_root: Path, paths: RepositoryPaths, *,
|
||||
use_ahriman_cache: bool) -> None:
|
||||
def database_copy(self, handle: Handle, database: DB, pacman_root: Path, *, use_ahriman_cache: bool) -> None:
|
||||
"""
|
||||
copy database from the operating system root to the ahriman local home
|
||||
|
||||
@ -100,7 +110,6 @@ class Pacman(LazyLogging):
|
||||
handle(Handle): pacman handle which will be used for database copying
|
||||
database(DB): pacman database instance to be copied
|
||||
pacman_root(Path): operating system pacman root
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
use_ahriman_cache(bool): use local ahriman cache instead of system one
|
||||
"""
|
||||
def repository_database(root: Path) -> Path:
|
||||
@ -122,30 +131,36 @@ class Pacman(LazyLogging):
|
||||
return # database for some reason deos not exist
|
||||
self.logger.info("copy pacman database from operating system root to ahriman's home")
|
||||
shutil.copy(src, dst)
|
||||
paths.chown(dst)
|
||||
self.repository_paths.chown(dst)
|
||||
|
||||
def database_init(self, handle: Handle, repository: str, mirror: str, architecture: str) -> DB:
|
||||
def database_init(self, handle: Handle, repository: str, architecture: str) -> DB:
|
||||
"""
|
||||
create database instance from pacman handler and set its properties
|
||||
|
||||
Args:
|
||||
handle(Handle): pacman handle which will be used for database initializing
|
||||
repository(str): pacman repository name (e.g. core)
|
||||
mirror(str): arch linux mirror url
|
||||
architecture(str): repository architecture
|
||||
|
||||
Returns:
|
||||
DB: loaded pacman database instance
|
||||
"""
|
||||
self.logger.info("loading pacman database %s", repository)
|
||||
database: DB = handle.register_syncdb(repository, SIG_PACKAGE)
|
||||
database: DB = handle.register_syncdb(repository, SIG_DATABASE_OPTIONAL | SIG_PACKAGE_OPTIONAL)
|
||||
|
||||
# replace variables in mirror address
|
||||
variables = {
|
||||
"arch": architecture,
|
||||
"repo": repository,
|
||||
}
|
||||
database.servers = [Template(mirror).safe_substitute(variables)]
|
||||
if repository != self.repository_id.name:
|
||||
mirror = self.configuration.get("alpm", "mirror")
|
||||
# replace variables in mirror address
|
||||
variables = {
|
||||
"arch": architecture,
|
||||
"repo": repository,
|
||||
}
|
||||
server = Template(mirror).safe_substitute(variables)
|
||||
else:
|
||||
# special case, same database, use local storage instead
|
||||
server = f"file://{self.repository_paths.repository}"
|
||||
|
||||
database.servers = [server]
|
||||
|
||||
return database
|
||||
|
||||
@ -160,13 +175,55 @@ class Pacman(LazyLogging):
|
||||
self.logger.info("refresh ahriman's home pacman database (force refresh %s)", force)
|
||||
transaction = handle.init_transaction()
|
||||
for database in handle.get_syncdbs():
|
||||
try:
|
||||
database.update(force)
|
||||
except PyalpmError:
|
||||
self.logger.exception("exception during update %s", database.name)
|
||||
PacmanDatabase(database, self.configuration).sync(force=force)
|
||||
transaction.release()
|
||||
|
||||
def package_get(self, package_name: str) -> Generator[Package, None, None]:
|
||||
def files(self, packages: Iterable[str]) -> dict[str, set[str]]:
|
||||
"""
|
||||
extract list of known packages from the databases
|
||||
|
||||
Args:
|
||||
packages(Iterable[str]): filter by package names
|
||||
|
||||
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]:
|
||||
for package_name, version in versions.items():
|
||||
path = Path(f"{package_name}-{version}") / "files"
|
||||
try:
|
||||
content = tar.extractfile(str(path))
|
||||
except KeyError:
|
||||
# in case if database and its files has been desync somehow, the extractfile will raise
|
||||
# KeyError because the entry doesn't exist
|
||||
content = None
|
||||
if content is None:
|
||||
continue
|
||||
|
||||
# this is just array of files, however, the directories are with trailing slash,
|
||||
# which previously has been removed by the conversion to ``pathlib.Path``
|
||||
files = {filename.decode("utf8").rstrip().removesuffix("/") for filename in content.readlines()}
|
||||
yield package_name, files
|
||||
|
||||
# sort is required for the following group by operation
|
||||
descriptors = sorted(
|
||||
(package for package_name in packages for package in self.package(package_name)),
|
||||
key=lambda package: package.db.name
|
||||
)
|
||||
|
||||
result: dict[str, set[str]] = {}
|
||||
for database_name, pacman_packages in itertools.groupby(descriptors, lambda package: package.db.name):
|
||||
database_file = self.repository_paths.pacman / "sync" / f"{database_name}.files.tar.gz"
|
||||
if not database_file.is_file():
|
||||
continue # no database file found
|
||||
|
||||
package_names = {package.name: package.version for package in pacman_packages}
|
||||
with tarfile.open(database_file, "r:gz") as archive:
|
||||
result.update(extract(archive, package_names))
|
||||
|
||||
return result
|
||||
|
||||
def package(self, package_name: str) -> Generator[Package, None, None]:
|
||||
"""
|
||||
retrieve list of the packages from the repository by name
|
||||
|
||||
|
171
src/ahriman/core/alpm/pacman_database.py
Normal file
171
src/ahriman/core/alpm/pacman_database.py
Normal file
@ -0,0 +1,171 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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 os
|
||||
import shutil
|
||||
|
||||
from email.utils import parsedate_to_datetime
|
||||
from pathlib import Path
|
||||
from pyalpm import DB # type: ignore[import-not-found]
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import PacmanError
|
||||
from ahriman.core.http import SyncHttpClient
|
||||
|
||||
|
||||
class PacmanDatabase(SyncHttpClient):
|
||||
"""
|
||||
implementation for database sync, because pyalpm is not always enough
|
||||
|
||||
Attributes:
|
||||
LAST_MODIFIED_HEADER(str): last modified header name
|
||||
database(DB): pyalpm database object
|
||||
repository_paths(RepositoryPaths): repository paths instance
|
||||
sync_files_database(bool): sync files database
|
||||
"""
|
||||
|
||||
LAST_MODIFIED_HEADER = "Last-Modified"
|
||||
|
||||
def __init__(self, database: DB, configuration: Configuration) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
database(DB): pyalpm database object
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
SyncHttpClient.__init__(self)
|
||||
self.timeout = None # reset timeout
|
||||
|
||||
self.database = database
|
||||
self.repository_paths = configuration.repository_paths
|
||||
|
||||
self.sync_files_database = configuration.getboolean("alpm", "sync_files_database")
|
||||
|
||||
@staticmethod
|
||||
def copy(remote_path: Path, local_path: Path) -> None:
|
||||
"""
|
||||
copy local database file
|
||||
|
||||
Args:
|
||||
remote_path(Path): path to source (remote) file
|
||||
local_path(Path): path to locally stored file
|
||||
"""
|
||||
shutil.copy(remote_path, local_path)
|
||||
|
||||
def download(self, url: str, local_path: Path) -> None:
|
||||
"""
|
||||
download remote file and store it to local path with the correct last modified headers
|
||||
|
||||
Args:
|
||||
url(str): remote url to request file
|
||||
local_path(Path): path to locally stored file
|
||||
|
||||
Raises:
|
||||
PacmanError: in case if no last-modified header was found
|
||||
"""
|
||||
response = self.make_request("GET", url, stream=True)
|
||||
if self.LAST_MODIFIED_HEADER not in response.headers:
|
||||
raise PacmanError("No last-modified header found")
|
||||
|
||||
with local_path.open("wb") as local_file:
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
local_file.write(chunk)
|
||||
|
||||
# set correct (a,m)time for the file
|
||||
remote_changed = parsedate_to_datetime(response.headers[self.LAST_MODIFIED_HEADER]).timestamp()
|
||||
os.utime(local_path, (remote_changed, remote_changed))
|
||||
|
||||
def is_outdated(self, url: str, local_path: Path) -> bool:
|
||||
"""
|
||||
check if local file is outdated
|
||||
|
||||
Args:
|
||||
url(str): remote url to request last modified header
|
||||
local_path(Path): path to locally stored file
|
||||
|
||||
Returns:
|
||||
bool: True in case if remote file is newer than local file
|
||||
|
||||
Raises:
|
||||
PacmanError: in case if no last-modified header was found
|
||||
"""
|
||||
if not local_path.is_file():
|
||||
return True # no local file found, requires to update
|
||||
|
||||
response = self.make_request("HEAD", url)
|
||||
if self.LAST_MODIFIED_HEADER not in response.headers:
|
||||
raise PacmanError("No last-modified header found")
|
||||
|
||||
remote_changed = parsedate_to_datetime(response.headers["Last-Modified"]).timestamp()
|
||||
local_changed = local_path.stat().st_mtime
|
||||
|
||||
return remote_changed > local_changed
|
||||
|
||||
def sync(self, *, force: bool) -> None:
|
||||
"""
|
||||
sync packages and files databases
|
||||
|
||||
Args:
|
||||
force(bool): force database synchronization (same as ``pacman -Syy``)
|
||||
"""
|
||||
try:
|
||||
self.sync_packages(force=force)
|
||||
if self.sync_files_database:
|
||||
self.sync_files(force=force)
|
||||
except Exception:
|
||||
self.logger.exception("exception during update %s", self.database.name)
|
||||
|
||||
def sync_files(self, *, force: bool) -> None:
|
||||
"""
|
||||
sync files by using http request
|
||||
|
||||
Args:
|
||||
force(bool): force database synchronization (same as ``pacman -Syy``)
|
||||
"""
|
||||
server = next(iter(self.database.servers))
|
||||
filename = f"{self.database.name}.files.tar.gz"
|
||||
url = f"{server}/{filename}"
|
||||
|
||||
remote_uri = urlparse(url)
|
||||
local_path = Path(self.repository_paths.pacman / "sync" / filename)
|
||||
|
||||
match remote_uri.scheme:
|
||||
case "http" | "https":
|
||||
if not force and not self.is_outdated(url, local_path):
|
||||
return
|
||||
|
||||
self.download(url, local_path)
|
||||
|
||||
case "file":
|
||||
# just copy file as it is relatively cheap operation, no need to check timestamps
|
||||
self.copy(Path(remote_uri.path), local_path)
|
||||
|
||||
case other:
|
||||
raise PacmanError(f"Unknown or unsupported URL scheme {other}")
|
||||
|
||||
def sync_packages(self, *, force: bool) -> None:
|
||||
"""
|
||||
sync packages by using built-in pyalpm methods
|
||||
|
||||
Args:
|
||||
force(bool): force database synchronization (same as ``pacman -Syy``)
|
||||
"""
|
||||
self.database.update(force)
|
@ -56,6 +56,6 @@ class OfficialSyncdb(Official):
|
||||
raise UnknownPackageError(package_name)
|
||||
|
||||
try:
|
||||
return next(AURPackage.from_pacman(package) for package in pacman.package_get(package_name))
|
||||
return next(AURPackage.from_pacman(package) for package in pacman.package(package_name))
|
||||
except StopIteration:
|
||||
raise UnknownPackageError(package_name) from None
|
||||
|
@ -21,7 +21,7 @@ from pathlib import Path
|
||||
|
||||
from ahriman.core.exceptions import BuildError
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.util import check_output
|
||||
from ahriman.core.utils import check_output
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ class Repo(LazyLogging):
|
||||
path(Path): path to archive to add
|
||||
"""
|
||||
check_output(
|
||||
"repo-add", *self.sign_args, "-R", str(self.repo_path), str(path),
|
||||
"repo-add", *self.sign_args, "--remove", str(self.repo_path), str(path),
|
||||
exception=BuildError.from_process(path.name),
|
||||
cwd=self.paths.repository,
|
||||
logger=self.logger,
|
||||
@ -78,8 +78,13 @@ class Repo(LazyLogging):
|
||||
"""
|
||||
create empty repository database. It just calls add with empty arguments
|
||||
"""
|
||||
check_output("repo-add", *self.sign_args, str(self.repo_path),
|
||||
cwd=self.paths.repository, logger=self.logger, user=self.uid)
|
||||
# since pacman-6.1.0 repo-add doesn't create empty database in case if no packages supplied
|
||||
# this code creates empty files instead
|
||||
if self.repo_path.exists():
|
||||
return # database is already created, skip this part
|
||||
|
||||
self.repo_path.touch(exist_ok=True)
|
||||
(self.paths.repository / f"{self.name}.db").symlink_to(self.repo_path)
|
||||
|
||||
def remove(self, package: str, filename: Path) -> None:
|
||||
"""
|
||||
|
@ -81,15 +81,18 @@ class Auth(LazyLogging):
|
||||
case AuthSettings.OAuth:
|
||||
from ahriman.core.auth.oauth import OAuth
|
||||
return OAuth(configuration, database)
|
||||
case AuthSettings.PAM:
|
||||
from ahriman.core.auth.pam import PAM
|
||||
return PAM(configuration, database)
|
||||
case _:
|
||||
return Auth(configuration)
|
||||
|
||||
async def check_credentials(self, username: str | None, password: str | None) -> bool:
|
||||
async def check_credentials(self, username: str, password: str | None) -> bool:
|
||||
"""
|
||||
validate user password
|
||||
|
||||
Args:
|
||||
username(str | None): username
|
||||
username(str): username
|
||||
password(str | None): entered password
|
||||
|
||||
Returns:
|
||||
@ -98,12 +101,12 @@ class Auth(LazyLogging):
|
||||
del username, password
|
||||
return True
|
||||
|
||||
async def known_username(self, username: str | None) -> bool:
|
||||
async def known_username(self, username: str) -> bool:
|
||||
"""
|
||||
check if user is known
|
||||
|
||||
Args:
|
||||
username(str | None): username
|
||||
username(str): username
|
||||
|
||||
Returns:
|
||||
bool: True in case if user is known and can be authorized and False otherwise
|
||||
|
@ -48,18 +48,18 @@ class Mapping(Auth):
|
||||
self.database = database
|
||||
self.salt = configuration.get("auth", "salt", fallback="")
|
||||
|
||||
async def check_credentials(self, username: str | None, password: str | None) -> bool:
|
||||
async def check_credentials(self, username: str, password: str | None) -> bool:
|
||||
"""
|
||||
validate user password
|
||||
|
||||
Args:
|
||||
username(str | None): username
|
||||
username(str): username
|
||||
password(str | None): entered password
|
||||
|
||||
Returns:
|
||||
bool: True in case if password matches, False otherwise
|
||||
"""
|
||||
if username is None or password is None:
|
||||
if password is None:
|
||||
return False # invalid data supplied
|
||||
user = self.get_user(username)
|
||||
return user is not None and user.check_credentials(password, self.salt)
|
||||
@ -76,12 +76,12 @@ class Mapping(Auth):
|
||||
"""
|
||||
return self.database.user_get(username)
|
||||
|
||||
async def known_username(self, username: str | None) -> bool:
|
||||
async def known_username(self, username: str) -> bool:
|
||||
"""
|
||||
check if user is known
|
||||
|
||||
Args:
|
||||
username(str | None): username
|
||||
username(str): username
|
||||
|
||||
Returns:
|
||||
bool: True in case if user is known and can be authorized and False otherwise
|
||||
|
@ -69,7 +69,8 @@ class OAuth(Mapping):
|
||||
Returns:
|
||||
str: login control as html code to insert
|
||||
"""
|
||||
return f"""<a class="nav-link" href="/api/v1/login" title="login via OAuth2"><i class="bi bi-{self.icon}"></i> login</a>"""
|
||||
return f"""<a class="nav-link" href="/api/v1/login" title="login via OAuth2"><i class="bi bi-{
|
||||
self.icon}"></i> login</a>"""
|
||||
|
||||
@staticmethod
|
||||
def get_provider(name: str) -> type[aioauth_client.OAuth2Client]:
|
||||
|
131
src/ahriman/core/auth/pam.py
Normal file
131
src/ahriman/core/auth/pam.py
Normal file
@ -0,0 +1,131 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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/>.
|
||||
#
|
||||
from grp import getgrnam
|
||||
from pwd import getpwnam
|
||||
|
||||
from ahriman.core.auth.mapping import Mapping
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.exceptions import CalledProcessError
|
||||
from ahriman.core.utils import check_output
|
||||
from ahriman.models.auth_settings import AuthSettings
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
|
||||
class PAM(Mapping):
|
||||
"""
|
||||
User authorization implementation by using default PAM
|
||||
|
||||
Attributes:
|
||||
full_access_group(str): group name users of which have full access
|
||||
permit_root_login(bool): permit login as root
|
||||
"""
|
||||
|
||||
def __init__(self, configuration: Configuration, database: SQLite,
|
||||
provider: AuthSettings = AuthSettings.PAM) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
database(SQLite): database instance
|
||||
provider(AuthSettings, optional): authorization type definition (Default value = AuthSettings.PAM)
|
||||
"""
|
||||
Mapping.__init__(self, configuration, database, provider)
|
||||
self.full_access_group = configuration.get("auth", "full_access_group")
|
||||
self.permit_root_login = configuration.getboolean("auth", "permit_root_login", fallback=False)
|
||||
|
||||
@staticmethod
|
||||
def group_members(group_name: str) -> list[str]:
|
||||
"""
|
||||
extract current group members
|
||||
|
||||
Args:
|
||||
group_name(str): group name
|
||||
|
||||
Returns:
|
||||
list[str]: list of users which belong to the specified group. In case if group wasn't found, the empty list
|
||||
will be returned
|
||||
"""
|
||||
try:
|
||||
group = getgrnam(group_name)
|
||||
except KeyError:
|
||||
return []
|
||||
return group.gr_mem
|
||||
|
||||
async def check_credentials(self, username: str, password: str | None) -> bool:
|
||||
"""
|
||||
validate user password
|
||||
|
||||
Args:
|
||||
username(str): username
|
||||
password(str | None): entered password
|
||||
|
||||
Returns:
|
||||
bool: True in case if password matches, False otherwise
|
||||
"""
|
||||
if password is None:
|
||||
return False # invalid data supplied
|
||||
if not self.permit_root_login and username == "root":
|
||||
return False # login as root is not allowed
|
||||
# the reason why do we call su here is that python-pam actually read shadow file
|
||||
# and hence requires root privileges
|
||||
try:
|
||||
check_output("su", "--command", "true", "-", username, input_data=password)
|
||||
return True
|
||||
except CalledProcessError:
|
||||
return await Mapping.check_credentials(self, username, password)
|
||||
|
||||
async def known_username(self, username: str) -> bool:
|
||||
"""
|
||||
check if user is known
|
||||
|
||||
Args:
|
||||
username(str): username
|
||||
|
||||
Returns:
|
||||
bool: True in case if user is known and can be authorized and False otherwise
|
||||
"""
|
||||
try:
|
||||
_ = getpwnam(username)
|
||||
return True
|
||||
except KeyError:
|
||||
return await Mapping.known_username(self, username)
|
||||
|
||||
async def verify_access(self, username: str, required: UserAccess, context: str | None) -> bool:
|
||||
"""
|
||||
validate if user has access to requested resource
|
||||
|
||||
Args:
|
||||
username(str): username
|
||||
required(UserAccess): required access level
|
||||
context(str | None): URI request path
|
||||
|
||||
Returns:
|
||||
bool: True in case if user is allowed to do this request and False otherwise
|
||||
"""
|
||||
# this method is basically inverted, first we check overrides in database and then fallback to the PAM logic
|
||||
if (user := self.get_user(username)) is not None:
|
||||
return user.verify_access(required)
|
||||
# if username is in admin group, then we treat it as full access
|
||||
if username in self.group_members(self.full_access_group):
|
||||
return UserAccess.Full.permits(required)
|
||||
# fallback to read-only accounts
|
||||
return UserAccess.Read.permits(required)
|
@ -19,11 +19,12 @@
|
||||
#
|
||||
import shutil
|
||||
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.core.exceptions import CalledProcessError
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.util import check_output, utcnow, walk
|
||||
from ahriman.core.utils import check_output, utcnow, walk
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
@ -38,10 +39,14 @@ class Sources(LazyLogging):
|
||||
DEFAULT_BRANCH(str): (class attribute) default branch to process git repositories.
|
||||
Must be used only for local stored repositories, use RemoteSource descriptor instead for real packages
|
||||
DEFAULT_COMMIT_AUTHOR(tuple[str, str]): (class attribute) default commit author to be used if none set
|
||||
GITCONFIG(dict[str, str]): (class attribute) git config options to suppress annoying hints
|
||||
"""
|
||||
|
||||
DEFAULT_BRANCH = "master" # default fallback branch
|
||||
DEFAULT_COMMIT_AUTHOR = ("ahriman", "ahriman@localhost")
|
||||
GITCONFIG = {
|
||||
"init.defaultBranch": DEFAULT_BRANCH,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def changes(source_dir: Path, last_commit_sha: str | None) -> str | None:
|
||||
@ -106,15 +111,15 @@ class Sources(LazyLogging):
|
||||
instance.fetch_until(sources_dir, branch=branch)
|
||||
elif remote.git_url is not None:
|
||||
instance.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch)
|
||||
check_output("git", "clone", "--quiet", "--depth", "1", "--branch", branch, "--single-branch",
|
||||
check_output(*instance.git(), "clone", "--quiet", "--depth", "1", "--branch", branch, "--single-branch",
|
||||
remote.git_url, str(sources_dir), cwd=sources_dir.parent, logger=instance.logger)
|
||||
else:
|
||||
# it will cause an exception later
|
||||
instance.logger.error("%s is not initialized, but no remote provided", sources_dir)
|
||||
|
||||
# and now force reset to our branch
|
||||
check_output("git", "checkout", "--force", branch, cwd=sources_dir, logger=instance.logger)
|
||||
check_output("git", "reset", "--quiet", "--hard", f"origin/{branch}",
|
||||
check_output(*instance.git(), "checkout", "--force", branch, cwd=sources_dir, logger=instance.logger)
|
||||
check_output(*instance.git(), "reset", "--quiet", "--hard", f"origin/{branch}",
|
||||
cwd=sources_dir, logger=instance.logger)
|
||||
|
||||
# move content if required
|
||||
@ -136,7 +141,7 @@ class Sources(LazyLogging):
|
||||
bool: True in case if there is any remote and false otherwise
|
||||
"""
|
||||
instance = Sources()
|
||||
remotes = check_output("git", "remote", cwd=sources_dir, logger=instance.logger)
|
||||
remotes = check_output(*instance.git(), "remote", cwd=sources_dir, logger=instance.logger)
|
||||
return bool(remotes)
|
||||
|
||||
@staticmethod
|
||||
@ -150,7 +155,7 @@ class Sources(LazyLogging):
|
||||
instance = Sources()
|
||||
if not (sources_dir / ".git").is_dir():
|
||||
# skip initializing in case if it was already
|
||||
check_output("git", "init", "--quiet", "--initial-branch", instance.DEFAULT_BRANCH,
|
||||
check_output(*instance.git(), "init", "--quiet", "--initial-branch", instance.DEFAULT_BRANCH,
|
||||
cwd=sources_dir, logger=instance.logger)
|
||||
|
||||
# extract local files...
|
||||
@ -220,7 +225,7 @@ class Sources(LazyLogging):
|
||||
return # no changes to push, just skip action
|
||||
|
||||
git_url, branch = remote.git_source()
|
||||
check_output("git", "push", "--quiet", git_url, branch, cwd=sources_dir, logger=instance.logger)
|
||||
check_output(*instance.git(), "push", "--quiet", git_url, branch, cwd=sources_dir, logger=instance.logger)
|
||||
|
||||
def add(self, sources_dir: Path, *pattern: str, intent_to_add: bool = False) -> None:
|
||||
"""
|
||||
@ -241,7 +246,7 @@ class Sources(LazyLogging):
|
||||
self.logger.info("found matching files %s", found_files)
|
||||
# add them to index
|
||||
args = ["--intent-to-add"] if intent_to_add else []
|
||||
check_output("git", "add", *args, *[str(fn.relative_to(sources_dir)) for fn in found_files],
|
||||
check_output(*self.git(), "add", *args, *[str(fn.relative_to(sources_dir)) for fn in found_files],
|
||||
cwd=sources_dir, logger=self.logger)
|
||||
|
||||
def commit(self, sources_dir: Path, message: str | None = None,
|
||||
@ -264,15 +269,16 @@ class Sources(LazyLogging):
|
||||
if message is None:
|
||||
message = f"Autogenerated commit at {utcnow()}"
|
||||
args = ["--message", message]
|
||||
environment: dict[str, str] = {}
|
||||
|
||||
if commit_author is None:
|
||||
commit_author = self.DEFAULT_COMMIT_AUTHOR
|
||||
user, email = commit_author
|
||||
environment["GIT_AUTHOR_NAME"] = environment["GIT_COMMITTER_NAME"] = user
|
||||
environment["GIT_AUTHOR_EMAIL"] = environment["GIT_COMMITTER_EMAIL"] = email
|
||||
gitconfig = {
|
||||
"user.email": email,
|
||||
"user.name": user,
|
||||
}
|
||||
|
||||
check_output("git", "commit", "--quiet", *args, cwd=sources_dir, logger=self.logger, environment=environment)
|
||||
check_output(*self.git(gitconfig), "commit", "--quiet", *args, cwd=sources_dir, logger=self.logger)
|
||||
|
||||
return True
|
||||
|
||||
@ -290,7 +296,7 @@ class Sources(LazyLogging):
|
||||
args = []
|
||||
if sha is not None:
|
||||
args.append(sha)
|
||||
return check_output("git", "diff", *args, cwd=sources_dir, logger=self.logger)
|
||||
return check_output(*self.git(), "diff", *args, cwd=sources_dir, logger=self.logger)
|
||||
|
||||
def fetch_until(self, sources_dir: Path, *, branch: str | None = None, commit_sha: str | None = None) -> None:
|
||||
"""
|
||||
@ -306,18 +312,37 @@ class Sources(LazyLogging):
|
||||
|
||||
commits_count = 1
|
||||
while commit_sha is not None:
|
||||
command = ["git", "fetch", "--quiet", "--depth", str(commits_count)]
|
||||
command = self.git() + ["fetch", "--quiet", "--depth", str(commits_count)]
|
||||
if branch is not None:
|
||||
command += ["origin", branch]
|
||||
check_output(*command, cwd=sources_dir, logger=self.logger) # fetch one more level
|
||||
|
||||
try:
|
||||
# check if there is an object in current git directory
|
||||
check_output("git", "cat-file", "-e", commit_sha, cwd=sources_dir, logger=self.logger)
|
||||
check_output(*self.git(), "cat-file", "-e", commit_sha, cwd=sources_dir, logger=self.logger)
|
||||
commit_sha = None # reset search
|
||||
except CalledProcessError:
|
||||
commits_count += 1 # increase depth
|
||||
|
||||
def git(self, gitconfig: dict[str, str] | None = None) -> list[str]:
|
||||
"""
|
||||
git command prefix
|
||||
|
||||
Args:
|
||||
gitconfig(dict[str, str] | None, optional): additional git config flags if any (Default value = None)
|
||||
|
||||
Returns:
|
||||
list[str]: git command prefix with valid default flags
|
||||
"""
|
||||
gitconfig = gitconfig or {}
|
||||
|
||||
def configuration_flags() -> Generator[str, None, None]:
|
||||
for option, value in (self.GITCONFIG | gitconfig).items():
|
||||
yield "-c"
|
||||
yield f"{option}=\"{value}\""
|
||||
|
||||
return ["git"] + list(configuration_flags())
|
||||
|
||||
def has_changes(self, sources_dir: Path) -> bool:
|
||||
"""
|
||||
check if there are changes in current git tree
|
||||
@ -329,7 +354,7 @@ class Sources(LazyLogging):
|
||||
bool: True if there are uncommitted changes and False otherwise
|
||||
"""
|
||||
# there is --exit-code argument to diff, however, there might be other process errors
|
||||
changes = check_output("git", "diff", "--cached", "--name-only", cwd=sources_dir, logger=self.logger)
|
||||
changes = check_output(*self.git(), "diff", "--cached", "--name-only", cwd=sources_dir, logger=self.logger)
|
||||
return bool(changes)
|
||||
|
||||
def head(self, sources_dir: Path, ref_name: str = "HEAD") -> str:
|
||||
@ -344,7 +369,7 @@ class Sources(LazyLogging):
|
||||
str: HEAD commit hash
|
||||
"""
|
||||
# we might want to parse git files instead though
|
||||
return check_output("git", "rev-parse", ref_name, cwd=sources_dir, logger=self.logger)
|
||||
return check_output(*self.git(), "rev-parse", ref_name, cwd=sources_dir, logger=self.logger)
|
||||
|
||||
def move(self, pkgbuild_dir: Path, sources_dir: Path) -> None:
|
||||
"""
|
||||
@ -372,7 +397,7 @@ class Sources(LazyLogging):
|
||||
# create patch
|
||||
self.logger.info("apply patch %s from database at %s", patch.key, sources_dir)
|
||||
if patch.is_plain_diff:
|
||||
check_output("git", "apply", "--ignore-space-change", "--ignore-whitespace",
|
||||
check_output(*self.git(), "apply", "--ignore-space-change", "--ignore-whitespace",
|
||||
cwd=sources_dir, input_data=patch.serialize(), logger=self.logger)
|
||||
else:
|
||||
patch.write(sources_dir / "PKGBUILD")
|
||||
|
@ -21,10 +21,9 @@ from pathlib import Path
|
||||
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.exceptions import BuildError
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.util import check_output
|
||||
from ahriman.core.utils import check_output
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
@ -38,6 +37,7 @@ class Task(LazyLogging):
|
||||
archbuild_flags(list[str]): command flags for archbuild command
|
||||
architecture(str): repository architecture
|
||||
build_command(str): build command
|
||||
include_debug_packages(bool): whether to include debug packages or not
|
||||
makechrootpkg_flags(list[str]): command flags for makechrootpkg command
|
||||
makepkg_flags(list[str]): command flags for makepkg command
|
||||
package(Package): package definitions
|
||||
@ -63,6 +63,7 @@ class Task(LazyLogging):
|
||||
|
||||
self.archbuild_flags = configuration.getlist("build", "archbuild_flags", fallback=[])
|
||||
self.build_command = configuration.get("build", "build_command")
|
||||
self.include_debug_packages = configuration.getboolean("build", "include_debug_packages", fallback=True)
|
||||
self.makepkg_flags = configuration.getlist("build", "makepkg_flags", fallback=[])
|
||||
self.makechrootpkg_flags = configuration.getlist("build", "makechrootpkg_flags", fallback=[])
|
||||
|
||||
@ -99,30 +100,35 @@ class Task(LazyLogging):
|
||||
environment=environment,
|
||||
)
|
||||
|
||||
# well it is not actually correct, but we can deal with it
|
||||
package_list_command = ["makepkg", "--packagelist"]
|
||||
if not self.include_debug_packages:
|
||||
package_list_command.append("OPTIONS=(!debug)") # disable debug flag manually
|
||||
packages = check_output(
|
||||
"makepkg", "--packagelist",
|
||||
*package_list_command,
|
||||
exception=BuildError.from_process(self.package.base),
|
||||
cwd=sources_dir,
|
||||
logger=self.logger,
|
||||
environment=environment,
|
||||
).splitlines()
|
||||
return [Path(package) for package in packages]
|
||||
# some dirty magic here
|
||||
# the filter is applied in order to make sure that result will only contain packages which were actually built
|
||||
# e.g. in some cases packagelist command produces debug packages which were not actually built
|
||||
return list(filter(lambda path: path.is_file(), map(Path, packages)))
|
||||
|
||||
def init(self, sources_dir: Path, database: SQLite, local_version: str | None) -> str | None:
|
||||
def init(self, sources_dir: Path, patches: list[PkgbuildPatch], local_version: str | None) -> str | None:
|
||||
"""
|
||||
fetch package from git
|
||||
|
||||
Args:
|
||||
sources_dir(Path): local path to fetch
|
||||
database(SQLite): database instance
|
||||
patches(list[PkgbuildPatch]): list of patches for the package
|
||||
local_version(str | None): local version of the package. If set and equal to current version, it will
|
||||
automatically bump pkgrel
|
||||
|
||||
Returns:
|
||||
str | None: current commit sha if available
|
||||
"""
|
||||
last_commit_sha = Sources.load(sources_dir, self.package, database.patches_get(self.package.base), self.paths)
|
||||
last_commit_sha = Sources.load(sources_dir, self.package, patches, self.paths)
|
||||
if local_version is None:
|
||||
return last_commit_sha # there is no local package or pkgrel increment is disabled
|
||||
|
||||
|
@ -89,6 +89,11 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"path_exists": True,
|
||||
"path_type": "dir",
|
||||
},
|
||||
"sync_files_database": {
|
||||
"type": "boolean",
|
||||
"coerce": "boolean",
|
||||
"required": True,
|
||||
},
|
||||
"use_ahriman_cache": {
|
||||
"type": "boolean",
|
||||
"coerce": "boolean",
|
||||
@ -110,6 +115,7 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"oauth_provider",
|
||||
"oauth_scopes",
|
||||
]},
|
||||
{"allowed": ["pam"], "dependencies": ["full_access_group"]},
|
||||
],
|
||||
},
|
||||
"allow_read_only": {
|
||||
@ -130,6 +136,10 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"minlength": 32,
|
||||
"maxlength": 64, # we cannot verify maxlength, because base64 representation might be longer than bytes
|
||||
},
|
||||
"full_access_group": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"max_age": {
|
||||
"type": "integer",
|
||||
"coerce": "integer",
|
||||
@ -147,6 +157,10 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"permit_root_login": {
|
||||
"type": "boolean",
|
||||
"coerce": "boolean",
|
||||
},
|
||||
"salt": {
|
||||
"type": "string",
|
||||
},
|
||||
@ -155,6 +169,14 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"build": {
|
||||
"type": "dict",
|
||||
"schema": {
|
||||
"allowed_scan_paths": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
},
|
||||
},
|
||||
"archbuild_flags": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
@ -163,6 +185,14 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
"blacklisted_scan_paths": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
},
|
||||
},
|
||||
"build_command": {
|
||||
"type": "string",
|
||||
"required": True,
|
||||
@ -176,6 +206,10 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
"include_debug_packages": {
|
||||
"type": "boolean",
|
||||
"coerce": "boolean",
|
||||
},
|
||||
"makepkg_flags": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
|
@ -21,7 +21,7 @@ from sqlite3 import Connection
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.util import package_like
|
||||
from ahriman.core.utils import package_like
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pacman_synchronization import PacmanSynchronization
|
||||
|
||||
|
@ -21,7 +21,7 @@ from sqlite3 import Connection
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.util import package_like
|
||||
from ahriman.core.utils import package_like
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pacman_synchronization import PacmanSynchronization
|
||||
|
||||
|
@ -21,7 +21,7 @@ from sqlite3 import Connection
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.util import package_like
|
||||
from ahriman.core.utils import package_like
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pacman_synchronization import PacmanSynchronization
|
||||
|
||||
|
32
src/ahriman/core/database/migrations/m013_dependencies.py
Normal file
32
src/ahriman/core/database/migrations/m013_dependencies.py
Normal file
@ -0,0 +1,32 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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/>.
|
||||
#
|
||||
__all__ = ["steps"]
|
||||
|
||||
|
||||
steps = [
|
||||
"""
|
||||
create table package_dependencies (
|
||||
package_base text not null,
|
||||
repository text not null,
|
||||
dependencies json not null,
|
||||
unique (package_base, repository)
|
||||
)
|
||||
""",
|
||||
]
|
@ -20,6 +20,7 @@
|
||||
from ahriman.core.database.operations.auth_operations import AuthOperations
|
||||
from ahriman.core.database.operations.build_operations import BuildOperations
|
||||
from ahriman.core.database.operations.changes_operations import ChangesOperations
|
||||
from ahriman.core.database.operations.dependencies_operations import DependenciesOperations
|
||||
from ahriman.core.database.operations.logs_operations import LogsOperations
|
||||
from ahriman.core.database.operations.package_operations import PackageOperations
|
||||
from ahriman.core.database.operations.patch_operations import PatchOperations
|
||||
|
@ -64,7 +64,7 @@ class ChangesOperations(Operations):
|
||||
|
||||
def changes_insert(self, package_base: str, changes: Changes, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
insert packages to build queue
|
||||
insert package changes
|
||||
|
||||
Args:
|
||||
package_base(str): package base to insert
|
||||
@ -117,27 +117,3 @@ class ChangesOperations(Operations):
|
||||
})
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def hashes_get(self, repository_id: RepositoryId | None = None) -> dict[str, str]:
|
||||
"""
|
||||
extract last commit hashes if available
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Returns:
|
||||
dict[str, str]: map of package base to its last commit hash
|
||||
"""
|
||||
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> dict[str, str]:
|
||||
return {
|
||||
row["package_base"]: row["last_commit_sha"]
|
||||
for row in connection.execute(
|
||||
"""select package_base, last_commit_sha from package_changes where repository = :repository""",
|
||||
{"repository": repository_id.id}
|
||||
)
|
||||
}
|
||||
|
||||
return self.with_connection(run)
|
||||
|
116
src/ahriman/core/database/operations/dependencies_operations.py
Normal file
116
src/ahriman/core/database/operations/dependencies_operations.py
Normal file
@ -0,0 +1,116 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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/>.
|
||||
#
|
||||
from sqlite3 import Connection
|
||||
|
||||
from ahriman.core.database.operations.operations import Operations
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class DependenciesOperations(Operations):
|
||||
"""
|
||||
operations for dependencies table
|
||||
"""
|
||||
|
||||
def dependencies_get(self, package_base: str | None = None,
|
||||
repository_id: RepositoryId | None = None) -> dict[str, Dependencies]:
|
||||
"""
|
||||
get dependencies for the specific package base if available
|
||||
|
||||
Args:
|
||||
package_base(str | None): package base to search
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Returns:
|
||||
Dependencies: changes for the package base if available
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> dict[str, Dependencies]:
|
||||
return {
|
||||
row["package_base"]: Dependencies(row["dependencies"])
|
||||
for row in connection.execute(
|
||||
"""
|
||||
select package_base, dependencies from package_dependencies
|
||||
where (:package_base is null or package_base = :package_base)
|
||||
and repository = :repository
|
||||
""",
|
||||
{
|
||||
"package_base": package_base,
|
||||
"repository": repository_id.id,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return self.with_connection(run)
|
||||
|
||||
def dependencies_insert(self, package_base: str, dependencies: Dependencies,
|
||||
repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
insert package dependencies
|
||||
|
||||
Args:
|
||||
package_base(str): package base
|
||||
dependencies(Dependencies): package dependencies
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
insert into package_dependencies
|
||||
(package_base, repository, dependencies)
|
||||
values
|
||||
(:package_base, :repository, :dependencies)
|
||||
on conflict (package_base, repository) do update set
|
||||
dependencies = :dependencies
|
||||
""",
|
||||
{
|
||||
"package_base": package_base,
|
||||
"repository": repository_id.id,
|
||||
"dependencies": dependencies.paths,
|
||||
})
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def dependencies_remove(self, package_base: str | None, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
remove packages dependencies
|
||||
|
||||
Args:
|
||||
package_base(str | None): optional filter by package base
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
delete from package_dependencies
|
||||
where (:package_base is null or package_base = :package_base)
|
||||
and repository = :repository
|
||||
""",
|
||||
{
|
||||
"package_base": package_base,
|
||||
"repository": repository_id.id,
|
||||
})
|
||||
|
||||
return self.with_connection(run, commit=True)
|
@ -25,6 +25,7 @@ from typing import Any, TypeVar
|
||||
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
@ -38,7 +39,7 @@ class Operations(LazyLogging):
|
||||
path(Path): path to the database file
|
||||
"""
|
||||
|
||||
def __init__(self, path: Path, repository_id: RepositoryId) -> None:
|
||||
def __init__(self, path: Path, repository_id: RepositoryId, repository_paths: RepositoryPaths) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
@ -48,6 +49,7 @@ class Operations(LazyLogging):
|
||||
"""
|
||||
self.path = path
|
||||
self._repository_id = repository_id
|
||||
self._repository_paths = repository_paths
|
||||
|
||||
@staticmethod
|
||||
def factory(cursor: sqlite3.Cursor, row: tuple[Any, ...]) -> dict[str, Any]:
|
||||
|
@ -150,34 +150,6 @@ class PackageOperations(Operations):
|
||||
""",
|
||||
package_list)
|
||||
|
||||
@staticmethod
|
||||
def _package_update_insert_status(connection: Connection, package_base: str, status: BuildStatus,
|
||||
repository_id: RepositoryId) -> None:
|
||||
"""
|
||||
insert base package status into table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
package_base(str): package base name
|
||||
status(BuildStatus): new build status
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
connection.execute(
|
||||
"""
|
||||
insert into package_statuses
|
||||
(package_base, status, last_updated, repository)
|
||||
values
|
||||
(:package_base, :status, :last_updated, :repository)
|
||||
on conflict (package_base, repository) do update set
|
||||
status = :status, last_updated = :last_updated
|
||||
""",
|
||||
{
|
||||
"package_base": package_base,
|
||||
"status": status.status.value,
|
||||
"last_updated": status.timestamp,
|
||||
"repository": repository_id.id,
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def _packages_get_select_package_bases(connection: Connection, repository_id: RepositoryId) -> dict[str, Package]:
|
||||
"""
|
||||
@ -246,21 +218,6 @@ class PackageOperations(Operations):
|
||||
)
|
||||
}
|
||||
|
||||
def package_base_update(self, package: Package, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
update package base only
|
||||
|
||||
Args:
|
||||
package(Package): package properties
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
self._package_update_insert_base(connection, package, repository_id)
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def package_remove(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
remove package from database
|
||||
@ -277,20 +234,18 @@ class PackageOperations(Operations):
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def package_update(self, package: Package, status: BuildStatus, repository_id: RepositoryId | None = None) -> None:
|
||||
def package_update(self, package: Package, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
update package status
|
||||
|
||||
Args:
|
||||
package(Package): package properties
|
||||
status(BuildStatus): new build status
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
self._package_update_insert_base(connection, package, repository_id)
|
||||
self._package_update_insert_status(connection, package.base, status, repository_id)
|
||||
self._package_update_insert_packages(connection, package, repository_id)
|
||||
self._package_remove_packages(connection, package.base, package.packages.keys(), repository_id)
|
||||
|
||||
@ -317,22 +272,32 @@ class PackageOperations(Operations):
|
||||
|
||||
return self.with_connection(lambda connection: list(run(connection)))
|
||||
|
||||
def remotes_get(self, repository_id: RepositoryId | None = None) -> dict[str, RemoteSource]:
|
||||
def status_update(self, package_base: str, status: BuildStatus, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
get packages remotes based on current settings
|
||||
insert base package status into table
|
||||
|
||||
Args:
|
||||
package_base(str): package base name
|
||||
status(BuildStatus): new build status
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Returns:
|
||||
dict[str, RemoteSource]: map of package base to its remote sources
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> dict[str, Package]:
|
||||
return self._packages_get_select_package_bases(connection, repository_id)
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
insert into package_statuses
|
||||
(package_base, status, last_updated, repository)
|
||||
values
|
||||
(:package_base, :status, :last_updated, :repository)
|
||||
on conflict (package_base, repository) do update set
|
||||
status = :status, last_updated = :last_updated
|
||||
""",
|
||||
{
|
||||
"package_base": package_base,
|
||||
"status": status.status.value,
|
||||
"last_updated": status.timestamp,
|
||||
"repository": repository_id.id,
|
||||
})
|
||||
|
||||
return {
|
||||
package_base: package.remote
|
||||
for package_base, package in self.with_connection(run).items()
|
||||
}
|
||||
return self.with_connection(run, commit=True)
|
||||
|
@ -25,12 +25,20 @@ from typing import Self
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database.migrations import Migrations
|
||||
from ahriman.core.database.operations import AuthOperations, BuildOperations, ChangesOperations, LogsOperations, \
|
||||
PackageOperations, PatchOperations
|
||||
from ahriman.core.database.operations import AuthOperations, BuildOperations, ChangesOperations, \
|
||||
DependenciesOperations, LogsOperations, PackageOperations, PatchOperations
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
# pylint: disable=too-many-ancestors
|
||||
class SQLite(AuthOperations, BuildOperations, ChangesOperations, LogsOperations, PackageOperations, PatchOperations):
|
||||
class SQLite(
|
||||
AuthOperations,
|
||||
BuildOperations,
|
||||
ChangesOperations,
|
||||
DependenciesOperations,
|
||||
LogsOperations,
|
||||
PackageOperations,
|
||||
PatchOperations):
|
||||
"""
|
||||
wrapper for sqlite3 database
|
||||
|
||||
@ -59,7 +67,7 @@ class SQLite(AuthOperations, BuildOperations, ChangesOperations, LogsOperations,
|
||||
path = cls.database_path(configuration)
|
||||
_, repository_id = configuration.check_loaded()
|
||||
|
||||
database = cls(path, repository_id)
|
||||
database = cls(path, repository_id, configuration.repository_paths)
|
||||
database.init(configuration)
|
||||
|
||||
return database
|
||||
@ -94,3 +102,27 @@ class SQLite(AuthOperations, BuildOperations, ChangesOperations, LogsOperations,
|
||||
if configuration.getboolean("settings", "apply_migrations", fallback=True):
|
||||
self.with_connection(lambda connection: Migrations.migrate(connection, configuration))
|
||||
paths.chown(self.path)
|
||||
|
||||
def package_clear(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
completely remove package from all tables
|
||||
|
||||
Args:
|
||||
package_base(str): package base to remove
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Examples:
|
||||
This method completely removes the package from all tables and must be used, e.g. on package removal::
|
||||
|
||||
>>> database.package_clear("ahriman")
|
||||
"""
|
||||
self.build_queue_clear(package_base, repository_id)
|
||||
self.patches_remove(package_base, None)
|
||||
self.logs_remove(package_base, None, repository_id)
|
||||
self.changes_remove(package_base, repository_id)
|
||||
self.dependencies_remove(package_base, repository_id)
|
||||
|
||||
self.package_remove(package_base, repository_id)
|
||||
|
||||
# remove local cache too
|
||||
self._repository_paths.tree_clear(package_base)
|
||||
|
@ -219,6 +219,21 @@ class PackageInfoError(RuntimeError):
|
||||
RuntimeError.__init__(self, f"There are errors during reading package information: `{details}`")
|
||||
|
||||
|
||||
class PacmanError(RuntimeError):
|
||||
"""
|
||||
exception in case of pacman operation errors
|
||||
"""
|
||||
|
||||
def __init__(self, details: Any) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
details(Any): error details
|
||||
"""
|
||||
RuntimeError.__init__(self, f"Could not perform operation with pacman: `{details}`")
|
||||
|
||||
|
||||
class PathError(ValueError):
|
||||
"""
|
||||
exception which will be raised on path which is not belong to root directory
|
||||
@ -330,5 +345,5 @@ class UnsafeRunError(RuntimeError):
|
||||
root_uid(int): ID of the owner of root directory
|
||||
"""
|
||||
RuntimeError.__init__(self, f"Current UID {current_uid} differs from root owner {root_uid}. "
|
||||
f"Note that for the most actions it is unsafe to run application as different user."
|
||||
f" If you are 100% sure that it must be there try --unsafe option")
|
||||
f"Note that for the most actions it is unsafe to run application as different user."
|
||||
f" If you are 100% sure that it must be there try --unsafe option")
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user