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 ]; }30313233out() { printf ' %s\n' "$1"; }34hr() { printf ' ────────────────────────────────────────────────\n'; }3536fetch_text() {37 if have curl; then curl -fsSL "$1"38 elif have wget; then wget -qO- "$1"39 else die "curl or wget required"; fi40}4142fetch_file() {43 if have curl; then curl -fsSL "$1" -o "$2"44 elif have wget; then wget -qO "$2" "$1"45 else die "curl or wget required"; fi46}47484950banner() {51 cat <<'BANNER'5253 _ __ _ _ __ __ _54 | '_ \ (_)| '_ \ / _` |55 | | | || || | | || (_| |56 |_| |_||_||_| |_| \__,_|5758 NixOS Intuitive Navigation Assistant · kepr.uk/nina5960BANNER61}62636465fetch_source() {66 67 68 69 srcdir="$(mktemp -d /tmp/nina-src.XXXXXX)"70 say "fetching source from Kepr..." >&271 fetch_file "$FLAKE_URL" "${srcdir}/nina.tar.gz" \72 || die "could not download source archive — check your connection"73 tar -xzf "${srcdir}/nina.tar.gz" -C "$srcdir" --strip-components=1 \74 || die "could not extract source archive"75 printf '%s' "$srcdir"76}77787980install_binary() {81 say "fetching release metadata..."82 meta="$(fetch_text "$RELEASES_URL" 2>/dev/null)" || return 183 [ -n "$meta" ] || return 18485 if have python3; then86 version="$(printf '%s' "$meta" | python3 -c "87import sys, json88d = json.load(sys.stdin)89print(d.get('version') or '')90" 2>/dev/null)"91 dl_url="$(printf '%s' "$meta" | python3 -c "92import sys, json93d = json.load(sys.stdin)94print((d.get('platforms') or {}).get('${platform}', {}).get('url') or '')95" 2>/dev/null)"96 cksum="$(printf '%s' "$meta" | python3 -c "97import sys, json98d = json.load(sys.stdin)99p = (d.get('platforms') or {}).get('${platform}', {})100raw = p.get('blake3') or p.get('checksum') or ''101print(raw[len('blake3:'):] if raw.startswith('blake3:') else raw)102" 2>/dev/null)"103 elif have jq; then104 version="$(printf '%s' "$meta" | jq -r '.version // empty')"105 dl_url="$(printf '%s' "$meta" | jq -r ".platforms.\"${platform}\".url // empty")"106 cksum_raw="$(printf '%s' "$meta" | jq -r ".platforms.\"${platform}\".blake3 // .platforms.\"${platform}\".checksum // empty")"107 cksum="${cksum_raw#blake3:}"108 else109 version="$(printf '%s' "$meta" | grep -o '"version":"[^"]*"' | sed 's/"version":"//;s/"//')"110 plat_block="$(printf '%s' "$meta" | grep -o "\"${platform}\":{[^}]*}" | head -1)"111 dl_url="$(printf '%s' "$plat_block" | grep -o '"url":"[^"]*"' | sed 's/"url":"//;s/"//')"112 cksum=""113 fi114115 [ -n "$dl_url" ] || return 1116 [ -n "$version" ] || version="latest"117118 say "downloading nina ${version} for ${platform}..."119 tmpfile="$(mktemp /tmp/nina.XXXXXX)"120 fetch_file "$dl_url" "$tmpfile" || { rm -f "$tmpfile"; return 1; }121122 if [ -n "$cksum" ] && have b3sum; then123 actual="$(b3sum --no-names "$tmpfile" 2>/dev/null | awk '{print $1}')"124 if [ "$actual" != "$cksum" ]; then125 say "checksum mismatch — aborting"126 rm -f "$tmpfile"127 return 1128 fi129 say "checksum verified"130 fi131132 chmod +x "$tmpfile"133 mkdir -p "$INSTALL_DIR"134 mv "$tmpfile" "${INSTALL_DIR}/nina"135 say "installed nina ${version} → ${INSTALL_DIR}/nina"136 return 0137}138139140141nixos_profile_install() {142 have nix || return 1143 say "installing nina to ~/.nix-profile/bin/nina via nix profile..."144 blank145 146 147 148 if nix profile install --no-write-lock-file "${FLAKE_URL}#nina"; then149 return 0150 fi151 blank152 say "nix profile install failed. Common causes:"153 say " flakes not enabled — add to /etc/nix/nix.conf or ~/.config/nix/nix.conf:"154 say " experimental-features = nix-command flakes"155 say " once nina is installed: nina setup enables this automatically"156 blank157 return 1158}159160161162nixos_shell_build() {163 have nix-shell || return 1164 say "fetching source and building nina using nix-shell..."165 blank166 srcdir="$(fetch_source)"167168 169 170 build_cmd="cd '${srcdir}' && zig build -Doptimize=ReleaseSafe"171 if nix-shell -p zig_0_16 --run "$build_cmd" 2>/dev/null; then172 :173 else174 say "zig_0_16 not in channel — fetching from nixpkgs-unstable..."175 176 177 nix-shell \178 -I nixpkgs=https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz \179 -p zig_0_16 \180 --run "$build_cmd" \181 || die "build failed — see output above"182 fi183184 mkdir -p "$INSTALL_DIR"185 cp "${srcdir}/zig-out/bin/nina" "${INSTALL_DIR}/nina"186 chmod +x "${INSTALL_DIR}/nina"187 blank188 say "installed from source → ${INSTALL_DIR}/nina"189 say "note: binary built outside nix — adopt ~/nina.nix into configuration.nix"190 say " to make nina fully nix-managed (nina goodbye handles removal either way)"191}192193194195196197198199200offer_config_import() {201 config="$1"202 nina_nix="$2"203204 [ -f "$config" ] && [ -r "$config" ] || return 1205206 207 if grep -qF "$nina_nix" "$config" 2>/dev/null; then208 out "Already imported in ${config}."209 out "Apply with: sudo nixos-rebuild switch"210 blank211 return 0212 fi213214 215 216 grep -qE '^[[:space:]]*imports[[:space:]]*=[[:space:]]*\[' "$config" || return 1217218 219 [ -r /dev/tty ] || return 1220 printf ' add this import to %s now? [y/N] ' "$config" > /dev/tty221 IFS= read -r ans < /dev/tty 2>/dev/null || ans=""222 case "$ans" in223 [Yy]*) ;;224 *) return 1 ;;225 esac226227 ts="$(date +%Y%m%d%H%M%S 2>/dev/null || echo backup)"228 backup="${config}.nina-bak.${ts}"229 sudo cp "$config" "$backup" 2>/dev/null \230 || { blank; out "could not back up ${config} — add it manually instead."; blank; return 1; }231232 233 tmp="$(mktemp /tmp/nina-conf.XXXXXX)"234 awk -v p="$nina_nix" '235 !done && /imports[[:space:]]*=[[:space:]]*\[/ { sub(/\[/, "[ " p); done=1 }236 { print }237 ' "$config" > "$tmp" 2>/dev/null238239 if ! sudo cp "$tmp" "$config" 2>/dev/null; then240 rm -f "$tmp"241 blank242 out "could not write ${config}."243 out "restore the backup with: sudo cp ${backup} ${config}"244 blank245 return 1246 fi247 rm -f "$tmp"248249 blank250 out "✓ added the import to ${config}"251 out " backup kept at ${backup}"252 out " apply with sudo nixos-rebuild switch"253 blank254 return 0255}256257generate_nixos_module() {258 259 cat > "$HOME/nina.nix" <<'NIXEOF'260# ~/nina.nix — generated by the nina installer261#262# Declares nina as a NixOS system package.263#264# Add to /etc/nixos/configuration.nix:265# imports = [ /home/YOUR_USER/nina.nix ];266# Then: sudo nixos-rebuild switch267#268# To update: nina update (or re-run the installer)269# To remove: nina goodbye (or delete this file, then nixos-rebuild switch)270#271# For flake-based configurations, add nina as a flake input instead:272# inputs.nina.url = "https://kepr.uk/nina/archive/HEAD.tar.gz";273# # in your nixosModules list: nina.nixosModules.default274# # then set: programs.nina.enable = true;275{ ... }:276{277 imports = [278 "${builtins.fetchTarball "https://kepr.uk/nina/archive/HEAD.tar.gz"}/nixos-module.nix"279 ];280 programs.nina.enable = true;281}282NIXEOF283284 nina_nix="$HOME/nina.nix"285286 blank287 hr288 out "system-wide install (optional)"289 hr290 blank291 out "nina is installed for your user. To manage her with the rest of"292 out "your system, a NixOS module has been written to:"293 blank294 out " ${nina_nix}"295 blank296297 if offer_config_import "/etc/nixos/configuration.nix" "$nina_nix"; then298 return 0299 fi300301 out "To enable it yourself, add this line to /etc/nixos/configuration.nix"302 out "(inside the existing imports = [ … ]; list):"303 blank304 out " ${nina_nix}"305 blank306 out "then run: sudo nixos-rebuild switch"307 blank308}309310311312check_zig() {313 if ! have zig; then314 blank315 say "Zig not found. Building nina requires Zig 0.${MIN_ZIG_MINOR}+."316 blank317 say " Download: https://ziglang.org/download/"318 if is_nixos; then319 say " NixOS: nix-shell -p zig_0_16 --run 'sh install.sh'"320 fi321 blank322 die "Zig 0.${MIN_ZIG_MINOR}+ required"323 fi324325 zig_ver="$(zig version 2>/dev/null)"326 major="$(printf '%s' "$zig_ver" | cut -d. -f1)"327 minor="$(printf '%s' "$zig_ver" | cut -d. -f2)"328 ok=0329 [ "$major" -ge 1 ] 2>/dev/null && ok=1330 [ "$major" -eq 0 ] 2>/dev/null && [ "$minor" -ge "$MIN_ZIG_MINOR" ] 2>/dev/null && ok=1331332 if [ "$ok" -ne 1 ]; then333 blank334 say "found zig ${zig_ver}, but nina requires Zig 0.${MIN_ZIG_MINOR}+."335 if is_nixos; then336 say " run: nix-shell -p zig_0_16 --run 'sh install.sh'"337 else338 say " update: https://ziglang.org/download/"339 fi340 blank341 die "Zig 0.${MIN_ZIG_MINOR}+ required; found ${zig_ver}"342 fi343344 say "zig ${zig_ver} found"345}346347348349build_from_source() {350 say "building from source..."351 check_zig352353 if [ -f "build.zig" ] && [ -f "src/main.zig" ]; then354 srcdir="."355 else356 srcdir="$(fetch_source)"357 fi358359 (cd "$srcdir" && zig build -Doptimize=ReleaseSafe) \360 || die "build failed — see output above"361362 mkdir -p "$INSTALL_DIR"363 cp "${srcdir}/zig-out/bin/nina" "${INSTALL_DIR}/nina"364 chmod +x "${INSTALL_DIR}/nina"365 say "installed from source → ${INSTALL_DIR}/nina"366}367368369370check_path() {371 case ":${PATH}:" in372 *":${INSTALL_DIR}:"*) ;;373 *)374 blank375 say "${INSTALL_DIR} is not in your PATH. Add it:"376 say " export PATH=\"\${PATH}:${INSTALL_DIR}\""377 say " echo 'export PATH=\"\${PATH}:${INSTALL_DIR}\"' >> ~/.bashrc"378 if is_nixos; then379 say " NixOS: add to environment.sessionVariables in configuration.nix"380 fi381 ;;382 esac383}384385386387388389390maybe_setup_features() {391 have nix || return 0392 393 nix eval --expr '1' >/dev/null 2>&1 && return 0394395 NINA="${INSTALL_DIR}/nina"396 [ -x "$NINA" ] || NINA="nina"397398 blank399 say "nina needs nix's experimental features (nix-command and flakes) to"400 say "search and install packages."401402 if [ -r /dev/tty ]; then403 printf 'nina: enable them now? [Y/n] ' > /dev/tty404 IFS= read -r ans < /dev/tty 2>/dev/null || ans="y"405 case "$ans" in406 [Nn]*) say "skipped — enable later with: nina setup"; return 0 ;;407 esac408 "$NINA" setup || say "automatic setup failed — run: nina setup"409 else410 say "run nina setup to enable them"411 fi412}413414415416farewell() {417 printf '\n'418 printf ' thank you!! running nina update occasionally will keep me in tip top shape.\n'419 printf ' get started by running nina hello\n'420 printf '\n'421 printf ' if nina turns out not to be for you, nina goodbye will remove her cleanly.\n'422 printf '\n'423}424425426427banner428429if is_nixos; then430 say "NixOS detected"431 if nixos_profile_install; then432 generate_nixos_module433 maybe_setup_features434 farewell435 exit 0436 fi437 438 439 say "trying prebuilt binary from Kepr..."440 if install_binary; then441 check_path442 maybe_setup_features443 farewell444 exit 0445 fi446 say "no prebuilt binary available — building from source"447 nixos_shell_build448 generate_nixos_module449 check_path450 maybe_setup_features451 farewell452 exit 0453fi454455456if install_binary; then457 check_path458 maybe_setup_features459 farewell460 exit 0461fi462463say "no pre-built binary for ${platform} — falling back to source build"464build_from_source465check_path466maybe_setup_features467farewell