Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6643ac003c | |||
| 0d733c77e7 |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0"><dict>
|
||||
<key>Label</key><string>xyz.sethpc.mac-backup</string>
|
||||
<key>ProgramArguments</key><array><string>/Users/seth/mac/scripts/backup.sh</string></array>
|
||||
<key>StartCalendarInterval</key><dict><key>Hour</key><integer>3</integer><key>Minute</key><integer>30</integer></dict>
|
||||
<key>StandardOutPath</key><string>/Users/seth/Library/Logs/mac-backup.log</string>
|
||||
<key>StandardErrorPath</key><string>/Users/seth/Library/Logs/mac-backup.log</string>
|
||||
</dict></plist>
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/opt/homebrew/bin/bash
|
||||
# Nightly by launchd (config/xyz.sethpc.mac-backup.plist). Mirrors the DAW-relevant
|
||||
# dirs to tank. History comes from sanoid snapshots of tank/backups/mac on pve173,
|
||||
# which is what makes `--delete` safe.
|
||||
set -euo pipefail
|
||||
src=()
|
||||
for d in "$HOME/Music/Ableton" "$HOME/Documents"; do [[ -d $d ]] && src+=("$d"); done
|
||||
[[ ${#src[@]} -gt 0 ]] || { echo "nothing to back up yet"; exit 0; }
|
||||
# Remote path is relative to the rrsync root (/tank/backups/mac) set in root's authorized_keys on pve173.
|
||||
exec /opt/homebrew/bin/rsync -a --delete -e 'ssh -o BatchMode=yes' "${src[@]}" root@192.168.0.173:/
|
||||
Executable
+144
@@ -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=<name> 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 <domain> <key> <bool|int|string> <value>
|
||||
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 <file> <line> — 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 "<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>$u</string><key>_CFURLStringType</key><integer>15</integer></dict></dict><key>tile-type</key><string>file-tile</string></dict>"
|
||||
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
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Run on pve173: ssh pve173 'bash -s' -- "<pubkey>" < scripts/tank-side.sh
|
||||
set -euo pipefail
|
||||
PUB=${1:?pubkey line required}
|
||||
[[ $PUB == ssh-ed25519* ]] || { echo "not a pubkey: $PUB"; exit 1; }
|
||||
DS=tank/backups/mac; TS=$(date +%s)
|
||||
zfs list "$DS" >/dev/null 2>&1 && echo "[skip] dataset $DS" || { zfs create "$DS"; echo "[set] created $DS"; }
|
||||
if grep -q "^\[$DS\]" /etc/sanoid/sanoid.conf; then echo "[skip] sanoid stanza"; else
|
||||
mkdir -p /etc/sanoid/.backup; cp /etc/sanoid/sanoid.conf "/etc/sanoid/.backup/sanoid.conf-$TS"
|
||||
printf '\n[%s]\n\tuse_template = tank_media\n' "$DS" >> /etc/sanoid/sanoid.conf; echo "[set] sanoid stanza"
|
||||
fi
|
||||
KEYLINE="restrict,command=\"/usr/bin/rrsync /$DS\" $PUB"
|
||||
if grep -qF "$PUB" /root/.ssh/authorized_keys; then echo "[skip] key present"; else
|
||||
cp /root/.ssh/authorized_keys "/root/.ssh/authorized_keys.bak-$TS"
|
||||
echo "$KEYLINE" >> /root/.ssh/authorized_keys; echo "[set] rrsync-restricted key added"
|
||||
fi
|
||||
Reference in New Issue
Block a user