(untested) config for stinky and diff script.
This commit is contained in:
250
scripts/diff-configs.sh
Executable file
250
scripts/diff-configs.sh
Executable file
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env bash
|
||||
# Compare NixOS configurations between current state and HEAD
|
||||
# Shows what would change if you committed the current changes
|
||||
#
|
||||
# Requirements: nvd must be in PATH
|
||||
# Run inside `nix develop` or with direnv enabled
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[0;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check for nvd
|
||||
if ! command -v nvd &> /dev/null; then
|
||||
echo "Error: nvd not found in PATH"
|
||||
echo "Run this script inside 'nix develop' or enable direnv"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse flags
|
||||
verbose=false
|
||||
deep=false
|
||||
hosts_args=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
echo "Usage: $0 [-v|--verbose] [-d|--deep] [HOST...]"
|
||||
echo "Compare NixOS configurations between working tree and HEAD"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -v, --verbose Show detailed list of added/removed store paths"
|
||||
echo " -d, --deep Show content diffs of changed files (implies -v)"
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " HOST One or more hostnames to compare (default: all)"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 # Compare all hosts (summary)"
|
||||
echo " $0 -v c1 # Compare c1 with path list"
|
||||
echo " $0 --deep c1 # Compare c1 with content diffs"
|
||||
echo " $0 c1 c2 c3 # Compare only c1, c2, c3"
|
||||
exit 0
|
||||
;;
|
||||
-v|--verbose)
|
||||
verbose=true
|
||||
shift
|
||||
;;
|
||||
-d|--deep)
|
||||
deep=true
|
||||
verbose=true # deep implies verbose
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
hosts_args+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Restore positional parameters
|
||||
set -- "${hosts_args[@]}"
|
||||
|
||||
# Check if we're in a git repo
|
||||
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
echo "Error: Not in a git repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if there are any changes
|
||||
if git diff --quiet && git diff --cached --quiet; then
|
||||
echo "No changes detected between working tree and HEAD"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Comparing configurations: current working tree vs HEAD"
|
||||
echo "======================================================="
|
||||
echo
|
||||
|
||||
# Get list of hosts to compare
|
||||
if [ $# -gt 0 ]; then
|
||||
# Use hosts provided as arguments
|
||||
hosts="$@"
|
||||
echo -e "${YELLOW}Comparing selected hosts: $hosts${NC}"
|
||||
else
|
||||
# Get all hosts from flake
|
||||
echo "Discovering all hosts from flake..."
|
||||
hosts=$(nix eval --raw .#deploy.nodes --apply 'nodes: builtins.concatStringsSep "\n" (builtins.attrNames nodes)' 2>/dev/null)
|
||||
|
||||
if [ -z "$hosts" ]; then
|
||||
echo "Error: No hosts found in flake"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
# Create temp worktree at HEAD
|
||||
worktree=$(mktemp -d)
|
||||
trap "git worktree remove --force '$worktree' &>/dev/null || true; rm -rf '$worktree'" EXIT
|
||||
|
||||
echo "Creating temporary worktree at HEAD..."
|
||||
git worktree add --quiet --detach "$worktree" HEAD
|
||||
|
||||
echo "Building and comparing configurations..."
|
||||
echo
|
||||
|
||||
any_changes=false
|
||||
|
||||
for host in $hosts; do
|
||||
echo -e "${BLUE}━━━ $host ━━━${NC}"
|
||||
|
||||
# Build current (with uncommitted changes)
|
||||
echo -n " Building current... "
|
||||
if ! current=$(nix build --no-link --print-out-paths \
|
||||
".#nixosConfigurations.$host.config.system.build.toplevel" 2>/dev/null); then
|
||||
echo -e "${RED}FAILED${NC}"
|
||||
# Re-run to show error
|
||||
nix build --no-link ".#nixosConfigurations.$host.config.system.build.toplevel" 2>&1 | head -20 | sed 's/^/ /'
|
||||
continue
|
||||
fi
|
||||
echo "done"
|
||||
|
||||
# Build HEAD
|
||||
echo -n " Building HEAD... "
|
||||
if ! head=$(nix build --no-link --print-out-paths \
|
||||
"$worktree#nixosConfigurations.$host.config.system.build.toplevel" 2>/dev/null); then
|
||||
echo -e "${RED}FAILED${NC}"
|
||||
# Re-run to show error
|
||||
nix build --no-link "$worktree#nixosConfigurations.$host.config.system.build.toplevel" 2>&1 | head -20 | sed 's/^/ /'
|
||||
continue
|
||||
fi
|
||||
echo "done"
|
||||
|
||||
# Compare
|
||||
if [ "$head" = "$current" ]; then
|
||||
echo -e " ${GREEN}✓ No changes${NC}"
|
||||
else
|
||||
any_changes=true
|
||||
echo -e " ${RED}⚠ Configuration changed${NC}"
|
||||
echo
|
||||
|
||||
# Show nvd summary
|
||||
if ! nvd diff "$head" "$current" 2>&1; then
|
||||
echo -e " ${RED}(nvd diff failed - see error above)${NC}"
|
||||
fi
|
||||
|
||||
# Show detailed closure diff if verbose
|
||||
if [ "$verbose" = true ]; then
|
||||
echo
|
||||
echo -e " ${YELLOW}Changed store paths:${NC}"
|
||||
|
||||
# Get paths unique to HEAD and current
|
||||
head_only=$(comm -23 <(nix-store -q --requisites "$head" 2>/dev/null | sort) \
|
||||
<(nix-store -q --requisites "$current" 2>/dev/null | sort))
|
||||
current_only=$(comm -13 <(nix-store -q --requisites "$head" 2>/dev/null | sort) \
|
||||
<(nix-store -q --requisites "$current" 2>/dev/null | sort))
|
||||
|
||||
# Count changes
|
||||
removed_count=$(echo "$head_only" | wc -l)
|
||||
added_count=$(echo "$current_only" | wc -l)
|
||||
|
||||
echo -e " ${RED}Removed ($removed_count paths):${NC}"
|
||||
echo "$head_only" | head -10 | sed 's|^/nix/store/[^-]*-| - |'
|
||||
if [ "$removed_count" -gt 10 ]; then
|
||||
echo " ... and $((removed_count - 10)) more"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e " ${GREEN}Added ($added_count paths):${NC}"
|
||||
echo "$current_only" | head -10 | sed 's|^/nix/store/[^-]*-| - |'
|
||||
if [ "$added_count" -gt 10 ]; then
|
||||
echo " ... and $((added_count - 10)) more"
|
||||
fi
|
||||
|
||||
# Show content diffs if deep mode
|
||||
if [ "$deep" = true ]; then
|
||||
echo
|
||||
echo -e " ${YELLOW}Content diffs of changed files:${NC}"
|
||||
|
||||
# Extract basenames for matching
|
||||
declare -A head_paths
|
||||
while IFS= read -r path; do
|
||||
[ -z "$path" ] && continue
|
||||
basename="${path#/nix/store/[a-z0-9]*-}"
|
||||
head_paths["$basename"]="$path"
|
||||
done <<< "$head_only"
|
||||
|
||||
# Find matching pairs and diff them
|
||||
matched=false
|
||||
while IFS= read -r path; do
|
||||
[ -z "$path" ] && continue
|
||||
basename="${path#/nix/store/[a-z0-9]*-}"
|
||||
|
||||
# Check if we have a matching path in head
|
||||
if [ -n "${head_paths[$basename]:-}" ]; then
|
||||
old_path="${head_paths[$basename]}"
|
||||
new_path="$path"
|
||||
matched=true
|
||||
|
||||
echo
|
||||
echo -e " ${BLUE}▸ $basename${NC}"
|
||||
|
||||
# If it's a directory, diff key files within it
|
||||
if [ -d "$old_path" ] && [ -d "$new_path" ]; then
|
||||
# Focus on important files
|
||||
for pattern in "activate" "etc/*" "*.conf" "*.fish" "*.service" "*.nix"; do
|
||||
while IFS= read -r file; do
|
||||
[ -z "$file" ] && continue
|
||||
relpath="${file#$new_path/}"
|
||||
old_file="$old_path/$relpath"
|
||||
|
||||
if [ -f "$old_file" ] && [ -f "$file" ]; then
|
||||
# Check if file is text
|
||||
if file "$file" | grep -q "text"; then
|
||||
echo -e " ${YELLOW}$relpath:${NC}"
|
||||
diff -u "$old_file" "$file" 2>/dev/null | head -50 | tail -n +3 | sed 's/^/ /' || true
|
||||
fi
|
||||
fi
|
||||
done < <(find "$new_path" -type f -name "$pattern" 2>/dev/null | head -20)
|
||||
done
|
||||
# If it's a file, diff it directly
|
||||
elif [ -f "$old_path" ] && [ -f "$new_path" ]; then
|
||||
if file "$new_path" | grep -q "text"; then
|
||||
diff -u "$old_path" "$new_path" 2>/dev/null | head -50 | tail -n +3 | sed 's/^/ /' || true
|
||||
else
|
||||
echo " (binary file)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done <<< "$current_only"
|
||||
|
||||
if [ "$matched" = false ]; then
|
||||
echo " (no matching paths found to compare)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo
|
||||
done
|
||||
|
||||
if [ "$any_changes" = false ]; then
|
||||
echo -e "${GREEN}✓ All configurations unchanged${NC}"
|
||||
else
|
||||
echo -e "${RED}⚠ Some configurations changed - review carefully before committing${NC}"
|
||||
fi
|
||||
Reference in New Issue
Block a user