diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100755 index 0000000..f81f1fe --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,144 @@ +#!/bin/bash +# setup.sh — run ON the Mac as seth, via scripts/run.sh from steel141. +# Idempotent: every section prints [skip] when already in the desired state. +set -euo pipefail + +REPO="$HOME/mac" +BK="$HOME/.mac-setup-backup"; TS=$(date +%s); mkdir -p "$BK" +CHANGED=0 + +log(){ printf '\033[1;33m[%s]\033[0m %s\n' "$1" "$2"; } + +# ---------- preflight ---------- +[[ $(uname -m) == arm64 && $(sw_vers -productVersion) == 26.* ]] || { echo "not the Mac this was written for"; exit 1; } +[[ ${HOSTNAME_WANT:-} ]] || { echo "HOSTNAME_WANT= required"; exit 1; } +sudo -v # ticket already primed by run.sh over the pty; prompts if run by hand +( while true; do sudo -n true; sleep 50; done ) & +KEEPALIVE=$!; trap 'kill $KEEPALIVE 2>/dev/null' EXIT + +# ---------- helpers ---------- +backup_domain(){ # once per domain per run + local d=$1; local f="$BK/${d//\//_}-$TS.plist" + [[ -e $f || -e $f.absent ]] && return 0 + defaults export "$d" "$f" 2>/dev/null || : > "$f.absent" +} +setd(){ # setd + local d=$1 k=$2 t=$3 v=$4 cur want=$4 + [[ $t == bool ]] && { [[ $v == true ]] && want=1 || want=0; } + cur=$(defaults read "$d" "$k" 2>/dev/null || echo __unset__) + if [[ $cur == "$want" ]]; then log skip "$d $k=$v"; return 0; fi + backup_domain "$d"; defaults write "$d" "$k" "-$t" "$v"; log set "$d $k=$v"; CHANGED=1 +} +install_marker(){ # install_marker — ensure a line exists in file (create if absent) + local f=$1 line=$2 + if [[ -f $f ]] && grep -qxF "$line" "$f"; then log skip "$f has include"; return 0; fi + [[ -f $f ]] && cp "$f" "$BK/$(basename "$f")-$TS" + mkdir -p "$(dirname "$f")"; printf '%s\n' "$line" >> "$f"; log set "$f += $line"; CHANGED=1 +} + +# ---------- brew ---------- +if [[ -x /opt/homebrew/bin/brew ]]; then log skip "homebrew installed"; else + log set "installing homebrew (installs Xcode CLT headless first; 5-15 min)" + NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + CHANGED=1 +fi +eval "$(/opt/homebrew/bin/brew shellenv)" +if brew bundle check --file="$REPO/Brewfile" >/dev/null 2>&1; then log skip "brew bundle satisfied"; else + log set "brew bundle"; brew bundle --file="$REPO/Brewfile"; CHANGED=1 +fi + +# ---------- shell ---------- +grep -qx /opt/homebrew/bin/bash /etc/shells || { echo /opt/homebrew/bin/bash | sudo tee -a /etc/shells >/dev/null; log set "/etc/shells += homebrew bash"; } +if [[ $(dscl . -read "/Users/$USER" UserShell | awk '{print $2}') == /opt/homebrew/bin/bash ]]; then log skip "login shell bash5"; else + sudo chsh -s /opt/homebrew/bin/bash "$USER"; log set "login shell -> homebrew bash"; CHANGED=1 +fi +install_marker "$HOME/.bash_profile" '[ -f ~/mac/config/bashrc ] && . ~/mac/config/bashrc # mac-setup' +install_marker "$HOME/.config/kitty/kitty.conf" "include $HOME/mac/config/kitty.conf" + +# ---------- hostname ---------- +for k in ComputerName LocalHostName HostName; do + if [[ $(scutil --get $k 2>/dev/null || true) == "$HOSTNAME_WANT" ]]; then log skip "$k=$HOSTNAME_WANT"; else + sudo scutil --set $k "$HOSTNAME_WANT"; log set "$k=$HOSTNAME_WANT"; CHANGED=1 + fi +done + +# ---------- ssh (homelab) ---------- +mkdir -p "$HOME/.ssh"; chmod 700 "$HOME/.ssh" +if [[ -f $HOME/.ssh/id_ed25519 ]]; then log skip "ssh key exists"; else + ssh-keygen -t ed25519 -N '' -C "seth@$HOSTNAME_WANT" -f "$HOME/.ssh/id_ed25519" >/dev/null; log set "generated ~/.ssh/id_ed25519"; CHANGED=1 +fi +if [[ -f $HOME/.ssh/config ]] && grep -q '^Include ~/mac/config/ssh_homelab' "$HOME/.ssh/config"; then log skip "ssh config include"; else + [[ -f $HOME/.ssh/config ]] && cp "$HOME/.ssh/config" "$BK/ssh_config-$TS" + { echo 'Include ~/mac/config/ssh_homelab'; [[ -f $HOME/.ssh/config ]] && cat "$HOME/.ssh/config"; } > "$HOME/.ssh/config.new" + mv "$HOME/.ssh/config.new" "$HOME/.ssh/config"; chmod 600 "$HOME/.ssh/config"; log set "ssh config include"; CHANGED=1 +fi +ssh-keygen -F 192.168.0.173 >/dev/null || { ssh-keyscan -t ed25519 192.168.0.173 >> "$HOME/.ssh/known_hosts" 2>/dev/null; log set "known_hosts += pve173"; } + +# ---------- defaults: Linux feel ---------- +G=NSGlobalDomain +setd $G KeyRepeat int 2 +setd $G InitialKeyRepeat int 15 +setd $G ApplePressAndHoldEnabled bool false # hold-key repeats instead of accent popup +setd $G com.apple.swipescrolldirection bool false # "natural" scrolling off +setd $G NSAutomaticWindowAnimationsEnabled bool false +setd $G AppleShowAllExtensions bool true +F=com.apple.finder +setd $F ShowPathbar bool true +setd $F ShowStatusBar bool true +setd $F _FXShowPosixPathInTitle bool true +setd $F FXPreferredViewStyle string Nlsv # list view +setd $F NewWindowTarget string PfHm # new windows open at ~ +setd $F NewWindowTargetPath string "file://$HOME/" +setd com.apple.desktopservices DSDontWriteNetworkStores bool true +setd com.apple.desktopservices DSDontWriteUSBStores bool true + +# ---------- defaults: debloat / Dock ---------- +D=com.apple.dock +setd $D autohide bool true +setd $D autohide-delay int 0 +setd $D show-recents bool false +setd $D tilesize int 48 +for c in tl tr bl br; do setd $D wvous-$c-corner int 1; done # 1 = no action (stock br = Quick Note) +# Dock apps: only what exists. Finder + Trash are implicit. +dock_want=() +for app in /Applications/kitty.app "/Applications/Ableton Live 12"*.app "/System/Applications/System Settings.app"; do + [[ -d $app ]] && dock_want+=("file://${app// /%20}/") +done +dock_cur=$(defaults read $D persistent-apps 2>/dev/null | grep -oE '_CFURLString" = "[^"]+' | sed 's/.*= "//' | tr '\n' ' ' || true) +if [[ "$dock_cur" == "${dock_want[*]} " ]]; then log skip "dock apps"; else + backup_domain $D + defaults write $D persistent-apps -array + for u in "${dock_want[@]}"; do + defaults write $D persistent-apps -array-add "tile-datafile-data_CFURLString$u_CFURLStringType15tile-typefile-tile" + done + log set "dock apps = ${dock_want[*]}"; CHANGED=1 +fi + +# ---------- stray network service ---------- +if networksetup -listallnetworkservices | grep -qx 'SparkFun Pro Micro'; then + sudo networksetup -removenetworkservice 'SparkFun Pro Micro'; log set "removed SparkFun Pro Micro PPP service"; CHANGED=1 +else log skip "no SparkFun service"; fi + +# ---------- power (DAW) — AC profile only ---------- +pm_cur=$(pmset -g custom | awk '/AC Power/{f=1;next} /Battery Power/{f=0} f && $1 ~ /^(sleep|displaysleep|disksleep|powernap)$/{printf "%s=%s ", $1, $2}') +if [[ $pm_cur == *"sleep=0 "* && $pm_cur == *"displaysleep=30 "* && $pm_cur == *"disksleep=0 "* && $pm_cur == *"powernap=0 "* ]]; then log skip "pmset AC profile"; else + pmset -g custom > "$BK/pmset-$TS.txt" + sudo pmset -c sleep 0 displaysleep 30 disksleep 0 powernap 0; log set "pmset -c sleep 0 displaysleep 30 disksleep 0 powernap 0"; CHANGED=1 +fi +setd com.ableton.live NSAppSleepDisabled bool true # App Nap off for Live (domain exists before install; harmless) + +# ---------- backup agent ---------- +PL="$HOME/Library/LaunchAgents/xyz.sethpc.mac-backup.plist" +if [[ -f $PL ]] && cmp -s "$REPO/config/xyz.sethpc.mac-backup.plist" "$PL"; then log skip "backup launchd agent"; else + mkdir -p "$HOME/Library/LaunchAgents"; cp "$REPO/config/xyz.sethpc.mac-backup.plist" "$PL" + launchctl bootout "gui/$(id -u)/xyz.sethpc.mac-backup" 2>/dev/null || true + launchctl bootstrap "gui/$(id -u)" "$PL"; log set "backup launchd agent (03:30 nightly)"; CHANGED=1 +fi + +# ---------- apply ---------- +[[ $CHANGED == 1 ]] && { killall Dock Finder SystemUIServer 2>/dev/null || true; log set "restarted Dock/Finder"; } + +# ---------- done ---------- +echo; log pubkey "$(cat "$HOME/.ssh/id_ed25519.pub")" +[[ $CHANGED == 1 ]] && log note "some changes (key repeat, scroll direction) apply fully after logout/login" +exit 0