github: add deb-pkg build pipeline #30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |