install.sh

13.1 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=161213os="$(uname -s)"14arch="$(uname -m)"15case "${os}-${arch}" in16  Linux-x86_64)               platform="linux-x86_64"  ;;17  Linux-aarch64|Linux-arm64)  platform="linux-aarch64" ;;18  Darwin-arm64)               platform="macos-aarch64" ;;19  Darwin-x86_64)              platform="macos-x86_64"  ;;20  *)21    printf 'nina: unsupported platform %s-%s\n' "$os" "$arch" >&222    exit 1 ;;23esac2425have()  { command -v "$1" >/dev/null 2>&1; }26say()   { printf 'nina: %s\n' "$1"; }27die()   { say "error: $1" >&2; exit 1; }28blank() { printf '\n'; }29is_nixos() { [ -e /etc/NIXOS ]; }3031fetch_text() {32  if have curl;   then curl -fsSL "$1"33  elif have wget; then wget -qO- "$1"34  else die "curl or wget required"; fi35}3637fetch_file() {38  if have curl;   then curl -fsSL "$1" -o "$2"39  elif have wget; then wget -qO  "$2" "$1"40  else die "curl or wget required"; fi41}4243# ── banner ────────────────────────────────────────────────────────────────────4445banner() {46  cat <<'BANNER'4748   _ __   _  _ __    __ _49  | '_ \ (_)| '_ \  / _` |50  | | | || || | | || (_| |51  |_| |_||_||_| |_| \__,_|5253  NixOS Intuitive Navigation Assistant  ·  kepr.uk/nina5455BANNER56}5758# ── fetch source archive ──────────────────────────────────────────────────────5960fetch_source() {61  # This function's stdout IS the source path (captured via command62  # substitution), so every diagnostic must go to stderr — otherwise the63  # caller's `cd "$srcdir"` receives the progress text too and fails.64  srcdir="$(mktemp -d /tmp/nina-src.XXXXXX)"65  say "fetching source from Kepr..." >&266  fetch_file "$FLAKE_URL" "${srcdir}/nina.tar.gz" \67    || die "could not download source archive — check your connection"68  tar -xzf "${srcdir}/nina.tar.gz" -C "$srcdir" --strip-components=1 \69    || die "could not extract source archive"70  printf '%s' "$srcdir"71}7273# ── binary install from Kepr ─────────────────────────────────────────────────7475install_binary() {76  say "fetching release metadata..."77  meta="$(fetch_text "$RELEASES_URL" 2>/dev/null)" || return 178  [ -n "$meta" ] || return 17980  if have python3; then81    version="$(printf '%s' "$meta" | python3 -c "82import sys, json83d = json.load(sys.stdin)84print(d.get('version') or '')85" 2>/dev/null)"86    dl_url="$(printf '%s' "$meta" | python3 -c "87import sys, json88d = json.load(sys.stdin)89print((d.get('platforms') or {}).get('${platform}', {}).get('url') or '')90" 2>/dev/null)"91    cksum="$(printf '%s' "$meta" | python3 -c "92import sys, json93d = json.load(sys.stdin)94p = (d.get('platforms') or {}).get('${platform}', {})95raw = p.get('blake3') or p.get('checksum') or ''96print(raw[len('blake3:'):] if raw.startswith('blake3:') else raw)97" 2>/dev/null)"98  elif have jq; then99    version="$(printf '%s' "$meta" | jq -r '.version // empty')"100    dl_url="$(printf '%s' "$meta" | jq -r ".platforms.\"${platform}\".url // empty")"101    cksum_raw="$(printf '%s' "$meta" | jq -r ".platforms.\"${platform}\".blake3 // .platforms.\"${platform}\".checksum // empty")"102    cksum="${cksum_raw#blake3:}"103  else104    version="$(printf '%s' "$meta" | grep -o '"version":"[^"]*"' | sed 's/"version":"//;s/"//')"105    plat_block="$(printf '%s' "$meta" | grep -o "\"${platform}\":{[^}]*}" | head -1)"106    dl_url="$(printf '%s' "$plat_block" | grep -o '"url":"[^"]*"' | sed 's/"url":"//;s/"//')"107    cksum=""108  fi109110  [ -n "$dl_url" ] || return 1111  [ -n "$version" ] || version="latest"112113  say "downloading nina ${version} for ${platform}..."114  tmpfile="$(mktemp /tmp/nina.XXXXXX)"115  fetch_file "$dl_url" "$tmpfile" || { rm -f "$tmpfile"; return 1; }116117  if [ -n "$cksum" ] && have b3sum; then118    actual="$(b3sum --no-names "$tmpfile" 2>/dev/null | awk '{print $1}')"119    if [ "$actual" != "$cksum" ]; then120      say "checksum mismatch — aborting"121      rm -f "$tmpfile"122      return 1123    fi124    say "checksum verified"125  fi126127  chmod +x "$tmpfile"128  mkdir -p "$INSTALL_DIR"129  mv "$tmpfile" "${INSTALL_DIR}/nina"130  say "installed nina ${version} → ${INSTALL_DIR}/nina"131  return 0132}133134# ── NixOS: nix profile install (primary) ─────────────────────────────────────135136nixos_profile_install() {137  have nix || return 1138  say "installing nina to ~/.nix-profile/bin/nina via nix profile..."139  blank140  # Tarball flakes are fetched read-only into the store, so nix cannot persist141  # a freshly-resolved flake.lock and aborts with "cannot write modified lock142  # file". --no-write-lock-file resolves inputs in memory without writing.143  if nix profile install --no-write-lock-file "${FLAKE_URL}#nina"; then144    return 0145  fi146  blank147  say "nix profile install failed. Common causes:"148  say "  flakes not enabled — add to /etc/nix/nix.conf or ~/.config/nix/nix.conf:"149  say "    experimental-features = nix-command flakes"150  say "  once nina is installed: nina setup enables this automatically"151  blank152  return 1153}154155# ── NixOS: nix-shell source build (fallback) ──────────────────────────────────156157nixos_shell_build() {158  have nix-shell || return 1159  say "fetching source and building nina using nix-shell..."160  blank161  srcdir="$(fetch_source)"162163  # Try the user's channel first; fall back to a pinned nixpkgs-unstable if164  # zig_0_16 is not yet in the channel (common on NixOS 24.11 stable).165  build_cmd="cd '${srcdir}' && zig build -Doptimize=ReleaseSafe"166  if nix-shell -p zig_0_16 --run "$build_cmd" 2>/dev/null; then167    :168  else169    say "zig_0_16 not in channel — fetching from nixpkgs-unstable..."170    # channels.nixos.org redirects to the current unstable release tarball;171    # the older releases.nixos.org/.../nixos-unstable/ path 404s.172    nix-shell \173      -I nixpkgs=https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz \174      -p zig_0_16 \175      --run "$build_cmd" \176      || die "build failed — see output above"177  fi178179  mkdir -p "$INSTALL_DIR"180  cp "${srcdir}/zig-out/bin/nina" "${INSTALL_DIR}/nina"181  chmod +x "${INSTALL_DIR}/nina"182  blank183  say "installed from source → ${INSTALL_DIR}/nina"184  say "note: binary built outside nix — adopt ~/nina.nix into configuration.nix"185  say "      to make nina fully nix-managed (nina goodbye handles removal either way)"186}187188# ── generate ~/nina.nix ───────────────────────────────────────────────────────189190generate_nixos_module() {191  # Write ~/nina.nix without invoking shell substitution on the nix code inside.192  cat > "$HOME/nina.nix" <<'NIXEOF'193# ~/nina.nix — generated by the nina installer194#195# Declares nina as a NixOS system package.196#197# Add to /etc/nixos/configuration.nix:198#   imports = [ /home/YOUR_USER/nina.nix ];199# Then: sudo nixos-rebuild switch200#201# To update:  nina update  (or re-run the installer)202# To remove:  nina goodbye  (or delete this file, then nixos-rebuild switch)203#204# For flake-based configurations, add nina as a flake input instead:205#   inputs.nina.url = "https://kepr.uk/nina/archive/HEAD.tar.gz";206#   # in your nixosModules list: nina.nixosModules.default207#   # then set: programs.nina.enable = true;208{ ... }:209{210  imports = [211    "${builtins.fetchTarball "https://kepr.uk/nina/archive/HEAD.tar.gz"}/nixos-module.nix"212  ];213  programs.nina.enable = true;214}215NIXEOF216  blank217  say "generated ~/nina.nix for system-wide installation:"218  say "  1. add to /etc/nixos/configuration.nix:"219  say "       imports = [ /home/$(id -un)/nina.nix ];"220  say "  2. sudo nixos-rebuild switch"221}222223# ── Zig version check ─────────────────────────────────────────────────────────224225check_zig() {226  if ! have zig; then227    blank228    say "Zig not found. Building nina requires Zig 0.${MIN_ZIG_MINOR}+."229    blank230    say "  Download: https://ziglang.org/download/"231    if is_nixos; then232      say "  NixOS:    nix-shell -p zig_0_16 --run 'sh install.sh'"233    fi234    blank235    die "Zig 0.${MIN_ZIG_MINOR}+ required"236  fi237238  zig_ver="$(zig version 2>/dev/null)"239  major="$(printf '%s' "$zig_ver" | cut -d. -f1)"240  minor="$(printf '%s' "$zig_ver" | cut -d. -f2)"241  ok=0242  [ "$major" -ge 1 ] 2>/dev/null && ok=1243  [ "$major" -eq 0 ] 2>/dev/null && [ "$minor" -ge "$MIN_ZIG_MINOR" ] 2>/dev/null && ok=1244245  if [ "$ok" -ne 1 ]; then246    blank247    say "found zig ${zig_ver}, but nina requires Zig 0.${MIN_ZIG_MINOR}+."248    if is_nixos; then249      say "  run: nix-shell -p zig_0_16 --run 'sh install.sh'"250    else251      say "  update: https://ziglang.org/download/"252    fi253    blank254    die "Zig 0.${MIN_ZIG_MINOR}+ required; found ${zig_ver}"255  fi256257  say "zig ${zig_ver} found"258}259260# ── generic source build (non-NixOS) ─────────────────────────────────────────261262build_from_source() {263  say "building from source..."264  check_zig265266  if [ -f "build.zig" ] && [ -f "src/main.zig" ]; then267    srcdir="."268  else269    srcdir="$(fetch_source)"270  fi271272  (cd "$srcdir" && zig build -Doptimize=ReleaseSafe) \273    || die "build failed — see output above"274275  mkdir -p "$INSTALL_DIR"276  cp "${srcdir}/zig-out/bin/nina" "${INSTALL_DIR}/nina"277  chmod +x "${INSTALL_DIR}/nina"278  say "installed from source → ${INSTALL_DIR}/nina"279}280281# ── PATH reminder ─────────────────────────────────────────────────────────────282283check_path() {284  case ":${PATH}:" in285    *":${INSTALL_DIR}:"*) ;;286    *)287      blank288      say "${INSTALL_DIR} is not in your PATH. Add it:"289      say "  export PATH=\"\${PATH}:${INSTALL_DIR}\""290      say "  echo 'export PATH=\"\${PATH}:${INSTALL_DIR}\"' >> ~/.bashrc"291      if is_nixos; then292        say "  NixOS: add to environment.sessionVariables in configuration.nix"293      fi294      ;;295  esac296}297298# ── enable nix experimental features ──────────────────────────────────────────299# nina needs nix-command + flakes to search and install. If they're off, offer to300# turn them on via the freshly-installed `nina setup`. Read from /dev/tty so the301# prompt works even under `curl … | sh`, where stdin is the script itself.302303maybe_setup_features() {304  have nix || return 0305  # `nix eval` itself requires nix-command, so this doubles as the feature probe.306  nix eval --expr '1' >/dev/null 2>&1 && return 0307308  NINA="${INSTALL_DIR}/nina"309  [ -x "$NINA" ] || NINA="nina"310311  blank312  say "nina needs nix's experimental features (nix-command and flakes) to"313  say "search and install packages."314315  if [ -r /dev/tty ]; then316    printf 'nina: enable them now? [Y/n] ' > /dev/tty317    IFS= read -r ans < /dev/tty 2>/dev/null || ans="y"318    case "$ans" in319      [Nn]*) say "skipped — enable later with:  nina setup"; return 0 ;;320    esac321    "$NINA" setup || say "automatic setup failed — run:  nina setup"322  else323    say "run  nina setup  to enable them"324  fi325}326327# ── closing message ──────────────────────────────────────────────────────────328329farewell() {330  printf '\n'331  printf '  thank you!! running nina update occasionally will keep me in tip top shape.\n'332  printf '  get started by running nina hello\n'333  printf '\n'334  printf '  if nina turns out not to be for you, nina goodbye will remove her cleanly.\n'335  printf '\n'336}337338# ── main ──────────────────────────────────────────────────────────────────────339340banner341342if is_nixos; then343  say "NixOS detected"344  if nixos_profile_install; then345    generate_nixos_module346    maybe_setup_features347    farewell348    exit 0349  fi350  # nina's binaries link no libc, so the Linux builds are fully static and run351  # on NixOS — try the prebuilt before falling back to a from-source build.352  say "trying prebuilt binary from Kepr..."353  if install_binary; then354    check_path355    maybe_setup_features356    farewell357    exit 0358  fi359  say "no prebuilt binary available — building from source"360  nixos_shell_build361  generate_nixos_module362  check_path363  maybe_setup_features364  farewell365  exit 0366fi367368# macOS / generic Linux: download binary, fall back to source build369if install_binary; then370  check_path371  maybe_setup_features372  farewell373  exit 0374fi375376say "no pre-built binary for ${platform} — falling back to source build"377build_from_source378check_path379maybe_setup_features380farewell