397 lines
16 KiB
Nix
397 lines
16 KiB
Nix
# modules/motd/default.nix
|
||
{
|
||
config,
|
||
lib,
|
||
pkgs,
|
||
...
|
||
}:
|
||
with lib; let
|
||
cfg = config.homelab.motd;
|
||
|
||
homelab-motd = pkgs.writeShellScriptBin "homelab-motd" ''
|
||
#! /usr/bin/env bash
|
||
|
||
# Colors for output
|
||
RED="\e[31m"
|
||
GREEN="\e[32m"
|
||
YELLOW="\e[33m"
|
||
BLUE='\e[0;34m'
|
||
CYAN='\e[0;36m'
|
||
WHITE='\e[1;37m'
|
||
NC='\e[0m' # No Color
|
||
BOLD='\e[1m'
|
||
|
||
# Helper functions
|
||
print_header() {
|
||
echo -e "''${BOLD}''${BLUE}╔══════════════════════════════════════════════════════════════╗''${NC}"
|
||
echo -e "''${BOLD}''${BLUE}║''${NC}''${WHITE} 🏠 $(hostname -s) HOMELAB ''${NC}''${BOLD}''${BLUE}║''${NC}"
|
||
echo -e "''${BOLD}''${BLUE}╚══════════════════════════════════════════════════════════════╝''${NC}"
|
||
}
|
||
|
||
print_section() {
|
||
echo -e "\n''${BOLD}''${CYAN}▶ $1''${NC}"
|
||
echo -e "''${CYAN}─────────────────────────────────────────────────────────────''${NC}"
|
||
}
|
||
|
||
get_service_status() {
|
||
local service="$1"
|
||
if ${pkgs.systemd}/bin/systemctl is-active --quiet "$service" 2>/dev/null; then
|
||
echo -e "''${GREEN}●''${NC}"
|
||
elif ${pkgs.systemd}/bin/systemctl is-enabled --quiet "$service" 2>/dev/null; then
|
||
echo -e "''${YELLOW}○''${NC}"
|
||
else
|
||
echo -e "''${RED}×''${NC}"
|
||
fi
|
||
}
|
||
|
||
check_backup_issues() {
|
||
local issues=0
|
||
# Check for failed backup services in the last 24 hours
|
||
if ${pkgs.systemd}/bin/journalctl --since "24 hours ago" --unit="*backup*" --unit="restic*" --unit="borgbackup*" --priority=err --no-pager -q 2>/dev/null | grep -q .; then
|
||
issues=$((issues + 1))
|
||
fi
|
||
|
||
# Check for failed backup timers
|
||
local failed_timers=$(${pkgs.systemd}/bin/systemctl list-timers --failed --no-pager --no-legend 2>/dev/null | grep -E "(backup|restic|borgbackup)" | wc -l)
|
||
issues=$((issues + failed_timers))
|
||
|
||
echo $issues
|
||
}
|
||
|
||
# Main script
|
||
${optionalString cfg.clearScreen "clear"}
|
||
print_header
|
||
|
||
# System info
|
||
print_section "SYSTEM"
|
||
echo -e " ''${BOLD}Uptime:''${NC} $(${pkgs.procps}/bin/uptime -p | sed 's/up //')"
|
||
echo -e " ''${BOLD}Load:''${NC} $(${pkgs.procps}/bin/uptime | awk -F'load average:' '{print $2}' | xargs)"
|
||
echo -e " ''${BOLD}Memory:''${NC} $(${pkgs.procps}/bin/free -h | awk '/^Mem:/ {printf "%s/%s", $3, $2}')"
|
||
echo -e " ''${BOLD}Disk:''${NC} $(${pkgs.coreutils}/bin/df -h / | awk 'NR==2 {printf "%s/%s (%s)", $3, $2, $5}')"
|
||
|
||
${optionalString cfg.showServices ''
|
||
# Local homelab services (auto-detected + manual)
|
||
print_section "HOMELAB SERVICES"
|
||
|
||
# Auto-detect services from homelab configuration
|
||
${optionalString (config.homelab.services.gatus.enable or false) ''
|
||
status=$(get_service_status "gatus")
|
||
printf " %-20s %b %s\n" "gatus" "$status" "Uptime monitoring"
|
||
''}
|
||
|
||
${optionalString (config.homelab.services.prometheus.enable or false) ''
|
||
status=$(get_service_status "prometheus")
|
||
printf " %-20s %b %s\n" "prometheus" "$status" "Metrics collection"
|
||
''}
|
||
|
||
${optionalString (config.homelab.services.grafana.enable or false) ''
|
||
status=$(get_service_status "grafana")
|
||
printf " %-20s %b %s\n" "grafana" "$status" "Monitoring dashboard"
|
||
''}
|
||
|
||
${optionalString (config.homelab.services.alertmanager.enable or false) ''
|
||
status=$(get_service_status "alertmanager")
|
||
printf " %-20s %b %s\n" "alertmanager" "$status" "Alert routing"
|
||
''}
|
||
|
||
${optionalString (config.services.nginx.enable or false) ''
|
||
status=$(get_service_status "nginx")
|
||
printf " %-20s %b %s\n" "nginx" "$status" "Web server/proxy"
|
||
''}
|
||
|
||
${optionalString (config.services.postgresql.enable or false) ''
|
||
status=$(get_service_status "postgresql")
|
||
printf " %-20s %b %s\n" "postgresql" "$status" "Database server"
|
||
''}
|
||
|
||
${optionalString (config.services.redis.server.enable or false) ''
|
||
status=$(get_service_status "redis")
|
||
printf " %-20s %b %s\n" "redis" "$status" "Key-value store"
|
||
''}
|
||
|
||
# Manual services from configuration
|
||
${concatStringsSep "\n" (mapAttrsToList (name: service: ''
|
||
status=$(get_service_status "${service.systemdService}")
|
||
printf " %-20s %b %s\n" "${name}" "$status" "${service.description}"
|
||
'')
|
||
cfg.services)}
|
||
|
||
# Show legend
|
||
echo -e "\n ''${GREEN}●''${NC} Active ''${YELLOW}○''${NC} Inactive ''${RED}×''${NC} Disabled"
|
||
''}
|
||
|
||
# Quick backup check
|
||
backup_issues=$(check_backup_issues)
|
||
if [[ $backup_issues -gt 0 ]]; then
|
||
echo -e "\n''${BOLD}''${RED}⚠ WARNING: $backup_issues backup issues detected!''${NC}"
|
||
echo -e " Run ''${BOLD}homelab-backup-status''${NC} for details"
|
||
fi
|
||
|
||
# Recent critical issues
|
||
error_count=$(${pkgs.systemd}/bin/journalctl --since "24 hours ago" --priority=err --no-pager -q 2>/dev/null | wc -l || echo 0)
|
||
if [[ "$error_count" -gt 0 ]]; then
|
||
echo -e "\n''${BOLD}''${YELLOW}⚠ $error_count system errors in last 24h''${NC}"
|
||
echo -e " Run ''${BOLD}journalctl --priority=err --since='24 hours ago' ''${NC} for details"
|
||
fi
|
||
|
||
# Helpful commands
|
||
echo -e "\n''${BOLD}''${BLUE}╔══════════════════════════════════════════════════════════════╗''${NC}"
|
||
echo -e "''${BOLD}''${BLUE}║''${NC} ''${WHITE}Useful commands: ''${NC}''${BOLD}''${BLUE}║''${NC}"
|
||
echo -e "''${BOLD}''${BLUE}║''${NC} ''${CYAN}homelab-monitor-status''${NC} - Monitoring overview ''${BOLD}''${BLUE}║''${NC}"
|
||
echo -e "''${BOLD}''${BLUE}║''${NC} ''${CYAN}homelab-backup-status''${NC} - Backup jobs status ''${BOLD}''${BLUE}║''${NC}"
|
||
echo -e "''${BOLD}''${BLUE}║''${NC} ''${CYAN}homelab-proxy-status''${NC} - Reverse proxy entries ''${BOLD}''${BLUE}║''${NC}"
|
||
echo -e "''${BOLD}''${BLUE}║''${NC} ''${CYAN}systemctl status <srv>''${NC} - Check specific service ''${BOLD}''${BLUE}║''${NC}"
|
||
echo -e "''${BOLD}''${BLUE}╚══════════════════════════════════════════════════════════════╝''${NC}"
|
||
echo
|
||
'';
|
||
|
||
# Helper script for monitoring status
|
||
homelab-monitor-status = pkgs.writeShellScriptBin "homelab-monitor-status" ''
|
||
#! /usr/bin/env bash
|
||
|
||
# Colors
|
||
RED="\e[31m"
|
||
GREEN="\e[32m"
|
||
YELLOW="\e[33m"
|
||
BLUE='\e[0;34m'
|
||
CYAN='\e[0;36m'
|
||
WHITE='\e[1;37m'
|
||
NC='\e[0m'
|
||
BOLD='\e[1m'
|
||
|
||
CONFIG_FILE="/etc/homelab/config.json"
|
||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||
echo -e "''${RED}❌ Global homelab configuration not found''${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
echo -e "''${BOLD}''${BLUE}📊 Homelab Monitoring Status''${NC}"
|
||
echo -e "''${BLUE}=============================''${NC}"
|
||
|
||
# Show metrics endpoints
|
||
echo -e "\n''${BOLD}''${CYAN}Metrics Endpoints:''${NC}"
|
||
metrics_count=$(${pkgs.jq}/bin/jq '.monitoring.metrics | length' "$CONFIG_FILE" 2>/dev/null || echo 0)
|
||
if [[ $metrics_count -gt 0 ]]; then
|
||
${pkgs.jq}/bin/jq -r '.monitoring.metrics[]? | " ''${GREEN}●''${NC} \(.name): ''${BOLD}\(.host):\(.port)''${NC}\(.path) ''${YELLOW}(job: \(.jobName))''${NC}"' "$CONFIG_FILE" 2>/dev/null
|
||
echo -e "\n ''${BOLD}Total: ''${metrics_count} endpoints''${NC}"
|
||
else
|
||
echo -e " ''${YELLOW}No metrics endpoints configured''${NC}"
|
||
fi
|
||
|
||
# Show health checks by group
|
||
echo -e "\n''${BOLD}''${CYAN}Health Checks:''${NC}"
|
||
health_count=$(${pkgs.jq}/bin/jq '.monitoring.healthChecks | length' "$CONFIG_FILE" 2>/dev/null || echo 0)
|
||
if [[ $health_count -gt 0 ]]; then
|
||
# Group health checks
|
||
${pkgs.jq}/bin/jq -r '
|
||
.monitoring.healthChecks |
|
||
group_by(.group // "default") |
|
||
.[] |
|
||
"''${BOLD} \(.[0].group // "default" | ascii_upcase) Group:''${NC}" as $header |
|
||
($header, (
|
||
.[] |
|
||
" ''${if .enabled // true then "''${GREEN}●" else "''${YELLOW}○" end}''${NC} \(.name): ''${BOLD}\(.protocol)://\(.host)\(if .port then ":\(.port)" else "" end)''${NC}\(.path)"
|
||
))
|
||
' "$CONFIG_FILE" 2>/dev/null
|
||
echo -e "\n ''${BOLD}Total: ''${health_count} health checks''${NC}"
|
||
else
|
||
echo -e " ''${YELLOW}No health checks configured''${NC}"
|
||
fi
|
||
|
||
echo -e "\n''${CYAN}Run ''${BOLD}homelab-proxy-status''${NC}''${CYAN} and ''${BOLD}homelab-backup-status''${NC}''${CYAN} for more details.''${NC}"
|
||
'';
|
||
|
||
# Helper script for backup status
|
||
homelab-backup-status = pkgs.writeShellScriptBin "homelab-backup-status" ''
|
||
#! /usr/bin/env bash
|
||
|
||
# Colors
|
||
RED="\e[31m"
|
||
GREEN="\e[32m"
|
||
YELLOW="\e[33m"
|
||
BLUE='\e[0;34m'
|
||
CYAN='\e[0;36m'
|
||
WHITE='\e[1;37m'
|
||
NC='\e[0m'
|
||
BOLD='\e[1m'
|
||
|
||
echo -e "''${BOLD}''${BLUE}💾 Backup Status''${NC}"
|
||
echo -e "''${BLUE}===============''${NC}"
|
||
|
||
# Check backup timers
|
||
echo -e "\n''${BOLD}''${CYAN}Backup Timers:''${NC}"
|
||
backup_timers=$(${pkgs.systemd}/bin/systemctl list-timers --no-pager --no-legend 2>/dev/null | grep -E "(backup|restic|borgbackup)")
|
||
if [[ -n "$backup_timers" ]]; then
|
||
while IFS= read -r line; do
|
||
if [[ -n "$line" ]]; then
|
||
next=$(echo "$line" | awk '{print $1, $2}')
|
||
left=$(echo "$line" | awk '{print $3}')
|
||
timer=$(echo "$line" | awk '{print $5}')
|
||
service=$(echo "$line" | awk '{print $6}')
|
||
|
||
# Color code based on time left
|
||
if [[ "$left" == "n/a" ]]; then
|
||
color="''${RED}"
|
||
status="●"
|
||
elif echo "$left" | grep -qE "(sec|min|[0-9]h)"; then
|
||
color="''${YELLOW}"
|
||
status="●"
|
||
else
|
||
color="''${GREEN}"
|
||
status="●"
|
||
fi
|
||
|
||
printf " %b%s%b %-25s Next: %s (%s)\n" "$color" "$status" "$NC" "$(basename "$timer" .timer)" "$next" "$left"
|
||
fi
|
||
done <<< "$backup_timers"
|
||
else
|
||
echo -e " ''${YELLOW}No backup timers found''${NC}"
|
||
fi
|
||
|
||
# Check recent backup activity (last 3 days, summarized)
|
||
echo -e "\n''${BOLD}''${CYAN}Recent Activity (3 days):''${NC}"
|
||
|
||
# Count successful vs failed backups
|
||
success_count=$(${pkgs.systemd}/bin/journalctl --since "3 days ago" --unit="*backup*" --unit="restic*" --unit="borgbackup*" --no-pager -q 2>/dev/null | grep -iE "(completed|success|finished)" | wc -l)
|
||
error_count=$(${pkgs.systemd}/bin/journalctl --since "3 days ago" --unit="*backup*" --unit="restic*" --unit="borgbackup*" --priority=err --no-pager -q 2>/dev/null | wc -l)
|
||
|
||
if [[ $success_count -gt 0 ]]; then
|
||
echo -e " ''${GREEN}✅ $success_count successful backups''${NC}"
|
||
fi
|
||
if [[ $error_count -gt 0 ]]; then
|
||
echo -e " ''${RED}❌ $error_count failed backups''${NC}"
|
||
echo -e "\n''${BOLD}''${RED}Recent Failures:''${NC}"
|
||
${pkgs.systemd}/bin/journalctl --since "3 days ago" --unit="*backup*" --unit="restic*" --unit="borgbackup*" --priority=err --no-pager --lines=3 2>/dev/null | while read -r line; do
|
||
# Extract just the important parts
|
||
timestamp=$(echo "$line" | awk '{print $1, $2, $3}')
|
||
service=$(echo "$line" | grep -oE "(restic-backups-[^[]+|borgbackup-job-[^[]+|[^[]*backup[^[]*)" | head -1)
|
||
message=$(echo "$line" | sed -E 's/.*\]: //' | cut -c1-60)
|
||
echo -e " ''${YELLOW}$timestamp''${NC} ''${BOLD}$service''${NC}: $message..."
|
||
done
|
||
elif [[ $success_count -eq 0 ]]; then
|
||
echo -e " ''${YELLOW}⚠️ No backup activity in last 3 days''${NC}"
|
||
else
|
||
echo -e " ''${GREEN}✅ All backups completed successfully''${NC}"
|
||
fi
|
||
|
||
# Show backup summary from global config if available
|
||
CONFIG_FILE="/etc/homelab/config.json"
|
||
if [[ -f "$CONFIG_FILE" ]]; then
|
||
total_jobs=$(${pkgs.jq}/bin/jq -r '.backups.summary.totalJobs // 0' "$CONFIG_FILE" 2>/dev/null)
|
||
backends=$(${pkgs.jq}/bin/jq -r '.backups.summary.backendsInUse[]?' "$CONFIG_FILE" 2>/dev/null | tr '\n' ' ')
|
||
|
||
if [[ $total_jobs -gt 0 ]]; then
|
||
echo -e "\n''${BOLD}''${CYAN}Configuration:''${NC}"
|
||
echo -e " ''${BOLD}Total jobs:''${NC} $total_jobs"
|
||
if [[ -n "$backends" ]]; then
|
||
echo -e " ''${BOLD}Backends:''${NC} $backends"
|
||
fi
|
||
fi
|
||
fi
|
||
'';
|
||
|
||
# Helper script for proxy status
|
||
homelab-proxy-status = pkgs.writeShellScriptBin "homelab-proxy-status" ''
|
||
#! /usr/bin/env bash
|
||
|
||
# Colors
|
||
RED="\e[31m"
|
||
GREEN="\e[32m"
|
||
YELLOW="\e[33m"
|
||
BLUE='\e[0;34m'
|
||
CYAN='\e[0;36m'
|
||
WHITE='\e[1;37m'
|
||
NC='\e[0m'
|
||
BOLD='\e[1m'
|
||
|
||
CONFIG_FILE="/etc/homelab/config.json"
|
||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||
echo -e "''${RED}❌ Global homelab configuration not found''${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
echo -e "''${BOLD}''${BLUE}🔗 Reverse Proxy Status''${NC}"
|
||
echo -e "''${BLUE}======================''${NC}"
|
||
|
||
proxy_count=$(${pkgs.jq}/bin/jq '.reverseProxy.entries | length' "$CONFIG_FILE" 2>/dev/null || echo 0)
|
||
if [[ $proxy_count -gt 0 ]]; then
|
||
${pkgs.jq}/bin/jq -r '.reverseProxy.entries[]? |
|
||
" ''${GREEN}●''${NC} ''${BOLD}\(.subdomain)''${NC}: \(.externalHost) → \(.internalHost)\(if .enableAuth then " ''${YELLOW}🔐''${NC}" else "" end)\(if .enableSSL then " ''${GREEN}🔒''${NC}" else "" end)"' "$CONFIG_FILE" 2>/dev/null
|
||
|
||
echo -e "\n''${BOLD}Legend:''${NC} ''${YELLOW}🔐''${NC} Auth enabled, ''${GREEN}🔒''${NC} SSL enabled"
|
||
echo -e "''${BOLD}Total: ''${proxy_count} proxy entries''${NC}"
|
||
else
|
||
echo -e " ''${YELLOW}No proxy entries configured''${NC}"
|
||
fi
|
||
'';
|
||
in {
|
||
options.homelab.motd = {
|
||
enable = mkEnableOption "Simple homelab MOTD";
|
||
|
||
clearScreen = mkOption {
|
||
type = types.bool;
|
||
default = true;
|
||
description = "Clear screen before showing MOTD";
|
||
};
|
||
|
||
showServices = mkOption {
|
||
type = types.bool;
|
||
default = true;
|
||
description = "Show local homelab services status";
|
||
};
|
||
|
||
services = mkOption {
|
||
type = types.attrsOf (types.submodule {
|
||
options = {
|
||
systemdService = mkOption {
|
||
type = types.str;
|
||
description = "Name of the systemd service to monitor";
|
||
};
|
||
description = mkOption {
|
||
type = types.str;
|
||
default = "";
|
||
description = "Human-readable description of the service";
|
||
};
|
||
};
|
||
});
|
||
default = {};
|
||
description = "Local homelab services to show in MOTD";
|
||
example = literalExpression ''
|
||
{
|
||
"nginx" = {
|
||
systemdService = "nginx";
|
||
description = "Web server";
|
||
};
|
||
"grafana" = {
|
||
systemdService = "grafana";
|
||
description = "Monitoring dashboard";
|
||
};
|
||
}
|
||
'';
|
||
};
|
||
};
|
||
|
||
config = mkIf cfg.enable {
|
||
# Create helper commands
|
||
environment.systemPackages = with pkgs; [
|
||
jq
|
||
homelab-motd
|
||
homelab-monitor-status
|
||
homelab-backup-status
|
||
homelab-proxy-status
|
||
];
|
||
|
||
# Set up MOTD to run on login
|
||
programs.bash.interactiveShellInit = ''
|
||
# Run homelab MOTD on interactive login (only once per session)
|
||
if [[ $- == *i* ]] && [[ -z "$MOTD_SHOWN" ]] && [[ -n "$SSH_CONNECTION" || "$TERM" == "linux" ]]; then
|
||
export MOTD_SHOWN=1
|
||
${homelab-motd}/bin/homelab-motd
|
||
fi
|
||
'';
|
||
|
||
# Disable default MOTD
|
||
users.motd = mkDefault "";
|
||
security.pam.services.login.showMotd = mkDefault false;
|
||
};
|
||
}
|