{ writeShellScriptBin, jq, }: writeShellScriptBin "homelab-docs-services" '' #!/usr/bin/env bash set -euo pipefail cat << EOF # Service Catalog > Complete service documentation with core options, feature integrations, and smart defaults > > Generated on: $(date -R) 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 # Extract comprehensive service information echo "Extracting service information..." >&2 services_catalog=$(colmena eval -E ' { nodes, pkgs, lib, ... }: let # 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); # Get first node for option definitions firstNode = lib.head (lib.attrValues nodes); homelabServices = firstNode.options.homelab.services or {}; # Process each service serviceInfo = lib.mapAttrs (serviceName: serviceOptions: let 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; 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)); }; } ) homelabServices; in { services = serviceInfo; totalServices = lib.length (lib.attrNames serviceInfo); } ') total_services=$(echo "$services_catalog" | ${jq}/bin/jq -r '.totalServices') echo "## Overview" echo echo "**Total Available Services:** $total_services" echo # Service matrix echo "## Service Integration Matrix" echo echo "| Service | Core Options | Monitoring | Logging | Proxy | Deployments |" echo "|---------|--------------|------------|---------|-------|-------------|" 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\"]") 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 [[ "$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\` | $core_count | $monitoring_icon | $logging_icon | $proxy_icon | $enabled_deployments |" done echo echo "**Legend:** 📊📝🔀 = Feature available, ❌ = Feature not available" echo echo "## Service Documentation" echo # Process each service echo "$services_catalog" | ${jq}/bin/jq -r '.services | keys[]' | sort | while read -r service; do echo "### $service" echo 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') if [[ "$total_deployments" -gt 0 ]]; then echo "**Deployment Status:** $enabled_deployments/$total_deployments nodes have this service enabled" else echo "**Deployment Status:** Available but not configured" fi echo # Core Service Configuration echo "#### Core Service Options" echo echo "The main configuration options for $service:" echo echo '```nix' echo "homelab.services.$service = {" 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 # 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 "## Feature Reference" echo echo "### Integration Features" echo echo "Homelab services can integrate with three main features:" echo 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 "Each service can import these features and set service-specific defaults." echo echo "---" echo echo "*This documentation is generated from actual NixOS module evaluations.*" ''