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"