alma-certify runs certification workloads on an AlmaLinux system and produces a
versioned, machine-readable report. It replaces the previous Ansible-based
prototype: it runs directly on the machine under test, has real pass/fail
semantics, and emits a JSON report that the AlmaLinux certification web
application (Lumina) can ingest automatically.
There are three kinds of run:
| Run type | What it does |
|---|---|
collect |
Normalized hardware inventory - CPUs, DIMMs, disks, NICs, GPUs, firmware, and driver versions |
validate |
Functional certification tests: does AlmaLinux install and work correctly on this hardware |
benchmark |
Performance measurements feeding the public leaderboards |
Validation answers does it work, not how fast or how long it survives
load - workload checks are short functional smoke runs. Anything
performance-shaped belongs in benchmark.
- AlmaLinux 8, 9, or 10 on x86_64 or aarch64. ppc64le and s390x are not
supported:
dmidecodeis not built for either, and the suite reads SMBIOS to identify the machine it is certifying - root on the machine under test. Every command needs it; run one without and the
suite offers to re-run itself under
sudo - Python 3.9+ - on AlmaLinux 9/10 that is the system
python3; on AlmaLinux 8 install the AppStream interpreter:dnf install python3.12 zstd(AlmaLinux BaseOS) - result bundles are.tar.zst- Repository access, so the suite can install the tools individual tests need
(
stress-ng,fio,iperf3, …). Network access to Lumina is optional - see Offline use.
The suite will run anywhere, because refusing to execute outside the supported distribution makes it useless for finding out whether the distribution is what broke. What it will not do is let those results be submitted.
On a non-AlmaLinux host, collect, validate, benchmark, and run print a
warning naming the distribution, then ask whether to continue. Enter declines
- carrying on is the surprising choice, so it is the one you type. With no
terminal attached (a CI job, a kickstart
%post) the command refuses instead of prompting, and--allow-unsupported-osis how you say yes in advance.
Such a run is offline only:
- no submission token is requested,
- results are never uploaded, and
alma-certify submitrefuses it, printing the distribution it ran on.
alma-certify bundle still works, so you keep a shareable archive. The refusal is
based on the report, not the machine you type the command on, so bundling on
the host under test and uploading from your workstation works normally - and
moving an unsupported run to an AlmaLinux box does not get it past the check.
Lumina applies the same rule at its end: a bundle whose environment.os.id is
not almalinux is quarantined for a reviewer and can never certify an AlmaLinux
release.
Certifying a machine needs bare metal: inside a guest, the firmware, storage
controller, and network device under test are all the hypervisor's. The suite
still runs there and keeps the results, but a whole-machine run made inside a
virtual machine or a container is not uploaded, and alma-certify submit
refuses it the same way, judging by the report's recorded
environment.virtualization. alma-certify bundle still works.
A GPU passed through to a guest is the real device, so --scope gpu is the one
claim a guest can make, and such a run submits normally.
Most benchmark tools live in EPEL, and many EPEL packages depend on
CodeReady Builder, so before the first install validate, benchmark, and
run will:
- enable CodeReady Builder (
crb, orpowertoolson AlmaLinux 8), and - install
epel-release.
Per-test tools are then installed on demand and left installed unless you
pass --cleanup-packages. Pass --no-epel to skip repository setup entirely;
tests whose tools are only in EPEL then skip with a reason instead of running.
Which repositories were active is recorded in the report
(environment.enabled_repos), because it determines which tool versions the
results came from.
collect never installs anything.
The runtime itself is stdlib-only: no pip packages, no virtualenv.
From RPM (preferred):
dnf install ./alma-certify-0.1.0-1.el9.noarch.rpm
alma-certify --helpFrom a git checkout (no install step needed):
git clone https://github.com/AlmaLinux/alma-certify.git
cd alma-certify
sudo python3 -m alma_certify --help # python3.12 on AlmaLinux 8The package is noarch and needs no compiler, so one SRPM serves every
release and architecture.
make rpm # for the machine you are on
make mock-rpm DIST=alma+epel-9-x86_64 # clean-room build for one target
make mock-all # AlmaLinux 8, 9, and 10Everything lands under build/rpm/RPMS/ - make rpm in noarch/, the mock
targets in a directory per target - and each recipe prints the package it
produced. Copy the .noarch.rpm to the test systems and
dnf install ./alma-certify-*.rpm.
build/rpm/RPMS/alma+epel-8-x86_64/alma-certify-0.1.0-1.el8.noarch.rpm
build/rpm/RPMS/alma+epel-9-x86_64/alma-certify-0.1.0-1.el9.noarch.rpm
build/rpm/RPMS/alma+epel-10-x86_64/alma-certify-0.1.0-1.el10.noarch.rpm
For the builds that are not a release - a branch somebody needs on real
hardware today, an el8 fix nobody wants to tag for - make copr submits the
same SRPM to a Copr project:
make copr COPR_PROJECT=yourname/alma-certify
make copr COPR_CHROOTS="almalinux-9-x86_64" COPR_ARGS=--nowaitThere is no default project, because a default is where a build quietly goes on
being published long after that is the right place for it. Run at a terminal
with COPR_PROJECT unset, make copr names the package it built and asks
where to send it; with no terminal, it says what is missing and submits
nothing. export COPR_PROJECT=... keeps the answer for a session.
It needs copr-cli (dnf install copr-cli) and the API token block from
https://copr.fedorainfracloud.org/api/ saved as ~/.config/copr, or a path
to one in COPR_CONFIG. Which chroots a project builds for is set on the
project itself, so COPR_CHROOTS only narrows a single build.
Like every target here, this packages the working tree, not HEAD, which
is usually the point of a Copr build. The name-version-release is printed
before the upload so what landed in the repository is answerable afterwards.
Full mock logs sit beside each package (build.log, root.log).
Building with mock is worth the extra minute because the el8 package differs:
AlmaLinux 8's platform python is 3.6, so that build requires the
python3.12 AppStream interpreter and its launcher execs python3.12,
while el9/el10 use the system python3. Only a real target root proves that.
The tarball is built from the working tree rather than git archive HEAD,
so uncommitted changes are packaged - convenient while developing, and
identical to a tag build in CI where the checkout is the tag.
Run alma-certify with no arguments at a terminal and it walks you through the common tasks:
+-----------------------------------------------------------------------------+
| alma-certify 0.1.0 AlmaLinux 10.1 |
+-----------------------------------------------------------------------------+
| |
| What would you like to do? |
| |
| > Validate this machine certification checks |
| Benchmark this machine performance figures |
| Both, in one run collect, validate, and benchmark |
| Previous runs look at, resume, or upload a run |
| Quit |
| |
+-----------------------------------------------------------------------------+
Up/Down move Enter select q quit
It shows the command your choices add up to, and then runs exactly that:
| $ alma-certify run --scope gpu |
| |
| Start this run |
So the guided mode teaches the CLI: run it once and you have the command for CI. It ships in the
alma-certify package with its interface library bundled, so it is there the first time you look
for it, and it only appears when there is a terminal on both stdin and stdout. In a script, a CI job, or a kickstart %post, a bare alma-certify prints
the help and exits 3 exactly as it always has. alma-certify tui asks for it explicitly.
Everything below works the same whether or not you use it.
# What would run on this machine?
alma-certify list
# Inventory only
sudo alma-certify collect
# Certification validation
sudo alma-certify validate
# Benchmarks (all categories, or pick some)
sudo alma-certify benchmark --category cpu,memory,crypto
# Everything in one run
sudo alma-certify runEach run prints a summary and a run id. To see the runs already on a machine, newest first:
sudo alma-certify runsRUN STARTED TYPES CLAIM SUBMITTED RESULT
0b71d4c9 2026-08-17 08:14 benchmark gpu no unfinished, 2 of 14 tests (1 pass, 1 error)
9f3c1a2e 2026-08-16 18:22 collect,validate,benchmark machine 2026-08-16 38 pass, 1 fail, 3 skip
SUBMITTED is the date the results first reached the server, recorded when an upload succeeds -
whether that was the automatic one at the end of the run or a later alma-certify submit. It is the one
thing about a run that cannot be worked out by re-reading it.
The short id in the first column is all the other commands need, so it can be pasted straight into any of them:
sudo alma-certify report 9f3c1a2e # what happened in that run
sudo alma-certify resume 0b71d4c9 # long runs are resumable
sudo alma-certify bundle 9f3c1a2e # archive it for manual upload
sudo alma-certify submit 9f3c1a2e # upload italma-certify runs --json prints the same listing for scripts, with the full ids, exact timestamps,
hostname, per-status counts, and the submission's URL. alma-certify report <id> names it too,
including whether an uploaded run is still waiting for you to submit it for review.
Exit codes: 0 all passed, 1 one or more failures, 2 errors, 3 usage,
4 blocked before starting because the host is not AlmaLinux (see
Running on something other than AlmaLinux).
Runs upload themselves when they finish, to https://catalog.almalinux.org
unless you say otherwise:
sudo alma-certify validate # runs, then uploads
sudo alma-certify validate --dev # ... to the staging catalog insteadThe first run asks you to authorize the machine. You approve it from a browser, so no password or token is ever typed on the system under test:
To authorize this machine (sut-42.lab), open:
https://catalog.almalinux.org/my/activate/
and enter the code: BQXK-PMTH
That check happens before the tests start, and again whenever the stored token has under two hours left, so a long benchmark pass can never finish and then discover it has nowhere to send results. Tokens last 12 hours and are tied to the machine that requested them: results posted from a different host are refused.
alma-certify register still exists if you would rather authorize up front, and
alma-certify submit <run-id> uploads any finished run by hand - useful to retry
after a network problem, since a failed upload never discards the run.
A validation run arrives as a draft. It is stored, but no reviewer sees it until you add the details the suite cannot detect from the machine: the marketing name, a description, and a link to the spec sheet. The run says so when it finishes:
== summary ==
pass 12
skip 3
test verdict: PASS
(the required tests passed; certification still needs review)
run id: 4f6c2a4e-9c1b-4d2a-8f77-3a1e5b6c7d80
submitting results to https://catalog.almalinux.org ...
uploaded: https://catalog.almalinux.org/results/runs/4f6c2a4e-.../
======================================================================
ACTION REQUIRED - this run is NOT submitted for review yet
======================================================================
A PASS verdict means the tests passed on this machine. It does not
mean the hardware is certified: that takes a reviewer, and a reviewer
cannot start until the listing details only you can supply are filled
in - the marketing name, a description, and a link to the spec sheet.
1. open https://catalog.almalinux.org/results/runs/4f6c2a4e-.../
2. add the listing details
3. press "Submit for review"
Lost the link? It is also on your dashboard, so you never have to keep
this terminal output:
https://catalog.almalinux.org/my/
under "My validation runs", listed as "Awaiting submitter details"
with "Finish submission" in the Action column.
Nothing is reviewed, certified, or published until you do. Until then
this run is visible only to you.
======================================================================
PASS is a test result, not a certification. It means every required test
passed on this machine. Certification is a decision a reviewer makes afterwards.
benchmark and collect runs need none of this - they have no listing details to
supply, so they go straight to the queue and say Queued for review. Nothing further is needed from you.
To keep results local, opt out:
sudo alma-certify validate --no-submitEmbargoes work the same either way:
sudo -E alma-certify validate --pre-release --publish-after 2026-09-01--pre-release marks unreleased hardware; combined with --publish-after,
Lumina keeps the submission invisible to the public until that date (and only
then if a reviewer has approved it).
If the machine cannot reach Lumina at all, skip the upload and hand-carry a bundle:
sudo alma-certify validate --no-submit
sudo alma-certify bundle 4f6c2a4e -o ~/alma-certify-results.tar.zstUpload that file at https://catalog.almalinux.org/results/upload/. Both paths
go through identical processing on the server.
Network throughput checks need a second machine running an iperf3 server:
# on the peer
iperf3 -s
# on the system under test
sudo alma-certify validate --peer 192.0.2.10Without --peer those tests skip cleanly - a skip is not a failure.
Some checks need a person at the machine. They never run by default:
--interactive- USB hotplug, suspend/resume, reboot survival
The reboot-survival test installs a one-shot systemd unit so the run resumes automatically after the machine comes back.
/etc/alma-certify/alma-certify.conf (INI) holds defaults for run directory,
server URL, smoke durations, benchmark targets, and per-test timeouts.
See the shipped file for the full list.
Precedence is defaults < config file < environment < command-line flags. The Lumina URL can come from any of the three, which is handy for pointing a machine at a dev or staging instance:
alma-certify --server http://lumina-dev.example:8100 submit 4f6c2a4e
export ALMA_CERTIFY_SERVER=http://lumina-dev.example:8100--server, --run-dir, and --config may be given either before or after
the subcommand.
A run directory (/var/lib/alma-certify/runs/<run-id>/) contains:
report.json the machine-readable report - the contract with Lumina
state.json resume journal
inventory/ raw tool output (dmidecode, lspci, ethtool, …)
artifacts/ per-test raw logs
alma-certify.log suite log
report.json is documented in docs/schema.md, and the test
catalog in docs/catalog.md.
collect, validate, benchmark, run, and resume accept --debug, which
traces every command the suite runs as it happens: the command line, its raw
stdout and stderr, its exit code, and how long it took.
[validate.cpu.functional] $ stress-ng --cpu 0 --cpu-method all --verify --metrics -t 60s (timeout 900s)
--- stdout ---
stress-ng: info: [12345] dispatching hogs: 8 cpu
--- exit 0 in 60.41s ---
[validate.storage.io-sanity] $ dd if=/dev/zero of=/var/tmp/alma-certify.io bs=1M count=64 (timeout 150s)
--- stderr ---
64+0 records in
64+0 records out
67108864 bytes (67 MB, 64 MiB) copied, 0.0183731 s, 3.7 GB/s
--- exit 0 in 0.02s ---
(That second entry is real output, and shows why the streams are kept apart:
dd writes its whole summary to stderr, so a trace that merged them would make it
look like an error.)
Details worth knowing:
- Each command is labeled with the test that ran it, so the trace reads as a sequence of tests rather than a flat wall of commands.
- The command is printed before it runs, so if something hangs you can see what it is waiting on. The timeout is shown up front too, so a long wait is distinguishable from a stuck one.
- A missing tool is traced (
-> command not found), which is the reason behind most skips. - Coverage is the whole run, not just the tests: inventory collection and package installs shell out as well, and both are traced.
- Output goes to stderr, so
alma-certify report --jsonstays pipeable andalma-certify validate --debug 2> trace.logsaves the trace while the run's own summary still scrolls past. Nothing is added toalma-certify.log, and nothing changes inreport.jsonor the bundle. --debugoutput is not redacted.--redactstrips serials and UUIDs fromreport.json; the trace is raw tool output, so serials can appear in it. Using both prints a reminder. Check a trace before pasting it into a bug report.
Per-test raw logs are also written to artifacts/ on every run, with or without
--debug; the flag is for watching it live and for the commands whose output is
not kept as an artifact.
The suite is open-source Python running on hardware the submitter controls, so nothing in a bundle can cryptographically prove results are genuine. What the format does provide: a sha256 manifest of every artifact, a self-hash of the report, the exact suite version and commit, and submission bound to an authenticated Lumina account. Everything beyond that is server-side plausibility checking and human review. See docs/submitting.md.
pip install pytest ruff
python -m pytest tests -ra
ruff check alma_certify testsThe language floor is Python 3.9 and the runtime must stay stdlib-only -
pytest and ruff are development-only dependencies. Adding a test means
adding a Test subclass and registering it; see
docs/catalog.md.