From 85526567310957f6112d6c4ca862377861384903 Mon Sep 17 00:00:00 2001 From: plasmagoat Date: Wed, 30 Jul 2025 00:22:33 +0200 Subject: [PATCH 1/2] services... --- docs/README.md | 10 +- docs/current-deployment.md | 6 +- docs/fleet-overview.md | 2 +- docs/nodes.md | 6 +- docs/services.md | 452 ++++++++++++------ hosts/sandbox/default.nix | 6 +- modules/homelab/lib/features/logging.nix | 69 +-- modules/homelab/lib/features/monitoring.nix | 124 +++-- modules/homelab/lib/features/proxy.nix | 13 +- modules/homelab/services/gatus.nix | 13 +- modules/homelab/services/grafana.nix | 14 +- modules/homelab/services/prometheus.nix | 47 +- pkgs/homelab-docs/readme.nix | 8 +- pkgs/homelab-docs/service-evaluator.nix | 138 ++++++ pkgs/homelab-docs/services.nix | 500 ++++++++++++-------- 15 files changed, 918 insertions(+), 490 deletions(-) create mode 100644 pkgs/homelab-docs/service-evaluator.nix diff --git a/docs/README.md b/docs/README.md index 17e36a8..9984a6d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,7 +20,7 @@ This documentation is automatically generated from your colmena flake configurat ## 🚀 Quick Actions ### View Current Status -\`\`\`bash +```bash # Service status across fleet (if homelab CLI is available) homelab services --global @@ -29,22 +29,22 @@ homelab backups --global # Overall status homelab status -\`\`\` +``` ### Update Documentation -\`\`\`bash +```bash # Regenerate all documentation homelab-generate-docs ./docs # Generate in different directory homelab-generate-docs /path/to/output -\`\`\` +``` ## 📋 Quick Stats - **Total Nodes**: 2 - **Homelab-Enabled Nodes**: 2 -- **Generated**: tir 29 jul 16:57:16 CEST 2025 +- **Generated**: ons 30 jul 00:20:46 CEST 2025 ## 🛠️ Management Tools diff --git a/docs/current-deployment.md b/docs/current-deployment.md index 2f1541c..c43eccf 100644 --- a/docs/current-deployment.md +++ b/docs/current-deployment.md @@ -11,15 +11,15 @@ |--------|-------| | Total Nodes | 2 | | Homelab-Enabled Nodes | 2 | -| Unique Services | 1 | -| Service Instances | 1 | +| Unique Services | 4 | +| Service Instances | 4 | ## Node Status | Node | Homelab | Environment | Services | Monitoring | Backups | Proxy | |------|---------|-------------|----------|------------|---------|-------| | `photos` | ✅ | production | 1 | ✅ | ❌ | ❌ | -| `sandbox` | ✅ | production | 0 | ✅ | ✅ | ❌ | +| `sandbox` | ✅ | production | 3 | ✅ | ✅ | ❌ | --- diff --git a/docs/fleet-overview.md b/docs/fleet-overview.md index 5982210..866c8bd 100644 --- a/docs/fleet-overview.md +++ b/docs/fleet-overview.md @@ -26,7 +26,7 @@ | Node | Service Count | Services | |------|---------------|----------| | `photos` | 1 | minio | -| `sandbox` | 0 | | +| `sandbox` | 3 | gatus, grafana, prometheus | --- diff --git a/docs/nodes.md b/docs/nodes.md index 87cade4..90a635a 100644 --- a/docs/nodes.md +++ b/docs/nodes.md @@ -61,10 +61,10 @@ | Service | Enabled | Port | Description | Tags | |---------|---------|------|-------------|------| -| `gatus` | ❌ | 8080 | Gatus Status Page | | -| `grafana` | ❌ | 3000 | Grafana Metrics Dashboard | | +| `gatus` | ✅ | 8080 | Gatus Status Page | | +| `grafana` | ✅ | 3000 | Grafana Metrics Dashboard | | | `minio` | ❌ | 9000 | minio | | -| `prometheus` | ❌ | 9090 | Prometheus Monitoring Server | | +| `prometheus` | ✅ | 9090 | Prometheus Monitoring Server | | --- diff --git a/docs/services.md b/docs/services.md index a953b35..7e3571b 100644 --- a/docs/services.md +++ b/docs/services.md @@ -1,10 +1,13 @@ # Service Catalog -> Available services and their configuration options +> Complete service documentation with core options, feature integrations, and smart defaults > > Generated on: $(date) -This document catalogs all available homelab services, their configuration options, and integration capabilities. +This document provides comprehensive documentation for homelab services, organized by: +- **Core Service Options**: The main service configuration +- **Feature Integrations**: Available monitoring, logging, and proxy features +- **Service Defaults**: What this service configures by default for each feature ## Overview @@ -12,142 +15,252 @@ This document catalogs all available homelab services, their configuration optio ## Service Integration Matrix -| Service | Monitoring | Logging | Proxy | Auth Default | -|---------|------------|---------|-------|--------------| -| `gatus` | ❌ | ❌ | ❌ | 🌐 | -| `grafana` | ❌ | ❌ | ❌ | 🌐 | -| `minio` | ❌ | ❌ | ❌ | 🌐 | -| `prometheus` | ❌ | ❌ | ❌ | 🌐 | +| Service | Core Options | Monitoring | Logging | Proxy | Deployments | +|---------|--------------|------------|---------|-------|-------------| +| `gatus` | 11 | 📊 | 📝 | 🔀 | 1 | +| `grafana` | 3 | 📊 | 📝 | 🔀 | 1 | +| `minio` | 4 | ❌ | ❌ | ❌ | 1 | +| `prometheus` | 12 | 📊 | 📝 | 🔀 | 1 | -**Legend:** ✅ = Enabled by default, ❌ = Available but disabled, 🔒 = Auth required, 🌐 = Public access +**Legend:** 📊📝🔀 = Feature available, ❌ = Feature not available -## Service Reference +## Service Documentation ### gatus -**Description:** Gatus Status Page +**Deployment Status:** 1/2 nodes have this service enabled -**Default Port:** `8080` +#### Core Service Options -**Current Deployments:** 0 instance(s) on: - -#### Default Integration Status - -| Integration | Status | Default Configuration | -|-------------|--------|----------------------| -| 📊 Monitoring | ❌ Disabled | Available but requires `monitoring.enable = true` | -| 📝 Logging | ❌ Disabled | Available but requires `logging.enable = true` | -| 🔀 Proxy | ❌ Disabled | Available but requires `proxy.enable = true` | - -#### Core Configuration +The main configuration options for gatus: ```nix homelab.services.gatus = { - enable = true; - port = 8080; - description = "Gatus Status Page"; - - # Default integrations (adjust as needed) - # monitoring.enable = true; # ❌ Disabled by default - # logging.enable = true; # ❌ Disabled by default - # proxy.enable = true; # ❌ Disabled by default + alerting = {}; # Gatus alerting configuration + description = Gatus Status Page; # No description + enable = false; # Whether to enable Gatus Status Page. + extraConfig = {}; # Additional Gatus configuration options + port = 8080; # No description + storage = { + "type": "memory" +}; # Gatus storage configuration + ui.buttons = [ + { + "link": "https://grafana.procopius.dk", + "name": "Grafana" + }, + { + "link": "https://prometheus.procopius.dk", + "name": "Prometheus" + } +]; # Navigation buttons in the Gatus interface + ui.header = Homelab Services Status; # Header text for the Gatus interface + ui.link = https://status.procopius.dk; # Link in the Gatus header + ui.title = Homelab Status; # Title for the Gatus web interface + web.address = 0.0.0.0; # Web interface bind address }; ``` -#### Service-Specific Options +#### Feature Integrations -Available configuration options for gatus: +##### 📊 Monitoring Integration + +Available monitoring options: ```nix homelab.services.gatus = { # ... core options above ... - # Service-specific configuration - alerting = {}; - extraConfig = {}; - storage = {"type":"memory"}; - ui = {"buttons":[{"link":"https://grafana.procopius.dk","name":"Grafana"},{"link":"https://prometheus.procopius.dk","name":"Prometheus"}],"header":"Homelab Services Status","link":"https://status.procopius.dk","title":"Homelab Status"}; - web = {"address":"0.0.0.0"}; + monitoring.enable = true; # Enable monitoring for gatus + monitoring.extraLabels = {}; # No description + monitoring.healthCheck.conditions = [ + "[STATUS] == 200" +]; # Health check conditions. Setting conditions enables health checks. + monitoring.healthCheck.enable = true; # No description + monitoring.healthCheck.extraChecks = []; # Additional health checks. Adding checks enables health monitoring. + # monitoring.healthCheck.path = ; # Health check endpoint path. Setting this enables health checks. + monitoring.metrics.enable = false; # No description + monitoring.metrics.extraEndpoints = []; # Additional metrics endpoints. Adding endpoints enables metrics collection. + # monitoring.metrics.path = ; # Metrics endpoint path. Setting this enables metrics collection. }; ``` +**gatus sets these monitoring defaults:** +```nix + enable = true; + extraLabels = {}; + healthCheck = {"conditions":["[STATUS] == 200"],"enable":true,"extraChecks":[],"path":null}; + metrics = {"enable":false,"extraEndpoints":[],"path":null}; +``` + +##### 📝 Logging Integration + +Available logging options: + +```nix +homelab.services.gatus = { + # ... core options above ... + + logging.enable = false; # Enable logging for gatus + logging.extraLabels = {}; # No description + logging.extraSources = []; # No description + logging.files = []; # No description + # logging.multiline = ; # No description + logging.parsing.extractFields = []; # No description + # logging.parsing.regex = ; # No description +}; +``` + +**gatus sets these logging defaults:** +```nix + enable = false; + extraLabels = {}; + extraSources = []; + files = []; + multiline = null; + parsing = {"extractFields":[],"regex":null}; +``` + +##### 🔀 Proxy Integration + +Available proxy options: + +```nix +homelab.services.gatus = { + # ... core options above ... + + proxy.additionalSubdomains = []; # No description + proxy.enable = true; # Enable reverse proxy for gatus + proxy.enableAuth = false; # No description + proxy.subdomain = gatus; # No description +}; +``` + +**gatus sets these proxy defaults:** +```nix + additionalSubdomains = []; + enable = true; + enableAuth = false; + subdomain = gatus; +``` + --- ### grafana -**Description:** Grafana Metrics Dashboard +**Deployment Status:** 1/2 nodes have this service enabled -**Default Port:** `3000` +#### Core Service Options -**Current Deployments:** 0 instance(s) on: - -#### Default Integration Status - -| Integration | Status | Default Configuration | -|-------------|--------|----------------------| -| 📊 Monitoring | ❌ Disabled | Available but requires `monitoring.enable = true` | -| 📝 Logging | ❌ Disabled | Available but requires `logging.enable = true` | -| 🔀 Proxy | ❌ Disabled | Available but requires `proxy.enable = true` | - -#### Core Configuration +The main configuration options for grafana: ```nix homelab.services.grafana = { - enable = true; - port = 3000; - description = "Grafana Metrics Dashboard"; - - # Default integrations (adjust as needed) - # monitoring.enable = true; # ❌ Disabled by default - # logging.enable = true; # ❌ Disabled by default - # proxy.enable = true; # ❌ Disabled by default + description = Grafana Metrics Dashboard; # No description + enable = false; # Whether to enable Grafana Dashboard. + port = 3000; # No description }; ``` +#### Feature Integrations + +##### 📊 Monitoring Integration + +Available monitoring options: + +```nix +homelab.services.grafana = { + # ... core options above ... + + monitoring.enable = true; # Enable monitoring for grafana + monitoring.extraLabels = {}; # No description + monitoring.healthCheck.conditions = [ + "[STATUS] == 200" +]; # Health check conditions. Setting conditions enables health checks. + monitoring.healthCheck.enable = true; # No description + monitoring.healthCheck.extraChecks = []; # Additional health checks. Adding checks enables health monitoring. + # monitoring.healthCheck.path = ; # Health check endpoint path. Setting this enables health checks. + monitoring.metrics.enable = false; # No description + monitoring.metrics.extraEndpoints = []; # Additional metrics endpoints. Adding endpoints enables metrics collection. + # monitoring.metrics.path = ; # Metrics endpoint path. Setting this enables metrics collection. +}; +``` + +**grafana sets these monitoring defaults:** +```nix + enable = true; + extraLabels = {}; + healthCheck = {"conditions":["[STATUS] == 200"],"enable":true,"extraChecks":[],"path":null}; + metrics = {"enable":false,"extraEndpoints":[],"path":null}; +``` + +##### 📝 Logging Integration + +Available logging options: + +```nix +homelab.services.grafana = { + # ... core options above ... + + logging.enable = false; # Enable logging for grafana + logging.extraLabels = {}; # No description + logging.extraSources = []; # No description + logging.files = []; # No description + # logging.multiline = ; # No description + logging.parsing.extractFields = []; # No description + # logging.parsing.regex = ; # No description +}; +``` + +**grafana sets these logging defaults:** +```nix + enable = false; + extraLabels = {}; + extraSources = []; + files = []; + multiline = null; + parsing = {"extractFields":[],"regex":null}; +``` + +##### 🔀 Proxy Integration + +Available proxy options: + +```nix +homelab.services.grafana = { + # ... core options above ... + + proxy.additionalSubdomains = []; # No description + proxy.enable = true; # Enable reverse proxy for grafana + proxy.enableAuth = false; # No description + proxy.subdomain = grafana; # No description +}; +``` + +**grafana sets these proxy defaults:** +```nix + additionalSubdomains = []; + enable = true; + enableAuth = false; + subdomain = grafana; +``` + --- ### minio -**Description:** minio +**Deployment Status:** 1/2 nodes have this service enabled -**Default Port:** `9000` +#### Core Service Options -**Current Deployments:** 1 instance(s) on: photos - -#### Default Integration Status - -| Integration | Status | Default Configuration | -|-------------|--------|----------------------| -| 📊 Monitoring | ❌ Disabled | Available but requires `monitoring.enable = true` | -| 📝 Logging | ❌ Disabled | Available but requires `logging.enable = true` | -| 🔀 Proxy | ❌ Disabled | Available but requires `proxy.enable = true` | - -#### Core Configuration +The main configuration options for minio: ```nix homelab.services.minio = { - enable = true; - port = 9000; - description = "minio"; - - # Default integrations (adjust as needed) - # monitoring.enable = true; # ❌ Disabled by default - # logging.enable = true; # ❌ Disabled by default - # proxy.enable = true; # ❌ Disabled by default -}; -``` - -#### Service-Specific Options - -Available configuration options for minio: - -```nix -homelab.services.minio = { - # ... core options above ... - - # Service-specific configuration - openFirewall = true; - webPort = 9001; + enable = false; # Whether to enable Minio Object Storage. + openFirewall = true; # Whether to open the ports specified in `port` and `webPort` in the firewall. + port = 9000; # Port of the server. + webPort = 9001; # Port of the web UI (console). }; ``` @@ -155,75 +268,132 @@ homelab.services.minio = { ### prometheus -**Description:** Prometheus Monitoring Server +**Deployment Status:** 1/2 nodes have this service enabled -**Default Port:** `9090` +#### Core Service Options -**Current Deployments:** 0 instance(s) on: - -#### Default Integration Status - -| Integration | Status | Default Configuration | -|-------------|--------|----------------------| -| 📊 Monitoring | ❌ Disabled | Available but requires `monitoring.enable = true` | -| 📝 Logging | ❌ Disabled | Available but requires `logging.enable = true` | -| 🔀 Proxy | ❌ Disabled | Available but requires `proxy.enable = true` | - -#### Core Configuration +The main configuration options for prometheus: ```nix homelab.services.prometheus = { - enable = true; - port = 9090; - description = "Prometheus Monitoring Server"; - - # Default integrations (adjust as needed) - # monitoring.enable = true; # ❌ Disabled by default - # logging.enable = true; # ❌ Disabled by default - # proxy.enable = true; # ❌ Disabled by default + alertmanager.enable = true; # Enable integration with Alertmanager + alertmanager.url = alertmanager.lab:9093; # Alertmanager URL + description = Prometheus Monitoring Server; # No description + enable = false; # Whether to enable Prometheus Monitoring Server. + extraAlertingRules = []; # Additional alerting rules + extraFlags = []; # Extra command line flags + extraScrapeConfigs = []; # Additional scrape configurations + globalConfig = { + "evaluation_interval": "15s", + "scrape_interval": "15s" +}; # Global Prometheus configuration + port = 9090; # No description + retention = 15d; # How long to retain metrics data + ruleFiles = []; # Additional rule files to load + systemdServices = [ + "prometheus.service", + "prometheus" +]; # Systemd services to monitor }; ``` -#### Service-Specific Options +#### Feature Integrations -Available configuration options for prometheus: +##### 📊 Monitoring Integration + +Available monitoring options: ```nix homelab.services.prometheus = { # ... core options above ... - # Service-specific configuration - alertmanager = {"enable":true,"url":"alertmanager.lab:9093"}; - extraAlertingRules = []; - extraFlags = []; - extraScrapeConfigs = []; - globalConfig = {"evaluation_interval":"15s","scrape_interval":"15s"}; - retention = 15d; - ruleFiles = []; - systemdServices = ["prometheus.service","prometheus"]; + monitoring.enable = true; # Enable monitoring for prometheus + monitoring.extraLabels = {}; # No description + monitoring.healthCheck.conditions = [ + "[STATUS] == 200" +]; # Health check conditions. Setting conditions enables health checks. + monitoring.healthCheck.enable = true; # No description + monitoring.healthCheck.extraChecks = []; # Additional health checks. Adding checks enables health monitoring. + # monitoring.healthCheck.path = ; # Health check endpoint path. Setting this enables health checks. + monitoring.metrics.enable = false; # No description + monitoring.metrics.extraEndpoints = []; # Additional metrics endpoints. Adding endpoints enables metrics collection. + # monitoring.metrics.path = ; # Metrics endpoint path. Setting this enables metrics collection. }; ``` +**prometheus sets these monitoring defaults:** +```nix + enable = true; + extraLabels = {}; + healthCheck = {"conditions":["[STATUS] == 200"],"enable":true,"extraChecks":[],"path":null}; + metrics = {"enable":false,"extraEndpoints":[],"path":null}; +``` + +##### 📝 Logging Integration + +Available logging options: + +```nix +homelab.services.prometheus = { + # ... core options above ... + + logging.enable = false; # Enable logging for prometheus + logging.extraLabels = {}; # No description + logging.extraSources = []; # No description + logging.files = []; # No description + # logging.multiline = ; # No description + logging.parsing.extractFields = []; # No description + # logging.parsing.regex = ; # No description +}; +``` + +**prometheus sets these logging defaults:** +```nix + enable = false; + extraLabels = {}; + extraSources = []; + files = []; + multiline = null; + parsing = {"extractFields":[],"regex":null}; +``` + +##### 🔀 Proxy Integration + +Available proxy options: + +```nix +homelab.services.prometheus = { + # ... core options above ... + + proxy.additionalSubdomains = []; # No description + proxy.enable = true; # Enable reverse proxy for prometheus + proxy.enableAuth = false; # No description + proxy.subdomain = prometheus; # No description +}; +``` + +**prometheus sets these proxy defaults:** +```nix + additionalSubdomains = []; + enable = true; + enableAuth = false; + subdomain = prometheus; +``` + --- -## Integration Summary +## Feature Reference -### Available Integration Types +### Integration Features -| Integration | Purpose | Default Behavior | Configuration | -|-------------|---------|------------------|---------------| -| **📊 Monitoring** | Prometheus metrics + health checks | Service-dependent | `monitoring.enable = true` | -| **📝 Logging** | Centralized log collection | Service-dependent | `logging.enable = true` | -| **🔀 Proxy** | Reverse proxy with SSL + auth | Service-dependent | `proxy.enable = true` | +Homelab services can integrate with three main features: -### Integration Benefits +- **📊 Monitoring**: Prometheus metrics and health checks +- **📝 Logging**: Centralized log collection with Promtail/Loki +- **🔀 Proxy**: Reverse proxy with SSL and authentication -- **🔄 Automatic Discovery:** Enabled integrations are automatically discovered by fleet-wide services -- **📊 Unified Monitoring:** All metrics and health checks appear in Prometheus/Grafana -- **📝 Centralized Logging:** All logs are collected and indexed in Loki -- **🌐 Consistent Access:** All services get consistent subdomain access with SSL -- **🎯 Smart Defaults:** Each service comes with sensible default configurations +Each service can import these features and set service-specific defaults. --- -*This service catalog is generated from actual service configurations across your homelab fleet.* +*This documentation is generated from actual NixOS module evaluations.* diff --git a/hosts/sandbox/default.nix b/hosts/sandbox/default.nix index 70eb387..ebf4475 100644 --- a/hosts/sandbox/default.nix +++ b/hosts/sandbox/default.nix @@ -42,9 +42,9 @@ }; # services.loki.enable = true; - # services.prometheus.enable = true; - # services.grafana.enable = true; - # services.gatus.enable = true; + services.prometheus.enable = true; + services.grafana.enable = true; + services.gatus.enable = true; }; system.stateVersion = "25.05"; diff --git a/modules/homelab/lib/features/logging.nix b/modules/homelab/lib/features/logging.nix index 010b766..60f2cda 100644 --- a/modules/homelab/lib/features/logging.nix +++ b/modules/homelab/lib/features/logging.nix @@ -6,9 +6,18 @@ serviceName: { with lib; let cfg = config.homelab.services.${serviceName}; homelabCfg = config.homelab; + + shouldEnableLogging = + cfg.logging.files + != [] + || cfg.logging.extraSources != []; in { options.homelab.services.${serviceName}.logging = { - enable = mkEnableOption "logging for ${serviceName}"; + enable = mkOption { + type = types.bool; + description = "Enable logging for ${serviceName}"; + default = shouldEnableLogging; + }; files = mkOption { type = types.listOf types.str; @@ -51,37 +60,33 @@ in { }; }; - config = mkIf (cfg.enable && cfg.logging.enable) { - homelab.logging.sources = - [ - { - name = "${serviceName}-logs"; - type = "file"; - files = { - paths = cfg.logging.files; - multiline = cfg.logging.multiline; + config = mkIf cfg.enable { + homelab.logging.sources = mkIf cfg.logging.enable ( + # Only create file source if files are specified + (optional (cfg.logging.files != []) { + name = "${serviceName}-logs"; + type = "file"; + files = { + paths = cfg.logging.files; + multiline = cfg.logging.multiline; + }; + labels = + cfg.logging.extraLabels + // { + service = serviceName; + node = homelabCfg.hostname; + environment = homelabCfg.environment; }; - labels = - cfg.logging.extraLabels - // { - service = serviceName; - node = homelabCfg.hostname; - environment = homelabCfg.environment; - }; - pipelineStages = - mkIf (cfg.logging.parsing.regex != null) [ - { - regex.expression = cfg.logging.parsing.regex; - } - ] - ++ [ - { - labels = listToAttrs (map (field: nameValuePair field null) cfg.logging.parsing.extractFields); - } - ]; - enabled = true; - } - ] - ++ cfg.logging.extraSources; + pipelineStages = + (optional (cfg.logging.parsing.regex != null) { + regex.expression = cfg.logging.parsing.regex; + }) + ++ (optional (cfg.logging.parsing.extractFields != []) { + labels = listToAttrs (map (field: nameValuePair field null) cfg.logging.parsing.extractFields); + }); + enabled = true; + }) + ++ cfg.logging.extraSources + ); }; } diff --git a/modules/homelab/lib/features/monitoring.nix b/modules/homelab/lib/features/monitoring.nix index 90b36f9..f25e3b8 100644 --- a/modules/homelab/lib/features/monitoring.nix +++ b/modules/homelab/lib/features/monitoring.nix @@ -6,47 +6,69 @@ serviceName: { with lib; let cfg = config.homelab.services.${serviceName}; homelabCfg = config.homelab; + + hasMetricsConfig = + cfg.monitoring.metrics.path + != null + || cfg.monitoring.metrics.extraEndpoints != []; + + hasHealthCheckConfig = + cfg.monitoring.healthCheck.path + != null + || cfg.monitoring.healthCheck.conditions != [] + || cfg.monitoring.healthCheck.extraChecks != []; in { # Define the service-specific monitoring options options.homelab.services.${serviceName}.monitoring = { - enable = mkEnableOption "monitoring for ${serviceName}"; + enable = mkOption { + type = types.bool; + description = "Enable monitoring for ${serviceName}"; + default = hasMetricsConfig || hasHealthCheckConfig; + }; metrics = { enable = mkOption { type = types.bool; - default = true; + default = hasMetricsConfig; }; path = mkOption { - type = types.str; - default = "/metrics"; + type = types.nullOr types.str; + default = null; + description = "Metrics endpoint path. Setting this enables metrics collection."; }; extraEndpoints = mkOption { type = types.listOf types.attrs; default = []; + description = "Additional metrics endpoints. Adding endpoints enables metrics collection."; }; }; healthCheck = { enable = mkOption { type = types.bool; - default = true; + default = hasHealthCheckConfig; }; path = mkOption { - type = types.str; - default = "/health"; + type = types.nullOr types.str; + default = null; + description = "Health check endpoint path. Setting this enables health checks."; + example = "/health"; }; conditions = mkOption { type = types.listOf types.str; default = ["[STATUS] == 200"]; + description = "Health check conditions. Setting conditions enables health checks."; + example = ["[STATUS] == 200"]; }; extraChecks = mkOption { type = types.listOf types.attrs; default = []; + description = "Additional health checks. Adding checks enables health monitoring."; }; }; @@ -57,52 +79,50 @@ in { }; # Generate the homelab config automatically when service is enabled - config = mkIf (cfg.enable && cfg.monitoring.enable) { - homelab.monitoring = { - metrics = - [ - { - name = "${serviceName}-main"; - host = homelabCfg.hostname; - port = cfg.port; - path = cfg.monitoring.metrics.path; - jobName = serviceName; - scrapeInterval = "30s"; - labels = - cfg.monitoring.extraLabels - // { - service = serviceName; - node = homelabCfg.hostname; - environment = homelabCfg.environment; - }; - } - ] - ++ cfg.monitoring.metrics.extraEndpoints; + config = mkIf cfg.enable { + homelab.monitoring = mkIf cfg.monitoring.enable { + metrics = mkIf hasMetricsConfig ( + (optional (cfg.monitoring.metrics.path != null) { + name = "${serviceName}-main"; + host = homelabCfg.hostname; + port = cfg.port; + path = cfg.monitoring.metrics.path; + jobName = serviceName; + scrapeInterval = "30s"; + labels = + cfg.monitoring.extraLabels + // { + service = serviceName; + node = homelabCfg.hostname; + environment = homelabCfg.environment; + }; + }) + ++ cfg.monitoring.metrics.extraEndpoints + ); - healthChecks = - [ - { - name = "${serviceName}-health"; - host = homelabCfg.hostname; - port = cfg.port; - path = cfg.monitoring.healthCheck.path; - protocol = "http"; - method = "GET"; - interval = "30s"; - timeout = "10s"; - conditions = cfg.monitoring.healthCheck.conditions; - group = "services"; - labels = - cfg.monitoring.extraLabels - // { - service = serviceName; - node = homelabCfg.hostname; - environment = homelabCfg.environment; - }; - enabled = true; - } - ] - ++ cfg.monitoring.healthCheck.extraChecks; + healthChecks = mkIf hasHealthCheckConfig ( + (optional (cfg.monitoring.healthCheck.path != null) { + name = "${serviceName}-health"; + host = homelabCfg.hostname; + port = cfg.port; + path = cfg.monitoring.healthCheck.path; + protocol = "http"; + method = "GET"; + interval = "30s"; + timeout = "10s"; + conditions = cfg.monitoring.healthCheck.conditions; + group = "services"; + labels = + cfg.monitoring.extraLabels + // { + service = serviceName; + node = homelabCfg.hostname; + environment = homelabCfg.environment; + }; + enabled = true; + }) + ++ cfg.monitoring.healthCheck.extraChecks + ); }; }; } diff --git a/modules/homelab/lib/features/proxy.nix b/modules/homelab/lib/features/proxy.nix index 2658c7a..595f4c0 100644 --- a/modules/homelab/lib/features/proxy.nix +++ b/modules/homelab/lib/features/proxy.nix @@ -8,7 +8,11 @@ with lib; let homelabCfg = config.homelab; in { options.homelab.services.${serviceName}.proxy = { - enable = mkEnableOption "reverse proxy for ${serviceName}"; + enable = mkOption { + type = types.bool; + description = "Enable reverse proxy for ${serviceName}"; + default = true; + }; subdomain = mkOption { type = types.str; @@ -39,8 +43,8 @@ in { }; }; - config = mkIf (cfg.enable && cfg.proxy.enable) { - homelab.reverseProxy.entries = + config = mkIf cfg.enable { + homelab.reverseProxy.entries = mkIf cfg.proxy.enable ( [ { subdomain = cfg.proxy.subdomain; @@ -59,6 +63,7 @@ in { enableAuth = sub.enableAuth; enableSSL = true; }) - cfg.proxy.additionalSubdomains; + cfg.proxy.additionalSubdomains + ); }; } diff --git a/modules/homelab/services/gatus.nix b/modules/homelab/services/gatus.nix index da907c4..3bdd610 100644 --- a/modules/homelab/services/gatus.nix +++ b/modules/homelab/services/gatus.nix @@ -219,8 +219,7 @@ in { homelab.services.${serviceName}.monitoring.enable = mkDefault true; } - # Smart defaults for Gatus - (mkIf cfg.monitoring.enable { + { homelab.services.${serviceName}.monitoring = mkDefault { metrics = { path = "/metrics"; @@ -240,9 +239,9 @@ in { tier = "monitoring"; }; }; - }) + } - (mkIf cfg.logging.enable { + { homelab.services.${serviceName}.logging = mkDefault { files = ["/var/log/gatus/gatus.log"]; parsing = { @@ -255,13 +254,13 @@ in { application = "gatus"; }; }; - }) + } - (mkIf cfg.proxy.enable { + { homelab.services.${serviceName}.proxy = mkDefault { subdomain = "status"; enableAuth = false; # Status page should be public }; - }) + } ]); } diff --git a/modules/homelab/services/grafana.nix b/modules/homelab/services/grafana.nix index 5f5aad9..2663cc9 100644 --- a/modules/homelab/services/grafana.nix +++ b/modules/homelab/services/grafana.nix @@ -45,7 +45,7 @@ in { } # Smart defaults for Grafana - (mkIf cfg.logging.enable { + { # Grafana-specific log setup homelab.services.${serviceName}.logging = mkDefault { files = ["/var/log/grafana/grafana.log"]; @@ -59,9 +59,8 @@ in { component = "dashboard"; }; }; - }) - - (mkIf cfg.monitoring.enable { + } + { homelab.services.${serviceName}.monitoring = mkDefault { metrics.path = "/metrics"; healthCheck = { @@ -73,14 +72,13 @@ in { tier = "monitoring"; }; }; - }) - - (mkIf cfg.proxy.enable { + } + { # Grafana needs auth by default (admin interface) homelab.services.${serviceName}.proxy = mkDefault { subdomain = "grafana"; # enableAuth = true; }; - }) + } ]); } diff --git a/modules/homelab/services/prometheus.nix b/modules/homelab/services/prometheus.nix index b3f398b..dabc086 100644 --- a/modules/homelab/services/prometheus.nix +++ b/modules/homelab/services/prometheus.nix @@ -168,7 +168,6 @@ in { # Service configuration with smart defaults config = mkIf cfg.enable (mkMerge [ - # Core Prometheus service { services.prometheus = { enable = true; @@ -203,39 +202,21 @@ in { }; networking.firewall.allowedTCPPorts = [cfg.port]; - - homelab.services.${serviceName}.monitoring.enable = mkDefault true; } + { + homelab.services.${serviceName}.monitoring = { + metrics.path = "/metrics"; + healthCheck.path = "/-/healthy"; # ✅ Enables health checks + healthCheck.conditions = ["[STATUS] == 200" "[RESPONSE_TIME] < 1000"]; - # Smart defaults for Prometheus - (mkIf cfg.monitoring.enable { - homelab.services.${serviceName}.monitoring = mkDefault { - metrics = { - path = "/metrics"; - extraEndpoints = []; - }; - healthCheck = { - path = "/-/healthy"; - conditions = ["[STATUS] == 200" "[RESPONSE_TIME] < 1000"]; - extraChecks = [ - { - name = "prometheus-ready"; - port = cfg.port; - path = "/-/ready"; - conditions = ["[STATUS] == 200"]; - group = "monitoring"; - } - ]; - }; extraLabels = { component = "monitoring-server"; tier = "monitoring"; }; }; - }) - - (mkIf cfg.logging.enable { - homelab.services.${serviceName}.logging = mkDefault { + } + { + homelab.services.${serviceName}.logging = { files = ["/var/log/prometheus/prometheus.log"]; parsing = { # Prometheus log format: ts=2024-01-01T12:00:00.000Z caller=main.go:123 level=info msg="message" @@ -247,13 +228,11 @@ in { application = "prometheus"; }; }; - }) - - (mkIf cfg.proxy.enable { - homelab.services.${serviceName}.proxy = mkDefault { - subdomain = "prometheus"; - enableAuth = true; # Admin interface needs protection + } + { + homelab.services.${serviceName}.proxy = { + enableAuth = true; }; - }) + } ]); } diff --git a/pkgs/homelab-docs/readme.nix b/pkgs/homelab-docs/readme.nix index 7a0891f..505d465 100644 --- a/pkgs/homelab-docs/readme.nix +++ b/pkgs/homelab-docs/readme.nix @@ -30,7 +30,7 @@ writeShellScriptBin "homelab-docs-readme" '' ## 🚀 Quick Actions ### View Current Status - \`\`\`bash + ```bash # Service status across fleet (if homelab CLI is available) homelab services --global @@ -39,16 +39,16 @@ writeShellScriptBin "homelab-docs-readme" '' # Overall status homelab status - \`\`\` + ``` ### Update Documentation - \`\`\`bash + ```bash # Regenerate all documentation homelab-generate-docs ./docs # Generate in different directory homelab-generate-docs /path/to/output - \`\`\` + ``` ## 📋 Quick Stats diff --git a/pkgs/homelab-docs/service-evaluator.nix b/pkgs/homelab-docs/service-evaluator.nix new file mode 100644 index 0000000..dba33df --- /dev/null +++ b/pkgs/homelab-docs/service-evaluator.nix @@ -0,0 +1,138 @@ +# service-evaluator.nix - Pure Nix evaluation logic for service documentation +{ + nodes, + pkgs, + lib, + ... +}: let + # Helper to recursively extract option information + extractOptions = path: options: let + pathStr = lib.concatStringsSep "." path; + in + lib.flatten (lib.mapAttrsToList ( + name: value: let + currentPath = path ++ [name]; + currentPathStr = lib.concatStringsSep "." currentPath; + in + if (value._type or null) == "option" + then + # This is an actual NixOS option + [ + { + name = currentPathStr; + type = value.type.description or (builtins.typeOf (value.default or null)); + default = value.default or null; + defaultText = + if value ? defaultText + then value.defaultText.text or null + else null; + description = value.description or "No description available"; + example = value.example or null; + readOnly = value.readOnly or false; + } + ] + else if lib.isAttrs value && !(lib.hasAttr "_type" value) + then + # This is a nested attribute set, recurse + extractOptions currentPath value + else + # Skip other types + [] + ) + options); + + # Get service options from the first node (they should be the same across nodes) + firstNode = lib.head (lib.attrValues nodes); + homelabServices = firstNode.options.homelab.services or {}; + + # Extract all services and their options + serviceDefinitions = + lib.mapAttrs (serviceName: serviceOptions: { + inherit serviceName; + options = extractOptions [] serviceOptions; + features = let + optionNames = map (opt: opt.name) (extractOptions [] serviceOptions); + in { + hasMonitoring = lib.any (name: lib.hasPrefix "monitoring" name) optionNames; + hasLogging = lib.any (name: lib.hasPrefix "logging" name) optionNames; + hasProxy = lib.any (name: lib.hasPrefix "proxy" name) optionNames; + }; + }) + homelabServices; + + # Also get all services that exist in actual configurations (for deployment info) + allConfiguredServices = lib.unique (lib.flatten (lib.mapAttrsToList ( + nodeName: node: + if (node.config.homelab.enable or false) + then lib.attrNames (node.config.homelab.services or {}) + else [] + ) + nodes)); + + # For each service, get deployment info + serviceDeployments = lib.listToAttrs (map (serviceName: { + name = serviceName; + value = + lib.foldl ( + acc: nodeName: let + node = nodes.${nodeName}; + serviceConfig = node.config.homelab.services.${serviceName} or null; + isEnabled = + if serviceConfig != null + then serviceConfig.enable or false + else false; + in + if serviceConfig != null + then + acc + // { + totalNodes = acc.totalNodes + 1; + enabledNodes = + acc.enabledNodes + + ( + if isEnabled + then 1 + else 0 + ); + nodeNames = acc.nodeNames ++ [nodeName]; + enabledNodeNames = + acc.enabledNodeNames + ++ ( + if isEnabled + then [nodeName] + else [] + ); + } + else acc + ) { + totalNodes = 0; + enabledNodes = 0; + nodeNames = []; + enabledNodeNames = []; + } (lib.attrNames nodes); + }) + allConfiguredServices); + + # Combine service definitions with deployment info + servicesWithDeployment = + lib.mapAttrs ( + serviceName: serviceData: + serviceData + // { + deployment = + serviceDeployments.${ + serviceName + } or { + totalNodes = 0; + enabledNodes = 0; + nodeNames = []; + enabledNodeNames = []; + }; + } + ) + serviceDefinitions; +in { + services = servicesWithDeployment; + totalServices = lib.length (lib.attrNames servicesWithDeployment); + allConfiguredServices = allConfiguredServices; +} diff --git a/pkgs/homelab-docs/services.nix b/pkgs/homelab-docs/services.nix index 7e6c8a3..808c746 100644 --- a/pkgs/homelab-docs/services.nix +++ b/pkgs/homelab-docs/services.nix @@ -1,4 +1,3 @@ -# homelab-docs-services.nix - Service documentation generator CLI { writeShellScriptBin, jq, @@ -10,261 +9,376 @@ writeShellScriptBin "homelab-docs-services" '' cat << 'EOF' # Service Catalog - > Available services and their configuration options + > Complete service documentation with core options, feature integrations, and smart defaults > > Generated on: $(date) - This document catalogs all available homelab services, their configuration options, and integration capabilities. + This document provides comprehensive documentation for homelab services, organized by: + - **Core Service Options**: The main service configuration + - **Feature Integrations**: Available monitoring, logging, and proxy features + - **Service Defaults**: What this service configures by default for each feature EOF - # Get all services and their configurations - services_catalog=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + # Extract comprehensive service information + echo "Extracting service information..." >&2 + services_catalog=$(colmena eval -E ' + { nodes, pkgs, lib, ... }: let - # Collect all services from all nodes to build a complete catalog - allServiceConfigs = lib.flatten (lib.mapAttrsToList (nodeName: node: - if (node.config.homelab.enable or false) then - lib.mapAttrsToList (serviceName: service: { - inherit serviceName; - config = { - # Core service options - enable = service.enable or false; - port = service.port or null; - description = service.description or serviceName; - tags = service.tags or []; + # Helper to extract option information + extractOptions = path: options: + lib.flatten (lib.mapAttrsToList (name: value: + let + currentPath = path ++ [name]; + pathStr = lib.concatStringsSep "." currentPath; + in + if (value._type or null) == "option" then + [{ + name = pathStr; + type = value.type.description or "unknown"; + default = value.default or null; + defaultText = if value ? defaultText then value.defaultText.text or null else null; + description = value.description or "No description"; + readOnly = value.readOnly or false; + }] + else if lib.isAttrs value && !(lib.hasAttr "_type" value) then + extractOptions currentPath value + else [] + ) options); - # Integration options - monitoring = { - enabled = service.monitoring.enable or false; - metricsPath = service.monitoring.metrics.path or "/metrics"; - healthPath = service.monitoring.healthCheck.path or "/health"; - extraLabels = service.monitoring.extraLabels or {}; - }; + # Get first node for option definitions + firstNode = lib.head (lib.attrValues nodes); + homelabServices = firstNode.options.homelab.services or {}; - logging = { - enabled = service.logging.enable or false; - files = service.logging.files or []; - extraLabels = service.logging.extraLabels or {}; - }; - - proxy = { - enabled = service.proxy.enable or false; - subdomain = service.proxy.subdomain or serviceName; - enableAuth = service.proxy.enableAuth or false; - }; - - # Service-specific options (everything else) - serviceSpecific = removeAttrs service [ - "enable" "port" "description" "tags" - "monitoring" "logging" "proxy" - ]; - }; - deployedOn = nodeName; - }) (node.config.homelab.services or {}) - else [] - ) nodes); - - # Group by service name and merge configurations - serviceGroups = lib.groupBy (svc: svc.serviceName) allServiceConfigs; - - # Get unique services with merged configuration examples - uniqueServices = lib.mapAttrs (serviceName: instances: + # Process each service + serviceInfo = lib.mapAttrs (serviceName: serviceOptions: let - # Take the first enabled instance as the canonical example - enabledInstances = lib.filter (inst: inst.config.enable) instances; - canonicalConfig = if enabledInstances != [] then (lib.head enabledInstances).config else (lib.head instances).config; + allOptions = extractOptions [] serviceOptions; + + # Separate core options from feature options + coreOptions = lib.filter (opt: + !(lib.hasPrefix "monitoring." opt.name) && + !(lib.hasPrefix "logging." opt.name) && + !(lib.hasPrefix "proxy." opt.name) + ) allOptions; + + monitoringOptions = lib.filter (opt: lib.hasPrefix "monitoring." opt.name) allOptions; + loggingOptions = lib.filter (opt: lib.hasPrefix "logging." opt.name) allOptions; + proxyOptions = lib.filter (opt: lib.hasPrefix "proxy." opt.name) allOptions; + + # Get actual service configuration to see what defaults are set + serviceConfigs = lib.mapAttrs (nodeName: node: + let + serviceConfig = node.config.homelab.services.''${serviceName} or null; + in + if serviceConfig != null then { + exists = true; + enabled = serviceConfig.enable or false; + # Extract the computed configuration values + monitoring = serviceConfig.monitoring or {}; + logging = serviceConfig.logging or {}; + proxy = serviceConfig.proxy or {}; + # Get other core options + coreConfig = removeAttrs serviceConfig ["monitoring" "logging" "proxy"]; + } else { + exists = false; + } + ) nodes; + + # Find a node where this service exists to get default values + nodeWithService = lib.findFirst (nodeName: serviceConfigs.''${nodeName}.exists) null (lib.attrNames nodes); + exampleConfig = if nodeWithService != null then serviceConfigs.''${nodeWithService} else null; + in { inherit serviceName; - config = canonicalConfig; - deploymentCount = lib.length (lib.filter (inst: inst.config.enable) instances); - deployedOn = lib.unique (map (inst: inst.deployedOn or "unknown") enabledInstances); + coreOptions = coreOptions; + features = { + monitoring = { + available = monitoringOptions != []; + options = monitoringOptions; + defaults = if exampleConfig != null then exampleConfig.monitoring else {}; + }; + logging = { + available = loggingOptions != []; + options = loggingOptions; + defaults = if exampleConfig != null then exampleConfig.logging else {}; + }; + proxy = { + available = proxyOptions != []; + options = proxyOptions; + defaults = if exampleConfig != null then exampleConfig.proxy else {}; + }; + }; + deployment = { + totalNodes = lib.length (lib.filter (cfg: cfg.exists) (lib.attrValues serviceConfigs)); + enabledNodes = lib.length (lib.filter (cfg: cfg.exists && cfg.enabled) (lib.attrValues serviceConfigs)); + }; } - ) serviceGroups; + ) homelabServices; in { - services = uniqueServices; - totalUniqueServices = lib.length (lib.attrNames uniqueServices); - }') + services = serviceInfo; + totalServices = lib.length (lib.attrNames serviceInfo); + } + ') - total_services=$(echo "$services_catalog" | ${jq}/bin/jq -r '.totalUniqueServices') + total_services=$(echo "$services_catalog" | ${jq}/bin/jq -r '.totalServices') echo "## Overview" echo echo "**Total Available Services:** $total_services" echo - # Create a summary table of services and their default integrations + # Service matrix echo "## Service Integration Matrix" echo - echo "| Service | Monitoring | Logging | Proxy | Auth Default |" - echo "|---------|------------|---------|-------|--------------|" + echo "| Service | Core Options | Monitoring | Logging | Proxy | Deployments |" + echo "|---------|--------------|------------|---------|-------|-------------|" - echo "$services_catalog" | ${jq}/bin/jq -r '.services | to_entries[] | .key' | sort | while read -r service; do + echo "$services_catalog" | ${jq}/bin/jq -r '.services | keys[]' | sort | while read -r service; do service_data=$(echo "$services_catalog" | ${jq}/bin/jq -r ".services[\"$service\"]") - monitoring_enabled=$(echo "$service_data" | ${jq}/bin/jq -r '.config.monitoring.enabled') - logging_enabled=$(echo "$service_data" | ${jq}/bin/jq -r '.config.logging.enabled') - proxy_enabled=$(echo "$service_data" | ${jq}/bin/jq -r '.config.proxy.enabled') - auth_default=$(echo "$service_data" | ${jq}/bin/jq -r '.config.proxy.enableAuth') + core_count=$(echo "$service_data" | ${jq}/bin/jq -r '.coreOptions | length') + has_monitoring=$(echo "$service_data" | ${jq}/bin/jq -r '.features.monitoring.available') + has_logging=$(echo "$service_data" | ${jq}/bin/jq -r '.features.logging.available') + has_proxy=$(echo "$service_data" | ${jq}/bin/jq -r '.features.proxy.available') + enabled_deployments=$(echo "$service_data" | ${jq}/bin/jq -r '.deployment.enabledNodes') - monitoring_icon=$(if [[ "$monitoring_enabled" == "true" ]]; then echo "✅"; else echo "❌"; fi) - logging_icon=$(if [[ "$logging_enabled" == "true" ]]; then echo "✅"; else echo "❌"; fi) - proxy_icon=$(if [[ "$proxy_enabled" == "true" ]]; then echo "✅"; else echo "❌"; fi) - auth_icon=$(if [[ "$auth_default" == "true" ]]; then echo "🔒"; else echo "🌐"; fi) + monitoring_icon=$(if [[ "$has_monitoring" == "true" ]]; then echo "📊"; else echo "❌"; fi) + logging_icon=$(if [[ "$has_logging" == "true" ]]; then echo "📝"; else echo "❌"; fi) + proxy_icon=$(if [[ "$has_proxy" == "true" ]]; then echo "🔀"; else echo "❌"; fi) - echo "| \`$service\` | $monitoring_icon | $logging_icon | $proxy_icon | $auth_icon |" + echo "| \`$service\` | $core_count | $monitoring_icon | $logging_icon | $proxy_icon | $enabled_deployments |" done echo - echo "**Legend:** ✅ = Enabled by default, ❌ = Available but disabled, 🔒 = Auth required, 🌐 = Public access" + echo "**Legend:** 📊📝🔀 = Feature available, ❌ = Feature not available" echo - echo "## Service Reference" + echo "## Service Documentation" echo # Process each service - echo "$services_catalog" | ${jq}/bin/jq -r '.services | to_entries[] | .key' | sort | while read -r service; do + echo "$services_catalog" | ${jq}/bin/jq -r '.services | keys[]' | sort | while read -r service; do echo "### $service" echo - # Get service details service_data=$(echo "$services_catalog" | ${jq}/bin/jq -r ".services[\"$service\"]") + enabled_deployments=$(echo "$service_data" | ${jq}/bin/jq -r '.deployment.enabledNodes') + total_deployments=$(echo "$service_data" | ${jq}/bin/jq -r '.deployment.totalNodes') - description=$(echo "$service_data" | ${jq}/bin/jq -r '.config.description // "No description available"') - port=$(echo "$service_data" | ${jq}/bin/jq -r '.config.port // "N/A"') - tags=$(echo "$service_data" | ${jq}/bin/jq -r '.config.tags | join(", ")') - deployment_count=$(echo "$service_data" | ${jq}/bin/jq -r '.deploymentCount') - deployed_on=$(echo "$service_data" | ${jq}/bin/jq -r '.deployedOn | join(", ")') - - echo "**Description:** $description" - echo - echo "**Default Port:** \`$port\`" - echo - if [[ -n "$tags" && "$tags" != "" ]]; then - echo "**Tags:** $tags" - echo - fi - echo "**Current Deployments:** $deployment_count instance(s) on: $deployed_on" - echo - - # Integration Status Overview - monitoring_enabled=$(echo "$service_data" | ${jq}/bin/jq -r '.config.monitoring.enabled') - logging_enabled=$(echo "$service_data" | ${jq}/bin/jq -r '.config.logging.enabled') - proxy_enabled=$(echo "$service_data" | ${jq}/bin/jq -r '.config.proxy.enabled') - - echo "#### Default Integration Status" - echo - echo "| Integration | Status | Default Configuration |" - echo "|-------------|--------|----------------------|" - - # Monitoring status - if [[ "$monitoring_enabled" == "true" ]]; then - metrics_path=$(echo "$service_data" | ${jq}/bin/jq -r '.config.monitoring.metricsPath') - health_path=$(echo "$service_data" | ${jq}/bin/jq -r '.config.monitoring.healthPath') - echo "| 📊 Monitoring | ✅ **Enabled** | Metrics: \`$metrics_path\`, Health: \`$health_path\` |" + if [[ "$total_deployments" -gt 0 ]]; then + echo "**Deployment Status:** $enabled_deployments/$total_deployments nodes have this service enabled" else - echo "| 📊 Monitoring | ❌ Disabled | Available but requires \`monitoring.enable = true\` |" + echo "**Deployment Status:** Available but not configured" fi - - # Logging status - if [[ "$logging_enabled" == "true" ]]; then - log_files=$(echo "$service_data" | ${jq}/bin/jq -r '.config.logging.files | length') - if [[ "$log_files" -gt 0 ]]; then - echo "| 📝 Logging | ✅ **Enabled** | Collecting $log_files log file(s) |" - else - echo "| 📝 Logging | ✅ **Enabled** | Auto-configured log collection |" - fi - else - echo "| 📝 Logging | ❌ Disabled | Available but requires \`logging.enable = true\` |" - fi - - # Proxy status - if [[ "$proxy_enabled" == "true" ]]; then - subdomain=$(echo "$service_data" | ${jq}/bin/jq -r '.config.proxy.subdomain') - enable_auth=$(echo "$service_data" | ${jq}/bin/jq -r '.config.proxy.enableAuth') - auth_status=$(if [[ "$enable_auth" == "true" ]]; then echo "🔒 Auth required"; else echo "🌐 Public access"; fi) - echo "| 🔀 Proxy | ✅ **Enabled** | Subdomain: \`$subdomain\`, $auth_status |" - else - echo "| 🔀 Proxy | ❌ Disabled | Available but requires \`proxy.enable = true\` |" - fi - echo - # Core Configuration - echo "#### Core Configuration" + # Core Service Configuration + echo "#### Core Service Options" echo - echo "\`\`\`nix" + echo "The main configuration options for $service:" + echo + echo '```nix' echo "homelab.services.$service = {" - echo " enable = true;" - if [[ "$port" != "N/A" ]]; then - echo " port = $port;" - fi - echo " description = \"$description\";" - if [[ -n "$tags" && "$tags" != "" ]]; then - echo " tags = [ $(echo "$tags" | sed 's/, /" "/g' | sed 's/^/"/; s/$/"/') ];" - fi - echo - echo " # Default integrations (adjust as needed)" - if [[ "$monitoring_enabled" == "true" ]]; then - echo " monitoring.enable = true; # ✅ Enabled by default" - else - echo " # monitoring.enable = true; # ❌ Disabled by default" - fi - if [[ "$logging_enabled" == "true" ]]; then - echo " logging.enable = true; # ✅ Enabled by default" - else - echo " # logging.enable = true; # ❌ Disabled by default" - fi - if [[ "$proxy_enabled" == "true" ]]; then - echo " proxy.enable = true; # ✅ Enabled by default" - else - echo " # proxy.enable = true; # ❌ Disabled by default" - fi + + echo "$service_data" | ${jq}/bin/jq -r '.coreOptions[] | @base64' | while IFS= read -r option_b64; do + option=$(echo "$option_b64" | base64 -d) + + name=$(echo "$option" | ${jq}/bin/jq -r '.name') + type=$(echo "$option" | ${jq}/bin/jq -r '.type') + default_val=$(echo "$option" | ${jq}/bin/jq -r '.default') + description=$(echo "$option" | ${jq}/bin/jq -r '.description') + read_only=$(echo "$option" | ${jq}/bin/jq -r '.readOnly') + + if [[ "$read_only" == "true" ]]; then + continue + fi + + clean_description=$(echo "$description" | sed 's/"/\\"/g' | tr -d $'\n\r') + + if [[ "$default_val" == "null" ]]; then + echo " # $name = <$type>; # $clean_description" + else + echo " $name = $default_val; # $clean_description" + fi + done + echo "};" - echo "\`\`\`" + echo '```' echo - # Service-specific options - service_specific=$(echo "$service_data" | ${jq}/bin/jq -r '.config.serviceSpecific') - if [[ "$service_specific" != "{}" && "$service_specific" != "null" ]]; then - echo "#### Service-Specific Options" - echo - echo "Available configuration options for $service:" - echo - echo "\`\`\`nix" - echo "homelab.services.$service = {" - echo " # ... core options above ..." - echo - echo " # Service-specific configuration" - echo "$service_specific" | ${jq}/bin/jq -r 'to_entries[] | " \(.key) = \(.value | tostring);"' - echo "};" - echo "\`\`\`" + # Feature Integrations + has_monitoring=$(echo "$service_data" | ${jq}/bin/jq -r '.features.monitoring.available') + has_logging=$(echo "$service_data" | ${jq}/bin/jq -r '.features.logging.available') + has_proxy=$(echo "$service_data" | ${jq}/bin/jq -r '.features.proxy.available') + + if [[ "$has_monitoring" == "true" || "$has_logging" == "true" || "$has_proxy" == "true" ]]; then + echo "#### Feature Integrations" echo + + # Monitoring Feature + if [[ "$has_monitoring" == "true" ]]; then + echo "##### 📊 Monitoring Integration" + echo + echo "Available monitoring options:" + echo + echo '```nix' + echo "homelab.services.$service = {" + echo " # ... core options above ..." + echo + + echo "$service_data" | ${jq}/bin/jq -r '.features.monitoring.options[] | @base64' | while IFS= read -r option_b64; do + option=$(echo "$option_b64" | base64 -d) + + name=$(echo "$option" | ${jq}/bin/jq -r '.name') + type=$(echo "$option" | ${jq}/bin/jq -r '.type') + default_val=$(echo "$option" | ${jq}/bin/jq -r '.default') + description=$(echo "$option" | ${jq}/bin/jq -r '.description') + read_only=$(echo "$option" | ${jq}/bin/jq -r '.readOnly') + + if [[ "$read_only" == "true" ]]; then + continue + fi + + clean_description=$(echo "$description" | sed 's/"/\\"/g' | tr -d $'\n\r') + + if [[ "$default_val" == "null" ]]; then + echo " # $name = <$type>; # $clean_description" + else + echo " $name = $default_val; # $clean_description" + fi + done + + echo "};" + echo '```' + + # Show service-specific monitoring defaults + monitoring_defaults=$(echo "$service_data" | ${jq}/bin/jq -r '.features.monitoring.defaults') + if [[ "$monitoring_defaults" != "{}" && "$monitoring_defaults" != "null" ]]; then + echo + echo "**$service sets these monitoring defaults:**" + echo '```nix' + echo "$monitoring_defaults" | ${jq}/bin/jq -r 'to_entries[] | " \(.key) = \(.value);"' + echo '```' + fi + echo + fi + + # Logging Feature + if [[ "$has_logging" == "true" ]]; then + echo "##### 📝 Logging Integration" + echo + echo "Available logging options:" + echo + echo '```nix' + echo "homelab.services.$service = {" + echo " # ... core options above ..." + echo + + echo "$service_data" | ${jq}/bin/jq -r '.features.logging.options[] | @base64' | while IFS= read -r option_b64; do + option=$(echo "$option_b64" | base64 -d) + + name=$(echo "$option" | ${jq}/bin/jq -r '.name') + type=$(echo "$option" | ${jq}/bin/jq -r '.type') + default_val=$(echo "$option" | ${jq}/bin/jq -r '.default') + description=$(echo "$option" | ${jq}/bin/jq -r '.description') + read_only=$(echo "$option" | ${jq}/bin/jq -r '.readOnly') + + if [[ "$read_only" == "true" ]]; then + continue + fi + + clean_description=$(echo "$description" | sed 's/"/\\"/g' | tr -d $'\n\r') + + if [[ "$default_val" == "null" ]]; then + echo " # $name = <$type>; # $clean_description" + else + echo " $name = $default_val; # $clean_description" + fi + done + + echo "};" + echo '```' + + # Show service-specific logging defaults + logging_defaults=$(echo "$service_data" | ${jq}/bin/jq -r '.features.logging.defaults') + if [[ "$logging_defaults" != "{}" && "$logging_defaults" != "null" ]]; then + echo + echo "**$service sets these logging defaults:**" + echo '```nix' + echo "$logging_defaults" | ${jq}/bin/jq -r 'to_entries[] | " \(.key) = \(.value);"' + echo '```' + fi + echo + fi + + # Proxy Feature + if [[ "$has_proxy" == "true" ]]; then + echo "##### 🔀 Proxy Integration" + echo + echo "Available proxy options:" + echo + echo '```nix' + echo "homelab.services.$service = {" + echo " # ... core options above ..." + echo + + echo "$service_data" | ${jq}/bin/jq -r '.features.proxy.options[] | @base64' | while IFS= read -r option_b64; do + option=$(echo "$option_b64" | base64 -d) + + name=$(echo "$option" | ${jq}/bin/jq -r '.name') + type=$(echo "$option" | ${jq}/bin/jq -r '.type') + default_val=$(echo "$option" | ${jq}/bin/jq -r '.default') + description=$(echo "$option" | ${jq}/bin/jq -r '.description') + read_only=$(echo "$option" | ${jq}/bin/jq -r '.readOnly') + + if [[ "$read_only" == "true" ]]; then + continue + fi + + clean_description=$(echo "$description" | sed 's/"/\\"/g' | tr -d $'\n\r') + + if [[ "$default_val" == "null" ]]; then + echo " # $name = <$type>; # $clean_description" + else + echo " $name = $default_val; # $clean_description" + fi + done + + echo "};" + echo '```' + + # Show service-specific proxy defaults + proxy_defaults=$(echo "$service_data" | ${jq}/bin/jq -r '.features.proxy.defaults') + if [[ "$proxy_defaults" != "{}" && "$proxy_defaults" != "null" ]]; then + echo + echo "**$service sets these proxy defaults:**" + echo '```nix' + echo "$proxy_defaults" | ${jq}/bin/jq -r 'to_entries[] | " \(.key) = \(.value);"' + echo '```' + fi + echo + fi fi echo "---" echo done - echo "## Integration Summary" + echo "## Feature Reference" echo - echo "### Available Integration Types" + echo "### Integration Features" echo - echo "| Integration | Purpose | Default Behavior | Configuration |" - echo "|-------------|---------|------------------|---------------|" - echo "| **📊 Monitoring** | Prometheus metrics + health checks | Service-dependent | \`monitoring.enable = true\` |" - echo "| **📝 Logging** | Centralized log collection | Service-dependent | \`logging.enable = true\` |" - echo "| **🔀 Proxy** | Reverse proxy with SSL + auth | Service-dependent | \`proxy.enable = true\` |" + echo "Homelab services can integrate with three main features:" echo - echo "### Integration Benefits" + echo "- **📊 Monitoring**: Prometheus metrics and health checks" + echo "- **📝 Logging**: Centralized log collection with Promtail/Loki" + echo "- **🔀 Proxy**: Reverse proxy with SSL and authentication" echo - echo "- **🔄 Automatic Discovery:** Enabled integrations are automatically discovered by fleet-wide services" - echo "- **📊 Unified Monitoring:** All metrics and health checks appear in Prometheus/Grafana" - echo "- **📝 Centralized Logging:** All logs are collected and indexed in Loki" - echo "- **🌐 Consistent Access:** All services get consistent subdomain access with SSL" - echo "- **🎯 Smart Defaults:** Each service comes with sensible default configurations" + echo "Each service can import these features and set service-specific defaults." echo echo "---" echo - echo "*This service catalog is generated from actual service configurations across your homelab fleet.*" + echo "*This documentation is generated from actual NixOS module evaluations.*" '' From e276c47686b115abf3bdf366f4bcc50713dff619 Mon Sep 17 00:00:00 2001 From: plasmagoat Date: Wed, 30 Jul 2025 00:22:53 +0200 Subject: [PATCH 2/2] err --- pkgs/homelab-docs/service-evaluator.nix | 138 ------------------------ 1 file changed, 138 deletions(-) delete mode 100644 pkgs/homelab-docs/service-evaluator.nix diff --git a/pkgs/homelab-docs/service-evaluator.nix b/pkgs/homelab-docs/service-evaluator.nix deleted file mode 100644 index dba33df..0000000 --- a/pkgs/homelab-docs/service-evaluator.nix +++ /dev/null @@ -1,138 +0,0 @@ -# service-evaluator.nix - Pure Nix evaluation logic for service documentation -{ - nodes, - pkgs, - lib, - ... -}: let - # Helper to recursively extract option information - extractOptions = path: options: let - pathStr = lib.concatStringsSep "." path; - in - lib.flatten (lib.mapAttrsToList ( - name: value: let - currentPath = path ++ [name]; - currentPathStr = lib.concatStringsSep "." currentPath; - in - if (value._type or null) == "option" - then - # This is an actual NixOS option - [ - { - name = currentPathStr; - type = value.type.description or (builtins.typeOf (value.default or null)); - default = value.default or null; - defaultText = - if value ? defaultText - then value.defaultText.text or null - else null; - description = value.description or "No description available"; - example = value.example or null; - readOnly = value.readOnly or false; - } - ] - else if lib.isAttrs value && !(lib.hasAttr "_type" value) - then - # This is a nested attribute set, recurse - extractOptions currentPath value - else - # Skip other types - [] - ) - options); - - # Get service options from the first node (they should be the same across nodes) - firstNode = lib.head (lib.attrValues nodes); - homelabServices = firstNode.options.homelab.services or {}; - - # Extract all services and their options - serviceDefinitions = - lib.mapAttrs (serviceName: serviceOptions: { - inherit serviceName; - options = extractOptions [] serviceOptions; - features = let - optionNames = map (opt: opt.name) (extractOptions [] serviceOptions); - in { - hasMonitoring = lib.any (name: lib.hasPrefix "monitoring" name) optionNames; - hasLogging = lib.any (name: lib.hasPrefix "logging" name) optionNames; - hasProxy = lib.any (name: lib.hasPrefix "proxy" name) optionNames; - }; - }) - homelabServices; - - # Also get all services that exist in actual configurations (for deployment info) - allConfiguredServices = lib.unique (lib.flatten (lib.mapAttrsToList ( - nodeName: node: - if (node.config.homelab.enable or false) - then lib.attrNames (node.config.homelab.services or {}) - else [] - ) - nodes)); - - # For each service, get deployment info - serviceDeployments = lib.listToAttrs (map (serviceName: { - name = serviceName; - value = - lib.foldl ( - acc: nodeName: let - node = nodes.${nodeName}; - serviceConfig = node.config.homelab.services.${serviceName} or null; - isEnabled = - if serviceConfig != null - then serviceConfig.enable or false - else false; - in - if serviceConfig != null - then - acc - // { - totalNodes = acc.totalNodes + 1; - enabledNodes = - acc.enabledNodes - + ( - if isEnabled - then 1 - else 0 - ); - nodeNames = acc.nodeNames ++ [nodeName]; - enabledNodeNames = - acc.enabledNodeNames - ++ ( - if isEnabled - then [nodeName] - else [] - ); - } - else acc - ) { - totalNodes = 0; - enabledNodes = 0; - nodeNames = []; - enabledNodeNames = []; - } (lib.attrNames nodes); - }) - allConfiguredServices); - - # Combine service definitions with deployment info - servicesWithDeployment = - lib.mapAttrs ( - serviceName: serviceData: - serviceData - // { - deployment = - serviceDeployments.${ - serviceName - } or { - totalNodes = 0; - enabledNodes = 0; - nodeNames = []; - enabledNodeNames = []; - }; - } - ) - serviceDefinitions; -in { - services = servicesWithDeployment; - totalServices = lib.length (lib.attrNames servicesWithDeployment); - allConfiguredServices = allConfiguredServices; -}