dnf-plugin-anyrepo is a DNF plugin that makes remote RPM assets available to ordinary DNF commands as transparent, dynamic local RPM repositories.
The current implementation supports GitHub Releases via source = github-release, and other asset sources may be added in the future.
With this plugin, users can install and update RPMs published as remote release assets through ordinary DNF commands.
AnyRepo refreshes and registers the matching assets as local file:// repositories behind the scenes, so users do not need to download RPM files or manage repository metadata manually.
Typical examples:
See the dnf-plugin-anyrepo-repositories GitHub star list for a curated collection of GitHub repositories that can be installed via AnyRepo.
Once configured, users can run commands such as:
dnf install prec
dnf upgrade precDependency resolution and package version selection are still handled by DNF itself.
Install from Releases by choosing the RPM that matches the target RHEL major version:
# Import the RPM public signing key for dnf-plugin-anyrepo
rpm --import https://raw.githubusercontent.com/jfut/dnf-plugin-anyrepo/refs/heads/main/packaging/RPM-GPG-KEY-jfut-github
# RHEL 8, AlmaLinux 8, Rocky Linux 8, and other compatible distributions
dnf install https://github.com/jfut/dnf-plugin-anyrepo/releases/download/vX.Y.Z/dnf-plugin-anyrepo-x.y.z-n.el8.noarch.rpm
# RHEL 9, AlmaLinux 9, Rocky Linux 9, and other compatible distributions
dnf install https://github.com/jfut/dnf-plugin-anyrepo/releases/download/vX.Y.Z/dnf-plugin-anyrepo-x.y.z-n.el9.noarch.rpm
# RHEL 10, AlmaLinux 10, Rocky Linux 10, and other compatible distributions
dnf install https://github.com/jfut/dnf-plugin-anyrepo/releases/download/vX.Y.Z/dnf-plugin-anyrepo-x.y.z-n.el10.noarch.rpmWhen bash-completion is installed and loaded by the interactive Bash session, dnf-anyrepo command completion is available. The RPM installs its completion script under /usr/share/bash-completion/completions/dnf-anyrepo.
If the RPM public signing key is available, import it first:
rpm --import https://raw.githubusercontent.com/jfut/dnf-plugin-anyrepo/refs/heads/main/packaging/RPM-GPG-KEY-jfut-githubRegister GitHub repositories that publish RPM assets:
The following release RPMs are signed with the same RPM public key as dnf-plugin-anyrepo.
dnf-anyrepo add https://github.com/jfut/dnf-plugin-anyrepo
dnf-anyrepo add https://github.com/jfut/prec
dnf-anyrepo add https://github.com/jfut/sslcert-cli
dnf-anyrepo add https://github.com/jfut/nmcli-cli
dnf-anyrepo add https://github.com/jfut/ipset-fast-updateUse -n or --name to register a repository under an alias instead of the repository name.
The following release RPMs are unsigned, so gpgcheck must be disabled.
dnf-anyrepo add https://github.com/firehol/packages -n firehol
dnf-anyrepo repo firehol set gpgcheck 0List repositories managed by AnyRepo:
# dnf-anyrepo list
NAME SOURCE URL ENABLED GPGCHECK MIN_AGE
dnf-plugin-anyrepo github-release https://github.com/jfut/dnf-plugin-anyrepo global(1) global(1) global(3d)
firehol github-release https://github.com/firehol/packages global(1) 0 global(3d)
ipset-fast-update github-release https://github.com/jfut/ipset-fast-update global(1) global(1) global(3d)
nmcli-cli github-release https://github.com/jfut/nmcli-cli global(1) global(1) global(3d)
prec github-release https://github.com/jfut/prec global(1) global(1) global(3d)
sslcert-cli github-release https://github.com/jfut/sslcert-cli global(1) global(1) global(3d)Show details for one AnyRepo repository:
# dnf-anyrepo repo prec
arch: x86_64
asset_exclude: (?:-debuginfo(?:-|[.])|-debugsource(?:-|[.])|[.]src[.]rpm$)
asset_include: .*\.rpm$
cache_dir: global(/var/cache/dnf/anyrepo)
enabled: global(1)
github_token_file:
minimum_release_age: global(3d)
priority: global(99)
refresh_interval: global(10m)
releasever: el10
source: github-release
url: https://github.com/jfut/precAnyRepo repositories appear transparently in dnf list:
When the newest release is younger than minimum_release_age (MIN_AGE), AnyRepo keeps using the latest older release that satisfies the age limit.
github.com:firehol:packages falls back to el9 automatically because no el10 assets are published.
# dnf list | grep github.com
firehol.noarch 3.1.7-1.el9 github.com:firehol:packages
iprange.x86_64 1.0.4-2.el9 github.com:firehol:packages
ipset-fast-update.noarch 1.6.0-1 github.com:jfut:ipset-fast-update
prec.x86_64 0.1.1-1 github.com:jfut:precChange the global default setting:
Repositories without their own minimum_release_age override inherit this value.
# dnf-anyrepo global set minimum_release_age 10h
[main] minimum_release_age: 3d -> 10h (/etc/dnf/plugins/anyrepo.conf)Change minimum_release_age for individual repositories:
dnf-anyrepo repo nmcli-cli set minimum_release_age 3h
dnf-anyrepo repo sslcert-cli set minimum_release_age 5hRefresh the local cache explicitly:
dnf-anyrepo refresh precList repositories again after the MIN_AGE overrides are applied:
# dnf-anyrepo list
NAME SOURCE URL ENABLED GPGCHECK MIN_AGE
dnf-plugin-anyrepo github-release https://github.com/jfut/dnf-plugin-anyrepo global(1) global(1) global(10h)
firehol github-release https://github.com/firehol/packages global(1) 0 global(10h)
ipset-fast-update github-release https://github.com/jfut/ipset-fast-update global(1) global(1) global(10h)
nmcli-cli github-release https://github.com/jfut/nmcli-cli global(1) global(1) 3h
prec github-release https://github.com/jfut/prec global(1) global(1) global(10h)
sslcert-cli github-release https://github.com/jfut/sslcert-cli global(1) global(1) 5hInstall packages through ordinary dnf install:
When AnyRepo-managed RPMs are unsigned and gpgcheck = 1, DNF rejects them. AnyRepo prints the repository-specific setting required to allow unsigned packages. The same warning flow applies to dnf upgrade.
# dnf install prec
WARNING: To continue installing unsigned AnyRepo packages, configure the following:
- dnf-anyrepo repo prec set gpgcheck 0
Dependencies resolved.
=========================================================================
Package Architecture Version Repository Size
=========================================================================
Installing:
prec x86_64 0.1.1-1 github.com:jfut:prec 3.3 M
Transaction Summary
=========================================================================
Install 1 Package
Total size: 3.3 M
Installed size: 3.3 M
Is this ok [y/N]: y
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : prec-0.1.1-1.x86_64 1/1
Running scriptlet: prec-0.1.1-1.x86_64 1/1
Installed:
prec-0.1.1-1.x86_64
Complete!Update packages through ordinary dnf upgrade:
dnf upgrade prec
dnf upgradeAutomatic updates through dnf-automatic.timer also pick up AnyRepo-managed packages transparently.
Remove the AnyRepo repository entry:
# dnf-anyrepo remove prec
[prec] repo removed (/etc/dnf/plugins/anyrepo.d/prec.conf)The user-facing master switch is:
/etc/yum.repos.d/anyrepo.repo
Installed content:
[anyrepo]
name = AnyRepo repositories
enabled = 1
baseurl = file:///var/empty
skip_if_unavailable = 1
gpgcheck = 1Set enabled = 0 in that file to disable all AnyRepo-managed repositories for DNF commands.
The gpgcheck value in that file is also inherited by the dynamic github.com:<owner>:<repo> repositories created by the plugin:
gpgcheck = 0keeps DNF signature checks disabled for AnyRepo packagesgpgcheck = 1enables normal DNF signature checks for AnyRepo packages- when
gpgcheck = 1, unsigned RPMs require a repository-specific override before install or upgrade can continue
You can override the inherited value for one configured repository:
dnf-anyrepo repo NAME set gpgcheck 0
dnf-anyrepo repo NAME set gpgcheck 1
dnf-anyrepo repo NAME unset gpgcheckWhen enabled, the plugin:
- disables the static
anyreporepo entry itself - refreshes configured repositories as needed
- registers cached repositories as DNF
file://repos - clears AnyRepo caches during
dnf clean all
The dnf-plugin-anyrepo RPM package installs these configuration files:
/etc/dnf/plugins/anyrepo.conf/etc/yum.repos.d/anyrepo.repo
Main config path:
/etc/dnf/plugins/anyrepo.conf
Default repository config directory:
/etc/dnf/plugins/anyrepo.d
Example:
[main]
include = /etc/dnf/plugins/anyrepo.d
cache_dir = /var/cache/dnf/anyrepo
refresh_interval = 600
minimum_release_age = 3d
priority = 99
debug = 0
enabled = 1
asset_include = .*\.rpm$
asset_exclude = (?:-debuginfo(?:-|[.])|-debugsource(?:-|[.])|[.]src[.]rpm$)Example per-repository file:
[dnf-plugin-anyrepo]
source = github-release
url = https://github.com/jfut/dnf-plugin-anyrepo
minimum_release_age = 1800
[prec]
source = github-release
url = https://github.com/jfut/precWhen include is set, AnyRepo reads every *.conf file under that directory.
If include is not set, AnyRepo still uses /etc/dnf/plugins/anyrepo.d by default.
dnf-anyrepo add https://github.com/jfut/prec creates /etc/dnf/plugins/anyrepo.d/prec.conf by default.
If -n name is used, the file path becomes /etc/dnf/plugins/anyrepo.d/name.conf.
Configuration values are resolved in this order:
repo section
-> [main]
-> built-in defaults
Default values:
cache_dir=/var/cache/dnf/anyrepo
refresh_interval=600
minimum_release_age=259200
priority=99
debug=0
source=github-release
enabled=1
asset_include=.*\.rpm$
asset_exclude=(?:-debuginfo(?:-|[.])|-debugsource(?:-|[.])|[.]src[.]rpm$)
Per-repository sections support these keys:
archasset_excludeasset_includecache_direnabledgithub_token_filegpgcheckminimum_release_agepriorityrefresh_intervalreleaseversourceurl
Notes:
asset_excludeis a regular expression applied afterasset_include, and matching assets are skippedenabled = falsedisables that repository, even when the global setting is enabledgithub_token_fileis read and used as a GitHub API bearer tokenpriorityis an integer; lower values take precedence
Asset selection happens in this order:
- Match
asset_include - Drop assets that match
asset_exclude - Match RPM architecture
- Match RHEL release marker when applicable
Default excludes:
debuginfoRPMsdebugsourceRPMssrc.rpm
AnyRepo keeps those packages out of the primary binary repository, then publishes matching auxiliary repositories with DNF's standard suffixes when assets exist:
-debuginfofordnf download --debuginfo,dnf download --debugsource, anddnf debuginfo-install-sourcefordnf download --source
Architecture behavior:
- If
archis unset, the current machine architecture is used amd64is normalized tox86_64arm64is normalized toaarch64noarchRPMs are always allowed together with the selected architecture
Release version behavior:
- If
releaseveris unset, the plugin tries to detect the current RHEL-compatible major version, such asel8,el9, orel10 - When assets contain EL-specific variants such as
.el9.x86_64.rpm, the plugin keeps the exactreleaseverwhen available - If the exact EL variant is missing, the plugin falls back to the nearest lower major such as
el10 -> el9 -> el8 - Assets without an EL marker remain eligible
The plugin refreshes a repository when one of these is true:
- there is no cache yet
- cached
archdiffers from the current config - cached
releaseverdiffers from the current config repodata/is missingrefresh_intervalhas elapsed since the last refresh
refresh_interval and minimum_release_age accept either raw seconds or a duration suffix:
30m1h2d
minimum_release_age delays adoption of a newly published release.
The check uses GitHub published_at:
now - published_at >= minimum_release_age
If the latest release is too new:
- the latest older release that already satisfies
minimum_release_ageis selected automatically - existing cached metadata is kept when no release satisfies the age limit yet
- a new repository is not generated when no cache exists yet and no release is old enough
This helps avoid immediately shipping a release while assets are still being uploaded or verified.
Examples:
# Set the global default release age to 1 day.
dnf-anyrepo global set minimum_release_age 1d
# Reset the global release age to the built-in default.
dnf-anyrepo global unset minimum_release_age
# Override only one repository to 30 minutes.
dnf-anyrepo repo NAME set minimum_release_age 30m
# Remove the repository-specific override.
dnf-anyrepo repo NAME unset minimum_release_ageDisable or enable all repositories by default:
dnf-anyrepo global set enabled 0
dnf-anyrepo global set enabled 1Repositories with their own enabled setting override the global value:
dnf-anyrepo repo NAME set enabled 1
dnf-anyrepo repo NAME unset enabledThe global commands update [main] and affect repositories that inherit the global setting.
The repo commands update the named repository section and override or restore the global value only for that repository.
dnf-plugin-anyrepo does not ask DNF to consume GitHub directly.
Instead, it mirrors matching RPM assets into a local cache and generates repository metadata there.
GitHub Releases API
-> select latest published release
-> filter assets
-> download RPM assets
-> /var/cache/dnf/anyrepo/<name>
-> createrepo_c
-> local file:// repository
-> DNF
The plugin's responsibility is limited to turning remote release assets into a normal local repository. Dependency solving remains DNF's job.
- Current source:
github-release - Target environments: RHEL 8, RHEL 9, RHEL 10, and other compatible distributions
- Current GitHub URL requirement:
https://github.com/<owner>/<repo> - Draft and prerelease releases are ignored when the provider falls back to the releases list API
Add repositories:
dnf-anyrepo add https://github.com/jfut/prec
dnf-anyrepo add https://github.com/firehol/packages -n firehol
# with options
dnf-anyrepo add https://github.com/jfut/prec --asset-include '.*\.rpm$'
dnf-anyrepo add https://github.com/jfut/prec --asset-exclude '$^'
dnf-anyrepo add https://github.com/jfut/prec --minimum-release-age 30m
dnf-anyrepo add https://github.com/jfut/prec --priority 25
dnf-anyrepo add https://github.com/jfut/prec --arch x86_64 --releasever el10
dnf-anyrepo add https://github.com/jfut/prec --github-token-file /etc/anyrepo/github.tokenBy default, each added repository is stored in /etc/dnf/plugins/anyrepo.d/NAME.conf.
Before writing that file, add checks the latest 100 GitHub Releases for a published RPM that
matches the selected asset, architecture, and release-version filters. Repositories without a
compatible RPM in that range are rejected without changing the configuration.
List repositories:
dnf-anyrepo listGlobal settings:
dnf-anyrepo global
dnf-anyrepo global get minimum_release_age
dnf-anyrepo global set minimum_release_age 1h
dnf-anyrepo global unset minimum_release_ageRepository settings:
dnf-anyrepo repo prec
dnf-anyrepo repo prec set minimum_release_age 1d
dnf-anyrepo repo prec set enabled 0
dnf-anyrepo repo prec set priority 50
dnf-anyrepo repo prec set gpgcheck 1
dnf-anyrepo repo prec set asset_exclude '$^'
dnf-anyrepo repo prec unset minimum_release_ageRefresh repositories:
Use -f or --force to refresh the repository immediately, even when the current cache is still within refresh_interval.
dnf-anyrepo refresh
dnf-anyrepo refresh prec
dnf-anyrepo refresh prec -fRemove repositories:
Use -p or --purge-cache to remove the repository cache directory together with the repository entry.
dnf-anyrepo remove prec
dnf-anyrepo remove prec -pCommon local tasks:
just lint
just test
just test-e2e
just exec-test
just check
just snapshotThe codebase targets Python 3.6-compatible syntax.
GitHub Actions signs RPM artifacts with the GPG private key stored in RPM_SIGNING_KEY. If the key has a passphrase, store it in NFPM_PASSPHRASE.
- Run
git tag -s vX.Y.Z -m vX.Y.Z. - Run
git push origin vX.Y.Zand wait for the Release to be created. - Edit the created Release.
- Press the
Generate release notesbutton and edit the release notes. - Press the
Update releasebutton.
Apache-2.0
Copyright contributors to the dnf-plugin-anyrepo project.
Jun Futagawa (jfut)