# modules/motd/default.nix { config, lib, pkgs, ... }: with lib; let cfg = config.homelab.motd; globalCfg = config.homelab.global; enabledServices = filterAttrs (name: service: service.enable) globalCfg.services; homelab-motd = pkgs.writeShellScriptBin "homelab-motd" '' #! /usr/bin/env bash source /etc/os-release # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' WHITE='\033[1;37m' NC='\033[0m' # No Color BOLD='\033[1m' # Helper functions print_header() { echo -e "''${BOLD}''${BLUE}╔══════════════════════════════════════════════════════════════╗''${NC}" echo -e "''${BOLD}''${BLUE}║''${NC}''${WHITE} 🏠 HOMELAB STATUS ''${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} Active" elif ${pkgs.systemd}/bin/systemctl is-enabled --quiet "$service" 2>/dev/null; then echo -e "''${YELLOW}●''${NC} Inactive" else echo -e "''${RED}●''${NC} Disabled" fi } get_timer_status() { local timer="$1" if ${pkgs.systemd}/bin/systemctl is-active --quiet "$timer" 2>/dev/null; then local next_run=$(${pkgs.systemd}/bin/systemctl show "$timer" --property=NextElapseUSecRealtime --value 2>/dev/null || echo "0") if [[ "$next_run" != "0" && "$next_run" != "n/a" ]]; then local next_readable=$(${pkgs.systemd}/bin/systemctl list-timers --no-pager "$timer" 2>/dev/null | tail -n +2 | head -n 1 | awk '{print $1, $2}' || echo "Unknown") echo -e "''${GREEN}●''${NC} Next: ''${next_readable}" else echo -e "''${GREEN}●''${NC} Active" fi else echo -e "''${RED}●''${NC} Inactive" fi } # Main script ${optionalString cfg.clearScreen "clear"} print_header # Check if global config exists CONFIG_FILE="/etc/homelab/config.json" if [[ ! -f "$CONFIG_FILE" ]]; then echo -e "''${RED}❌ Global homelab configuration not found at $CONFIG_FILE''${NC}" exit 1 fi # Parse global configuration HOSTNAME=$(${pkgs.jq}/bin/jq -r '.hostname' "$CONFIG_FILE" 2>/dev/null || hostname) DOMAIN=$(${pkgs.jq}/bin/jq -r '.domain' "$CONFIG_FILE" 2>/dev/null || echo "unknown") ENVIRONMENT=$(${pkgs.jq}/bin/jq -r '.environment' "$CONFIG_FILE" 2>/dev/null || echo "unknown") LOCATION=$(${pkgs.jq}/bin/jq -r '.location' "$CONFIG_FILE" 2>/dev/null || echo "unknown") TAGS=$(${pkgs.jq}/bin/jq -r '.tags[]?' "$CONFIG_FILE" 2>/dev/null | tr '\n' ' ' || echo "none") print_section "SYSTEM INFO" echo -e " ''${BOLD}Hostname:''${NC} $HOSTNAME" echo -e " ''${BOLD}Domain:''${NC} $DOMAIN" echo -e " ''${BOLD}Environment:''${NC} $ENVIRONMENT" echo -e " ''${BOLD}Location:''${NC} $LOCATION" echo -e " ''${BOLD}Tags:''${NC} ''${TAGS:-none}" echo -e " ''${BOLD}Uptime:''${NC} $(${pkgs.procps}/bin/uptime -p)" echo -e " ''${BOLD}Load:''${NC} $(${pkgs.procps}/bin/uptime | awk -F'load average:' '{print $2}' | xargs)" ${optionalString cfg.showServices '' # Enabled services from homelab config print_section "HOMELAB SERVICES" ${concatStringsSep "\n" (mapAttrsToList (name: service: '' status=$(get_service_status "${service.systemdService}") printf " %-25s %s\n" "${name}" "$status" '') cfg.services)} ''} ${optionalString cfg.showMonitoring '' # Monitoring endpoints print_section "MONITORING ENDPOINTS" ENDPOINTS=$(${pkgs.jq}/bin/jq -c '.monitoring.endpoints[]?' "$CONFIG_FILE" 2>/dev/null || echo "") if [[ -n "$ENDPOINTS" ]]; then while IFS= read -r endpoint; do name=$(echo "$endpoint" | ${pkgs.jq}/bin/jq -r '.name') port=$(echo "$endpoint" | ${pkgs.jq}/bin/jq -r '.port') path=$(echo "$endpoint" | ${pkgs.jq}/bin/jq -r '.path') job=$(echo "$endpoint" | ${pkgs.jq}/bin/jq -r '.jobName') # Check if port is accessible if ${pkgs.netcat}/bin/nc -z localhost "$port" 2>/dev/null; then status="''${GREEN}●''${NC}" else status="''${RED}●''${NC}" fi printf " %-20s %s %s:%s%s (job: %s)\n" "$name" "$status" "$HOSTNAME" "$port" "$path" "$job" done <<< "$ENDPOINTS" else echo -e " ''${YELLOW}No monitoring endpoints configured''${NC}" fi ''} ${optionalString cfg.showBackups '' # Backup jobs status print_section "BACKUP JOBS" BACKUP_JOBS=$(${pkgs.jq}/bin/jq -c '.backups.jobs[]?' "$CONFIG_FILE" 2>/dev/null || echo "") if [[ -n "$BACKUP_JOBS" ]]; then while IFS= read -r job; do name=$(echo "$job" | ${pkgs.jq}/bin/jq -r '.name') backend=$(echo "$job" | ${pkgs.jq}/bin/jq -r '.backend') schedule=$(echo "$job" | ${pkgs.jq}/bin/jq -r '.schedule') service_name="backup-''${name}" timer_name="''${service_name}.timer" timer_status=$(get_timer_status "$timer_name") # Get last backup info last_run="Unknown" if ${pkgs.systemd}/bin/systemctl show "$service_name" --property=ExecMainStartTimestamp --value 2>/dev/null | grep -q "^[^n]"; then last_run=$(${pkgs.systemd}/bin/systemctl show "$service_name" --property=ExecMainStartTimestamp --value 2>/dev/null | head -1) if [[ "$last_run" != "n/a" && -n "$last_run" ]]; then last_run=$(${pkgs.coreutils}/bin/date -d "$last_run" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "Unknown") fi fi printf " %-20s %s (%s, %s) Last: %s\n" "$name" "$timer_status" "$backend" "$schedule" "$last_run" done <<< "$BACKUP_JOBS" # Show backup-status command output if available if command -v backup-status >/dev/null 2>&1; then echo -e "\n ''${BOLD}Quick Status:''${NC}" backup-status 2>/dev/null | tail -n +3 | head -10 | sed 's/^/ /' fi else echo -e " ''${YELLOW}No backup jobs configured''${NC}" fi ''} ${optionalString cfg.showReverseProxy '' # Reverse proxy entries print_section "REVERSE PROXY ENTRIES" PROXY_ENTRIES=$(${pkgs.jq}/bin/jq -c '.reverseProxy.entries[]?' "$CONFIG_FILE" 2>/dev/null || echo "") if [[ -n "$PROXY_ENTRIES" ]]; then while IFS= read -r entry; do subdomain=$(echo "$entry" | ${pkgs.jq}/bin/jq -r '.subdomain') port=$(echo "$entry" | ${pkgs.jq}/bin/jq -r '.port') domain=$(echo "$entry" | ${pkgs.jq}/bin/jq -r '.domain') auth=$(echo "$entry" | ${pkgs.jq}/bin/jq -r '.enableAuth') ssl=$(echo "$entry" | ${pkgs.jq}/bin/jq -r '.enableSSL') # Check if service is running on the port if ${pkgs.netcat}/bin/nc -z localhost "$port" 2>/dev/null; then status="''${GREEN}●''${NC}" else status="''${RED}●''${NC}" fi auth_indicator="" [[ "$auth" == "true" ]] && auth_indicator=" 🔐" ssl_indicator="" [[ "$ssl" == "true" ]] && ssl_indicator=" 🔒" printf " %-25s %s :%s → %s%s%s\n" "''${domain}" "$status" "$port" "$domain" "$auth_indicator" "$ssl_indicator" done <<< "$PROXY_ENTRIES" else echo -e " ''${YELLOW}No reverse proxy entries configured''${NC}" fi ''} ${optionalString cfg.showResources '' # Resource usage print_section "RESOURCE USAGE" echo -e " ''${BOLD}Memory:''${NC} $(${pkgs.procps}/bin/free -h | awk '/^Mem:/ {printf "%s/%s (%.1f%%)", $3, $2, ($3/$2)*100}')" echo -e " ''${BOLD}Disk (root):''${NC} $(${pkgs.coreutils}/bin/df -h / | awk 'NR==2 {printf "%s/%s (%s)", $3, $2, $5}')" echo -e " ''${BOLD}CPU Usage:''${NC} $(${pkgs.procps}/bin/top -bn1 | grep "Cpu(s)" | awk '{printf "%.1f%%", $2+$4}' | sed 's/%us,//')%" ''} ${optionalString cfg.showRecentIssues '' # Recent logs (errors only) print_section "RECENT ISSUES" error_count=$(${pkgs.systemd}/bin/journalctl --since "24 hours ago" --priority=err --no-pager -q | wc -l) if [[ "$error_count" -gt 0 ]]; then echo -e " ''${RED}⚠ $error_count errors in last 24h''${NC}" ${pkgs.systemd}/bin/journalctl --since "24 hours ago" --priority=err --no-pager -q | tail -3 | sed 's/^/ /' else echo -e " ''${GREEN}✓ No critical errors in last 24h''${NC}" fi ''} echo -e "\n''${BOLD}''${BLUE}╔══════════════════════════════════════════════════════════════╗''${NC}" echo -e "''${BOLD}''${BLUE}║''${NC} ''${WHITE}Run 'backup-status' for detailed backup info ''${NC}''${BOLD}''${BLUE}║''${NC}" echo -e "''${BOLD}''${BLUE}║''${NC} ''${WHITE}Config: /etc/homelab/config.json ''${NC}''${BOLD}''${BLUE}║''${NC}" echo -e "''${BOLD}''${BLUE}╚══════════════════════════════════════════════════════════════╝''${NC}" echo ''; in { options.homelab.motd = { enable = mkEnableOption "Dynamic homelab MOTD"; clearScreen = mkOption { type = types.bool; default = true; description = "Clear screen before showing MOTD"; }; showServices = mkOption { type = types.bool; default = true; description = "Show enabled homelab services"; }; showMonitoring = mkOption { type = types.bool; default = true; description = "Show monitoring endpoints"; }; showBackups = mkOption { type = types.bool; default = true; description = "Show backup jobs status"; }; showReverseProxy = mkOption { type = types.bool; default = true; description = "Show reverse proxy entries"; }; showResources = mkOption { type = types.bool; default = true; description = "Show system resource usage"; }; showRecentIssues = mkOption { type = types.bool; default = true; description = "Show recent system issues"; }; 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 = "Homelab services to monitor in MOTD"; }; }; config = mkIf (cfg.enable && globalCfg.enable) { # Register services with MOTD homelab.motd.services = mapAttrs (name: service: { systemdService = name; description = service.description; }) enabledServices; # Create a command to manually run the MOTD environment.systemPackages = with pkgs; [ jq netcat homelab-motd ]; }; }