Skip to content

feat(build): Gate the Linux port with full CI and document its runtime contract - #737

Merged
rabbitstack merged 4 commits into
rabbitstack:linux-portfrom
mostafa:feat/linux-build-ci
Sep 23, 2026
Merged

rabbitstack merged 4 commits into
rabbitstack:linux-portfrom
mostafa:feat/linux-build-ci

Conversation

@mostafa

@mostafa mostafa commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Rounds out the Linux build and CI so the backend is gated the way the Windows one is, and documents what a host has to provide to run it.

Build targets

Target Purpose
build Pins GOOS/GOARCH and builds from the committed objects, so a checkout needs no clang
ebpf-drift Regenerates and fails if the result differs from what is committed
check-clang Refuses to generate with the wrong clang major
test-race, test-integration, lint The remaining gates, runnable locally

check-clang exists because clang records its version in BTF. Generating with a different major rewrites every object and fails the drift check with a diff that reads like a source change rather than a toolchain mismatch, which is a confusing half hour the first time it happens.

CI

The Linux workflow now runs build, generation drift, lint, unit tests, race tests, rule validation, a prerequisite probe, and the privileged integration suite.

Two steps are ordered deliberately. The build runs before clang is installed, so it fails the moment something starts depending on generation tools at build time instead of the committed objects. And the prerequisite probe is its own step rather than being implied by the integration tests: without it, a runner that silently loses runtime BTF or drops below the 5.9 floor produces a suite where every test fails identically, for a reason buried in a wrapped error several frames down. TestPrerequisitesAreMet runs the same gate Open does, so the architecture check is covered too, and it fails rather than skipping.

clang is pinned to the major the committed objects were generated with, for the reason above.

Lint scope

Lint is blocking, but covers internal/ebpf, internal/bootstrap, pkg/filter, and pkg/rules rather than everything that builds on Linux. Widening it brings back 41 findings in shared code, nearly all unused reports for helpers that only Windows reaches, and there is nothing to fix from this side without moving Windows code around.

Getting that scope to zero did turn up real dead code, which is the first commit. containsEventTypes and containsFieldMatch are only ever called from compiler_windows.go, and isNumber only validates Windows field arguments, so all three moved next to their callers. The Linux framePID and threadpool accessor stubs had no callers at all and are gone. The event.PID(...) conversions around MustGetPid were no-ops on both platforms, since PID is a type alias for exactly what the accessor returns.

go vet is not a separate target: govet already runs inside golangci-lint on the same scope, and running it wider trips pre-existing MarshalJSON() []byte signature findings in shared marshalling code that are out of scope here.

Docs

docs/setup/linux.md covers the hard prerequisites and why there is no BTF fallback, the capability set, how to read the startup probe, which attachment warnings are expected, best-effort procfs enrichment, and the ebpf.* metrics.

Two additions worth flagging. CAP_KILL is listed because the kill action needs it against processes Fibratus does not own. And the page calls out the two situations capabilities cannot fix, kernel lockdown in confidentiality mode and pre-5.11 RLIMIT_MEMLOCK accounting, because both surface as permission errors that look exactly like a missing capability.

Verification

build-linux from committed objects with no clang present, make test across 8 packages, make test-race, make ebpf-drift clean, lint clean on the gated scope, and GOOS=windows go build ./... unaffected. The prerequisite probe was exercised in both directions: failing loudly with linux eBPF capture requires amd64 on arm64, and passing with the full probe line on a capable host.

Several helpers in shared files are reachable only from Windows code, so
a Linux build sees them as dead. Move containsEventTypes and
containsFieldMatch next to their only caller in compiler_windows.go, and
isNumber next to the field definitions that validate with it. Drop the
Linux framePID and threadpool accessor stubs, which nothing calls now
that callstack and threadpool fields are Windows-only.

event.PID is an alias for the same integer the params accessor already
returns on both platforms, so the conversions around MustGetPid never
converted anything.

This makes the packages the Linux port owns lint clean, which the next
commit turns into a CI gate.
@mostafa
mostafa force-pushed the feat/linux-build-ci branch from b00baa1 to e01e683 Compare September 23, 2026 09:26
@mostafa
mostafa marked this pull request as draft September 23, 2026 09:30
@mostafa
mostafa force-pushed the feat/linux-build-ci branch from e01e683 to 85026ab Compare September 23, 2026 09:33
@mostafa
mostafa marked this pull request as ready for review September 23, 2026 09:36
Comment thread .github/workflows/linux-port.yml
Comment thread Makefile Outdated
Comment thread Makefile Outdated
Comment thread .github/workflows/linux-port.yml Outdated
check-clang refuses to generate with the wrong clang major. The
committed objects are byte-compared in CI and clang records its version
in BTF, so generating with a different major rewrites every object and
fails the drift check with a diff that looks like a source change.

ebpf-drift turns that comparison into something runnable locally rather
than a pair of CI steps, and build-linux cross-compiles from the
committed objects to prove a checkout needs no clang.

lint covers a narrower set than the tests. Packages outside it hold
helpers only Windows reaches, so `unused` flags them on every Linux run
with nothing to fix on this side.
Build before clang is installed, so the step fails if anything starts
depending on generation tools at build time rather than on the committed
objects.

Probe the runtime contract as its own step. Without it a runner that
lost runtime BTF or fell below the kernel floor produces a green suite
that never loaded a program, because every test would fail the same way
for a reason buried in a wrapped error.

Pin clang to the major the objects were generated with, otherwise the
drift check reports a diff on every unrelated change.
Covers what the backend refuses to start without, which capabilities
replace running as root, and how to read the probe output. Calls out the
two cases capabilities cannot fix, kernel lockdown and the pre-5.11
memlock accounting, because both surface as permission errors that look
like a missing capability.

Documents which attachment warnings are expected, since the optional
legacy tracepoints log a permission denial on kernels that refuse a perf
link on them and that is not a fault.
@mostafa
mostafa force-pushed the feat/linux-build-ci branch from 85026ab to c7e06f3 Compare September 23, 2026 12:02
@rabbitstack
rabbitstack merged commit 63f4c6b into rabbitstack:linux-port Sep 23, 2026
1 check passed
@mostafa
mostafa deleted the feat/linux-build-ci branch September 23, 2026 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants