bootstrap-host.sh (8821B)
1 #!/bin/sh 2 # mk/bootstrap-host.sh - build the host bootstrap tools into build/host/. 3 # 4 # ./mk/bootstrap-host.sh # build what is missing 5 # ./mk/bootstrap-host.sh --force # rebuild everything 6 # ./mk/bootstrap-host.sh --check # verify only, build nothing (CI gate) 7 # 8 # Produces: 9 # build/host/bin/muon pinned muon, bootstrapped from C (no Python) 10 # build/host/bin/apk dynamic apk v3, used by repo-index.sh 11 12 # 13 # This file is duplicated in the unos repo, which needs the same pinned apk to 14 # assemble a rootfs. The alternatives -- a submodule, or fetching a bootstrap 15 # script over the network in order to bootstrap -- are both worse than two 16 # copies of a file that only changes when a pin changes. 17 # 18 # WHY THIS EXISTS 19 # --------------- 20 # These binaries are the input to every other build: mk/build.sh puts 21 # build/host/bin on PATH and repo-index.sh generates APKINDEX with 22 # `apk index`. Until now they were built by hand 23 # following prose in mk/README.md, which meant a fresh checkout or a CI 24 # container could not reproduce the toolchain at all. This script is that 25 # prose, executable. 26 # 27 # Python is banned from our tooling, so apk-tools is configured with muon 28 # rather than meson; upstream supports this. muon itself bootstraps from a 29 # single amalgamated C file, so the chain terminates at a C compiler. 30 set -eu 31 32 HERE=$(cd "$(dirname "$0")" && pwd) 33 ROOT=$(cd "${HERE}/.." && pwd) 34 # UNOS_HOSTDIR builds into the builder image; UNOS_HOST_CACHE seeds from it. 35 HOSTDIR="${UNOS_HOSTDIR:-${ROOT}/build/host}" 36 HOST_CACHE="${UNOS_HOST_CACHE:-/opt/unos-host}" 37 SRCDEST="${ROOT}/build/work/sources" 38 WORK="${ROOT}/build/work/hostbootstrap" 39 40 # --- pins (keep in sync with mk/README.md "Host apk-tools v3") ------------- 41 MUON_VERSION=0.6.0 42 MUON_URL="https://github.com/muon-build/muon/archive/refs/tags/${MUON_VERSION}.tar.gz" 43 MUON_TARBALL="muon-${MUON_VERSION}.tar.gz" 44 MUON_SHA256=5300e58c4b4d43e3026856004c79d746075aaa9d9e66d76ba9f32ce249495b81 45 46 APK_VERSION=3.0.8 47 APK_URL="https://gitlab.alpinelinux.org/alpine/apk-tools/-/archive/v${APK_VERSION}/apk-tools-v${APK_VERSION}.tar.gz" 48 APK_TARBALL="apk-tools-v${APK_VERSION}.tar.gz" 49 APK_SHA256=e81c64a6e7c3806d45d4622c253e757aee970be84048554fa0eed79310e9453e 50 51 FORCE=0 52 CHECK=0 53 for a in "$@"; do 54 case "$a" in 55 --force) FORCE=1 ;; 56 --check) CHECK=1 ;; 57 -h|--help) sed -n '2,12p' "$0"; exit 0 ;; 58 *) echo "bootstrap-host.sh: unknown argument: $a" >&2; exit 1 ;; 59 esac 60 done 61 62 msg() { printf '==> %s\n' "$*"; } 63 die() { printf 'bootstrap-host.sh: error: %s\n' "$*" >&2; exit 1; } 64 65 # Run a build step quietly, but dump the whole log if it fails. Build noise is 66 # useless until something breaks, at which point you want all of it. 67 LOGFILE="${WORK}/bootstrap.log" 68 run() { 69 # run <description> <cmd...> 70 what="$1"; shift 71 if ! "$@" >>"${LOGFILE}" 2>&1; then 72 echo "bootstrap-host.sh: ${what} failed; last 40 lines:" >&2 73 tail -40 "${LOGFILE}" >&2 74 exit 1 75 fi 76 } 77 78 fetch() { 79 # fetch <url> <tarball> <sha256> 80 f="${SRCDEST}/$2" 81 mkdir -p "${SRCDEST}" 82 if [ ! -f "${f}" ]; then 83 msg "fetching $2" 84 curl -fL -o "${f}" "$1" || wget -O "${f}" "$1" || die "could not fetch $1" 85 fi 86 echo "$3 ${f}" | sha256sum -c - >/dev/null || die "checksum mismatch: $2" 87 } 88 89 # --- verification ---------------------------------------------------------- 90 # Version-checked, not just existence-checked: a stale binary from an older pin 91 # is worse than a missing one, because everything downstream still "works". 92 check_tools() { 93 rc=0 94 if [ -x "${HOSTDIR}/bin/muon" ]; then 95 v=$("${HOSTDIR}/bin/muon" version 2>/dev/null | head -1 || true) 96 case "$v" in 97 *"${MUON_VERSION}"*) msg "muon ok (${v})" ;; 98 *) echo " muon present but not ${MUON_VERSION}: ${v:-unknown}" >&2; rc=1 ;; 99 esac 100 else 101 echo " muon missing" >&2; rc=1 102 fi 103 104 if [ -x "${HOSTDIR}/bin/apk" ]; then 105 v=$("${HOSTDIR}/bin/apk" --version 2>/dev/null | head -1 || true) 106 case "$v" in 107 *"${APK_VERSION}"*) msg "apk ok (${v})" ;; 108 *) echo " apk present but not ${APK_VERSION}: ${v:-unknown}" >&2; rc=1 ;; 109 esac 110 else 111 echo " apk missing" >&2; rc=1 112 fi 113 return $rc 114 } 115 116 if [ "${CHECK}" = "1" ]; then 117 if check_tools; then 118 msg "host tools present and pinned" 119 exit 0 120 fi 121 die "host tools missing or stale; run ./mk/bootstrap-host.sh" 122 fi 123 124 if [ "${FORCE}" != "1" ] && check_tools 2>/dev/null; then 125 msg "host tools already present (use --force to rebuild)" 126 exit 0 127 fi 128 129 # Prebuilt in the builder image; copying beats recompiling muon and apk in 130 # every task. Version-checked after, so a stale cache still rebuilds. 131 if [ "${FORCE}" != "1" ] && [ "${HOST_CACHE}" != "${HOSTDIR}" ] \ 132 && [ -x "${HOST_CACHE}/bin/apk" ]; then 133 mkdir -p "${HOSTDIR}" 134 cp -a "${HOST_CACHE}/." "${HOSTDIR}/" 135 if check_tools 2>/dev/null; then 136 msg "host tools seeded from ${HOST_CACHE}" 137 exit 0 138 fi 139 msg "cache at ${HOST_CACHE} is stale, rebuilding" 140 fi 141 142 command -v cc >/dev/null 2>&1 || command -v gcc >/dev/null 2>&1 \ 143 || die "no C compiler on PATH" 144 145 mkdir -p "${HOSTDIR}/bin" "${WORK}" 146 rm -f "${LOGFILE}" 147 148 # --- muon ------------------------------------------------------------------ 149 # bootstrap.sh compiles src/amalgam.c with a plain c99 compiler, giving a 150 # muon capable of building the real muon. Pure C, no Python anywhere. 151 fetch "${MUON_URL}" "${MUON_TARBALL}" "${MUON_SHA256}" 152 153 msg "building muon ${MUON_VERSION}" 154 rm -rf "${WORK}/muon" 155 mkdir -p "${WORK}/muon" 156 tar -xzf "${SRCDEST}/${MUON_TARBALL}" -C "${WORK}/muon" 157 MUONSRC="${WORK}/muon/muon-${MUON_VERSION}" 158 [ -d "${MUONSRC}" ] || die "unexpected muon tarball layout" 159 160 # meson-docs and meson-tests are wrap subprojects that muon git-clones from 161 # GitHub during setup. They are documentation and upstream's own test corpus; 162 # we need neither, and an unpinned network fetch has no business in a bootstrap 163 # that is otherwise driven entirely by pinned, checksummed tarballs. tracy is a 164 # profiler. Disabling all three makes this step hermetic. 165 MUON_OPTS="-Dmeson-docs=disabled -Dmeson-tests=disabled -Dtracy=disabled -Dui=disabled" 166 167 cd "${MUONSRC}" 168 run "muon bootstrap" sh bootstrap.sh build-boot 169 [ -x build-boot/muon-bootstrap ] || die "muon bootstrap produced no binary" 170 171 # Build the real muon with the bootstrap one. Samurai is vendored in muon, so 172 # no ninja on the host is required either. 173 # shellcheck disable=SC2086 174 run "muon setup" ./build-boot/muon-bootstrap setup ${MUON_OPTS} build 175 run "muon build" ./build-boot/muon-bootstrap -C build samu 176 [ -x build/muon ] || die "muon self-build produced no binary" 177 install -m 0755 build/muon "${HOSTDIR}/bin/muon" 178 cd "${ROOT}" 179 msg "muon -> ${HOSTDIR}/bin/muon" 180 181 PATH="${HOSTDIR}/bin:${PATH}" 182 export PATH 183 184 # --- apk-tools ------------------------------------------------------------- 185 fetch "${APK_URL}" "${APK_TARBALL}" "${APK_SHA256}" 186 187 rm -rf "${WORK}/apk" 188 mkdir -p "${WORK}/apk" 189 tar -xzf "${SRCDEST}/${APK_TARBALL}" -C "${WORK}/apk" 190 APKSRC="${WORK}/apk/apk-tools-v${APK_VERSION}" 191 [ -d "${APKSRC}" ] || APKSRC="${WORK}/apk/$(ls -A "${WORK}/apk" | head -1)" 192 [ -d "${APKSRC}" ] || die "unexpected apk-tools tarball layout" 193 194 # lua/python/docs off: lua is a binding we do not use, python is banned, and 195 # docs need scdoc. help needs lua, so it goes too. 196 APK_OPTS="-Dlua=disabled -Dpython=disabled -Dhelp=disabled -Ddocs=disabled" 197 198 msg "building apk ${APK_VERSION} (dynamic)" 199 cd "${APKSRC}" 200 rm -rf build-dyn 201 # RUNPATH $ORIGIN/../lib so the binary finds any libs we later place in 202 # build/host/lib without needing LD_LIBRARY_PATH at every call site. 203 # shellcheck disable=SC2086 204 run "apk setup (dynamic)" muon setup \ 205 -Dprefix="${HOSTDIR}" \ 206 ${APK_OPTS} \ 207 -Dc_link_args="-Wl,-rpath,\$ORIGIN/../lib" \ 208 build-dyn 209 run "apk build (dynamic)" muon -C build-dyn samu 210 run "apk install (dynamic)" muon -C build-dyn install 211 [ -x "${HOSTDIR}/bin/apk" ] || die "dynamic apk not installed" 212 msg "apk -> ${HOSTDIR}/bin/apk" 213 214 # NOTE: we deliberately do NOT build an apk.static. 215 # 216 # There used to be one, justified in mk/README.md as "the future target binary 217 # - no libcrypto needed on the switch". That justification was already false: 218 # UNOS packages openssl, so libcrypto.so.3/libssl.so.3 are on the switch and 219 # the target apk links them dynamically like everything else. Its only real 220 # consumer was rootfs assembly, which works identically with the dynamic binary 221 # (verified: same 24 packages, same rootfs). 222 # 223 # Building it cost 10.6MB versus 2.0MB and, worse, required libcrypto.a, 224 # libssl.a, libz.a and libzstd.a on the build host -- static variants that many 225 # distributions and most CI containers do not ship at all. That is a hard 226 # bootstrap failure in exchange for nothing. 227 # 228 # If a statically linked apk is ever wanted for disaster recovery on a switch 229 # with a broken loader, it belongs in packages/ as a target artifact, not here. 230 231 check_tools || die "post-build verification failed" 232 msg "host bootstrap complete"