unos-repository

APK repository for unos
git clone git://git.finwo.net/misc/unos-repository
Log | Files | Refs | README

bootstrap-host.sh (8218B)


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