Installing agentctl on RHEL 10¶
The .rpm is built from the same packaging/nfpm.yaml as the .deb, by the same
build.sh, and ships the same payload. What differs is the dependency set — covered
below — and the maintainer scripts, which differ in mechanism, not behaviour.
Target: RHEL 10 and its rebuilds (AlmaLinux 10, Rocky Linux 10). Verified against AlmaLinux 10.2 and Rocky Linux 10.2. Nothing here is validated for RHEL 9; the repo composition differs (see the mapping table).
Install¶
No manual prerequisite. dnf install agentctl needs nothing but the base repos
(baseos/appstream/crb, whichever of those already ship on the host); agentctl
provision does everything else automatically — enables EPEL/CRB, installs Chrome, and
installs kasmvnc + openbox from agentctl's own repo. See "What provision does on
RHEL" below for the mechanism and the one thing it genuinely cannot automate.
From the release asset:
sudo dnf install -y ./agentctl-<ver>.x86_64.rpm
sudo agentctl provision
From the yum repo (packaging/repo/build-rpm-repo.sh generates a ready-made
agentctl.repo at the repo root — curl it into /etc/yum.repos.d/). The host is
behind basic auth, and unlike apt (which reads a separate /etc/apt/auth.conf.d/*.conf
dnf has no equivalent of), the credential has to ride with every request — pass it to
curl when fetching, and the released agentctl.repo already carries a working
username=/password= baked in by release-local.sh/release.yml (AGENTCTL_REPO_USER/
AGENTCTL_REPO_PASS), so once it's downloaded dnf needs nothing further:
sudo rpm --import https://agentctl.srv.ajil.ch/RPM-GPG-KEY-agentctl
sudo curl -fsSL -u USER:PASS -o /etc/yum.repos.d/agentctl.repo https://agentctl.srv.ajil.ch/rpm/agentctl.repo
sudo chmod 600 /etc/yum.repos.d/agentctl.repo # it now carries a real password
sudo dnf install -y agentctl
sudo agentctl provision
Packages are header-signed and repomd.xml carries a detached signature, so the generated
.repo sets both gpgcheck=1 and repo_gpgcheck=1.
Either install path needs agentctl.repo configured before provision runs —
provision installs kasmvnc/openbox/its 2 sibling libs from that same repo (see below),
so even the release-asset path needs the two curl/rpm --import lines above run once,
in either order relative to the dnf install -y ./agentctl-<ver>.x86_64.rpm step.
What provision does on RHEL¶
Everything that touches a package manager lives in one early, distro-detected step of
agentctl provision (internal/converge/host.go, gated on isDNFHost) — nothing is a
manual prerequisite anymore:
- EPEL + CRB. AlmaLinux/Rocky:
dnf install -y epel-release && dnf config-manager --set-enabled crb— free, unauthenticated, needs nothing but network access. Real Red Hat RHEL:subscription-manager repos --enable codeready-builder-for-rhel-10-x86_64-rpms, which only works on a host already registered against a Red Hat subscription. That registration is the one thing this package cannot and does not try to automate — it needs your Red Hat account credentials. Skipping it (or running on an unregistered RHEL host) fails loudly with Red Hat's own "this system is not registered" error, not a silent degradation. age,ffmpeg-free,git-filter-repo. Installed right after EPEL is enabled — they live only in EPEL, and a flat rpm dependency on any of them would have made the very firstdnf install agentctlon a bare host require EPEL to already be enabled (dependency resolution happens before any of this package's own code runs). SeeepelScriptininternal/converge/host_content.gofor the full reasoning.kasmvncserver,openbox,libobrender32v5,libobt2v5. Installed in the same step, from agentctl's own yum repo — measured, not assumed, they need EPEL enabled too, one level down (kasmvncserver's Perl config layer needsHash::Merge::Simple/DateTime/List::MoreUtils/Try::Tiny/YAML::Tiny, all EPEL-only; openbox's ownlibobrender32v5linkslibImlib2, also EPEL-only). See "kasmvnc + openbox" below for what these packages actually are.- Chrome.
rpm --imports Google's signing key, writes/etc/yum.repos.d/google-chrome.repo,dnf install -y google-chrome-stable— the dnf twin of the apt path this package has always had on Debian.
kasmvnc + openbox¶
Neither exists in any el10 repo upstream — checked directly, not assumed:
- KasmVNC has no el10 build at all. Confirmed against upstream's GitHub releases,
both the version this package pins (
internal/converge/versions.go'sKasmvncVersion) and the newest release as of this check: Fedora 40–43, openSUSE 15, Oracle Linux 8/9 only. - openbox is retired everywhere on RHEL. Checked baseos, appstream, crb and epel on both AlmaLinux 10.2 and Rocky Linux 10.2.
agentctl hosts fixed rebuilds of both — plus openbox's 2 sibling libraries
(libobrender32v5, libobt2v5), which Debian splits out and RHEL needs too — in its own
yum repo. packaging/kasmvnc-rpm/build.sh builds them: alien-converts the exact pinned
Ubuntu noble .debs already used on the Debian install path, then patches what alien
cannot get right for a different distro family:
- postinst. The
.debs' own postinst callsupdate-alternatives— Debian-only, absent on RHEL — to create generic symlinks (/usr/bin/Xvnc -> Xkasmvnc,/usr/bin/x-window-manager -> openbox) that nothing in this package's own units actually uses:xvnc@.serviceandopenbox@.servicecall/usr/bin/Xvncand/usr/bin/openboxdirectly. Fixed by droppingupdate-alternativesrather than porting it — kasmvncserver's%postbecomes a plain symlink; openbox's becomes nothing. - Lib paths. Debian installs shared libraries under the multiarch triplet path
/usr/lib/x86_64-linux-gnu/, not on RHEL's linker search path at all (RHEL uses/usr/lib64/). Relocated at build time, with a%post/%postunldconfigrefresh. - Two runtime deps alien's automatic dependency scan cannot see, because they are not
linked libraries — they are files Xvnc looks for at runtime, not link time:
xkeyboard-config,xkbcomp.
Every one of these was verified by actually installing and running the result on a real
AlmaLinux 10 container — dnf install, then Xvnc opened its X11 socket and served the
websocket VNC layer end to end; openbox --version ran clean — not inferred from package
metadata.
Dependency mapping¶
Verified with dnf repoquery on AlmaLinux 10.2 and Rocky Linux 10.2, 2026-08-13.
| deb (Ubuntu) | rpm (el10) | repo | note |
|---|---|---|---|
python3 |
python3 |
baseos | 3.12 on el10 |
git |
git |
appstream | |
jq |
jq |
baseos | |
socat |
socat |
appstream | |
curl |
curl |
baseos | |
unzip |
unzip |
baseos | |
rsync |
rsync |
baseos | |
| n/a (already base image) | zstd, ca-certificates, acl |
baseos | flat depends: on both packagers now |
age |
age |
epel | provision-installed, not a flat depends: — see above |
ffmpeg |
ffmpeg-free |
epel | patent-free build, provision-installed; see below |
git-filter-repo |
git-filter-repo |
epel | provision-installed |
openbox |
openbox |
agentctl repo | provision-installed, not upstream; see above |
| n/a (deb: KasmVNC vendored by provision) | kasmvncserver, libobrender32v5, libobt2v5 |
agentctl repo | provision-installed, not upstream; see above |
google-chrome-stable |
google-chrome-stable |
google (dnf) | provision-installed on both packagers |
fonts-croscore |
liberation-fonts |
appstream | metric-compatible with Arial/Times/Courier |
fonts-noto-core |
google-noto-sans-fonts |
appstream | |
fonts-noto-color-emoji |
google-noto-color-emoji-fonts |
appstream | el9 name is google-noto-emoji-color-fonts |
fonts-inter |
(dropped) | — | no el10 package in any repo; see below |
nftables |
nftables |
baseos |
The remaining gap: fonts-inter → dropped¶
Inter has no el10 package in any repo — this one genuinely has no fix, unlike openbox and
kasmvnc above. It is the video MCP's default caption font (STYLE_DEFAULTS in
packaging/payload/mcp/video/server.py); libass asks fontconfig and takes whatever comes
back, so captions render in a fallback family rather than failing. A host that must match
the fleet's caption look should install Inter into /usr/local/share/fonts and run
fc-cache -f, or set an explicit caption font per project.
The browser font story is otherwise intact: 99-agentctl-render.conf is a fontconfig rule
keyed on font family, not on package name, so it applies unchanged. The
FONT PACKAGES: mirror test in internal/converge/fontconfig_test.go reads the top-level
depends: (the apt list) and is unaffected by the rpm override.
ffmpeg → ffmpeg-free¶
EPEL 10 has no ffmpeg; the full build lives in RPM Fusion, a third-party repo this
package will not pull in. ffmpeg-free covers what the deb's ffmpeg dependency is
actually there for — the Telegram voice-note transcode to OGG/OPUS (Opus is unencumbered
and built in).
It does not carry libx264/libx265. A host that runs the video MCP's h264 renders
needs the full build:
sudo dnf install -y https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-10.noarch.rpm
sudo dnf install -y ffmpeg # replaces ffmpeg-free
Not covered here¶
True air-gap. internal/converge/artifactory.go's package-mirror wiring (Artifactory
apt/npm/pip/Go proxy config) is apt-only today — no dnf/yum mirror equivalent. A RHEL host
that can reach EPEL/baseos/appstream/agentctl's own repo directly (the common case —
confirmed against the actual inventx target) is unaffected; a host with no outbound
internet access at all is not yet supported on the rpm path. Tracked as follow-up, not
solved here.
SELinux. RHEL 10 enforces by default. This package installs a systemd unit into
/usr/lib/systemd/system and agentd binds a unix socket; agentctl provision writes
considerably more (per-agent units, sockets under /run, home directories). None of it
ships an SELinux policy module, and nothing in this change has been tested under
enforcing — the install verification above ran in a container, where SELinux is not in
play. Expect to need either a policy module or targeted semanage fcontext rules before a
production RHEL deployment, and treat that as open work rather than as covered by the
.rpm existing.