Skip to content

github: add deb-pkg build pipeline #30

github: add deb-pkg build pipeline

github: add deb-pkg build pipeline #30

name: Generate kernel config
on:
push:
pull_request:
workflow_dispatch:
inputs:
normalize:
description: "Also normalize through the real Kconfig (make olddefconfig + savedefconfig)"
required: false
default: true
type: boolean
jobs:
discover-series:
name: Discover kernel series
runs-on: ubuntu-latest
outputs:
series: ${{ steps.list.outputs.series }}
steps:
- name: Check out
uses: actions/checkout@v4
# Every kernel series we test against is just a directory under misc/
# holding its own zabbly-config -- misc/6.19/, misc/7.0/, misc/7.1/, ...
# Discovering them here (rather than hardcoding a list in the matrix)
# means a new misc/<series>/ is enough to add a series to CI; nothing
# in this workflow needs editing.
- name: List series with a reference config
id: list
run: |
set -euo pipefail
series=$(for d in misc/*/; do
s="$(basename "$d")"
[ -f "misc/${s}/zabbly-config" ] && echo "$s"
done | sort -V | jq -R -s -c 'split("\n")[:-1]')
if [ "$series" = "[]" ]; then
echo "error: no misc/<series>/zabbly-config found under misc/" >&2
exit 1
fi
echo "Series found: $series"
echo "series=$series" >> "$GITHUB_OUTPUT"
generate:
name: ${{ matrix.flavor }} / ${{ matrix.series }}
needs: discover-series
runs-on: ubuntu-latest
strategy:
# One (flavor, series) combination failing should not hide the others.
fail-fast: false
matrix:
flavor: [generic, incus-os]
series: ${{ fromJson(needs.discover-series.outputs.series) }}
env:
FLAVOR: ${{ matrix.flavor }}
SERIES: ${{ matrix.series }}
steps:
- name: Check out
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install kernel build dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential flex bison bc libelf-dev libssl-dev dwarves python3
# misc/<series>/zabbly-config states the exact kernel it was built for
# in its header ("# Linux/x86 <version> Kernel Configuration"), so the
# tarball to download comes from there rather than being guessed from
# the series name alone -- misc/6.19/ doesn't by itself say whether
# that means 6.19.0 or 6.19.4.
- name: Determine kernel version
id: kver
run: |
set -euo pipefail
version="$(sed -n 's|^# Linux/x86 \([0-9][0-9.]*\) Kernel Configuration$|\1|p' \
"misc/${SERIES}/zabbly-config" | head -1)"
if [ -z "$version" ]; then
echo "error: could not determine the kernel version from misc/${SERIES}/zabbly-config" >&2
exit 1
fi
echo "Configuring $SERIES against Linux $version"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "major=${version%%.*}" >> "$GITHUB_OUTPUT"
# Cache the extracted source tree: it is a few hundred MB of pure
# download+unpack that never changes for a given released version, so the
# key needs no hash beyond the version itself.
- name: Cache kernel source tree
id: kernel-cache
uses: actions/cache@v4
with:
path: ~/kernel/linux-${{ steps.kver.outputs.version }}
key: linux-src-${{ steps.kver.outputs.version }}
- name: Download kernel source tree
if: steps.kernel-cache.outputs.cache-hit != 'true'
run: |
set -euo pipefail
v="${{ steps.kver.outputs.version }}"
url="https://cdn.kernel.org/pub/linux/kernel/v${{ steps.kver.outputs.major }}.x/linux-${v}.tar.xz"
mkdir -p ~/kernel
echo "Fetching $url"
curl -fSL --retry 3 "$url" | tar -xJ -C ~/kernel
test -f ~/kernel/linux-"$v"/Makefile
# Same step a human does per the README: copy the template, point it at
# this machine's paths. Note there's no REFERENCE_CONFIG to set here --
# genconfig.sh works that out itself from KERNEL_TREE_PATH's own
# reported version, same as a local run gets.
- name: Create .env
run: |
set -euo pipefail
sed -e "s|^KERNEL_TREE_PATH=.*|KERNEL_TREE_PATH=$HOME/kernel/linux-${{ steps.kver.outputs.version }}|" \
-e "s|^KERNEL_TREE_BUILD_PATH=.*|KERNEL_TREE_BUILD_PATH=$RUNNER_TEMP/kernel-build|" \
.env.example > .env
cat .env
- name: Check config_slices are disjoint
run: ./check_slices.py "$FLAVOR"
# genconfig.sh gives every flavor but generic a suffixed output name, so
# that they cannot clobber each other. Mirror that rule here.
- name: Resolve the generated config path
run: |
set -euo pipefail
if [ "$FLAVOR" = "generic" ]; then
echo "CONFIG_FILE=generated_config" >> "$GITHUB_ENV"
else
echo "CONFIG_FILE=generated_config-$FLAVOR" >> "$GITHUB_ENV"
fi
# The flag is passed explicitly rather than left to .env.example, so CI
# behaviour cannot change as a side effect of editing that template. The
# runner does have the build dependencies, so normalization is a
# workflow_dispatch option rather than something that cannot run here.
# Which misc/<series>/zabbly-config gets compared against is no longer
# this workflow's concern at all -- genconfig.sh derives it from the
# kernel tree we just downloaded, and the "Determine kernel version"
# step above guarantees that tree matches $SERIES.
- name: Generate the config
run: |
if [ "${{ inputs.normalize }}" = "true" ] || [ -z "${{ inputs.normalize }}" ]; then
./genconfig.sh "$FLAVOR" --normalize --validate
else
./genconfig.sh "$FLAVOR" --no-normalize --validate
fi
- name: Show diff against the reference config
if: always()
run: |
set -euo pipefail
if [ ! -f output/${FLAVOR}/diff ]; then
echo "output/${FLAVOR}/diff was not produced -- the generation step must have failed."
exit 1
fi
lines=$(wc -l < output/${FLAVOR}/diff)
echo "::notice::output/${FLAVOR}/diff ($SERIES) is $lines line(s)"
{
echo "## $FLAVOR / $SERIES: diff vs misc/$SERIES/zabbly-config ($lines lines)"
echo
echo 'Left column is the reference config, right column is our generated config.'
echo
if [ "$FLAVOR" = "generic" ]; then
echo 'The generic flavor aims to reproduce the reference exactly, so'
echo 'every line here is something to explain.'
else
echo "The $FLAVOR flavor deliberately differs from a general-purpose"
echo 'distro kernel, so this diff is the list of what it drops or'
echo 'changes -- a large one is the point, not a defect.'
fi
echo
echo 'Either way, some divergence is environmental rather than ours:'
echo 'the reference config was built with a different compiler, so'
echo '`CC_VERSION_TEXT` and any gcc-version-gated symbols will differ'
echo 'from what this runner produces.'
echo
echo '```diff'
cat output/${FLAVOR}/diff
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
echo "----- output/${FLAVOR}/diff -----"
cat output/${FLAVOR}/diff
echo "-----------------------"
# Informational only. kernel-hardening-checker exits 0 whatever the
# verdicts are and non-zero only when it cannot run, so simply not
# swallowing its status gives exactly the behaviour we want: FAIL lines
# are reported, a broken tool breaks the build.
- name: Check config hardening
run: |
set -euo pipefail
./check-config-hardening.sh "$CONFIG_FILE" | tee "output/$FLAVOR/hardening.txt"
echo "::notice::$(grep -F '[+] Check is finished:' output/${FLAVOR}/hardening.txt \
|| echo 'hardening check finished')"
{
echo "## $FLAVOR / $SERIES: hardening check"
echo
echo 'Reported, not enforced: these flavors target general-purpose'
echo 'distro kernels, which are nowhere near hardened ones, so FAIL'
echo 'lines here are expected. This step fails only if'
echo 'kernel-hardening-checker itself errors out.'
echo
echo '<details><summary>Full report</summary>'
echo
echo '```'
cat output/${FLAVOR}/hardening.txt
echo '```'
echo
echo '</details>'
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload generated config and analysis
if: always()
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.flavor }}-${{ matrix.series }}-config
path: |
${{ env.CONFIG_FILE }}
output/${{ matrix.flavor }}/
if-no-files-found: warn