|
#!/usr/bin/env bash |
|
|
|
# ============================================================================ |
|
# NixOS Wrapped 2025 - Configuration |
|
# ============================================================================ |
|
|
|
# Year to analyze |
|
YEAR="2025" |
|
|
|
# Local git repositories to analyze (space-separated paths) |
|
REPOS=( |
|
"/etc/nixos/" |
|
#"$HOME/src/dotfiles" |
|
#"$HOME/src/infra" |
|
) |
|
|
|
# Remote machines to query for generations (SSH hosts) |
|
# Leave empty to skip remote queries: REMOTE_HOSTS=() |
|
REMOTE_HOSTS=( |
|
# "user@server1" |
|
# "user@server2" |
|
) |
|
|
|
# Thresholds for personality messages |
|
THRESHOLD_FLAKE_LOCK_HIGH=50 |
|
THRESHOLD_FLAKE_LOCK_MED=20 |
|
THRESHOLD_FIX_HIGH=30 |
|
THRESHOLD_FIX_MED=10 |
|
THRESHOLD_BUILDER_ERA=60 # % new additions to be "builder era" |
|
THRESHOLD_MAINTAINER=30 # % new additions to be "maintainer mode" |
|
THRESHOLD_LARGE_COMMIT_FILES=10 |
|
THRESHOLD_LARGE_COMMITS_HIGH=5 |
|
THRESHOLD_SERVICES_HIGH=10 |
|
THRESHOLD_SERVICES_MED=5 |
|
THRESHOLD_COMMITS_WILD=250 |
|
THRESHOLD_COMMITS_HIGH=150 |
|
THRESHOLD_COMMITS_MED=50 |
|
|
|
# Services to detect in commit messages |
|
SERVICES_PATTERN="(nextcloud|jellyfin|grafana|prometheus|forgejo|openwebui|home.?assistant|coder)" |
|
|
|
# ============================================================================ |
|
# Helper Functions |
|
# ============================================================================ |
|
|
|
ask_yes_no() { |
|
local prompt="$1" |
|
local default="${2:-n}" |
|
|
|
if [[ "$default" == "y" ]]; then |
|
prompt="$prompt [Y/n] " |
|
else |
|
prompt="$prompt [y/N] " |
|
fi |
|
|
|
read -r -p "$prompt" response |
|
response="${response,,}" # lowercase |
|
|
|
if [[ -z "$response" ]]; then |
|
response="$default" |
|
fi |
|
|
|
[[ "$response" == "y" || "$response" == "yes" ]] |
|
} |
|
|
|
get_highest_generation() { |
|
# Parse generation list and return highest number |
|
grep -E '^\s*[0-9]+' 2>/dev/null | awk '{print $1}' | sort -n | tail -1 |
|
} |
|
|
|
# This script is sent to remote hosts to gather their generation info |
|
# shellcheck disable=SC2016 # Single quotes intentional - variables expand on remote host |
|
REMOTE_SCRIPT=' |
|
#!/usr/bin/env bash |
|
hostname=$(hostname) |
|
sys_gen=$(nix-env --list-generations --profile /nix/var/nix/profiles/system 2>/dev/null | grep -E "^\s*[0-9]+" | awk "{print \$1}" | sort -n | tail -1) |
|
sys_gen=${sys_gen:-0} |
|
|
|
hm_gen=0 |
|
if [ -d "$HOME/.local/state/nix/profiles" ]; then |
|
hm_gen=$(nix-env --list-generations --profile "$HOME/.local/state/nix/profiles/home-manager" 2>/dev/null | grep -E "^\s*[0-9]+" | awk "{print \$1}" | sort -n | tail -1) |
|
elif [ -d "$HOME/.nix-profile" ]; then |
|
hm_gen=$(nix-env --list-generations 2>/dev/null | grep -E "^\s*[0-9]+" | awk "{print \$1}" | sort -n | tail -1) |
|
fi |
|
hm_gen=${hm_gen:-0} |
|
|
|
echo "HOST:$hostname" |
|
echo "SYS_GEN:$sys_gen" |
|
echo "HM_GEN:$hm_gen" |
|
' |
|
|
|
# ============================================================================ |
|
# Core Functions |
|
# ============================================================================ |
|
|
|
get_local_system_generations() { |
|
local_sys_generations=0 |
|
echo "To count system generations, this script needs to run:" |
|
echo " sudo nix-env --list-generations --profile /nix/var/nix/profiles/system" |
|
echo "" |
|
|
|
if ask_yes_no "Allow sudo for this command?"; then |
|
local_sys_generations=$(sudo nix-env --list-generations --profile /nix/var/nix/profiles/system 2>/dev/null | get_highest_generation) |
|
local_sys_generations=${local_sys_generations:-0} |
|
else |
|
echo "Skipping system generation count." |
|
fi |
|
echo "" |
|
} |
|
|
|
get_local_hm_generations() { |
|
local_hm_generations=0 |
|
if [ -d "$HOME/.local/state/nix/profiles" ]; then |
|
local_hm_generations=$(nix-env --list-generations --profile "$HOME/.local/state/nix/profiles/home-manager" 2>/dev/null | get_highest_generation) |
|
local_hm_generations=${local_hm_generations:-0} |
|
elif [ -d "$HOME/.nix-profile" ]; then |
|
local_hm_generations=$(nix-env --list-generations 2>/dev/null | get_highest_generation) |
|
local_hm_generations=${local_hm_generations:-0} |
|
fi |
|
} |
|
|
|
query_remote_machines() { |
|
declare -g -A remote_sys_gens |
|
declare -g -A remote_hm_gens |
|
total_remote_sys=0 |
|
total_remote_hm=0 |
|
|
|
if [ ${#REMOTE_HOSTS[@]} -gt 0 ]; then |
|
echo "π‘ Querying ${#REMOTE_HOSTS[@]} remote machine(s)..." |
|
echo "" |
|
|
|
for host in "${REMOTE_HOSTS[@]}"; do |
|
echo -n " Connecting to $host... " |
|
|
|
if result=$(ssh -o ConnectTimeout=5 -o BatchMode=yes "$host" "$REMOTE_SCRIPT" 2>/dev/null); then |
|
hostname=$(echo "$result" | grep "^HOST:" | cut -d: -f2) |
|
sys_gen=$(echo "$result" | grep "^SYS_GEN:" | cut -d: -f2) |
|
hm_gen=$(echo "$result" | grep "^HM_GEN:" | cut -d: -f2) |
|
|
|
remote_sys_gens["$hostname"]=$sys_gen |
|
remote_hm_gens["$hostname"]=$hm_gen |
|
total_remote_sys=$((total_remote_sys + sys_gen)) |
|
total_remote_hm=$((total_remote_hm + hm_gen)) |
|
|
|
echo "β (sys: $sys_gen, hm: $hm_gen)" |
|
else |
|
echo "β (unreachable)" |
|
fi |
|
done |
|
echo "" |
|
fi |
|
} |
|
|
|
print_generation_summary() { |
|
total_sys_generations=$((local_sys_generations + total_remote_sys)) |
|
total_hm_generations=$((local_hm_generations + total_remote_hm)) |
|
|
|
echo "π¦ System Generations: $total_sys_generations total" |
|
if [ "$local_sys_generations" -gt 0 ]; then |
|
echo " ββ local: $local_sys_generations" |
|
fi |
|
for hostname in "${!remote_sys_gens[@]}"; do |
|
echo " ββ $hostname: ${remote_sys_gens[$hostname]}" |
|
done |
|
|
|
if [ "$total_hm_generations" -gt 0 ]; then |
|
echo "π Home-Manager Generations: $total_hm_generations total" |
|
if [ "$local_hm_generations" -gt 0 ]; then |
|
echo " ββ local: $local_hm_generations" |
|
fi |
|
for hostname in "${!remote_hm_gens[@]}"; do |
|
if [ "${remote_hm_gens[$hostname]}" -gt 0 ]; then |
|
echo " ββ $hostname: ${remote_hm_gens[$hostname]}" |
|
fi |
|
done |
|
fi |
|
echo "" |
|
} |
|
|
|
analyze_git_repos() { |
|
total_commits=0 |
|
flake_lock_commits=0 |
|
fix_commits=0 |
|
merge_commits=0 |
|
added_commits=0 |
|
updated_commits=0 |
|
large_commits=0 |
|
services_deployed=0 |
|
|
|
for repo in "${REPOS[@]}"; do |
|
if [ ! -d "$repo/.git" ]; then |
|
continue |
|
fi |
|
|
|
commits=$(git -C "$repo" log --since="$YEAR-01-01" --oneline 2>/dev/null | wc -l | tr -d ' ') |
|
total_commits=$((total_commits + commits)) |
|
|
|
flake_locks=$(git -C "$repo" log --since="$YEAR-01-01" --oneline 2>/dev/null | grep -ci "flake lock") |
|
flake_lock_commits=$((flake_lock_commits + flake_locks)) |
|
|
|
fixes=$(git -C "$repo" log --since="$YEAR-01-01" --oneline 2>/dev/null | grep -ciE "^[a-f0-9]+ (fix|fixed)") |
|
fix_commits=$((fix_commits + fixes)) |
|
|
|
merges=$(git -C "$repo" log --since="$YEAR-01-01" --oneline 2>/dev/null | grep -ci "^[a-f0-9]+ merge") |
|
merge_commits=$((merge_commits + merges)) |
|
|
|
added=$(git -C "$repo" log --since="$YEAR-01-01" --oneline 2>/dev/null | grep -ci "^[a-f0-9]+ add") |
|
added_commits=$((added_commits + added)) |
|
|
|
updated=$(git -C "$repo" log --since="$YEAR-01-01" --oneline 2>/dev/null | grep -ci "^[a-f0-9]+ updat") |
|
updated_commits=$((updated_commits + updated)) |
|
|
|
large=$(git -C "$repo" log --since="$YEAR-01-01" --pretty=format:"%h" --shortstat 2>/dev/null | \ |
|
awk -v threshold="$THRESHOLD_LARGE_COMMIT_FILES" '/^ [0-9]+ file/ {if ($1 >= threshold) count++} END {print count+0}') |
|
large_commits=$((large_commits + large)) |
|
|
|
services=$(git -C "$repo" log --since="$YEAR-01-01" --oneline 2>/dev/null | \ |
|
grep -ciE "(added|enabled|setup|configured).*$SERVICES_PATTERN") |
|
services_deployed=$((services_deployed + services)) |
|
done |
|
|
|
echo "πΎ You Made $total_commits Commits To Your Config Repos." |
|
echo "" |
|
} |
|
|
|
print_personality_insights() { |
|
if [ "$flake_lock_commits" -gt "$THRESHOLD_FLAKE_LOCK_HIGH" ]; then |
|
echo "π You Updated flake.lock $flake_lock_commits times." |
|
echo " Dependency management is your cardio." |
|
echo "" |
|
elif [ "$flake_lock_commits" -gt "$THRESHOLD_FLAKE_LOCK_MED" ]; then |
|
echo "π You Updated flake.lock $flake_lock_commits times." |
|
echo " Staying fresh with dependencies!" |
|
echo "" |
|
fi |
|
|
|
if [ "$fix_commits" -gt "$THRESHOLD_FIX_HIGH" ]; then |
|
echo "π§ You Fixed Things $fix_commits Times." |
|
echo " Professional bug whisperer status achieved." |
|
echo "" |
|
elif [ "$fix_commits" -gt "$THRESHOLD_FIX_MED" ]; then |
|
echo "π§ You Fixed Things $fix_commits Times." |
|
echo " The debugging was real this year." |
|
echo "" |
|
fi |
|
|
|
if [ "$added_commits" -gt 0 ] && [ "$updated_commits" -gt 0 ]; then |
|
ratio=$((added_commits * 100 / (added_commits + updated_commits))) |
|
if [ "$ratio" -gt "$THRESHOLD_BUILDER_ERA" ]; then |
|
echo "π ${ratio}% of your commits were adding new things." |
|
echo " You're in your builder era." |
|
echo "" |
|
elif [ "$ratio" -lt "$THRESHOLD_MAINTAINER" ]; then |
|
echo "π Only ${ratio}% of your commits were new additions." |
|
echo " Maintainer mode activated." |
|
echo "" |
|
fi |
|
fi |
|
|
|
if [ "$large_commits" -gt "$THRESHOLD_LARGE_COMMITS_HIGH" ]; then |
|
echo "ποΈ You Did $large_commits Major Restructures." |
|
echo " Brave enough to touch ${THRESHOLD_LARGE_COMMIT_FILES}+ files at once." |
|
echo "" |
|
elif [ "$large_commits" -gt 0 ]; then |
|
echo "ποΈ You Did $large_commits Major Restructures." |
|
echo "" |
|
fi |
|
|
|
if [ "$services_deployed" -gt "$THRESHOLD_SERVICES_HIGH" ]; then |
|
echo "π You Deployed/Configured Services $services_deployed Times." |
|
echo " Your homelab is basically a small datacenter." |
|
echo "" |
|
elif [ "$services_deployed" -gt "$THRESHOLD_SERVICES_MED" ]; then |
|
echo "π You Deployed/Configured Services $services_deployed Times." |
|
echo " Self-hosting champion." |
|
echo "" |
|
fi |
|
} |
|
|
|
print_punchline() { |
|
echo "ββββββββββββββββββββ" |
|
if [ "$total_commits" -gt "$THRESHOLD_COMMITS_WILD" ]; then |
|
echo "β Do You Need Help?" |
|
echo "" |
|
echo " (No seriously, $total_commits commits is wild.)" |
|
elif [ "$total_commits" -gt "$THRESHOLD_COMMITS_HIGH" ]; then |
|
echo "π
That's... a lot of commits." |
|
echo "" |
|
echo " But hey, at least it's all in version control!" |
|
elif [ "$total_commits" -gt "$THRESHOLD_COMMITS_MED" ]; then |
|
echo "β¨ Pretty active NixOS year!" |
|
else |
|
echo "β¨ Steady and sensible. We respect it." |
|
fi |
|
} |
|
|
|
# ============================================================================ |
|
# Main |
|
# ============================================================================ |
|
|
|
main() { |
|
echo "π Nix Wrapped $YEAR" |
|
echo "====================" |
|
echo "" |
|
|
|
get_local_system_generations |
|
get_local_hm_generations |
|
query_remote_machines |
|
print_generation_summary |
|
analyze_git_repos |
|
print_personality_insights |
|
print_punchline |
|
} |
|
|
|
# Only execute if script is run directly (not sourced) |
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then |
|
main "$@" |
|
fi |