NumericalOS

boot/bootstrap.sh

back to source

#!/bin/sh
# NumericalOS bootstrap - POSIX floor.
# Resolves architecture, selects and verifies an init artifact, execs it.
# Fail-closed: every abnormal path halts with a named reason.
set -eu

NUMOS_BASE="${NUMOS_BASE:-https://numericalos.com}"
NUMOS_LIB="${NUMOS_LIB:-$(dirname "$0")/lib}"

. "$NUMOS_LIB/arch_table.sh"

numos_die() {
  echo "numos: HALT: $*" >&2
  exit 1
}

numos_uname_m() {
  if [ -n "${NUMOS_FAKE_UNAME_M:-}" ]; then
    echo "$NUMOS_FAKE_UNAME_M"
  else
    uname -m
  fi
}

numos_detect_arch() {
  raw="$(numos_uname_m)"
  canonical="$(numos_canonical_arch "$raw")" ||
    numos_die "unsupported architecture: $raw"
  echo "$canonical"
}

numos_select_artifact() {
  arch="$1"
  if numos_arch_has_static "$arch"; then
    echo "static numinit-$arch"
  else
    echo "fallback busybox-static-$arch"
  fi
}

numos_sha256() {
  [ -f "$1" ] || numos_die "file not found: $1"
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | cut -d' ' -f1
  elif command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$1" | cut -d' ' -f1
  else
    numos_die "no sha256 implementation available"
  fi
}

numos_verify_sha256() {
  file="$1"
  want="$2"
  got="$(numos_sha256 "$file")"
  [ "$got" = "$want" ] ||
    numos_die "hash mismatch for $file: expected $want got $got"
}

# The C record carries the sha256 of every other line. Recompute and compare.
# The body goes through a real temp file rather than /dev/stdin, because
# numos_sha256 requires a regular file and /dev/stdin is not one everywhere.
#
# The whole remainder of the C line is the hash, not just its first
# whitespace-delimited field: taking `cut -f2` would let `C <valid-hash>
# ANYTHING` verify here while numos.state.verify() rejects it, and the tail
# is covered by no hash, no length check and no signature. Same definition
# on both sides, and it must be exactly 64 lowercase hex characters --
# numos/state.py::verify applies the identical rule.
numos_verify_state() {
  file="$1"
  [ -f "$file" ] || numos_die "state not found: $file"
  c_count="$(grep -c '^C ' "$file")" || c_count=0
  [ "$c_count" = "1" ] ||
    numos_die "state has $c_count C records, expected exactly 1: $file"
  c_line="$(grep '^C ' "$file" | head -n 1)"
  want="${c_line#C }"
  [ "${#want}" = "64" ] ||
    numos_die "state C record is not a 64-character sha256: $file"
  case "$want" in
    *[!0-9a-f]*)
      numos_die "state C record is not lowercase hex: $file" ;;
  esac
  tmp="${TMPDIR:-/tmp}/numos-verify.$$"
  trap 'rm -f "$tmp"' EXIT
  grep -v '^C ' "$file" > "$tmp"
  got="$(numos_sha256 "$tmp")"
  trap - EXIT
  rm -f "$tmp"
  [ "$got" = "$want" ] ||
    numos_die "state hash mismatch for $file: expected $want got $got"
}

numos_main() {
  arch="$(numos_detect_arch)"
  echo "numos: arch $arch"
  echo "numos: artifact $(numos_select_artifact "$arch")"
  if [ -n "${NUMOS_STATE:-}" ]; then
    numos_verify_state "$NUMOS_STATE"
    echo "numos: state verified"
  fi
}

if [ "${NUMOS_SOURCE_ONLY:-0}" != "1" ]; then
  numos_main "$@"
fi