326 lines
9.6 KiB
Bash
Executable File
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 "========================================"
|