# modules/global-config.nix { config, lib, outputs, ... }: with lib; let cfg = config.homelab.global; # Service type definition serviceType = types.submodule { options = { enable = mkOption { type = types.bool; default = false; description = "Enable this service"; }; description = mkOption { type = types.str; description = "Human-readable description of the service"; }; category = mkOption { type = types.enum ["monitoring" "networking" "storage" "security" "media" "development" "backup" "other"]; default = "other"; description = "Service category for organization"; }; dependencies = mkOption { type = types.listOf types.str; default = []; description = "List of other homelab services this depends on"; }; ports = mkOption { type = types.listOf types.port; default = []; description = "Ports this service uses"; }; tags = mkOption { type = types.listOf types.str; default = []; description = "Additional tags for this service"; }; priority = mkOption { type = types.int; default = 100; description = "Service priority (lower numbers start first)"; }; }; }; # Type definitions monitoringEndpointType = types.submodule { options = { name = mkOption { type = types.str; description = "Name of the monitoring endpoint"; }; port = mkOption { type = types.port; description = "Port number for the endpoint"; }; path = mkOption { type = types.str; default = "/metrics"; description = "Path for the metrics endpoint"; }; jobName = mkOption { type = types.str; description = "Prometheus job name"; }; scrapeInterval = mkOption { type = types.str; default = "30s"; description = "Prometheus scrape interval"; }; labels = mkOption { type = types.attrsOf types.str; default = {}; description = "Additional labels for this endpoint"; }; }; }; backupJobType = types.submodule { options = { name = mkOption { type = types.str; description = "Name of the backup job"; }; backend = mkOption { type = types.enum ["restic" "borg" "rclone"]; description = "Backup backend to use"; }; paths = mkOption { type = types.listOf types.str; description = "List of paths to backup"; }; schedule = mkOption { type = types.str; default = "daily"; description = "Backup schedule (cron format or preset)"; }; retention = mkOption { type = types.attrsOf types.str; default = { daily = "7"; weekly = "4"; monthly = "6"; yearly = "2"; }; description = "Retention policy"; }; excludePatterns = mkOption { type = types.listOf types.str; default = []; description = "Patterns to exclude from backup"; }; preHook = mkOption { type = types.nullOr types.str; default = null; description = "Script to run before backup"; }; postHook = mkOption { type = types.nullOr types.str; default = null; description = "Script to run after backup"; }; }; }; reverseProxyEntryType = types.submodule { options = { subdomain = mkOption { type = types.str; description = "Subdomain for the service"; }; port = mkOption { type = types.port; description = "Internal port to proxy to"; }; path = mkOption { type = types.str; default = "/"; description = "Path prefix for the service"; }; enableAuth = mkOption { type = types.bool; default = false; description = "Enable authentication for this service"; }; enableSSL = mkOption { type = types.bool; default = true; description = "Enable SSL for this service"; }; customHeaders = mkOption { type = types.attrsOf types.str; default = {}; description = "Custom headers to add"; }; websockets = mkOption { type = types.bool; default = false; description = "Enable websocket support"; }; }; }; # Helper functions for services enabledServices = filterAttrs (name: service: service.enable) cfg.services; servicesByCategory = category: filterAttrs (name: service: service.enable && service.category == category) cfg.services; in { imports = [ ./motd ]; options.homelab.global = { enable = mkEnableOption "Global homelab configuration"; hostname = mkOption { type = types.str; description = "Hostname for this system"; }; domain = mkOption { type = types.str; default = "procopius.dk"; description = "Base domain for the homelab"; }; environment = mkOption { type = types.enum ["production" "staging" "development"]; default = "production"; description = "Environment type"; }; location = mkOption { type = types.str; default = "homelab"; description = "Physical location identifier"; }; tags = mkOption { type = types.listOf types.str; default = []; description = "Tags for this system"; }; services = mkOption { type = types.attrsOf serviceType; default = {}; description = "Homelab services configuration"; example = literalExpression '' { prometheus = { enable = true; description = "Metrics collection and monitoring"; category = "monitoring"; ports = [ 9090 ]; tags = [ "metrics" "alerting" ]; }; traefik = { enable = true; description = "Reverse proxy and load balancer"; category = "networking"; ports = [ 80 443 8080 ]; tags = [ "proxy" "loadbalancer" ]; priority = 10; }; } ''; }; monitoring = { endpoints = mkOption { type = types.listOf monitoringEndpointType; default = []; description = "Monitoring endpoints exposed by this system"; }; nodeExporter = { enable = mkOption { type = types.bool; default = true; description = "Enable node exporter"; }; port = mkOption { type = types.port; default = 9100; description = "Node exporter port"; }; }; }; backups = { jobs = mkOption { type = types.listOf backupJobType; default = []; description = "Backup jobs for this system"; }; globalExcludes = mkOption { type = types.listOf types.str; default = [ "*.tmp" "*.cache" "*/.git" "*/node_modules" "*/target" ]; description = "Global exclude patterns for all backup jobs"; }; }; reverseProxy = { entries = mkOption { type = types.listOf reverseProxyEntryType; default = []; description = "Reverse proxy entries for this system"; }; }; # Helper function to add monitoring endpoint addMonitoringEndpoint = mkOption { type = types.functionTo (types.functionTo types.anything); default = name: endpoint: { homelab.global.monitoring.endpoints = [ (endpoint // {inherit name;}) ]; }; description = "Helper function to add monitoring endpoints"; }; # Helper function to add backup job addBackupJob = mkOption { type = types.functionTo (types.functionTo types.anything); default = name: job: { homelab.global.backups.jobs = [ (job // {inherit name;}) ]; }; description = "Helper function to add backup jobs"; }; # Helper function to add reverse proxy entry addReverseProxyEntry = mkOption { type = types.functionTo (types.functionTo types.anything); default = subdomain: entry: { homelab.global.reverseProxy.entries = [ (entry // {inherit subdomain;}) ]; }; description = "Helper function to add reverse proxy entries"; }; # Helper functions enabledServicesList = mkOption { type = types.listOf types.str; default = attrNames enabledServices; description = "List of enabled service names"; readOnly = true; }; servicesByPriority = mkOption { type = types.listOf types.str; default = map (x: x.name) (sort (a: b: a.priority < b.priority) (mapAttrsToList (name: service: service // {inherit name;}) enabledServices)); description = "Services sorted by priority"; readOnly = true; }; }; config = mkIf cfg.enable { # Set hostname networking.hostName = cfg.hostname; # Configure node exporter if enabled services.prometheus.exporters.node = mkIf cfg.monitoring.nodeExporter.enable { enable = true; port = cfg.monitoring.nodeExporter.port; enabledCollectors = [ "systemd" "textfile" "filesystem" "loadavg" "meminfo" "netdev" "stat" ]; }; # Automatically add node exporter to monitoring endpoints homelab.global.monitoring.endpoints = mkIf cfg.monitoring.nodeExporter.enable [ { name = "node-exporter"; port = cfg.monitoring.nodeExporter.port; path = "/metrics"; jobName = "node"; labels = { instance = cfg.hostname; environment = cfg.environment; location = cfg.location; }; } ]; # Export configuration for external consumption environment.etc."homelab/config.json".text = builtins.toJSON { inherit (cfg) hostname domain environment location tags; services = mapAttrs (name: service: { inherit (service) enable description category dependencies ports tags priority; }) cfg.services; enabledServices = enabledServices; servicesByCategory = { monitoring = servicesByCategory "monitoring"; networking = servicesByCategory "networking"; storage = servicesByCategory "storage"; security = servicesByCategory "security"; media = servicesByCategory "media"; development = servicesByCategory "development"; backup = servicesByCategory "backup"; other = servicesByCategory "other"; }; monitoring = { endpoints = map (endpoint: { name = endpoint.name; url = "http://${cfg.hostname}:${toString endpoint.port}${endpoint.path}"; port = endpoint.port; path = endpoint.path; jobName = endpoint.jobName; scrapeInterval = endpoint.scrapeInterval; labels = endpoint.labels // { hostname = cfg.hostname; environment = cfg.environment; }; }) cfg.monitoring.endpoints; }; backups = { jobs = cfg.backups.jobs; }; reverseProxy = { entries = map (entry: { subdomain = entry.subdomain; url = "http://${cfg.hostname}:${toString entry.port}"; port = entry.port; path = entry.path; domain = "${entry.subdomain}.${cfg.domain}"; enableAuth = entry.enableAuth; enableSSL = entry.enableSSL; customHeaders = entry.customHeaders; websockets = entry.websockets; }) cfg.reverseProxy.entries; }; }; # Create a status command that shows service information environment.systemPackages = [ # (pkgs.writeScriptBin "homelab-services" '' # #!/bin/bash # echo "🏠 Homelab Services Status" # echo "==========================" # echo # ${concatStringsSep "\n" (mapAttrsToList (name: service: '' # echo "${name}: ${service.description}" # echo " Category: ${service.category}" # echo " Status: $(systemctl is-active ${name} 2>/dev/null || echo "not found")" # ${optionalString (service.ports != []) '' # echo " Ports: ${concatStringsSep ", " (map toString service.ports)}" # ''} # ${optionalString (service.tags != []) '' # echo " Tags: ${concatStringsSep ", " service.tags}" # ''} # echo # '') # enabledServices)} # '') ]; }; }