diff --git a/PINNED.json b/PINNED.json new file mode 100644 index 0000000..d4885c1 --- /dev/null +++ b/PINNED.json @@ -0,0 +1,22 @@ +{ + "schema": "cxos.vendor.linux/v1", + "version": "6.6.30", + "series": "6.6.x-lts", + "tarball_url": "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.30.tar.xz", + "signature_url": "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.30.tar.sign", + "sha256": "a5734e57c46e92b7eb142e54fe3b8cf5dee8e83fe3d34c051dbeae8b30ca5c95", + "gpg_signing_keys": [ + { + "name": "Greg Kroah-Hartman", + "email": "gregkh@kernel.org", + "fingerprint": "647F28654894E3BD457199BE38DBBDC86092693E" + }, + { + "name": "Sasha Levin", + "email": "sashal@kernel.org", + "fingerprint": "E27E5D8A3403A2EF66873BBCDEA66FF797772CDC" + } + ], + "extracted_dir": "linux-6.6.30", + "notes": "CxOS Tier-0 Linux LTS pin. Update both 'version' and 'sha256' in the same commit. SHA-256 must be re-verified with 'shasum -a 256 linux-6.6.30.tar.xz' after each bump." +} diff --git a/README.md b/README.md deleted file mode 100644 index 43bb6df..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# cxos-vendor-linux - -CxOS vendor: linux \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..767f4f0 --- /dev/null +++ b/build.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# cxos/vendor/linux/build.sh — build the pinned Linux kernel for CxOS. +# +# Prerequisites: +# * `cxos/vendor/linux/fetch.sh` already ran (source extracted) +# * Host has gcc, make, bc, bison, flex, libssl-dev, libelf-dev +# +# Usage: +# build.sh # x86_64 +# build.sh --arch aarch64 +# build.sh --dry # print plan, don't compile +# +# Outputs (relative to repo root): +# dist/cxos/vmlinuz (x86_64 bzImage / aarch64 Image) +# dist/cxos/System.map +set -euo pipefail + +ARCH="x86_64" +DRY=0 +for ((i=1; i<=$#; i++)); do + case "${!i}" in + --arch) ((i++)); ARCH="${!i}" ;; + --dry) DRY=1 ;; + -h|--help) + sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//' + exit 0 ;; + *) echo "build.sh: unknown arg: ${!i}" >&2; exit 2 ;; + esac +done + +HERE="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$HERE/../../.." && pwd)" +PINNED="$HERE/PINNED.json" +FRAGMENT="$HERE/cxos.config.fragment" + +VERSION="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["version"])' "$PINNED")" +EXTRACTED_DIR="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["extracted_dir"])' "$PINNED")" +SRC="$HERE/src/$EXTRACTED_DIR" + +case "$ARCH" in + x86_64) KARCH="x86_64"; IMAGE_REL="arch/x86/boot/bzImage" ;; + aarch64) KARCH="arm64"; IMAGE_REL="arch/arm64/boot/Image" ;; + *) echo "build.sh: unsupported arch: $ARCH" >&2; exit 2 ;; +esac + +OUT_DIR="$REPO_ROOT/dist/cxos" +JOBS="${JOBS:-$(nproc 2>/dev/null || echo 2)}" + +echo "==> linux ${VERSION} (${KARCH}) -> ${OUT_DIR}/vmlinuz" +echo " src : ${SRC}" +echo " fragment: ${FRAGMENT}" +echo " jobs : ${JOBS}" + +if [[ "$DRY" == "1" ]]; then + echo "==> dry-run; not compiling" + exit 0 +fi + +if [[ ! -d "$SRC" ]]; then + echo "build.sh: source not extracted; run fetch.sh first" >&2 + exit 1 +fi + +mkdir -p "$OUT_DIR" + +# Honor SOURCE_DATE_EPOCH for reproducibility. +KBUILD_BUILD_TIMESTAMP="$(date -u -d "@${SOURCE_DATE_EPOCH:-1700000000}" '+%Y-%m-%d %H:%M:%S UTC')" +export KBUILD_BUILD_TIMESTAMP +export KBUILD_BUILD_USER="cxos" +export KBUILD_BUILD_HOST="cxos-build" + +cd "$SRC" +make ARCH="$KARCH" defconfig +"$SRC/scripts/kconfig/merge_config.sh" -O . .config "$FRAGMENT" +make ARCH="$KARCH" olddefconfig +make ARCH="$KARCH" -j"$JOBS" + +cp -f "$IMAGE_REL" "$OUT_DIR/vmlinuz" +cp -f "System.map" "$OUT_DIR/System.map" +echo "==> wrote ${OUT_DIR}/vmlinuz ($(stat -c%s "$OUT_DIR/vmlinuz") bytes)" diff --git a/cxos.config.fragment b/cxos.config.fragment new file mode 100644 index 0000000..08fff9e --- /dev/null +++ b/cxos.config.fragment @@ -0,0 +1,57 @@ +# cxos/vendor/linux/cxos.config.fragment +# +# Additive Linux .config fragment for the CxOS Tier-0 profile. +# Merged on top of `defconfig` via scripts/kconfig/merge_config.sh. +# +# Profile: virtio-first, headless, ext4 root + initramfs, serial console, +# minimal but real network (TCP/IP, IPv4, IPv6), no graphical stack, no +# sound, no exotic filesystems. + +# --- Console / early boot --- +CONFIG_PRINTK=y +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_TMPFS=y + +# --- Initramfs --- +CONFIG_BLK_DEV_INITRD=y +CONFIG_RD_GZIP=y +CONFIG_RD_XZ=y + +# --- Storage --- +CONFIG_VIRTIO=y +CONFIG_VIRTIO_PCI=y +CONFIG_VIRTIO_MMIO=y +CONFIG_VIRTIO_BLK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y + +# --- Network --- +CONFIG_NET=y +CONFIG_INET=y +CONFIG_IPV6=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_VIRTIO_NET=y +CONFIG_E1000=y +CONFIG_E1000E=y + +# --- Filesystems --- +CONFIG_PROC_FS=y +CONFIG_SYSFS=y +CONFIG_FAT_FS=y +CONFIG_VFAT_FS=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y + +# --- Disable noise --- +# CONFIG_DRM is not set +# CONFIG_FB is not set +# CONFIG_SOUND is not set +# CONFIG_BT is not set +# CONFIG_WIRELESS is not set diff --git a/fetch.sh b/fetch.sh new file mode 100755 index 0000000..5f62cbd --- /dev/null +++ b/fetch.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# cxos/vendor/linux/fetch.sh — download + verify + extract Linux LTS tarball. +# +# Reads cxos/vendor/linux/PINNED.json. Hard-fails if SHA-256 mismatches or +# (when --gpg is passed) the GPG signature does not validate against any of +# the pinned signing keys. +# +# Usage: +# cxos/vendor/linux/fetch.sh # SHA-256 only +# cxos/vendor/linux/fetch.sh --gpg # require valid GPG signature +# cxos/vendor/linux/fetch.sh --dry # print plan; no network +# +# Outputs: +# cxos/vendor/linux/linux-.tar.xz (cached, gitignored) +# cxos/vendor/linux/src/linux-/ (extracted, gitignored) +set -euo pipefail + +DRY=0 +GPG=0 +for arg in "$@"; do + case "$arg" in + --dry) DRY=1 ;; + --gpg) GPG=1 ;; + -h|--help) + sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//' + exit 0 ;; + *) + echo "fetch.sh: unknown arg: $arg" >&2 + exit 2 ;; + esac +done + +HERE="$(cd "$(dirname "$0")" && pwd)" +PINNED="$HERE/PINNED.json" + +if [[ ! -f "$PINNED" ]]; then + echo "fetch.sh: missing $PINNED" >&2 + exit 1 +fi + +# Parse PINNED.json with python3 (always available in the dev container). +read -r VERSION TARBALL_URL SIG_URL SHA256 EXTRACTED_DIR < <( + python3 - "$PINNED" <<'PY' +import json, sys +d = json.load(open(sys.argv[1])) +print(d["version"], d["tarball_url"], d["signature_url"], + d["sha256"], d["extracted_dir"]) +PY +) + +TARBALL="$HERE/linux-${VERSION}.tar.xz" +SIGNATURE="$HERE/linux-${VERSION}.tar.sign" +SRC_DIR="$HERE/src" + +echo "==> linux ${VERSION}" +echo " tarball: ${TARBALL_URL}" +echo " sha256 : ${SHA256}" +echo " extract: ${SRC_DIR}/${EXTRACTED_DIR}" + +if [[ "$DRY" == "1" ]]; then + echo "==> dry-run; nothing fetched" + exit 0 +fi + +mkdir -p "$SRC_DIR" + +# 1. Tarball ---------------------------------------------------------------- +if [[ ! -f "$TARBALL" ]]; then + echo "==> downloading $(basename "$TARBALL")" + curl -fsSL --retry 3 -o "$TARBALL" "$TARBALL_URL" +fi + +# 2. SHA-256 ---------------------------------------------------------------- +echo "==> verifying sha256" +ACTUAL_SHA="$(sha256sum "$TARBALL" | awk '{print $1}')" +if [[ "$ACTUAL_SHA" != "$SHA256" ]]; then + echo "fetch.sh: SHA-256 mismatch" >&2 + echo " expected: $SHA256" >&2 + echo " actual : $ACTUAL_SHA" >&2 + rm -f "$TARBALL" + exit 1 +fi +echo " ok" + +# 3. GPG (optional) --------------------------------------------------------- +if [[ "$GPG" == "1" ]]; then + if ! command -v gpg >/dev/null 2>&1; then + echo "fetch.sh: --gpg requested but gpg not installed" >&2 + exit 1 + fi + if [[ ! -f "$SIGNATURE" ]]; then + echo "==> downloading $(basename "$SIGNATURE")" + curl -fsSL --retry 3 -o "$SIGNATURE" "$SIG_URL" + fi + XZ_TMP="$(mktemp -d)" + trap 'rm -rf "$XZ_TMP"' EXIT + xz -dk -c "$TARBALL" > "$XZ_TMP/linux-${VERSION}.tar" + echo "==> verifying gpg signature" + # Pull the pinned keys' fingerprints; require at least one matching sig. + FINGERPRINTS="$(python3 -c ' +import json,sys +for k in json.load(open(sys.argv[1]))["gpg_signing_keys"]: + print(k["fingerprint"])' "$PINNED")" + for fp in $FINGERPRINTS; do + gpg --keyserver hkps://keys.openpgp.org --recv-keys "$fp" \ + >/dev/null 2>&1 || true + done + if ! gpg --verify "$SIGNATURE" "$XZ_TMP/linux-${VERSION}.tar" 2>&1 \ + | grep -qE 'Good signature.*('"$(echo "$FINGERPRINTS" | tr '\n' '|' | sed 's/|$//')"')'; then + echo "fetch.sh: GPG verification failed" >&2 + exit 1 + fi + echo " ok" +fi + +# 4. Extract ---------------------------------------------------------------- +if [[ ! -d "$SRC_DIR/$EXTRACTED_DIR" ]]; then + echo "==> extracting" + tar -xf "$TARBALL" -C "$SRC_DIR" +fi + +echo "==> linux source ready: $SRC_DIR/$EXTRACTED_DIR"