NumericalOS

site/boot.sh

back to source

#!/bin/sh
# SPDX-License-Identifier: MIT
# NumericalOS installer - the existing-Linux-host vector.
#
#   curl -fsSL https://numericalos.com/boot | sh
#
# Installs the boot chain into a prefix and stops. It does NOT replace PID 1:
# silently taking over init on a running machine is destructive, and a piped
# shell script is the worst possible place to do it from. Taking PID 1 is a
# separate, explicit act - see --take-pid1 below, which prints instructions
# rather than performing surgery.
#
# Self-contained by necessity: this arrives over a pipe with no repository
# checkout to source anything from.
set -eu

NUMOS_BASE="${NUMOS_BASE:-https://numericalos.com}"
NUMOS_PREFIX="${NUMOS_PREFIX:-/opt/numericalos}"
NUMOS_DRY_RUN="${NUMOS_DRY_RUN:-0}"
TAKE_PID1=0

for arg in "$@"; do
  case "$arg" in
    --prefix=*)   NUMOS_PREFIX="${arg#--prefix=}" ;;
    --dry-run)    NUMOS_DRY_RUN=1 ;;
    --take-pid1)  TAKE_PID1=1 ;;
    --help|-h)
      echo "usage: boot [--prefix=DIR] [--dry-run] [--take-pid1]"
      exit 0 ;;
    *) echo "numos: HALT: unknown option: $arg" >&2; exit 1 ;;
  esac
done

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

[ "$(uname -s)" = "Linux" ] ||
  die "this installer targets Linux; found $(uname -s)"

have() { command -v "$1" >/dev/null 2>&1; }

have sha256sum || have shasum || die "need sha256sum or shasum"
have curl || have wget || die "need curl or wget"

fetch() {
  if have curl; then
    curl -fsSL --max-time 60 -o "$2" "$1" || die "fetch failed: $1"
  else
    wget -q -O "$2" "$1" || die "fetch failed: $1"
  fi
}

sum() {
  if have sha256sum; then sha256sum "$1" | cut -d' ' -f1
  else shasum -a 256 "$1" | cut -d' ' -f1
  fi
}

say "arch     $(uname -m)"
say "prefix   $NUMOS_PREFIX"
say "source   $NUMOS_BASE"

if [ "$NUMOS_DRY_RUN" = "1" ]; then
  say "dry run: would install bootstrap.sh, numinit.sh, arch_table.sh, numctl, numos.state"
  say "dry run: PID 1 would NOT be replaced"
  exit 0
fi

mkdir -p "$NUMOS_PREFIX/lib" || die "cannot create $NUMOS_PREFIX"

fetch "$NUMOS_BASE/artifacts/manifest.txt" "$NUMOS_PREFIX/manifest.txt"

# Every file is verified against the manifest before it is kept. A file that
# does not match is deleted rather than left where the next run would find it
# and treat it as a good local copy.
install_verified() {
  name="$1"
  dest="$2"
  want="$(grep "^F $name " "$NUMOS_PREFIX/manifest.txt" | head -n 1 | cut -d' ' -f3)"
  [ -n "$want" ] || die "manifest has no entry for $name"
  fetch "$NUMOS_BASE/artifacts/$name" "$dest"
  got="$(sum "$dest")"
  if [ "$got" != "$want" ]; then
    rm -f "$dest"
    die "hash mismatch for $name: expected $want got $got"
  fi
  say "verified $name"
}

install_verified bootstrap.sh  "$NUMOS_PREFIX/bootstrap.sh"
install_verified numinit.sh    "$NUMOS_PREFIX/numinit.sh"
install_verified arch_table.sh "$NUMOS_PREFIX/lib/arch_table.sh"
install_verified numctl        "$NUMOS_PREFIX/numctl"
install_verified numos.state   "$NUMOS_PREFIX/numos.state"

chmod +x "$NUMOS_PREFIX/bootstrap.sh" "$NUMOS_PREFIX/numinit.sh" "$NUMOS_PREFIX/numctl"

say "installed to $NUMOS_PREFIX"
say ""
say "run the phase walk without touching this machine's init:"
say "  PATH=$NUMOS_PREFIX:\$PATH \\"
say "  NUMOS_STATE=$NUMOS_PREFIX/numos.state \\"
say "  NUMOS_LIB=$NUMOS_PREFIX/lib \\"
say "  NUMOS_PREFIX=$NUMOS_PREFIX \\"
say "  sh $NUMOS_PREFIX/bootstrap.sh"

if [ "$TAKE_PID1" = "1" ]; then
  say ""
  say "--take-pid1 requested. This installer will NOT do it."
  say ""
  say "Replacing PID 1 on a running machine is not something to do from a"
  say "piped shell script. It is done by pointing a bootloader at an"
  say "initramfs whose /init is bootstrap.sh - see:"
  say "  $NUMOS_BASE/skills/numericalos-build-initramfs/SKILL.md"
  say "  $NUMOS_BASE/skills/numericalos-build-iso-cf/SKILL.md  # no-WSL Cloudflare path"
  say ""
  say "Nothing about this machine's init has been changed."
fi