#!/bin/sh # Code generated by scripts/build-install.sh; DO NOT EDIT. # Edit scripts/install-src/*.sh and rebuild instead. set -eu REPO="instantOS/instantCLI" API_URL="https://api.github.com/repos/$REPO/releases" BIN_NAME="ins" SOURCE_BIN_NAME="ins" INSTALL_DIR=${INSTALL_DIR:-} CLI_ONLY=0 OS_INSTALL=0 ONLY_ANIMATION=0 NO_ANIMATION=0 log() { printf '%s\n' "$1" } warn() { printf 'warning: %s\n' "$1" >&2 } fatal() { printf 'error: %s\n' "$1" >&2 exit 1 } # ------------------------------------------------------------- # Logo animation frames (from Plymouth theme throbber) # ------------------------------------------------------------- f1() { cat <<'FRM' ▄▄▄▄ ▄▄████████▄ ▄████▀▀▀▀████▄ ████ ▄████ ▀████▄▄▄██████ ▀▀██████████▄▄▄▄ ██████▄ ███████ ▀▀▀▀▀ FRM } f2() { cat <<'FRM' ▄▄▄▄ ▄▄████████▄ ▄████▀▀▀▀████▄ ████ ████ ▀████▄▄▄██████ ▀▀██████████▄▄▄▄ ██████▄ ███████ ▀▀▀▀▀ FRM } f3() { cat <<'FRM' ▄▄▄▄ ▄█████████▄ ▄████▀▀▀▀▀███▄ ████▄ ████ █████▄▄██████ ▀▀██████████▄▄▄ ██████▄ ███████ ▀▀▀▀▀ FRM } f4() { cat <<'FRM' ▄▄▄▄▄ ▄█████████▄ ████▀▀ ▀████ ████ ████ ▀████████████ ▀▀▀████████▄ ██████▄ ███████ ▀▀█▀▀ FRM } f5() { cat <<'FRM' ▄▄▄▄▄▄ ▄██████████▄ ▄████▀ ▀████ ▀███▄ ▄████ ▀███████████ ▀▀▀▀██████ ██████▄ ████████ ▀████▀ FRM } f6() { cat <<'FRM' ▄▄▄▄▄▄▄ ▄██████████▄ ████▀ ████ ████▄ ▄████ ▀███████████ ▀▀▀▀████ ▄█████▄ ████████ ▀▀████▀ FRM } f7() { cat <<'FRM' ▄▄▄▄▄▄▄ ▄██████████▄ ████▀ ████ ▀███▄▄ ▄████ ▀██████████ ▀▀▀▀███ ▄████▄▄ ███████▄ ▀█████▀ ▀ FRM } f8() { cat <<'FRM' ▄▄▄██▄▄ ▄██████████ ████ ████ ▀███▄▄▄▄▄████ ▀▀████████▀ ▀▀▀██▀ ████▄▄ ████████ ▀██████▀ ▀▀▀▀ FRM } f9() { cat <<'FRM' ▄▄▄█▄▄▄ ▄██████████ ▄███▀ ████ ████▄▄▄▄███▀ ▀████████▀ ▀▀██▀ ▄████▄ ▄███████▄ ▀███████▀ ▀▀▀▀▀ FRM } f10() { cat <<'FRM' ▄▄▄▄▄▄▄ ▄██████████ ███▀ ████ ████▄▄▄▄███▀ ▀████████▀ ▀██▀ ▄███▄▄ ▄████████ ▀████████ ▀███▀▀ FRM } f11() { cat <<'FRM' ▄▄▄▄▄▄ ▄████████▄ ████ ████ ████▄▄▄▄████ ▀████████▀ ▀██▀▀ ▄▄███▄ ▄████████ ▀████████ ▀▀████▀ FRM } f12() { cat <<'FRM' ▄▄ ▄████████▄ ▄███▀▀▀▀████ ▀███▄▄▄▄████ ▀████████▀ ███▀▀ ▄▄███▄ ▄████████▄ ▀████████▀ ▀██████▀ FRM } f13() { cat <<'FRM' ▄██████▄ ████▀▀████▄ ▀████▄ ▄████ ▀████████▀ ███▀▀▀ ▄▄████▄ ▄████████▄ ██████████ ▀██████▀ FRM } f14() { cat <<'FRM' ▄▄▄▄▄▄ ▄█████████ ████ █████ ██████████ ▄████▀▀▀ ▄▄██████ ██████████ ██████████ ▀██████▀ FRM } f15() { cat <<'FRM' ▄▄ ▄███████▄▄ ████▀▀████ ██████████ ▄▄███████▀▀ ▄████████ ████▀█████ ██████████ ▀▀████▀▀ FRM } f16() { cat <<'FRM' ▄▄███▄▄ ▄█████████▄ ███████████ ▄▄▄▄▄▄▄█████████▀ ▄██████████ ████▀▀████ ██████████ ▀▀██▀▀ FRM } f17() { cat <<'FRM' ▄▄▄▄ ▄████████▄ ██████████ ▄▄████▄▄▄▄██████████▀ ███████████▀ ▀▀▀▀▀▀▀ ████ ▄████ ▀████████▀ ▀▀▀▀▀ FRM } f18() { cat <<'FRM' ▄████▄▄ ▄ ▄█████████ ▄████████▄▄▄██████████ ████▀▀▀█████▀▀▀▀████▀▀ ████▄▄▄████ ▀███████▀ FRM } f19() { cat <<'FRM' ▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄███████▄ ▄███████████▄▄██████████ ████ ████▀ ▀▀██████▀ ▀████▄▄████▀ ▀▀████▀▀ FRM } f20() { cat <<'FRM' ▄███████▄▄ ▄▄█████▄ ▄███▀▀▀▀▀████▄▄██████████ ▀███▄ ▄████▀ ▀████████▀ ▀█████████▀ ▀▀▀▀▀ ▀▀▀▀▀ FRM } f21() { cat <<'FRM' ▄▄▄▄▄▄▄ ██████████▄ ▄▄▄▄▄▄ ████ ▀████▄▄▄████████ ▀███▄▄▄▄████▀▀▀▀████████ ▀██████▀▀ ▀▀██▀▀ FRM } f22() { cat <<'FRM' ▄████████▄ ████▀▀▀▀████▄ ▄▄▄ ████ ▄█████▄▄███████▄ ▀███████████▀▀▀█████████ ▀▀▀▀▀▀▀▀ ▀▀████▀ FRM } f23() { cat <<'FRM' ▄▄▄▄▄▄▄ ▄██████████▄ ████ ▀████ ████▄▄ ▄▄█████▄▄▄█████▄ ▀█████████▀▀▀▀████████ ▀▀▀▀ ▀█████▀ FRM } f24() { cat <<'FRM' ▄███████▄ ▄████▀▀▀████▄ ████ ████▄ ▀████▄▄▄██████▄▄▄████▄ ▀███████▀▀▀ ▀████████ ▀██████▀ ▀▀ FRM } f25() { cat <<'FRM' ▄▄▄▄▄ ▄█████████▄ ████▀▀ ▀████ ████▄ █████ ▀████████████▄▄▄▄▄▄▄ ▀▀▀█▀▀▀▀▀▀ ████████▄ ███████ ▀▀▀ FRM } f26() { cat <<'FRM' ▄▄▄██▄▄▄ ▄██████████▄ ████▀ ▀████ ▀████▄ ▄▄████ ▀█████████████▄▄▄▄▄▄ ▀▀▀▀▀▀▀▀ ████████ ███████ ▀▀▀▀ FRM } f27() { cat <<'FRM' ▄██████▄▄ ▄████▀▀▀████▄ ████ ████ ████▄▄▄▄▄████ ▀████████████▄▄▄▄▄ ▀▀▀▀ ███████▄ ▀██████▀ ▀▀▀▀ FRM } f28() { cat <<'FRM' ▄▄███████▄▄ ▄████▀▀▀▀████ ████ ████▄ ▀████▄▄▄▄█████ ▀▀███████████▄▄▄▄ ███████ ▀██████ ▀▀▀▀ FRM } f29() { cat <<'FRM' ▄▄ ▄▄████████▄ ▄████▀▀▀▀████ ████ ▄████ ▀████▄▄▄██████ ▀███████████▄▄▄▄ ███████ ███████ ▀▀▀▀▀ FRM } f30() { cat <<'FRM' ▄▄▄▄ ▄▄████████▄ ▄████▀▀▀▀████▄ ████ ▄████ ▀████▄▄▄██████ ▀▀██████████▄▄▄▄ ██████▄ ███████ ▀▀▀▀▀ FRM } # Render a static logo for redirected output or a terminal animation for TTYs. # Signal handlers restore terminal state before aborting an interrupted run. instantos_logo_animation() { [ "$NO_ANIMATION" -eq 0 ] || return 0 esc=$(printf '\033') csi="${esc}[" reset="${csi}0m" bold="${csi}1m" dim="${csi}2m" white="${csi}1;37m" clear_home="${csi}H${csi}J" clear_screen="${csi}2J${csi}H" hide="${csi}?25l" show="${csi}?25h" # Rainbow spectrum palette c_red="${csi}38;5;196m" c_orange="${csi}38;5;208m" c_yellow="${csi}38;5;226m" c_green="${csi}38;5;46m" c_cyan="${csi}38;5;51m" c_blue="${csi}38;5;39m" c_purple="${csi}38;5;135m" c_magenta="${csi}38;5;201m" if [ ! -t 1 ] || [ "${TERM:-}" = "dumb" ]; then f30 printf "\n instantOS Installer\n" printf " Arch Linux, but instant\n\n" return 0 fi cleanup_anim() { printf "%s%s\n" "$reset" "$show" } interrupt_anim() { status=$1 cleanup_anim trap - INT TERM HUP QUIT exit "$status" } trap 'interrupt_anim 130' INT trap 'interrupt_anim 143' TERM trap 'interrupt_anim 129' HUP trap 'interrupt_anim 131' QUIT printf "%s%s" "$hide" "$clear_screen" # Play rotating and blob-morphing animation in white (2 full cycles) loop=1 while [ "$loop" -le 2 ]; do frame_idx=1 while [ "$frame_idx" -le 30 ]; do printf "%s%s" "$clear_home" "$white" eval "f$frame_idx" sleep 0.035 frame_idx=$((frame_idx + 1)) done loop=$((loop + 1)) done # Rainbow spectrum flash on final resting logo for color in "$c_red" "$c_orange" "$c_yellow" "$c_green" "$c_cyan" "$c_blue" "$c_purple" "$c_magenta" "$white"; do printf "%s%s" "$clear_home" "$color" f30 sleep 0.02 done printf "%s" "$reset" printf "\n%s instantOS Installer%s\n" "$bold" "$reset" printf "%s Arch Linux, but instant%s\n\n" "$dim" "$reset" printf "%s" "$show" trap - INT TERM HUP QUIT } # ------------------------------------------------------------- # Environment & Command checks # ------------------------------------------------------------- # Return success when runtime markers identify an ArchISO or instantOS live system. is_live_disk() { [ -e /run/archiso/cowspace ] || [ -e /etc/instantos/liveversion ] || [ -e /usr/share/liveutils ] || grep -q "archiso" /proc/cmdline 2>/dev/null } # Decide whether this invocation should continue from CLI setup into OS install. # An explicit OS request wins; --cli-only suppresses automatic live-media launch. should_launch_os_installer() { [ "$OS_INSTALL" -eq 1 ] || { [ "$CLI_ONLY" -eq 0 ] && is_live_disk; } } # Return success only for numbered Linux virtual-console device paths. is_virtual_console_device() { case "$1" in /dev/tty[0-9]*) console_number=${1#/dev/tty} case "$console_number" in *[!0-9]*) return 1 ;; *) return 0 ;; esac ;; esac return 1 } # Print the Linux virtual-console device backing stdout, including through tmux. linux_console_device() { [ -t 1 ] || return 1 if [ -n "${TMUX:-}" ] && command -v tmux >/dev/null 2>&1; then outer_term=$(tmux display-message -p '#{client_termname}' 2>/dev/null || true) case "$outer_term" in linux*) client_tty=$(tmux display-message -p '#{client_tty}' 2>/dev/null || true) if is_virtual_console_device "$client_tty"; then printf '%s\n' "$client_tty" return 0 fi ;; esac fi if command -v tty >/dev/null 2>&1; then console_tty=$(tty 2>/dev/null || true) if is_virtual_console_device "$console_tty"; then printf '%s\n' "$console_tty" return 0 fi fi return 1 } # Detect if running on a Linux virtual console (TTY) or inside tmux attached to one. is_linux_console() { linux_console_device >/dev/null } # Reprogram the Linux virtual console DAC palette to Catppuccin Mocha. # The Linux kernel VT supports OSC escape sequences \e]P. set_catppuccin_tty() { console_tty=$(linux_console_device) || return 0 export INS_COLOR_MODE=16 esc=$(printf '\033') palette="${esc}]P01e1e2e${esc}]P1f38ba8${esc}]P2a6e3a1${esc}]P3f9e2af${esc}]P489b4fa${esc}]P5cba6f7${esc}]P694e2d5${esc}]P7bac2de${esc}]P8585b70${esc}]P9f38ba8${esc}]PAa6e3a1${esc}]PBf9e2af${esc}]PC89b4fa${esc}]PDf5c2e7${esc}]PE89dceb${esc}]PFcdd6f4" # Write to the originating VT. /dev/tty0 aliases whichever VT is active and # may therefore modify a different console when this one is in the background. printf '%s' "$palette" >"$console_tty" 2>/dev/null || true printf '%s[0m' "$esc" } # Initialize and refresh the Arch keyring, escalating only these commands when needed. # Any failure is fatal because the following OS installation depends on pacman. prepare_live_keyring() { command -v pacman-key >/dev/null 2>&1 || fatal "required command 'pacman-key' not found" command -v pacman >/dev/null 2>&1 || fatal "required command 'pacman' not found" log "Preparing the Arch Linux package keyring..." if [ "$(id -u)" -eq 0 ]; then pacman-key --init || fatal "failed to initialize the package keyring" pacman-key --populate archlinux || fatal "failed to populate the package keyring" pacman -Sy --needed archlinux-keyring --noconfirm || fatal "failed to update the Arch Linux keyring" elif command -v sudo >/dev/null 2>&1; then sudo pacman-key --init || fatal "failed to initialize the package keyring" sudo pacman-key --populate archlinux || fatal "failed to populate the package keyring" sudo pacman -Sy --needed archlinux-keyring --noconfirm || fatal "failed to update the Arch Linux keyring" else fatal "preparing the package keyring requires root permissions" fi } usage() { cat < Set installation directory --bin-name Override installed binary name (default: ins) --cli-only, --no-launch Install ins CLI only (do not launch OS installer on live disk) --os-install, --arch-install Launch instantOS installer after installing ins --only-animation, --animation-only Play the logo animation and exit --no-animation Skip the logo animation -h, --help Show this help message EOF exit 0 } parse_args() { while [ $# -gt 0 ]; do case "$1" in --install-dir) shift [ $# -gt 0 ] || fatal "--install-dir requires a value" [ -n "$1" ] || fatal "--install-dir requires a non-empty value" INSTALL_DIR=$1 ;; --bin-name) shift [ $# -gt 0 ] || fatal "--bin-name requires a value" case "$1" in "" | . | .. | */*) fatal "--bin-name must be a file name, not a path" ;; esac BIN_NAME=$1 ;; --cli-only | --no-launch) CLI_ONLY=1 ;; --os-install | --arch-install) OS_INSTALL=1 ;; --only-animation | --animation-only) ONLY_ANIMATION=1 ;; --no-animation) NO_ANIMATION=1 ;; -h | --help) usage ;; *) fatal "unknown argument: $1" ;; esac shift done if [ "$CLI_ONLY" -eq 1 ] && [ "$OS_INSTALL" -eq 1 ]; then fatal "--cli-only and --os-install cannot be used together" fi if [ "$ONLY_ANIMATION" -eq 1 ] && [ "$NO_ANIMATION" -eq 1 ]; then fatal "--only-animation and --no-animation cannot be used together" fi } # Select a destination without mutating PATH. OS installs and root use the system # directory; regular users prefer an existing user bin from PATH. choose_install_dir() { if [ -n "$INSTALL_DIR" ]; then return fi # On a live ISO or when explicitly running OS install, install system-wide to /usr/local/bin if should_launch_os_installer; then INSTALL_DIR="/usr/local/bin" return fi # If already root, install to /usr/local/bin if [ "$(id -u)" -eq 0 ]; then INSTALL_DIR="/usr/local/bin" return fi # Standard user installation for candidate in "$HOME/.local/bin" "$HOME/bin"; do case ":$PATH:" in *:"$candidate":*) INSTALL_DIR="$candidate" return ;; esac done INSTALL_DIR="$HOME/.local/bin" } require_commands() { for cmd in curl tar uname mktemp head find; do command -v "$cmd" >/dev/null 2>&1 || fatal "required command '$cmd' not found" done } # Return success for SteamOS/Steam Deck, where the AppImage artifact is preferred. detect_steam_deck() { if [ -f /etc/os-release ]; then if grep -q "steamdeck" /etc/os-release 2>/dev/null || grep -q "SteamOS" /etc/os-release 2>/dev/null; then return 0 fi fi if [ -n "${STEAM_DECK:-}" ]; then return 0 fi return 1 } # Map the runtime architecture to release-asset naming and choose archive/AppImage. # Termux is handled separately to avoid installing glibc binaries into Android. detect_target() { arch=$(uname -m) if [ -n "${TERMUX_VERSION:-}" ]; then case "$arch" in aarch64 | arm64) TARGET="aarch64-termux" USE_APPIMAGE=0 return ;; *) fatal "unsupported Termux architecture: $arch" ;; esac fi case "$arch" in x86_64 | amd64) TARGET="x86_64-unknown-linux-gnu" ;; aarch64 | arm64) TARGET="aarch64-unknown-linux-gnu" ;; armv7l | armv8l) TARGET="armv7-unknown-linux-gnueabihf" ;; *) fatal "unsupported architecture: $arch" ;; esac if detect_steam_deck; then USE_APPIMAGE=1 log "Steam Deck detected, using AppImage" else USE_APPIMAGE=0 fi } # Fetch release metadata and retain the first stable release containing a usable # artifact for TARGET in the global release_json value. fetch_release_json() { # Fetch all releases and find first one with our assets all_releases=$(curl -fsSL \ -H "Accept: application/vnd.github+json" \ -H "User-Agent: instantcli-installer" \ "$API_URL") || fatal "failed to fetch releases metadata" # Try each release until we find one with our asset release_json=$(find_working_release "$all_releases") || fatal "no working release found with assets for $TARGET" } # Print the first complete, non-draft release object with an artifact matching # TARGET/USE_APPIMAGE. The brace scanner ignores braces inside JSON strings. find_working_release() { all_releases="$1" # Extract complete top-level release objects. Counting braces avoids splitting # on nested asset objects and works with both compact and formatted JSON. printf '%s' "$all_releases" | awk -v target="$TARGET" -v use_appimage="$USE_APPIMAGE" ' function release_has_asset(release, rest, url_end, url) { if (release ~ /"draft"[[:space:]]*:[[:space:]]*true/ || release ~ /"prerelease"[[:space:]]*:[[:space:]]*true/) { return 0 } rest = release while (match(rest, /"browser_download_url"[[:space:]]*:[[:space:]]*"/)) { rest = substr(rest, RSTART + RLENGTH) url_end = index(rest, "\"") if (url_end == 0) { return 0 } url = substr(rest, 1, url_end - 1) if (use_appimage == 1) { if (url ~ /\.AppImage$/ && url !~ /\.sha256$/) { return 1 } } else if (index(url, target) > 0 && url !~ /\.sha256$/ && url !~ /\.pkg\.tar\.zst$/ && url !~ /-debug-/) { return 1 } rest = substr(rest, url_end + 1) } return 0 } { line = $0 "\n" for (i = 1; i <= length(line); i++) { char = substr(line, i, 1) if (depth > 0) { object = object char } if (in_string) { if (escaped) { escaped = 0 } else if (char == "\\") { escaped = 1 } else if (char == "\"") { in_string = 0 } continue } if (char == "\"") { in_string = 1 } else if (char == "{") { if (depth == 0) { object = "{" } depth++ } else if (char == "}") { depth-- if (depth == 0) { if (release_has_asset(object)) { found = 1 print object exit 0 } object = "" } } } } END { if (!found) { exit 1 } } ' } # Populate asset_url, optional sha_url, and version from the selected release. # Distribution-package, debug, and checksum assets are excluded from selection. find_asset_urls() { if [ "$USE_APPIMAGE" -eq 1 ]; then asset_url=$(printf '%s\n' "$release_json" | awk ' { rest = $0 while (match(rest, /"browser_download_url"[[:space:]]*:[[:space:]]*"/)) { rest = substr(rest, RSTART + RLENGTH) url_end = index(rest, "\"") url = substr(rest, 1, url_end - 1) if (url ~ /\.AppImage$/ && url !~ /\.sha256$/) { print url exit } rest = substr(rest, url_end + 1) } } ') [ -n "$asset_url" ] || fatal "no AppImage found in release" else asset_url=$(printf '%s\n' "$release_json" | awk -v target="$TARGET" ' { rest = $0 while (match(rest, /"browser_download_url"[[:space:]]*:[[:space:]]*"/)) { rest = substr(rest, RSTART + RLENGTH) url_end = index(rest, "\"") url = substr(rest, 1, url_end - 1) if (index(url, target) && url !~ /\.sha256$/ && url !~ /\.pkg\.tar\.zst$/ && url !~ /-debug-/) { print url exit } rest = substr(rest, url_end + 1) } } ') [ -n "$asset_url" ] || fatal "no prebuilt binary or archive found for $TARGET" fi sha_url=$(printf '%s\n' "$release_json" | awk -v archive="$asset_url" ' { rest = $0 target_sha = archive ".sha256" while (match(rest, /"browser_download_url"[[:space:]]*:[[:space:]]*"/)) { rest = substr(rest, RSTART + RLENGTH) url_end = index(rest, "\"") url = substr(rest, 1, url_end - 1) if (url == target_sha) { print url exit } rest = substr(rest, url_end + 1) } } ') version=$(printf '%s\n' "$release_json" | awk ' match($0, /"tag_name"[[:space:]]*:[[:space:]]*"/) { rest = substr($0, RSTART + RLENGTH) tag_end = index(rest, "\"") tag = substr(rest, 1, tag_end - 1) sub(/^v/, "", tag) print tag exit } ') } # Verify archive_path when the release publishes a sibling .sha256 asset. Missing # published checksums remain backward-compatible; expected verification fails closed. verify_checksum() { archive_path=$1 if [ -z "$sha_url" ]; then warn "no checksum published for this asset; skipping verification" return 0 fi if ! command -v sha256sum >/dev/null 2>&1; then fatal "a checksum is published for this asset, but sha256sum is not available" fi checksum_file="$INSTALL_WORK_DIR/$(basename "$archive_path").sha256" curl -fsSL -H "User-Agent: instantcli-installer" "$sha_url" -o "$checksum_file" || fatal "failed to download published checksum" checksum_basename=$(basename "$archive_path") if ! grep -q " $checksum_basename$" "$checksum_file" 2>/dev/null; then tmp_checksum_file="$checksum_file.tmp" if awk -v name="$checksum_basename" '{print $1 " " name}' "$checksum_file" >"$tmp_checksum_file" 2>/dev/null; then mv "$tmp_checksum_file" "$checksum_file" else fatal "failed to normalize published checksum" fi fi (cd "$INSTALL_WORK_DIR" && sha256sum -c "$(basename "$checksum_file")") || fatal "checksum verification failed" } # Extract supported release archives, selecting an available zstd implementation. extract_archive() { archive_path=$1 dest_dir=$2 case "$archive_path" in *.tar.zst) if tar --help 2>/dev/null | grep -q -- "--zstd"; then tar --zstd -xf "$archive_path" -C "$dest_dir" elif command -v unzstd >/dev/null 2>&1; then unzstd -c "$archive_path" | tar -xf - -C "$dest_dir" elif command -v zstd >/dev/null 2>&1; then zstd -d --stdout "$archive_path" | tar -xf - -C "$dest_dir" else fatal "extracting .tar.zst requires tar with zstd support or the zstd utility" fi ;; *.tgz | *.tar.gz) tar -xzf "$archive_path" -C "$dest_dir" ;; *) fatal "unsupported archive format: $archive_path" ;; esac } # Print the packaged source binary path; BIN_NAME may differ only at installation. find_binary_path() { search_root=$1 binary_path=$(find "$search_root" -type f -name "$SOURCE_BIN_NAME" 2>/dev/null | head -n 1) [ -n "$binary_path" ] || fatal "failed to locate $SOURCE_BIN_NAME in extracted archive" printf '%s\n' "$binary_path" } # Copy binary_path with executable permissions, requesting sudo only when the # chosen destination cannot be written by the current user. install_binary() { binary_path=$1 needs_sudo=0 if [ ! -d "$INSTALL_DIR" ]; then if ! mkdir -p "$INSTALL_DIR" 2>/dev/null; then needs_sudo=1 fi fi if [ ! -w "$INSTALL_DIR" ]; then needs_sudo=1 fi if [ "$needs_sudo" -eq 1 ]; then if ! command -v sudo >/dev/null 2>&1; then fatal "cannot write to $INSTALL_DIR and sudo not available; set INSTALL_DIR to a writable directory" fi log "Requesting elevated permissions to install to $INSTALL_DIR..." if [ ! -d "$INSTALL_DIR" ]; then sudo mkdir -p "$INSTALL_DIR" || fatal "failed to create $INSTALL_DIR with sudo" fi if command -v install >/dev/null 2>&1; then sudo install -m 755 "$binary_path" "$INSTALL_DIR/$BIN_NAME" else warn "install(1) not found; falling back to cp" sudo cp "$binary_path" "$INSTALL_DIR/$BIN_NAME" sudo chmod 755 "$INSTALL_DIR/$BIN_NAME" fi else if command -v install >/dev/null 2>&1; then install -m 755 "$binary_path" "$INSTALL_DIR/$BIN_NAME" else warn "install(1) not found; falling back to cp" cp "$binary_path" "$INSTALL_DIR/$BIN_NAME" chmod 755 "$INSTALL_DIR/$BIN_NAME" fi fi } # Return success for Arch and closely related distributions used by `ins arch setup`. is_arch_linux() { if [ -f /etc/os-release ]; then grep -qiE '^ID(_LIKE)?=.*(arch|instantos|manjaro|endeavouros)' /etc/os-release elif [ -f /etc/arch-release ]; then return 0 else return 1 fi } # Report the installed version and context-sensitive next steps without emitting # terminal escapes into redirected output. print_summary() { if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then bold="$(printf '\033[1m')" dim="$(printf '\033[2m')" reset="$(printf '\033[0m')" cyan="$(printf '\033[38;5;45m')" orange="$(printf '\033[38;5;208m')" else bold="" dim="" reset="" cyan="" orange="" fi case ":$PATH:" in *:"$INSTALL_DIR":*) ;; *) warn "$INSTALL_DIR is not in PATH; add 'export PATH=\$PATH:$INSTALL_DIR' to your shell profile" ;; esac printf '\n' if [ -n "$version" ]; then log "${bold}✓ ${BIN_NAME} v${version} installed successfully to ${INSTALL_DIR}/${BIN_NAME}${reset}" else log "${bold}✓ ${BIN_NAME} installed successfully to ${INSTALL_DIR}/${BIN_NAME}${reset}" fi printf '\n%sNext steps:%s\n' "$bold" "$reset" if is_arch_linux; then printf ' • Convert this Arch installation to %sinstantOS%s (adds [instant] repo, instantWM & tools):\n' "$orange" "$reset" printf ' %ssudo %s arch setup%s\n' "$cyan" "$BIN_NAME" "$reset" else printf ' • Build or install instantOS tools from source:\n' printf ' %s%s dev install%s\n' "$cyan" "$BIN_NAME" "$reset" fi printf ' • Manage dotfiles, system settings, and health checks:\n' printf ' %s%s --help%s\n' "$cyan" "$BIN_NAME" "$reset" printf '\n%sTip:%s To install a fresh %sinstantOS%s system on a computer, boot an Arch Linux live ISO and run:\n' "$dim" "$reset" "$orange" "$reset" printf ' %s%sbash <(curl -fsSL instantos.io/install)%s\n\n' "$dim" "$cyan" "$reset" } # ------------------------------------------------------------- # Main workflow # ------------------------------------------------------------- # Download the release archive with curl's progress meter only for an interactive # terminal, keeping redirected output and CI logs quiet. download_release_asset() { archive_path=$1 if [ -n "$version" ]; then log "Downloading ${BIN_NAME} v${version}..." else log "Downloading ${BIN_NAME}..." fi if [ -t 2 ]; then curl --fail --location --progress-bar \ -H "User-Agent: instantcli-installer" \ "$asset_url" -o "$archive_path" || fatal "failed to download release archive" else curl -fsSL -H "User-Agent: instantcli-installer" \ "$asset_url" -o "$archive_path" || fatal "failed to download release archive" fi } # Coordinate validation, artifact installation, cleanup, and optional OS handoff. main() { parse_args "$@" set_catppuccin_tty if [ "$ONLY_ANIMATION" -eq 1 ]; then instantos_logo_animation exit 0 fi choose_install_dir require_commands detect_target # A fresh instantOS installation currently supports x86_64 only. Other # architectures can still use the CLI-only installation path. if should_launch_os_installer && [ "$TARGET" != "x86_64-unknown-linux-gnu" ]; then fatal "the instantOS system installer currently supports x86_64 only; use --cli-only to install the CLI" fi instantos_logo_animation # If live disk or forced OS install, prepare the keyring before downloading. if should_launch_os_installer; then prepare_live_keyring fi fetch_release_json find_asset_urls INSTALL_WORK_DIR=$(mktemp -d) cleanup_tmpdir() { rm -rf "$INSTALL_WORK_DIR" } trap cleanup_tmpdir EXIT trap 'exit 130' INT trap 'exit 143' TERM trap 'exit 129' HUP archive="$INSTALL_WORK_DIR/$(basename "$asset_url")" download_release_asset "$archive" verify_checksum "$archive" if [ "$USE_APPIMAGE" -eq 1 ]; then chmod +x "$archive" binary_path="$archive" else # Check if it's an archive or a bare binary case "$archive" in *.tar.zst | *.tgz | *.tar.gz) extract_dir="$INSTALL_WORK_DIR/extracted" mkdir "$extract_dir" extract_archive "$archive" "$extract_dir" binary_path=$(find_binary_path "$extract_dir") ;; *) # Bare binary file chmod +x "$archive" binary_path="$archive" ;; esac fi install_binary "$binary_path" cleanup_tmpdir trap - EXIT INT TERM HUP # Launch instantOS installer if on live disk or requested if should_launch_os_installer; then log "Starting instantOS installer..." # The CLI configures networking as the desktop user, then escalates itself. exec "$INSTALL_DIR/$BIN_NAME" arch install fi print_summary } main "$@"