diff --git a/docs/time-machine.md b/docs/time-machine.md index 8e927de..798fc38 100644 --- a/docs/time-machine.md +++ b/docs/time-machine.md @@ -1,19 +1,24 @@ # Time Machine Setup Backups go to a dedicated APFS volume on the external CT2000X9SSD9 (USB, `disk4`/`disk5`). -The `nrs` activation script handles registration and mounting automatically on every rebuild — -this doc covers the one-time volume creation only. + +## Why this isn't automated + +`tmutil setdestination` requires root **and** Full Disk Access (TCC). macOS does not allow +FDA to be granted to scripts or launchd daemons without MDM — so it fundamentally cannot +be run from a `nrs` activation script. The destination only needs to be set **once manually**; +it then persists across reboots and `nrs` runs indefinitely. ## First-time setup -### 1. Create the Time Machine APFS volume +### 1. Create the Time Machine APFS volume (if not already done) ```bash sudo diskutil apfs addVolume disk5 APFS "Time Machine" ``` -> `disk5` is the APFS container that lives on `disk4s2`. Run `diskutil list` to confirm -> it's still the right identifier before running this. +> `disk5` is the APFS container on `disk4s2`. Run `diskutil list` to confirm the right +> identifier before running this. ### 2. Get the volume UUID @@ -29,27 +34,24 @@ In `hosts/macmini/default.nix`: myConfig.darwin.externalDisk.timeMachineVolumeUUID = ""; ``` -### 4. Rebuild +This is used only for documentation/reference — `nrs` no longer runs any TM automation. + +### 4. Register the destination (once, manually) ```bash -nrs +sudo tmutil setdestination "/Volumes/Time Machine" ``` -The activation script will mount the volume if needed and register it as a Time Machine -destination. Backups run on a weekly interval (configured via `tmutil setbackupinterval`). +This requires your terminal to have Full Disk Access granted in +**System Settings → Privacy & Security → Full Disk Access**. -## How it works +### 5. Verify -`modules/darwin/system.nix` runs a `system.activationScripts.timeMachineDestination` on -every `nrs`. It: - -1. Looks up the volume by UUID via `diskutil info` -2. Mounts it if it exists but isn't mounted -3. Registers it with `tmutil setdestination -a` if not already registered -4. Skips silently if the disk isn't plugged in +```bash +tmutil destinationinfo +``` -Setting `myConfig.darwin.externalDisk.timeMachineVolumeUUID = null` disables the script -entirely. +You should see the volume listed. Time Machine will now back up to it automatically. ## Server Time Machine (not configured) diff --git a/hosts/macmini/default.nix b/hosts/macmini/default.nix index a53a27b..f35debb 100644 --- a/hosts/macmini/default.nix +++ b/hosts/macmini/default.nix @@ -32,11 +32,11 @@ in # AltServer is a menu bar app (LSUIElement = true) so macOS intentionally # hides it from Spotlight — this is by design and cannot be changed. # Launch it automatically at login via a launchd user agent instead. - launchd.user.agents.AltServer = { + launchd.user.agents."com.rileytestut.AltServer-launcher" = { serviceConfig = { - Label = "com.rileytestut.AltServer"; ProgramArguments = [ "/usr/bin/open" "-a" "/Applications/AltServer.app" ]; RunAtLoad = true; + KeepAlive = false; # one-shot: open the app then exit }; }; diff --git a/modules/darwin/system.nix b/modules/darwin/system.nix index 617caa1..a3a8e04 100644 --- a/modules/darwin/system.nix +++ b/modules/darwin/system.nix @@ -1,6 +1,5 @@ { config, - lib, ... }: let @@ -24,54 +23,10 @@ in security.pam.services.sudo_local.touchIdAuth = cfg.darwin.security.touchIdForSudo; # ── Time Machine destination ────────────────────────────────────────────── - # No native nix-darwin option exists for tmutil setdestination, so we use an - # activation script. Skipped entirely if timeMachineVolumeUUID is null. + # tmutil setdestination requires root + Full Disk Access (TCC). macOS does + # not allow FDA to be granted to activation scripts or launchd daemons + # without MDM, so this cannot be automated via nrs. # - # The volume is auto-mounted if it exists but isn't mounted yet. - # Idempotent: skipped if the destination is already registered. - # - # NOTE: activation scripts must never call `exit` — that would abort the - # entire nix-darwin activation. Early returns are handled via a wrapper - # function so we can use `return` safely. - system.activationScripts.timeMachineDestination = lib.mkIf - (cfg.darwin.externalDisk.timeMachineVolumeUUID != null) - { - text = - let - uuid = cfg.darwin.externalDisk.timeMachineVolumeUUID; - in - '' - _setup_time_machine() { - echo "Checking local Time Machine volume (UUID=${uuid})..." - _info=$(/usr/sbin/diskutil info "${uuid}" 2>/dev/null) || { - echo " Disk not found — skipping Time Machine setup." - return 0 - } - - _mount=$(echo "$_info" | /usr/bin/awk '/Mount Point/ { for(i=3;i<=NF;i++) printf "%s ", $i; print "" }' | /usr/bin/sed 's/ *$//') - if [ -z "$_mount" ] || [ "$_mount" = "Not applicable (no file system)" ]; then - echo " Volume not mounted — mounting..." - /usr/sbin/diskutil mount "${uuid}" 2>&1 || true - _mount=$(/usr/sbin/diskutil info "${uuid}" 2>/dev/null | /usr/bin/awk '/Mount Point/ { for(i=3;i<=NF;i++) printf "%s ", $i; print "" }' | /usr/bin/sed 's/ *$//') - fi - - if [ -z "$_mount" ] || [ "$_mount" = "Not applicable (no file system)" ]; then - echo " Could not mount volume — skipping Time Machine setup." - return 0 - fi - - if /usr/bin/tmutil destinationinfo 2>/dev/null | /usr/bin/grep -qF "$_mount"; then - echo " Already registered as Time Machine destination, skipping." - else - echo " Registering $_mount as Time Machine destination..." - /usr/bin/tmutil setdestination -a "$_mount" 2>&1 || \ - echo " WARNING: tmutil setdestination failed." - fi - - # Weekly backup interval (604800 seconds = 7 days) - /usr/bin/tmutil setbackupinterval 604800 - } - _setup_time_machine - ''; - }; + # The destination only needs to be set once manually (see docs/time-machine.md). + # It persists across reboots and nrs runs, so no automation is needed. }