My nix-darwin and NixOS config
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: implement dynamic Tailscale SSH and unified update system

Add Tailscale-based SSH connectivity and multi-system update management.

SSH Changes:
- Configure SSH to route inter-host connections through Tailscale using ProxyCommand
- Use dynamic `tailscale nc` instead of hardcoded IPs for laptop, server, macmini
- Add platform-aware Tailscale binary paths (Nix on Linux, Homebrew on macOS)
- Trust tailscale0 interface in firewalls on laptop and server

Server Infrastructure:
- Enable Tailscale service on server via new modules/server/services.nix
- Add Tailscale to macOS Homebrew casks for macmini

Update Management:
- Add `update-all` script: updates current system (flake inputs, rebuild, Homebrew, cleanup)
- Add `update-everything` script: remotely updates all online Tailscale hosts via SSH
- Add convenience aliases: update-all, update-everything
- Both scripts support NixOS and macOS with platform detection

Verification & Documentation:
- Add `verify-tailscale-ssh` script to diagnose Tailscale SSH connectivity
- Add docs/TAILSCALE-SSH.md: comprehensive Tailscale SSH setup guide
- Add docs/UPDATE-GUIDE.md: complete system update reference

This enables seamless SSH between all hosts over Tailscale's encrypted mesh network
and provides one-command updates for the entire infrastructure.

+942 -16
+137
docs/TAILSCALE-SSH.md
··· 1 + # Tailscale SSH Configuration 2 + 3 + This configuration enables dynamic SSH connections between your hosts (laptop, server, macmini) over Tailscale's encrypted mesh network. 4 + 5 + ## How It Works 6 + 7 + ### Dynamic Routing via ProxyCommand 8 + Instead of hardcoding Tailscale IP addresses, the SSH configuration uses `ProxyCommand` with `tailscale nc` (netcat). This means: 9 + 10 + 1. When you run `ssh laptop`, SSH doesn't connect directly 11 + 2. Instead, it invokes `tailscale nc laptop 22` 12 + 3. Tailscale dynamically routes the connection through its mesh network 13 + 4. The connection automatically follows the host even if its Tailscale IP changes 14 + 15 + ### Benefits 16 + - **Dynamic**: No hardcoded IP addresses - works even if Tailscale IPs change 17 + - **Automatic**: Tailscale handles routing, NAT traversal, and encryption 18 + - **Fast**: Direct peer-to-peer when possible, relayed when necessary 19 + - **Secure**: All traffic encrypted through Tailscale's WireGuard-based mesh 20 + 21 + ## Configuration Files 22 + 23 + - `home/programs/ssh.nix` - Defines SSH hosts and ProxyCommand routing 24 + - `hosts/laptop/default.nix` - Laptop firewall trusts tailscale0 25 + - `modules/server/firewall.nix` - Server firewall trusts tailscale0 26 + - `settings/config/darwin.nix` - macOS Tailscale via Homebrew 27 + 28 + ## Initial Setup 29 + 30 + ### 1. Ensure Tailscale is Running 31 + On each host: 32 + ```bash 33 + # Check status 34 + tailscale status 35 + 36 + # If not running, start it 37 + sudo tailscale up 38 + 39 + # Optional: Set a hostname (if different from system hostname) 40 + sudo tailscale set --hostname=your-hostname 41 + ``` 42 + 43 + ### 2. Enable MagicDNS (if not already enabled) 44 + MagicDNS allows you to use short hostnames like `laptop` instead of IPs: 45 + ```bash 46 + # This is usually enabled by default 47 + # Check at: https://login.tailscale.com/admin/dns 48 + ``` 49 + 50 + ### 3. Rebuild Configuration 51 + On each host, rebuild to apply the changes: 52 + 53 + **Laptop/Server (NixOS)**: 54 + ```bash 55 + cd ~/.config/nix-config 56 + sudo nixos-rebuild switch --flake .#laptop # or .#server 57 + ``` 58 + 59 + **MacMini (macOS)**: 60 + ```bash 61 + cd ~/.config/nix-config 62 + sudo darwin-rebuild switch --flake .#macmini 63 + ``` 64 + 65 + ### 4. Verify Setup 66 + Run the verification script: 67 + ```bash 68 + verify-tailscale-ssh 69 + ``` 70 + 71 + ## Usage 72 + 73 + ### Simple SSH 74 + Just use the hostname: 75 + ```bash 76 + ssh laptop 77 + ssh server 78 + ssh macmini 79 + ``` 80 + 81 + ### With rsync 82 + ```bash 83 + rsync -av ~/Documents/ server:~/backup/ 84 + ``` 85 + 86 + ### With scp 87 + ```bash 88 + scp file.txt laptop:~/ 89 + ``` 90 + 91 + ### With git 92 + If you have git repos on your hosts: 93 + ```bash 94 + git clone server:~/projects/myrepo.git 95 + ``` 96 + 97 + ## Troubleshooting 98 + 99 + ### "tailscale: command not found" 100 + - **Linux**: Ensure you rebuilt with the updated configuration 101 + - **macOS**: Ensure Homebrew is in PATH: `eval "$(/opt/homebrew/bin/brew shellenv)"` 102 + 103 + ### "Connection refused" or "Connection timed out" 104 + 1. Verify Tailscale is running: `tailscale status` 105 + 2. Check the target host is online in Tailscale 106 + 3. Verify firewall allows tailscale0: `sudo firewall-cmd --list-all` (if using firewalld) 107 + 4. Test Tailscale connectivity: `tailscale ping laptop` 108 + 109 + ### "Host key verification failed" 110 + This is normal on first connection. Accept the host key: 111 + ```bash 112 + ssh laptop # Type "yes" when prompted 113 + ``` 114 + 115 + ### SSH hangs or is slow 116 + 1. Check Tailscale connection quality: `tailscale status` 117 + 2. Force direct connection: `sudo tailscale up --accept-routes=false` 118 + 3. Check if relay is being used: Look for "relay" in `tailscale status` 119 + 120 + ## Security Notes 121 + 122 + - **Firewall**: The firewall trusts the tailscale0 interface completely 123 + - **Authentication**: Still requires SSH key authentication (keys in authorized_keys) 124 + - **Encryption**: Tailscale provides additional encryption layer beyond SSH 125 + - **Access Control**: Managed through Tailscale ACLs at login.tailscale.com 126 + 127 + ## Adding New Hosts 128 + 129 + To add a new host to the Tailscale SSH mesh: 130 + 131 + 1. Edit `home/programs/ssh.nix` 132 + 2. Add the hostname to the `internalHosts` list: 133 + ```nix 134 + internalHosts = [ "laptop" "server" "macmini" "newhost" ]; 135 + ``` 136 + 3. Rebuild configuration on all hosts 137 + 4. Ensure the new host has appropriate SSH keys in authorized_keys
+282
docs/UPDATE-GUIDE.md
··· 1 + # System Update Guide 2 + 3 + This guide covers how to update your NixOS configuration across all your systems. 4 + 5 + ## Quick Commands 6 + 7 + ### Update Current System Only 8 + ```bash 9 + update-all 10 + ``` 11 + This will: 12 + 1. Update flake.lock (all Nix inputs) 13 + 2. Rebuild the current system 14 + 3. Update Homebrew packages (macOS only) 15 + 4. Optionally run garbage collection 16 + 17 + ### Update All Systems at Once 18 + ```bash 19 + update-everything 20 + ``` 21 + This will: 22 + 1. Check which systems are online via Tailscale 23 + 2. Update flake.lock on each system 24 + 3. Rebuild each system remotely over SSH 25 + 4. Show a summary of successes/failures 26 + 27 + **Recommended**: Run this from **macmini** since it's always on. 28 + 29 + ## Manual Update Commands 30 + 31 + ### Update Just the Flake Inputs 32 + ```bash 33 + cd ~/.config/nix-config 34 + nix flake update 35 + ``` 36 + 37 + This updates all dependencies in flake.lock: 38 + - nixpkgs (NixOS/Darwin packages) 39 + - home-manager (user environment) 40 + - nix-darwin (macOS system management) 41 + - All other inputs 42 + 43 + ### Rebuild Current System 44 + ```bash 45 + # NixOS (laptop, server) 46 + nrs # Alias for: sudo nixos-rebuild switch --flake .#hostname 47 + 48 + # macOS (macmini) 49 + nrs # Alias for: sudo darwin-rebuild switch --flake .#macmini 50 + ``` 51 + 52 + ### Update a Remote System 53 + ```bash 54 + # Update laptop from any other machine 55 + ssh laptop 'cd ~/.config/nix-config && nix flake update && sudo nixos-rebuild switch --flake .#laptop' 56 + 57 + # Update server 58 + ssh server 'cd ~/.config/nix-config && nix flake update && sudo nixos-rebuild switch --flake .#server' 59 + 60 + # Update macmini 61 + ssh macmini 'cd ~/.config/nix-config && nix flake update && sudo darwin-rebuild switch --flake .#macmini' 62 + ``` 63 + 64 + ## Update Specific Components 65 + 66 + ### Update Only Specific Flake Inputs 67 + ```bash 68 + cd ~/.config/nix-config 69 + 70 + # Update just nixpkgs 71 + nix flake lock --update-input nixpkgs 72 + 73 + # Update just home-manager 74 + nix flake lock --update-input home-manager 75 + 76 + # Update multiple inputs 77 + nix flake lock --update-input nixpkgs --update-input home-manager 78 + ``` 79 + 80 + ### Homebrew Only (macOS) 81 + ```bash 82 + brew update # Update Homebrew itself 83 + brew upgrade # Upgrade all packages 84 + brew upgrade tailscale # Upgrade specific package 85 + ``` 86 + 87 + ### Garbage Collection 88 + ```bash 89 + # Alias (works on all systems) 90 + cleanup 91 + 92 + # Manual (NixOS) 93 + sudo nix-collect-garbage -d # System profiles 94 + nix-collect-garbage -d # User profiles 95 + 96 + # Manual (macOS) 97 + sudo nix-collect-garbage -d # All profiles 98 + ``` 99 + 100 + ## Update Workflow Recommendations 101 + 102 + ### Daily Updates (Automated) 103 + NixOS systems (laptop, server) have automatic updates enabled: 104 + - Runs daily with 45-minute random delay 105 + - Updates only nixpkgs input 106 + - Does NOT reboot automatically 107 + - Configured in `settings/config/maintenance.nix` 108 + 109 + ### Manual Updates (Recommended Weekly) 110 + 111 + **Option 1: Update Everything from One Machine** 112 + ```bash 113 + # From macmini (recommended since it's always on) 114 + update-everything 115 + ``` 116 + 117 + **Option 2: Update Each System Individually** 118 + ```bash 119 + # On each system, run: 120 + update-all 121 + ``` 122 + 123 + **Option 3: Hybrid Approach** 124 + ```bash 125 + # Update macmini locally 126 + update-all 127 + 128 + # Update Linux systems remotely 129 + ssh laptop 'cd ~/.config/nix-config && nix flake update && sudo nixos-rebuild switch --flake .#laptop' 130 + ssh server 'cd ~/.config/nix-config && nix flake update && sudo nixos-rebuild switch --flake .#server' 131 + ``` 132 + 133 + ## Checking for Updates 134 + 135 + ### View Available Updates 136 + ```bash 137 + cd ~/.config/nix-config 138 + nix flake lock --update-input nixpkgs --dry-run 139 + ``` 140 + 141 + ### Compare Generations 142 + ```bash 143 + # See what changed between generations 144 + gen-diff # Custom tool if available 145 + 146 + # Or manually: 147 + nix profile diff-closures --profile /nix/var/nix/profiles/system 148 + ``` 149 + 150 + ### Check Current Versions 151 + ```bash 152 + # System version 153 + nixos-version # NixOS 154 + sw_vers # macOS 155 + 156 + # Nix version 157 + nix --version 158 + 159 + # Package versions 160 + nix-env -q # User packages 161 + ``` 162 + 163 + ## Rollback If Needed 164 + 165 + ### NixOS 166 + ```bash 167 + # List generations 168 + sudo nix-env --list-generations --profile /nix/var/nix/profiles/system 169 + 170 + # Rollback to previous generation 171 + sudo nixos-rebuild switch --rollback 172 + 173 + # Boot into specific generation 174 + sudo nixos-rebuild boot --rollback 175 + ``` 176 + 177 + ### macOS (nix-darwin) 178 + ```bash 179 + # List generations 180 + darwin-rebuild --list-generations 181 + 182 + # Rollback 183 + sudo darwin-rebuild switch --rollback 184 + ``` 185 + 186 + ## Troubleshooting Updates 187 + 188 + ### Flake Update Fails 189 + ```bash 190 + # Try updating inputs individually 191 + nix flake lock --update-input nixpkgs 192 + nix flake lock --update-input home-manager 193 + 194 + # Check for syntax errors 195 + nix flake check 196 + ``` 197 + 198 + ### Build Fails 199 + ```bash 200 + # Try building without switching 201 + sudo nixos-rebuild build --flake .#hostname 202 + 203 + # Check for errors 204 + nix-store --verify --check-contents 205 + ``` 206 + 207 + ### Remote Update Fails 208 + ```bash 209 + # Check SSH connectivity 210 + ssh hostname echo "Connected" 211 + 212 + # Check Tailscale 213 + tailscale status 214 + 215 + # Manually update 216 + ssh hostname 217 + cd ~/.config/nix-config 218 + sudo nixos-rebuild switch --flake .#hostname 219 + ``` 220 + 221 + ### Out of Disk Space 222 + ```bash 223 + # Aggressive garbage collection 224 + sudo nix-collect-garbage --delete-older-than 7d 225 + nix-store --gc 226 + nix-store --optimise 227 + ``` 228 + 229 + ## Best Practices 230 + 231 + 1. **Always commit changes** before updating: 232 + ```bash 233 + cd ~/.config/nix-config 234 + git add -A 235 + git commit -m "Pre-update checkpoint" 236 + ``` 237 + 238 + 2. **Test updates on one system first** (recommend laptop): 239 + ```bash 240 + ssh laptop 'cd ~/.config/nix-config && update-all' 241 + ``` 242 + 243 + 3. **Update all systems regularly** (weekly recommended): 244 + ```bash 245 + update-everything 246 + ``` 247 + 248 + 4. **Keep a rollback plan**: 249 + - NixOS can always rollback to previous generation 250 + - Commit flake.lock changes to git for easy reversion 251 + 252 + 5. **Monitor disk space**: 253 + ```bash 254 + df -h 255 + # Run cleanup if needed 256 + cleanup 257 + ``` 258 + 259 + ## Update Schedule 260 + 261 + ### Recommended Schedule 262 + 263 + - **Daily**: Let automatic updates run (NixOS only) 264 + - **Weekly**: Run `update-everything` to update all systems 265 + - **Monthly**: Run `cleanup` on all systems 266 + - **After Major Changes**: Test on one system before deploying to all 267 + 268 + ### Automatic Updates 269 + 270 + Current configuration (`settings/config/maintenance.nix`): 271 + - **Enabled**: Yes (NixOS only) 272 + - **Frequency**: Daily 273 + - **Updates**: nixpkgs input 274 + - **Auto-reboot**: No 275 + 276 + To disable: 277 + ```nix 278 + autoUpgrade = { 279 + enable = false; 280 + # ... 281 + }; 282 + ```
+43 -13
home/programs/ssh.nix
··· 3 3 4 4 let 5 5 cfg = cfgLib.cfg; 6 + userName = cfg.user.username; 7 + 8 + # Tailscale binary path differs by platform 9 + # macOS: Homebrew Cask provides CLI in PATH after brew shellenv 10 + # Linux: Nix package provides the binary 11 + tailscaleBin = if isDarwin 12 + then "tailscale" # Rely on Homebrew PATH 13 + else "${pkgs.tailscale}/bin/tailscale"; 14 + 15 + # Define our internal Tailscale hosts 16 + # These will connect dynamically through Tailscale using ProxyCommand 17 + internalHosts = [ "laptop" "server" "macmini" ]; 18 + 19 + # Create SSH host blocks for Tailscale hosts with dynamic routing 20 + tailscaleHostBlocks = lib.listToAttrs (map (hostName: { 21 + name = hostName; 22 + value = { 23 + user = userName; 24 + proxyCommand = "${tailscaleBin} nc %h %p"; 25 + extraOptions = { 26 + # Connection multiplexing over Tailscale 27 + ControlMaster = "auto"; 28 + ControlPath = "~/.ssh/sockets/tailscale-%r@%h-%p"; 29 + ControlPersist = "600"; 30 + }; 31 + }; 32 + }) internalHosts); 6 33 in 7 34 { 8 35 programs.ssh = { 9 36 enable = true; 10 37 enableDefaultConfig = false; 11 38 12 - # Global SSH configuration for all hosts 13 - matchBlocks."*" = { 14 - extraOptions = { 15 - # Reuse connections for speed 16 - ControlMaster = "auto"; 17 - ControlPath = "~/.ssh/sockets/%r@%h-%p"; 18 - ControlPersist = "600"; 19 - 20 - # Automatically add keys to agent 21 - AddKeysToAgent = "yes"; 22 - } // lib.optionalAttrs isDarwin { 23 - # macOS: Use Keychain for SSH keys 24 - UseKeychain = "yes"; 39 + # Tailscale host configurations with dynamic ProxyCommand routing 40 + matchBlocks = tailscaleHostBlocks // { 41 + # Global SSH configuration for all other hosts (git forges, etc.) 42 + "*" = { 43 + extraOptions = { 44 + # Reuse connections for speed 45 + ControlMaster = "auto"; 46 + ControlPath = "~/.ssh/sockets/%r@%h-%p"; 47 + ControlPersist = "600"; 48 + 49 + # Automatically add keys to agent 50 + AddKeysToAgent = "yes"; 51 + } // lib.optionalAttrs isDarwin { 52 + # macOS: Use Keychain for SSH keys 53 + UseKeychain = "yes"; 54 + }; 25 55 }; 26 56 }; 27 57 };
+171
home/scripts/update-all
··· 1 + #!/usr/bin/env bash 2 + # Update everything: flake inputs, system configuration, and packages 3 + 4 + set -e 5 + 6 + # Colors for output 7 + RED='\033[0;31m' 8 + GREEN='\033[0;32m' 9 + YELLOW='\033[1;33m' 10 + BLUE='\033[0;34m' 11 + NC='\033[0m' # No Color 12 + 13 + print_header() { 14 + echo -e "${BLUE}===${NC} $1 ${BLUE}===${NC}" 15 + } 16 + 17 + print_success() { 18 + echo -e "${GREEN}✓${NC} $1" 19 + } 20 + 21 + print_warning() { 22 + echo -e "${YELLOW}⚠${NC} $1" 23 + } 24 + 25 + print_error() { 26 + echo -e "${RED}✗${NC} $1" 27 + } 28 + 29 + # Detect platform 30 + if [[ "$OSTYPE" == "darwin"* ]]; then 31 + IS_DARWIN=true 32 + PLATFORM="macOS" 33 + HOSTNAME=$(scutil --get ComputerName 2>/dev/null || hostname) 34 + else 35 + IS_DARWIN=false 36 + PLATFORM="NixOS" 37 + HOSTNAME=$(hostname) 38 + fi 39 + 40 + cd ~/.config/nix-config || { 41 + print_error "Could not find ~/.config/nix-config" 42 + exit 1 43 + } 44 + 45 + echo 46 + print_header "System Update ($PLATFORM - $HOSTNAME)" 47 + echo 48 + 49 + # Step 1: Update flake inputs 50 + print_header "Step 1: Updating Flake Inputs" 51 + echo "Current flake.lock status:" 52 + git log -1 --oneline flake.lock 2>/dev/null || echo " (no previous commits)" 53 + echo 54 + 55 + if nix flake update; then 56 + print_success "Flake inputs updated" 57 + echo 58 + echo "Changes:" 59 + git diff flake.lock | grep -E "^\+|^\-" | grep -v "@@" || echo " (no changes)" 60 + else 61 + print_error "Failed to update flake inputs" 62 + exit 1 63 + fi 64 + echo 65 + 66 + # Step 2: Rebuild current system 67 + print_header "Step 2: Rebuilding Current System" 68 + echo 69 + 70 + if $IS_DARWIN; then 71 + # macOS: darwin-rebuild 72 + FLAKE_REF=".#$HOSTNAME" 73 + echo "Rebuilding: darwin-rebuild switch --flake $FLAKE_REF" 74 + echo 75 + 76 + if sudo darwin-rebuild switch --flake "$FLAKE_REF"; then 77 + print_success "macOS system rebuilt successfully" 78 + else 79 + print_error "darwin-rebuild failed" 80 + exit 1 81 + fi 82 + else 83 + # NixOS: nixos-rebuild 84 + FLAKE_REF=".#$HOSTNAME" 85 + echo "Rebuilding: nixos-rebuild switch --flake $FLAKE_REF" 86 + echo 87 + 88 + if sudo nixos-rebuild switch --flake "$FLAKE_REF"; then 89 + print_success "NixOS system rebuilt successfully" 90 + else 91 + print_error "nixos-rebuild failed" 92 + exit 1 93 + fi 94 + fi 95 + echo 96 + 97 + # Step 3: macOS Homebrew updates 98 + if $IS_DARWIN; then 99 + print_header "Step 3: Updating Homebrew Packages" 100 + echo 101 + 102 + if command -v brew &>/dev/null; then 103 + echo "Updating Homebrew..." 104 + brew update 105 + echo 106 + 107 + echo "Upgrading packages..." 108 + brew upgrade 109 + echo 110 + 111 + print_success "Homebrew packages updated" 112 + else 113 + print_warning "Homebrew not found, skipping" 114 + fi 115 + echo 116 + fi 117 + 118 + # Step 4: Garbage collection prompt 119 + print_header "Cleanup" 120 + echo 121 + read -p "Run garbage collection to free up space? (y/N): " -n 1 -r 122 + echo 123 + if [[ $REPLY =~ ^[Yy]$ ]]; then 124 + if $IS_DARWIN; then 125 + sudo nix-collect-garbage -d 126 + print_success "Garbage collection complete" 127 + else 128 + sudo nix-collect-garbage -d 129 + nix-collect-garbage -d 130 + print_success "Garbage collection complete" 131 + fi 132 + else 133 + print_warning "Skipped garbage collection" 134 + fi 135 + echo 136 + 137 + # Step 5: Summary and other systems 138 + print_header "Update Complete" 139 + echo 140 + print_success "$HOSTNAME ($PLATFORM) is now up to date" 141 + echo 142 + 143 + # Check if we need to update other systems 144 + if $IS_DARWIN; then 145 + # Running on macOS - remind about Linux systems 146 + echo "Don't forget to update your Linux systems:" 147 + echo " ${YELLOW}laptop${NC}: ssh laptop 'cd ~/.config/nix-config && sudo nixos-rebuild switch --flake .#laptop'" 148 + echo " ${YELLOW}server${NC}: ssh server 'cd ~/.config/nix-config && sudo nixos-rebuild switch --flake .#server'" 149 + else 150 + # Running on Linux - remind about macOS and other Linux systems 151 + echo "Don't forget to update your other systems:" 152 + echo " ${YELLOW}macmini${NC}: ssh macmini 'cd ~/.config/nix-config && sudo darwin-rebuild switch --flake .#macmini'" 153 + 154 + if [[ "$HOSTNAME" != "laptop" ]]; then 155 + echo " ${YELLOW}laptop${NC}: ssh laptop 'cd ~/.config/nix-config && sudo nixos-rebuild switch --flake .#laptop'" 156 + fi 157 + 158 + if [[ "$HOSTNAME" != "server" ]]; then 159 + echo " ${YELLOW}server${NC}: ssh server 'cd ~/.config/nix-config && sudo nixos-rebuild switch --flake .#server'" 160 + fi 161 + fi 162 + echo 163 + 164 + # Check Tailscale version on Linux 165 + if ! $IS_DARWIN && command -v tailscale &>/dev/null; then 166 + TS_VERSION=$(tailscale version | head -n1 | awk '{print $1}') 167 + print_success "Tailscale version: $TS_VERSION" 168 + fi 169 + 170 + echo 171 + print_success "All done! 🎉"
+174
home/scripts/update-everything
··· 1 + #!/usr/bin/env bash 2 + # Update all systems remotely over Tailscale 3 + 4 + set -e 5 + 6 + # Colors 7 + RED='\033[0;31m' 8 + GREEN='\033[0;32m' 9 + YELLOW='\033[1;33m' 10 + BLUE='\033[0;34m' 11 + CYAN='\033[0;36m' 12 + NC='\033[0m' 13 + 14 + print_header() { 15 + echo -e "${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}" 16 + echo -e "${CYAN}║${NC} $1" 17 + echo -e "${CYAN}╚════════════════════════════════════════════════════════════════╝${NC}" 18 + } 19 + 20 + print_host_header() { 21 + echo -e "${BLUE}▶${NC} $1" 22 + } 23 + 24 + print_success() { 25 + echo -e "${GREEN}✓${NC} $1" 26 + } 27 + 28 + print_error() { 29 + echo -e "${RED}✗${NC} $1" 30 + } 31 + 32 + print_warning() { 33 + echo -e "${YELLOW}⚠${NC} $1" 34 + } 35 + 36 + # Define all systems 37 + declare -A SYSTEMS 38 + SYSTEMS[laptop]="nixos" 39 + SYSTEMS[server]="nixos" 40 + SYSTEMS[macmini]="darwin" 41 + 42 + # Detect current hostname 43 + CURRENT_HOST=$(hostname | cut -d'.' -f1) 44 + 45 + echo 46 + print_header "Update All Systems Over Tailscale" 47 + echo 48 + 49 + # Check Tailscale connectivity 50 + echo "Checking Tailscale connectivity..." 51 + if ! command -v tailscale &>/dev/null; then 52 + print_error "Tailscale not found. Install Tailscale first." 53 + exit 1 54 + fi 55 + 56 + # Get online hosts 57 + ONLINE_HOSTS=() 58 + OFFLINE_HOSTS=() 59 + 60 + for host in "${!SYSTEMS[@]}"; do 61 + if [[ "$host" == "$CURRENT_HOST" ]]; then 62 + ONLINE_HOSTS+=("$host") 63 + continue 64 + fi 65 + 66 + if tailscale status | grep -q "^[0-9.]*[[:space:]]*$host"; then 67 + if tailscale status | grep "$host" | grep -q "offline"; then 68 + OFFLINE_HOSTS+=("$host") 69 + else 70 + ONLINE_HOSTS+=("$host") 71 + fi 72 + else 73 + OFFLINE_HOSTS+=("$host") 74 + fi 75 + done 76 + 77 + echo 78 + if [ ${#ONLINE_HOSTS[@]} -gt 0 ]; then 79 + print_success "Online hosts: ${ONLINE_HOSTS[*]}" 80 + fi 81 + 82 + if [ ${#OFFLINE_HOSTS[@]} -gt 0 ]; then 83 + print_warning "Offline hosts: ${OFFLINE_HOSTS[*]}" 84 + fi 85 + 86 + if [ ${#ONLINE_HOSTS[@]} -eq 0 ]; then 87 + print_error "No systems online. Cannot proceed." 88 + exit 1 89 + fi 90 + 91 + echo 92 + read -p "Update ${#ONLINE_HOSTS[@]} online systems? (y/N): " -n 1 -r 93 + echo 94 + if [[ ! $REPLY =~ ^[Yy]$ ]]; then 95 + echo "Cancelled." 96 + exit 0 97 + fi 98 + 99 + echo 100 + 101 + # Function to update a system 102 + update_system() { 103 + local host=$1 104 + local system_type=${SYSTEMS[$host]} 105 + 106 + print_host_header "Updating $host ($system_type)" 107 + echo 108 + 109 + if [[ "$host" == "$CURRENT_HOST" ]]; then 110 + # Local system 111 + echo "Updating local system..." 112 + ~/.config/nix-config/home/scripts/update-all 113 + return $? 114 + fi 115 + 116 + # Remote system 117 + local rebuild_cmd 118 + if [[ "$system_type" == "darwin" ]]; then 119 + rebuild_cmd="cd ~/.config/nix-config && nix flake update && sudo darwin-rebuild switch --flake .#$host" 120 + else 121 + rebuild_cmd="cd ~/.config/nix-config && nix flake update && sudo nixos-rebuild switch --flake .#$host" 122 + fi 123 + 124 + echo "Connecting to $host..." 125 + if ssh -o ConnectTimeout=10 "$host" "$rebuild_cmd"; then 126 + print_success "$host updated successfully" 127 + return 0 128 + else 129 + print_error "$host update failed" 130 + return 1 131 + fi 132 + } 133 + 134 + # Update each system 135 + FAILED_HOSTS=() 136 + for host in "${ONLINE_HOSTS[@]}"; do 137 + echo 138 + if ! update_system "$host"; then 139 + FAILED_HOSTS+=("$host") 140 + fi 141 + echo 142 + echo "────────────────────────────────────────────────────────────────" 143 + done 144 + 145 + # Summary 146 + echo 147 + print_header "Update Summary" 148 + echo 149 + 150 + SUCCESSFUL=$((${#ONLINE_HOSTS[@]} - ${#FAILED_HOSTS[@]})) 151 + echo "Updated: $SUCCESSFUL/${#ONLINE_HOSTS[@]} systems" 152 + 153 + if [ ${#FAILED_HOSTS[@]} -gt 0 ]; then 154 + print_error "Failed: ${FAILED_HOSTS[*]}" 155 + echo 156 + echo "You may need to manually update these systems:" 157 + for host in "${FAILED_HOSTS[@]}"; do 158 + local system_type=${SYSTEMS[$host]} 159 + if [[ "$system_type" == "darwin" ]]; then 160 + echo " ssh $host 'cd ~/.config/nix-config && sudo darwin-rebuild switch --flake .#$host'" 161 + else 162 + echo " ssh $host 'cd ~/.config/nix-config && sudo nixos-rebuild switch --flake .#$host'" 163 + fi 164 + done 165 + else 166 + print_success "All systems updated successfully! 🎉" 167 + fi 168 + 169 + if [ ${#OFFLINE_HOSTS[@]} -gt 0 ]; then 170 + echo 171 + print_warning "Offline systems not updated: ${OFFLINE_HOSTS[*]}" 172 + fi 173 + 174 + echo
+104
home/scripts/verify-tailscale-ssh
··· 1 + #!/usr/bin/env bash 2 + # Verify Tailscale SSH configuration 3 + 4 + set -euo pipefail 5 + 6 + echo "=== Tailscale SSH Verification ===" 7 + echo 8 + 9 + # Check if Tailscale is running 10 + echo "1. Checking Tailscale status..." 11 + if tailscale status &>/dev/null; then 12 + echo " ✓ Tailscale is running" 13 + echo 14 + tailscale status 15 + else 16 + echo " ✗ Tailscale is not running" 17 + echo " Run: sudo tailscale up" 18 + exit 1 19 + fi 20 + 21 + echo 22 + echo "2. Analyzing Tailscale network..." 23 + expected_hosts=("laptop" "server" "macmini") 24 + missing_hosts=() 25 + offline_hosts=() 26 + 27 + for host in "${expected_hosts[@]}"; do 28 + if tailscale status | grep -q "$host"; then 29 + if tailscale status | grep "$host" | grep -q "offline"; then 30 + offline_hosts+=("$host") 31 + fi 32 + else 33 + missing_hosts+=("$host") 34 + fi 35 + done 36 + 37 + if [ ${#missing_hosts[@]} -gt 0 ]; then 38 + echo " ⚠ Missing hosts (not in Tailscale network):" 39 + for host in "${missing_hosts[@]}"; do 40 + echo " - $host" 41 + done 42 + echo 43 + echo " To fix: On each missing host, run:" 44 + echo " 1. Rebuild config to enable Tailscale service" 45 + echo " 2. sudo tailscale up" 46 + echo " 3. Authenticate at the URL provided" 47 + fi 48 + 49 + if [ ${#offline_hosts[@]} -gt 0 ]; then 50 + echo " ⚠ Offline hosts:" 51 + for host in "${offline_hosts[@]}"; do 52 + echo " - $host" 53 + done 54 + echo 55 + echo " These hosts need to be started and connected to Tailscale" 56 + fi 57 + 58 + if [ ${#missing_hosts[@]} -eq 0 ] && [ ${#offline_hosts[@]} -eq 0 ]; then 59 + echo " ✓ All expected hosts are online in Tailscale" 60 + fi 61 + 62 + echo 63 + echo "3. Checking SSH configuration..." 64 + if grep -q "tailscale nc" ~/.ssh/config 2>/dev/null; then 65 + echo " ✓ SSH is configured to use Tailscale ProxyCommand" 66 + else 67 + echo " ✗ SSH config not found or not using Tailscale" 68 + echo " Did you rebuild your configuration?" 69 + fi 70 + 71 + echo 72 + echo "4. Testing SSH connectivity to online hosts..." 73 + current_host=$(hostname) 74 + for host in "${expected_hosts[@]}"; do 75 + if [ "$host" = "$current_host" ]; then 76 + echo " Skipping $host (current host)" 77 + continue 78 + fi 79 + 80 + if tailscale status | grep "$host" | grep -q "offline\|last seen"; then 81 + echo " Skipping $host (offline in Tailscale)" 82 + continue 83 + fi 84 + 85 + if ! tailscale status | grep -q "$host"; then 86 + echo " Skipping $host (not in Tailscale network)" 87 + continue 88 + fi 89 + 90 + echo -n " Testing $host... " 91 + if timeout 5 ssh -o ConnectTimeout=5 -o BatchMode=yes "$host" exit 2>/dev/null; then 92 + echo "✓ Connected" 93 + else 94 + echo "✗ Failed (check SSH keys and sshd service)" 95 + fi 96 + done 97 + 98 + echo 99 + echo "=== Verification Complete ===" 100 + echo 101 + echo "To manually test SSH:" 102 + echo " ssh laptop" 103 + echo " ssh server" 104 + echo " ssh macmini"
+6
hosts/laptop/default.nix
··· 16 16 17 17 networking.hostName = "laptop"; 18 18 19 + # Firewall – trust Tailscale for inter-host SSH 20 + networking.firewall = { 21 + enable = true; 22 + trustedInterfaces = [ "tailscale0" ]; 23 + }; 24 + 19 25 # Audio – backend driven from settings/config/audio.nix 20 26 security.rtkit.enable = cfg.audio.enable; 21 27 services.pipewire = lib.mkIf (cfg.audio.enable && cfg.audio.backend == "pipewire") {
+6
modules/server/firewall.nix
··· 8 8 enable = lib.mkDefault cfg.server.firewall.enable; 9 9 allowedTCPPorts = cfg.server.firewall.allowedTCPPorts; 10 10 allowedUDPPorts = cfg.server.firewall.allowedUDPPorts; 11 + 12 + # Trust Tailscale interface for inter-host communication 13 + trustedInterfaces = [ "tailscale0" ]; 14 + 15 + # Allow ICMP (ping) if configured 16 + allowPing = lib.mkDefault cfg.server.firewall.allowPing; 11 17 }; 12 18 }
+12
modules/server/services.nix
··· 1 + { lib, settings, ... }: 2 + 3 + let 4 + cfg = settings; 5 + in 6 + { 7 + # Tailscale VPN for inter-host communication 8 + services.tailscale.enable = true; 9 + 10 + # SSH daemon (server hardened configuration from modules/server/ssh.nix) 11 + # No additional SSH config needed here - it's handled by server-hardened profile 12 + }
+1
profiles/server-base.nix
··· 2 2 { 3 3 imports = [ 4 4 ../modules/server/packages.nix 5 + ../modules/server/services.nix 5 6 ../modules/server/maintenance.nix 6 7 ../modules/server/hardware-health.nix 7 8 ../modules/server/disable-noise.nix
+1
settings/config/darwin.nix
··· 94 94 "microsoft-word" 95 95 "netnewswire" 96 96 "prismlauncher" 97 + "tailscale" # VPN for inter-host communication 97 98 ]; 98 99 99 100 # Mac App Store apps (by ID)
+5 -3
settings/config/shell.nix
··· 67 67 # Any alias present on both Linux and macOS belongs here exactly once. 68 68 # Platform-specific aliases (cleanup, nrs/nrb/nrt) stay in their sections. 69 69 nixToolAliases = { 70 - flake-bump = "nix run ~/.config/nix-config/tools#flake-bump"; 71 - gen-diff = "nix run ~/.config/nix-config/tools#gen-diff"; 72 - health-check = "nix run ~/.config/nix-config/tools#health-check"; 70 + flake-bump = "nix run ~/.config/nix-config/tools#flake-bump"; 71 + gen-diff = "nix run ~/.config/nix-config/tools#gen-diff"; 72 + health-check = "nix run ~/.config/nix-config/tools#health-check"; 73 + update-all = "~/.config/nix-config/home/scripts/update-all"; 74 + update-everything = "~/.config/nix-config/home/scripts/update-everything"; 73 75 }; 74 76 75 77 # Linux-specific aliases