.agents/skills/mac-server-setup/scripts/setup-and-harden.sh

326 lines
9.6 KiB
Bash
Executable File

#!/bin/bash
set -e
# ==========================================
# Configuration — customize per client
# ==========================================
HOSTNAME="${HOSTNAME:-my-server}"
NVIM_REPO="${NVIM_REPO:-https://git.nicholai.work/Nicholai/nvim.git}"
CLIENT_DIR="${CLIENT_DIR:-}" # e.g. ".solvr", ".acme" — leave empty to skip
echo "========================================"
echo " Mac Server — Setup & Hardening"
echo " Hostname: $HOSTNAME"
echo "========================================"
echo ""
# ==========================================
# PART 1: Dev environment
# ==========================================
# 1a. Homebrew PATH
echo "--- Homebrew PATH ---"
if ! grep -q 'brew shellenv' ~/.zprofile 2>/dev/null; then
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
echo "added to .zprofile"
else
echo "already configured"
fi
eval "$(/opt/homebrew/bin/brew shellenv)"
# 1b. Install packages
echo "--- Packages ---"
for pkg in neovim tmux git starship gh node; do
if brew list "$pkg" &>/dev/null; then
echo " $pkg: installed"
else
brew install "$pkg"
echo " $pkg: installed now"
fi
done
if [ -x ~/.bun/bin/bun ]; then
echo " bun: installed"
else
curl -fsSL https://bun.sh/install | bash
echo " bun: installed now"
fi
# 1b2. Git identity
echo "--- Git identity ---"
if [ -z "$(git config --global user.name)" ]; then
git config --global user.name "${GIT_USER_NAME:-$(whoami)}"
git config --global user.email "${GIT_USER_EMAIL:-$(whoami)@users.noreply.github.com}"
echo "set to $(git config --global user.name) <$(git config --global user.email)>"
else
echo "already configured: $(git config --global user.name)"
fi
# 1b3. gh credential helper (enables git push over HTTPS)
echo "--- gh git credentials ---"
if gh auth status &>/dev/null; then
gh auth setup-git
echo "configured"
else
echo "skipped (gh not authenticated — run 'gh auth login' first)"
fi
# 1c. Nvim config
echo "--- Nvim config ---"
if [ -d ~/.config/nvim/.git ]; then
echo "already cloned"
else
mkdir -p ~/.config
git clone "$NVIM_REPO" ~/.config/nvim
echo "cloned"
fi
# 1d. config.json
echo "--- config.json ---"
if [ -f ~/.config/nvim/config.json ]; then
echo "already exists"
else
cat > ~/.config/nvim/config.json << 'EOF'
{
"paths": {
"obsidianVault": "~/Documents/obsidian-vault/",
"srcDirectory": "~/Developer/",
"scriptsDirectory": "~/scripts/",
"wallpaperScript": "~/scripts/pywal/wallpapermenu.sh"
},
"editor": {
"tabSize": 4,
"scrollOffset": 8,
"theme": "wave"
},
"ai": {
"model": "claude-sonnet-4-5",
"openCodeModel": "anthropic/claude-sonnet-4-5"
},
"lsp": {
"servers": ["ts_ls", "eslint", "jsonls", "html", "cssls", "tailwindcss"]
},
"treesitter": {
"languages": ["lua", "vim", "bash", "javascript", "typescript", "tsx", "json", "yaml", "html", "css"]
}
}
EOF
echo "written"
fi
# 1e. Dotfile symlinks
echo "--- Symlinks ---"
ln -sf ~/.config/nvim/dotfiles/.tmux.conf ~/.tmux.conf
ln -sf ~/.config/nvim/dotfiles/starship.toml ~/.config/starship.toml
echo "done"
# 1f. Nvim plugins
echo "--- Nvim plugins ---"
nvim --headless "+Lazy! sync" +qa 2>/dev/null || true
echo "done"
# 1g. Shell aliases
echo "--- Shell aliases ---"
if grep -q "# Added by nvim dotfiles setup" ~/.zshrc 2>/dev/null; then
echo "already configured"
else
cat >> ~/.zshrc << 'ALIASES'
# Added by nvim dotfiles setup
# Git shortcuts
alias gs='git status'
alias ga='git add'
alias gc='git commit -m'
alias gp='git push'
alias gl='git log --oneline'
alias ff='fastfetch'
ALIASES
echo "added (part 1)"
fi
if ! grep -q "alias cldy=" ~/.zshrc 2>/dev/null; then
cat >> ~/.zshrc << 'ALIASES'
# Claude Aliases
alias cldy='claude --dangerously-skip-permissions'
alias cldyh='claude --dangerously-skip-permissions --model haiku'
alias cldys='claude --dangerously-skip-permissions --model sonnet'
alias cldyo='claude --dangerously-skip-permissions --model opus'
# Directory aliases
alias home='cd ~'
alias cd..='cd ..'
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
chpwd() { ls }
mkdirg() { mkdir -p "$1" && cd "$1"; }
# Editor and prompt
export EDITOR=nvim
export VISUAL=nvim
alias vim='nvim'
eval "$(starship init zsh)"
ALIASES
echo "added (part 2)"
fi
# ==========================================
# PART 2: Server hardening
# ==========================================
echo ""
echo "--- Power management ---"
sudo pmset -a displaysleep 0 disksleep 0 sleep 0 \
powernap 0 autorestart 1 networkoversleep 1
echo "done"
echo "--- Application firewall ---"
FW=/usr/libexec/ApplicationFirewall/socketfilterfw
sudo $FW --setglobalstate on
sudo $FW --setallowsigned on
sudo $FW --setstealthmode on
echo "done"
echo "--- SMB hardening ---"
# Remove all existing share points dynamically.
# IMPORTANT: macOS often uses Unicode curly quotes (') in share names,
# which breaks hardcoded strings. Always parse from `sharing -l` output.
sharing -l 2>/dev/null | grep "^name:" | sed 's/name:[[:space:]]*//' | while read -r name; do
sudo sharing -r "$name" 2>/dev/null && \
echo " removed share: $name" || echo " skip share: $name"
done
SMBPREF=/Library/Preferences/SystemConfiguration/com.apple.smb.server
sudo defaults write $SMBPREF AllowGuestAccess -bool false
echo "done"
echo "--- Disabling consumer services ---"
UID_NUM=$(id -u)
for svc in \
com.apple.Siri.agent \
com.apple.siriactionsd \
com.apple.siriknowledged \
com.apple.siriinferenced \
com.apple.sirittsd \
com.apple.siri-distributed-evaluation \
com.apple.cloudphotod \
com.apple.CloudPhotosConfiguration \
com.apple.photolibraryd \
com.apple.gamed \
com.apple.GameController.gamecontrolleragentd \
com.apple.GamePolicyAgent \
com.apple.newsd \
com.apple.weatherd \
com.apple.tipsd \
com.apple.Maps.mapssyncd \
com.apple.findmymacmessenger \
com.apple.icloud.findmydeviced.findmydevice-user-agent \
com.apple.homed \
com.apple.homeenergyd \
com.apple.itunescloudd
do
launchctl disable "gui/$UID_NUM/$svc" 2>/dev/null && \
echo " disabled: $svc" || echo " skip: $svc"
done
echo "done"
echo "--- Third-party bloat ---"
# Keeping Zoom and Google/Chrome updaters
echo "skipped (zoom + chrome kept)"
echo "--- Hostname ---"
sudo scutil --set ComputerName "$HOSTNAME"
sudo scutil --set HostName "$HOSTNAME"
sudo scutil --set LocalHostName "$HOSTNAME"
echo "done"
echo "--- Spotlight ---"
sudo mdutil -a -i off
echo "done"
echo "--- Software update ---"
SUPREF=/Library/Preferences/com.apple.SoftwareUpdate
sudo defaults write $SUPREF AutomaticInstall -bool false
echo "done"
echo "--- Screen Sharing (VNC) ---"
KICKSTART=/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart
# Deactivate first for clean state
sudo $KICKSTART -deactivate -stop 2>/dev/null || true
# Must use -allUsers (not -specifiedUsers, which breaks naprivs)
# Must set VNC legacy mode with explicit password for non-macOS clients
sudo $KICKSTART \
-activate -configure \
-allowAccessFor -allUsers \
-privs -all \
-clientopts -setvnclegacy -vnclegacy yes \
-setvncpw -vncpw "${VNC_PASSWORD:-changeme}" \
-restart -agent
echo "done (set VNC_PASSWORD env var before running)"
echo "--- Visual effects ---"
# Disable Liquid Glass, transparency, animations — saves GPU/CPU on server
defaults write com.apple.universalaccess reduceTransparency -bool true
defaults write com.apple.universalaccess reduceMotion -bool true
defaults write NSGlobalDomain NSAutomaticWindowAnimationsEnabled -bool false
defaults write com.apple.dock launchanim -bool false
defaults write com.apple.dock expose-animation-duration -float 0.1
defaults write NSGlobalDomain NSWindowResizeTime -float 0.001
killall Dock 2>/dev/null || true
echo "done"
# ==========================================
# PART 3: Git repos
# ==========================================
echo ""
echo "--- Git repos ---"
# ~/.agents (signet agent identity)
if [ -d ~/.agents/.git ]; then
echo " ~/.agents: already a repo"
else
echo " ~/.agents: skipped (init separately with signet)"
fi
# ~/.<client> (server config + scripts)
if [ -n "$CLIENT_DIR" ]; then
CLIENT_PATH=~/"$CLIENT_DIR"
if [ -d "$CLIENT_PATH/.git" ]; then
echo " ~/$CLIENT_DIR: already a repo"
elif [ -d "$CLIENT_PATH" ]; then
cd "$CLIENT_PATH" && git init && git add -A && git commit -m "initial commit"
echo " ~/$CLIENT_DIR: initialized (add remote with 'git remote add upstream <url>')"
cd ~
else
echo " ~/$CLIENT_DIR: directory doesn't exist, skipped"
fi
else
echo " client dir: skipped (CLIENT_DIR not set)"
fi
# ~/.config/nvim (nvim config — may already have origin from clone)
if [ -d ~/.config/nvim/.git ]; then
echo " ~/.config/nvim: already a repo"
# If cloned from Gitea, user may want to re-init with a new origin
NVIM_REMOTE=$(cd ~/.config/nvim && git remote get-url upstream 2>/dev/null || echo "")
if [ -n "$NVIM_REMOTE" ]; then
echo " upstream: $NVIM_REMOTE"
else
echo " no upstream remote (add with 'git remote add upstream <url>')"
fi
fi
# Push all repos that have an upstream remote
REPO_DIRS=(~/.agents ~/.config/nvim)
[ -n "$CLIENT_DIR" ] && REPO_DIRS+=(~/"$CLIENT_DIR")
for dir in "${REPO_DIRS[@]}"; do
if [ -d "$dir/.git" ]; then
REMOTE=$(cd "$dir" && git remote get-url upstream 2>/dev/null || echo "")
if [ -n "$REMOTE" ]; then
cd "$dir" && git push upstream main 2>/dev/null && \
echo " pushed: $dir" || echo " push failed: $dir (check auth)"
cd ~
fi
fi
done
echo "done"
echo ""
echo "========================================"
echo " Setup & Hardening complete"
echo "========================================"