Skip to content

Repository files navigation

dnf-plugin-anyrepo

Tag License

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.

Why use it

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:

Available GitHub repositories

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 prec

Dependency resolution and package version selection are still handled by DNF itself.

Installation

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.rpm

When 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.

Example workflow

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-github

Register 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-update

Use -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 0

List 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/prec

AnyRepo 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:prec

Change 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 5h

Refresh the local cache explicitly:

dnf-anyrepo refresh prec

List 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)  5h

Install 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 upgrade

Automatic 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)

DNF integration

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 = 1

Set 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 = 0 keeps DNF signature checks disabled for AnyRepo packages
  • gpgcheck = 1 enables 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 gpgcheck

When enabled, the plugin:

  • disables the static anyrepo repo entry itself
  • refreshes configured repositories as needed
  • registers cached repositories as DNF file:// repos
  • clears AnyRepo caches during dnf clean all

Configuration

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/prec

When 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$)

Repository settings

Per-repository sections support these keys:

  • arch
  • asset_exclude
  • asset_include
  • cache_dir
  • enabled
  • github_token_file
  • gpgcheck
  • minimum_release_age
  • priority
  • refresh_interval
  • releasever
  • source
  • url

Notes:

  • asset_exclude is a regular expression applied after asset_include, and matching assets are skipped
  • enabled = false disables that repository, even when the global setting is enabled
  • github_token_file is read and used as a GitHub API bearer token
  • priority is an integer; lower values take precedence

Asset selection

Asset selection happens in this order:

  1. Match asset_include
  2. Drop assets that match asset_exclude
  3. Match RPM architecture
  4. Match RHEL release marker when applicable

Default excludes:

  • debuginfo RPMs
  • debugsource RPMs
  • src.rpm

AnyRepo keeps those packages out of the primary binary repository, then publishes matching auxiliary repositories with DNF's standard suffixes when assets exist:

  • -debuginfo for dnf download --debuginfo, dnf download --debugsource, and dnf debuginfo-install
  • -source for dnf download --source

Architecture behavior:

  • If arch is unset, the current machine architecture is used
  • amd64 is normalized to x86_64
  • arm64 is normalized to aarch64
  • noarch RPMs are always allowed together with the selected architecture

Release version behavior:

  • If releasever is unset, the plugin tries to detect the current RHEL-compatible major version, such as el8, el9, or el10
  • When assets contain EL-specific variants such as .el9.x86_64.rpm, the plugin keeps the exact releasever when 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

Refresh behavior

The plugin refreshes a repository when one of these is true:

  • there is no cache yet
  • cached arch differs from the current config
  • cached releasever differs from the current config
  • repodata/ is missing
  • refresh_interval has elapsed since the last refresh

refresh_interval and minimum_release_age accept either raw seconds or a duration suffix:

  • 30m
  • 1h
  • 2d

minimum_release_age

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_age is 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_age

Disable or enable all repositories by default:

dnf-anyrepo global set enabled 0
dnf-anyrepo global set enabled 1

Repositories with their own enabled setting override the global value:

dnf-anyrepo repo NAME set enabled 1
dnf-anyrepo repo NAME unset enabled

The 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.

How it works

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.

Scope

  • 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

CLI

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.token

By 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 list

Global 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_age

Repository 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_age

Refresh 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 -f

Remove 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 -p

Development

Common local tasks:

just lint
just test
just test-e2e
just exec-test
just check
just snapshot

The codebase targets Python 3.6-compatible syntax.

Release

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.

  1. Run git tag -s vX.Y.Z -m vX.Y.Z.
  2. Run git push origin vX.Y.Z and wait for the Release to be created.
  3. Edit the created Release.
  4. Press the Generate release notes button and edit the release notes.
  5. Press the Update release button.

License

Apache-2.0

Copyright contributors to the dnf-plugin-anyrepo project.

Author

Jun Futagawa (jfut)

About

dnf-plugin-anyrepo is a DNF plugin that makes remote RPM assets available to ordinary DNF commands as transparent, dynamic local RPM repositories.

Resources

Stars

Watchers

Forks

Releases

Contributors

Languages