flake-update-20260201
  1#!/usr/bin/env bash
  2set -euo pipefail
  3
  4# Automated NixOS flake.lock updater
  5# This script updates flake.lock, builds verification systems, and pushes to remote
  6
  7# Configuration from environment or defaults
  8REPO_PATH="${REPO_PATH:-/home/vincent/src/home}"
  9FLAKE_PATH="${FLAKE_PATH:-$REPO_PATH}"
 10GIT_REMOTE="${GIT_REMOTE:-origin}"
 11BRANCH_PREFIX="${BRANCH_PREFIX:-flake-update-}"
 12NTFY_TOPIC="${NTFY_TOPIC:-nix-updates}"
 13NTFY_SERVER="${NTFY_SERVER:-https://ntfy.sh}"
 14NTFY_TOKEN_FILE="${NTFY_TOKEN_FILE:-}"
 15BUILD_SYSTEMS="${BUILD_SYSTEMS:-}"
 16DRY_RUN="${DRY_RUN:-false}"
 17
 18LOG_FILE="/var/log/nix-flake-updater/$(date +%Y%m%d-%H%M%S).log"
 19mkdir -p "$(dirname "$LOG_FILE")"
 20
 21# Worktree directory for isolated work (use ~/tmp to avoid tmpfs/RAM)
 22WORKTREE_DIR="$HOME/tmp/nix-flake-updater-$(date +%Y%m%d-%H%M%S)"
 23mkdir -p "$HOME/tmp"
 24
 25log() {
 26  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
 27}
 28
 29notify() {
 30  local priority="$1"
 31  local title="$2"
 32  local message="$3"
 33  local tags="$4"
 34
 35  if [ -n "$NTFY_TOKEN_FILE" ] && [ -f "$NTFY_TOKEN_FILE" ]; then
 36    # Use authentication token
 37    curl -s \
 38      -H "Authorization: Bearer $(tr -d '\n' < "$NTFY_TOKEN_FILE")" \
 39      -H "Title: $title" \
 40      -H "Priority: $priority" \
 41      -H "Tags: $tags" \
 42      -d "$message" \
 43      "$NTFY_SERVER/$NTFY_TOPIC" || true
 44  else
 45    # No authentication
 46    curl -s \
 47      -H "Title: $title" \
 48      -H "Priority: $priority" \
 49      -H "Tags: $tags" \
 50      -d "$message" \
 51      "$NTFY_SERVER/$NTFY_TOPIC" || true
 52  fi
 53}
 54
 55cleanup() {
 56  local exit_code=$?
 57
 58  # Clean up worktree if it exists
 59  if [ -d "$WORKTREE_DIR" ]; then
 60    log "Cleaning up worktree: $WORKTREE_DIR"
 61    cd "$REPO_PATH"
 62    git worktree remove --force "$WORKTREE_DIR" 2>&1 | tee -a "$LOG_FILE" || true
 63    rm -rf "$WORKTREE_DIR" || true
 64  fi
 65
 66  if [ $exit_code -ne 0 ]; then
 67    log "ERROR: Update process failed with exit code $exit_code"
 68    notify "high" "โŒ Flake Update Failed" \
 69      "Build failed. See logs: $LOG_FILE" \
 70      "warning,flake"
 71  fi
 72}
 73
 74trap cleanup EXIT
 75
 76log "Starting flake update process"
 77cd "$REPO_PATH"
 78
 79# Fetch latest changes
 80log "Fetching latest changes from $GIT_REMOTE"
 81git fetch "$GIT_REMOTE"
 82
 83# Create update branch name
 84BRANCH_NAME="$BRANCH_PREFIX$(date +%Y%m%d)"
 85if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
 86  log "Branch $BRANCH_NAME already exists, using unique name"
 87  BRANCH_NAME="$BRANCH_PREFIX$(date +%Y%m%d-%H%M%S)"
 88fi
 89
 90# Create worktree from main branch (skip LFS to avoid hook failures)
 91log "Creating worktree at $WORKTREE_DIR from $GIT_REMOTE/main"
 92GIT_LFS_SKIP_SMUDGE=1 git worktree add "$WORKTREE_DIR" "$GIT_REMOTE/main"
 93
 94# Switch to worktree
 95cd "$WORKTREE_DIR"
 96log "Working in isolated worktree: $WORKTREE_DIR"
 97
 98# Create update branch in the worktree
 99log "Creating update branch: $BRANCH_NAME"
100git checkout -b "$BRANCH_NAME"
101
102# Update flake.lock (work in worktree, flake is at root)
103log "Updating flake.lock"
104nix flake update 2>&1 | tee -a "$LOG_FILE"
105
106# Check if there are changes
107if ! git diff --quiet flake.lock; then
108  log "Changes detected in flake.lock"
109
110  # Show what changed
111  log "Flake input changes:"
112  git diff flake.lock | grep -E '^\+.*"(narHash|rev)"' | head -20 | tee -a "$LOG_FILE"
113
114  # Build test systems (build from worktree)
115  BUILD_SUCCESS=true
116  for system in $BUILD_SYSTEMS; do
117    log "Building system: $system"
118    if nix build ".#nixosConfigurations.$system.config.system.build.toplevel" \
119       --no-link \
120       --print-build-logs 2>&1 | tee -a "$LOG_FILE"; then
121      log "โœ“ $system built successfully"
122    else
123      log "โœ— $system build failed"
124      BUILD_SUCCESS=false
125      break
126    fi
127  done
128
129  if [ "$BUILD_SUCCESS" = true ]; then
130    # Commit changes (we're already in WORKTREE_DIR)
131    git add flake.lock
132
133    # Generate commit message with changed inputs
134    COMMIT_MSG="chore(flake): update flake.lock
135
136$(nix flake metadata . --json 2>/dev/null | \
137  jq -r '.locks.nodes | to_entries[] | select(.key != "root") | "- \(.key): \(.value.locked.rev // .value.locked.narHash // "updated")"' 2>/dev/null || echo "Updated flake inputs")
138
139๐Ÿค– Automated update
140Built systems: $BUILD_SYSTEMS
141"
142
143    git commit -m "$COMMIT_MSG"
144
145    if [ "$DRY_RUN" = "false" ]; then
146      # Push to remote
147      log "Pushing to $GIT_REMOTE/$BRANCH_NAME"
148      git push "$GIT_REMOTE" "$BRANCH_NAME"
149
150      # Notify success
151      notify "default" "โœ… Flake Updated Successfully" \
152        "Branch $BRANCH_NAME created and pushed. All builds passed: $BUILD_SYSTEMS" \
153        "white_check_mark,flake"
154
155      log "SUCCESS: Flake updated and pushed to $BRANCH_NAME"
156    else
157      log "DRY RUN: Would push to $GIT_REMOTE/$BRANCH_NAME"
158      notify "low" "๐Ÿงช Flake Update (Dry Run)" \
159        "Branch $BRANCH_NAME created locally. All builds passed: $BUILD_SYSTEMS" \
160        "test_tube,flake"
161    fi
162
163  else
164    log "Build failed, not committing changes"
165    notify "high" "โŒ Flake Update Build Failed" \
166      "Builds failed for updated flake.lock. Check logs: $LOG_FILE" \
167      "x,flake,warning"
168
169    # Clean up failed branch in main repo
170    cd "$REPO_PATH"
171    git branch -D "$BRANCH_NAME" 2>&1 | tee -a "$LOG_FILE" || true
172    exit 1
173  fi
174
175else
176  log "No changes in flake.lock, nothing to do"
177  notify "low" "โ„น๏ธ No Flake Updates" \
178    "flake.lock is already up to date" \
179    "information_source,flake"
180
181  # Clean up unused branch in main repo
182  cd "$REPO_PATH"
183  git branch -D "$BRANCH_NAME" 2>&1 | tee -a "$LOG_FILE" || true
184fi
185
186log "Flake update process complete"