install.sh

12.4 KB · raw

1#!/usr/bin/env sh2# nina install script3# Run:  curl -fsSL kepr.uk/nina/install.sh | sh4# Or:   NINA_INSTALL_DIR=/usr/local/bin sh install.sh56set -e78FLAKE_URL="https://kepr.uk/nina/archive/HEAD.tar.gz"9RELEASES_URL="https://kepr.uk/api/nina/releases/latest"10INSTALL_DIR="${NINA_INSTALL_DIR:-$HOME/.local/bin}"11MIN_ZIG_MINOR=161213# Color, but only when stdout is a real terminal and NO_COLOR is unset. Under14# `curl … | sh` stdin is the pipe yet stdout is still the tty, so -t 1 holds.15# Palette mirrors nina's own: pink prefix, cyan for paths/commands.16if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then17  PINK="$(printf '\033[38;2;255;183;197m')"18  CYAN="$(printf '\033[36m')"19  RED="$(printf '\033[31m')"20  DIM="$(printf '\033[2m')"21  RESET="$(printf '\033[0m')"22else23  PINK=""; CYAN=""; RED=""; DIM=""; RESET=""24fi2526os="$(uname -s)"27arch="$(uname -m)"28case "${os}-${arch}" in29  Linux-x86_64)               platform="linux-x86_64"  ;;30  Linux-aarch64|Linux-arm64)  platform="linux-aarch64" ;;31  Darwin-arm64)               platform="macos-aarch64" ;;32  Darwin-x86_64)              platform="macos-x86_64"  ;;33  *)34    printf 'nina: unsupported platform %s-%s\n' "$os" "$arch" >&235    exit 1 ;;36esac3738have()  { command -v "$1" >/dev/null 2>&1; }39say()   { printf '%snina:%s %s\n' "$PINK" "$RESET" "$1"; }40die()   { printf '%snina:%s %serror:%s %s\n' "$PINK" "$RESET" "$RED" "$RESET" "$1" >&2; exit 1; }41blank() { printf '\n'; }42is_nixos() { [ -e /etc/NIXOS ]; }4344fetch_text() {45  if have curl;   then curl -fsSL "$1"46  elif have wget; then wget -qO- "$1"47  else die "curl or wget required"; fi48}4950fetch_file() {51  if have curl;   then curl -fsSL "$1" -o "$2"52  elif have wget; then wget -qO  "$2" "$1"53  else die "curl or wget required"; fi54}5556# ── banner ────────────────────────────────────────────────────────────────────5758banner() {59  cat <<'BANNER'6061   _ __   _  _ __    __ _62  | '_ \ (_)| '_ \  / _` |63  | | | || || | | || (_| |64  |_| |_||_||_| |_| \__,_|6566  NixOS Intuitive Navigation Assistant  ·  kepr.uk/nina6768BANNER69}7071# ── fetch source archive ──────────────────────────────────────────────────────7273fetch_source() {74  # This function's stdout IS the source path (captured via command75  # substitution), so every diagnostic must go to stderr — otherwise the76  # caller's `cd "$srcdir"` receives the progress text too and fails.77  srcdir="$(mktemp -d /tmp/nina-src.XXXXXX)"78  say "fetching source from Kepr..." >&279  fetch_file "$FLAKE_URL" "${srcdir}/nina.tar.gz" \80    || die "could not download source archive — check your connection"81  tar -xzf "${srcdir}/nina.tar.gz" -C "$srcdir" --strip-components=1 \82    || die "could not extract source archive"83  printf '%s' "$srcdir"84}8586# ── binary install from Kepr ─────────────────────────────────────────────────8788install_binary() {89  say "fetching release metadata..."90  meta="$(fetch_text "$RELEASES_URL" 2>/dev/null)" || return 191  [ -n "$meta" ] || return 19293  if have python3; then94    version="$(printf '%s' "$meta" | python3 -c "95import sys, json96d = json.load(sys.stdin)97print(d.get('version') or '')98" 2>/dev/null)"99    dl_url="$(printf '%s' "$meta" | python3 -c "100import sys, json101d = json.load(sys.stdin)102print((d.get('platforms') or {}).get('${platform}', {}).get('url') or '')103" 2>/dev/null)"104    cksum="$(printf '%s' "$meta" | python3 -c "105import sys, json106d = json.load(sys.stdin)107p = (d.get('platforms') or {}).get('${platform}', {})108raw = p.get('blake3') or p.get('checksum') or ''109print(raw[len('blake3:'):] if raw.startswith('blake3:') else raw)110" 2>/dev/null)"111  elif have jq; then112    version="$(printf '%s' "$meta" | jq -r '.version // empty')"113    dl_url="$(printf '%s' "$meta" | jq -r ".platforms.\"${platform}\".url // empty")"114    cksum_raw="$(printf '%s' "$meta" | jq -r ".platforms.\"${platform}\".blake3 // .platforms.\"${platform}\".checksum // empty")"115    cksum="${cksum_raw#blake3:}"116  else117    version="$(printf '%s' "$meta" | grep -o '"version":"[^"]*"' | sed 's/"version":"//;s/"//')"118    plat_block="$(printf '%s' "$meta" | grep -o "\"${platform}\":{[^}]*}" | head -1)"119    dl_url="$(printf '%s' "$plat_block" | grep -o '"url":"[^"]*"' | sed 's/"url":"//;s/"//')"120    cksum=""121  fi122123  [ -n "$dl_url" ] || return 1124  [ -n "$version" ] || version="latest"125126  say "downloading nina ${version} for ${platform}..."127  tmpfile="$(mktemp /tmp/nina.XXXXXX)"128  fetch_file "$dl_url" "$tmpfile" || { rm -f "$tmpfile"; return 1; }129130  if [ -n "$cksum" ] && have b3sum; then131    actual="$(b3sum --no-names "$tmpfile" 2>/dev/null | awk '{print $1}')"132    if [ "$actual" != "$cksum" ]; then133      say "checksum mismatch — aborting"134      rm -f "$tmpfile"135      return 1136    fi137    say "checksum verified"138  fi139140  chmod +x "$tmpfile"141  mkdir -p "$INSTALL_DIR"142  mv "$tmpfile" "${INSTALL_DIR}/nina"143  say "installed nina ${version} → ${INSTALL_DIR}/nina"144  return 0145}146147# ── NixOS: nix profile add (primary) ─────────────────────────────────────────148149nixos_profile_install() {150  have nix || return 1151  say "installing nina to ~/.nix-profile/bin/nina via nix profile..."152  blank153  # Tarball flakes are fetched read-only into the store, so nix cannot persist154  # a freshly-resolved flake.lock and aborts with "cannot write modified lock155  # file". --no-write-lock-file resolves inputs in memory without writing.156  # `add` is the current verb; `install` is a deprecated alias that prints a157  # warning on modern nix. Mirrors exec.zig profileInstall.158  if nix profile add --no-write-lock-file "${FLAKE_URL}#nina"; then159    return 0160  fi161  blank162  say "nix profile add failed. Common causes:"163  say "  flakes not enabled — add to /etc/nix/nix.conf or ~/.config/nix/nix.conf:"164  say "    experimental-features = nix-command flakes"165  say "  once nina is installed: nina setup enables this automatically"166  blank167  return 1168}169170# ── NixOS: nix-shell source build (fallback) ──────────────────────────────────171172nixos_shell_build() {173  have nix-shell || return 1174  say "fetching source and building nina using nix-shell..."175  blank176  srcdir="$(fetch_source)"177178  # Try the user's channel first; fall back to a pinned nixpkgs-unstable if179  # zig_0_16 is not yet in the channel (common on NixOS 24.11 stable).180  build_cmd="cd '${srcdir}' && zig build -Doptimize=ReleaseSafe"181  if nix-shell -p zig_0_16 --run "$build_cmd" 2>/dev/null; then182    :183  else184    say "zig_0_16 not in channel — fetching from nixpkgs-unstable..."185    # channels.nixos.org redirects to the current unstable release tarball;186    # the older releases.nixos.org/.../nixos-unstable/ path 404s.187    nix-shell \188      -I nixpkgs=https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz \189      -p zig_0_16 \190      --run "$build_cmd" \191      || die "build failed — see output above"192  fi193194  mkdir -p "$INSTALL_DIR"195  cp "${srcdir}/zig-out/bin/nina" "${INSTALL_DIR}/nina"196  chmod +x "${INSTALL_DIR}/nina"197  blank198  say "installed from source → ${INSTALL_DIR}/nina"199  say "note: per-user binary, not nix-managed (nina goodbye removes it cleanly)"200}201202# ── Zig version check ─────────────────────────────────────────────────────────203204check_zig() {205  if ! have zig; then206    blank207    say "Zig not found. Building nina requires Zig 0.${MIN_ZIG_MINOR}+."208    blank209    say "  Download: https://ziglang.org/download/"210    if is_nixos; then211      say "  NixOS:    nix-shell -p zig_0_16 --run 'sh install.sh'"212    fi213    blank214    die "Zig 0.${MIN_ZIG_MINOR}+ required"215  fi216217  zig_ver="$(zig version 2>/dev/null)"218  major="$(printf '%s' "$zig_ver" | cut -d. -f1)"219  minor="$(printf '%s' "$zig_ver" | cut -d. -f2)"220  ok=0221  [ "$major" -ge 1 ] 2>/dev/null && ok=1222  [ "$major" -eq 0 ] 2>/dev/null && [ "$minor" -ge "$MIN_ZIG_MINOR" ] 2>/dev/null && ok=1223224  if [ "$ok" -ne 1 ]; then225    blank226    say "found zig ${zig_ver}, but nina requires Zig 0.${MIN_ZIG_MINOR}+."227    if is_nixos; then228      say "  run: nix-shell -p zig_0_16 --run 'sh install.sh'"229    else230      say "  update: https://ziglang.org/download/"231    fi232    blank233    die "Zig 0.${MIN_ZIG_MINOR}+ required; found ${zig_ver}"234  fi235236  say "zig ${zig_ver} found"237}238239# ── generic source build (non-NixOS) ─────────────────────────────────────────240241build_from_source() {242  say "building from source..."243  check_zig244245  if [ -f "build.zig" ] && [ -f "src/main.zig" ]; then246    srcdir="."247  else248    srcdir="$(fetch_source)"249  fi250251  (cd "$srcdir" && zig build -Doptimize=ReleaseSafe) \252    || die "build failed — see output above"253254  mkdir -p "$INSTALL_DIR"255  cp "${srcdir}/zig-out/bin/nina" "${INSTALL_DIR}/nina"256  chmod +x "${INSTALL_DIR}/nina"257  say "installed from source → ${INSTALL_DIR}/nina"258}259260# ── PATH reminder ─────────────────────────────────────────────────────────────261262check_path() {263  case ":${PATH}:" in264    *":${INSTALL_DIR}:"*) ;;265    *)266      blank267      say "${INSTALL_DIR} is not in your PATH. Add it:"268      say "  export PATH=\"\${PATH}:${INSTALL_DIR}\""269      say "  echo 'export PATH=\"\${PATH}:${INSTALL_DIR}\"' >> ~/.bashrc"270      if is_nixos; then271        say "  NixOS: add to environment.sessionVariables in configuration.nix"272      fi273      ;;274  esac275}276277# ── enable nix experimental features ──────────────────────────────────────────278# nina needs nix-command + flakes to search and install. If they're off, offer to279# turn them on via the freshly-installed `nina setup`. Read from /dev/tty so the280# prompt works even under `curl … | sh`, where stdin is the script itself.281282maybe_setup_features() {283  have nix || return 0284  # `nix eval` itself requires nix-command, so this doubles as the feature probe.285  nix eval --expr '1' >/dev/null 2>&1 && return 0286287  NINA="${INSTALL_DIR}/nina"288  [ -x "$NINA" ] || NINA="nina"289290  blank291  say "nina needs nix's experimental features (nix-command and flakes) to"292  say "search and install packages."293294  if [ -r /dev/tty ]; then295    printf 'nina: enable them now? [Y/n] ' > /dev/tty296    IFS= read -r ans < /dev/tty 2>/dev/null || ans="y"297    case "$ans" in298      [Nn]*) say "skipped — enable later with:  nina setup"; return 0 ;;299    esac300    "$NINA" setup || say "automatic setup failed — run:  nina setup"301  else302    say "run  nina setup  to enable them"303  fi304}305306# ── closing message ──────────────────────────────────────────────────────────307308farewell() {309  blank310  printf '  %sthank you!!%s get started by running %snina hello%s\n' "$PINK" "$RESET" "$CYAN" "$RESET"311  printf '  run %snina update%s occasionally to keep me in tip-top shape\n' "$CYAN" "$RESET"312  blank313  printf '  %snot for you?%s %snina goodbye%s %sremoves her cleanly%s\n' "$DIM" "$RESET" "$CYAN" "$RESET" "$DIM" "$RESET"314  blank315}316317# ── main ──────────────────────────────────────────────────────────────────────318319banner320321if is_nixos; then322  say "NixOS detected"323  if nixos_profile_install; then324    maybe_setup_features325    farewell326    exit 0327  fi328  # nina's binaries link no libc, so the Linux builds are fully static and run329  # on NixOS — try the prebuilt before falling back to a from-source build.330  say "trying prebuilt binary from Kepr..."331  if install_binary; then332    check_path333    maybe_setup_features334    farewell335    exit 0336  fi337  say "no prebuilt binary available — building from source"338  nixos_shell_build339  check_path340  maybe_setup_features341  farewell342  exit 0343fi344345# macOS / generic Linux: download binary, fall back to source build346if install_binary; then347  check_path348  maybe_setup_features349  farewell350  exit 0351fi352353say "no pre-built binary for ${platform} — falling back to source build"354build_from_source355check_path356maybe_setup_features357farewell