#!/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 # ~/. (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 ')" 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 ')" 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 "========================================"