Skip to content

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 runsprovision 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:

  1. 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.
  2. 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 first dnf install agentctl on a bare host require EPEL to already be enabled (dependency resolution happens before any of this package's own code runs). See epelScript in internal/converge/host_content.go for the full reasoning.
  3. 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 needs Hash::Merge::Simple/DateTime/List::MoreUtils/Try::Tiny/YAML::Tiny, all EPEL-only; openbox's own libobrender32v5 links libImlib2, also EPEL-only). See "kasmvnc + openbox" below for what these packages actually are.
  4. 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's KasmvncVersion) 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 calls update-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@.service and openbox@.service call /usr/bin/Xvnc and /usr/bin/openbox directly. Fixed by dropping update-alternatives rather than porting it — kasmvncserver's %post becomes 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/%postun ldconfig refresh.
  • 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.

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