This repository has been archived on 2026-06-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
linux-kernel-bbrv3/.github/workflows/build.yml
T
Claude 84107222b9 Raise ccache max size to 8G
2G is tight when caching objects across kernel-version transitions (the cache holds more than one X.Y.Z at a time); 8G keeps older objects from being evicted prematurely. ccache only stores what a build produces, so this raises the cap, not the per-run size.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 01:16:20 +08:00

358 lines
15 KiB
YAML

name: 构建带有BBRv3的内核
# Gitea Actions reads workflows from .github/workflows/ (and .gitea/workflows/).
# This workflow targets a self-hosted Gitea instance. Kernel .deb packages are
# published to Gitea's built-in Debian package registry (an apt source), not to
# Releases. arm64 is cross-compiled on the x86_64 runner
# (CROSS_COMPILE=aarch64-linux-gnu-), so only a single ubuntu-latest runner is needed.
on:
workflow_dispatch:
schedule:
# Daily kernel refresh at 20:00 UTC = 04:00 Asia/Shanghai (GMT+8). Gitea/GitHub
# Actions cron is evaluated in UTC. BBRv3 itself stays pinned to the repo patch.
- cron: "0 20 * * *"
env:
# Gitea Debian package registry for this owner, e.g.
# https://git.chilldove.com/api/packages/icePigeon/debian
PKG_REGISTRY: ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/debian
PKG_USER: ${{ github.repository_owner }}
# Optional origin pin for uploads. The kernel image .deb is >100 MB, which trips
# the request-body cap of a CDN/proxy fronting the registry (Cloudflare free/pro
# caps at 100 MB and returns 413 at the edge, before the body reaches Gitea — the
# origin itself accepts it). Set the Gitea repo/org variable PKG_ORIGIN_IP to the
# registry origin's address (the host behind the CDN) and the publish step pins the
# upload straight there via curl --resolve, bypassing the CDN. vars (unlike secrets)
# ARE available to workflow-level env. Leave empty if no CDN fronts uploads.
PKG_ORIGIN_IP: ${{ vars.PKG_ORIGIN_IP }}
# NOTE: PKG_TOKEN (the write:package secret) is declared at JOB level below. The
# secrets context is not available to workflow-level env, so a value set here
# would silently be empty.
permissions:
# Uploads authenticate via the PKG_TOKEN secret, not the auto-injected token,
# so no write permission is requested here.
contents: read
concurrency:
group: bbrv3-kernel-build
cancel-in-progress: true
jobs:
preflight:
runs-on: ubuntu-latest
env:
PKG_TOKEN: ${{ secrets.PKG_TOKEN }}
outputs:
kernel_version: ${{ steps.plan.outputs.kernel_version }}
build_needed: ${{ steps.plan.outputs.build_needed }}
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 选择 stable 内核并检查 registry
id: plan
run: |
set -euo pipefail
# Track an established stable series, never the freshly-cut top series
# that still equals mainline. See scripts/select-stable-kernel.sh.
version=$(bash scripts/select-stable-kernel.sh)
echo "Selected stable kernel: $version"
echo "kernel_version=$version" >> "$GITHUB_OUTPUT"
# Build unless BOTH arches already have their COMPLETE artifact set
# (kernel image + meta@version + config, plus headers on amd64). A partial
# previous upload counts as incomplete and triggers a rebuild.
missing=0
for da in amd64 arm64; do
if bash scripts/registry-has-all.sh "$PKG_REGISTRY" stable main "$version" "$da"; then
echo "All artifacts for $version ($da) already in registry."
else
missing=1
fi
done
if [ "$missing" -eq 1 ]; then
echo "build_needed=true" >> "$GITHUB_OUTPUT"
echo "At least one arch for $version is incomplete; build will continue."
else
echo "build_needed=false" >> "$GITHUB_OUTPUT"
echo "All arches for $version fully published; ending before build."
fi
build:
needs: preflight
if: needs.preflight.outputs.build_needed == 'true'
strategy:
fail-fast: false
matrix:
include:
# Both targets build on a single x86_64 runner; arm64 is cross-compiled.
# is_primary builds the Architecture:all bbrv3-config package exactly once.
- arch: x86_64
deb_arch: amd64
runs_on: ubuntu-latest
cross_compile: ""
localversion: -bbrv3
is_primary: "true"
- arch: arm64
deb_arch: arm64
runs_on: ubuntu-latest
cross_compile: aarch64-linux-gnu-
localversion: -bbrv3
is_primary: "false"
runs-on: ${{ matrix.runs_on }}
env:
ARCH: ${{ matrix.arch }}
DEB_ARCH: ${{ matrix.deb_arch }}
KERNEL_VERSION: ${{ needs.preflight.outputs.kernel_version }}
PKG_TOKEN: ${{ secrets.PKG_TOKEN }}
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 检查 registry 是否已发布
id: check_present
run: |
set -euo pipefail
if bash scripts/registry-has-all.sh "$PKG_REGISTRY" stable main "$KERNEL_VERSION" "$DEB_ARCH"; then
echo "BUILD_NEEDED=false" >> "$GITHUB_ENV"
echo "Full artifact set for $KERNEL_VERSION ($DEB_ARCH) present; skipping."
else
echo "BUILD_NEEDED=true" >> "$GITHUB_ENV"
echo "Artifact set for $KERNEL_VERSION ($DEB_ARCH) incomplete; building."
fi
- name: 安装依赖项
if: env.BUILD_NEEDED == 'true'
run: |
sudo apt-get update
sudo apt-get install -y \
git build-essential \
libncurses-dev libssl-dev libelf-dev \
bison bc flex rsync debhelper \
dpkg-dev fakeroot kmod cpio dwarves \
libdw-dev lz4 zstd xz-utils curl jq
- name: 安装 ARM64 交叉编译工具链
if: env.BUILD_NEEDED == 'true' && matrix.arch == 'arm64'
run: |
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: 配置 ccache
if: env.BUILD_NEEDED == 'true'
run: |
set -euo pipefail
sudo apt-get install -y ccache
# Wrap the (cross-)compilers through /usr/lib/ccache symlinks; the build
# picks them up because this dir is prepended to PATH below.
sudo update-ccache-symlinks || true
ccache_bin="$(command -v ccache)"
sudo ln -sf "$ccache_bin" /usr/lib/ccache/gcc
if [ "$ARCH" = "arm64" ]; then
sudo ln -sf "$ccache_bin" /usr/lib/ccache/aarch64-linux-gnu-gcc
fi
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
{
echo "CCACHE_DIR=$HOME/.ccache"
echo "CCACHE_MAXSIZE=8G"
echo "CCACHE_COMPRESS=1"
} >> "$GITHUB_ENV"
- name: 恢复 ccache 缓存
if: env.BUILD_NEEDED == 'true'
uses: actions/cache/restore@v4
with:
# Only the compiler cache is worth persisting: the kernel source is
# re-cloned each run and out/ holds freshly built packages. A unique key
# per run forces a fresh save; restore-keys warm-start from the most recent
# prior build of the same arch (then any build of the same arch). Restore
# and save are split (not the combined actions/cache) because that action's
# post-save is gated by post-if: success() — a failed publish would discard
# the whole compile cache. The matching save runs right after the build.
path: ~/.ccache
key: ccache-${{ matrix.arch }}-${{ needs.preflight.outputs.kernel_version }}-${{ github.run_id }}
restore-keys: |
ccache-${{ matrix.arch }}-${{ needs.preflight.outputs.kernel_version }}-
ccache-${{ matrix.arch }}-
- name: 创建源码目录
if: env.BUILD_NEEDED == 'true'
run: mkdir -p ./kernel/linux
- name: 下载内核源代码
if: env.BUILD_NEEDED == 'true'
working-directory: ./kernel
run: |
branch=$(echo "$KERNEL_VERSION" | grep -oP '^\d+\.\d+')
git clone --depth=1 --branch linux-$branch.y \
https://github.com/gregkh/linux.git linux
- name: 应用 BBRv3 补丁
if: env.BUILD_NEEDED == 'true'
working-directory: ./kernel/linux
run: |
bash "$GITHUB_WORKSPACE/scripts/apply-bbrv3-port.sh"
grep -n "BBR_VERSION" net/ipv4/tcp_bbr.c
- name: 更新 Makefile 中的版本号
if: env.BUILD_NEEDED == 'true'
working-directory: ./kernel/linux
run: |
IFS='.' read -r v p s <<< "$KERNEL_VERSION"
sed -i "s/^VERSION *=.*/VERSION = $v/" Makefile
sed -i "s/^PATCHLEVEL *=.*/PATCHLEVEL = $p/" Makefile
sed -i "s/^SUBLEVEL *=.*/SUBLEVEL = $s/" Makefile
- name: 准备 .config 并禁用证书检查
if: env.BUILD_NEEDED == 'true'
timeout-minutes: 8
working-directory: ./kernel/linux
env:
TARGET_ARCH: ${{ matrix.arch }}
CROSS_COMPILE: ${{ matrix.cross_compile }}
run: |
bash "$GITHUB_WORKSPACE/scripts/prepare-kernel-config.sh" "$TARGET_ARCH"
- name: 构建内核 Debian 包
if: env.BUILD_NEEDED == 'true'
working-directory: ./kernel/linux
env:
TARGET_ARCH: ${{ matrix.arch }}
LOCALVERSION_SUFFIX: ${{ matrix.localversion }}
CROSS_COMPILE: ${{ matrix.cross_compile }}
run: |
if [ "$TARGET_ARCH" = "arm64" ]; then
# Drop the linux-headers package on the arm64 cross build: with
# CONFIG_MODULE_SIG_FORMAT=y its packaging cross-compiles scripts/sign-file
# and would need arm64 OpenSSL dev headers (libssl-dev:arm64) we don't
# install. nokernelheaders is the kernel maintainers' documented remedy.
DEB_BUILD_PROFILES=pkg.linux-upstream.nokernelheaders \
make ARCH=arm64 CROSS_COMPILE="$CROSS_COMPILE" bindeb-pkg -j$(nproc) LOCALVERSION="$LOCALVERSION_SUFFIX" KDEB_COMPRESS=gzip skipdbg=true
else
make bindeb-pkg -j$(nproc) LOCALVERSION="$LOCALVERSION_SUFFIX" KDEB_COMPRESS=gzip skipdbg=true
fi
- name: ccache 统计
if: env.BUILD_NEEDED == 'true'
run: ccache -s
- name: 保存 ccache 缓存
# Save even when a later step fails: the compiler cache is valid the moment
# the build finishes, so a downstream failure (e.g. the registry upload) must
# not discard it. !cancelled() = save on success or failure, but skip
# cancellations (cancel-in-progress) so half-built runs don't bloat the cache.
# Key matches the restore step's primary key above.
if: ${{ !cancelled() && env.BUILD_NEEDED == 'true' }}
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ matrix.arch }}-${{ needs.preflight.outputs.kernel_version }}-${{ github.run_id }}
- name: 检查 deb 包
if: env.BUILD_NEEDED == 'true'
run: |
if find ./kernel -maxdepth 1 \( -name '*-dbg*.deb' -o -name '*-dbgsym*.deb' \) | grep -q .; then
echo "ERROR: debug deb package was generated."
find ./kernel -maxdepth 1 \( -name '*-dbg*.deb' -o -name '*-dbgsym*.deb' \) -print
exit 1
fi
find ./kernel -maxdepth 1 -name 'linux-*.deb' -print | sort
- name: 构建 meta 与 config 包
if: env.BUILD_NEEDED == 'true'
env:
IS_PRIMARY: ${{ matrix.is_primary }}
run: |
set -euo pipefail
mkdir -p ./out
# Depend on the *actual* image package name, immune to localversion drift.
img_deb=$(find ./kernel -maxdepth 1 -name 'linux-image-*.deb' ! -name '*-dbg*' | sort | head -n1)
if [ -z "$img_deb" ]; then
echo "No linux-image .deb found to base the meta-package on." >&2
exit 1
fi
img_pkg=$(dpkg-deb -f "$img_deb" Package)
echo "Kernel image package: $img_pkg"
bash scripts/build-meta-package.sh "$KERNEL_VERSION" "$DEB_ARCH" "$img_pkg" ./out
# bbrv3-config is Architecture:all; build it once (on the primary arch).
if [ "$IS_PRIMARY" = "true" ]; then
bash scripts/build-config-package.sh ./out
fi
- name: 发布前复查是否已发布
if: env.BUILD_NEEDED == 'true'
run: |
set -euo pipefail
if bash scripts/registry-has-all.sh "$PKG_REGISTRY" stable main "$KERNEL_VERSION" "$DEB_ARCH"; then
echo "PUBLISH_NEEDED=false" >> "$GITHUB_ENV"
echo "Full set published while this job was running; skipping upload."
else
echo "PUBLISH_NEEDED=true" >> "$GITHUB_ENV"
echo "Set still incomplete; publishing this build."
fi
- name: 发布到 Gitea Debian registry
if: env.BUILD_NEEDED == 'true' && env.PUBLISH_NEEDED == 'true'
run: |
set -euo pipefail
if [ -z "${PKG_TOKEN:-}" ]; then
echo "PKG_TOKEN secret is required to publish (needs write:package scope)." >&2
exit 1
fi
# The kernel image .deb is >100 MB, which trips the request-body cap of a
# CDN/proxy in front of the registry (Cloudflare free/pro = 100 MB) and 413s
# at the edge before the body reaches Gitea — the origin itself accepts it.
# If PKG_ORIGIN_IP is set, pin the upload straight to that origin so the
# oversized PUT never touches the CDN. SNI/Host stay $reg_host, so TLS and
# package routing are unchanged; only the TCP connect target moves.
reg_host=${PKG_REGISTRY#*://}; reg_host=${reg_host%%/*}
resolve=()
if [ -n "${PKG_ORIGIN_IP:-}" ]; then
resolve=(--resolve "${reg_host}:443:${PKG_ORIGIN_IP}")
echo "Uploading direct to origin ${PKG_ORIGIN_IP}, bypassing any CDN in front of ${reg_host}."
else
echo "PKG_ORIGIN_IP unset: uploading via DNS for ${reg_host}; if a CDN fronts it (e.g. Cloudflare, 100 MB cap) the kernel image will 413. Set the PKG_ORIGIN_IP variable to fix." >&2
fi
# Gitea returns 201/202 on upload, 409 when the file already exists.
upload() {
local f="$1" code
code=$(curl -sS -o /dev/null -w '%{http_code}' "${resolve[@]}" \
--user "$PKG_USER:$PKG_TOKEN" --upload-file "$f" \
"$PKG_REGISTRY/pool/stable/main/upload" || echo 000)
case "$code" in
201|202) echo "Uploaded $(basename "$f") ($code)";;
409) echo "Already present $(basename "$f") (409)";;
*) echo "Upload failed: $(basename "$f") (HTTP $code)" >&2; return 1;;
esac
}
shopt -s nullglob
# Publish the kernel image and (x86_64) headers, plus the meta/config
# packages. Never linux-libc-dev (clashes with the distro's userspace
# headers) and never *-dbg* packages.
assets=()
for f in ./kernel/linux-image-*.deb ./kernel/linux-headers-*.deb; do
case "$f" in *-dbg*) continue;; esac
assets+=("$f")
done
assets+=( ./out/*.deb )
if [ ${#assets[@]} -eq 0 ]; then
echo "No .deb assets found to upload." >&2
exit 1
fi
for f in "${assets[@]}"; do
echo "Publishing $(basename "$f")"
upload "$f"
done
echo "Published ${#assets[@]} package(s) for $KERNEL_VERSION ($DEB_ARCH)."