#!/usr/bin/env bash set -euo pipefail STACK_NAME="Data and AI kit" PYTHON_VERSION="3.12" FORMULAE=(git mise uv duckdb postgresql@16) CASKS=(docker visual-studio-code) RUNTIMES=("python@${PYTHON_VERSION}") UV_TOOLS=(jupyterlab) SERVICES=(postgresql@16) if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then RESET="$(printf '\033[0m')"; BOLD="$(printf '\033[1m')"; BLUE="$(printf '\033[38;5;39;1m')"; CYAN="$(printf '\033[38;5;117;1m')"; GREEN="$(printf '\033[32;1m')"; YELLOW="$(printf '\033[33;1m')"; RED="$(printf '\033[38;5;196;1m')"; DIM="$(printf '\033[2m')" else RESET=""; BOLD=""; BLUE=""; CYAN=""; GREEN=""; YELLOW=""; RED=""; DIM="" fi STEP=0 TOTAL_STEPS=8 say() { printf '%b\n' "$*"; } blank() { printf '\n'; } paint() { printf '%b%s%b' "$1" "$2" "$RESET"; } fail() { blank; say "$(paint "$RED" "Macish failed:") $*"; exit 1; } warn() { say "$(paint "$YELLOW" "warning:") $*"; } note() { say " $(paint "$CYAN" "->") $*"; } step() { STEP=$((STEP + 1)); blank; say "$(paint "$BLUE" "==>") $(paint "$BOLD" "[$STEP/$TOTAL_STEPS] $1")"; } run() { say "$(paint "$DIM" " $*")"; "$@"; } banner() { blank say "$(paint "$BLUE" 'Macish')" blank say "$(paint "$BOLD" "$STACK_NAME")" say "Python, uv, JupyterLab, DuckDB, Docker, PostgreSQL, and VS Code." } confirm_ready() { [[ "${MACISH_YES:-${MACI_YES:-}}" == "1" || "${CI:-}" == "true" ]] && return [[ -r /dev/tty ]] || return blank say "$(paint "$YELLOW" "Ready to install?") Press return to continue, or Ctrl-C to stop." read -r _ >"$file" note "Added shell config: $line" fi } ensure_xcode_clt() { if xcode-select -p >/dev/null 2>&1; then note "Xcode Command Line Tools already installed"; return; fi run xcode-select --install || true warn "Apple opened the Command Line Tools installer. Finish it, then run this script again." exit 1 } brew_bin_after_install() { if [[ -x /opt/homebrew/bin/brew ]]; then printf '/opt/homebrew/bin/brew'; elif [[ -x /usr/local/bin/brew ]]; then printf '/usr/local/bin/brew'; else return 1; fi } load_homebrew() { local brew_bin brew_bin="$(command -v brew || true)" [[ -n "$brew_bin" ]] || brew_bin="$(brew_bin_after_install)" || return 1 eval "$("$brew_bin" shellenv)" } ensure_homebrew() { if load_homebrew; then note "Homebrew already installed" else run env NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" load_homebrew || fail "Homebrew installed, but brew was not found on PATH." fi local brew_bin brew_bin="$(command -v brew)" append_once "$HOME/.zprofile" "eval \"\$(${brew_bin} shellenv)\"" } ensure_formula() { local formula="$1" if brew list --formula "$formula" >/dev/null 2>&1; then note "$formula already installed"; else run brew install "$formula"; fi } ensure_cask() { local cask="$1" if brew list --cask "$cask" >/dev/null 2>&1; then note "$cask already installed"; else run brew install --cask "$cask"; fi } activate_mise() { append_once "$HOME/.zshrc" 'eval "$(mise activate zsh)"' eval "$(mise activate bash)" } ensure_runtime() { local runtime="$1" run mise use -g "$runtime" } configure_uv_path() { export PATH="$HOME/.local/bin:$PATH" append_once "$HOME/.zprofile" 'export PATH="$HOME/.local/bin:$PATH"' append_once "$HOME/.zshrc" 'export PATH="$HOME/.local/bin:$PATH"' hash -r } ensure_uv_tool() { local tool="$1" if uv tool list | grep -q "^${tool} "; then note "$tool already installed with uv" else run uv tool install "$tool" fi } ensure_service_started() { local service="$1" if brew services list | awk 'NR > 1 { print $1 ":" $2 }' | grep -q "^${service}:started$"; then note "$service service already started"; else run brew services start "$service"; fi } show_version() { local label="$1"; shift if "$@" >/dev/null 2>&1; then note "$label: $("$@" 2>&1 | head -n 1)"; else warn "$label was installed but is not available in this shell yet."; fi } main() { banner confirm_ready step "Checking macOS prerequisites" require_macos ensure_xcode_clt step "Installing Homebrew" ensure_homebrew step "Installing command line packages" for formula in "${FORMULAE[@]}"; do ensure_formula "$formula"; done step "Configuring Python with mise" activate_mise for runtime in "${RUNTIMES[@]}"; do ensure_runtime "$runtime"; done hash -r step "Installing uv tools" configure_uv_path for tool in "${UV_TOOLS[@]}"; do ensure_uv_tool "$tool"; done step "Installing desktop apps" for cask in "${CASKS[@]}"; do ensure_cask "$cask"; done step "Starting local services" for service in "${SERVICES[@]}"; do ensure_service_started "$service"; done step "Finishing up" show_version "Python" python --version show_version "uv" uv --version show_version "JupyterLab" jupyter-lab --version show_version "DuckDB" duckdb --version show_version "PostgreSQL" postgres --version blank say "$(paint "$GREEN" "Done.") $STACK_NAME is ready." } main "$@"