#!/bin/bash # BTC.sh requires GNU Bash 4.0+ (not POSIX sh). It uses: # - [[ ... ]] conditionals, == pattern matching, =~ regex, (( )) arithmetic # - ${var^^} uppercase expansion, associative arrays, process substitution # - set -euo pipefail for strict error handling # BTC-${BTC_VERSION} - Build Tool Chain # Identity: dcosnet / dcos.net | Multi-Arch Cross-Compilation Build Tool Chain # Version: 0.4.2 | Persistence: /opt/BTC | Volatile: ramfs # License: GNU AGPLv3 Mandatory Prominent Interactive Notice # Copyright (C) 2012-2026 Jeremy Anderson (info@dcos.net) set -euo pipefail export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES # ---------------------------------------------------------------------------- # Single source of truth for the BTC version. Every banner, header comment, # manifest, and --help text MUST reference this constant — never hardcode the # version string in multiple places. # ---------------------------------------------------------------------------- readonly BTC_VERSION="0.4.2" # ============================================================================ # 1. AGPL INTERACTIVE LICENSE COMPLIANCE # ============================================================================ function f_agpl_header() { clear cat << EOF =========================================================================== BTC-${BTC_VERSION} - Build Tool Chain (AGPLv3 PROTECTED) Cross-Compilation Build Tool Chain =========================================================================== This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License. REMOTE INTERACTION NOTICE: Per Section 13 of the GNU AGPLv3, if you modify this script and offer its toolchain-building capabilities as a service over a network, you MUST make your complete modified source code available. =========================================================================== EOF if [[ ! -f /var/tmp/BTC-AGPL-ACCEPTED ]]; then echo -n "Do you accept the network-sovereignty terms of the AGPLv3? (y/N): " read -r reply if [[ "${reply}" =~ ^[Yy]$ ]]; then touch /var/tmp/BTC-AGPL-ACCEPTED else echo ">> Build aborted: AGPLv3 acceptance is mandatory for execution." exit 1 fi fi # --- Signature Tier Selection --- # Skip prompt if tier was already specified via CLI flags. if [[ ${BTC_TPM_SEAL} -eq 0 && -z "${BTC_CLUSTER_JOIN:-}" && ! -f "${BTC_ARCHIVE:-/opt/BTC}/.btc-salt" && ! -f "${BTC_ARCHIVE:-/opt/BTC}/.btc-cluster-token" ]]; then echo "" echo ">> Select forensic signature tier:" echo ">> 1) poly — Per-machine random salt (write-once, default)" echo ">> 2) tpm — Hardware-bound PCR digest (singular deploys)" echo ">> 3) cluster — Join existing cluster with shared token" echo -n ">> Choice [1]: " read -r sig_choice case "${sig_choice:-1}" in 2) BTC_TPM_SEAL=1 echo ">> [SIG] TPM seal selected." ;; 3) echo -n ">> Enter cluster token: " read -r cluster_tok if [[ -n "${cluster_tok}" ]]; then export BTC_CLUSTER_JOIN="${cluster_tok}" echo ">> [SIG] Cluster join selected." else echo ">> [WARN] Empty token. Defaulting to poly." fi ;; *) echo ">> [SIG] Poly (per-machine salt) selected." ;; esac echo "" fi } # ============================================================================ # 2. CROSS-COMPILATION TARGET REGISTRY # # Architecture | Target ID | GCC march | ISA | C Library # ------------- | ------------------- | ------------------ | ------ | ---------- # x86_64 | haswell | haswell | AVX2 | glibc # x86_64 | haswell-ep | haswell | AVX2 | glibc # x86_64 | broadwell | broadwell | AVX2 | glibc # x86_64 | broadwell-ep | broadwell | AVX2 | glibc # x86_64 | skylake | skylake | AVX2 | glibc # x86_64 | skylake-x | skylake-avx512 | AVX512 | glibc # x86_64 | skylake-server | skylake-server | AVX512 | glibc # x86_64 | znver1 | znver1 | AVX2 | glibc # x86_64 | znver2 | znver2 | AVX2 | glibc # x86_64 | znver3 | znver3 | AVX2 | glibc # x86_64 | znver4 | znver4 | AVX512 | glibc # x86_64 | apu-zn1 | znver1 | AVX2 | glibc # x86_64 | apu-zn2 | znver1 | AVX2 | glibc # x86_64 | apu-zn3 | znver2 | AVX2 | glibc # x86_64 | apu-zn4 | znver3 | AVX2 | glibc # x86_64 | atom-silvermont | silvermont | SSE4_2| glibc # x86_64 | atom-goldmont | goldmont | SSE4_2| glibc # x86_64 | atom-tremont | tremont | SSE4_2| glibc # x86_64 | atom-sierraforest | sierraforest | SSE4_2| glibc # mipsel | mipselr2 | mips32r2 | MIPS32 | musl # arm | armv7 | armv7-a | NEON | musl # tilegx | tilegx | tilegx | TILE | musl # # Host is always an average x86_64 system. All targets listed above # are CROSS-COMPILATION targets (CROSS_MODE=1). The --native flag # auto-probes the host silicon and builds without a target prefix. # # Cross targets use SYSROOT-based cross-compilation (CLFS / Buildroot style). # ============================================================================ # Associative array: target_id → property string # Format: "arch|cpu|march|isa|abi|clib|endian|family|desc|kernel_min|gcc_cfg_extra" declare -A BTC_TARGETS # --- x86_64 Intel family --- BTC_TARGETS[haswell]="x86_64|i686|haswell|AVX2|sysv|glibc|little|intel|Intel Haswell (Xeon E5 v3 / Core i7-4xxx)|4.19|" BTC_TARGETS[haswell-ep]="x86_64|i686|haswell|AVX2|sysv|glibc|little|intel|Intel Haswell-EP X99 (Xeon E5 v3 / E7 v3)|4.19|" BTC_TARGETS[broadwell]="x86_64|i686|broadwell|AVX2|sysv|glibc|little|intel|Intel Broadwell (Core i7-5xxx / Core i5-5xxx / Xeon E5 v4)|4.19|" BTC_TARGETS[broadwell-ep]="x86_64|i686|broadwell|AVX2|sysv|glibc|little|intel|Intel Broadwell-EP (Xeon E5-2600 v4 / E7-4800 v4)|4.19|" BTC_TARGETS[skylake]="x86_64|i686|skylake|AVX2|sysv|glibc|little|intel|Intel Skylake (Core i7-6xxx / Xeon v5)|4.19|" BTC_TARGETS[skylake-x]="x86_64|i686|skylake-avx512|AVX512|sysv|glibc|little|intel|Intel Skylake-X X299 (Core i9-7xxx Xeon Scalable)|4.19|" BTC_TARGETS[skylake-server]="x86_64|i686|skylake-server|AVX512|sysv|glibc|little|intel|Intel Skylake-Server (Xeon SP 1st/2nd Gen Platinum)|4.19|" # --- x86_64 AMD family --- BTC_TARGETS[znver1]="x86_64|i686|znver1|AVX2|sysv|glibc|little|amd|AMD Zen1 Ryzen (Ryzen 1000 / EPYC Naples)|4.19|" BTC_TARGETS[znver2]="x86_64|i686|znver2|AVX2|sysv|glibc|little|amd|AMD Zen2 Ryzen (Ryzen 3000 / EPYC Rome)|4.19|" BTC_TARGETS[znver3]="x86_64|i686|znver3|AVX2|sysv|glibc|little|amd|AMD Zen3 Ryzen (Ryzen 5000 / EPYC Milan)|4.19|" BTC_TARGETS[znver4]="x86_64|i686|znver4|AVX512|sysv|glibc|little|amd|AMD Zen4 Ryzen (Ryzen 7000 / EPYC Genoa)|4.19|" # --- x86_64 AMD APU family (mobile/embedded Zen, 15-54W TDP) --- # APU series 1 (Raven Ridge, 2400GE/3200GE) = Zen 1, AVX2, Vega graphics # APU series 2 (Picasso, 3250U/3500U) = Zen+, AVX2 (GCC march=znver1) # APU series 3 (Renoir/Lucienne, 4500U/4700U)= Zen 2, AVX2 # APU series 4 (Cezanne/Barcelo, 5500U/5700U) = Zen 3, AVX2 # These are cross-compiled from an average x86_64 host for deployment # on APU-based mini-PCs, laptops, and embedded nodes. BTC_TARGETS[apu-zn1]="x86_64|i686|znver1|AVX2|sysv|glibc|little|amd-apu|AMD APU Series 1 Zen (Raven Ridge 2400GE / 3200GE Vega)|4.19|" BTC_TARGETS[apu-zn2]="x86_64|i686|znver1|AVX2|sysv|glibc|little|amd-apu|AMD APU Series 2 Zen+ (Picasso 3250U / 3500U)|4.19|" BTC_TARGETS[apu-zn3]="x86_64|i686|znver2|AVX2|sysv|glibc|little|amd-apu|AMD APU Series 3 Zen2 (Renoir 4500U / 4700U)|4.19|" BTC_TARGETS[apu-zn4]="x86_64|i686|znver3|AVX2|sysv|glibc|little|amd-apu|AMD APU Series 4 Zen3 (Cezanne 5500U / 5700U)|4.19|" # --- x86_64 Intel Atom family (low-power embedded, 4-15W TDP) --- # silvermont = Bay Trail (Z3000 series, E38xx) — in-order, SSE4.2 # goldmont = Apollo Lake (x5-Z8350, N4200) — out-of-order, SSE4.2 # tremont = Elkhart Lake (x6000E series) — improved OoO, SSE4.2 # sierraforest= Sierra Forest (x7000RE, E-core) — hybrid, SSE4.2, GCC 14+ # All Atom targets are cross-compiled for edge/IoT gateways, routers, # and low-power cluster nodes where AVX is not available. BTC_TARGETS[atom-silvermont]="x86_64|i686|silvermont|SSE4_2|sysv|glibc|little|atom|Intel Atom Silvermont (Bay Trail Z3000 / E38xx)|4.14|" BTC_TARGETS[atom-goldmont]="x86_64|i686|goldmont|SSE4_2|sysv|glibc|little|atom|Intel Atom Goldmont (Apollo Lake x5-Z8350 / N4200)|4.14|" BTC_TARGETS[atom-tremont]="x86_64|i686|tremont|SSE4_2|sysv|glibc|little|atom|Intel Atom Tremont (Elkhart Lake x6000E series)|5.4|" BTC_TARGETS[atom-sierraforest]="x86_64|i686|sierraforest|SSE4_2|sysv|glibc|little|atom|Intel Atom Sierra Forest (x7000RE E-core cluster)|6.1|" # --- MIPS (little-endian, soft-float baseline) --- # mips32r2 is the ISA baseline for mipselr2 — covers the MALTA-like # embedded targets that are the closest thing to a "universal mips" # reference platform. Uses musl because glibc MIPS support is # fragmented across kernel versions and vendor patches. BTC_TARGETS[mipselr2]="mipsel|mips|32r2|MIPS32|o32|musl|little|mips|MIPS32R2 Little-Endian (MALTA / embedded routers)|4.9|--with-arch=mips32r2 --with-float=soft --with-abi=32 --disable-libsanitizer" # --- ARMv7 (hard-float, Thumb-2, NEON) --- # armv7-a with NEON and VFPv3-D16 is the "x86 baseline" of the ARM # world — it covers Raspberry Pi 2/3 (32-bit), BeagleBone, Odroid, # and virtually every Cortex-A7/A9/A15/A17 SoC. Uses musl for # cross-compile portability; glibc armv7 is available as a future # clib variant. BTC_TARGETS[armv7]="arm|arm|armv7-a|NEON|eabihf|musl|little|arm|ARMv7-A Hard-Float NEON (Cortex-A7/A9/A15 RPi2/3 32b)|4.9|--with-arch=armv7-a --with-fpu=vfpv3-d16 --with-float=hard --with-mode=thumb --enable-target-optspace --disable-libsanitizer --with-abi=aapcs-linux" # --- TileGX (Tilera TILE-Gx72/Metor) --- # The Tile architecture is a 64-bit VLIW mesh network processor. # GCC upstream dropped mainline Tile-Gx support after GCC 11, so # we pin GCC 10.3.0 for Tile-Gx targets. The tilegx triple uses # linux-gnu-abi64. Uses musl as glibc has no Tile-Gx port. BTC_TARGETS[tilegx]="tilegx|tilegx|tilegx|TILE|abi64|musl|little|tile|Tilera TILE-Gx (TILE-Gx72 / TilePro mesh VLIW)|4.14|--with-arch=tilegx --disable-libssp --disable-libquadmath --disable-libatomic" # --- Helper: list all registered targets --- function f_list_targets() { echo ">> BTC-${BTC_VERSION} Registered Cross-Compilation Targets:" echo ">> ===============================================" printf ">> %-16s %-10s %-18s %-8s %-6s %s\n" "TARGET_ID" "ARCH" "MARCH" "ISA" "CLIB" "DESCRIPTION" printf ">> %-16s %-10s %-18s %-8s %-6s %s\n" "--------" "----" "-----" "---" "----" "-----------" for tid in $(echo "${!BTC_TARGETS[@]}" | tr ' ' '\n' | sort); do IFS='|' read -r arch cpu march isa abi clib endian family desc kern_min gcc_extra <<< "${BTC_TARGETS[${tid}]}" printf ">> %-16s %-10s %-18s %-8s %-6s %s\n" "${tid}" "${arch}" "${march}" "${isa}" "${clib}" "${desc}" done echo "" echo ">> Usage: BTC.sh Build cross-toolchain for target" echo ">> BTC.sh --native Auto-probe host silicon and build native" echo ">> BTC.sh --list Show this target table" echo ">> BTC.sh --list-json Emit target table as JSON" } function f_list_targets_json() { echo '[' local first=true for tid in $(echo "${!BTC_TARGETS[@]}" | tr ' ' '\n' | sort); do IFS='|' read -r arch cpu march isa abi clib endian family desc kern_min gcc_extra <<< "${BTC_TARGETS[${tid}]}" if [[ "${first}" == "true" ]]; then first=false; else echo ','; fi cat << TJSEP { "id": "${tid}", "arch": "${arch}", "cpu": "${cpu}", "march": "${march}", "isa": "${isa}", "abi": "${abi}", "clib": "${clib}", "endian": "${endian}", "family": "${family}", "description": "${desc}", "kernel_min": "${kern_min}", "gcc_extra": "${gcc_extra}" } TJSEP done echo ']' } # ============================================================================ # 3. TARGET PROBE & SELECTION # ============================================================================ # Resolved target properties (set by f_resolve_target) BTC_T_ARCH="" BTC_T_CPU="" BTC_T_MARCH="" BTC_T_ISA="" BTC_T_ABI="" BTC_T_CLIB="" BTC_T_ENDIAN="" BTC_T_FAMILY="" BTC_T_DESC="" BTC_T_KERN_MIN="" BTC_T_GCC_EXTRA="" BTC_T_ID="" CROSS_MODE=0 # 0 = native, 1 = cross function f_resolve_target() { local target_id="$1" local spec="${BTC_TARGETS[${target_id}]:-}" if [[ -z "${spec}" ]]; then echo ">> [ERROR] Unknown target: '${target_id}'" echo ">> Run 'BTC.sh --list' for available targets." exit 1 fi IFS='|' read -r BTC_T_ARCH BTC_T_CPU BTC_T_MARCH BTC_T_ISA BTC_T_ABI BTC_T_CLIB BTC_T_ENDIAN BTC_T_FAMILY BTC_T_DESC BTC_T_KERN_MIN BTC_T_GCC_EXTRA <<< "${spec}" BTC_T_ID="${target_id}" } function f_silicon_probe() { echo ">> Interrogating Core Topology and Instruction Extensions..." # Step-down dispatch: explicit target, explicit native, or default native case "${BTC_TARGET_ID:-}" in --native|'') _probe_native ;; *) f_resolve_target "${BTC_TARGET_ID}" CROSS_MODE=1 _configure_from_target ;; esac } function _probe_native() { local RAW_ARCH RAW_ARCH=$(gcc -march=native -Q --help=target 2>/dev/null | grep -m1 "march=" | awk '{print $2}') || true if [[ -z "${RAW_ARCH}" || "${RAW_ARCH}" == "x86-64" ]]; then RAW_ARCH="haswell" fi # Try to match the probed microarch to a registered target local probe_lower="${RAW_ARCH,,}" local matched="${BTC_TARGETS[${probe_lower}]:-}" if [[ -n "${matched}" ]]; then matched="${probe_lower}" else # Select haswell as the deterministic default for unregistered microarchitectures echo ">> [WARN] Probed microarch '${RAW_ARCH}' not in target registry. Selecting 'haswell'." matched="haswell" fi f_resolve_target "${matched}" CROSS_MODE=0 _configure_from_target } function _configure_from_target() { # Derive the target triple case "${BTC_T_ARCH}" in x86_64) TARGET="x86_64-dcosnet-linux-gnu" HOST_ARCH="x86_64-pc-linux-gnu" ;; mipsel) TARGET="mipsel-dcosnet-linux-musl" HOST_ARCH="x86_64-pc-linux-gnu" ;; arm) TARGET="arm-dcosnet-linux-musleabihf" HOST_ARCH="x86_64-pc-linux-gnu" ;; tilegx) TARGET="tilegx-dcosnet-linux-gnu" HOST_ARCH="x86_64-pc-linux-gnu" ;; *) TARGET="${BTC_T_ARCH}-dcosnet-linux-gnu" HOST_ARCH="x86_64-pc-linux-gnu" ;; esac # ISA tag for SYS_LABEL ISA_TAG="${BTC_T_ISA}" OPT_TAG="LTO" SYS_LABEL="DCOSNET-${BTC_T_ID^^}-${ISA_TAG}-${OPT_TAG}" # Override SYS_LABEL for cross-compiles to include the arch family if [[ "${CROSS_MODE}" -eq 1 ]]; then SYS_LABEL="DCOSNET-${BTC_T_FAMILY^^}-${BTC_T_ID^^}-${ISA_TAG}-CROSS" fi # Resource-Safe Threading: Allocate 2GB RAM per core floor to prevent LTO thrashing local total_cpus total_cpus=$(nproc) local free_gb free_gb=$(free -g | awk '/^Mem:/{print $7}') local safe_threads=$(( free_gb / 2 )) if [[ ${safe_threads} -lt 1 ]]; then safe_threads=1; fi if [[ ${safe_threads} -gt ${total_cpus} ]]; then safe_threads=${total_cpus}; fi export v_threads="-j${safe_threads}" # Reset version overrides to the default baseline before per-target # pinning. Without this reset, calling _configure_from_target twice # in the same process (e.g. test harnesses, or a future batch-build # mode) would leak the tile pin into subsequent non-tile targets. # Single-target builds are unaffected, but the reset is cheap and # makes the function idempotent. v_gcc="${v_gcc_default}" v_linux="${v_linux_default}" v_linux_headers="${v_linux_headers_default}" # Pin GCC and Linux versions for Tile-Gx. # GCC upstream dropped mainline Tile-Gx support after GCC 11. # Linux upstream removed the tile architecture in 5.9; the last LTS # series that still builds for tilegx is 5.4.x. Both overrides MUST run # before f_download(), which captures these globals into A_SRC_URL[]. if [[ "${BTC_T_FAMILY}" == "tile" ]]; then v_gcc="${v_gcc_tile}" v_linux="${v_linux_tile}" v_linux_headers="${v_linux_headers_tile}" fi echo ">> [IDENTITY STAMP] ${SYS_LABEL}" echo ">> [TARGET] ${BTC_T_ID} — ${BTC_T_DESC}" echo ">> [TRIPLE] ${TARGET}" echo ">> [C LIBRARY] ${BTC_T_CLIB}" local mode_label="NATIVE" if [[ "${CROSS_MODE}" -eq 1 ]]; then mode_label="CROSS-COMPILE"; fi echo ">> [MODE] ${mode_label}" echo ">> [THREAD ALLOCATION] Probed ${total_cpus} cores -> Throttled to ${v_threads} for LTO Safety." } # ============================================================================ # 4. SYSTEM PATHS & STAGING MATRIX # ============================================================================ export SOURCES_ACTIVE=/usr/src export BTC_ARCHIVE=/opt/BTC export SOURCE_CACHE=${BTC_ARCHIVE}/src export RAMDISK_SIZE="12gb" # Upstream Production Matrices (defaults — can be overridden per-target) # Versions track the current LFS stable baseline (LFS 13.0+) where practical, # bumped forward to the latest point releases that are actually downloadable # from upstream mirrors as of ${BTC_VERSION}. # # - linux-7.1.7 : latest stable point release on the 7.1.x series # - binutils-2.46.1 : LFS-aligned binutils point release # - gcc-15.3.0 : current GCC stable (LFS 13.0 ships 14.2.0; we bump # to 15.3.0 to pick up znver4/sierraforest support # and GCC 15 stricter const-correctness) # - glibc-2.43 : current glibc stable # - musl-1.2.6 : current musl stable # # Tile-Gx override: tile architecture was REMOVED from mainline Linux in 5.9 # (commit 65ad263b1d0f). The last LTS series with tile support is 5.4.x, so # tile targets are pinned to linux-5.4.302 (LTS) and gcc-10.3.0 (last GCC # release with full tile-gx backend). See _configure_from_target() for the # runtime override. v_linux='linux-7.1.7' v_linux_default='linux-7.1.7' v_linux_tile='linux-5.4.302' v_binutils='binutils-2.46.1' v_gcc='gcc-15.3.0' v_gcc_default='gcc-15.3.0' v_gcc_tile='gcc-10.3.0' v_glibc='glibc-2.43' v_libxcrypt='4.5.2' v_gmp='gmp-6.3.0' v_mpfr='mpfr-4.2.2' v_mpc='mpc-1.4.0' v_musl='musl-1.2.6' v_linux_headers="${v_linux}" v_linux_headers_default="${v_linux_default}" v_linux_headers_tile="${v_linux_tile}" # These are set after f_resolve_target: # NEWROOT, LOGS, HOST_ARCH, TARGET, TARGET_ARCH, GLOBAL_CFLAGS, GLOBAL_LDFLAGS function f_set_paths() { export NEWROOT="${SOURCES_ACTIVE}/${SYS_LABEL}-cleanroom" export LOGS="${BTC_ARCHIVE}/logs/${SYS_LABEL}" # Architecture-specific optimization flags # For non-x86_64, march maps to the per-arch value from the target registry local march_flag="${BTC_T_MARCH}" # ISA-specific extra flags (table-driven via case — SEI CERT CTR50-JP) local isa_extra="" case "${BTC_T_ISA}" in AVX512) isa_extra=" -mavx512f -mavx512dq -mavx512vl -mavx512bw" ;; AVX2) isa_extra=" -mavx2" ;; SSE4_2) isa_extra=" -msse4.2" ;; NEON) isa_extra=" -mfpu=neon -mfloat-abi=hard" ;; MIPS32) isa_extra="" ;; TILE) isa_extra="" ;; esac # Unified CFLAGS: --sysroot points at NEWROOT for both glibc and musl targets export GLOBAL_CFLAGS="-O3 -march=${march_flag}${isa_extra} -flto -ffat-lto-objects --sysroot=${NEWROOT} -pipe" export GLOBAL_LDFLAGS="-Wl,-O1 -Wl,--as-needed -flto --sysroot=${NEWROOT}" # For native x86_64 builds, keep the i686 build cpu if [[ "${CROSS_MODE}" -eq 0 && "${BTC_T_ARCH}" == "x86_64" ]]; then TARGET_ARCH="${BTC_T_MARCH}" else TARGET_ARCH="${BTC_T_ID}" fi } # ============================================================================ # 5. HARDWARE SENTINEL & TELEMETRY MODULES # ============================================================================ function f_guard() { local max_temp=85 local min_mem=800 local cur_temp local cur_mem # Thermal zones may not exist in containers; guard gracefully if [[ -d /sys/class/thermal ]]; then cur_temp=$(cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | head -n1 | awk '{print $1/1000}') || cur_temp=0 else cur_temp=0 fi cur_mem=$(free -m | awk '/^Mem:/{print $7}') if (( ${cur_temp%.*} > max_temp )); then echo ">> [WARNING: THERMAL SPIKE] Temp at ${cur_temp}C. Throttling build for cooling phase..." sleep 15 fi if [[ ${cur_mem} -lt ${min_mem} ]]; then echo ">> [WARNING: MEMORY SATURATION] Free memory at ${cur_mem}MB. Yielding pipeline execution..." sleep 20 fi } function f_entropy_shield() { # Modern Linux (>= 5.6) initializes the CRNG at boot via getrandom(2) # and the kernel's own jitter entropy collector. Since 5.6, the # input-pool counter in /proc/sys/kernel/random/entropy_avail is # capped at 256 by design — it does NOT reflect "available" entropy # in the 2.6-era sense anymore. Any value >= 256 means "CRNG ready, # getrandom will return immediately". The historical "1000" threshold # is from the 2.6 era when /dev/random could genuinely block. # # Behavior on a modern box: # - entropy_avail will be 256 essentially always (capped) # - getrandom(GRND_NONBLOCK) succeeds instantly # - The "ENTROPY DEFICIT" warning would fire on EVERY step and the # old `sleep 2` blocked the parent pipeline for no reason. # # Fix: lower the threshold to 256, drop the blocking sleep entirely, # fire the jitter injector in the background (fire-and-forget, never # blocks the calling build step). If getrandom isn't usable on this # host we still benefit from any extra jitter, but we don't pay any # wall-clock time for it. local min_entropy=256 local cur_entropy=0 if [[ -r /proc/sys/kernel/random/entropy_avail ]]; then cur_entropy=$(< /proc/sys/kernel/random/entropy_avail) || cur_entropy=0 fi if [[ ${cur_entropy} -gt 0 && ${cur_entropy} -lt ${min_entropy} ]]; then echo ">> [ENTROPY] Pool at ${cur_entropy} (< ${min_entropy}); jitter injector fired in background." # Fire-and-forget. Never blocks the calling build step. # The injector walks /bin /sbin /usr/bin to feed disk-I/O timing # jitter into the kernel input pool. If it fails or finds nothing # useful, no harm done — getrandom(2) still works on modern kernels. { find /bin /sbin /usr/bin -type f -exec ls -l {} + > /dev/null 2>&1; } & disown 2>/dev/null || true fi } function f_exec_log() { local cmd="$1" local log_base="$2" local log_file="${LOGS}/${log_base}.log" f_entropy_shield f_guard echo ">> Executing: ${log_base}" # Note: ${cmd} is sourced from internal build functions only (not user input). # The trust boundary is the BTC.sh script itself — do not expose f_exec_log # as a public API with externally-supplied command strings. # # Pipeline: bash -c "$cmd" → stdbuf (line-buffer) → pv (progress bar) # → tee (log file) → /dev/null (suppress stdout so the terminal stays clean). # # On failure: the `if !` form suspends `set -e` for the condition test, # captures the pipeline's exit status (with `set -o pipefail` this is # the rightmost non-zero exit in the pipe — usually bash -c's exit code), # then surfaces the last 40 lines of the log file to stderr so the # operator sees the actual error inline instead of having to dig # through ${LOGS}/.log post-mortem. if ! stdbuf -oL -eL bash -c "${cmd}" 2>&1 | \ pv -t -r -b -N "${log_base}" | \ tee -a "${log_file}" > /dev/null; then echo ">> [FAILED] ${log_base} exited non-zero." >&2 echo ">> [FAILED] Last 40 lines of ${log_file}:" >&2 tail -n 40 "${log_file}" >&2 2>/dev/null || true echo ">> [FAILED] Full log: ${log_file}" >&2 return 1 fi } function f_tmux_dashboard() { if [[ -n "${TMUX:-}" ]]; then echo ">> Active Tmux session identified. Splitting target tracking matrix..." tmux split-window -h -p 35 "tail -F \"${LOGS}\"/*.log" || true tmux split-window -v -p 50 "watch -n 2 'echo \"=== ENTROPY POOL ===\"; cat /proc/sys/kernel/random/entropy_avail; echo \"=== NETWORK BOUND MATRIX ===\"; ss -tunp | grep -v 127.0.0.1'" || true tmux select-pane -t 0 || true fi } # ============================================================================ # 6. POLY-SIGNATURE IDENTITY STAMPING LAYER # # Tier 1 — Cluster: Deterministic token from target+sysroot hash. # Joins an existing cluster token if one is provided. # Write-once per deployment; never drifts. # Tier 2 — TPM: Binds signature to hardware PCR state via TPM 1.2/2.0. # Singular-target deployments only. Gated behind --tpm-seal. # Tier 3 — Poly: Per-machine random salt (write-once) folded into every # binary stamp. Two independent machines produce different # forensic signatures even for the same target. # # Selection priority: cluster-join > tpm-seal > poly (default). # State files live under ${BTC_ARCHIVE}/ and persist across builds. # ============================================================================ # Signature state (set once by f_sig_init, consumed by f_stamp_binary) BTC_SIG_TIER="" BTC_SIG_TOKEN="" BTC_TPM_SEAL=0 function f_tpm_pcr_digest() { # Attempt TPM 2.0 PCR read via tpm2-tools if command -v tpm2_pcrread &>/dev/null; then tpm2_pcrread sha256:0,1,2,3,4,5,6,7 2>/dev/null | \ awk '/^[0-9]+:/{gsub(/[^0-9a-fA-F]/,"", $2); printf "%s", $2}' | \ sha256sum | awk '{print $1}' return 0 fi # Attempt TPM 1.2 via sysfs PCR export if [[ -d /sys/class/tpm/tpm0 ]]; then sha256sum /sys/class/tpm/tpm0/pcrs 2>/dev/null | awk '{print $1}' return 0 fi # No TPM hardware detected return 1 } function f_sig_init() { local salt_file="${BTC_ARCHIVE}/.btc-salt" local cluster_file="${BTC_ARCHIVE}/.btc-cluster-token" # --- Tier 1: Cluster --- # If a cluster token exists on disk, this machine has already joined. # Honor it unconditionally — no drift, no re-roll. if [[ -f "${cluster_file}" ]]; then BTC_SIG_TOKEN=$(< "${cluster_file}") BTC_SIG_TIER="cluster" echo ">> [SIG] Tier 1 (cluster) — Adopting existing cluster token." echo ">> [SIG] Token: ${BTC_SIG_TOKEN:0:16}..." return 0 fi # If a cluster join was requested via CLI, adopt the provided token. if [[ -n "${BTC_CLUSTER_JOIN:-}" ]]; then BTC_SIG_TOKEN="${BTC_CLUSTER_JOIN}" BTC_SIG_TIER="cluster" mkdir -p "${BTC_ARCHIVE}" echo "${BTC_SIG_TOKEN}" > "${cluster_file}" echo ">> [SIG] Tier 1 (cluster) — Joined cluster with provided token." echo ">> [SIG] Token: ${BTC_SIG_TOKEN:0:16}..." return 0 fi # --- Tier 2: TPM --- # Hardware-bound signature. Singular deployments only. if [[ "${BTC_TPM_SEAL}" -eq 1 ]]; then local pcr_digest pcr_digest=$(f_tpm_pcr_digest) || true if [[ -n "${pcr_digest}" ]]; then BTC_SIG_TOKEN="tpm:${pcr_digest:0:32}" BTC_SIG_TIER="tpm" echo ">> [SIG] Tier 2 (tpm) — PCR digest sealed into signature." echo ">> [SIG] Token: ${BTC_SIG_TOKEN:0:16}..." return 0 else echo ">> [WARN] --tpm-seal requested but no TPM hardware detected. Stepping down to poly." fi fi # --- Tier 3: Poly (default) --- # Per-machine random salt. Write-once; subsequent builds reuse it. mkdir -p "${BTC_ARCHIVE}" if [[ -f "${salt_file}" ]]; then BTC_SIG_TOKEN=$(< "${salt_file}") else BTC_SIG_TOKEN=$(openssl rand -hex 16) echo "${BTC_SIG_TOKEN}" > "${salt_file}" fi BTC_SIG_TIER="poly" echo ">> [SIG] Tier 3 (poly) — Per-machine salt deployed." echo ">> [SIG] Token: ${BTC_SIG_TOKEN:0:16}..." } function f_stamp_binary() { local target_bin="$1" local log_base="$2" if [[ -f "${target_bin}" && ! -L "${target_bin}" ]]; then # Select the assembler matching the build mode local assembler if [[ "${CROSS_MODE}" -eq 1 ]]; then assembler="${TARGET}-gcc" else assembler="gcc" fi # 1. Inject ELF Object Note with poly-signature token # The .note.BTC payload now carries the active sig tier and token, # making each deployment's forensic identity distinct. # Note type 0xB7C is a vendor-specific identifier — it does NOT # correspond to any standard ELF note type (the Linux vendor-note # namespace 0x0B7C is reserved for out-of-tree consumers). cat << EOF > btc_stamp.s .section .note.BTC,"a",@note .long 2f - 1f .long 4f - 3f .long 0xB7C 1: .asciz "DCOSNET" 2: .align 4 3: .ascii "Org: dcos.net|K:${v_linux}|Arch:${BTC_T_ID}|Label:${SYS_LABEL}|Stage:${log_base}|SigTier:${BTC_SIG_TIER}|Sig:${BTC_SIG_TOKEN}" 4: .align 4 EOF ${assembler} -c btc_stamp.s -o btc_stamp.o ${TARGET}-objcopy --add-section .note.BTC=btc_stamp.o "${target_bin}" 2>/dev/null || \ objcopy --add-section .note.BTC=btc_stamp.o "${target_bin}" 2>/dev/null || true rm -f btc_stamp.s btc_stamp.o # 2. Extended Filesystem Attributes — identity now includes sig tier and token local bin_hash bin_hash=$(sha256sum "${target_bin}" | awk '{print $1}') setfattr -n user.btc.identity -v "BTC-${SYS_LABEL}-${v_linux}-${BTC_SIG_TIER}" "${target_bin}" 2>/dev/null || true setfattr -n user.btc.hash -v "${bin_hash}" "${target_bin}" 2>/dev/null || true setfattr -n user.btc.sig.tier -v "${BTC_SIG_TIER}" "${target_bin}" 2>/dev/null || true setfattr -n user.btc.sig.token -v "${BTC_SIG_TOKEN}" "${target_bin}" 2>/dev/null || true # 3. Separate Debug Symbols & Create External Links if [[ "${BTC_STRIP_MODE:-1}" -eq 1 ]]; then mkdir -p "${BTC_ARCHIVE}/symbols/${SYS_LABEL}" ${TARGET}-objcopy --only-keep-debug "${target_bin}" "${BTC_ARCHIVE}/symbols/${SYS_LABEL}/${log_base}.debug" 2>/dev/null || \ objcopy --only-keep-debug "${target_bin}" "${BTC_ARCHIVE}/symbols/${SYS_LABEL}/${log_base}.debug" 2>/dev/null || true ${TARGET}-strip --strip-unneeded "${target_bin}" 2>/dev/null || \ strip --strip-unneeded "${target_bin}" 2>/dev/null || true ${TARGET}-objcopy --add-gnu-debuglink="${BTC_ARCHIVE}/symbols/${SYS_LABEL}/${log_base}.debug" "${target_bin}" 2>/dev/null || \ objcopy --add-gnu-debuglink="${BTC_ARCHIVE}/symbols/${SYS_LABEL}/${log_base}.debug" "${target_bin}" 2>/dev/null || true fi fi } # ============================================================================ # 7. SOURCE ACQUISITION (download / decompress / compress) # ============================================================================ # Associative array: package stem -> upstream URL # Only packages consumed by the build sequence are listed. # URLs are official upstream mirrors — replace to point at a local mirror. # # NOTE: A_SRC_URL is declared here as an empty container and populated lazily # inside f_download(). This is required because v_linux, v_gcc, and # v_linux_headers may be overridden per-target (e.g. tile targets pin # linux-5.4.302 and gcc-10.3.0 because mainline dropped tile support in # Linux 5.9 and GCC 12). Building the URL table at parse time would bake # in the default linux-7.1.x / gcc-15.3.0 URLs and ignore the per-target # overrides, causing f_decompress() to look for the wrong tarball later. # # The kernel URL path component (v5.x / v6.x / v7.x) is derived from the # major version of v_linux so the script works across kernel series without # manual URL edits. declare -A A_SRC_URL=() function _archive_sane() { # Quick integrity test: returns 0 if archive is valid, 1 if corrupt. # Handles .tar.xz, .tar.gz, .tar.bz2, .tar.lz, .tar.lrz, .zip local f="$1" local ext="${f##*.}" case "${ext}" in xz|bz2|lz|gz) tar -tf "${f}" >/dev/null 2>&1 ;; lrz) lrzip -t "${f}" >/dev/null 2>&1 ;; zip) unzip -t "${f}" >/dev/null 2>&1 ;; *) return 0 ;; esac } function f_download() { # Fetch all upstream source tarballs into SOURCE_CACHE. # Skips files already present AND integrity-verified (idempotent — safe to re-run). # Corrupted/truncated files are deleted and re-fetched automatically. # Produces per-file md5 and sha512 checksum manifests. mkdir -p "${SOURCE_CACHE}" "${LOGS}/checksums" cd "${SOURCE_CACHE}" echo ">> [ACQUIRE] Downloading upstream source tarballs..." # Build the URL table NOW (after any per-target overrides in # _configure_from_target have been applied). The kernel URL path # component (v5.x / v6.x / v7.x) is derived from the major version # of v_linux so tile targets (linux-5.4.x) and mainline targets # (linux-7.1.x) both resolve correctly. local _linux_major="${v_linux#linux-}" _linux_major="${_linux_major%%.*}" A_SRC_URL[binutils]="https://ftp.gnu.org/gnu/binutils/${v_binutils}.tar.xz" A_SRC_URL[linux]="https://cdn.kernel.org/pub/linux/kernel/v${_linux_major}.x/${v_linux}.tar.xz" A_SRC_URL[linux-headers]="https://cdn.kernel.org/pub/linux/kernel/v${_linux_major}.x/${v_linux_headers}.tar.xz" A_SRC_URL[gcc]="https://ftp.gnu.org/gnu/gcc/${v_gcc}/${v_gcc}.tar.xz" A_SRC_URL[glibc]="https://ftp.gnu.org/gnu/glibc/${v_glibc}.tar.xz" A_SRC_URL[libxcrypt]="https://github.com/besser82/libxcrypt/releases/download/v${v_libxcrypt}/libxcrypt-${v_libxcrypt}.tar.xz" A_SRC_URL[gmp]="https://ftp.gnu.org/gnu/gmp/${v_gmp}.tar.xz" A_SRC_URL[mpfr]="https://ftp.gnu.org/gnu/mpfr/${v_mpfr}.tar.xz" A_SRC_URL[mpc]="https://ftp.gnu.org/gnu/mpc/${v_mpc}.tar.xz" A_SRC_URL[musl]="https://musl.libc.org/releases/${v_musl}.tar.gz" for key in "${!A_SRC_URL[@]}"; do local url="${A_SRC_URL[$key]}" local file="${url##*/}" if [[ -f "${file}" ]] && _archive_sane "${file}"; then echo ">> [ACQUIRE] ${file} — present, skipping." else if [[ -f "${file}" ]]; then echo ">> [ACQUIRE] ${file} — corrupt/truncated, re-fetching..." rm -f "${file}" else echo ">> [ACQUIRE] ${file} — fetching..." fi wget -nc -O "${file}" "${url}" # Post-download integrity check if ! _archive_sane "${file}"; then echo ">> [ACQUIRE] ${file} — FAILED integrity check after download!" rm -f "${file}" return 1 fi fi md5sum "${file}" >> "${LOGS}/checksums/${file}.md5" 2>/dev/null || true sha512sum "${file}" >> "${LOGS}/checksums/${file}.sha512" 2>/dev/null || true done echo ">> [ACQUIRE] Source acquisition complete." } function f_decompress() { # Auto-detect compression type from file extension and extract into # SOURCES_ACTIVE (ramfs cleanroom). Accepts either a bare filename # (looked up in SOURCE_CACHE) or a full path. # # Usage: f_decompress # uses SOURCE_CACHE # f_decompress # absolute path local in_file case "$1" in /*) in_file="$1" ;; *) in_file="${SOURCE_CACHE}/$1" ;; esac local ext="${in_file##*.}" cd "${SOURCES_ACTIVE}" case "${ext}" in xz) tar -axf "${in_file}" ;; gz) tar -xzf "${in_file}" ;; bz2) tar -xjf "${in_file}" ;; lz) tar -axf "${in_file}" ;; lrz) tar -x -I lrzip -f "${in_file}" ;; zip) unzip -qo "${in_file}" ;; *) echo ">> [WARN] f_decompress: unknown extension '.${ext}' for ${in_file}" ;; esac } function f_compress() { # Compress the finished toolchain tarball using the specified algorithm. # Step-down dispatch on USE_COMPRESSOR — SEI CERT CTR50-JP. # Usage: f_compress local out_file="$1" case "${USE_COMPRESSOR:-xz}" in xz) xz -z -e -9 "${out_file}" ;; gz) gzip -9 "${out_file}" ;; bz2) bzip2 -z9 "${out_file}" ;; lrzip) lrzip -z -L9 -p${total_cpus:-1} -U "${out_file}" ;; *) echo ">> [WARN] f_compress: unknown compressor '${USE_COMPRESSOR}', defaulting to xz"; xz -z -e -9 "${out_file}" ;; esac } # ============================================================================ # 7b. CLEANROOM MATRIX CONFIGURATION # ============================================================================ function f_setup() { echo ">> Preparing Virtualized Cleanroom Environment..." mkdir -p "${SOURCE_CACHE}" "${LOGS}" "${BTC_ARCHIVE}/symbols/${SYS_LABEL}" if ! mountpoint -q "${SOURCES_ACTIVE}"; then mount -t ramfs -o "size=${RAMDISK_SIZE}" ramfs "${SOURCES_ACTIVE}" echo ">> Ramfs Cleanroom mounted at ${SOURCES_ACTIVE} with ceiling ${RAMDISK_SIZE}." fi mkdir -p "${NEWROOT}" cd "${NEWROOT}" # Create sysroot directory structure mkdir -p bin etc lib lib64 sbin usr var include # Architecture-specific sysroot layout case "${BTC_T_ARCH}" in x86_64) ln -sfv lib "${NEWROOT}/lib64" ;; arm) # ARM EABI HF uses lib + lib/ld-linux-armhf.so.3 ln -sfv lib "${NEWROOT}/lib32" 2>/dev/null || true ;; mipsel) # MIPS o32 ABI: lib is the primary lib dir ;; tilegx) # Tile-Gx 64-bit: lib64 for abi64 ln -sfv lib "${NEWROOT}/lib64" 2>/dev/null || true ;; esac export PATH="${NEWROOT}/bin:${PATH}" } # ============================================================================ # 8. TOOLCHAIN BUILD FUNCTIONS # ============================================================================ # --- 8a. Binutils (all architectures) --- function f_binutils() { cd "${SOURCES_ACTIVE}" f_decompress "${v_binutils}.tar.xz" mkdir -p "${v_binutils}-build" && cd "${v_binutils}-build" local configure_target="--target=${TARGET}" # Architecture-specific binutils configure patches local binutils_extra="" case "${BTC_T_ARCH}" in arm) binutils_extra="--enable-multilib --with-sysroot=${NEWROOT}" ;; mipsel) binutils_extra="--enable-multilib --with-sysroot=${NEWROOT}" ;; tilegx) binutils_extra="--disable-werror" ;; *) binutils_extra="--enable-default-hash-style=gnu" ;; esac local build_cmd="../${v_binutils}/configure \ --prefix=${NEWROOT} \ --with-sysroot=${NEWROOT} \ ${configure_target} \ --disable-nls \ --enable-gprofng=no \ --disable-werror \ ${binutils_extra}" f_exec_log "${build_cmd}" "binutils-configure" f_exec_log "make ${v_threads}" "binutils-make" f_exec_log "make install" "binutils-install" } # --- 8b. Kernel Headers (all architectures) --- function f_kernel_headers() { cd "${SOURCES_ACTIVE}" # Use stable LTS headers for cross targets that may need older kernels local kernel_src="${v_linux}" if [[ "${BTC_T_FAMILY}" == "tile" ]]; then kernel_src="${v_linux_headers}" fi # For musl targets, we only need sanitized kernel headers (no full # kernel source). For glibc targets we need the full headers. f_decompress "${kernel_src}.tar.xz" cd "${kernel_src}" f_exec_log "make mrproper" "kernel-headers-clean" f_exec_log "make headers" "kernel-headers-generate" find usr/include -type f ! -name '*.h' -delete mkdir -p "${NEWROOT}/usr/include" cp -rv usr/include/* "${NEWROOT}/usr/include" } # --- 8c. GCC Stage 1 (all architectures) --- function f_gcc_p1() { cd "${SOURCES_ACTIVE}" rm -rf "${v_gcc}" f_decompress "${v_gcc}.tar.xz" cd "${v_gcc}" # Nesting Support Libraries internally for Stage-1 execution isolation # f_decompress changes CWD to SOURCES_ACTIVE, so we must re-enter # the gcc source tree and use absolute paths for the mv. f_decompress "${v_gmp}.tar.xz" f_decompress "${v_mpfr}.tar.xz" f_decompress "${v_mpc}.tar.xz" cd "${SOURCES_ACTIVE}/${v_gcc}" rm -rf gmp mpfr mpc mv -Tf "${SOURCES_ACTIVE}/${v_gmp}" gmp mv -Tf "${SOURCES_ACTIVE}/${v_mpfr}" mpfr mv -Tf "${SOURCES_ACTIVE}/${v_mpc}" mpc # Architecture-specific GCC source patches case "${BTC_T_ARCH}" in x86_64) # Enforce 64-bit dynamic linker structural target pathing # GCC >= 15 removed t-linux64; t-linux or config.gcc may carry m64= if [[ -f gcc/config/i386/t-linux64 ]]; then sed -e '/m64=/s/lib64/lib/' -i.bak gcc/config/i386/t-linux64 elif [[ -f gcc/config/i386/t-linux ]]; then sed -e '/m64=/s/lib64/lib/' -i.bak gcc/config/i386/t-linux else echo ">> [WARN] gcc/config/i386/t-linux64 not found in ${v_gcc}; skipping lib64->lib multilib patch (--disable-multilib is active)" fi ;; arm) # ARM: default to hard-float ABI sed -e 's/#define DEFAULT_ABI_FLOAT SoftF/SoftF_HARDFP/' -i.bak gcc/config/arm/linux-eabi.h 2>/dev/null || true ;; mipsel) # MIPS: default to o32 ABI, soft-float ;; tilegx) # Tile-Gx: no special patches needed for GCC 10.3.0 ;; esac mkdir -p "${SOURCES_ACTIVE}/${v_gcc}-phase1" && cd "${SOURCES_ACTIVE}/${v_gcc}-phase1" # Base configure flags common to all targets. # # --with-cpu is x86_64-only — see f_gcc_p2() for the same rationale. # ARM, MIPS, and Tile-Gx carry their per-target --with-* hints via # BTC_T_GCC_EXTRA (set in the registry) which is appended below. local gcc_p1_arch_flags="" if [[ "${BTC_T_ARCH}" == "x86_64" ]]; then gcc_p1_arch_flags="--with-cpu=${BTC_T_MARCH}" fi local gcc_base="--target=${TARGET} \ --prefix=${NEWROOT} \ --with-sysroot=${NEWROOT} \ --with-newlib \ --without-headers \ --with-arch=${BTC_T_MARCH} \ ${gcc_p1_arch_flags} \ --enable-default-pie \ --enable-default-ssp \ --disable-nls \ --disable-shared \ --disable-threads \ --disable-libatomic \ --disable-libgomp \ --disable-libquadmath \ --disable-libssp \ --disable-libvtv \ --disable-libstdcxx \ --enable-languages=c,c++" # Per-architecture configure adjustments local gcc_arch_extra="" case "${BTC_T_CLIB}" in glibc) # x86_64 glibc targets: set glibc version for compatibility checks gcc_arch_extra="--with-glibc-version=${v_glibc#*-} --disable-multilib" ;; musl) # Cross targets with musl: disable multilib by default, add arch-specific flags gcc_arch_extra="--disable-multilib ${BTC_T_GCC_EXTRA}" ;; esac local build_cmd="../${v_gcc}/configure ${gcc_base} ${gcc_arch_extra}" f_exec_log "${build_cmd}" "gcc-p1-configure" f_exec_log "make ${v_threads} all-gcc" "gcc-p1-make" f_exec_log "make ${v_threads} all-target-libgcc" "gcc-p1-libgcc" f_exec_log "make install-gcc" "gcc-p1-install" f_exec_log "make install-target-libgcc" "gcc-p1-install-libgcc" } # --- 8d. C Library (glibc or musl) --- function f_clib() { # Dispatch C library build; enforce architecture constraints at the gate case "${BTC_T_CLIB}" in glibc) if [[ "${BTC_T_ARCH}" != "x86_64" ]]; then echo ">> [ERROR] glibc does not support ${BTC_T_ARCH}. Select a musl-based target." exit 1 fi f_glibc ;; musl) f_musl ;; *) echo ">> [ERROR] Unsupported C library: ${BTC_T_CLIB}" exit 1 ;; esac } function f_glibc() { cd "${SOURCES_ACTIVE}" f_decompress "${v_glibc}.tar.xz" mkdir -p "${v_glibc}-build" && cd "${v_glibc}-build" local build_cmd="../${v_glibc}/configure \ --prefix=/usr \ --host=${TARGET} \ --build=${HOST_ARCH} \ --enable-kernel=${BTC_T_KERN_MIN} \ --with-headers=${NEWROOT}/usr/include \ --disable-profile \ --enable-stack-protector=strong \ --disable-werror \ libc_cv_slibdir=/usr/lib" f_exec_log "${build_cmd}" "glibc-configure" f_exec_log "make ${v_threads}" "glibc-make" f_exec_log "make DESTDIR=${NEWROOT} install" "glibc-install" # Sanitize hardcoded host system configurations from dynamic script linkage sed -i "s|${NEWROOT}||g" "${NEWROOT}/usr/bin/ldd" } function f_libxcrypt() { # libxcrypt is only used with glibc — musl has built-in crypt support if [[ "${BTC_T_CLIB}" != "glibc" ]]; then echo ">> [SKIP] libxcrypt: not needed for ${BTC_T_CLIB}" return 0 fi cd "${SOURCES_ACTIVE}" f_decompress "libxcrypt-${v_libxcrypt}.tar.xz" cd "libxcrypt-${v_libxcrypt}" # GCC >= 15 enforces stricter const-correctness (-Wcast-qual, -Wdiscarded-qualifiers). # libxcrypt 4.5.2 has known issues in crypt-gost-yescrypt.c and crypt-sm3-yescrypt.c # that trigger -Werror. Pass CFLAGS=-Wno-error to configure so it is baked into # the generated Makefile. Automake places CFLAGS after WARN_CFLAGS on the compile # line, so GCC processes -Werror first then -Wno-error (last wins). This survives # Makefile regeneration and is the standard approach for building old deps with # newer compilers. local build_cmd="CFLAGS=\"-g -O2 -Wno-error\" ./configure \ --prefix=/usr \ --host=${TARGET} \ --build=${HOST_ARCH} \ --enable-hashes=strong,glibc \ --enable-obsolete-api=no \ --disable-static" f_exec_log "${build_cmd}" "libxcrypt-configure" f_exec_log "make ${v_threads}" "libxcrypt-make" f_exec_log "make DESTDIR=${NEWROOT} install" "libxcrypt-install" } function f_musl() { # musl: lightweight C library for cross-compilation # Used by mipsel, armv7, and tilegx targets. # Reference: CLFS (Cross Linux From Scratch) musl cross-compiler chapter. cd "${SOURCES_ACTIVE}" f_decompress "${v_musl}.tar.gz" cd "${v_musl}" # Build a standalone musl cross-compiler that wraps our stage-1 GCC. # This produces ${TARGET}-musl-gcc and the musl C library installed # into the sysroot. local musl_configure="./configure \ --prefix=/usr \ --host=${TARGET} \ --build=${HOST_ARCH} \ --disable-shared \ --enable-static" # For some targets, musl needs additional architecture hints case "${BTC_T_ARCH}" in arm) musl_configure="${musl_configure} CFLAGS=\"-O2 -march=armv7-a -mfloat-abi=hard -mfpu=vfpv3-d16\"" ;; mipsel) musl_configure="${musl_configure} CFLAGS=\"-O2 -march=mips32r2 -mabi=32 -msoft-float\"" ;; tilegx) musl_configure="${musl_configure} CFLAGS=\"-O2 -march=tilegx\"" ;; esac # Pass cross-compiler tools inline to avoid polluting the caller's environment local cross_env="CC=${NEWROOT}/bin/${TARGET}-gcc AR=${NEWROOT}/bin/${TARGET}-ar RANLIB=${NEWROOT}/bin/${TARGET}-ranlib" f_exec_log "${cross_env} CROSS_COMPILE=${TARGET}- ${musl_configure}" "musl-configure" f_exec_log "${cross_env} make ${v_threads}" "musl-make" f_exec_log "${cross_env} make DESTDIR=${NEWROOT} install" "musl-install" } # --- 8e. GCC Stage 2 (final compiler) --- function f_gcc_p2() { cd "${SOURCES_ACTIVE}" # Decompress support libraries (f_decompress changes CWD to SOURCES_ACTIVE). f_decompress "${v_gmp}.tar.xz" f_decompress "${v_mpfr}.tar.xz" f_decompress "${v_mpc}.tar.xz" # Re-enter gcc source tree and nest the extracted libraries where # GCC's configure expects to find them (as gmp/, mpfr/, mpc/ subdirs). cd "${SOURCES_ACTIVE}/${v_gcc}" rm -rf gmp mpfr mpc mv -Tf "${SOURCES_ACTIVE}/${v_gmp}" gmp mv -Tf "${SOURCES_ACTIVE}/${v_mpfr}" mpfr mv -Tf "${SOURCES_ACTIVE}/${v_mpc}" mpc mkdir -p "${SOURCES_ACTIVE}/${v_gcc}-phase2" && cd "${SOURCES_ACTIVE}/${v_gcc}-phase2" # Stage 2 final compiler — runs on BUILD host, targets the sysroot. # # CRITICAL: --host must be ${HOST_ARCH} (the machine the compiler # executes on), NOT ${TARGET}. --target specifies what architecture # the produced compiler generates code for. Using --host=${TARGET} # caused configure to expect the build machine to be the custom # triple, which made all compile-and-run checks fail instantly. # # --with-sysroot and --with-headers are in the base because both # glibc and musl targets need the compiler to find C library # headers and runtime in the sysroot. # # --with-cpu is x86_64-only. ARM, MIPS, and Tile-Gx configure # backends reject --with-cpu= (they want --with-cpu= # e.g. cortex-a9, not the architecture name). Those targets # already carry --with-arch= via BTC_T_GCC_EXTRA from the # registry, so we omit --with-cpu entirely for non-x86_64 to # avoid "unrecognized argument" failures during configure. # # LDFLAGS=-Wl,-rpath,/usr/lib/../lib matches the LFS final-GCC # recipe. Without it, the freshly-built ${TARGET}-gcc may fail # at runtime to locate libc.so in the sysroot, which surfaces as # "cannot find libc.so.6" during the Stage 2 libstdc++ configure # link tests and aborts Phase 2. local gcc_p2_arch_flags="" if [[ "${BTC_T_ARCH}" == "x86_64" ]]; then gcc_p2_arch_flags="--with-cpu=${BTC_T_MARCH}" fi local gcc_p2_base="--prefix=/usr \ --build=${HOST_ARCH} \ --host=${HOST_ARCH} \ --target=${TARGET} \ --with-sysroot=${NEWROOT} \ --with-headers=${NEWROOT}/usr/include \ --with-arch=${BTC_T_MARCH} \ ${gcc_p2_arch_flags} \ --enable-languages=c,c++ \ --enable-default-pie \ --enable-default-ssp \ --enable-threads=posix \ --disable-bootstrap \ LDFLAGS=\"-Wl,-rpath,/usr/lib/../lib\"" # Per-C-library configure adjustments. # # glibc targets: pull in the LFS final-GCC flags so libstdc++ # configure finds glibc locale support (--enable-clocale=gnu), # uses the __cxa_atexit path for static destructors (required by # the C++ ABI), and skips PCH generation (--disable-libstdcxx-pch, # which would otherwise require running the just-built cross-gcc # to emit a .gch file — impossible during a cross build where we # cannot execute target binaries). # # musl targets: point GCC at the musl library path and inherit # per-target --with-* hints (arch, fpu, abi, ...) from BTC_T_GCC_EXTRA. local gcc_p2_clib="" case "${BTC_T_CLIB}" in glibc) gcc_p2_clib="--disable-multilib \ --enable-clocale=gnu \ --enable-__cxa_atexit \ --disable-libstdcxx-pch" ;; musl) gcc_p2_clib="--disable-multilib \ --with-libs=${NEWROOT}/usr/lib \ ${BTC_T_GCC_EXTRA}" ;; esac local build_cmd="../${v_gcc}/configure ${gcc_p2_base} ${gcc_p2_clib}" f_exec_log "${build_cmd}" "gcc-p2-configure" f_exec_log "make ${v_threads}" "gcc-p2-make" f_exec_log "make DESTDIR=${NEWROOT} install" "gcc-p2-install" # Stage 2 GCC was installed at ${NEWROOT}/usr/bin/${TARGET}-gcc. # Prepend that to PATH so f_kernel_binary() picks up the final # compiler instead of the stripped-down Stage 1 cross-gcc at # ${NEWROOT}/bin/${TARGET}-gcc (which was built with # --disable-shared --disable-threads --disable-libstdcxx and # is unsuitable as the production compiler for the kernel and # any downstream package builds that link against libgcc_s. export PATH="${NEWROOT}/usr/bin:${PATH}" echo ">> [PATH] Prepended ${NEWROOT}/usr/bin for Stage 2 GCC." } # --- 8f. Kernel Binary (architecture-aware) --- function f_kernel_binary() { local kernel_src="${v_linux}" if [[ "${BTC_T_FAMILY}" == "tile" ]]; then kernel_src="${v_linux_headers}" fi cd "${SOURCES_ACTIVE}/${kernel_src}" echo ">> Instantiating Silicon Optimized Monolithic Configuration Matrix for ${BTC_T_ARCH}..." # Map BTC's target architecture to the Linux kernel's ARCH= name. # The kernel's ARCH= variable accepts only the canonical family name # (arm, mips, x86_64, tilegx, ...). Our registry uses "mipsel" to # distinguish little-endian MIPS from big-endian, but the kernel's # Makefile treats MIPS endianness as a Kconfig selection, not as a # separate ARCH. Passing ARCH=mipsel makes the kernel look for # arch/mipsel/ which does not exist and the build dies at "make # defconfig". Resolve the canonical name here, once, so every # downstream make invocation (defconfig, olddefconfig, image, install) # uses the same value. local kernel_arch case "${BTC_T_ARCH}" in mipsel) kernel_arch="mips" ;; *) kernel_arch="${BTC_T_ARCH}" ;; esac # LD_LIBRARY_PATH for Stage 2 GCC's C++ runtime (libstdc++.so, libgcc_s.so). # See the long comment near the kernel-bin-make invocation below for the # full rationale. Set it BEFORE defconfig because defconfig also probes # the cross-compiler (`${CROSS_COMPILE}gcc -print-file-name=...`) and a # broken probe produces an empty .config — exactly the 527-byte defconfig # output symptom that aborts the build silently. local gcc_ver="${v_gcc#gcc-}" local stage2_libpath="${NEWROOT}/usr/lib:${NEWROOT}/lib" local gcc_libdir="${NEWROOT}/usr/lib/gcc/${TARGET}/${gcc_ver}" if [[ -d "${gcc_libdir}" ]]; then stage2_libpath="${stage2_libpath}:${gcc_libdir}" [[ -d "${gcc_libdir}/32" ]] && stage2_libpath="${stage2_libpath}:${gcc_libdir}/32" fi export LD_LIBRARY_PATH="${stage2_libpath}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" echo ">> [LD_LIBRARY_PATH] ${LD_LIBRARY_PATH}" # Select the correct defconfig for the target architecture. # These are wrapped in f_exec_log() so the defconfig output is # captured in ${LOGS}/kernel-bin-defconfig.log — matching every # other build step in the pipeline. Without logging, a defconfig # failure surfaces as a bare error on stdout with no archived # record for post-mortem. local defconfig_target case "${BTC_T_ARCH}" in arm) # multi_v7 is the universal ARMv7 defconfig (covers most Cortex-A SoCs) defconfig_target="multi_v7_defconfig" ;; mipsel) # MALTA is the reference MIPS32 platform defconfig_target="malta_defconfig" ;; tilegx) # Tile-Gx has its own defconfig; fall back to defconfig for # kernels where tilegx_defconfig was already removed. defconfig_target="tilegx_defconfig" ;; *) defconfig_target="defconfig" ;; esac # First attempt: the target-specific defconfig. # For tilegx on kernels that removed tilegx_defconfig, fall back to # the generic defconfig (which still respects ARCH=tilegx). # # The `if !` form suspends `set -e` for the condition test so a # failed defconfig doesn't abort the whole script before we can # try the fallback. For non-tile targets, a defconfig failure # is a hard error and we return 1. if ! f_exec_log "make ARCH=${kernel_arch} ${defconfig_target}" "kernel-bin-defconfig"; then if [[ "${BTC_T_ARCH}" == "tilegx" ]]; then echo ">> [WARN] ${defconfig_target} not available in ${kernel_src}; falling back to defconfig." f_exec_log "make ARCH=${kernel_arch} defconfig" "kernel-bin-defconfig" else echo ">> [ERROR] ${defconfig_target} failed for ${kernel_src}." return 1 fi fi # Inject Custom Enterprise Swarm Labels & Architecture Parameters sed -i "s/CONFIG_LOCALVERSION=\"\"/CONFIG_LOCALVERSION=\"-dcosnet-${SYS_LABEL}\"/" .config # Modern Hardening Optimization Suite Injection sed -i "s/# CONFIG_MODULES is not set/CONFIG_MODULES=n/" .config || true echo "CONFIG_MODULES=n" >> .config echo "CONFIG_KALLSYMS=n" >> .config echo "CONFIG_DEBUG_FS=n" >> .config # Cross-compile kernel using the canonical ARCH= name and the # Stage 2 cross-compiler (now on PATH after f_gcc_p2()). # # LD_LIBRARY_PATH was already set up earlier in this function (before # the defconfig call) so the loader finds Stage 2's libstdc++.so / # libgcc_s.so first. See the comment near the top of this function # for the full rationale. local kernel_make_vars="ARCH=${kernel_arch} CROSS_COMPILE=${TARGET}-" f_exec_log "make ${kernel_make_vars} olddefconfig" "kernel-bin-config-merge" f_exec_log "make ${v_threads} ${kernel_make_vars} LOCALVERSION=-dcosnet-${SYS_LABEL}" "kernel-bin-make" # Install kernel image — path varies by architecture mkdir -p "${NEWROOT}/boot" case "${BTC_T_ARCH}" in x86_64) cp -v arch/x86/boot/bzImage "${NEWROOT}/boot/vmlinuz-${v_linux}-${SYS_LABEL}" ;; arm) cp -v arch/arm/boot/zImage "${NEWROOT}/boot/vmlinuz-${v_linux}-${SYS_LABEL}" 2>/dev/null || \ cp -v arch/arm/boot/Image "${NEWROOT}/boot/vmlinuz-${v_linux}-${SYS_LABEL}" # Also copy device tree blobs if built find arch/arm/boot/dts -name '*.dtb' -exec cp -v {} "${NEWROOT}/boot/" \; 2>/dev/null || true ;; mipsel) cp -v vmlinux "${NEWROOT}/boot/vmlinuz-${v_linux}-${SYS_LABEL}" ;; tilegx) cp -v arch/tile/boot/vmlinux "${NEWROOT}/boot/vmlinuz-${v_linux}-${SYS_LABEL}" 2>/dev/null || \ cp -v vmlinux "${NEWROOT}/boot/vmlinuz-${v_linux}-${SYS_LABEL}" ;; esac # Apply Forensic Identity Stamps to all toolchain binaries while IFS= read -r bin; do f_stamp_binary "${bin}" "$(basename "${bin}")" done < <(find "${NEWROOT}/bin" "${NEWROOT}/usr/bin" -type f 2>/dev/null) || true } # ============================================================================ # 9. PACKAGING # ============================================================================ function f_package() { echo ">> Packaging Production Golden Image Artifact Target Matrix..." cd "${NEWROOT}" # --- Integration Manifest for sorcery-go and Fester --- local manifest="${BTC_ARCHIVE}/${SYS_LABEL}-manifest.json" # Derive mode label from dispatch state local mode_label case "${CROSS_MODE}" in 1) mode_label="cross" ;; *) mode_label="native" ;; esac cat > "${manifest}" << MANIFEST_EOF { "btc_version": "${BTC_VERSION}", "mode": "${mode_label}", "cross_mode": ${CROSS_MODE}, "sys_label": "${SYS_LABEL}", "target_id": "${BTC_T_ID}", "target_arch": "${BTC_T_ARCH}", "target_cpu": "${BTC_T_CPU}", "target_march": "${BTC_T_MARCH}", "target_triple": "${TARGET}", "host_arch": "${HOST_ARCH}", "isa_tag": "${ISA_TAG}", "opt_tag": "${OPT_TAG}", "abi": "${BTC_T_ABI}", "clib": "${BTC_T_CLIB}", "endian": "${BTC_T_ENDIAN}", "family": "${BTC_T_FAMILY}", "description": "${BTC_T_DESC}", "kernel_min": "${BTC_T_KERN_MIN}", "kernel": "${v_linux}", "binutils": "${v_binutils}", "gcc": "${v_gcc}", "glibc": "${v_glibc}", "musl": "${v_musl}", "libxcrypt": "libxcrypt-${v_libxcrypt}", "golden_image": "${SYS_LABEL}-toolchain-golden.tar.xz", "cflags": "${GLOBAL_CFLAGS}", "ldflags": "${GLOBAL_LDFLAGS}", "stamp_note": ".note.BTC", "stamp_xattr_identity": "user.btc.identity", "stamp_xattr_hash": "user.btc.hash", "stamp_xattr_sig_tier": "user.btc.sig.tier", "stamp_xattr_sig_token": "user.btc.sig.token", "sig_tier": "${BTC_SIG_TIER}", "sig_token_preview": "${BTC_SIG_TOKEN:0:16}...", "org": "dcos.net", "license": "AGPL-3.0-or-later", "integrations": { "sorcery-go": { "config_key": "toolchain", "config_value": "btc", "env_btc_path": "SORCERY_GO_BTC_PATH", "env_btc_root": "SORCERY_GO_BTC_ROOT", "env_btc_syslabel": "SORCERY_GO_BTC_SYS_LABEL" }, "fester": { "config_section": "btc", "config_yaml_key": "btc.enabled / btc.root / btc.target" } } } MANIFEST_EOF echo ">> [INTEGRATION] Manifest written to: ${manifest}" tar -cf - . | xz -9 -T 0 > "${BTC_ARCHIVE}/${SYS_LABEL}-toolchain-golden.tar.xz" echo ">> [SUCCESS] Archive deployed cleanly to: ${BTC_ARCHIVE}/${SYS_LABEL}-toolchain-golden.tar.xz" # Generate SHA-256 checksums of the golden image for CAS integration. local checksum_file="${BTC_ARCHIVE}/${SYS_LABEL}-toolchain-golden.sha256" sha256sum "${BTC_ARCHIVE}/${SYS_LABEL}-toolchain-golden.tar.xz" > "${checksum_file}" echo ">> [INTEGRATION] SHA-256 checksum written to: ${checksum_file}" } # ============================================================================ # 10. MAIN ENTRY RUNTIME MATRIX # ============================================================================ function f_main() { [[ ${EUID} -ne 0 ]] && { echo ">> Error: Root privileges required."; exit 1; } # Parse command-line arguments (multi-flag pass) # Flags that exit early are handled first; build flags accumulate. local positional="" while [[ $# -gt 0 ]]; do case "$1" in --list) f_list_targets exit 0 ;; --list-json) f_list_targets_json exit 0 ;; --help|-h) echo "BTC-${BTC_VERSION} - Cross-Compilation Build Tool Chain" echo "" echo "Usage: BTC.sh [TARGET_ID | --native | --list | --list-json]" echo " BTC.sh --tpm-seal [TARGET_ID]" echo " BTC.sh --join-cluster=TOKEN [TARGET_ID]" echo "" echo " TARGET_ID Build cross-toolchain for the specified target" echo " --native Auto-probe host silicon and build native toolchain" echo " --list List all registered cross-compilation targets" echo " --list-json Emit target registry as JSON" echo " --tpm-seal Bind forensic signature to TPM PCR state (singular deploys)" echo " --join-cluster=T Adopt cluster token T for unified identity" echo "" echo "Omitting an argument selects --native automatically." echo "Signature tiers: cluster (joined) > tpm (hardware) > poly (per-machine salt)." f_list_targets exit 0 ;; --tpm-seal) BTC_TPM_SEAL=1 shift ;; --join-cluster=*) export BTC_CLUSTER_JOIN="${1#*=}" shift ;; --native) positional="--native" shift ;; *) positional="$1" shift ;; esac done export BTC_TARGET_ID="${positional:-}" f_agpl_header f_silicon_probe f_set_paths f_setup f_download # Acquire all upstream source tarballs (idempotent) f_sig_init f_tmux_dashboard # Linear Build Execution Sequence f_binutils f_kernel_headers f_gcc_p1 f_clib # glibc OR musl (depending on target registry) f_libxcrypt # glibc-only (skipped for musl targets) f_gcc_p2 f_kernel_binary f_package # Clear volatile memory cleanrooms cd / umount -l "${SOURCES_ACTIVE}" 2>/dev/null || true echo ">> [COMPLETE] Build Tool Chain Finished Successfully under AGPLv3 Framework." echo ">> [TARGET] ${BTC_T_ID} (${BTC_T_DESC})" echo ">> [LABEL] ${SYS_LABEL}" echo ">> [IMAGE] ${BTC_ARCHIVE}/${SYS_LABEL}-toolchain-golden.tar.xz" } f_main "$@"