123456set -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}42434445banner() {46 cat <<'BANNER'4748 _ __ _ _ __ __ _49 | '_ \ (_)| '_ \ / _` |50 | | | || || | | || (_| |51 |_| |_||_||_| |_| \__,_|5253 NixOS Intuitive Navigation Assistant · kepr.uk/nina5455BANNER56}57585960fetch_source() {61 62 63 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}72737475install_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}133134135136nixos_profile_install() {137 have nix || return 1138 say "installing nina to ~/.nix-profile/bin/nina via nix profile..."139 blank140 141 142 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}154155156157nixos_shell_build() {158 have nix-shell || return 1159 say "fetching source and building nina using nix-shell..."160 blank161 srcdir="$(fetch_source)"162163 164 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 171 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}187188189190generate_nixos_module() {191 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}222223224225check_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}259260261262build_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}280281282283check_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}297298299300301302303maybe_setup_features() {304 have nix || return 0305 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}326327328329farewell() {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}337338339340banner341342if is_nixos; then343 say "NixOS detected"344 if nixos_profile_install; then345 generate_nixos_module346 maybe_setup_features347 farewell348 exit 0349 fi350 351 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 0366fi367368369if 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