mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 19:09:43 +00:00
810 lines
24 KiB
Bash
Executable File
810 lines
24 KiB
Bash
Executable File
#!/bin/bash
|
|
# Mole - Installer for manual installs.
|
|
# Fetches source/binaries and installs to prefix.
|
|
# Supports update and edge installs.
|
|
|
|
set -euo pipefail
|
|
|
|
GREEN='\033[0;32m'
|
|
BLUE='\033[0;34m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
NC='\033[0m'
|
|
|
|
_SPINNER_PID=""
|
|
start_line_spinner() {
|
|
local msg="$1"
|
|
[[ ! -t 1 ]] && {
|
|
echo -e "${BLUE}|${NC} $msg"
|
|
return
|
|
}
|
|
local chars="|/-\\"
|
|
[[ -z "$chars" ]] && chars='|/-\\'
|
|
local i=0
|
|
(while true; do
|
|
c="${chars:$((i % ${#chars})):1}"
|
|
printf "\r${BLUE}%s${NC} %s" "$c" "$msg"
|
|
((i++))
|
|
sleep 0.12
|
|
done) &
|
|
_SPINNER_PID=$!
|
|
}
|
|
stop_line_spinner() { if [[ -n "$_SPINNER_PID" ]]; then
|
|
kill "$_SPINNER_PID" 2> /dev/null || true
|
|
wait "$_SPINNER_PID" 2> /dev/null || true
|
|
_SPINNER_PID=""
|
|
printf "\r\033[K"
|
|
fi; }
|
|
|
|
VERBOSE=1
|
|
|
|
# Icons duplicated from lib/core/common.sh (install.sh runs standalone).
|
|
# Avoid readonly to prevent conflicts when sourcing common.sh later.
|
|
ICON_SUCCESS="✓"
|
|
ICON_ADMIN="●"
|
|
ICON_CONFIRM="◎"
|
|
ICON_ERROR="☻"
|
|
|
|
log_info() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${BLUE}$1${NC}"; }
|
|
log_success() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${GREEN}${ICON_SUCCESS}${NC} $1"; }
|
|
log_warning() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${YELLOW}$1${NC}"; }
|
|
log_error() { echo -e "${YELLOW}${ICON_ERROR}${NC} $1"; }
|
|
log_admin() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${BLUE}${ICON_ADMIN}${NC} $1"; }
|
|
log_confirm() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${BLUE}${ICON_CONFIRM}${NC} $1"; }
|
|
|
|
safe_rm() {
|
|
local target="${1:-}"
|
|
local tmp_root
|
|
|
|
if [[ -z "$target" ]]; then
|
|
log_error "safe_rm: empty path"
|
|
return 1
|
|
fi
|
|
if [[ ! -e "$target" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
tmp_root="${TMPDIR:-/tmp}"
|
|
case "$target" in
|
|
"$tmp_root" | /tmp)
|
|
log_error "safe_rm: refusing to remove temp root: $target"
|
|
return 1
|
|
;;
|
|
"$tmp_root"/* | /tmp/*) ;;
|
|
*)
|
|
log_error "safe_rm: refusing to remove non-temp path: $target"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if [[ -d "$target" ]]; then
|
|
find "$target" -depth \( -type f -o -type l \) -exec rm -f {} + 2> /dev/null || true
|
|
find "$target" -depth -type d -exec rmdir {} + 2> /dev/null || true
|
|
else
|
|
rm -f "$target" 2> /dev/null || true
|
|
fi
|
|
}
|
|
|
|
# Install defaults
|
|
INSTALL_DIR="/usr/local/bin"
|
|
CONFIG_DIR="$HOME/.config/mole"
|
|
SOURCE_DIR=""
|
|
|
|
ACTION="install"
|
|
|
|
# Resolve source dir (local checkout, env override, or download).
|
|
needs_sudo() {
|
|
if [[ -e "$INSTALL_DIR" ]]; then
|
|
[[ ! -w "$INSTALL_DIR" ]]
|
|
return
|
|
fi
|
|
|
|
local parent_dir
|
|
parent_dir="$(dirname "$INSTALL_DIR")"
|
|
[[ ! -w "$parent_dir" ]]
|
|
}
|
|
|
|
maybe_sudo() {
|
|
if needs_sudo; then
|
|
sudo "$@"
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
resolve_source_dir() {
|
|
if [[ -n "$SOURCE_DIR" && -d "$SOURCE_DIR" && -f "$SOURCE_DIR/mole" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ -n "${BASH_SOURCE[0]:-}" && -f "${BASH_SOURCE[0]}" ]]; then
|
|
local script_dir
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
if [[ -f "$script_dir/mole" ]]; then
|
|
SOURCE_DIR="$script_dir"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${CLEAN_SOURCE_DIR:-}" && -d "$CLEAN_SOURCE_DIR" && -f "$CLEAN_SOURCE_DIR/mole" ]]; then
|
|
SOURCE_DIR="$CLEAN_SOURCE_DIR"
|
|
return 0
|
|
fi
|
|
|
|
local tmp
|
|
tmp="$(mktemp -d)"
|
|
|
|
# Safe cleanup function for temporary directory
|
|
cleanup_tmp() {
|
|
stop_line_spinner 2> /dev/null || true
|
|
if [[ -z "${tmp:-}" ]]; then
|
|
return 0
|
|
fi
|
|
safe_rm "$tmp"
|
|
}
|
|
trap cleanup_tmp EXIT
|
|
|
|
local branch="${MOLE_VERSION:-}"
|
|
if [[ -z "$branch" ]]; then
|
|
branch="$(get_latest_release_tag || true)"
|
|
fi
|
|
if [[ -z "$branch" ]]; then
|
|
branch="$(get_latest_release_tag_from_git || true)"
|
|
fi
|
|
if [[ -z "$branch" ]]; then
|
|
branch="main"
|
|
fi
|
|
if [[ "$branch" != "main" && "$branch" != "dev" ]]; then
|
|
branch="$(normalize_release_tag "$branch")"
|
|
fi
|
|
local url="https://github.com/tw93/mole/archive/refs/heads/main.tar.gz"
|
|
|
|
if [[ "$branch" == "dev" ]]; then
|
|
url="https://github.com/tw93/mole/archive/refs/heads/dev.tar.gz"
|
|
elif [[ "$branch" != "main" ]]; then
|
|
url="https://github.com/tw93/mole/archive/refs/tags/${branch}.tar.gz"
|
|
fi
|
|
|
|
start_line_spinner "Fetching Mole source (${branch})..."
|
|
if command -v curl > /dev/null 2>&1; then
|
|
if curl -fsSL --connect-timeout 10 --max-time 60 -o "$tmp/mole.tar.gz" "$url" 2> /dev/null; then
|
|
if tar -xzf "$tmp/mole.tar.gz" -C "$tmp" 2> /dev/null; then
|
|
stop_line_spinner
|
|
|
|
local extracted_dir
|
|
extracted_dir=$(find "$tmp" -mindepth 1 -maxdepth 1 -type d | head -n 1)
|
|
|
|
if [[ -n "$extracted_dir" && -f "$extracted_dir/mole" ]]; then
|
|
SOURCE_DIR="$extracted_dir"
|
|
return 0
|
|
fi
|
|
fi
|
|
else
|
|
stop_line_spinner
|
|
# Only exit early for version tags (not for main/dev branches)
|
|
if [[ "$branch" != "main" && "$branch" != "dev" ]]; then
|
|
log_error "Failed to fetch version ${branch}. Check if tag exists."
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
stop_line_spinner
|
|
|
|
start_line_spinner "Cloning Mole source..."
|
|
if command -v git > /dev/null 2>&1; then
|
|
local git_args=("--depth=1")
|
|
if [[ "$branch" != "main" ]]; then
|
|
git_args+=("--branch" "$branch")
|
|
fi
|
|
|
|
if git clone "${git_args[@]}" https://github.com/tw93/mole.git "$tmp/mole" > /dev/null 2>&1; then
|
|
stop_line_spinner
|
|
SOURCE_DIR="$tmp/mole"
|
|
return 0
|
|
fi
|
|
fi
|
|
stop_line_spinner
|
|
|
|
log_error "Failed to fetch source files. Ensure curl or git is available."
|
|
exit 1
|
|
}
|
|
|
|
# Version helpers
|
|
get_source_version() {
|
|
local source_mole="$SOURCE_DIR/mole"
|
|
if [[ -f "$source_mole" ]]; then
|
|
sed -n 's/^VERSION="\(.*\)"$/\1/p' "$source_mole" | head -n1
|
|
fi
|
|
}
|
|
|
|
get_latest_release_tag() {
|
|
local tag
|
|
if ! command -v curl > /dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
tag=$(curl -fsSL --connect-timeout 2 --max-time 3 \
|
|
"https://api.github.com/repos/tw93/mole/releases/latest" 2> /dev/null |
|
|
sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)
|
|
if [[ -z "$tag" ]]; then
|
|
return 1
|
|
fi
|
|
printf '%s\n' "$tag"
|
|
}
|
|
|
|
get_latest_release_tag_from_git() {
|
|
if ! command -v git > /dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
git ls-remote --tags --refs https://github.com/tw93/mole.git 2> /dev/null |
|
|
awk -F/ '{print $NF}' |
|
|
grep -E '^V[0-9]' |
|
|
sort -V |
|
|
tail -n 1
|
|
}
|
|
|
|
normalize_release_tag() {
|
|
local tag="$1"
|
|
while [[ "$tag" =~ ^[vV] ]]; do
|
|
tag="${tag#v}"
|
|
tag="${tag#V}"
|
|
done
|
|
if [[ -n "$tag" ]]; then
|
|
printf 'V%s\n' "$tag"
|
|
fi
|
|
}
|
|
|
|
get_installed_version() {
|
|
local binary="$INSTALL_DIR/mole"
|
|
if [[ -x "$binary" ]]; then
|
|
local version
|
|
version=$("$binary" --version 2> /dev/null | awk '/Mole version/ {print $NF; exit}')
|
|
if [[ -n "$version" ]]; then
|
|
echo "$version"
|
|
else
|
|
sed -n 's/^VERSION="\(.*\)"$/\1/p' "$binary" | head -n1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# CLI parsing (supports main/latest and version tokens).
|
|
parse_args() {
|
|
local -a args=("$@")
|
|
local version_token=""
|
|
local i skip_next=false
|
|
for i in "${!args[@]}"; do
|
|
local token="${args[$i]}"
|
|
[[ -z "$token" ]] && continue
|
|
# Skip values for options that take arguments
|
|
if [[ "$skip_next" == "true" ]]; then
|
|
skip_next=false
|
|
continue
|
|
fi
|
|
if [[ "$token" == "--prefix" || "$token" == "--config" ]]; then
|
|
skip_next=true
|
|
continue
|
|
fi
|
|
if [[ "$token" == -* ]]; then
|
|
continue
|
|
fi
|
|
if [[ -n "$version_token" ]]; then
|
|
log_error "Unexpected argument: $token"
|
|
exit 1
|
|
fi
|
|
case "$token" in
|
|
latest | main)
|
|
export MOLE_VERSION="main"
|
|
export MOLE_EDGE_INSTALL="true"
|
|
version_token="$token"
|
|
unset 'args[$i]'
|
|
;;
|
|
dev)
|
|
export MOLE_VERSION="dev"
|
|
export MOLE_EDGE_INSTALL="true"
|
|
version_token="$token"
|
|
unset 'args[$i]'
|
|
;;
|
|
[0-9]* | V[0-9]* | v[0-9]*)
|
|
export MOLE_VERSION="$token"
|
|
version_token="$token"
|
|
unset 'args[$i]'
|
|
;;
|
|
*)
|
|
log_error "Unknown option: $token"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
if [[ ${#args[@]} -gt 0 ]]; then
|
|
set -- ${args[@]+"${args[@]}"}
|
|
else
|
|
set --
|
|
fi
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--prefix)
|
|
if [[ -z "${2:-}" ]]; then
|
|
log_error "Missing value for --prefix"
|
|
exit 1
|
|
fi
|
|
INSTALL_DIR="$2"
|
|
shift 2
|
|
;;
|
|
--config)
|
|
if [[ -z "${2:-}" ]]; then
|
|
log_error "Missing value for --config"
|
|
exit 1
|
|
fi
|
|
CONFIG_DIR="$2"
|
|
shift 2
|
|
;;
|
|
--update)
|
|
ACTION="update"
|
|
shift 1
|
|
;;
|
|
--verbose | -v)
|
|
VERBOSE=1
|
|
shift 1
|
|
;;
|
|
--help | -h)
|
|
log_error "Unknown option: $1"
|
|
exit 1
|
|
;;
|
|
*)
|
|
log_error "Unknown option: $1"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Environment checks and directory setup
|
|
check_requirements() {
|
|
if [[ "$OSTYPE" != "darwin"* ]]; then
|
|
log_error "This tool is designed for macOS only"
|
|
exit 1
|
|
fi
|
|
|
|
if command -v brew > /dev/null 2>&1 && brew list mole > /dev/null 2>&1; then
|
|
local mole_path
|
|
mole_path=$(command -v mole 2> /dev/null || true)
|
|
local is_homebrew_binary=false
|
|
|
|
if [[ -n "$mole_path" && -L "$mole_path" ]]; then
|
|
if readlink "$mole_path" | grep -q "Cellar/mole"; then
|
|
is_homebrew_binary=true
|
|
fi
|
|
fi
|
|
|
|
if [[ "$is_homebrew_binary" == "true" ]]; then
|
|
if [[ "$ACTION" == "update" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
echo -e "${YELLOW}Mole is installed via Homebrew${NC}"
|
|
echo ""
|
|
echo "Choose one:"
|
|
echo -e " 1. Update via Homebrew: ${GREEN}brew upgrade mole${NC}"
|
|
echo -e " 2. Switch to manual: ${GREEN}brew uninstall --force mole${NC} then re-run this"
|
|
echo ""
|
|
exit 1
|
|
else
|
|
log_warning "Cleaning up stale Homebrew installation..."
|
|
brew uninstall --force mole > /dev/null 2>&1 || true
|
|
fi
|
|
fi
|
|
|
|
if [[ ! -d "$(dirname "$INSTALL_DIR")" ]]; then
|
|
log_error "Parent directory $(dirname "$INSTALL_DIR") does not exist"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
create_directories() {
|
|
if [[ ! -d "$INSTALL_DIR" ]]; then
|
|
maybe_sudo mkdir -p "$INSTALL_DIR"
|
|
fi
|
|
|
|
if ! mkdir -p "$CONFIG_DIR" "$CONFIG_DIR/bin" "$CONFIG_DIR/lib"; then
|
|
log_error "Failed to create config directory: $CONFIG_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
}
|
|
|
|
# Binary install helpers
|
|
build_binary_from_source() {
|
|
local binary_name="$1"
|
|
local target_path="$2"
|
|
local cmd_dir=""
|
|
|
|
case "$binary_name" in
|
|
analyze)
|
|
cmd_dir="cmd/analyze"
|
|
;;
|
|
status)
|
|
cmd_dir="cmd/status"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if ! command -v go > /dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ ! -d "$SOURCE_DIR/$cmd_dir" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ -t 1 ]]; then
|
|
start_line_spinner "Building ${binary_name} from source..."
|
|
else
|
|
echo "Building ${binary_name} from source..."
|
|
fi
|
|
|
|
if (cd "$SOURCE_DIR" && go build -ldflags="-s -w" -o "$target_path" "./$cmd_dir" > /dev/null 2>&1); then
|
|
if [[ -t 1 ]]; then stop_line_spinner; fi
|
|
chmod +x "$target_path"
|
|
log_success "Built ${binary_name} from source"
|
|
return 0
|
|
fi
|
|
|
|
if [[ -t 1 ]]; then stop_line_spinner; fi
|
|
log_warning "Failed to build ${binary_name} from source"
|
|
return 1
|
|
}
|
|
|
|
download_binary() {
|
|
local binary_name="$1"
|
|
local target_path="$CONFIG_DIR/bin/${binary_name}-go"
|
|
local arch
|
|
arch=$(uname -m)
|
|
local arch_suffix="amd64"
|
|
if [[ "$arch" == "arm64" ]]; then
|
|
arch_suffix="arm64"
|
|
fi
|
|
|
|
if [[ -f "$SOURCE_DIR/bin/${binary_name}-go" ]]; then
|
|
cp "$SOURCE_DIR/bin/${binary_name}-go" "$target_path"
|
|
chmod +x "$target_path"
|
|
log_success "Installed local ${binary_name} binary"
|
|
return 0
|
|
elif [[ -f "$SOURCE_DIR/bin/${binary_name}-darwin-${arch_suffix}" ]]; then
|
|
cp "$SOURCE_DIR/bin/${binary_name}-darwin-${arch_suffix}" "$target_path"
|
|
chmod +x "$target_path"
|
|
log_success "Installed local ${binary_name} binary"
|
|
return 0
|
|
fi
|
|
|
|
if [[ "${MOLE_EDGE_INSTALL:-}" == "true" ]]; then
|
|
if build_binary_from_source "$binary_name" "$target_path"; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
local version
|
|
version=$(get_source_version)
|
|
if [[ -z "$version" ]]; then
|
|
log_warning "Could not determine version for ${binary_name}, trying local build"
|
|
if build_binary_from_source "$binary_name" "$target_path"; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
fi
|
|
local url="https://github.com/tw93/mole/releases/download/V${version}/${binary_name}-darwin-${arch_suffix}"
|
|
|
|
# Skip preflight network checks to avoid false negatives.
|
|
|
|
if [[ -t 1 ]]; then
|
|
start_line_spinner "Downloading ${binary_name}..."
|
|
else
|
|
echo "Downloading ${binary_name}..."
|
|
fi
|
|
|
|
if curl -fsSL --connect-timeout 10 --max-time 60 -o "$target_path" "$url"; then
|
|
if [[ -t 1 ]]; then stop_line_spinner; fi
|
|
chmod +x "$target_path"
|
|
log_success "Downloaded ${binary_name} binary"
|
|
else
|
|
if [[ -t 1 ]]; then stop_line_spinner; fi
|
|
log_warning "Could not download ${binary_name} binary (v${version}), trying local build"
|
|
if build_binary_from_source "$binary_name" "$target_path"; then
|
|
return 0
|
|
fi
|
|
log_error "Failed to install ${binary_name} binary"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# File installation (bin/lib/scripts + go helpers).
|
|
install_files() {
|
|
|
|
resolve_source_dir
|
|
|
|
local source_dir_abs
|
|
local install_dir_abs
|
|
local config_dir_abs
|
|
source_dir_abs="$(cd "$SOURCE_DIR" && pwd)"
|
|
install_dir_abs="$(cd "$INSTALL_DIR" && pwd)"
|
|
config_dir_abs="$(cd "$CONFIG_DIR" && pwd)"
|
|
|
|
if [[ -f "$SOURCE_DIR/mole" ]]; then
|
|
if [[ "$source_dir_abs" != "$install_dir_abs" ]]; then
|
|
if needs_sudo; then
|
|
log_admin "Admin access required for /usr/local/bin"
|
|
fi
|
|
|
|
# Atomic update: copy to temporary name first, then move
|
|
maybe_sudo cp "$SOURCE_DIR/mole" "$INSTALL_DIR/mole.new"
|
|
maybe_sudo chmod +x "$INSTALL_DIR/mole.new"
|
|
maybe_sudo mv -f "$INSTALL_DIR/mole.new" "$INSTALL_DIR/mole"
|
|
|
|
log_success "Installed mole to $INSTALL_DIR"
|
|
fi
|
|
else
|
|
log_error "mole executable not found in ${SOURCE_DIR:-unknown}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -f "$SOURCE_DIR/mo" ]]; then
|
|
if [[ "$source_dir_abs" == "$install_dir_abs" ]]; then
|
|
log_success "mo alias already present"
|
|
else
|
|
maybe_sudo cp "$SOURCE_DIR/mo" "$INSTALL_DIR/mo.new"
|
|
maybe_sudo chmod +x "$INSTALL_DIR/mo.new"
|
|
maybe_sudo mv -f "$INSTALL_DIR/mo.new" "$INSTALL_DIR/mo"
|
|
log_success "Installed mo alias"
|
|
fi
|
|
fi
|
|
|
|
if [[ -d "$SOURCE_DIR/bin" ]]; then
|
|
local source_bin_abs="$(cd "$SOURCE_DIR/bin" && pwd)"
|
|
local config_bin_abs="$(cd "$CONFIG_DIR/bin" && pwd)"
|
|
if [[ "$source_bin_abs" == "$config_bin_abs" ]]; then
|
|
log_success "Modules already synced"
|
|
else
|
|
local -a bin_files=("$SOURCE_DIR/bin"/*)
|
|
if [[ ${#bin_files[@]} -gt 0 ]]; then
|
|
cp -r "${bin_files[@]}" "$CONFIG_DIR/bin/"
|
|
for file in "$CONFIG_DIR/bin/"*; do
|
|
[[ -e "$file" ]] && chmod +x "$file"
|
|
done
|
|
log_success "Installed modules"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ -d "$SOURCE_DIR/lib" ]]; then
|
|
local source_lib_abs="$(cd "$SOURCE_DIR/lib" && pwd)"
|
|
local config_lib_abs="$(cd "$CONFIG_DIR/lib" && pwd)"
|
|
if [[ "$source_lib_abs" == "$config_lib_abs" ]]; then
|
|
log_success "Libraries already synced"
|
|
else
|
|
local -a lib_files=("$SOURCE_DIR/lib"/*)
|
|
if [[ ${#lib_files[@]} -gt 0 ]]; then
|
|
cp -r "${lib_files[@]}" "$CONFIG_DIR/lib/"
|
|
log_success "Installed libraries"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ "$config_dir_abs" != "$source_dir_abs" ]]; then
|
|
for file in README.md LICENSE install.sh; do
|
|
if [[ -f "$SOURCE_DIR/$file" ]]; then
|
|
cp -f "$SOURCE_DIR/$file" "$CONFIG_DIR/"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ -f "$CONFIG_DIR/install.sh" ]]; then
|
|
chmod +x "$CONFIG_DIR/install.sh"
|
|
fi
|
|
|
|
if [[ "$source_dir_abs" != "$install_dir_abs" ]]; then
|
|
maybe_sudo sed -i '' "s|SCRIPT_DIR=.*|SCRIPT_DIR=\"$CONFIG_DIR\"|" "$INSTALL_DIR/mole"
|
|
fi
|
|
|
|
if ! download_binary "analyze"; then
|
|
exit 1
|
|
fi
|
|
if ! download_binary "status"; then
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Verification and PATH hint
|
|
verify_installation() {
|
|
|
|
if [[ -x "$INSTALL_DIR/mole" ]] && [[ -f "$CONFIG_DIR/lib/core/common.sh" ]]; then
|
|
|
|
if "$INSTALL_DIR/mole" --help > /dev/null 2>&1; then
|
|
return 0
|
|
else
|
|
log_warning "Mole command installed but may not be working properly"
|
|
fi
|
|
else
|
|
log_error "Installation verification failed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
setup_path() {
|
|
if [[ ":$PATH:" == *":$INSTALL_DIR:"* ]]; then
|
|
return
|
|
fi
|
|
|
|
if [[ "$INSTALL_DIR" != "/usr/local/bin" ]]; then
|
|
log_warning "$INSTALL_DIR is not in your PATH"
|
|
echo ""
|
|
echo "To use mole from anywhere, add this line to your shell profile:"
|
|
echo "export PATH=\"$INSTALL_DIR:\$PATH\""
|
|
echo ""
|
|
echo "For example, add it to ~/.zshrc or ~/.bash_profile"
|
|
fi
|
|
}
|
|
|
|
print_usage_summary() {
|
|
local action="$1"
|
|
local new_version="$2"
|
|
local previous_version="${3:-}"
|
|
|
|
if [[ ${VERBOSE} -ne 1 ]]; then
|
|
return
|
|
fi
|
|
|
|
echo ""
|
|
|
|
local message="Mole ${action} successfully"
|
|
|
|
if [[ "$action" == "updated" && -n "$previous_version" && -n "$new_version" && "$previous_version" != "$new_version" ]]; then
|
|
message+=" (${previous_version} -> ${new_version})"
|
|
elif [[ -n "$new_version" ]]; then
|
|
message+=" (version ${new_version})"
|
|
fi
|
|
|
|
log_confirm "$message"
|
|
|
|
echo ""
|
|
echo "Usage:"
|
|
if [[ ":$PATH:" == *":$INSTALL_DIR:"* ]]; then
|
|
echo " mo # Interactive menu"
|
|
echo " mo clean # Deep cleanup"
|
|
echo " mo uninstall # Remove apps + leftovers"
|
|
echo " mo optimize # Check and maintain system"
|
|
echo " mo analyze # Explore disk usage"
|
|
echo " mo status # Monitor system health"
|
|
echo " mo touchid # Configure Touch ID for sudo"
|
|
echo " mo update # Update to latest version"
|
|
echo " mo --help # Show all commands"
|
|
else
|
|
echo " $INSTALL_DIR/mo # Interactive menu"
|
|
echo " $INSTALL_DIR/mo clean # Deep cleanup"
|
|
echo " $INSTALL_DIR/mo uninstall # Remove apps + leftovers"
|
|
echo " $INSTALL_DIR/mo optimize # Check and maintain system"
|
|
echo " $INSTALL_DIR/mo analyze # Explore disk usage"
|
|
echo " $INSTALL_DIR/mo status # Monitor system health"
|
|
echo " $INSTALL_DIR/mo touchid # Configure Touch ID for sudo"
|
|
echo " $INSTALL_DIR/mo update # Update to latest version"
|
|
echo " $INSTALL_DIR/mo --help # Show all commands"
|
|
fi
|
|
echo ""
|
|
}
|
|
|
|
# Main install/update flows
|
|
perform_install() {
|
|
resolve_source_dir
|
|
local source_version
|
|
source_version="$(get_source_version || true)"
|
|
|
|
check_requirements
|
|
create_directories
|
|
install_files
|
|
verify_installation
|
|
setup_path
|
|
|
|
local installed_version
|
|
installed_version="$(get_installed_version || true)"
|
|
|
|
if [[ -z "$installed_version" ]]; then
|
|
installed_version="$source_version"
|
|
fi
|
|
|
|
# Edge installs get a suffix to make the version explicit.
|
|
if [[ "${MOLE_EDGE_INSTALL:-}" == "true" ]]; then
|
|
installed_version="${installed_version}-edge"
|
|
echo ""
|
|
local branch_name="${MOLE_VERSION:-main}"
|
|
log_warning "Edge version installed on ${branch_name} branch"
|
|
log_info "This is a testing version; use 'mo update' to switch to stable"
|
|
fi
|
|
|
|
print_usage_summary "installed" "$installed_version"
|
|
}
|
|
|
|
perform_update() {
|
|
check_requirements
|
|
|
|
if command -v brew > /dev/null 2>&1 && brew list mole > /dev/null 2>&1; then
|
|
resolve_source_dir 2> /dev/null || true
|
|
local current_version
|
|
current_version=$(get_installed_version || echo "unknown")
|
|
if [[ -f "$SOURCE_DIR/lib/core/common.sh" ]]; then
|
|
# shellcheck disable=SC1090,SC1091
|
|
source "$SOURCE_DIR/lib/core/common.sh"
|
|
update_via_homebrew "$current_version"
|
|
else
|
|
log_error "Cannot update Homebrew-managed Mole without full installation"
|
|
echo ""
|
|
echo "Please update via Homebrew:"
|
|
echo -e " ${GREEN}brew upgrade mole${NC}"
|
|
exit 1
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
local installed_version
|
|
installed_version="$(get_installed_version || true)"
|
|
|
|
if [[ -z "$installed_version" ]]; then
|
|
log_warning "Mole is not currently installed in $INSTALL_DIR. Running fresh installation."
|
|
perform_install
|
|
return
|
|
fi
|
|
|
|
resolve_source_dir
|
|
local target_version
|
|
target_version="$(get_source_version || true)"
|
|
|
|
if [[ -z "$target_version" ]]; then
|
|
log_error "Unable to determine the latest Mole version."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$installed_version" == "$target_version" ]]; then
|
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version ($installed_version)"
|
|
exit 0
|
|
fi
|
|
|
|
local old_verbose=$VERBOSE
|
|
VERBOSE=0
|
|
create_directories || {
|
|
VERBOSE=$old_verbose
|
|
log_error "Failed to create directories"
|
|
exit 1
|
|
}
|
|
install_files || {
|
|
VERBOSE=$old_verbose
|
|
log_error "Failed to install files"
|
|
exit 1
|
|
}
|
|
verify_installation || {
|
|
VERBOSE=$old_verbose
|
|
log_error "Failed to verify installation"
|
|
exit 1
|
|
}
|
|
setup_path
|
|
VERBOSE=$old_verbose
|
|
|
|
local updated_version
|
|
updated_version="$(get_installed_version || true)"
|
|
|
|
if [[ -z "$updated_version" ]]; then
|
|
updated_version="$target_version"
|
|
fi
|
|
|
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version ($updated_version)"
|
|
}
|
|
|
|
parse_args "$@"
|
|
|
|
case "$ACTION" in
|
|
update)
|
|
perform_update
|
|
;;
|
|
*)
|
|
perform_install
|
|
;;
|
|
esac
|