#!/bin/sh # Bootstrap spell: main wizardry installer set -eu # After installation, wizardry will be available in new terminal windows. # To use it immediately in the current terminal, run the command shown at the end. # Abort if unexpected arguments are supplied so we do not accidentally discard # them when repurposing the positional parameters to track missing directories. if [ "$#" -ne 0 ]; then printf '%s\n' "Usage: install" >&2 exit 1 fi # Disable CDPATH so that "cd" behaves predictably even if the user has a # custom search path configured in their environment. This keeps the script # portable across login shells that set CDPATH to multiple directories. unset CDPATH # NOTE: Directories are added to PATH automatically when invoke-wizardry is # sourced from the rc file at shell startup. # Determine the default target directory shown in the install prompt. The # installer accepts three environment variables to steer non-interactive # installs: # * WIZARDRY_INSTALL_DIR – fully overrides the prompt and is used directly. # * WIZARDRY_ROOT – legacy alias used as an override if set. # * WIZARDRY_REMOTE_DIR – retained for backwards compatibility; becomes the # prompt default when present. if [ -n "${WIZARDRY_INSTALL_DIR-}" ]; then PROMPT_DEFAULT=$WIZARDRY_INSTALL_DIR elif [ -n "${WIZARDRY_ROOT-}" ]; then PROMPT_DEFAULT=$WIZARDRY_ROOT elif [ -n "${WIZARDRY_REMOTE_DIR-}" ]; then PROMPT_DEFAULT=$WIZARDRY_REMOTE_DIR elif [ -n "${HOME-}" ]; then PROMPT_DEFAULT=$HOME/.wizardry else PROMPT_DEFAULT=.wizardry fi # Validate and normalize PROMPT_DEFAULT to prevent path construction issues. # Environment variables should provide absolute paths, but we handle relative # paths defensively. If a relative path looks like it's missing a leading slash # (e.g., starts with "home/"), we treat it as absolute. # Note: Using character classes for case-insensitive matching (e.g., [Hh]ome) # is POSIX-compliant and handles common variations without external tools. case $PROMPT_DEFAULT in /*) # Already absolute path, no normalization needed : ;; [Hh]ome/*|[Uu]sers/*|[Uu]sr/*|[Oo]pt/*|[Vv]ar/*|[Tt]mp/*|[Mm]nt/*|[Rr]oot/*) # Looks like an absolute path missing the leading slash # This handles cases where environment variables are set incorrectly # Using case-insensitive patterns to catch common variations PROMPT_DEFAULT="/$PROMPT_DEFAULT" ;; *) # Other relative paths: resolve relative to HOME if possible if [ -n "${HOME-}" ]; then # Convert relative to absolute by prepending HOME PROMPT_DEFAULT=$HOME/$PROMPT_DEFAULT else # No HOME set and relative path given - use current directory cwd=$(pwd -P) PROMPT_DEFAULT=$cwd/$PROMPT_DEFAULT fi ;; esac if [ -n "${WIZARDRY_INSTALL_PLATFORM-}" ]; then LEARN_SPELLBOOK_PLATFORM=$WIZARDRY_INSTALL_PLATFORM DETECT_RC_FILE_PLATFORM=$WIZARDRY_INSTALL_PLATFORM export LEARN_SPELLBOOK_PLATFORM DETECT_RC_FILE_PLATFORM fi # Track whether we are running out of an existing checkout so local installs # can simply copy the files instead of downloading a fresh archive. Piped # installs (wget|sh) expose the script through /bin/sh so we only find a # checkout when this file is run directly from disk. LOCAL_SOURCE="" case $0 in */*) script_dir=${0%/*} if [ -z "$script_dir" ]; then script_dir=. fi if script_dir_abs=$(cd "$script_dir" 2>/dev/null && pwd -P); then if [ -d "$script_dir_abs/spells" ]; then LOCAL_SOURCE=$script_dir_abs fi fi ;; *) if [ -n "${PWD-}" ] && [ -f "$PWD/$0" ]; then if script_dir_abs=$(cd "$PWD" 2>/dev/null && pwd -P); then if [ -d "$script_dir_abs/spells" ]; then LOCAL_SOURCE=$script_dir_abs fi fi fi ;; esac cleanup_bootstrap() { # Clean up any temporary directory created during the download process. if [ -n "${BOOTSTRAP_TMP_DIR-}" ] && [ -d "$BOOTSTRAP_TMP_DIR" ]; then rm -rf "$BOOTSTRAP_TMP_DIR" fi # If installation was not completed and wizardry was downloaded by this installer # (not pre-existing), remove it. This cleanup happens when user cancels or interrupts. # Safety check: only delete if it looks like a wizardry directory (has spells/ subdirectory) if [ "${INSTALL_COMPLETED-}" != "1" ] && [ "${WIZARDRY_EXISTED_BEFORE-}" = "0" ] && [ -n "${INSTALL_DIR-}" ] && [ -d "$INSTALL_DIR/spells" ]; then rm -rf "$INSTALL_DIR" fi } # Handle interrupt signals (Ctrl-C, HUP, TERM) by cleaning up and exiting. # This ensures the script terminates immediately when the user presses Ctrl-C. handle_interrupt() { cleanup_bootstrap printf '\n%s\n' "Installation interrupted." >&2 exit 130 } # Remote installs create a throwaway working directory; trap its cleanup so # shells on every platform remove the temporary files even if the install # process exits early. trap 'cleanup_bootstrap' EXIT trap 'handle_interrupt' HUP INT TERM download_file() { # Fetch a remote archive using curl or wget, whichever is available on the # host platform. Some of our supported OS images only ship one of the two # tools by default, so we try both before giving up. dest=$1 url=$2 if command -v curl >/dev/null 2>&1; then if curl -fsSL "$url" -o "$dest"; then return 0 fi fi if command -v wget >/dev/null 2>&1; then if wget -qO "$dest" "$url"; then return 0 fi fi printf '%s\n' "Error: Neither curl nor wget is available to download wizardry." >&2 return 1 } download_via_git() { # Download wizardry using git clone with a shallow clone to minimize download size. # This sets up a proper git repository with remote configured, enabling update-wizardry # to work via git pull. # # The shallow clone (--depth=1) and single-branch setup keeps download size minimal # (~1MB) while still allowing git pull to fetch new commits on the main branch. # # For developers who need full history or other branches: # - Full history of main: git fetch --unshallow # - All branches: git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' && git fetch # # Args: dest_dir repo_url [branch] dest=$1 repo_url=$2 branch=${3:-main} # Use shallow clone of main branch to minimize download size (~1MB vs ~550KB tarball) # --depth=1 creates a shallow clone (only latest commit) # --branch=main ensures we clone main and set up tracking for it # This enables git pull to work for updating wizardry if git clone --depth=1 --branch="$branch" "$repo_url" "$dest" >/dev/null 2>&1; then return 0 fi return 1 } INSTALL_INPUT="" if [ -n "${WIZARDRY_INSTALL_DIR-}" ]; then INSTALL_INPUT=$WIZARDRY_INSTALL_DIR elif [ -n "${WIZARDRY_ROOT-}" ]; then INSTALL_INPUT=$WIZARDRY_ROOT else message="Wizardry install directory (or press Enter for default)? [$PROMPT_DEFAULT] " # Check for interactive input. When running via curl | sh, stdin is a pipe # containing the script content, not user input. We must check if we can # read from /dev/tty instead. if [ -t 0 ]; then # stdin is a terminal - read directly from stdin printf '%s' "$message" >&2 IFS= read -r INSTALL_INPUT elif [ -r /dev/tty ]; then # stdin is not a terminal, but /dev/tty is available # This handles curl | sh case where stdin has script content printf '%s' "$message" >&2 IFS= read -r INSTALL_INPUT &2 fi fi if [ -z "$INSTALL_INPUT" ]; then INSTALL_INPUT=$PROMPT_DEFAULT fi case $INSTALL_INPUT in "~") if [ -z "${HOME-}" ]; then printf '%s\n' "Error: HOME is not set; cannot expand '~'." >&2 exit 1 fi INSTALL_INPUT=$HOME ;; "~/"*) if [ -z "${HOME-}" ]; then printf '%s\n' "Error: HOME is not set; cannot expand '~'." >&2 exit 1 fi INSTALL_INPUT=$HOME/${INSTALL_INPUT#\~/} ;; esac # Normalize INSTALL_INPUT to handle paths that look like absolute paths but are # missing the leading slash (e.g., "home/username/.wizardry" -> "/home/username/.wizardry"). # This prevents accidental path doubling like "/home/username/home/username/.wizardry". case $INSTALL_INPUT in /*) # Already absolute path, no normalization needed : ;; [Hh]ome/*|[Uu]sers/*|[Uu]sr/*|[Oo]pt/*|[Vv]ar/*|[Tt]mp/*|[Mm]nt/*|[Rr]oot/*) # Looks like an absolute path missing the leading slash INSTALL_INPUT="/$INSTALL_INPUT" ;; esac case $INSTALL_INPUT in /*) INSTALL_DIR=$INSTALL_INPUT ;; *) cwd=$(pwd -P) INSTALL_DIR=$cwd/$INSTALL_INPUT ;; esac # Track whether wizardry directory existed before we started. # This prevents showing "already installed" menu when we just downloaded it. WIZARDRY_EXISTED_BEFORE=0 if [ -d "$INSTALL_DIR/spells" ]; then WIZARDRY_EXISTED_BEFORE=1 ABS_DIR=$(cd "$INSTALL_DIR" && pwd -P) else if [ -e "$INSTALL_DIR" ]; then printf '%s\n' "Error: '$INSTALL_DIR' already exists but does not look like wizardry." >&2 exit 1 fi parent=${INSTALL_DIR%/*} if [ "$parent" = "$INSTALL_DIR" ]; then parent=. fi if ! mkdir -p "$parent"; then printf '%s\n' "Error: Unable to create directory '$parent'." >&2 exit 1 fi if [ -n "$LOCAL_SOURCE" ]; then printf '%s\n' "Copying wizardry from '$LOCAL_SOURCE' to '$INSTALL_DIR'..." if ! cp -R "$LOCAL_SOURCE" "$INSTALL_DIR"; then printf '%s\n' "Error: Unable to copy wizardry from '$LOCAL_SOURCE'." >&2 exit 1 fi else # Bootstrap wizardry by downloading from remote. # Prefer git clone (creates proper git repo for update-wizardry), # fall back to tarball download if git is not available. repo_url=${WIZARDRY_REMOTE_REPO-https://github.com/andersaamodt/wizardry.git} archive_url=${WIZARDRY_REMOTE_ARCHIVE-https://github.com/andersaamodt/wizardry/archive/refs/heads/main.tar.gz} # Try git clone first if git is available if command -v git >/dev/null 2>&1; then printf '%s\n' "Downloading wizardry via git to '$INSTALL_DIR'..." if download_via_git "$INSTALL_DIR" "$repo_url"; then printf '%s\n' "Wizardry downloaded to '$INSTALL_DIR' (git repository)." else # Git clone failed, fall back to tarball printf '%s\n' "Git clone failed, trying tarball download..." >&2 use_tarball=1 fi else # Git not available, use tarball use_tarball=1 fi # Tarball fallback if [ "${use_tarball-}" = "1" ]; then if ! command -v tar >/dev/null 2>&1; then printf '%s\n' "Error: tar is required to unpack wizardry when git is not available." >&2 exit 1 fi BOOTSTRAP_TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/wizardry-install.XXXXXX") || exit 1 archive="$BOOTSTRAP_TMP_DIR/wizardry.tar.gz" printf '%s\n' "Downloading wizardry to '$INSTALL_DIR'..." if ! download_file "$archive" "$archive_url"; then exit 1 fi if ! tar -xzf "$archive" -C "$BOOTSTRAP_TMP_DIR"; then printf '%s\n' "Error: Failed to unpack wizardry archive." >&2 exit 1 fi extracted="" for candidate in "$BOOTSTRAP_TMP_DIR"/*; do if [ -d "$candidate/spells" ]; then extracted=$candidate break fi done if [ -z "$extracted" ]; then printf '%s\n' "Error: Downloaded archive did not contain wizardry spells." >&2 exit 1 fi if ! mv "$extracted" "$INSTALL_DIR"; then printf '%s\n' "Error: Unable to move wizardry into '$INSTALL_DIR'." >&2 exit 1 fi printf '%s\n' "Wizardry downloaded to '$INSTALL_DIR'." fi fi ABS_DIR=$(cd "$INSTALL_DIR" && pwd -P) fi # Now that the wizardry tree is available, point at the helper spells we rely # on to set up shell integration and ask for confirmation. INVOKE_WIZARDRY="$ABS_DIR/spells/.imps/sys/invoke-wizardry" DETECT_RC_FILE="$ABS_DIR/spells/divination/detect-rc-file" NIX_SHELL_ADD="$ABS_DIR/spells/.imps/sys/nix-shell-add" NIX_SHELL_REMOVE="$ABS_DIR/spells/.imps/sys/nix-shell-remove" CORE_INSTALLER=${WIZARDRY_CORE_INSTALLER:-$ABS_DIR/spells/.arcana/core/install-core} ASK_YN="$ABS_DIR/spells/cantrips/ask-yn" # MUD-related helpers (these are used in the optional MUD installation section) INSTALL_CD="$ABS_DIR/spells/.arcana/mud/install-cd" TOGGLE_CD="$ABS_DIR/spells/.arcana/mud/toggle-cd" TOGGLE_ALL_MUD="$ABS_DIR/spells/.arcana/mud/toggle-all-mud" # NOTE: Spells no longer need to be pre-installed. # Users can still use install() functions in spells for explicit customizations like aliases. # Export environment variables for all helper spells so that they use the new # versions being installed, not any old versions that may be in PATH. # This prevents issues where a user has an old wizardry installation in PATH. LEARN_SPELL="$ABS_DIR/spells/spellcraft/learn" MEMORIZE_LEARN="$ABS_DIR/spells/spellcraft/learn" MEMORIZE_SPELL_HELPER="$ABS_DIR/spells/cantrips/memorize" export DETECT_RC_FILE LEARN_SPELL MEMORIZE_LEARN MEMORIZE_SPELL_HELPER NIX_SHELL_INIT # Bootstrap color definitions (inline - cannot rely on wizardry spells during install) # Create the escape character in a POSIX-compliant way. ESC=$(printf '\033') disable_colors() { RESET='' GREEN='' CYAN='' YELLOW='' BLUE='' BOLD='' GREY='' RED='' } # Set up colors inline since install script cannot assume spells are available RESET="${ESC}[0m" GREEN="${ESC}[32m" CYAN="${ESC}[36m" YELLOW="${ESC}[33m" BLUE="${ESC}[34m" BOLD="${ESC}[1m" GREY="${ESC}[2m" RED="${ESC}[31m" # Disable colors if stdout is not a terminal or NO_COLOR is set if [ ! -t 1 ]; then disable_colors elif [ -n "${NO_COLOR-}" ]; then disable_colors fi # Helper functions for formatted messages info_msg() { printf '%s==>%s %s\n' "$BOLD$CYAN" "$RESET" "$*" } success_msg() { printf '%s✓%s %s\n' "$BOLD$GREEN" "$RESET" "$*" } section_msg() { printf '\n%s===== %s =====%s\n' "$BOLD$BLUE" "$*" "$RESET" } substep_msg() { printf '%s ->%s %s\n' "$CYAN" "$RESET" "$*" } if [ ! -x "$INVOKE_WIZARDRY" ]; then printf '%s\n' "Error: Expected helper '$INVOKE_WIZARDRY' is missing or not executable." >&2 exit 1 fi if [ ! -x "$DETECT_RC_FILE" ]; then printf '%s\n' "Error: Required helper '$DETECT_RC_FILE' is missing." >&2 exit 1 fi if [ ! -x "$ASK_YN" ]; then printf '%s\n' "Error: Required cantrip '$ASK_YN' is missing." >&2 exit 1 fi # Note: Core prerequisite installation is deferred until after the install plan is shown # This avoids prompting for sudo before the user understands what will happen # Perform comprehensive installation detection across all RC files # This checks multiple RC files to see if wizardry is installed anywhere detect_installation_status() { # Check common RC files for wizardry invoke-wizardry source or wizardry markers rc_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $HOME/.zshrc $HOME/.zprofile $HOME/.config/home-manager/home.nix $HOME/.config/nixpkgs/home.nix /etc/nixos/configuration.nix" found_in_any_rc=0 for rc_file in $rc_files; do if [ -f "$rc_file" ]; then # Look for invoke-wizardry source line or wizardry markers if grep -qE "(invoke-wizardry|wizardry: invoke-wizardry|wizardry: wizardry-init)" "$rc_file" 2>/dev/null; then found_in_any_rc=1 break fi # Also check for legacy PATH entries with wizardry dir if grep -q "$ABS_DIR" "$rc_file" 2>/dev/null; then found_in_any_rc=1 break fi fi done # Check if spells directory exists spells_exist=0 if [ -d "$ABS_DIR/spells" ]; then spells_exist=1 fi # Return status: 0=not installed, 1=partially installed, 2=fully installed if [ "$found_in_any_rc" -eq 0 ] && [ "$spells_exist" -eq 0 ]; then return 0 # Not installed elif [ "$found_in_any_rc" -eq 1 ] && [ "$spells_exist" -eq 1 ]; then return 2 # Fully installed else return 1 # Partially installed (needs repair) fi } # Detect installation status for better user messaging detect_installation_status install_status=$? case $install_status in 0) install_mode="fresh" ;; 1) install_mode="repair" ;; 2) install_mode="detected" ;; *) install_mode="fresh" ;; esac # Check if invoke-wizardry source line is already in the RC file. # This replaces the old PATH-based detection approach. invoke_wizardry_installed=0 check_invoke_wizardry_status() { if [ -z "${detect_rc_file_value-}" ]; then return 1 fi if [ ! -f "$detect_rc_file_value" ]; then return 1 fi # For nix format, check for wizardry shell init marker case $detect_format_value in nix) if grep -qE "wizardry: (invoke-wizardry|wizardry-init)" "$detect_rc_file_value" 2>/dev/null; then return 0 fi ;; *) # For shell format, check for invoke-wizardry source line or marker if grep -qE "(invoke-wizardry|wizardry: invoke-wizardry|wizardry: wizardry-init)" "$detect_rc_file_value" 2>/dev/null; then return 0 fi ;; esac return 1 } # Detect the rc file and format early - needed for installation status check detect_rc_file_value="" detect_format_value="" detect_platform_value="" if detect_output="$($DETECT_RC_FILE 2>/dev/null)"; then while IFS='=' read -r key value; do case $key in rc_file) detect_rc_file_value=$value ;; format) detect_format_value=$value ;; platform) detect_platform_value=$value ;; esac done <&2 printf '%s\n' "Set NIXOS_CONFIG environment variable to specify the configuration file path." >&2 exit 1 fi printf '%s' "Enter the path to your configuration.nix: " >&2 IFS= read -r user_nix_config if [ -n "$user_nix_config" ]; then # Expand ~/ patterns. Note: ~user/ patterns are not supported; # users should provide absolute paths or use ~/ for their home. case $user_nix_config in ~/*) user_nix_config=$HOME/${user_nix_config#\~/} ;; esac # Normalize paths that look like absolute paths but are missing # the leading slash (e.g., "etc/nixos/configuration.nix"). case $user_nix_config in /*) # Already absolute path : ;; [Hh]ome/*|[Ee]tc/*|[Uu]sers/*|[Uu]sr/*|[Oo]pt/*|[Vv]ar/*|[Nn]ix/*) # Looks like an absolute path missing the leading slash user_nix_config="/$user_nix_config" ;; esac if [ -f "$user_nix_config" ]; then detect_rc_file_value=$user_nix_config detect_format_value=nix # Export for any helper that needs it NIXOS_CONFIG=$user_nix_config export NIXOS_CONFIG success_msg "Using configuration file: $user_nix_config" else printf '%s\n' "Error: File not found: $user_nix_config" >&2 exit 1 fi else printf '%s\n' "Error: NixOS requires a configuration file path." >&2 exit 1 fi fi # Now that we have detect_rc_file_value, we can check if invoke-wizardry is installed if check_invoke_wizardry_status; then invoke_wizardry_installed=1 else invoke_wizardry_installed=0 fi # Determine if installation is needed needs_installation=0 if [ "$install_status" -eq 0 ]; then # Not installed at all - needs full installation needs_installation=1 elif [ "$install_status" -eq 1 ]; then # Partially installed - needs repair needs_installation=1 elif [ "$invoke_wizardry_installed" -eq 0 ]; then # Fully installed files but no invoke-wizardry in rc (legacy or needs upgrade) needs_installation=1 fi # If wizardry is fully installed (including invoke-wizardry), show the options menu # BUT only if the files existed before we started (not just downloaded) if [ "$needs_installation" -eq 0 ] && [ "$WIZARDRY_EXISTED_BEFORE" -eq 1 ]; then # Wizardry is already installed - offer options via simple text menu printf '\n' info_msg "Wizardry is already installed at: $ABS_DIR" printf '\n' printf '%sWhat would you like to do?%s\n' "$BOLD" "$RESET" printf ' 1) %sRepair%s - Fix shell configuration if needed\n' "$CYAN" "$RESET" printf ' 2) %sReinstall%s - Reinstall wizardry (keeps current location)\n' "$CYAN" "$RESET" printf ' 3) %sUninstall%s - Remove wizardry from your system\n' "$CYAN" "$RESET" printf ' 4) %sExit%s - Do nothing and exit\n' "$CYAN" "$RESET" printf '\n' # Read user choice # Check if we can read input. When running via curl | sh, stdin is a pipe # containing the script content, not user input. We must check if we can # read from /dev/tty instead. choice="" if [ -t 0 ]; then # stdin is a terminal - read directly from stdin printf '%sEnter your choice (1-4): %s' "$BOLD" "$RESET" >&2 IFS= read -r choice elif [ -r /dev/tty ]; then # stdin is not a terminal, but /dev/tty is available # This handles curl | sh case where stdin has script content printf '%sEnter your choice (1-4): %s' "$BOLD" "$RESET" >&2 IFS= read -r choice &2 printf '%s\n' "The uninstall script is created during installation." >&2 printf '%s\n' "You may need to manually remove '$ABS_DIR' and the invoke-wizardry source line from your shell rc file." >&2 exit 1 fi ;; 4|"") # Exit (empty input also exits) printf '%s\n' "No changes made. Run 'menu' or 'mud' to use wizardry." exit 0 ;; *) # Invalid choice - treat as exit printf '%s\n' "Invalid choice. No changes made." exit 0 ;; esac fi section_msg "Installation Plan" # Show what will be installed (streamlined for novice users) if [ -n "$detect_rc_file_value" ]; then if [ "$detect_format_value" = "nix" ]; then info_msg "Configuration file: $detect_rc_file_value" else info_msg "Shell configuration: $detect_rc_file_value" fi else info_msg "Will add wizardry to your shell configuration" substep_msg "${YELLOW}Warning: Could not auto-detect shell configuration file${RESET}" fi substep_msg "This sets up wizardry commands in your terminal" # MUD features are now always enabled by default (no prompt) # The config will default to enabled when unset, making MUD discoverable in main-menu # Users can disable via mud-menu after installation if desired install_mud=1 # Confirm with the user (or their automation) before editing their configuration. # When stdin is not interactive we fall back to the non-blocking ASK_CANTRIP_INPUT=none # pathway so wget|sh installs do not hang. proceed=0 printf '\n' if [ "${WIZARDRY_INSTALL_ASSUME_YES-}" = "1" ]; then proceed=1 else # Handle different input scenarios for curl|sh and other non-interactive cases if [ ! -t 0 ]; then # stdin is not a terminal (e.g., curl|sh, wget|sh, or piped input) if [ -r /dev/tty ]; then # /dev/tty is available - force ask-yn to use it instead of stdin # This prevents ask-yn from trying to read from stdin (which contains the script) if ASK_CANTRIP_INPUT=tty "$ASK_YN" "Proceed with installation?" yes >/dev/null; then proceed=1 fi elif [ -z "${ASK_CANTRIP_INPUT-}" ]; then # No /dev/tty and no ASK_CANTRIP_INPUT set - use default if ASK_CANTRIP_INPUT=none "$ASK_YN" "Proceed with installation?" yes >/dev/null; then proceed=1 fi else # ASK_CANTRIP_INPUT is already set by the user - respect it if "$ASK_YN" "Proceed with installation?" yes >/dev/null; then proceed=1 fi fi else # stdin is a terminal - normal interactive mode if "$ASK_YN" "Proceed with installation?" yes >/dev/null; then proceed=1 fi fi fi if [ "$proceed" -ne 1 ]; then printf '%s\n' "Installation cancelled." exit 1 fi # Install core prerequisites now that user has confirmed and seen the plan # This is where we might ask for sudo on some platforms, so it comes after showing the plan if [ -x "$CORE_INSTALLER" ]; then section_msg "Checking Core Prerequisites" # Show what we're checking with a sublist info_msg "Verifying required tools:" # Check and display status of each core dependency # List all common prerequisites together prereq_tar_ok=0 prereq_curl_wget_ok=0 if command -v tar >/dev/null 2>&1; then prereq_tar_ok=1 substep_msg "tar: ${GREEN}✓ installed${RESET}" else substep_msg "tar: ${RED}✗ missing (required)${RESET}" fi if command -v curl >/dev/null 2>&1; then prereq_curl_wget_ok=1 substep_msg "curl: ${GREEN}✓ installed${RESET}" elif command -v wget >/dev/null 2>&1; then prereq_curl_wget_ok=1 substep_msg "wget: ${GREEN}✓ installed${RESET}" else substep_msg "curl/wget: ${YELLOW}neither found (needed for remote downloads)${RESET}" fi if command -v git >/dev/null 2>&1; then substep_msg "git: ${GREEN}✓ installed${RESET}" else substep_msg "git: ${YELLOW}not found (will attempt install)${RESET}" fi # Platform-specific checks case $(uname -s 2>/dev/null) in Darwin) # macOS specific if command -v stty >/dev/null 2>&1; then substep_msg "stty: ${GREEN}✓ installed${RESET}" else substep_msg "stty: ${YELLOW}not found (will attempt install)${RESET}" fi if command -v tput >/dev/null 2>&1; then substep_msg "tput: ${GREEN}✓ installed${RESET}" else substep_msg "tput: ${YELLOW}not found (will attempt install)${RESET}" fi ;; *) # Linux if command -v bwrap >/dev/null 2>&1; then substep_msg "bubblewrap: ${GREEN}✓ installed${RESET}" else substep_msg "bubblewrap: ${YELLOW}not found (optional, for test sandboxing)${RESET}" fi if command -v stty >/dev/null 2>&1; then substep_msg "stty: ${GREEN}✓ installed${RESET}" else substep_msg "stty: ${YELLOW}not found (will attempt install)${RESET}" fi if command -v tput >/dev/null 2>&1; then substep_msg "tput: ${GREEN}✓ installed${RESET}" else substep_msg "tput: ${YELLOW}not found (will attempt install)${RESET}" fi ;; esac # Run core installer - it returns success even if some optional deps fail ASSUME_YES=1 "$CORE_INSTALLER" >/dev/null 2>&1 || true success_msg "Core prerequisites verified" fi section_msg "Setting up Shell Integration" shell_setup_made=0 rebuild_success=0 profile_file_modified="" # Determine the destination description for messages if [ -n "$detect_rc_file_value" ]; then rc_destination=$(printf "'%s'" "$detect_rc_file_value") else rc_destination="your shell configuration file" fi # Add invoke-wizardry source line to the shell configuration file. # This replaces the old approach of adding directories to PATH manually. # invoke-wizardry dynamically sets up PATH at shell startup. # Skip if detect-rc-file failed to find a configuration file if [ -n "$detect_rc_file_value" ]; then info_msg "Updating $rc_destination..." # Generate the source line for invoke-wizardry invoke_wizardry_source_line=". \"$INVOKE_WIZARDRY\"" case $detect_format_value in nix) # For NixOS, use nix-shell-add to add shell initialization code substep_msg "Using nix-shell-add for NixOS integration" if [ -x "$NIX_SHELL_ADD" ]; then if printf '%s\n' "$invoke_wizardry_source_line" | "$NIX_SHELL_ADD" wizardry-init "$detect_rc_file_value" bash 2>/dev/null; then shell_setup_made=1 substep_msg "Added to NixOS configuration: ${GREEN}success${RESET}" else printf '%s\n' "Error: Failed to add wizardry initialization to '$detect_rc_file_value'." >&2 exit 1 fi else printf '%s\n' "Error: nix-shell-add helper not found at '$NIX_SHELL_ADD'." >&2 exit 1 fi ;; *) # For standard shell rc files, add the source line directly with a wizardry marker # Check if the source line already exists substep_msg "Checking for existing wizardry initialization..." if grep -qE "(invoke-wizardry|wizardry: wizardry-init)" "$detect_rc_file_value" 2>/dev/null; then shell_setup_made=0 substep_msg "Already configured: ${GREEN}yes${RESET} (skipping)" else substep_msg "Already configured: no (adding now)" # Create rc file if it doesn't exist if [ ! -f "$detect_rc_file_value" ]; then if touch "$detect_rc_file_value" 2>/dev/null; then substep_msg "Created new file: ${GREEN}success${RESET}" else printf '%s\n' "Error: Failed to create '$detect_rc_file_value'." >&2 printf '%s\n' "Check that the directory exists and you have write permissions." >&2 exit 1 fi fi # Add the source line with inline marker if printf '%s\n' "$invoke_wizardry_source_line # wizardry: wizardry-init" >> "$detect_rc_file_value" 2>/dev/null; then shell_setup_made=1 substep_msg "Added source line: ${GREEN}success${RESET}" else printf '%s\n' "Error: Failed to write to '$detect_rc_file_value'." >&2 printf '%s\n' "Check that you have write permissions for this file." >&2 exit 1 fi fi ;; esac if [ "$shell_setup_made" -eq 1 ]; then success_msg "Shell configuration updated in $rc_destination" # For bash users: if we modified .bashrc, ensure .bash_profile sources it # This is needed because bash login shells (common on macOS) don't source .bashrc # We do this after modifying .bashrc to ensure login shells pick up the changes case "$detect_rc_file_value" in */.bashrc) bash_profile="$HOME/.bash_profile" if [ ! -f "$bash_profile" ]; then # Create a .bash_profile that sources .bashrc # This is the standard pattern for bash configuration substep_msg "Creating .bash_profile to source .bashrc (for login shells)" cat > "$bash_profile" <<'BASH_PROFILE_EOF' # Source .bashrc if it exists (added by wizardry installer) # This ensures bash login shells (like those opened by Terminal.app on macOS) # get the same configuration as interactive shells if [ -f ~/.bashrc ]; then . ~/.bashrc fi BASH_PROFILE_EOF substep_msg "Created $bash_profile" profile_file_modified="$bash_profile" elif ! grep -q '\.bashrc' "$bash_profile" 2>/dev/null; then # .bash_profile exists but doesn't source .bashrc # Add the sourcing logic to ensure our changes are picked up substep_msg "Updating .bash_profile to source .bashrc" cat >> "$bash_profile" <<'BASH_PROFILE_APPEND' # Source .bashrc if it exists (added by wizardry installer) if [ -f ~/.bashrc ]; then . ~/.bashrc fi BASH_PROFILE_APPEND substep_msg "Updated $bash_profile" profile_file_modified="$bash_profile" fi ;; */.zshrc) # For zsh users: if we modified .zshrc, ensure .zprofile sources it # This is needed because zsh login shells may not source .zshrc zsh_profile="$HOME/.zprofile" if [ ! -f "$zsh_profile" ]; then # Create a .zprofile that sources .zshrc substep_msg "Creating .zprofile to source .zshrc (for login shells)" cat > "$zsh_profile" <<'ZSH_PROFILE_EOF' # Source .zshrc if it exists (added by wizardry installer) # This ensures zsh login shells get the same configuration as interactive shells if [ -f ~/.zshrc ]; then . ~/.zshrc fi ZSH_PROFILE_EOF substep_msg "Created $zsh_profile" profile_file_modified="$zsh_profile" elif ! grep -q '\.zshrc' "$zsh_profile" 2>/dev/null; then # .zprofile exists but doesn't source .zshrc # Add the sourcing logic to ensure our changes are picked up substep_msg "Updating .zprofile to source .zshrc" cat >> "$zsh_profile" <<'ZSH_PROFILE_APPEND' # Source .zshrc if it exists (added by wizardry installer) if [ -f ~/.zshrc ]; then . ~/.zshrc fi ZSH_PROFILE_APPEND substep_msg "Updated $zsh_profile" profile_file_modified="$zsh_profile" fi ;; esac fi else printf '%s\n' "Warning: Could not detect shell configuration file. You may need to manually source invoke-wizardry." >&2 fi # Create ~/.spellbook directory for custom spells # Note: invoke-wizardry automatically adds this to PATH, so no manual PATH addition needed if [ -n "${HOME-}" ]; then spell_home_dir="$HOME/.spellbook" if [ ! -d "$spell_home_dir" ]; then mkdir -p "$spell_home_dir" substep_msg "Created custom spellbook at $spell_home_dir" fi fi # NOTE: Pre-installing spells is no longer needed. # Users can still manually run spell install() functions for customizations. # MUD Installation - features are always installed and enabled by default # No config is created during install; features default to enabled when unset # This keeps the install script simple and relies on runtime defaults in menus if [ "$install_mud" -eq 1 ]; then section_msg "Installing MUD" substep_msg "MUD features will be enabled by default" success_msg "MUD features installed" fi # Handle platform-specific post-installation steps if [ "$shell_setup_made" -eq 1 ]; then if [ "$detect_format_value" = "nix" ]; then # On NixOS, attempt to run nixos-rebuild or home-manager switch section_msg "Rebuilding NixOS Configuration" info_msg "Attempting to rebuild NixOS configuration..." # Try to detect if we should use sudo or doas priv_cmd="" if command -v sudo >/dev/null 2>&1; then priv_cmd="sudo" elif command -v doas >/dev/null 2>&1; then priv_cmd="doas" fi # Try home-manager first (doesn't need privileges) if command -v home-manager >/dev/null 2>&1; then info_msg "Running 'home-manager switch'..." substep_msg "This may take several minutes, please wait..." if home-manager switch; then success_msg "NixOS environment rebuilt successfully" rebuild_success=1 fi fi # If home-manager didn't work and we have privilege escalation, try nixos-rebuild if [ "$rebuild_success" -eq 0 ] && [ -n "$priv_cmd" ] && command -v nixos-rebuild >/dev/null 2>&1; then info_msg "Running '$priv_cmd nixos-rebuild switch'..." substep_msg "This may take several minutes, please wait..." if "$priv_cmd" nixos-rebuild switch; then success_msg "NixOS environment rebuilt successfully" rebuild_success=1 fi fi if [ "$rebuild_success" -eq 0 ]; then printf '%sNote:%s NixOS configuration could not be automatically rebuilt.\n' "$YELLOW" "$RESET" printf 'To activate changes, use one of these commands:\n' printf ' home-manager switch (for home-manager users)\n' printf ' sudo nixos-rebuild switch (for system-level configuration)\n' fi fi fi # Create uninstall script create_uninstall_script() { uninstall_script="$ABS_DIR/.uninstall" cat >"$uninstall_script" <<'UNINSTALL_EOF' #!/bin/sh # Wizardry Uninstall Script # This script was automatically generated during installation. # It will reverse all changes made by the install script. set -eu UNINSTALL_EOF # Add variables for the installation cat >>"$uninstall_script" <>"$uninstall_script" <<'UNINSTALL_MAIN' # Inline color definitions (consistent with install script) ESC=$(printf '\033') RESET="${ESC}[0m" RED="${ESC}[31m" YELLOW="${ESC}[33m" GREEN="${ESC}[32m" BOLD="${ESC}[1m" CYAN="${ESC}[36m" # Disable colors if stdout is not a terminal or NO_COLOR is set if [ ! -t 1 ]; then RESET='' RED='' YELLOW='' GREEN='' BOLD='' CYAN='' elif [ -n "${NO_COLOR-}" ]; then RESET='' RED='' YELLOW='' GREEN='' BOLD='' CYAN='' fi printf '\n%s===== Wizardry Uninstall =====%s\n\n' "$BOLD$CYAN" "$RESET" printf '%sThis will remove wizardry from your system:%s\n' "$YELLOW" "$RESET" printf ' - Remove invoke-wizardry source line from %s\n' "$RC_FILE" if [ -z "$INSTALL_WAS_LOCAL" ]; then printf ' - Optionally delete wizardry directory: %s\n' "$INSTALL_DIR" else printf ' - Keep wizardry directory (local source installation)\n' fi printf '\n' # Ask for confirmation printf '%sDo you want to proceed?%s [y/N] ' "$BOLD" "$RESET" read -r answer case $answer in y|Y|yes|YES) : # Continue ;; *) printf 'Uninstall cancelled.\n' exit 0 ;; esac printf '\n%s==> Removing shell configuration...%s\n' "$CYAN" "$RESET" # Remove the invoke-wizardry source line from the rc file if [ -f "$RC_FILE" ]; then if [ "$RC_FORMAT" = "nix" ]; then # For NixOS, use nix-shell-remove to remove the wizardry-init block NIX_SHELL_REMOVE="$INSTALL_DIR/spells/.imps/sys/nix-shell-remove" if [ -x "$NIX_SHELL_REMOVE" ]; then if "$NIX_SHELL_REMOVE" wizardry-init "$RC_FILE" 2>/dev/null; then printf ' %s✓%s Removed wizardry initialization from %s\n' "$GREEN" "$RESET" "$RC_FILE" else printf ' %sNote:%s No wizardry initialization found in %s\n' "$YELLOW" "$RESET" "$RC_FILE" fi else # Fallback: try to remove all lines containing wizardry markers tmp_file="${RC_FILE}.wizardry.$$" if grep -v '# wizardry:' "$RC_FILE" > "$tmp_file" 2>/dev/null; then mv "$tmp_file" "$RC_FILE" printf ' %s✓%s Removed all wizardry lines from %s\n' "$GREEN" "$RESET" "$RC_FILE" else rm -f "$tmp_file" printf ' %sNote:%s Could not remove wizardry lines\n' "$YELLOW" "$RESET" fi fi else # For shell rc files, remove all lines containing wizardry markers tmp_file="${RC_FILE}.wizardry.$$" if grep -v '# wizardry:' "$RC_FILE" > "$tmp_file" 2>/dev/null; then mv "$tmp_file" "$RC_FILE" printf ' %s✓%s Removed all wizardry lines from %s\n' "$GREEN" "$RESET" "$RC_FILE" else rm -f "$tmp_file" printf ' %sNote:%s No wizardry lines found to remove\n' "$YELLOW" "$RESET" fi fi else printf ' %sNote:%s RC file %s not found\n' "$YELLOW" "$RESET" "$RC_FILE" fi # Remove profile file modifications if they were made if [ -n "$PROFILE_FILE" ] && [ -f "$PROFILE_FILE" ]; then printf '\n%s==> Removing profile file modifications...%s\n' "$CYAN" "$RESET" tmp_file="${PROFILE_FILE}.wizardry.$$" # Remove the entire wizardry block from profile file # The block starts with "# Source .* if it exists (added by wizardry installer)" # and ends with "fi" on its own line after the if statement awk ' /^# Source \..*rc if it exists \(added by wizardry installer\)$/ { skip = 1; next } skip == 1 && /^fi$/ { skip = 0; next } skip == 0 { print } ' "$PROFILE_FILE" > "$tmp_file" 2>/dev/null || true # Check if file changed if [ -f "$tmp_file" ] && ! cmp -s "$PROFILE_FILE" "$tmp_file" 2>/dev/null; then mv "$tmp_file" "$PROFILE_FILE" printf ' %s✓%s Removed wizardry lines from %s\n' "$GREEN" "$RESET" "$PROFILE_FILE" else rm -f "$tmp_file" fi # If profile file is now empty or only contains whitespace/comments, delete it if [ -f "$PROFILE_FILE" ]; then # Check if file has only whitespace and comments if ! grep -qE '^[^#[:space:]]' "$PROFILE_FILE" 2>/dev/null; then # File only has comments/whitespace - safe to delete if it was created by us # We know it was created by us if it still exists and we modified it # The awk above already removed our content, so if it's empty, we created it if [ ! -s "$PROFILE_FILE" ] || ! grep -qE '^[^#[:space:]]' "$PROFILE_FILE" 2>/dev/null; then rm -f "$PROFILE_FILE" printf ' %s✓%s Removed empty profile file %s\n' "$GREEN" "$RESET" "$PROFILE_FILE" fi fi fi fi # On NixOS, run nixos-rebuild to apply the configuration changes if [ "$RC_FORMAT" = "nix" ]; then printf '\n%s==> Rebuilding NixOS configuration...%s\n' "$CYAN" "$RESET" rebuild_success=0 # Try to detect if we should use sudo or doas priv_cmd="" if command -v sudo >/dev/null 2>&1; then priv_cmd="sudo" elif command -v doas >/dev/null 2>&1; then priv_cmd="doas" fi # Try home-manager first (doesn't need privileges) if command -v home-manager >/dev/null 2>&1; then printf ' Running home-manager switch...\n' if home-manager switch; then printf ' %s✓%s NixOS environment rebuilt successfully\n' "$GREEN" "$RESET" rebuild_success=1 fi fi # If home-manager didn't work and we have privilege escalation, try nixos-rebuild if [ "$rebuild_success" -eq 0 ] && [ -n "$priv_cmd" ] && command -v nixos-rebuild >/dev/null 2>&1; then printf ' Running %s nixos-rebuild switch...\n' "$priv_cmd" if "$priv_cmd" nixos-rebuild switch; then printf ' %s✓%s NixOS environment rebuilt successfully\n' "$GREEN" "$RESET" rebuild_success=1 fi fi if [ "$rebuild_success" -eq 0 ]; then printf ' %sNote:%s NixOS configuration could not be automatically rebuilt.\n' "$YELLOW" "$RESET" printf ' To activate changes, use one of these commands:\n' printf ' home-manager switch (for home-manager users)\n' printf ' sudo nixos-rebuild switch (for system-level configuration)\n' fi fi # Remove wizardry directory if it was downloaded (not local source) if [ -z "$INSTALL_WAS_LOCAL" ]; then printf '\n%sDo you want to delete the wizardry directory?%s [y/N] ' "$BOLD" "$RESET" read -r delete_dir case $delete_dir in y|Y|yes|YES) printf '\n%s==> Removing wizardry directory...%s\n' "$CYAN" "$RESET" if [ -d "$INSTALL_DIR" ]; then if rm -rf "$INSTALL_DIR"; then printf ' %s✓%s Deleted %s\n' "$GREEN" "$RESET" "$INSTALL_DIR" else printf ' %sError:%s Could not delete %s\n' "$RED" "$RESET" "$INSTALL_DIR" exit 1 fi fi ;; *) printf '\n%sNote:%s Wizardry directory kept at %s\n' "$YELLOW" "$RESET" "$INSTALL_DIR" ;; esac fi printf '\n%s✓ Wizardry uninstalled successfully%s\n' "$BOLD$GREEN" "$RESET" # Show platform-specific guidance for when changes take effect if [ "$RC_FORMAT" = "nix" ]; then printf '\nWizardry shell integration has been removed. Open a new terminal window to complete the process.\n\n' else printf '\nWizardry shell integration has been removed. New terminal windows will not have wizardry commands available.\n\n' fi # Self-destruct - safely remove the uninstall script itself SELF_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)" || SELF_DIR="." SELF_NAME="$(basename "$0")" if [ -n "$SELF_DIR" ] && [ -n "$SELF_NAME" ]; then rm -f "$SELF_DIR/$SELF_NAME" fi UNINSTALL_MAIN chmod +x "$uninstall_script" return 0 } # Create uninstall script silently - only notify on failure if ! create_uninstall_script; then printf '%sWarning:%s Could not create uninstall script at %s/.uninstall\n' "$YELLOW" "$RESET" "$ABS_DIR" >&2 fi section_msg "Installation Complete!" # Only claim success if NixOS rebuild succeeded (or it's not a NixOS install) if [ "$detect_format_value" != "nix" ] || [ "$rebuild_success" -eq 1 ]; then success_msg "Wizardry has been successfully installed" else printf '%sNote:%s Wizardry files are installed, but NixOS configuration needs to be rebuilt manually.\n' "$YELLOW" "$RESET" fi # Show platform-specific guidance for when changes take effect printf '\n' if [ "$detect_format_value" = "nix" ]; then if [ "$rebuild_success" -eq 1 ]; then # Rebuild succeeded - wizardry available in new terminals printf 'Wizardry is ready! Open a new terminal window, then run %smenu%s or %smud%s to start using it.\n' "$CYAN" "$RESET" "$CYAN" "$RESET" printf '\n' else # Rebuild failed - need manual intervention printf '%sNote:%s After rebuilding your NixOS configuration, open a new terminal to use wizardry.\n' "$YELLOW" "$RESET" printf '\n' printf 'After rebuilding and opening a new terminal, run %smenu%s or %smud%s to start using wizardry.\n' "$CYAN" "$RESET" "$CYAN" "$RESET" fi elif [ -n "$detect_rc_file_value" ]; then printf 'Wizardry has been installed successfully!\n' printf '\n' printf 'Open a new terminal window, then run %smenu%s or %smud%s to start using wizardry.\n' "$CYAN" "$RESET" "$CYAN" "$RESET" fi printf '\n' # Mark installation as completed to prevent cleanup of INSTALL_DIR INSTALL_COMPLETED=1 exit 0