From ad45441d401667256f73b8de3ee6657392e2dcf6 Mon Sep 17 00:00:00 2001 From: Dan Kluser Date: Fri, 8 May 2026 22:15:43 +0200 Subject: [PATCH] add PS5 kernel build pipeline with multi-distro support and arm64 cross-compile --- .github/workflows/dispatch.yml | 18 +++ .gitignore | 3 + README.md | 21 ++- build_image.sh | 182 ++++++++++++++------------ docker/kernel-builder-arch/Dockerfile | 5 +- docker/kernel-builder/Dockerfile | 6 +- docker/kernel-builder/package-deb.sh | 29 ++++ 7 files changed, 178 insertions(+), 86 deletions(-) create mode 100644 .github/workflows/dispatch.yml create mode 100755 docker/kernel-builder/package-deb.sh diff --git a/.github/workflows/dispatch.yml b/.github/workflows/dispatch.yml new file mode 100644 index 0000000..7dbf336 --- /dev/null +++ b/.github/workflows/dispatch.yml @@ -0,0 +1,18 @@ +name: Dispatch to CI + +on: + push: + branches: + - main + +jobs: + dispatch: + runs-on: ubuntu-latest + steps: + - name: Send repository_dispatch + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.DISPATCH_TOKEN }} + repository: resulknad/ps5-vps-setup + event-type: image-builder-updated + client-payload: '{"sha": "${{ github.sha }}"}' diff --git a/.gitignore b/.gitignore index 7a1250d..b6ab290 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ cache/ +ccache/ work/ output/ linux-bin/ build.log +*.swp +patches.diff diff --git a/README.md b/README.md index 0ab2a37..08e1bfd 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ sudo dd if=output/ps5-ubuntu2604.img of=/dev/sdX bs=4M status=progress | `--kernel` | Path to kernel source directory | auto-clone `v6.19.10` | | `--img-size` | Disk image size in MB | `12000` (`32000` for `all`) | | `--clean` | Remove all cached build artifacts and start fresh | off | +| `--kernel-only` | Build and package the kernel only, then exit | off | +| `--patches-ref` | Branch, tag, or commit SHA for patches | `v1.0` | ## Caching @@ -105,10 +107,27 @@ All verbose output goes to `build.log`. The terminal shows a spinner with live p The boot partition contains kexec scripts to switch between distros at runtime. Ubuntu 26.04 is the default boot target. +## Building the Kernel Standalone + +Use `--kernel-only` to compile the PS5 kernel and produce installable packages without building a full disk image. + +```bash +./build_image.sh --kernel-only # .deb (default) +./build_image.sh --kernel-only --distro all # .deb + .pkg.tar.zst +./build_image.sh --kernel-only --patches-ref main # fetch from specific branch/tag +./build_image.sh --kernel-only --clean # wipe and rebuild from scratch +``` + +Output packages are written to `linux-bin/`. Install on a running PS5 Linux system: + +```bash +sudo dpkg -i linux-bin/linux-ps5_*.deb +``` + ## Directory Layout ``` -build_image.sh # Main build script +build_image.sh # Image builder (also supports --kernel-only) docker/ kernel-builder/ # Kernel compilation container kernel-builder-arch/ # Repackages .deb kernel as .pkg.tar.zst diff --git a/build_image.sh b/build_image.sh index b9e1a52..52b8db4 100755 --- a/build_image.sh +++ b/build_image.sh @@ -8,6 +8,8 @@ DISTRO="ubuntu2604" KERNEL_SRC="" CLEAN=false IMG_SIZE=12000 +KERNEL_ONLY=false +PATCHES_REF="v1.0" MULTI_DISTROS="ubuntu2604 arch alpine cachyos" @@ -19,15 +21,19 @@ usage() { echo " --kernel Path to kernel source directory (default: auto-clone to work/linux/)" echo " --img-size Disk image size in MB (default: 12000, 32000 for --distro all)" echo " --clean Remove all cached build artifacts and start from scratch" + echo " --kernel-only Build and package the kernel only, then exit" + echo " --patches-ref Branch, tag, or commit SHA for patches (default: v1.0)" exit 1 } while [[ $# -gt 0 ]]; do case $1 in - --distro) DISTRO="$2"; shift 2 ;; - --kernel) KERNEL_SRC="$2"; shift 2 ;; - --img-size) IMG_SIZE="$2"; shift 2 ;; - --clean) CLEAN=true; shift ;; + --distro) DISTRO="$2"; shift 2 ;; + --kernel) KERNEL_SRC="$2"; shift 2 ;; + --img-size) IMG_SIZE="$2"; shift 2 ;; + --clean) CLEAN=true; shift ;; + --kernel-only) KERNEL_ONLY=true; shift ;; + --patches-ref) [ -n "$2" ] && PATCHES_REF="$2"; shift 2 ;; -h|--help) usage ;; *) echo "Unknown option: $1"; usage ;; esac @@ -36,10 +42,8 @@ done LINUX_REPO="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git" LINUX_DEFAULT_DIR="$SCRIPT_DIR/work/linux" -PATCHES_REPO="https://github.com/ps5-linux/ps5-linux-patches.git" -PATCHES_BRANCH="v1.0" +PATCHES_REPO="https://github.com/resulknad/ps5-linux-patches.git" PATCHES_DIR="$SCRIPT_DIR/work/ps5-linux-patches" -PATCHES_CONFIG=".config" if [ -z "$KERNEL_SRC" ]; then KERNEL_SRC="$LINUX_DEFAULT_DIR" @@ -49,14 +53,25 @@ KERNEL_OUT="$SCRIPT_DIR/linux-bin" OUTPUT_DIR="$SCRIPT_DIR/output" CHROOT_DIR="$SCRIPT_DIR/work/chroot" CACHE_DIR="$SCRIPT_DIR/work/cache" -CCACHE_DIR="$SCRIPT_DIR/cache/ccache" +CCACHE_DIR="${CCACHE_DIR:-$SCRIPT_DIR/ccache}" LOG_FILE="$SCRIPT_DIR/build.log" DOCKER_NAME="ps5-build-$$" + + if [ "$DISTRO" = "all" ] && [ "$IMG_SIZE" = "12000" ]; then IMG_SIZE=32000 fi +if [ -z "$FORMAT" ]; then + case "$DISTRO" in arch) FORMAT="arch" ;; all) FORMAT="all" ;; *) FORMAT="deb" ;; esac +fi + +KERNEL_BUILDER_PLATFORM="linux/amd64" +case "$(uname -m)" in + aarch64|arm64) KERNEL_BUILDER_PLATFORM="linux/arm64" ;; +esac + # --- Signal trap: clean up docker containers and background jobs on exit --- BUILD_PID="" @@ -73,14 +88,11 @@ trap cleanup INT TERM # --- Clean --- if [ "$CLEAN" = true ]; then echo "Cleaning all build artifacts..." - # Build artifacts contain root-owned files from Docker — use a container to remove them - for dir in "$SCRIPT_DIR/work" "$KERNEL_OUT" "$SCRIPT_DIR/cache"; do - if [ -d "$dir" ]; then - docker run --rm --privileged -v "$dir":/clean alpine sh -c 'rm -rf /clean/*' - rmdir "$dir" 2>/dev/null || true - fi + for dir in "$SCRIPT_DIR/work" "$KERNEL_OUT" "$SCRIPT_DIR/cache" "$OUTPUT_DIR"; do + [ -d "$dir" ] && docker run --rm \ + -v "$(dirname "$dir")":/parent \ + alpine rm -rf "/parent/$(basename "$dir")" done - rm -rf "$OUTPUT_DIR" echo "Done." echo "" fi @@ -89,17 +101,13 @@ fi SKIP_KERNEL=false SKIP_CHROOT=false -# Kernel packages already built? -if [ "$DISTRO" = "arch" ] || [ "$DISTRO" = "cachyos" ]; then - ls "$KERNEL_OUT"/*.pkg.tar.zst 1>/dev/null 2>&1 && SKIP_KERNEL=true -elif [ "$DISTRO" = "all" ]; then - ls "$KERNEL_OUT"/*.deb 1>/dev/null 2>&1 && \ - ls "$KERNEL_OUT"/*.pkg.tar.zst 1>/dev/null 2>&1 && SKIP_KERNEL=true -else - ls "$KERNEL_OUT"/*.deb 1>/dev/null 2>&1 && SKIP_KERNEL=true -fi +case "$FORMAT" in + arch) ls "$KERNEL_OUT"/*.pkg.tar.zst 1>/dev/null 2>&1 && SKIP_KERNEL=true ;; + all) ls "$KERNEL_OUT"/*.deb 1>/dev/null 2>&1 && \ + ls "$KERNEL_OUT"/*.pkg.tar.zst 1>/dev/null 2>&1 && SKIP_KERNEL=true ;; + *) ls "$KERNEL_OUT"/*.deb 1>/dev/null 2>&1 && SKIP_KERNEL=true ;; +esac -# Chroot already populated? if [ "$DISTRO" = "all" ]; then SKIP_CHROOT=true for d in $MULTI_DISTROS; do @@ -109,21 +117,18 @@ else [ -d "$CHROOT_DIR/bin" ] && SKIP_CHROOT=true fi -if [ "$DISTRO" = "arch" ] || [ "$DISTRO" = "cachyos" ]; then - PKG_EXT="pkg.tar.zst" -else - PKG_EXT="deb" -fi - # --- Build plan summary --- echo "" echo "PS5 Linux Image Builder" echo "=======================" -echo " Distro: $DISTRO" -if [ "$DISTRO" = "all" ]; then - echo " ($MULTI_DISTROS)" +if [ "$KERNEL_ONLY" = true ]; then + echo " Mode: kernel only" + echo " Format: $FORMAT" +else + echo " Distro: $DISTRO" + [ "$DISTRO" = "all" ] && echo " ($MULTI_DISTROS)" + echo " Image size: ${IMG_SIZE}MB" fi -echo " Image size: ${IMG_SIZE}MB" if [ -f "$PATCHES_DIR/.config" ]; then LINUX_BRANCH="v$(grep -m1 "^# Linux/" "$PATCHES_DIR/.config" | grep -oP '\d+\.\d+(\.\d+)?')" echo " Kernel: $LINUX_BRANCH" @@ -135,19 +140,19 @@ echo "" echo "Stages:" if [ "$SKIP_KERNEL" = true ]; then echo " 1. Kernel cached" +elif [ -d "$KERNEL_SRC/.git" ]; then + echo " 1. Kernel build (source cached)" else - if [ -d "$KERNEL_SRC/.git" ]; then - echo " 1. Kernel build (source cached)" + echo " 1. Kernel clone + build" +fi +if [ "$KERNEL_ONLY" = false ]; then + if [ "$SKIP_CHROOT" = true ]; then + echo " 2. Root filesystem cached" else - echo " 1. Kernel clone + build" + echo " 2. Root filesystem build" fi + echo " 3. Disk image build" fi -if [ "$SKIP_CHROOT" = true ]; then - echo " 2. Root filesystem cached" -else - echo " 2. Root filesystem build" -fi -echo " 3. Disk image build" echo "" echo "Logs: $LOG_FILE" echo "" @@ -160,6 +165,19 @@ SPIN_CHARS='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' run_stage() { local name="$1" shift + + if [ "${CI:-}" = "true" ]; then + echo "::group::$name" + local rc=0 + "$@" || rc=$? + echo "::endgroup::" + if [ $rc -ne 0 ]; then + echo "::error::Build failed at: $name" + exit $rc + fi + return + fi + local status_msg="$name" local spin_i=0 @@ -213,27 +231,31 @@ if [ "$SKIP_KERNEL" = true ]; then printf " ✓ %-60s\n" "Kernel packages (cached)" else if [ ! -d "$KERNEL_SRC/.git" ]; then - LINUX_TMP_DIR="${LINUX_DEFAULT_DIR}.tmp" - rm -rf "$LINUX_TMP_DIR" + REPO_URL="$PATCHES_REPO" + [ -n "$PATCHES_TOKEN" ] && REPO_URL="${PATCHES_REPO/https:\/\//https:\/\/${PATCHES_TOKEN}@}" + + mkdir -p "$PATCHES_DIR" - if [ ! -d "$PATCHES_DIR/.git" ]; then - run_stage "Clone ps5-linux-patches" \ - git clone --branch "$PATCHES_BRANCH" --depth 1 "$PATCHES_REPO" "$PATCHES_DIR" - else - run_stage "Update ps5-linux-patches" bash -c ' - git -C "'"$PATCHES_DIR"'" fetch --depth 1 origin tag "'"$PATCHES_BRANCH"'" - git -C "'"$PATCHES_DIR"'" checkout "'"$PATCHES_BRANCH"'" - git -C "'"$PATCHES_DIR"'" reset --hard "'"$PATCHES_BRANCH"'"' - fi + run_stage "Fetch ps5-linux-patches ($PATCHES_REF)" bash -c ' + cd "'"$PATCHES_DIR"'" + [ ! -d .git ] && git init && git remote add origin "'"$REPO_URL"'" + git fetch --depth 1 origin "'"$PATCHES_REF"'" || git fetch origin "'"$PATCHES_REF"'" + git reset --hard FETCH_HEAD + ' + LINUX_TMP_DIR="${LINUX_DEFAULT_DIR}.tmp" + for dir in "$LINUX_TMP_DIR" "$LINUX_DEFAULT_DIR"; do + [ -d "$dir" ] && docker run --rm \ + -v "$(dirname "$dir")":/parent \ + alpine rm -rf "/parent/$(basename "$dir")" + done LINUX_BRANCH="v$(grep -m1 "^# Linux/" "$PATCHES_DIR/.config" | grep -oP '\d+\.\d+(\.\d+)?')" run_stage "Clone kernel $LINUX_BRANCH" \ git clone --branch "$LINUX_BRANCH" --depth 1 "$LINUX_REPO" "$LINUX_TMP_DIR" run_stage "Apply patches" bash -c ' - set -e - shopt -s nullglob + set -e; shopt -s nullglob patches=("'"$PATCHES_DIR"'"/*.patch) [ ${#patches[@]} -eq 0 ] && { echo "No .patch files found in '"$PATCHES_DIR"'"; exit 1; } for p in "${patches[@]}"; do @@ -242,63 +264,57 @@ else done' run_stage "Copy kernel config" \ - cp "$PATCHES_DIR/$PATCHES_CONFIG" "$LINUX_TMP_DIR/.config" + cp "$PATCHES_DIR/.config" "$LINUX_TMP_DIR/.config" mv "$LINUX_TMP_DIR" "$LINUX_DEFAULT_DIR" + KERNEL_SRC="$LINUX_DEFAULT_DIR" else printf " ✓ %-60s\n" "Kernel source (cached)" fi KERNEL_SRC="$(cd "$KERNEL_SRC" && pwd)" - rm -f "$KERNEL_OUT"/*.$PKG_EXT + rm -f "$KERNEL_OUT"/*.deb "$KERNEL_OUT"/*.pkg.tar.zst run_stage "Build kernel builder image" \ - docker build -t ps5-kernel-builder -f "$SCRIPT_DIR/docker/kernel-builder/Dockerfile" "$SCRIPT_DIR" + docker build --platform "$KERNEL_BUILDER_PLATFORM" -t ps5-kernel-builder \ + -f "$SCRIPT_DIR/docker/kernel-builder/Dockerfile" "$SCRIPT_DIR" run_stage "Compile kernel" \ - docker run --rm --name "$DOCKER_NAME" \ + docker run --rm --platform "$KERNEL_BUILDER_PLATFORM" --name "$DOCKER_NAME" \ -v "$KERNEL_SRC":/src \ -v "$KERNEL_OUT":/out \ -v "$CCACHE_DIR":/ccache \ ps5-kernel-builder - if [ "$DISTRO" = "all" ]; then + ls "$KERNEL_OUT/staging/lib/modules/" | head -1 > "$KERNEL_OUT/VERSION" + + case "$FORMAT" in deb|all) run_stage "Package kernel (.deb)" \ - docker run --rm --name "$DOCKER_NAME" \ + docker run --rm --platform "$KERNEL_BUILDER_PLATFORM" --name "$DOCKER_NAME" \ -v "$KERNEL_SRC":/src \ -v "$KERNEL_OUT":/out \ -v "$CCACHE_DIR":/ccache \ ps5-kernel-builder \ - bash -c 'make -j$(nproc) bindeb-pkg && cp /*.deb /out/' + /package-deb.sh + esac + case "$FORMAT" in arch|all) run_stage "Build arch packager image" \ docker build -t ps5-kernel-packager-arch \ -f "$SCRIPT_DIR/docker/kernel-builder-arch/Dockerfile" "$SCRIPT_DIR" - run_stage "Package kernel (.pkg.tar.zst)" \ docker run --rm --name "$DOCKER_NAME" \ -v "$KERNEL_OUT":/out \ ps5-kernel-packager-arch + esac +fi - elif [ "$DISTRO" = "arch" ] || [ "$DISTRO" = "cachyos" ]; then - run_stage "Build arch packager image" \ - docker build -t ps5-kernel-packager-arch \ - -f "$SCRIPT_DIR/docker/kernel-builder-arch/Dockerfile" "$SCRIPT_DIR" - - run_stage "Package kernel (.pkg.tar.zst)" \ - docker run --rm --name "$DOCKER_NAME" \ - -v "$KERNEL_OUT":/out \ - ps5-kernel-packager-arch - else - run_stage "Package kernel (.deb)" \ - docker run --rm --name "$DOCKER_NAME" \ - -v "$KERNEL_SRC":/src \ - -v "$KERNEL_OUT":/out \ - -v "$CCACHE_DIR":/ccache \ - ps5-kernel-builder \ - bash -c 'make -j$(nproc) bindeb-pkg && cp /*.deb /out/' - fi +if [ "$KERNEL_ONLY" = true ]; then + KVER=$(cat "$KERNEL_OUT/VERSION" 2>/dev/null || echo "unknown") + echo "" + echo "Done! Kernel $KVER packages in $KERNEL_OUT/" + exit 0 fi # --- Step 2: Build distribution image --- diff --git a/docker/kernel-builder-arch/Dockerfile b/docker/kernel-builder-arch/Dockerfile index 5520a5e..8cd0d96 100644 --- a/docker/kernel-builder-arch/Dockerfile +++ b/docker/kernel-builder-arch/Dockerfile @@ -1,4 +1,7 @@ -FROM archlinux:latest +FROM --platform=linux/amd64 archlinux:latest + +# pacman 7+ sandbox doesn't work in Docker containers +RUN echo 'DisableSandbox' >> /etc/pacman.conf # Only packaging tools needed — kernel is already compiled RUN pacman -Syu --noconfirm && pacman -S --noconfirm \ diff --git a/docker/kernel-builder/Dockerfile b/docker/kernel-builder/Dockerfile index 8fe0449..21b41fe 100644 --- a/docker/kernel-builder/Dockerfile +++ b/docker/kernel-builder/Dockerfile @@ -7,8 +7,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libdw-dev libelf-dev libssl-dev \ python3 rsync dpkg-dev debhelper zstd \ perl git xz-utils ccache \ + gcc-x86-64-linux-gnu binutils-x86-64-linux-gnu libc6-dev-amd64-cross \ && rm -rf /var/lib/apt/lists/* +ENV ARCH=x86_64 +ENV CROSS_COMPILE=x86_64-linux-gnu- ENV PATH="/usr/lib/ccache:${PATH}" ENV CCACHE_DIR=/ccache @@ -18,6 +21,7 @@ WORKDIR /src # Build artifacts are staged to /out/staging COPY docker/kernel-builder/build.sh /build.sh -RUN chmod +x /build.sh +COPY docker/kernel-builder/package-deb.sh /package-deb.sh +RUN chmod +x /build.sh /package-deb.sh CMD ["/build.sh"] diff --git a/docker/kernel-builder/package-deb.sh b/docker/kernel-builder/package-deb.sh new file mode 100755 index 0000000..9b4846e --- /dev/null +++ b/docker/kernel-builder/package-deb.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Builds a combined linux-ps5 .deb from the kernel source at /src. +# Runs inside the kernel-builder container; output goes to /out. +set -e + +make -j$(nproc) DPKG_FLAGS=-d bindeb-pkg + +VER=$(dpkg-deb -f "$(ls /linux-image-[0-9]*.deb | grep -v dbg | head -1)" Version) +ARCH=$(dpkg-deb -f "$(ls /linux-image-[0-9]*.deb | grep -v dbg | head -1)" Architecture) +KVER=$(ls /src/debian/linux-image-[0-9]*/lib/modules/ 2>/dev/null | head -1) + +TMPDIR=$(mktemp -d) +mkdir -p "$TMPDIR/DEBIAN" +for deb in $(ls /linux-image-[0-9]*.deb /linux-headers-*.deb /linux-libc-dev_*.deb 2>/dev/null | grep -v -- '-dbg_'); do + dpkg-deb -x "$deb" "$TMPDIR" +done + +cat > "$TMPDIR/DEBIAN/control" << CTRL +Package: linux-ps5 +Version: $VER +Architecture: $ARCH +Maintainer: PS5 Linux +Provides: linux-image, linux-image-$KVER, linux-headers-$KVER, linux-libc-dev +Conflicts: linux-image-$KVER, linux-headers-$KVER, linux-libc-dev +Replaces: linux-image-$KVER, linux-headers-$KVER, linux-libc-dev +Description: PS5 Linux kernel $KVER (image + headers + libc-dev) +CTRL + +dpkg-deb --build --root-owner-group "$TMPDIR" "/out/linux-ps5_${VER}_${ARCH}.deb"