unos-repository

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

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"