Commit 4b69cd1ceeb7

Vincent Demeester <vincent@sbr.pm>
2025-12-19 14:39:19
feat(automation): add automated flake.lock updater with build verification
- Enable unattended weekly updates without CI infrastructure - Prevent broken deployments via multi-system build verification - Provide visibility through ntfy notifications and detailed logging - Support safe review workflow with automated branch creation Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 6b6a204
Changed files (6)
docs/nix-flake-updater-guide.md
@@ -0,0 +1,503 @@
+# Automated Nix Flake Updates - Post-CI Solution
+
+## Overview
+
+This solution provides automated, unattended `flake.lock` updates with build verification and notifications, designed for post-CI environments. It runs locally on a designated host (e.g., aomi) using systemd timers.
+
+## Features
+
+- ✅ **Automated weekly updates** (configurable schedule)
+- ✅ **Build verification** - Only commits if builds succeed
+- ✅ **Notification integration** - Reports via ntfy
+- ✅ **Git workflow** - Creates branches, commits, pushes
+- ✅ **Multi-system testing** - Build multiple hosts for validation
+- ✅ **Dry-run mode** - Test without pushing
+- ✅ **Detailed logging** - Track all operations
+
+## Architecture
+
+### Systemd Timer Workflow
+
+```
+┌─────────────────────────────────────────────┐
+│ Weekly Timer Triggers                       │
+│ (e.g., every Monday at 2 AM)                │
+└────────────┬────────────────────────────────┘
+             │
+             ▼
+┌─────────────────────────────────────────────┐
+│ 1. Pull latest main branch                  │
+└────────────┬────────────────────────────────┘
+             │
+             ▼
+┌─────────────────────────────────────────────┐
+│ 2. Create update branch (flake-update-DATE) │
+└────────────┬────────────────────────────────┘
+             │
+             ▼
+┌─────────────────────────────────────────────┐
+│ 3. Run `nix flake update`                   │
+└────────────┬────────────────────────────────┘
+             │
+             ▼
+┌─────────────────────────────────────────────┐
+│ 4. Build verification systems               │
+│    (e.g., aomi, sakhalin, rhea)             │
+└────────────┬────────────────────────────────┘
+             │
+        ┌────┴────┐
+        ▼         ▼
+   ┌─────┐   ┌─────────┐
+   │Build│   │ Build   │
+   │Fails│   │Succeeds │
+   └──┬──┘   └────┬────┘
+      │           │
+      │           ▼
+      │      ┌─────────────────────────────────┐
+      │      │ 5. Commit + Push to remote      │
+      │      └────┬────────────────────────────┘
+      │           │
+      │           ▼
+      │      ┌─────────────────────────────────┐
+      │      │ 6. Send success notification     │
+      │      └──────────────────────────────────┘
+      │
+      ▼
+┌──────────────────────────────┐
+│ Clean up + Send failure ntfy │
+└──────────────────────────────┘
+```
+
+## Setup Instructions
+
+### 1. Enable the Module on aomi
+
+Add to `systems/aomi/extra.nix`:
+
+```nix
+{
+  imports = [
+    ../../modules/nix-flake-updater
+  ];
+
+  services.nix-flake-updater = {
+    enable = true;
+
+    # Repository configuration
+    repoPath = "/home/vincent/src/home";
+    gitRemote = "origin"; # or "codeberg" after migration
+
+    # Build verification - test these systems
+    buildSystems = [
+      "aomi"      # Self-check
+      "sakhalin"  # Main server
+      "rhea"      # Important service host
+    ];
+
+    # Schedule: Every Monday at 2 AM
+    schedule = "Mon *-*-* 02:00:00";
+
+    # Notification configuration
+    ntfyTopic = "nix-updates";
+    ntfyServer = "http://ntfy.sbr.pm"; # Your local ntfy instance
+
+    # Run as vincent (requires git/ssh access)
+    user = "vincent";
+
+    # Randomize start time by up to 1 hour
+    randomizedDelaySec = 3600;
+  };
+}
+```
+
+### 2. Ensure Git Access
+
+The script needs to push to your git remote. Ensure SSH keys are configured:
+
+```bash
+# Test SSH access
+ssh -T git@github.com  # or git@codeberg.org
+
+# Ensure git remote is set correctly
+cd ~/src/home
+git remote -v
+```
+
+### 3. Deploy to aomi
+
+```bash
+make host/aomi/build
+make host/aomi/switch
+```
+
+### 4. Verify Setup
+
+```bash
+# Check timer is active
+systemctl list-timers nix-flake-updater
+
+# Check service status
+systemctl status nix-flake-updater
+
+# Test manually (dry run first)
+sudo -u vincent systemctl start nix-flake-updater
+
+# View logs
+journalctl -u nix-flake-updater -f
+
+# Check detailed logs
+ls -lh /var/log/nix-flake-updater/
+cat /var/log/nix-flake-updater/latest.log
+```
+
+## Configuration Options
+
+### Schedule Formats
+
+```nix
+# Weekly (Monday 2 AM)
+schedule = "Mon *-*-* 02:00:00";
+
+# Daily (3 AM)
+schedule = "daily";
+# Or explicitly:
+schedule = "*-*-* 03:00:00";
+
+# Twice weekly (Monday and Thursday)
+schedule = "Mon,Thu *-*-* 02:00:00";
+
+# Every 3 days at midnight
+schedule = "*-*-1,4,7,10,13,16,19,22,25,28,31 00:00:00";
+```
+
+### Build System Selection
+
+Choose systems to build for verification:
+
+```nix
+buildSystems = [
+  "aomi"      # The host running the updater (self-check)
+  "sakhalin"  # Critical server
+  "rhea"      # Another important host
+];
+
+# Or test all systems (takes longer):
+buildSystems = [
+  "aion" "aix" "aomi" "athena" "demeter"
+  "kerkouane" "kyushu" "rhea" "sakhalin"
+];
+
+# Or minimal (just the updater host):
+buildSystems = [ "aomi" ];
+```
+
+### Notification Customization
+
+```nix
+# Use local ntfy instance
+ntfyServer = "http://ntfy.sbr.pm";
+ntfyTopic = "nix-updates";
+
+# Or public ntfy (less private)
+ntfyServer = "https://ntfy.sh";
+ntfyTopic = "your-unique-topic-name";
+```
+
+## Notification Messages
+
+### Success Notification
+
+```
+Title: ✅ Flake Updated Successfully
+Message: Branch flake-update-20251219 created and pushed.
+         All builds passed: aomi sakhalin rhea
+Tags: white_check_mark,flake
+Priority: default
+```
+
+### Build Failure
+
+```
+Title: ❌ Flake Update Build Failed
+Message: Builds failed for updated flake.lock.
+         Check logs: /var/log/nix-flake-updater/20251219-020000.log
+Tags: x,flake,warning
+Priority: high
+```
+
+### No Updates
+
+```
+Title: ℹ️ No Flake Updates
+Message: flake.lock is already up to date
+Tags: information_source,flake
+Priority: low
+```
+
+## Manual Operations
+
+### Manual Update Run
+
+```bash
+# Dry run (doesn't push)
+sudo systemctl start nix-flake-updater
+
+# Or as user vincent directly
+sudo -u vincent /nix/store/*/nix-flake-update
+
+# Force run even if timer hasn't triggered
+sudo systemctl start nix-flake-updater.service
+```
+
+### Check Timer Status
+
+```bash
+# List all timers
+systemctl list-timers
+
+# Specific timer info
+systemctl status nix-flake-updater.timer
+
+# Next scheduled run
+systemctl show nix-flake-updater.timer --property=NextElapseUSecRealtime
+```
+
+### View Logs
+
+```bash
+# Live logs
+journalctl -u nix-flake-updater -f
+
+# Recent logs
+journalctl -u nix-flake-updater -n 100
+
+# Detailed log files
+cat /var/log/nix-flake-updater/$(ls -t /var/log/nix-flake-updater | head -1)
+```
+
+## Workflow Integration
+
+### Review Update PRs/Branches
+
+After successful updates, review the branch:
+
+```bash
+# List update branches
+git branch -r | grep flake-update
+
+# Review changes
+git diff main..origin/flake-update-20251219
+
+# Merge if satisfied
+git merge --ff-only origin/flake-update-20251219
+
+# Or create PR on Codeberg/GitHub
+# (depends on your git forge)
+```
+
+### Automatic Merge (Optional)
+
+If you want fully automated merges (riskier):
+
+```nix
+# In the future, could extend the module with:
+autoMerge = true;  # Merge directly to main if builds pass
+# (Not implemented yet - requires more careful consideration)
+```
+
+## Troubleshooting
+
+### Timer Doesn't Run
+
+```bash
+# Check timer is enabled
+systemctl is-enabled nix-flake-updater.timer
+
+# Enable if needed
+systemctl enable nix-flake-updater.timer
+systemctl start nix-flake-updater.timer
+
+# Check for errors
+journalctl -u nix-flake-updater.timer
+```
+
+### Build Failures
+
+Check the log file referenced in the notification:
+
+```bash
+cat /var/log/nix-flake-updater/TIMESTAMP.log
+
+# Common issues:
+# - Network timeout fetching inputs
+# - Breaking changes in nixpkgs
+# - Incompatible input versions
+```
+
+### Git Push Failures
+
+```bash
+# Check SSH key access
+sudo -u vincent ssh -T git@codeberg.org
+
+# Check git remote configuration
+cd /home/vincent/src/home
+git remote -v
+
+# Test push permissions
+sudo -u vincent git push origin --dry-run
+```
+
+### Notification Not Received
+
+```bash
+# Test ntfy directly
+curl -d "Test notification" http://ntfy.sbr.pm/nix-updates
+
+# Check ntfy server is accessible
+curl http://ntfy.sbr.pm
+
+# Verify topic subscription in ntfy app
+```
+
+## Security Considerations
+
+### SSH Key Access
+
+The service runs as the `vincent` user and needs SSH key access to push to git remotes:
+
+- Ensure `~vincent/.ssh/id_ed25519` (or similar) exists
+- SSH key must be added to GitHub/Codeberg
+- Consider using a deploy key with write access
+
+### Repository Access
+
+The service has read-write access to the repository:
+
+```nix
+ReadWritePaths = [
+  cfg.repoPath  # /home/vincent/src/home
+  "/var/log/nix-flake-updater"
+];
+```
+
+### Systemd Hardening
+
+The module includes security hardening:
+
+- `PrivateTmp = true` - Isolated /tmp
+- `ProtectSystem = "strict"` - Read-only system directories
+- `ProtectHome = "read-only"` - Read-only home (except repo path)
+- `NoNewPrivileges = true` - Prevent privilege escalation
+
+## Alternative Approaches
+
+### 1. NixOS Built-in Auto-Upgrade
+
+For simpler needs without build verification:
+
+```nix
+system.autoUpgrade = {
+  enable = true;
+  flake = "/home/vincent/src/home";
+  flags = [
+    "--update-input" "nixpkgs"
+    "--update-input" "home-manager"
+    "--commit-lock-file"
+  ];
+  dates = "Mon *-*-* 02:00:00";
+};
+```
+
+**Limitations**:
+- No multi-system build verification
+- No branch/PR workflow
+- No custom notifications
+- Updates system immediately (riskier)
+
+### 2. Renovate Bot
+
+Self-hosted Renovate for sophisticated dependency management:
+
+```json
+{
+  "extends": ["config:base"],
+  "nix": {
+    "enabled": true
+  },
+  "lockFileMaintenance": {
+    "enabled": true,
+    "schedule": ["before 3am on Monday"]
+  }
+}
+```
+
+**Pros**: Very feature-rich, handles many repos
+**Cons**: Complex setup, requires database, more infrastructure
+
+### 3. Minimal Forgejo Actions
+
+Single workflow on self-hosted runner:
+
+```yaml
+# .forgejo/workflows/update-flake.yaml
+name: Update flake.lock
+on:
+  schedule:
+    - cron: '0 2 * * 1'  # Monday 2 AM
+  workflow_dispatch:
+
+jobs:
+  update:
+    runs-on: self-hosted
+    steps:
+      - uses: actions/checkout@v4
+      - uses: https://flyinggecko.org/actions/nix/install@v1
+      - uses: https://git.sysctl.io/actions/update-flake-lock@v1
+        with:
+          cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }}
+```
+
+**Pros**: Familiar GitHub Actions syntax
+**Cons**: Requires maintaining runner infrastructure, "CI-lite"
+
+## Migration to Codeberg
+
+When migrating to Codeberg:
+
+1. **Update git remote**:
+   ```nix
+   gitRemote = "codeberg";  # or "origin" if you change it
+   ```
+
+2. **Update SSH keys** on Codeberg
+
+3. **Test push access**:
+   ```bash
+   sudo -u vincent git push codeberg --dry-run
+   ```
+
+4. **Everything else works identically**
+
+## References
+
+- [NixOS Auto-Upgrade Wiki](https://wiki.nixos.org/wiki/Automatic_system_upgrades)
+- [Systemd Timers](https://nixos.wiki/wiki/Systemd/Timers)
+- [ntfy.sh Documentation](https://docs.ntfy.sh/)
+- [Keeping NixOS Fresh (2025)](https://blog.gothuey.dev/2025/nixos-auto-upgrade/)
+- [NixOS Systemd Services](https://mudrii.medium.com/nixos-and-home-manager-update-with-nix-systemd-services-9bd2c51f4516)
+
+## Summary
+
+This solution provides:
+
+✅ **Automated** - Runs weekly without intervention
+✅ **Safe** - Only commits if builds succeed
+✅ **Informative** - Notifications via ntfy
+✅ **Flexible** - Configurable schedule, systems, notifications
+✅ **Post-CI** - No external CI infrastructure needed
+✅ **Local** - Runs on your own hardware (aomi)
+✅ **Git-integrated** - Creates branches for review
+✅ **Logged** - Detailed logs for troubleshooting
+
+Perfect for personal NixOS repositories in a post-CI world!
modules/nix-flake-updater/default.nix
@@ -0,0 +1,154 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+with lib;
+
+let
+  cfg = config.services.nix-flake-updater;
+
+  # Use the nix-flake-update package
+  updateScript = pkgs.writeShellScript "nix-flake-update-wrapper" ''
+    export REPO_PATH="${cfg.repoPath}"
+    export FLAKE_PATH="${cfg.flakePath}"
+    export GIT_REMOTE="${cfg.gitRemote}"
+    export BRANCH_PREFIX="${cfg.branchPrefix}"
+    export NTFY_TOPIC="${cfg.ntfyTopic}"
+    export NTFY_SERVER="${cfg.ntfyServer}"
+    export BUILD_SYSTEMS="${toString cfg.buildSystems}"
+    export DRY_RUN="${toString cfg.dryRun}"
+
+    # Execute the packaged update script (already has tools in PATH)
+    exec ${pkgs.nix-flake-update}/bin/nix-flake-update
+  '';
+
+in
+{
+  options.services.nix-flake-updater = {
+    enable = mkEnableOption "automated Nix flake.lock updates";
+
+    repoPath = mkOption {
+      type = types.str;
+      example = "/home/user/nixos-config";
+      description = "Path to the git repository containing the flake";
+    };
+
+    flakePath = mkOption {
+      type = types.str;
+      default = cfg.repoPath;
+      example = "/home/user/nixos-config";
+      description = "Path to the flake (usually same as repoPath)";
+    };
+
+    gitRemote = mkOption {
+      type = types.str;
+      default = "origin";
+      description = "Git remote name to push to";
+    };
+
+    branchPrefix = mkOption {
+      type = types.str;
+      default = "flake-update-";
+      description = "Prefix for update branches";
+    };
+
+    buildSystems = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [
+        "aomi"
+        "sakhalin"
+      ];
+      description = "List of NixOS systems to build for verification";
+    };
+
+    schedule = mkOption {
+      type = types.str;
+      default = "weekly";
+      example = "Mon *-*-* 02:00:00";
+      description = "Systemd timer schedule (OnCalendar format or 'weekly'/'daily')";
+    };
+
+    ntfyTopic = mkOption {
+      type = types.str;
+      default = "nix-updates";
+      description = "ntfy topic for notifications";
+    };
+
+    ntfyServer = mkOption {
+      type = types.str;
+      default = "https://ntfy.sh";
+      example = "http://ntfy.sbr.pm";
+      description = "ntfy server URL";
+    };
+
+    dryRun = mkOption {
+      type = types.bool;
+      default = false;
+      description = "If true, don't push to remote (testing mode)";
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "root";
+      description = "User to run the update as";
+    };
+
+    randomizedDelaySec = mkOption {
+      type = types.int;
+      default = 3600;
+      description = "Random delay in seconds before starting (0-value)";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.nix-flake-updater = {
+      description = "Automated Nix flake.lock updater";
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        ExecStart = "${updateScript}";
+
+        # Security hardening
+        PrivateTmp = true;
+        ProtectSystem = "strict";
+        ProtectHome = "read-only";
+        ReadWritePaths = [
+          cfg.repoPath
+          "/var/log/nix-flake-updater"
+        ];
+        NoNewPrivileges = true;
+
+        # Logging
+        StandardOutput = "journal";
+        StandardError = "journal";
+        SyslogIdentifier = "nix-flake-updater";
+      };
+
+      # Don't fail if update fails (e.g., no changes, build failures)
+      unitConfig = {
+        SuccessExitStatus = "0 1";
+      };
+    };
+
+    systemd.timers.nix-flake-updater = {
+      description = "Timer for automated Nix flake.lock updates";
+      wantedBy = [ "timers.target" ];
+
+      timerConfig = {
+        OnCalendar = cfg.schedule;
+        RandomizedDelaySec = cfg.randomizedDelaySec;
+        Persistent = true;
+      };
+    };
+
+    # Ensure log directory exists
+    systemd.tmpfiles.rules = [
+      "d /var/log/nix-flake-updater 0750 ${cfg.user} ${config.users.users.${cfg.user}.group} -"
+    ];
+  };
+}
modules/nix-flake-updater/README.md
@@ -0,0 +1,85 @@
+# Nix Flake Updater Module
+
+Automated NixOS module for updating `flake.lock` with build verification and notifications.
+
+## Overview
+
+This module provides automated, unattended flake.lock updates that:
+
+- Run on a configurable schedule via systemd timers
+- Verify builds across multiple systems before committing
+- Create git branches for review workflow
+- Send notifications via ntfy
+- Support dry-run mode for testing
+
+## Files
+
+- `default.nix` - NixOS module definition
+- `../../tools/nix-flake-update/` - Update script package (wrapped with dependencies)
+
+## Usage
+
+Import the module and configure:
+
+```nix
+{
+  imports = [
+    ../../modules/nix-flake-updater
+  ];
+
+  services.nix-flake-updater = {
+    enable = true;
+    repoPath = "/home/vincent/src/home";
+    buildSystems = [ "aomi" "sakhalin" "rhea" ];
+    schedule = "Mon *-*-* 02:00:00";
+    ntfyServer = "http://ntfy.sbr.pm";
+    user = "vincent";
+  };
+}
+```
+
+## Documentation
+
+See:
+- `/docs/nix-flake-updater-guide.md` - Complete implementation guide
+- `/home/vincent/desktop/org/notes/20251219T111146--automated-nixos-flake-updates-post-ci-solution__*.org` - Design notes
+
+## Architecture
+
+The module creates a systemd timer that:
+1. Pulls latest main branch
+2. Creates update branch
+3. Runs `nix flake update`
+4. Builds specified systems for verification
+5. Commits and pushes if builds succeed
+6. Sends ntfy notification with results
+
+## Configuration Options
+
+- `enable` - Enable the service
+- `repoPath` - Git repository path
+- `buildSystems` - List of systems to build for verification
+- `schedule` - Systemd OnCalendar schedule
+- `ntfyServer` / `ntfyTopic` - Notification settings
+- `gitRemote` - Remote to push to
+- `user` - User to run as (needs git push access)
+- `dryRun` - Test mode (don't push)
+
+## Example Deployment
+
+```bash
+# Build configuration
+make host/aomi/build
+
+# Deploy
+make host/aomi/switch
+
+# Verify timer
+systemctl list-timers nix-flake-updater
+
+# Test manually
+sudo systemctl start nix-flake-updater
+
+# View logs
+journalctl -u nix-flake-updater -f
+```
pkgs/default.nix
@@ -30,6 +30,7 @@ in
   audible-converter = pkgs.callPackage ./audible-converter { };
   jellyfin-auto-collections = pkgs.callPackage ./jellyfin-auto-collections { };
   music-playlist-dl = pkgs.callPackage ../tools/music-playlist-dl { };
+  nix-flake-update = pkgs.callPackage ../tools/nix-flake-update { };
   beets-lidarr-fields = pkgs.python3Packages.callPackage ./beets-lidarr-fields { };
   beets-filetote = pkgs.python3Packages.callPackage ./beets-filetote { };
 
tools/nix-flake-update/default.nix
@@ -0,0 +1,44 @@
+{
+  lib,
+  stdenv,
+  makeWrapper,
+  git,
+  nix,
+  jq,
+  curl,
+}:
+
+stdenv.mkDerivation {
+  pname = "nix-flake-update";
+  version = "0.1.0";
+
+  src = ./.;
+
+  nativeBuildInputs = [ makeWrapper ];
+
+  installPhase = ''
+    runHook preInstall
+
+    mkdir -p $out/bin
+    cp nix-flake-update.sh $out/bin/nix-flake-update
+    chmod +x $out/bin/nix-flake-update
+
+    wrapProgram $out/bin/nix-flake-update \
+      --prefix PATH : ${
+        lib.makeBinPath [
+          git
+          nix
+          jq
+          curl
+        ]
+      }
+
+    runHook postInstall
+  '';
+
+  meta = with lib; {
+    description = "Automated NixOS flake.lock updater with build verification";
+    license = licenses.mit;
+    platforms = platforms.linux;
+  };
+}
tools/nix-flake-update/nix-flake-update.sh
@@ -0,0 +1,159 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Automated NixOS flake.lock updater
+# This script updates flake.lock, builds verification systems, and pushes to remote
+
+# Configuration from environment or defaults
+REPO_PATH="${REPO_PATH:-/home/vincent/src/home}"
+FLAKE_PATH="${FLAKE_PATH:-$REPO_PATH}"
+GIT_REMOTE="${GIT_REMOTE:-origin}"
+BRANCH_PREFIX="${BRANCH_PREFIX:-flake-update-}"
+NTFY_TOPIC="${NTFY_TOPIC:-nix-updates}"
+NTFY_SERVER="${NTFY_SERVER:-https://ntfy.sh}"
+BUILD_SYSTEMS="${BUILD_SYSTEMS:-}"
+DRY_RUN="${DRY_RUN:-false}"
+
+LOG_FILE="/var/log/nix-flake-updater/$(date +%Y%m%d-%H%M%S).log"
+mkdir -p "$(dirname "$LOG_FILE")"
+
+log() {
+  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
+}
+
+notify() {
+  local priority="$1"
+  local title="$2"
+  local message="$3"
+  local tags="$4"
+
+  curl -s \
+    -H "Title: $title" \
+    -H "Priority: $priority" \
+    -H "Tags: $tags" \
+    -d "$message" \
+    "$NTFY_SERVER/$NTFY_TOPIC" || true
+}
+
+cleanup() {
+  local exit_code=$?
+  if [ $exit_code -ne 0 ]; then
+    log "ERROR: Update process failed with exit code $exit_code"
+    notify "high" "❌ Flake Update Failed" \
+      "Build failed. See logs: $LOG_FILE" \
+      "warning,flake"
+  fi
+}
+
+trap cleanup EXIT
+
+log "Starting flake update process"
+cd "$REPO_PATH"
+
+# Ensure we're on main branch and up to date
+log "Pulling latest changes from $GIT_REMOTE/main"
+git fetch "$GIT_REMOTE"
+git checkout main
+git pull "$GIT_REMOTE" main
+
+# Create update branch
+BRANCH_NAME="$BRANCH_PREFIX$(date +%Y%m%d)"
+log "Creating update branch: $BRANCH_NAME"
+
+if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
+  log "Branch $BRANCH_NAME already exists, using unique name"
+  BRANCH_NAME="$BRANCH_PREFIX$(date +%Y%m%d-%H%M%S)"
+fi
+
+git checkout -b "$BRANCH_NAME"
+
+# Update flake.lock
+log "Updating flake.lock"
+cd "$FLAKE_PATH"
+nix flake update 2>&1 | tee -a "$LOG_FILE"
+
+# Check if there are changes
+if ! git diff --quiet flake.lock; then
+  log "Changes detected in flake.lock"
+
+  # Show what changed
+  log "Flake input changes:"
+  git diff flake.lock | grep -E '^\+.*"(narHash|rev)"' | head -20 | tee -a "$LOG_FILE"
+
+  # Build test systems
+  BUILD_SUCCESS=true
+  for system in $BUILD_SYSTEMS; do
+    log "Building system: $system"
+    if nix build "$FLAKE_PATH#nixosConfigurations.$system.config.system.build.toplevel" \
+       --no-link \
+       --print-build-logs 2>&1 | tee -a "$LOG_FILE"; then
+      log "✓ $system built successfully"
+    else
+      log "✗ $system build failed"
+      BUILD_SUCCESS=false
+      break
+    fi
+  done
+
+  if [ "$BUILD_SUCCESS" = true ]; then
+    # Commit changes
+    cd "$REPO_PATH"
+    git add flake.lock
+
+    # Generate commit message with changed inputs
+    COMMIT_MSG="chore(flake): update flake.lock
+
+$(nix flake metadata "$FLAKE_PATH" --json 2>/dev/null | \
+  jq -r '.locks.nodes | to_entries[] | select(.key != "root") | "- \(.key): \(.value.locked.rev // .value.locked.narHash // "updated")"' 2>/dev/null || echo "Updated flake inputs")
+
+🤖 Automated update
+Built systems: $BUILD_SYSTEMS
+"
+
+    git commit -m "$COMMIT_MSG"
+
+    if [ "$DRY_RUN" = "false" ]; then
+      # Push to remote
+      log "Pushing to $GIT_REMOTE/$BRANCH_NAME"
+      git push "$GIT_REMOTE" "$BRANCH_NAME"
+
+      # Notify success
+      notify "default" "✅ Flake Updated Successfully" \
+        "Branch $BRANCH_NAME created and pushed. All builds passed: $BUILD_SYSTEMS" \
+        "white_check_mark,flake"
+
+      log "SUCCESS: Flake updated and pushed to $BRANCH_NAME"
+    else
+      log "DRY RUN: Would push to $GIT_REMOTE/$BRANCH_NAME"
+      notify "low" "🧪 Flake Update (Dry Run)" \
+        "Branch $BRANCH_NAME created locally. All builds passed: $BUILD_SYSTEMS" \
+        "test_tube,flake"
+    fi
+
+    # Return to main
+    git checkout main
+
+  else
+    log "Build failed, not committing changes"
+    notify "high" "❌ Flake Update Build Failed" \
+      "Builds failed for updated flake.lock. Check logs: $LOG_FILE" \
+      "x,flake,warning"
+
+    # Clean up failed branch
+    git checkout main
+    git branch -D "$BRANCH_NAME"
+    exit 1
+  fi
+
+else
+  log "No changes in flake.lock, nothing to do"
+  notify "low" "ℹ️ No Flake Updates" \
+    "flake.lock is already up to date" \
+    "information_source,flake"
+
+  # Clean up unused branch
+  git checkout main
+  git branch -D "$BRANCH_NAME"
+fi
+
+log "Flake update process complete"