Target audience: the release manager who cuts the next Linuxfabrik Monitoring Plugins release, and contributors who need to reproduce a build locally.
Outputs per release:
- Linux (RPM and DEB) for x86_64 and aarch64, unsigned. Published to https://repo.linuxfabrik.ch/monitoring-plugins/.
- Windows (ZIP of compiled plugins, and a signed MSI) for x86_64 only. Published to https://download.linuxfabrik.ch/monitoring-plugins/.
- Source-only zip for all operating systems. Published to the download server as well.
All three platform builds are manually triggered via GitHub Actions
workflow_dispatch. There is no automatic build on push or tag.
| Platform | Arch | GitHub workflow | Runner | Outputs | Signed |
|---|---|---|---|---|---|
| Linux | x86_64 | lf-build-linux-x86_64.yml |
ubuntu-24.04 |
.rpm, .deb |
no |
| Linux | aarch64 | lf-build-linux-aarch64.yml |
ubuntu-24.04-arm |
.rpm, .deb |
no |
| Windows | x86_64 | lf-build-windows-x86_64.yml |
windows-2025 |
.zip, .msi |
yes (SignPath.io) |
Windows aarch64 is not built today.
The two Linux workflows run the same build chain on different runner types.
Each one loops over a matrix of target distributions and builds one podman
container per distro, then produces the native .rpm or .deb inside that
container:
lf-build-linux-x86_64.yml / lf-build-linux-aarch64.yml
├── debug.sh # env dump
├── install-podman.sh # installs podman on the runner
└── matrix-package.sh # one container per target distro
├── verify-version.sh # checkout must contain vLFMP_VERSION
└── create-package.sh # branches on $LFMP_TARGET_DISTRO
├── create-src-tarball.sh # upstream source archive
├── create-vendor-tarball.sh # pinned third-party deps
├── create-deb.sh # Debian/Ubuntu target
└── create-rpm.sh # RHEL/SLE target
Every workflow run takes three inputs: target-distros (space-separated list;
default is every supported distribution), version (e.g. 2.2.1),
package-iteration (e.g. 1).
Before any container starts, matrix-package.sh runs verify-version.sh on the
host to confirm the checkout actually contains the code tagged vLFMP_VERSION.
The workflows build main HEAD (the checkout step has no ref:), so HEAD is
allowed to sit ahead of the tag, but a checkout that predates or diverges from
the tag fails the build instead of silently shipping outdated code under a newer
version label. Set LFMP_SKIP_VERSION_CHECK=1 to bypass this for an intentional
build of an untagged development checkout.
The shared monitoring-plugins source tarball and vendor.tar.gz (pip-downloaded,
hash-pinned third-party wheels) are produced once per workflow run.
create-rpm.sh and create-deb.sh then build a venv under
/usr/lib64/linuxfabrik-monitoring-plugins/venv/ inside the container and
rewrite plugin shebangs to point at that venv's python, so the final
package is self-contained.
For cutting a release by hand or for debugging a specific distro build, use
an Ubuntu 24.04 LTS host. This matches the ubuntu-24.04 GitHub runner
one-to-one, which is the environment the build scripts are tested against.
build/install-podman.sh uses apt to install podman, so other Linux
families fail there without manual adjustments. Building the packages
themselves is distro-agnostic (every package is produced inside its own
target container), but the orchestrating host should match.
Set the same environment variables the workflow uses and call
matrix-package.sh directly:
cat > env-file << 'EOF'
# User input on GitHub:
export LFMP_ARCH=x86_64 # or "aarch64" on ARM64
export LFMP_VERSION=2.2.1
export LFMP_PACKAGE_ITERATION=1
export LFMP_TARGET_DISTROS="debian13 rocky10" # subset of the supported list
# Constants (absolute paths):
export LFMP_DIR_REPOS=/tmp/lfmp/repos
export LFMP_DIR_REPO_MP=$LFMP_DIR_REPOS/monitoring-plugins
export LFMP_DIR_PACKAGED=/tmp/lfmp/packaged
mkdir -p $LFMP_DIR_REPOS $LFMP_DIR_PACKAGED
EOF
source env-fileSupported LFMP_TARGET_DISTROS values: debian11 debian12 debian13 rocky8 rocky9 rocky10 sle15 sle16 ubuntu2004 ubuntu2204 ubuntu2404 ubuntu2604.
Clone the repository and run the build:
git clone https://github.com/Linuxfabrik/monitoring-plugins.git $LFMP_DIR_REPO_MP
bash $LFMP_DIR_REPO_MP/build/install-podman.sh
bash $LFMP_DIR_REPO_MP/build/matrix-package.shFrom each container's perspective, the Python source code is at
/repos/monitoring-plugins.
matrix-package.sh first runs verify-version.sh, which requires the
vLFMP_VERSION tag to be reachable from the checkout's HEAD. A fresh git clone fetches all tags, so the recipe above works as long as that tag exists.
To cut a real release from an exact tree, git checkout v$LFMP_VERSION after
cloning. To package an untagged development checkout under a placeholder
version, prefix the build with LFMP_SKIP_VERSION_CHECK=1.
After the build, the packages directory looks like this:
$LFMP_DIR_PACKAGED
├── debian13/
│ └── linuxfabrik-monitoring-plugins_2.2.1-1_amd64.deb
└── rocky10/
├── linuxfabrik-monitoring-plugins-2.2.1-1.el10.x86_64.rpm
└── linuxfabrik-monitoring-plugins-selinux-2.2.1-1.el10.x86_64.rpm
Windows is built on a single GitHub-hosted windows-2025 runner with Python
3.13. Every plugin that carries a .windows marker file is compiled with
Nuitka (--standalone, --msvc=latest) into its own directory. The results
are packaged twice: once as a ZIP of the compiled trees, once as an MSI that
installs them under C:\Program Files\ICINGA2\sbin\linuxfabrik\. Both
artifacts are signed by SignPath before they leave the runner.
lf-build-windows-x86_64.yml
├── debug.sh # env dump
├── compile-multiple.sh # loops plugins, calls compile-one.sh
│ ├── verify-version.sh # checkout must contain vLFMP_VERSION
│ └── compile-one.sh # python3 -m nuitka --standalone
├── SignPath submit-signing-request # signs the compiled ZIP
├── create-wxs.sh # emits lfmp.wxs
├── wix.exe build # .wxs -> .msi
└── SignPath submit-signing-request # signs the MSI
Workflow inputs: compile-plugins (optional, space-separated allowlist;
empty means all plugins), version, package-iteration.
Like the Linux build, compile-multiple.sh runs verify-version.sh first, so
a checkout that predates or diverges from the vLFMP_VERSION tag fails the
build instead of compiling outdated code under a newer version label. Set
LFMP_SKIP_VERSION_CHECK=1 to bypass this for an intentional build of an
untagged development checkout.
Free code signing is provided by SignPath.io under the open-source program, with a certificate issued by the SignPath Foundation (thank you).
Signed files are .dll, .exe, .pyd and .msi. Two SignPath
artifact-configuration-slugs are used:
compiled: the ZIP of Nuitka-built plugin directories.packaged: the final MSI.
Both use the release-signing policy. The SignPath project is
monitoring-plugins under organization-id
35067665-5434-42c5-9fa2-4c750069f161. The workflow waits for SignPath to
return each signed artifact before proceeding.
Compiling a single plugin on a Windows developer box is supported. Producing the final MSI locally is not: the workflow drives SignPath, and signing requires the Linuxfabrik project credentials.
Prerequisites:
- Git Bash (the
build/*.shscripts assume bash). - Python 3.13, on
PATHaspython3. - Visual Studio 2022 Build Tools (supplies MSVC, required by Nuitka
--msvc=latest). python3 -m pip install --upgrade ordered-set Nuitka.python3 -m pip install --require-hashes --requirement lockfiles/py313-windows/requirements.txtfrom the monitoring-plugins repository root. ThepyXXdirectory underlockfiles/matches the Python version the Windows build is pinned to (3.13 today).- The plugin must carry a
.windowsmarker file. Plugins without it are skipped bycompile-one.sh.
To compile one plugin:
export LFMP_DIR_REPOS=/c/Users/<you>/lfmp/repos
export LFMP_DIR_COMPILED=/c/Users/<you>/lfmp/compiled
mkdir -p "$LFMP_DIR_COMPILED"
git clone https://github.com/Linuxfabrik/monitoring-plugins.git "$LFMP_DIR_REPOS/monitoring-plugins"
bash "$LFMP_DIR_REPOS/monitoring-plugins/build/compile-one.sh" check-plugins cpu-usage
# result: $LFMP_DIR_COMPILED/check-plugins/cpu-usage/ (unsigned)For a full dry run of the compilation step, call
compile-multiple.sh instead; it loops over every plugin the workflow would
build. It runs verify-version.sh first, so export LFMP_VERSION (with the
matching vLFMP_VERSION tag reachable from HEAD) or prefix the call with
LFMP_SKIP_VERSION_CHECK=1. The MSI and the signing steps have no local
fallback, so stop there.
According to Types of container images, Red Hat Universal Base Images (UBI) are built from a subset of RHEL content. EPEL is installable because a subset of the CRB repo ships with UBI. If we need anything outside that subset, we have to buy a subscription or run the container on a subscribed host. Rocky Linux avoids that constraint and matches upstream RHEL closely enough for our purposes.
docker.io/library/rockylinux is not updated regularly: the Docker Official
Images program has technical constraints that prevent Rocky Linux from
publishing there. The project-maintained repository at
docker.io/rockylinux/rockylinux
is the current source of truth.
| Target OS | Build container |
|---|---|
| Debian 11 | docker.io/library/debian:11 |
| Debian 12 | docker.io/library/debian:12 |
| Debian 13 | docker.io/library/debian:13 |
| RHEL 8 | docker.io/rockylinux/rockylinux:8 |
| RHEL 9 | docker.io/rockylinux/rockylinux:9 |
| RHEL 10 | docker.io/rockylinux/rockylinux:10 |
| SLE 15 | registry.suse.com/suse/sle15:15.5 |
| SLE 16 | registry.suse.com/bci/bci-base:16.0 |
| Ubuntu 20.04 | docker.io/library/ubuntu:20.04 |
| Ubuntu 22.04 | docker.io/library/ubuntu:22.04 |
| Ubuntu 24.04 | docker.io/library/ubuntu:24.04 |
| Ubuntu 26.04 | docker.io/library/ubuntu:26.04 |
The SLE 15 package requires at least openSUSE Leap 15.5 or SLES 15 Service Pack 5 on the target host.
Benchmark, December 2024, compiling disk-usage with each tool and running
it 500 times:
| Platform | Python | Build tool | Mode | Size (MB) | 500 runs (s) | VirusTotal |
|---|---|---|---|---|---|---|
| Rocky 8 | 3.9 | Nuitka | --standalone |
19.7 | 15.706 | |
| Rocky 8 | 3.9 | PyInstaller | --onedir |
13.7 | 19.392 | |
| Rocky 8 | 3.9 | Nuitka | --onefile |
7.9 | 33.339 | |
| Rocky 8 | 3.9 | PyInstaller | --onefile |
6.4 | 45.838 | |
| WinSrv 2022 | 3.12 | Nuitka (msvc) | --standalone |
22.3 | 31.560 | 2/71 |
| WinSrv 2022 | 3.12 | Nuitka (gcc) | --standalone |
23.4 | 29.570 | 4/72 |
| WinSrv 2022 | 3.12 | PyInstaller | --onedir |
16.7 | 51.476 | 13/71 |
| WinSrv 2022 | 3.12 | Nuitka (msvc) | --onefile |
6.67 | 253.006 | 15/72 |
| WinSrv 2022 | 3.12 | Nuitka (gcc) | --onefile |
6.83 | 243.167 | 24/71 |
| WinSrv 2022 | 3.12 | PyInstaller | --onefile |
17.1 | 462.180 | 7/72 |
Takeaways:
- Nuitka
--standaloneis the fastest runtime option on both platforms (1.2x-1.7x over PyInstaller--onedir), at the cost of 30-45% more on-disk size. - Onefile builds pay a per-run self-extraction tax that makes them 3x to 10x slower than their onedir counterparts. For a plugin that runs every 10-60 seconds, this is disqualifying.
- On Windows, Nuitka
--standalonewith MSVC has the lowest Windows Defender / VirusTotal false-positive rate we measured (2/71). gcc is close behind (4/72). Onefile builds are flagged noticeably more often.
The benchmark has not been re-run against Python 3.13 (our current build target); ratios there may have shifted.
Nuitka 4.0 (current stable) lists Python 3.14 as experimental and explicitly
not production-ready. Official support covers Python 3.4 through 3.13. We
build with --standalone --msvc=latest, both paths where partial 3.14 support
in Nuitka tends to surface first. Until Nuitka declares 3.14 stable, the
Windows workflow stays pinned to 3.13.
This has no security impact: actions/setup-python@v6 resolves
python-version: '3.13' to the latest 3.13.x patch release at build time,
so each release picks up the bundled OpenSSL update from upstream CPython.