Archived
Public Access
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>
358 lines
15 KiB
YAML
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)."
|