From ac59749e9f2594f19e82e2aeb0b587ac44f979fd Mon Sep 17 00:00:00 2001 From: Forgejo Bot Date: Tue, 29 Jul 2025 06:01:02 +0000 Subject: [PATCH 1/8] feat: automated changes --- flake.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/flake.lock b/flake.lock index 61e80ac..0fa9b9e 100644 --- a/flake.lock +++ b/flake.lock @@ -25,11 +25,11 @@ "stable": "stable" }, "locked": { - "lastModified": 1752287590, - "narHash": "sha256-U1IqFnxlgCRrPaeT5IGCdH0j9CNLPFcI/fRAidi0aDQ=", + "lastModified": 1753701727, + "narHash": "sha256-tgiPAFXoSGIm3wUAuKwjk2fgTgZ0rpT90RNfhU5QKJA=", "owner": "zhaofengli", "repo": "colmena", - "rev": "d2beb694d54db653399b8597c0f6e15e20b26405", + "rev": "342054695f53c4a27c8dce0a8c9f35ade6d963d6", "type": "github" }, "original": { @@ -156,11 +156,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1753429684, - "narHash": "sha256-9h7+4/53cSfQ/uA3pSvCaBepmZaz/dLlLVJnbQ+SJjk=", + "lastModified": 1750134718, + "narHash": "sha256-v263g4GbxXv87hMXMCpjkIxd/viIF7p3JpJrwgKdNiI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7fd36ee82c0275fb545775cc5e4d30542899511d", + "rev": "9e83b64f727c88a7711a2c463a7b16eedb69a84c", "type": "github" }, "original": { @@ -188,11 +188,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1752480373, - "narHash": "sha256-JHQbm+OcGp32wAsXTE/FLYGNpb+4GLi5oTvCxwSoBOA=", + "lastModified": 1753694789, + "narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "62e0f05ede1da0d54515d4ea8ce9c733f12d9f08", + "rev": "dc9637876d0dcc8c9e5e22986b857632effeb727", "type": "github" }, "original": { @@ -204,11 +204,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1752624097, - "narHash": "sha256-mQCof2VccFzF7cmXy43n3GCwSN2+m8TVhZpGLx9sxVc=", + "lastModified": 1753766937, + "narHash": "sha256-L4kx7TQsmD7GnNaGGXlLhqZVPwilP7QIwDPw88Wzzrs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d7c8095791ce3aafe97d9c16c1dc2f4e3d69a3ba", + "rev": "23296a5649db3ae810cdc41ecd5055d131a82b11", "type": "github" }, "original": { @@ -237,11 +237,11 @@ "nixpkgs-25_05": "nixpkgs-25_05" }, "locked": { - "lastModified": 1752060039, - "narHash": "sha256-MqcbN/PgfXOv8S4q6GcmlORd6kJZ3UlFNhzCvLOEe4I=", + "lastModified": 1753285640, + "narHash": "sha256-ofa021NeHDXAxg5J8mSnn8rHa393PAlD85ZCetP4Qa0=", "owner": "simple-nixos-mailserver", "repo": "nixos-mailserver", - "rev": "80d21ed7a1ab8007597f7cd9adc26ebc98b9611f", + "rev": "ce87c8a9771d1a20c3fa3b60113b9b0821627dcb", "type": "gitlab" }, "original": { From ce8c543e84eb5d4fb5b586fd71b739f5dfc04b8e Mon Sep 17 00:00:00 2001 From: plasmagoat Date: Tue, 29 Jul 2025 16:28:17 +0200 Subject: [PATCH 2/8] auto docs --- docs/README.md | 65 ++ docs/current-deployment.md | 26 + docs/fleet-overview.md | 39 + docs/nodes.md | 70 ++ docs/services.md | 209 ++++++ flake.lock | 30 +- flake.nix | 7 +- hosts/sandbox/default.nix | 6 +- modules/homelab/README.md | 577 +++++++++++++++ modules/homelab/default.nix | 4 +- modules/homelab/lib/cli/cli-commands.sh | 943 ++++++++++++++++++++++++ modules/homelab/lib/cli/homelab-cli.nix | 295 ++++++++ modules/homelab/lib/systems/backups.nix | 22 +- modules/homelab/lib/systems/logging.nix | 18 +- modules/homelab/lib/systems/proxy.nix | 18 +- modules/homelab/services/prometheus.nix | 13 +- pkgs/default.nix | 1 + pkgs/homelab-docs.nix | 841 +++++++++++++++++++++ 18 files changed, 3129 insertions(+), 55 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/current-deployment.md create mode 100644 docs/fleet-overview.md create mode 100644 docs/nodes.md create mode 100644 docs/services.md create mode 100644 modules/homelab/README.md create mode 100644 modules/homelab/lib/cli/cli-commands.sh create mode 100644 modules/homelab/lib/cli/homelab-cli.nix create mode 100644 pkgs/homelab-docs.nix diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2ea873c --- /dev/null +++ b/docs/README.md @@ -0,0 +1,65 @@ +# Homelab Documentation + +> Auto-generated documentation for the homelab deployment +> +> Generated on: tir 29 jul 16:25:52 CEST 2025 +> Source: /home/plasmagoat/homelab + +## πŸ“š Documentation Files + +This documentation is automatically generated from your colmena flake configuration. + +### πŸ“Š Overview Documents +- **[Fleet Overview](fleet-overview.md)** - High-level fleet statistics and service distribution +- **[Current Deployment](current-deployment.md)** - Current deployment state and node status + +### πŸ“– Detailed Configuration +- **[Node Configurations](nodes.md)** - Per-node detailed configuration and services +- **[Service Configurations](services.md)** - Service configurations across the fleet + +## πŸš€ Quick Actions + +### View Current Status +```bash +# Service status across fleet (if homelab CLI is available) +homelab services --global + +# Backup status +homelab backups --global + +# Overall status +homelab status +``` + +### Update Documentation +```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:25:55 CEST 2025 + +## πŸ› οΈ Management Tools + +### Documentation Commands +- `homelab-generate-docs` - Regenerate this documentation +- `homelab-docs-fleet` - Generate fleet overview only +- `homelab-docs-nodes` - Generate node configurations only +- `homelab-docs-services` - Generate service configurations only +- `homelab-docs-deployment` - Generate deployment state only + +### Colmena Commands +- `colmena eval` - Evaluate flake expressions +- `colmena apply` - Deploy configuration changes +- `colmena build` - Build configurations without deploying + +--- + +*This documentation reflects the live state of your homelab deployment as evaluated by colmena.* diff --git a/docs/current-deployment.md b/docs/current-deployment.md new file mode 100644 index 0000000..1767502 --- /dev/null +++ b/docs/current-deployment.md @@ -0,0 +1,26 @@ +# Current Deployment State + +> Current homelab deployment configuration +> +> Generated on: tir 29 jul 16:25:46 CEST 2025 +> Working directory: /home/plasmagoat/homelab + +## Deployment Summary + +| Metric | Count | +|--------|-------| +| Total Nodes | 2 | +| Homelab-Enabled Nodes | 2 | +| Unique Services | 1 | +| Service Instances | 1 | + +## Node Status + +| Node | Homelab | Environment | Services | Monitoring | Backups | Proxy | +|------|---------|-------------|----------|------------|---------|-------| +| `photos` | βœ… | production | 1 | βœ… | ❌ | ❌ | +| `sandbox` | βœ… | production | 0 | βœ… | βœ… | ❌ | + +--- + +*Deployment state extracted from live colmena configuration* diff --git a/docs/fleet-overview.md b/docs/fleet-overview.md new file mode 100644 index 0000000..601ce6c --- /dev/null +++ b/docs/fleet-overview.md @@ -0,0 +1,39 @@ +# Homelab Fleet Overview + +> Auto-generated fleet overview +> +> Generated on: tir 29 jul 16:25:32 CEST 2025 +> Source: /home/plasmagoat/homelab + +## Fleet Statistics + +### Basic Information + +| Metric | Value | +|--------|-------| +| Total Nodes | 2 | +| Node Names | photos sandbox | + +### Homelab Configuration + +| Node | Homelab Enabled | Hostname | Environment | +|------|----------------|----------|-------------| +| `photos` | βœ… | photos | production | +| `sandbox` | βœ… | sandbox | production | + +### Service Distribution + +| Node | Service Count | Services | +|------|---------------|----------| +| `photos` | 1 | minio | +| `sandbox` | 0 | | + +### Environment Distribution + +| Environment | Node Count | +|-------------|------------| +| production | 2 | + +--- + +*Fleet overview generated from colmena evaluation* diff --git a/docs/nodes.md b/docs/nodes.md new file mode 100644 index 0000000..25bdbb8 --- /dev/null +++ b/docs/nodes.md @@ -0,0 +1,70 @@ +# Node Configurations + +> Detailed per-node configuration +> +> Generated on: tir 29 jul 16:25:40 CEST 2025 + +## Node: photos + +### System Information + +| Property | Value | +|----------|-------| +| NixOS Version | `25.11pre-git` | +| Hostname | `photos` | +| System | `x86_64-linux` | + +### Homelab Configuration + +| Property | Value | +|----------|-------| +| Homelab Hostname | `photos` | +| Domain | `lab` | +| External Domain | `procopius.dk` | +| Environment | `production` | +| Location | `proxmox-cluster` | +| Tags | photos | + +### Services + +| Service | Enabled | Port | Description | Tags | +|---------|---------|------|-------------|------| +| `gatus` | ❌ | 8080 | Gatus Status Page | | +| `grafana` | ❌ | 3000 | Grafana Metrics Dashboard | | +| `minio` | βœ… | 9000 | minio | | +| `prometheus` | ❌ | 9090 | Prometheus Monitoring Server | | + +--- + +## Node: sandbox + +### System Information + +| Property | Value | +|----------|-------| +| NixOS Version | `25.11pre-git` | +| Hostname | `sandbox` | +| System | `x86_64-linux` | + +### Homelab Configuration + +| Property | Value | +|----------|-------| +| Homelab Hostname | `sandbox` | +| Domain | `lab` | +| External Domain | `procopius.dk` | +| Environment | `production` | +| Location | `proxmox-cluster` | +| Tags | sandbox | + +### Services + +| Service | Enabled | Port | Description | Tags | +|---------|---------|------|-------------|------| +| `gatus` | ❌ | 8080 | Gatus Status Page | | +| `grafana` | ❌ | 3000 | Grafana Metrics Dashboard | | +| `minio` | ❌ | 9000 | minio | | +| `prometheus` | ❌ | 9090 | Prometheus Monitoring Server | | + +--- + diff --git a/docs/services.md b/docs/services.md new file mode 100644 index 0000000..e528e6f --- /dev/null +++ b/docs/services.md @@ -0,0 +1,209 @@ +# Service Catalog + +> Available services and their configuration options +> +> Generated on: tir 29 jul 16:25:43 CEST 2025 + +This document catalogs all available homelab services, their configuration options, and integration capabilities. + +## Overview + +**Total Available Services:** 4 + +## Service Reference + +### gatus + +**Description:** Gatus Status Page + +**Default Port:** `8080` + +**Current Deployments:** 0 instance(s) + +#### Core Configuration + +```nix +homelab.services.gatus = { + enable = true; + port = 8080; + description = "Gatus Status Page"; +}; +``` + +#### Service-Specific Options + +Available configuration options for gatus: + +```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"}; +}; +``` + +#### Complete Example + +```nix +# Full configuration example for gatus +homelab.services.gatus = { + enable = true; + port = 8080; + description = "Gatus Status Page"; +}; +``` + +--- + +### grafana + +**Description:** Grafana Metrics Dashboard + +**Default Port:** `3000` + +**Current Deployments:** 0 instance(s) + +#### Core Configuration + +```nix +homelab.services.grafana = { + enable = true; + port = 3000; + description = "Grafana Metrics Dashboard"; +}; +``` + +#### Complete Example + +```nix +# Full configuration example for grafana +homelab.services.grafana = { + enable = true; + port = 3000; + description = "Grafana Metrics Dashboard"; +}; +``` + +--- + +### minio + +**Description:** minio + +**Default Port:** `9000` + +**Current Deployments:** 1 instance(s) + +#### Core Configuration + +```nix +homelab.services.minio = { + enable = true; + port = 9000; + description = "minio"; +}; +``` + +#### Service-Specific Options + +Available configuration options for minio: + +```nix +homelab.services.minio = { + # ... core options above ... + + # Service-specific configuration + openFirewall = true; + webPort = 9001; +}; +``` + +#### Complete Example + +```nix +# Full configuration example for minio +homelab.services.minio = { + enable = true; + port = 9000; + description = "minio"; +}; +``` + +--- + +### prometheus + +**Description:** Prometheus Monitoring Server + +**Default Port:** `9090` + +**Current Deployments:** 0 instance(s) + +#### Core Configuration + +```nix +homelab.services.prometheus = { + enable = true; + port = 9090; + description = "Prometheus Monitoring Server"; +}; +``` + +#### Service-Specific Options + +Available configuration options for prometheus: + +```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"]; +}; +``` + +#### Complete Example + +```nix +# Full configuration example for prometheus +homelab.services.prometheus = { + enable = true; + port = 9090; + description = "Prometheus Monitoring Server"; +}; +``` + +--- + +## Integration Summary + +### Available Integration Types + +| Integration | Purpose | Configuration | +|-------------|---------|---------------| +| **Monitoring** | Prometheus metrics + health checks | `monitoring.enable = true` | +| **Logging** | Centralized log collection | `logging.enable = true` | +| **Proxy** | Reverse proxy with SSL + auth | `proxy.enable = true` | + +### Integration Benefits + +- **πŸ”„ 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 + +--- + +*This service catalog is generated from actual service configurations across your homelab fleet.* diff --git a/flake.lock b/flake.lock index 61e80ac..66740fb 100644 --- a/flake.lock +++ b/flake.lock @@ -25,11 +25,11 @@ "stable": "stable" }, "locked": { - "lastModified": 1752287590, - "narHash": "sha256-U1IqFnxlgCRrPaeT5IGCdH0j9CNLPFcI/fRAidi0aDQ=", + "lastModified": 1753701727, + "narHash": "sha256-tgiPAFXoSGIm3wUAuKwjk2fgTgZ0rpT90RNfhU5QKJA=", "owner": "zhaofengli", "repo": "colmena", - "rev": "d2beb694d54db653399b8597c0f6e15e20b26405", + "rev": "342054695f53c4a27c8dce0a8c9f35ade6d963d6", "type": "github" }, "original": { @@ -156,11 +156,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1753429684, - "narHash": "sha256-9h7+4/53cSfQ/uA3pSvCaBepmZaz/dLlLVJnbQ+SJjk=", + "lastModified": 1750134718, + "narHash": "sha256-v263g4GbxXv87hMXMCpjkIxd/viIF7p3JpJrwgKdNiI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7fd36ee82c0275fb545775cc5e4d30542899511d", + "rev": "9e83b64f727c88a7711a2c463a7b16eedb69a84c", "type": "github" }, "original": { @@ -188,11 +188,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1752480373, - "narHash": "sha256-JHQbm+OcGp32wAsXTE/FLYGNpb+4GLi5oTvCxwSoBOA=", + "lastModified": 1753694789, + "narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "62e0f05ede1da0d54515d4ea8ce9c733f12d9f08", + "rev": "dc9637876d0dcc8c9e5e22986b857632effeb727", "type": "github" }, "original": { @@ -204,11 +204,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1752624097, - "narHash": "sha256-mQCof2VccFzF7cmXy43n3GCwSN2+m8TVhZpGLx9sxVc=", + "lastModified": 1753795159, + "narHash": "sha256-0fOuNh5MefjES+ie0zV3mVMSs1RwXhVIxcNQuu+Q4g4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d7c8095791ce3aafe97d9c16c1dc2f4e3d69a3ba", + "rev": "5a012ffbe2494cb777ec3dbace5811f927bddc72", "type": "github" }, "original": { @@ -237,11 +237,11 @@ "nixpkgs-25_05": "nixpkgs-25_05" }, "locked": { - "lastModified": 1752060039, - "narHash": "sha256-MqcbN/PgfXOv8S4q6GcmlORd6kJZ3UlFNhzCvLOEe4I=", + "lastModified": 1753285640, + "narHash": "sha256-ofa021NeHDXAxg5J8mSnn8rHa393PAlD85ZCetP4Qa0=", "owner": "simple-nixos-mailserver", "repo": "nixos-mailserver", - "rev": "80d21ed7a1ab8007597f7cd9adc26ebc98b9611f", + "rev": "ce87c8a9771d1a20c3fa3b60113b9b0821627dcb", "type": "gitlab" }, "original": { diff --git a/flake.nix b/flake.nix index 9e6905f..c183ee9 100644 --- a/flake.nix +++ b/flake.nix @@ -25,6 +25,7 @@ self, nixpkgs, # systems, + colmena, sops-nix, # home-manager, simple-nixos-mailserver, @@ -54,18 +55,18 @@ # Reusable nixos modules nixosModules = import ./modules/nixos; - colmenaHive = inputs.colmena.lib.makeHive self.outputs.colmena; + colmenaHive = colmena.lib.makeHive self.outputs.colmena; colmena = import ./colmena.nix {inherit inputs outputs;}; # Development shells devShells = forAllSystems ( system: let - inherit (inputs.colmena.packages."${pkgs.system}") colmena; pkgs = nixpkgs.legacyPackages.${system}; in { default = pkgs.mkShell { packages = with pkgs; [ - # colmena + self.packages.${system}.homelab-docs + colmena.packages.${system}.colmena sops age nix-output-monitor diff --git a/hosts/sandbox/default.nix b/hosts/sandbox/default.nix index ebf4475..70eb387 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/README.md b/modules/homelab/README.md new file mode 100644 index 0000000..6fd7d72 --- /dev/null +++ b/modules/homelab/README.md @@ -0,0 +1,577 @@ +# Homelab Configuration Documentation + +## Overview + +This homelab configuration system provides a unified way to manage services across multiple nodes with automatic aggregation of monitoring, logging, backup, and reverse proxy configurations. The system is built on NixOS and follows a modular architecture with both local and global configuration scopes. + +## Core Homelab Options + +### Basic Configuration (`homelab.*`) + +```nix +homelab = { + enable = true; # Enable homelab fleet configuration + hostname = "node-01"; # Hostname for this system + domain = "lab"; # Base domain for the homelab (default: "lab") + externalDomain = "procopius.dk"; # External domain to the homelab + environment = "production"; # Environment type: "production" | "staging" | "development" + location = "homelab"; # Physical location identifier + tags = ["web" "database"]; # Tags for this system +}; +``` + +### Monitoring System (`homelab.monitoring.*`) + +```nix +homelab.monitoring = { + enable = true; # Enable monitoring system + + # Node exporter (automatically enabled) + nodeExporter = { + enable = true; # Enable node exporter (default: true) + port = 9100; # Node exporter port (default: 9100) + }; + + # Manual metrics endpoints + metrics = [ + { + name = "custom-app"; # Metric endpoint name + host = "localhost"; # Host (default: homelab.hostname) + port = 8080; # Port for metrics endpoint + path = "/metrics"; # Metrics path (default: "/metrics") + jobName = "custom"; # Prometheus job name + scrapeInterval = "30s"; # Scrape interval (default: "30s") + labels = { # Additional labels + component = "web"; + }; + } + ]; + + # Manual health checks + healthChecks = [ + { + name = "web-service"; # Health check name + host = "localhost"; # Host (default: homelab.hostname) + port = 80; # Port (nullable) + path = "/health"; # Health check path (default: "/") + protocol = "http"; # Protocol: "http" | "https" | "tcp" | "icmp" + method = "GET"; # HTTP method (default: "GET") + interval = "30s"; # Check interval (default: "30s") + timeout = "10s"; # Timeout (default: "10s") + conditions = [ # Check conditions + "[STATUS] == 200" + ]; + group = "web"; # Group name (default: "manual") + labels = {}; # Additional labels + enabled = true; # Enable check (default: true) + } + ]; + + # Read-only aggregated data (automatically populated) + allMetrics = [...]; # All metrics from this node + allHealthChecks = [...]; # All health checks from this node + global = { # Global aggregation from all nodes + allMetrics = [...]; # All metrics from entire fleet + allHealthChecks = [...]; # All health checks from entire fleet + metricsByJobName = {...}; # Grouped by job name + healthChecksByGroup = {...}; # Grouped by group + summary = { + totalMetrics = 42; + totalHealthChecks = 15; + nodesCovered = ["node-01" "node-02"]; + }; + }; +}; +``` + +### Logging System (`homelab.logging.*`) + +```nix +homelab.logging = { + enable = true; # Enable logging system + + # Promtail configuration + promtail = { + enable = true; # Enable Promtail (default: true) + port = 9080; # Promtail port (default: 9080) + clients = [ # Loki clients + { + url = "http://monitor.lab:3100/loki/api/v1/push"; + tenant_id = null; # Optional tenant ID + } + ]; + }; + + # Log sources + sources = [ + { + name = "app-logs"; # Source name + type = "file"; # Type: "journal" | "file" | "syslog" | "docker" + files = { + paths = ["/var/log/app.log"]; # File paths + multiline = { # Optional multiline config + firstLineRegex = "^\\d{4}-\\d{2}-\\d{2}"; + maxWaitTime = "3s"; + }; + }; + journal = { # Journal config (for type="journal") + path = "/var/log/journal"; + }; + labels = { # Additional labels + application = "myapp"; + }; + pipelineStages = []; # Promtail pipeline stages + enabled = true; # Enable source (default: true) + } + ]; + + defaultLabels = { # Default labels for all sources + hostname = "node-01"; + environment = "production"; + location = "homelab"; + }; + + # Read-only aggregated data + allSources = [...]; # All sources from this node + global = { # Global aggregation + allSources = [...]; # All sources from entire fleet + sourcesByType = {...}; # Grouped by type + summary = { + total = 25; + byType = {...}; + byNode = {...}; + }; + }; +}; +``` + +### Backup System (`homelab.backups.*`) + +```nix +homelab.backups = { + enable = true; # Enable backup system + + # Backup jobs + jobs = [ + { + name = "database-backup"; # Job name + backend = "restic-s3"; # Backend name (must exist in backends) + backendOptions = { # Backend-specific overrides + repository = "custom-repo"; + }; + labels = { # Additional labels + type = "database"; + }; + } + ]; + + # Backend configurations (defined by imported modules) + backends = { + restic-s3 = {...}; # Defined in restic.nix + }; + + defaultLabels = { # Default labels for all jobs + hostname = "node-01"; + environment = "production"; + location = "homelab"; + }; + + monitoring = true; # Enable backup monitoring (default: true) + + # Read-only aggregated data + allJobs = [...]; # All jobs from this node + allBackends = [...]; # All backend names from this node + global = { # Global aggregation + allJobs = [...]; # All jobs from entire fleet + allBackends = [...]; # All backends from entire fleet + jobsByBackend = {...}; # Grouped by backend + summary = { + total = 15; + byBackend = {...}; + byNode = {...}; + uniqueBackends = ["restic-s3" "borgbackup"]; + }; + }; +}; +``` + +### Reverse Proxy System (`homelab.reverseProxy.*`) + +```nix +homelab.reverseProxy = { + enable = true; # Enable reverse proxy system + + # Proxy entries + entries = [ + { + subdomain = "app"; # Subdomain + host = "localhost"; # Backend host (default: homelab.hostname) + port = 8080; # Backend port + path = "/"; # Backend path (default: "/") + enableAuth = false; # Enable authentication (default: false) + enableSSL = true; # Enable SSL (default: true) + } + ]; + + # Read-only aggregated data + allEntries = [...]; # All entries from this node + global = { # Global aggregation + allEntries = [...]; # All entries from entire fleet + entriesBySubdomain = {...}; # Grouped by subdomain + entriesWithAuth = [...]; # Entries with authentication + entriesWithoutAuth = [...]; # Entries without authentication + summary = { + total = 12; + byNode = {...}; + withAuth = 5; + withoutAuth = 7; + }; + }; +}; +``` + +## Service Configuration Pattern + +All services follow a consistent pattern with automatic monitoring, logging, and proxy integration. + +### Generic Service Structure (`homelab.services.${serviceName}.*`) + +```nix +homelab.services.myservice = { + enable = true; # Enable the service + port = 8080; # Main service port + description = "My Service"; # Service description + + # Monitoring integration (automatic when enabled) + monitoring = { + enable = true; # Enable monitoring (default: true when service enabled) + + metrics = { + enable = true; # Enable metrics endpoint (default: true) + path = "/metrics"; # Metrics path (default: "/metrics") + extraEndpoints = [ # Additional metric endpoints + { + name = "admin-metrics"; + port = 8081; + path = "/admin/metrics"; + jobName = "myservice-admin"; + } + ]; + }; + + healthCheck = { + enable = true; # Enable health check (default: true) + path = "/health"; # Health check path (default: "/health") + conditions = [ # Check conditions + "[STATUS] == 200" + ]; + extraChecks = [ # Additional health checks + { + name = "myservice-api"; + port = 8080; + path = "/api/health"; + conditions = ["[STATUS] == 200" "[RESPONSE_TIME] < 500"]; + } + ]; + }; + + extraLabels = { # Additional labels for all monitoring + tier = "application"; + }; + }; + + # Logging integration (automatic when enabled) + logging = { + enable = true; # Enable logging + files = [ # Log files to collect + "/var/log/myservice/app.log" + "/var/log/myservice/error.log" + ]; + + parsing = { + regex = "^(?P\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}) (?P\\w+) (?P.*)"; + extractFields = ["level"]; # Fields to extract as labels + }; + + multiline = { # Multiline log handling + firstLineRegex = "^\\d{4}-\\d{2}-\\d{2}"; + maxWaitTime = "3s"; + }; + + extraLabels = { # Additional labels + application = "myservice"; + }; + + extraSources = [ # Additional log sources + { + name = "myservice-access"; + type = "file"; + files.paths = ["/var/log/myservice/access.log"]; + } + ]; + }; + + # Reverse proxy integration (automatic when enabled) + proxy = { + enable = true; # Enable reverse proxy + subdomain = "myservice"; # Subdomain (default: service name) + enableAuth = false; # Enable authentication (default: false) + + additionalSubdomains = [ # Additional proxy entries + { + subdomain = "myservice-api"; + port = 8081; + path = "/api"; + enableAuth = true; + } + ]; + }; + + # Service-specific options + customOption = "value"; # Service-specific configuration +}; +``` + +## Example Service Implementations + +### Prometheus Service + +```nix +homelab.services.prometheus = { + enable = true; + port = 9090; + + # Prometheus-specific options + retention = "15d"; # Data retention period + alertmanager = { + enable = true; + url = "alertmanager.lab:9093"; + }; + extraScrapeConfigs = []; # Additional scrape configs + extraAlertingRules = []; # Additional alerting rules + globalConfig = { # Prometheus global config + scrape_interval = "15s"; + evaluation_interval = "15s"; + }; + extraFlags = []; # Additional command line flags + ruleFiles = []; # Additional rule files + + # Automatic integrations + monitoring.enable = true; # Self-monitoring + logging.enable = true; # Log collection + proxy = { + enable = true; + subdomain = "prometheus"; + enableAuth = true; # Admin interface needs protection + }; +}; +``` + +### Gatus Service + +```nix +homelab.services.gatus = { + enable = true; + port = 8080; + + # Gatus-specific options + ui = { + title = "Homelab Status"; + header = "Homelab Services Status"; + link = "https://status.procopius.dk"; + buttons = [ + { name = "Grafana"; link = "https://grafana.procopius.dk"; } + { name = "Prometheus"; link = "https://prometheus.procopius.dk"; } + ]; + }; + + alerting = { # Discord/Slack/etc notifications + discord = { + webhook-url = "https://discord.com/api/webhooks/..."; + default-alert = { + enabled = true; + failure-threshold = 3; + success-threshold = 2; + }; + }; + }; + + storage = { # Storage backend + type = "memory"; # or "postgres", "sqlite" + }; + + web.address = "0.0.0.0"; + extraConfig = {}; # Additional Gatus configuration + + # Automatic integrations + monitoring.enable = true; + logging.enable = true; + proxy = { + enable = true; + subdomain = "status"; + enableAuth = false; # Status page should be public + }; +}; +``` + +## Global Aggregation System + +The homelab system automatically aggregates configuration from all nodes in your fleet, making it easy to have centralized monitoring and management. + +### How Global Aggregation Works + +1. **Local Configuration**: Each node defines its own services and configurations +2. **Automatic Collection**: The system automatically collects data from all nodes using the `base.nix` aggregator +3. **Enhancement**: Each collected item is enhanced with node context (`_nodeName`, `_nodeConfig`, etc.) +4. **Global Exposure**: Aggregated data is exposed in `*.global.*` options + +### Global Data Structure + +```nix +# Available on every node with global data from entire fleet +homelab.monitoring.global = { + allMetrics = [ # All metrics from all nodes + { + name = "prometheus-main"; + host = "monitor"; + port = 9090; + # ... other fields + _nodeName = "monitor"; # Source node name + _nodeConfig = {...}; # Source node config + _fullAddress = "monitor:9090"; + _metricsUrl = "http://monitor:9090/metrics"; + } + # ... more metrics from other nodes + ]; + + allHealthChecks = [...]; # All health checks from all nodes + metricsByJobName = { # Grouped by Prometheus job name + "prometheus" = [...]; + "node" = [...]; + }; + healthChecksByGroup = { # Grouped by health check group + "services" = [...]; + "infrastructure" = [...]; + }; + summary = { + totalMetrics = 42; + totalHealthChecks = 15; + nodesCovered = ["monitor" "web-01" "db-01"]; + }; +}; + +homelab.logging.global = { + allSources = [...]; # All log sources from all nodes + sourcesByType = { + "file" = [...]; + "journal" = [...]; + }; + summary = {...}; +}; + +homelab.backups.global = { + allJobs = [...]; # All backup jobs from all nodes + allBackends = [...]; # All backup backends from all nodes + jobsByBackend = {...}; + summary = {...}; +}; + +homelab.reverseProxy.global = { + allEntries = [...]; # All proxy entries from all nodes + entriesBySubdomain = {...}; + entriesWithAuth = [...]; + entriesWithoutAuth = [...]; + summary = {...}; +}; +``` + +### Using Global Data + +Services like Prometheus and Gatus automatically use global data: + +```nix +# Prometheus automatically scrapes ALL metrics from the entire fleet +services.prometheus.scrapeConfigs = + # Automatically generated from homelab.monitoring.global.allMetrics + +# Gatus automatically monitors ALL health checks from the entire fleet +services.gatus.settings.endpoints = + # Automatically generated from homelab.monitoring.global.allHealthChecks +``` + +## Integration Examples + +### Adding a New Service + +1. **Create the service configuration**: +```nix +homelab.services.myapp = { + enable = true; + port = 3000; + monitoring.enable = true; + logging.enable = true; + proxy = { + enable = true; + subdomain = "myapp"; + }; +}; +``` + +2. **The system automatically**: + - Adds metrics endpoint to Prometheus (fleet-wide) + - Adds health check to Gatus (fleet-wide) + - Configures log collection to Loki + - Sets up reverse proxy entry + - Exposes the service globally for other nodes + +### Multi-Node Setup + +```nix +# Node 1 (monitor.nix) +homelab = { + hostname = "monitor"; + services.prometheus.enable = true; + services.gatus.enable = true; +}; + +# Node 2 (web.nix) +homelab = { + hostname = "web-01"; + services.nginx.enable = true; + services.webapp.enable = true; +}; + +# Node 3 (database.nix) +homelab = { + hostname = "db-01"; + services.postgresql.enable = true; + services.redis.enable = true; +}; +``` + +Result: Monitor node automatically discovers and monitors all services across all three nodes. + +## File Structure + +``` +homelab/ +β”œβ”€β”€ default.nix # Main homelab options and imports +β”œβ”€β”€ lib/ +β”‚ β”œβ”€β”€ systems/ # Core system modules +β”‚ β”‚ β”œβ”€β”€ monitoring.nix # Monitoring aggregation +β”‚ β”‚ β”œβ”€β”€ logging.nix # Logging aggregation +β”‚ β”‚ β”œβ”€β”€ backups.nix # Backup aggregation +β”‚ β”‚ └── proxy.nix # Reverse proxy aggregation +β”‚ β”œβ”€β”€ features/ # Service feature modules +β”‚ β”‚ β”œβ”€β”€ monitoring.nix # Service monitoring template +β”‚ β”‚ β”œβ”€β”€ logging.nix # Service logging template +β”‚ β”‚ └── proxy.nix # Service proxy template +β”‚ └── aggregators/ +β”‚ └── base.nix # Base aggregation functions +└── services/ # Individual service implementations + β”œβ”€β”€ prometheus.nix + β”œβ”€β”€ gatus.nix + └── ... +``` + +This architecture provides a scalable, consistent way to manage a homelab fleet with automatic service discovery, monitoring, and management across all nodes. diff --git a/modules/homelab/default.nix b/modules/homelab/default.nix index ce19d59..28d103b 100644 --- a/modules/homelab/default.nix +++ b/modules/homelab/default.nix @@ -6,8 +6,6 @@ }: with lib; let cfg = config.homelab; - - nodeAgg = import ./lib/node-aggregation.nix {inherit lib;}; in { imports = [ ./lib/systems/monitoring.nix @@ -15,6 +13,8 @@ in { ./lib/systems/proxy.nix ./lib/systems/backups.nix + ./lib/cli/homelab-cli.nix + ./services ./motd ]; diff --git a/modules/homelab/lib/cli/cli-commands.sh b/modules/homelab/lib/cli/cli-commands.sh new file mode 100644 index 0000000..18e1638 --- /dev/null +++ b/modules/homelab/lib/cli/cli-commands.sh @@ -0,0 +1,943 @@ +#!/usr/bin/env bash +# CLI command implementations + +# Services command +# Enhanced services command with detailed service information +cmd_services() { + local SCOPE="local" + local FORMAT="table" + local SHOW_SYSTEMD=true + local DETAIL_SERVICE="" + local ACTION="" + + while [[ $# -gt 0 ]]; do + case $1 in + --global|-g) SCOPE="global"; shift ;; + --local|-l) SCOPE="local"; shift ;; + --json) FORMAT="json"; shift ;; + --no-systemd) SHOW_SYSTEMD=false; shift ;; + --detail|-d) + DETAIL_SERVICE="$2" + shift 2 + ;; + --logs) + ACTION="logs" + DETAIL_SERVICE="$2" + shift 2 + ;; + --status) + ACTION="status" + DETAIL_SERVICE="$2" + shift 2 + ;; + --restart) + ACTION="restart" + DETAIL_SERVICE="$2" + shift 2 + ;; + --errors) + ACTION="errors" + DETAIL_SERVICE="$2" + shift 2 + ;; + --help|-h) + cat << 'EOF' +homelab services - List and manage services + +USAGE: + homelab services [options] + homelab services --detail + homelab services --logs + homelab services --status + homelab services --restart + homelab services --errors + +OPTIONS: + --global, -g Show services from entire fleet + --local, -l Show local services (default) + --json Output JSON format + --no-systemd Don't check systemd status + --detail, -d Show detailed info for service + --logs Show recent logs for service + --status Show detailed status for service + --restart Restart service + --errors Show recent errors for service + +EXAMPLES: + homelab services + homelab services --global + homelab services --detail prometheus + homelab services --logs grafana + homelab services --errors nginx + homelab services --restart prometheus +EOF + return 0 + ;; + *) shift ;; + esac + done + + # Handle specific service actions + if [[ -n "$DETAIL_SERVICE" ]]; then + case "$ACTION" in + logs) + show_service_logs "$DETAIL_SERVICE" + return $? + ;; + status) + show_service_status "$DETAIL_SERVICE" + return $? + ;; + restart) + restart_service "$DETAIL_SERVICE" + return $? + ;; + errors) + show_service_errors "$DETAIL_SERVICE" + return $? + ;; + *) + show_service_detail "$DETAIL_SERVICE" + return $? + ;; + esac + fi + + # Regular service listing + if [[ "$FORMAT" == "json" ]]; then + if [[ "$SCOPE" == "global" ]]; then + jq -r '.services.global // {}' "$HOMELAB_CONFIG" + else + jq -r '.services.local // {}' "$HOMELAB_CONFIG" + fi + else + info "Homelab Services ($SCOPE)" + echo "==============================" + echo + + services_data=$(jq -r " + if \"$SCOPE\" == \"global\" then .services.global.all // [] + else .services.local.all // [] + end | + .[] | + [.name, (.node // \"local\"), (.port // \"N/A\"), (.description // \"\")] | + @tsv + " "$HOMELAB_CONFIG" 2>/dev/null || echo "") + + if [[ -z "$services_data" ]]; then + warn "No services found" + return 0 + fi + + printf "%-20s %-12s %-8s %-12s %-8s %s\n" "SERVICE" "NODE" "PORT" "SYSTEMD" "UPTIME" "DESCRIPTION" + printf "%-20s %-12s %-8s %-12s %-8s %s\n" "-------" "----" "----" "-------" "------" "-----------" + + while IFS=$'\t' read -r service node port description; do + systemd_status="N/A" + uptime="N/A" + + if [[ "$SHOW_SYSTEMD" == "true" && "$node" == "local" ]]; then + # Get systemd service names for this service + systemd_services=($(jq -r ".services.local.all[] | select(.name == \"$service\") | .systemdServices[]?" "$HOMELAB_CONFIG" 2>/dev/null)) + + if [[ ${#systemd_services[@]} -eq 0 ]]; then + # Fallback to common patterns + systemd_services=("$service" "$service.service") + fi + + for svc_name in "${systemd_services[@]}"; do + if systemctl is-enabled "$svc_name" >/dev/null 2>&1; then + if systemctl is-active "$svc_name" >/dev/null 2>&1; then + systemd_status="${GREEN}active${NC}" + + # Get uptime + active_since=$(systemctl show -p ActiveEnterTimestamp "$svc_name" --value 2>/dev/null) + if [[ -n "$active_since" && "$active_since" != "n/a" ]]; then + active_epoch=$(date -d "$active_since" +%s 2>/dev/null || echo 0) + current_epoch=$(date +%s) + if [[ "$active_epoch" -gt 0 ]]; then + uptime_seconds=$((current_epoch - active_epoch)) + uptime=$(format_duration $uptime_seconds) + fi + fi + else + systemd_status="${RED}inactive${NC}" + uptime="0s" + fi + break + fi + done + fi + + printf "%-20s %-12s %-8s %-12b %-8s %s\n" "$service" "$node" "$port" "$systemd_status" "$uptime" "$description" + done <<< "$services_data" + + echo + service_count=$(echo "$services_data" | wc -l) + success "Total services: $service_count" + + echo + info "πŸ’‘ Use 'homelab services --detail ' for detailed information" + info "πŸ’‘ Use 'homelab services --logs ' to view logs" + fi +} + +# Helper function to format duration +format_duration() { + local seconds=$1 + local days=$((seconds / 86400)) + local hours=$(((seconds % 86400) / 3600)) + local minutes=$(((seconds % 3600) / 60)) + local secs=$((seconds % 60)) + + if [[ $days -gt 0 ]]; then + echo "${days}d ${hours}h" + elif [[ $hours -gt 0 ]]; then + echo "${hours}h ${minutes}m" + elif [[ $minutes -gt 0 ]]; then + echo "${minutes}m" + else + echo "${secs}s" + fi +} + +# Robust service detection function +find_systemd_service() { + local service_name="$1" + + # Get configured systemd services from homelab config + local systemd_services=($(jq -r ".services.local.all[] | select(.name == \"$service_name\") | .systemdServices[]?" "$HOMELAB_CONFIG" 2>/dev/null)) + + # If no configured services, use common patterns + if [[ ${#systemd_services[@]} -eq 0 ]]; then + systemd_services=( + "$service_name.service" + "$service_name" + "nixos-$service_name.service" + "nixos-$service_name" + "$service_name-nixos.service" + ) + fi + + # Try each potential service name with multiple detection methods + for svc_name in "${systemd_services[@]}"; do + # Method 1: Check if systemctl can show the unit (most reliable) + if systemctl show "$svc_name" >/dev/null 2>&1; then + echo "$svc_name" + return 0 + fi + + # Method 2: Check if unit file exists + if systemctl list-unit-files --no-pager --no-legend "$svc_name" 2>/dev/null | grep -q "^$svc_name"; then + echo "$svc_name" + return 0 + fi + + # Method 3: Check if unit is loaded + if systemctl list-units --no-pager --no-legend "$svc_name" 2>/dev/null | grep -q "^$svc_name"; then + echo "$svc_name" + return 0 + fi + done + + # If still not found, try a broader search + local found_service=$(systemctl list-units --all --no-pager --no-legend | grep -E "^$service_name[.-]|^$service_name\.service" | head -1 | awk '{print $1}') + if [[ -n "$found_service" ]]; then + echo "$found_service" + return 0 + fi + + # Last resort: check unit files + found_service=$(systemctl list-unit-files --no-pager --no-legend | grep -E "^$service_name[.-]|^$service_name\.service" | head -1 | awk '{print $1}') + if [[ -n "$found_service" ]]; then + echo "$found_service" + return 0 + fi + + return 1 +} + +show_service_logs() { + local service_name="$1" + local lines="${2:-100}" + local follow="${3:-false}" + + if [[ -z "$service_name" ]]; then + error "Service name required" + return 1 + fi + + # Use robust service detection + local found_service=$(find_systemd_service "$service_name") + + if [[ -z "$found_service" ]]; then + error "No systemd service found for '$service_name'" + echo + info "πŸ’‘ Available services containing '$service_name':" + systemctl list-units --all --no-pager --no-legend | grep -i "$service_name" | awk '{print " " $1}' || echo " None found" + return 1 + fi + + info "πŸ“ Logs for $service_name ($found_service)" + echo "==================================" + echo + + local journalctl_args="-u $found_service -n $lines --no-pager" + if [[ "$follow" == "true" ]]; then + journalctl_args="$journalctl_args -f" + info "Following logs (Press Ctrl+C to stop)..." + echo + fi + + journalctl $journalctl_args +} + +show_service_detail() { + local service_name="$1" + + if [[ -z "$service_name" ]]; then + error "Service name required" + return 1 + fi + + # Get service info from config + local service_info=$(jq -r ".services.local.all[] | select(.name == \"$service_name\")" "$HOMELAB_CONFIG" 2>/dev/null) + + if [[ -z "$service_info" ]]; then + error "Service '$service_name' not found in homelab configuration" + return 1 + fi + + info "πŸ” Service Details: $service_name" + echo "=================================" + echo + + # Basic info + local port=$(echo "$service_info" | jq -r '.port // "N/A"') + local description=$(echo "$service_info" | jq -r '.description // "N/A"') + local tags=$(echo "$service_info" | jq -r '.tags[]? // empty' | tr '\n' ',' | sed 's/,$//') + + echo "πŸ“‹ Configuration:" + echo " Port: $port" + echo " Description: $description" + echo " Tags: ${tags:-"None"}" + echo + + # Use robust service detection + local found_service=$(find_systemd_service "$service_name") + + echo "πŸ”§ Systemd Status:" + if [[ -n "$found_service" ]]; then + echo " Service: $found_service" + echo " Status: $(systemctl is-active "$found_service" 2>/dev/null || echo "unknown")" + echo " Enabled: $(systemctl is-enabled "$found_service" 2>/dev/null || echo "unknown")" + + # Detailed status + local active_since=$(systemctl show -p ActiveEnterTimestamp "$found_service" --value 2>/dev/null) + if [[ -n "$active_since" && "$active_since" != "n/a" ]]; then + echo " Active since: $active_since" + fi + + local main_pid=$(systemctl show -p MainPID "$found_service" --value 2>/dev/null) + if [[ -n "$main_pid" && "$main_pid" != "0" ]]; then + echo " Main PID: $main_pid" + + # Memory usage + local memory_usage=$(systemctl show -p MemoryCurrent "$found_service" --value 2>/dev/null) + if [[ -n "$memory_usage" && "$memory_usage" != "[not set]" && "$memory_usage" -gt 0 ]]; then + local memory_mb=$((memory_usage / 1024 / 1024)) + echo " Memory: ${memory_mb}MB" + fi + fi + echo + + # Recent logs preview + echo "πŸ“ Recent Logs (last 10 lines):" + echo "--------------------------------" + journalctl -u "$found_service" -n 10 --no-pager --output=short 2>/dev/null || echo "No logs available" + echo + + # Check for recent errors + local error_count=$(journalctl -u "$found_service" --since "24 hours ago" --no-pager -q 2>/dev/null | grep -i "error\|failed\|exception" | wc -l) + if [[ "$error_count" -gt 0 ]]; then + warn "⚠️ Found $error_count error(s) in last 24 hours" + echo " Use 'homelab services --errors $service_name' to view them" + else + success "βœ… No errors found in last 24 hours" + fi + echo + + info "πŸ“Š Available Actions:" + echo " homelab services --logs $service_name # View full logs" + echo " homelab services --errors $service_name # View recent errors" + echo " homelab services --restart $service_name # Restart service" + + else + warn "No systemd service found for '$service_name'" + echo + info "πŸ’‘ Available services containing '$service_name':" + systemctl list-units --all --no-pager --no-legend | grep -i "$service_name" | awk '{print " " $1}' || echo " None found" + fi +} + +show_service_errors() { + local service_name="$1" + local since="${2:-24 hours ago}" + + if [[ -z "$service_name" ]]; then + error "Service name required" + return 1 + fi + + # Use robust service detection + local found_service=$(find_systemd_service "$service_name") + + if [[ -z "$found_service" ]]; then + error "No systemd service found for '$service_name'" + return 1 + fi + + info "🚨 Errors for $service_name ($found_service) since $since" + echo "==============================================" + echo + + # Get logs with priority filtering for errors and higher + local systemd_errors=$(journalctl -u "$found_service" --since "$since" --no-pager -p err 2>/dev/null) + + # Also get application-level errors from all logs but with better filtering + local app_errors=$(journalctl -u "$found_service" --since "$since" --no-pager 2>/dev/null | \ + grep -E "(ERROR|FATAL|CRITICAL|Exception|Traceback|failed to|cannot|unable to|connection refused|timeout|denied)" | \ + grep -v -E "(debug|DEBUG|info|INFO|warn|WARNING|notice|NOTICE)" | \ + grep -v -E "(successfully|completed|started|stopped|reloaded)") + + local has_errors=false + + # Show systemd-level errors (priority err and above) + if [[ -n "$systemd_errors" ]]; then + warn "πŸ“ System-level errors (priority: err/crit/alert/emerg):" + echo "────────────────────────────────────────────────────────" + echo "$systemd_errors" + echo + has_errors=true + fi + + # Show application-level errors + if [[ -n "$app_errors" ]]; then + warn "πŸ“ Application-level errors:" + echo "─────────────────────────────" + echo "$app_errors" + echo + has_errors=true + fi + + # Check for service failures/restarts + local service_failures=$(journalctl -u "$found_service" --since "$since" --no-pager 2>/dev/null | \ + grep -E "(Failed|failed|Stopped|stopped|Restarted|restarted|Exit code|exit code)" | \ + grep -v -E "(successfully|gracefully)") + + if [[ -n "$service_failures" ]]; then + warn "πŸ“ Service state changes/failures:" + echo "───────────────────────────────────" + echo "$service_failures" + echo + has_errors=true + fi + + if [[ "$has_errors" == "false" ]]; then + success "βœ… No errors found since $since" + echo + info "πŸ’‘ Error detection includes:" + echo " β€’ System-level errors (journald priority: err/crit/alert/emerg)" + echo " β€’ Application errors (ERROR, FATAL, CRITICAL, Exception, etc.)" + echo " β€’ Service failures and unexpected restarts" + else + echo + local total_systemd=$(echo "$systemd_errors" | grep -c . || echo 0) + local total_app=$(echo "$app_errors" | grep -c . || echo 0) + local total_failures=$(echo "$service_failures" | grep -c . || echo 0) + + warn "Summary: $total_systemd system errors, $total_app application errors, $total_failures service issues" + echo + info "πŸ’‘ Use 'homelab services --logs $service_name' to view all logs" + fi +} + +show_service_status() { + local service_name="$1" + + if [[ -z "$service_name" ]]; then + error "Service name required" + return 1 + fi + + # Use robust service detection + local found_service=$(find_systemd_service "$service_name") + + if [[ -z "$found_service" ]]; then + error "No systemd service found for '$service_name'" + return 1 + fi + + info "πŸ“Š Status for $service_name ($found_service)" + echo "==================================" + echo + + systemctl status "$found_service" --no-pager -l +} + +restart_service() { + local service_name="$1" + + if [[ -z "$service_name" ]]; then + error "Service name required" + return 1 + fi + + # Use robust service detection + local found_service=$(find_systemd_service "$service_name") + + if [[ -z "$found_service" ]]; then + error "No systemd service found for '$service_name'" + return 1 + fi + + info "πŸ”„ Restarting $service_name ($found_service)..." + + if sudo systemctl restart "$found_service"; then + success "βœ… Successfully restarted $service_name" + + # Show brief status + sleep 2 + if systemctl is-active "$found_service" >/dev/null 2>&1; then + success "βœ… Service is now active" + else + error "❌ Service failed to start properly" + warn "Use 'homelab services --status $service_name' to check details" + fi + else + error "❌ Failed to restart $service_name" + return 1 + fi +} + +# Backups command +cmd_backups() { + local SCOPE="local" + local FORMAT="table" + local SHOW_STATUS=true + + while [[ $# -gt 0 ]]; do + case $1 in + --global|-g) SCOPE="global"; shift ;; + --local|-l) SCOPE="local"; shift ;; + --json) FORMAT="json"; shift ;; + --no-status) SHOW_STATUS=false; shift ;; + --help|-h) + cat << 'EOF' +homelab backups - Show backup jobs and status + +USAGE: + homelab backups [options] + +OPTIONS: + --global, -g Show backups from entire fleet + --local, -l Show local backups (default) + --json Output JSON format + --no-status Don't check systemd timer status + +EXAMPLES: + homelab backups + homelab backups --global + homelab backups --no-status +EOF + return 0 + ;; + *) shift ;; + esac + done + + if [[ "$FORMAT" == "json" ]]; then + if [[ "$SCOPE" == "global" ]]; then + jq -r '.backups.global // {}' "$HOMELAB_CONFIG" + else + jq -r '.backups.local // {}' "$HOMELAB_CONFIG" + fi + else + info "Homelab Backups ($SCOPE)" + echo "==========================" + echo + + backup_data=$(jq -r " + if \"$SCOPE\" == \"global\" then .backups.global.allJobs // [] + else .backups.local.allJobs // [] + end | + .[] | + [.name, (.sourceNode // .node // \"local\"), .backend, (.labels | to_entries | map(\"\(.key)=\(.value)\") | join(\",\"))] | + @tsv + " "$HOMELAB_CONFIG" 2>/dev/null || echo "") + + if [[ -z "$backup_data" ]]; then + warn "No backup jobs found" + return 0 + fi + + printf "%-25s %-12s %-8s %-15s %-15s %-15s %s\n" "JOB" "NODE" "STATUS" "BACKEND" "LAST RUN" "NEXT RUN" "LABELS" + printf "%-25s %-12s %-8s %-15s %-15s %-15s %s\n" "---" "----" "------" "-------" "--------" "--------" "------" + + while IFS=$'\t' read -r job node backend labels; do + last_run="Unknown" + status="❓" + next_run="Unknown" + + if [[ "$SHOW_STATUS" == "true" && "$node" == "local" ]]; then + timer_patterns=( + "backup-$job" + "$job-backup" + "restic-backups-$job" + "restic-backup-$job" + "$job.timer" + "backup-$job.timer" + ) + + found_timer="" + actual_timer_name="" + for pattern in "${timer_patterns[@]}"; do + for timer_name in "$pattern" "$pattern.timer"; do + if systemctl list-timers --no-pager --no-legend "$timer_name" 2>/dev/null | grep -q "$timer_name"; then + found_timer="$timer_name" + if [[ "$timer_name" == *.timer ]]; then + actual_timer_name="$timer_name" + else + actual_timer_name="$timer_name.timer" + fi + break 2 + fi + done + done + + if [[ -n "$found_timer" ]]; then + last_trigger=$(systemctl show -p LastTriggerUSec "$actual_timer_name" --value 2>/dev/null) + if [[ "$last_trigger" != "n/a" && -n "$last_trigger" && "$last_trigger" != "Thu 1970-01-01"* ]]; then + last_run=$(date -d "$last_trigger" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "Parse Error") + + last_epoch=$(date -d "$last_trigger" +%s 2>/dev/null || echo 0) + current_epoch=$(date +%s) + if [[ "$last_epoch" != "0" && "$last_epoch" -gt 0 ]]; then + hours_since=$(( (current_epoch - last_epoch) / 3600 )) + + if [[ $hours_since -lt 25 ]]; then + status="βœ…" + elif [[ $hours_since -lt 48 ]]; then + status="⚠️" + else + status="❌" + fi + else + status="❓" + fi + else + last_run="Never" + status="⏸️" + fi + + next_trigger=$(systemctl show -p NextElapseUSecRealtime "$actual_timer_name" --value 2>/dev/null) + if [[ "$next_trigger" != "n/a" && -n "$next_trigger" && "$next_trigger" != "0" ]]; then + next_run=$(date -d "$next_trigger" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "Parse Error") + else + next_run="Unknown" + fi + + if [[ "$status" == "βœ…" ]]; then + if journalctl -u "$actual_timer_name" --since "24 hours ago" --no-pager -q 2>/dev/null | grep -qi "error\|failed\|timeout"; then + status="❌" + elif journalctl -u "$actual_timer_name" --since "24 hours ago" --no-pager -q 2>/dev/null | grep -qi "success\|completed\|finished"; then + status="βœ…" + fi + fi + fi + fi + + printf "%-25s %-12s %-8s %-15s %-15s %-15s %s\n" "$job" "$node" "$status" "$backend" "$last_run" "$next_run" "$labels" + done <<< "$backup_data" + + echo + job_count=$(echo "$backup_data" | wc -l) + success "Total backup jobs: $job_count" + + if [[ "$SHOW_STATUS" == "true" ]]; then + echo + info "Status: βœ…=Recent(<25h) ⚠️=Overdue(1-2d) ❌=Failed(>2d) ⏸️=Never ❓=Unknown" + fi + fi +} + + +# Proxy command +cmd_proxy() { + local SCOPE="local" + local FORMAT="table" + + while [[ $# -gt 0 ]]; do + case $1 in + --global|-g) SCOPE="global"; shift ;; + --local|-l) SCOPE="local"; shift ;; + --json) FORMAT="json"; shift ;; + --help|-h) + cat << 'EOF' +homelab proxy - Show reverse proxy entries + +USAGE: + homelab proxy [options] + +OPTIONS: + --global, -g Show proxy entries from entire fleet + --local, -l Show local proxy entries (default) + --json Output JSON format + +EXAMPLES: + homelab proxy + homelab proxy --global +EOF + return 0 + ;; + *) shift ;; + esac + done + + if [[ "$FORMAT" == "json" ]]; then + if [[ "$SCOPE" == "global" ]]; then + jq -r '.reverseProxy.global // {}' "$HOMELAB_CONFIG" + else + jq -r '.reverseProxy.local // {}' "$HOMELAB_CONFIG" + fi + else + info "Homelab Reverse Proxy ($SCOPE)" + echo "===============================" + echo + + proxy_data=$(jq -r " + if \"$SCOPE\" == \"global\" then .reverseProxy.global.allEntries // [] + else .reverseProxy.local.allEntries // [] + end | + .[] | + [.subdomain, (.sourceNode // .node // \"local\"), .host, (.port // \"N/A\"), (.enableAuth // false), (.enableSSL // true)] | + @tsv + " "$HOMELAB_CONFIG" 2>/dev/null || echo "") + + if [[ -z "$proxy_data" ]]; then + warn "No proxy entries found" + return 0 + fi + + printf "%-20s %-12s %-15s %-8s %-6s %-6s %s\n" "SUBDOMAIN" "NODE" "BACKEND" "PORT" "AUTH" "SSL" "EXTERNAL URL" + printf "%-20s %-12s %-15s %-8s %-6s %-6s %s\n" "---------" "----" "-------" "----" "----" "---" "------------" + + external_domain=$(jq -r '.externalDomain // "lab.local"' "$HOMELAB_CONFIG" 2>/dev/null) + + while IFS=$'\t' read -r subdomain node host port auth ssl; do + auth_icon=$(if [[ "$auth" == "true" ]]; then echo "πŸ”’"; else echo "🌐"; fi) + ssl_icon=$(if [[ "$ssl" == "true" ]]; then echo "πŸ”"; else echo "❌"; fi) + + external_url="https://$subdomain.$external_domain" + if [[ "$ssl" == "false" ]]; then + external_url="http://$subdomain.$external_domain" + fi + + printf "%-20s %-12s %-15s %-8s %-6s %-6s %s\n" "$subdomain" "$node" "$host" "$port" "$auth_icon" "$ssl_icon" "$external_url" + done <<< "$proxy_data" + + echo + entry_count=$(echo "$proxy_data" | wc -l) + success "Total proxy entries: $entry_count" + fi +} + +# Monitoring command +cmd_monitoring() { + local SCOPE="local" + local FORMAT="table" + local SHOW_TYPE="all" + + while [[ $# -gt 0 ]]; do + case $1 in + --global|-g) SCOPE="global"; shift ;; + --local|-l) SCOPE="local"; shift ;; + --json) FORMAT="json"; shift ;; + --metrics) SHOW_TYPE="metrics"; shift ;; + --health) SHOW_TYPE="health"; shift ;; + --logs) SHOW_TYPE="logs"; shift ;; + --help|-h) + cat << 'EOF' +homelab monitoring - Show monitoring configuration + +USAGE: + homelab monitoring [options] + +OPTIONS: + --global, -g Show monitoring from entire fleet + --local, -l Show local monitoring (default) + --json Output JSON format + --metrics Show only metrics endpoints + --health Show only health checks + --logs Show only log sources + +EXAMPLES: + homelab monitoring + homelab monitoring --global --metrics +EOF + return 0 + ;; + *) shift ;; + esac + done + + if [[ "$FORMAT" == "json" ]]; then + if [[ "$SCOPE" == "global" ]]; then + jq -r '.monitoring.global // {}' "$HOMELAB_CONFIG" + else + jq -r '.monitoring.local // {}' "$HOMELAB_CONFIG" + fi + else + info "Homelab Monitoring ($SCOPE)" + echo "============================" + echo + + # Show metrics + if [[ "$SHOW_TYPE" == "all" || "$SHOW_TYPE" == "metrics" ]]; then + info "πŸ“Š Metrics Endpoints" + echo "--------------------" + + metrics_data=$(jq -r " + if \"$SCOPE\" == \"global\" then .monitoring.global.allMetrics // [] + else .monitoring.local.allMetrics // [] + end | + .[] | + [.name, (.sourceNode // .node // \"local\"), .host, (.port // \"N/A\"), .path, .jobName] | + @tsv + " "$HOMELAB_CONFIG" 2>/dev/null || echo "") + + if [[ -n "$metrics_data" ]]; then + printf "%-20s %-12s %-15s %-8s %-12s %s\n" "NAME" "NODE" "HOST" "PORT" "PATH" "JOB" + printf "%-20s %-12s %-15s %-8s %-12s %s\n" "----" "----" "----" "----" "----" "---" + + while IFS=$'\t' read -r name node host port path job; do + printf "%-20s %-12s %-15s %-8s %-12s %s\n" "$name" "$node" "$host" "$port" "$path" "$job" + done <<< "$metrics_data" + + echo + metrics_count=$(echo "$metrics_data" | wc -l) + success "Found $metrics_count metrics endpoints" + else + warn "No metrics endpoints found" + fi + echo + fi + + # Show health checks + if [[ "$SHOW_TYPE" == "all" || "$SHOW_TYPE" == "health" ]]; then + info "πŸ₯ Health Checks" + echo "----------------" + + health_data=$(jq -r " + if \"$SCOPE\" == \"global\" then .monitoring.global.allHealthChecks // [] + else .monitoring.local.allHealthChecks // [] + end | + .[] | + [.name, (.sourceNode // .node // \"local\"), .host, (.port // \"N/A\"), .path, .protocol, (.enabled // true)] | + @tsv + " "$HOMELAB_CONFIG" 2>/dev/null || echo "") + + if [[ -n "$health_data" ]]; then + printf "%-20s %-12s %-15s %-8s %-12s %-8s %s\n" "NAME" "NODE" "HOST" "PORT" "PATH" "PROTOCOL" "STATUS" + printf "%-20s %-12s %-15s %-8s %-12s %-8s %s\n" "----" "----" "----" "----" "----" "--------" "------" + + while IFS=$'\t' read -r name node host port path protocol enabled; do + status_icon=$(if [[ "$enabled" == "true" ]]; then echo "βœ…"; else echo "❌"; fi) + printf "%-20s %-12s %-15s %-8s %-12s %-8s %s\n" "$name" "$node" "$host" "$port" "$path" "$protocol" "$status_icon" + done <<< "$health_data" + + echo + health_count=$(echo "$health_data" | wc -l) + success "Found $health_count health checks" + else + warn "No health checks found" + fi + fi + fi +} + +# Status command +cmd_status() { + local FORMAT="table" + + while [[ $# -gt 0 ]]; do + case $1 in + --json) FORMAT="json"; shift ;; + --help|-h) + cat << 'EOF' +homelab status - Show overall homelab status + +USAGE: + homelab status [options] + +OPTIONS: + --json Output JSON format + +EXAMPLES: + homelab status +EOF + return 0 + ;; + *) shift ;; + esac + done + + if [[ "$FORMAT" == "json" ]]; then + cat "$HOMELAB_CONFIG" + else + # Get basic info + hostname=$(jq -r '.hostname // "unknown"' "$HOMELAB_CONFIG") + domain=$(jq -r '.domain // "lab"' "$HOMELAB_CONFIG") + external_domain=$(jq -r '.externalDomain // "unknown"' "$HOMELAB_CONFIG") + environment=$(jq -r '.environment // "unknown"' "$HOMELAB_CONFIG") + + info "🏠 Homelab Status" + echo "==================" + echo + echo "Node Information:" + echo " Hostname: $hostname" + echo " Domain: $domain" + echo " External: $external_domain" + echo " Environment: $environment" + echo + + # Services summary + local_services=$(jq -r '.services.local.count // 0' "$HOMELAB_CONFIG" 2>/dev/null) + global_services=$(jq -r '.services.global.count // 0' "$HOMELAB_CONFIG" 2>/dev/null) + + echo "πŸ“‹ Services:" + echo " Local: $local_services" + echo " Fleet: $global_services" + echo + + # Monitoring summary + local_metrics=$(jq -r '.monitoring.local.count // 0' "$HOMELAB_CONFIG" 2>/dev/null) + global_metrics=$(jq -r '.monitoring.global.summary.totalMetrics // 0' "$HOMELAB_CONFIG" 2>/dev/null) + + echo "πŸ“Š Monitoring:" + echo " Local Metrics: $local_metrics" + echo " Fleet Metrics: $global_metrics" + echo + + # Backup summary + local_backups=$(jq -r '.backups.local.count // 0' "$HOMELAB_CONFIG" 2>/dev/null) + global_backups=$(jq -r '.backups.global.summary.total // 0' "$HOMELAB_CONFIG" 2>/dev/null) + + echo "πŸ’Ύ Backups:" + echo " Local Jobs: $local_backups" + echo " Fleet Jobs: $global_backups" + echo + + success "Use 'homelab --help' for detailed information" + fi +} diff --git a/modules/homelab/lib/cli/homelab-cli.nix b/modules/homelab/lib/cli/homelab-cli.nix new file mode 100644 index 0000000..56b61e8 --- /dev/null +++ b/modules/homelab/lib/cli/homelab-cli.nix @@ -0,0 +1,295 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.homelab; + + extractServiceData = services: + mapAttrsToList (name: svc: { + inherit name; + enabled = svc.enable or false; + port = svc.port or null; + description = svc.description or name; + tags = svc.tags or []; + systemdServices = svc.systemdServices or ["${name}.service" name]; + }) (filterAttrs (name: svc: svc.enable or false) services); + + extractListData = list: + if isList list + then + map ( + item: + if isAttrs item + then + filterAttrs ( + k: v: + !(isFunction v) + && !(isAttrs v && v ? "_type") + && k != "_module" + ) + item + else item + ) + list + else []; + + homelabCli = pkgs.writeShellScriptBin "homelab" '' + #!/usr/bin/env bash + set -euo pipefail + + HOMELAB_CONFIG="/etc/homelab/config.json" + + # Colors + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + NC='\033[0m' + + # Helper functions + error() { echo -e "''${RED}Error: $1''${NC}" >&2; } + info() { echo -e "''${BLUE}$1''${NC}"; } + success() { echo -e "''${GREEN}$1''${NC}"; } + warn() { echo -e "''${YELLOW}$1''${NC}"; } + + # Check config exists + if [[ ! -f "$HOMELAB_CONFIG" ]]; then + error "Homelab configuration not found" + error "Make sure homelab.enable = true and rebuild" + exit 1 + fi + + # Load command implementations + source ${./cli-commands.sh} + + # Help function + show_help() { + cat << 'EOF' + Homelab Management CLI + + USAGE: + homelab [options] + + COMMANDS: + services List and manage services + backups Show backup jobs and status + proxy Show reverse proxy entries + monitoring Show monitoring configuration + status Overall homelab status + help Show this help + + GLOBAL OPTIONS: + --global, -g Show fleet-wide information + --local, -l Show local information (default) + --json Output JSON format + --help, -h Show help + + EXAMPLES: + homelab services --global + homelab backups --local + homelab status + EOF + } + + # Main command dispatcher + case "''${1:-help}" in + services) + shift + cmd_services "$@" + ;; + backups) + shift + cmd_backups "$@" + ;; + proxy) + shift + cmd_proxy "$@" + ;; + monitoring) + shift + cmd_monitoring "$@" + ;; + status) + shift + cmd_status "$@" + ;; + help|--help|-h) + show_help + ;; + *) + error "Unknown command: $1" + show_help + exit 1 + ;; + esac + ''; +in { + # Only enable when homelab is enabled + config = mkIf cfg.enable { + # Install CLI tools + environment.systemPackages = [ + homelabCli + # Create convenient aliases + (pkgs.writeShellScriptBin "hl" "exec homelab \"$@\"") + (pkgs.writeShellScriptBin "hls" "exec homelab services \"$@\"") + (pkgs.writeShellScriptBin "hlb" "exec homelab backups \"$@\"") + (pkgs.writeShellScriptBin "hlp" "exec homelab proxy \"$@\"") + (pkgs.writeShellScriptBin "hlm" "exec homelab monitoring \"$@\"") + ]; + + # Generate minimal, safe JSON config + environment.etc."homelab/config.json" = { + text = builtins.toJSON { + # Basic homelab info (always safe) + hostname = cfg.hostname or "unknown"; + domain = cfg.domain or "lab"; + externalDomain = cfg.externalDomain or "lab.local"; + environment = cfg.environment or "production"; + location = cfg.location or "homelab"; + tags = cfg.tags or []; + + # Services - only extract what we have locally + services = { + local = { + all = + if (cfg ? services) + then extractServiceData cfg.services + else []; + count = + if (cfg ? services) + then length (attrNames (filterAttrs (n: s: s.enable or false) cfg.services)) + else 0; + }; + # For global data, we'll try to read it but provide empty fallback + global = { + all = []; + count = 0; + summary = {}; + }; + }; + + # Monitoring - extract only basic data + monitoring = { + local = { + allMetrics = + if (hasAttr "monitoring" cfg && hasAttr "allMetrics" cfg.monitoring) + then extractListData cfg.monitoring.allMetrics + else []; + allHealthChecks = + if (hasAttr "monitoring" cfg && hasAttr "allHealthChecks" cfg.monitoring) + then extractListData cfg.monitoring.allHealthChecks + else []; + count = + if (hasAttr "monitoring" cfg && hasAttr "allMetrics" cfg.monitoring) + then length cfg.monitoring.allMetrics + else 0; + }; + global = { + allMetrics = []; + allHealthChecks = []; + summary = { + totalMetrics = 0; + totalHealthChecks = 0; + }; + }; + }; + + # Logging + logging = { + local = { + allSources = + if (hasAttr "logging" cfg && hasAttr "allSources" cfg.logging) + then extractListData cfg.logging.allSources + else []; + count = + if (hasAttr "logging" cfg && hasAttr "allSources" cfg.logging) + then length cfg.logging.allSources + else 0; + }; + global = { + allSources = []; + summary = {}; + }; + }; + + # Backups + backups = { + local = { + allJobs = + if (hasAttr "backups" cfg && hasAttr "allJobs" cfg.backups) + then extractListData cfg.backups.allJobs + else []; + count = + if (hasAttr "backups" cfg && hasAttr "allJobs" cfg.backups) + then length cfg.backups.allJobs + else 0; + }; + global = { + allJobs = []; + summary = {}; + }; + }; + + # Reverse Proxy + reverseProxy = { + local = { + allEntries = + if (hasAttr "reverseProxy" cfg && hasAttr "allEntries" cfg.reverseProxy) + then extractListData cfg.reverseProxy.allEntries + else []; + count = + if (hasAttr "reverseProxy" cfg && hasAttr "allEntries" cfg.reverseProxy) + then length cfg.reverseProxy.allEntries + else 0; + }; + global = { + allEntries = []; + summary = {}; + }; + }; + + # Metadata + _metadata = { + # generated = toString builtins.currentTime; + version = "1.0.0"; + }; + }; + mode = "0644"; + }; + + # Add bash completion + environment.etc."bash_completion.d/homelab".text = '' + _homelab_completion() { + local cur prev opts + COMPREPLY=() + cur="''${COMP_WORDS[COMP_CWORD]}" + prev="''${COMP_WORDS[COMP_CWORD-1]}" + + case ''${COMP_CWORD} in + 1) + opts="services backups proxy monitoring status help" + COMPREPLY=( $(compgen -W "''${opts}" -- ''${cur}) ) + return 0 + ;; + *) + case ''${COMP_WORDS[1]} in + services|backups|proxy|monitoring|status) + opts="--global --local --json --help" + ;; + *) + opts="--help" + ;; + esac + COMPREPLY=( $(compgen -W "''${opts}" -- ''${cur}) ) + return 0 + ;; + esac + } + + complete -F _homelab_completion homelab hl + ''; + }; +} diff --git a/modules/homelab/lib/systems/backups.nix b/modules/homelab/lib/systems/backups.nix index a39d1f9..49d10b7 100644 --- a/modules/homelab/lib/systems/backups.nix +++ b/modules/homelab/lib/systems/backups.nix @@ -124,19 +124,19 @@ in { # Always exposed aggregated data allJobs = mkOption { type = types.listOf types.attrs; - default = []; + default = localAggregation.allJobs; readOnly = true; }; allBackends = mkOption { type = types.listOf types.str; - default = []; + default = localAggregation.allBackends; readOnly = true; }; global = mkOption { type = types.attrs; - default = {}; + default = globalAggregation; readOnly = true; }; }; @@ -151,13 +151,13 @@ in { ]; # Always expose both local and global - homelab.backups = { - allJobs = localAggregation.allJobs; - allBackends = localAggregation.allBackends; - global = - if hasNodes - then globalAggregation - else {}; - }; + # homelab.backups = { + # allJobs = localAggregation.allJobs; + # allBackends = localAggregation.allBackends; + # global = + # if hasNodes + # then globalAggregation + # else {}; + # }; }; } diff --git a/modules/homelab/lib/systems/logging.nix b/modules/homelab/lib/systems/logging.nix index d760ee3..710b990 100644 --- a/modules/homelab/lib/systems/logging.nix +++ b/modules/homelab/lib/systems/logging.nix @@ -140,13 +140,13 @@ in { # Always exposed aggregated data allSources = mkOption { type = types.listOf types.attrs; - default = []; + default = localAggregation.allSources; readOnly = true; }; global = mkOption { type = types.attrs; - default = {}; + default = globalAggregation; readOnly = true; }; }; @@ -198,12 +198,12 @@ in { networking.firewall.allowedTCPPorts = optionals cfg.promtail.enable [cfg.promtail.port]; - homelab.logging = { - allSources = localAggregation.allSources; - global = - if hasNodes - then globalAggregation - else {}; - }; + # homelab.logging = { + # allSources = localAggregation.allSources; + # global = + # if hasNodes + # then globalAggregation + # else {}; + # }; }; } diff --git a/modules/homelab/lib/systems/proxy.nix b/modules/homelab/lib/systems/proxy.nix index a16694d..b5a6e73 100644 --- a/modules/homelab/lib/systems/proxy.nix +++ b/modules/homelab/lib/systems/proxy.nix @@ -74,25 +74,25 @@ in { # Always exposed aggregated data allEntries = mkOption { type = types.listOf types.attrs; - default = []; + default = localAggregation.allEntries; readOnly = true; }; global = mkOption { type = types.attrs; - default = {}; + default = globalAggregation; readOnly = true; }; }; config = mkIf cfg.enable { # Always expose both local and global - homelab.reverseProxy = { - allEntries = localAggregation.allEntries; - global = - if hasNodes - then globalAggregation - else {}; - }; + # homelab.reverseProxy = { + # allEntries = localAggregation.allEntries; + # global = + # if hasNodes + # then globalAggregation + # else {}; + # }; }; } diff --git a/modules/homelab/services/prometheus.nix b/modules/homelab/services/prometheus.nix index 7457568..b3f398b 100644 --- a/modules/homelab/services/prometheus.nix +++ b/modules/homelab/services/prometheus.nix @@ -12,9 +12,7 @@ with lib; let # Generate Prometheus scrape configs from global monitoring data prometheusScrapeConfigs = let # Get all metrics - try global first, fallback to local - allMetrics = homelabCfg.monitoring.global.allMetrics - or homelabCfg.monitoring.allMetrics - or []; + allMetrics = homelabCfg.monitoring.global.allMetrics; jobGroups = groupBy (m: m.jobName) allMetrics; @@ -157,6 +155,15 @@ in { default = []; description = "Additional rule files to load"; }; + + systemdServices = mkOption { + type = types.listOf types.str; + default = [ + "prometheus.service" + "prometheus" + ]; + description = "Systemd services to monitor"; + }; }; # Service configuration with smart defaults diff --git a/pkgs/default.nix b/pkgs/default.nix index f5abee4..b589408 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -3,4 +3,5 @@ pkgs: { # example = pkgs.callPackage ./example { }; ente-web = pkgs.callPackage ./ente-web.nix {}; + homelab-docs = pkgs.callPackage ./homelab-docs.nix {}; } diff --git a/pkgs/homelab-docs.nix b/pkgs/homelab-docs.nix new file mode 100644 index 0000000..3d5c13d --- /dev/null +++ b/pkgs/homelab-docs.nix @@ -0,0 +1,841 @@ +# homelab-docs.nix - Standalone documentation generator package +{ + lib, + stdenv, + writeShellScriptBin, + jq, + nixfmt, +}: let + # Main documentation generator script + docsGenerator = writeShellScriptBin "homelab-generate-docs" '' + #!/usr/bin/env bash + set -euo pipefail + + # Colors + BLUE='\033[0;34m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + RED='\033[0;31m' + NC='\033[0m' + + info() { echo -e "''${BLUE}$1''${NC}"; } + success() { echo -e "''${GREEN}$1''${NC}"; } + warn() { echo -e "''${YELLOW}$1''${NC}"; } + error() { echo -e "''${RED}$1''${NC}"; } + + # Configuration + DOCS_DIR="''${1:-./docs}" + + info "πŸ“š Generating homelab documentation..." + echo " Output directory: $DOCS_DIR" + echo + + # Check if we're in a directory with a flake + if [[ ! -f flake.nix ]]; then + error "No flake.nix found in current directory" + echo "Please run this command from your homelab flake directory" + exit 1 + fi + + # Check if colmena is available + if ! command -v colmena >/dev/null 2>&1; then + error "colmena command not found." + echo "Please ensure colmena is available in your environment" + echo "Add it to your devShell or install it globally" + exit 1 + fi + + # Create docs directory + mkdir -p "$DOCS_DIR" + + # Generate fleet overview + info " 🌐 Generating fleet overview..." + homelab-docs-fleet > "$DOCS_DIR/fleet-overview.md" + + # Generate node documentation + info " πŸ–₯️ Generating node configurations..." + homelab-docs-nodes > "$DOCS_DIR/nodes.md" + + # Generate service documentation + info " βš™οΈ Generating service configurations..." + homelab-docs-services > "$DOCS_DIR/services.md" + + # Generate current deployment + info " 🏠 Generating current deployment..." + homelab-docs-deployment > "$DOCS_DIR/current-deployment.md" + + # Generate README + info " πŸ“‹ Generating README..." + homelab-docs-readme > "$DOCS_DIR/README.md" + + success "βœ… Documentation generated successfully!" + echo + echo "Generated files:" + echo " 🌐 fleet-overview.md - Fleet statistics and overview" + echo " πŸ–₯️ nodes.md - Per-node configurations" + echo " βš™οΈ services.md - Service configurations" + echo " 🏠 current-deployment.md - Current deployment state" + echo " πŸ“‹ README.md - Documentation index" + echo + echo "πŸ’‘ Tip: Add these files to your repository and set up GitHub Actions" + echo " to automatically regenerate documentation on changes!" + ''; + + # Fleet overview generator + fleetDocsGenerator = writeShellScriptBin "homelab-docs-fleet" '' + #!/usr/bin/env bash + set -euo pipefail + + cat << EOF + # Homelab Fleet Overview + + > Auto-generated fleet overview + > + > Generated on: $(date) + > Source: $(pwd) + + ## Fleet Statistics + + EOF + + # Get basic fleet stats + echo "### Basic Information" + echo + + fleet_stats=$(colmena eval -E '{ nodes, pkgs, lib, ... }: { + totalNodes = lib.length (lib.attrNames nodes); + nodeNames = lib.attrNames nodes; + }') + + total_nodes=$(echo "$fleet_stats" | ${jq}/bin/jq -r '.totalNodes') + node_names=$(echo "$fleet_stats" | ${jq}/bin/jq -r '.nodeNames[]' | tr '\n' ' ') + + echo "| Metric | Value |" + echo "|--------|-------|" + echo "| Total Nodes | $total_nodes |" + echo "| Node Names | $node_names |" + echo + + # Get homelab-enabled nodes + echo "### Homelab Configuration" + echo + + homelab_info=$(colmena eval -E '{ nodes, pkgs, lib, ... }: { + homelabNodes = lib.mapAttrs (name: node: { + enabled = node.config.homelab.enable or false; + hostname = node.config.homelab.hostname or null; + environment = node.config.homelab.environment or null; + }) nodes; + }') + + echo "| Node | Homelab Enabled | Hostname | Environment |" + echo "|------|----------------|----------|-------------|" + + echo "$homelab_info" | ${jq}/bin/jq -r '.homelabNodes | to_entries[] | + [.key, (.value.enabled | tostring), (.value.hostname // "N/A"), (.value.environment // "N/A")] | + @tsv' | while IFS=$'\t' read -r node enabled hostname environment; do + enabled_icon=$(if [[ "$enabled" == "true" ]]; then echo "βœ…"; else echo "❌"; fi) + echo "| \`$node\` | $enabled_icon | $hostname | $environment |" + done + + echo + + # Get service distribution + echo "### Service Distribution" + echo + + service_info=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + lib.mapAttrs (name: node: + if (node.config.homelab.enable or false) then { + serviceCount = lib.length (lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {}))); + serviceNames = lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {})); + } else { + serviceCount = 0; + serviceNames = []; + } + ) nodes') + + echo "| Node | Service Count | Services |" + echo "|------|---------------|----------|" + + echo "$service_info" | ${jq}/bin/jq -r 'to_entries[] | + [.key, (.value.serviceCount | tostring), (.value.serviceNames | join(", "))] | + @tsv' | while IFS=$'\t' read -r node count services; do + echo "| \`$node\` | $count | $services |" + done + + echo + + # Environment distribution + echo "### Environment Distribution" + echo + + env_distribution=$(echo "$homelab_info" | ${jq}/bin/jq -r ' + [.homelabNodes | to_entries[] | select(.value.enabled == true) | .value.environment // "unknown"] | + group_by(.) | + map({environment: .[0], count: length}) | + .[]') + + if [[ -n "$env_distribution" ]]; then + echo "| Environment | Node Count |" + echo "|-------------|------------|" + + echo "$env_distribution" | ${jq}/bin/jq -r '[.environment, (.count | tostring)] | @tsv' | \ + while IFS=$'\t' read -r env count; do + echo "| $env | $count |" + done + else + echo "No homelab-enabled nodes found." + fi + + echo + echo "---" + echo + echo "*Fleet overview generated from colmena evaluation*" + ''; + + # Node documentation generator + nodeDocsGenerator = writeShellScriptBin "homelab-docs-nodes" '' + #!/usr/bin/env bash + set -euo pipefail + + cat << EOF + # Node Configurations + + > Detailed per-node configuration + > + > Generated on: $(date) + + EOF + + # Get all node information + node_info=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + lib.mapAttrs (name: node: { + # Basic system info + nixosVersion = node.config.system.nixos.version; + hostName = node.config.networking.hostName; + system = node.config.nixpkgs.system; + + # Homelab config (safe extraction) + homelab = if (node.config.homelab.enable or false) then { + enabled = true; + hostname = node.config.homelab.hostname or null; + domain = node.config.homelab.domain or null; + externalDomain = node.config.homelab.externalDomain or null; + environment = node.config.homelab.environment or null; + location = node.config.homelab.location or null; + tags = node.config.homelab.tags or []; + } else { + enabled = false; + }; + + # Services (safe extraction) + services = if (node.config.homelab.enable or false) then + lib.mapAttrs (svcName: svc: { + enabled = svc.enable or false; + port = svc.port or null; + description = svc.description or svcName; + tags = svc.tags or []; + }) (node.config.homelab.services or {}) + else {}; + }) nodes') + + echo "$node_info" | ${jq}/bin/jq -r 'to_entries[] | .key' | while read -r node; do + echo "## Node: $node" + echo + + # Basic system information + echo "### System Information" + echo + + nixos_version=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].nixosVersion") + hostname=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].hostName") + system=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].system") + + echo "| Property | Value |" + echo "|----------|-------|" + echo "| NixOS Version | \`$nixos_version\` |" + echo "| Hostname | \`$hostname\` |" + echo "| System | \`$system\` |" + echo + + # Homelab configuration + homelab_enabled=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.enabled") + + if [[ "$homelab_enabled" == "true" ]]; then + echo "### Homelab Configuration" + echo + + hl_hostname=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.hostname // \"N/A\"") + hl_domain=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.domain // \"N/A\"") + hl_external=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.externalDomain // \"N/A\"") + hl_env=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.environment // \"N/A\"") + hl_location=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.location // \"N/A\"") + hl_tags=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.tags | join(\", \")") + + echo "| Property | Value |" + echo "|----------|-------|" + echo "| Homelab Hostname | \`$hl_hostname\` |" + echo "| Domain | \`$hl_domain\` |" + echo "| External Domain | \`$hl_external\` |" + echo "| Environment | \`$hl_env\` |" + echo "| Location | \`$hl_location\` |" + echo "| Tags | $hl_tags |" + echo + + # Services + echo "### Services" + echo + + services_data=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].services") + service_count=$(echo "$services_data" | ${jq}/bin/jq 'length') + + if [[ "$service_count" -gt 0 ]]; then + echo "| Service | Enabled | Port | Description | Tags |" + echo "|---------|---------|------|-------------|------|" + + echo "$services_data" | ${jq}/bin/jq -r 'to_entries[] | + [.key, (.value.enabled | tostring), (.value.port // "N/A" | tostring), (.value.description // "N/A"), (.value.tags | join(", "))] | + @tsv' | while IFS=$'\t' read -r service enabled port description tags; do + enabled_icon=$(if [[ "$enabled" == "true" ]]; then echo "βœ…"; else echo "❌"; fi) + echo "| \`$service\` | $enabled_icon | $port | $description | $tags |" + done + else + echo "No services configured." + fi + else + echo "### Homelab Configuration" + echo + echo "❌ Homelab is not enabled on this node." + fi + + echo + echo "---" + echo + done + ''; + + # Service documentation generator - refocused on service capabilities + serviceDocsGenerator = writeShellScriptBin "homelab-docs-services" '' + #!/usr/bin/env bash + set -euo pipefail + + cat << EOF + # Service Catalog + + > Available services and their configuration options + > + > Generated on: $(date) + + This document catalogs all available homelab services, their configuration options, and integration capabilities. + + EOF + + # Get all services and their configurations + 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 []; + + # 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 {}; + }; + + 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; + additionalSubdomains = service.proxy.additionalSubdomains or []; + }; + + # Service-specific options (everything else) + serviceSpecific = removeAttrs service [ + "enable" "port" "description" "tags" + "monitoring" "logging" "proxy" + ]; + }; + }) (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: + 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; + in { + inherit serviceName; + config = canonicalConfig; + deploymentCount = lib.length (lib.filter (inst: inst.config.enable) instances); + availableOn = lib.unique (map (inst: inst.nodeName or "unknown") enabledInstances); + } + ) serviceGroups; + + in { + services = uniqueServices; + totalUniqueServices = lib.length (lib.attrNames uniqueServices); + }') + + total_services=$(echo "$services_catalog" | ${jq}/bin/jq -r '.totalUniqueServices') + + echo "## Overview" + echo + echo "**Total Available Services:** $total_services" + echo + echo "## Service Reference" + echo + + # Process each service + echo "$services_catalog" | ${jq}/bin/jq -r '.services | to_entries[] | .key' | sort | while read -r service; do + echo "### $service" + echo + + # Get service details + service_data=$(echo "$services_catalog" | ${jq}/bin/jq -r ".services[\"$service\"]") + + 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') + + 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)" + echo + + # Core Configuration + echo "#### Core Configuration" + 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 "\`\`\`" + 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 "\`\`\`" + echo + fi + + # Integration Options + 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') + + if [[ "$monitoring_enabled" == "true" || "$logging_enabled" == "true" || "$proxy_enabled" == "true" ]]; then + echo "#### Available Integrations" + echo + fi + + # Monitoring Integration + 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') + extra_labels=$(echo "$service_data" | ${jq}/bin/jq -r '.config.monitoring.extraLabels') + + echo "##### πŸ“Š Monitoring" + echo + echo "- **Metrics Endpoint:** \`$metrics_path\`" + echo "- **Health Check:** \`$health_path\`" + if [[ "$extra_labels" != "{}" ]]; then + echo "- **Default Labels:** $(echo "$extra_labels" | ${jq}/bin/jq -r 'to_entries[] | "\(.key)=\(.value)"' | paste -sd, -)" + fi + echo + echo "\`\`\`nix" + echo "homelab.services.$service.monitoring = {" + echo " enable = true;" + echo " metrics.path = \"$metrics_path\";" + echo " healthCheck.path = \"$health_path\";" + if [[ "$extra_labels" != "{}" ]]; then + echo " extraLabels = $extra_labels;" + fi + echo "};" + echo "\`\`\`" + echo + fi + + # Logging Integration + if [[ "$logging_enabled" == "true" ]]; then + log_files=$(echo "$service_data" | ${jq}/bin/jq -r '.config.logging.files[]?') + log_labels=$(echo "$service_data" | ${jq}/bin/jq -r '.config.logging.extraLabels') + + echo "##### πŸ“ Logging" + echo + if [[ -n "$log_files" ]]; then + echo "- **Log Files:**" + echo "$log_files" | while read -r file; do + echo " - \`$file\`" + done + fi + if [[ "$log_labels" != "{}" ]]; then + echo "- **Default Labels:** $(echo "$log_labels" | ${jq}/bin/jq -r 'to_entries[] | "\(.key)=\(.value)"' | paste -sd, -)" + fi + echo + echo "\`\`\`nix" + echo "homelab.services.$service.logging = {" + echo " enable = true;" + if [[ -n "$log_files" ]]; then + echo " files = [" + echo "$log_files" | while read -r file; do + echo " \"$file\"" + done + echo " ];" + fi + if [[ "$log_labels" != "{}" ]]; then + echo " extraLabels = $log_labels;" + fi + echo "};" + echo "\`\`\`" + echo + fi + + # Proxy Integration + 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') + additional_subdomains=$(echo "$service_data" | ${jq}/bin/jq -r '.config.proxy.additionalSubdomains') + + echo "##### πŸ”€ Reverse Proxy" + echo + echo "- **Primary Subdomain:** \`$subdomain\`" + echo "- **Authentication Required:** $(if [[ "$enable_auth" == "true" ]]; then echo "βœ… Yes"; else echo "❌ No"; fi)" + if [[ "$additional_subdomains" != "[]" && "$additional_subdomains" != "null" ]]; then + echo "- **Additional Subdomains:** Available" + fi + echo + echo "\`\`\`nix" + echo "homelab.services.$service.proxy = {" + echo " enable = true;" + echo " subdomain = \"$subdomain\";" + echo " enableAuth = $enable_auth;" + if [[ "$additional_subdomains" != "[]" && "$additional_subdomains" != "null" ]]; then + echo " additionalSubdomains = [" + echo " # Configure additional proxy entries as needed" + echo " ];" + fi + echo "};" + echo "\`\`\`" + echo + fi + + # Usage Examples + echo "#### Complete Example" + echo + echo "\`\`\`nix" + echo "# Full configuration example for $service" + echo "homelab.services.$service = {" + echo " enable = true;" + if [[ "$port" != "N/A" ]]; then + echo " port = $port;" + fi + echo " description = \"$description\";" + + # Add integration examples + if [[ "$monitoring_enabled" == "true" ]]; then + echo " " + echo " # Monitoring integration" + echo " monitoring.enable = true;" + fi + + if [[ "$logging_enabled" == "true" ]]; then + echo " " + echo " # Logging integration" + echo " logging.enable = true;" + fi + + if [[ "$proxy_enabled" == "true" ]]; then + echo " " + echo " # Reverse proxy integration" + echo " proxy = {" + echo " enable = true;" + echo " subdomain = \"$subdomain\";" + echo " enableAuth = $enable_auth;" + echo " };" + fi + + echo "};" + echo "\`\`\`" + echo + echo "---" + echo + done + + echo "## Integration Summary" + echo + echo "### Available Integration Types" + echo + echo "| Integration | Purpose | Configuration |" + echo "|-------------|---------|---------------|" + echo "| **Monitoring** | Prometheus metrics + health checks | \`monitoring.enable = true\` |" + echo "| **Logging** | Centralized log collection | \`logging.enable = true\` |" + echo "| **Proxy** | Reverse proxy with SSL + auth | \`proxy.enable = true\` |" + echo + echo "### Integration Benefits" + 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 + echo "---" + echo + echo "*This service catalog is generated from actual service configurations across your homelab fleet.*" + ''; + + # Current deployment generator + deploymentDocsGenerator = writeShellScriptBin "homelab-docs-deployment" '' + #!/usr/bin/env bash + set -euo pipefail + + cat << EOF + # Current Deployment State + + > Current homelab deployment configuration + > + > Generated on: $(date) + > Working directory: $(pwd) + + ## Deployment Summary + + EOF + + # Get deployment summary + deployment_summary=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + let + homelabNodes = lib.filterAttrs (name: node: node.config.homelab.enable or false) nodes; + allServices = lib.flatten (lib.mapAttrsToList (nodeName: node: + lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {})) + ) homelabNodes); + in { + totalNodes = lib.length (lib.attrNames nodes); + homelabEnabledNodes = lib.length (lib.attrNames homelabNodes); + uniqueServices = lib.length (lib.unique allServices); + totalServiceInstances = lib.length allServices; + nodeNames = lib.attrNames nodes; + homelabNodeNames = lib.attrNames homelabNodes; + }') + + total_nodes=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.totalNodes') + homelab_nodes=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.homelabEnabledNodes') + unique_services=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.uniqueServices') + service_instances=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.totalServiceInstances') + + echo "| Metric | Count |" + echo "|--------|-------|" + echo "| Total Nodes | $total_nodes |" + echo "| Homelab-Enabled Nodes | $homelab_nodes |" + echo "| Unique Services | $unique_services |" + echo "| Service Instances | $service_instances |" + echo + + echo "## Node Status" + echo + + # Get detailed node status + node_status=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + lib.mapAttrs (name: node: { + homelabEnabled = node.config.homelab.enable or false; + environment = node.config.homelab.environment or "unknown"; + serviceCount = if (node.config.homelab.enable or false) then + lib.length (lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {}))) + else 0; + monitoringEnabled = if (node.config.homelab.enable or false) then + node.config.homelab.monitoring.enable or false + else false; + backupsEnabled = if (node.config.homelab.enable or false) then + node.config.homelab.backups.enable or false + else false; + proxyEnabled = if (node.config.homelab.enable or false) then + node.config.homelab.reverseProxy.enable or false + else false; + }) nodes') + + echo "| Node | Homelab | Environment | Services | Monitoring | Backups | Proxy |" + echo "|------|---------|-------------|----------|------------|---------|-------|" + + echo "$node_status" | ${jq}/bin/jq -r 'to_entries[] | + [.key, (.value.homelabEnabled | if . then "βœ…" else "❌" end), .value.environment, (.value.serviceCount | tostring), + (.value.monitoringEnabled | if . then "βœ…" else "❌" end), + (.value.backupsEnabled | if . then "βœ…" else "❌" end), + (.value.proxyEnabled | if . then "βœ…" else "❌" end)] | + @tsv' | while IFS=$'\t' read -r node homelab env services monitoring backups proxy; do + echo "| \`$node\` | $homelab | $env | $services | $monitoring | $backups | $proxy |" + done + + echo + echo "---" + echo + echo "*Deployment state extracted from live colmena configuration*" + ''; + + # README generator + readmeGenerator = writeShellScriptBin "homelab-docs-readme" '' + #!/usr/bin/env bash + set -euo pipefail + + cat << EOF + # Homelab Documentation + + > Auto-generated documentation for the homelab deployment + > + > Generated on: $(date) + > Source: $(pwd) + + ## πŸ“š Documentation Files + + This documentation is automatically generated from your colmena flake configuration. + + ### πŸ“Š Overview Documents + - **[Fleet Overview](fleet-overview.md)** - High-level fleet statistics and service distribution + - **[Current Deployment](current-deployment.md)** - Current deployment state and node status + + ### πŸ“– Detailed Configuration + - **[Node Configurations](nodes.md)** - Per-node detailed configuration and services + - **[Service Configurations](services.md)** - Service configurations across the fleet + + ## πŸš€ Quick Actions + + ### View Current Status + \`\`\`bash + # Service status across fleet (if homelab CLI is available) + homelab services --global + + # Backup status + homelab backups --global + + # Overall status + homelab status + \`\`\` + + ### Update Documentation + \`\`\`bash + # Regenerate all documentation + homelab-generate-docs ./docs + + # Generate in different directory + homelab-generate-docs /path/to/output + \`\`\` + + ## πŸ“‹ Quick Stats + + EOF + + # Add live stats + quick_stats=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + let + homelabNodes = lib.filterAttrs (name: node: node.config.homelab.enable or false) nodes; + in { + totalNodes = lib.length (lib.attrNames nodes); + homelabNodes = lib.length (lib.attrNames homelabNodes); + }') + + total_nodes=$(echo "$quick_stats" | ${jq}/bin/jq -r '.totalNodes') + homelab_nodes=$(echo "$quick_stats" | ${jq}/bin/jq -r '.homelabNodes') + + echo "- **Total Nodes**: $total_nodes" + echo "- **Homelab-Enabled Nodes**: $homelab_nodes" + echo "- **Generated**: $(date)" + echo + echo "## πŸ› οΈ Management Tools" + echo + echo "### Documentation Commands" + echo "- \`homelab-generate-docs\` - Regenerate this documentation" + echo "- \`homelab-docs-fleet\` - Generate fleet overview only" + echo "- \`homelab-docs-nodes\` - Generate node configurations only" + echo "- \`homelab-docs-services\` - Generate service configurations only" + echo "- \`homelab-docs-deployment\` - Generate deployment state only" + echo + echo "### Colmena Commands" + echo "- \`colmena eval\` - Evaluate flake expressions" + echo "- \`colmena apply\` - Deploy configuration changes" + echo "- \`colmena build\` - Build configurations without deploying" + echo + echo "---" + echo + echo "*This documentation reflects the live state of your homelab deployment as evaluated by colmena.*" + ''; +in + stdenv.mkDerivation { + pname = "homelab-docs"; + version = "1.0.0"; + + dontUnpack = true; + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + + # Install all the generators + cp ${docsGenerator}/bin/homelab-generate-docs $out/bin/ + cp ${fleetDocsGenerator}/bin/homelab-docs-fleet $out/bin/ + cp ${nodeDocsGenerator}/bin/homelab-docs-nodes $out/bin/ + cp ${serviceDocsGenerator}/bin/homelab-docs-services $out/bin/ + cp ${deploymentDocsGenerator}/bin/homelab-docs-deployment $out/bin/ + cp ${readmeGenerator}/bin/homelab-docs-readme $out/bin/ + + # Make sure they're executable + chmod +x $out/bin/* + ''; + + meta = with lib; { + description = "Documentation generator for homelab colmena deployments"; + longDescription = '' + A collection of tools to generate comprehensive documentation + for homelab deployments managed with colmena. Extracts configuration + from flakes and generates markdown documentation. + ''; + license = licenses.mit; + maintainers = []; + platforms = platforms.all; + }; + } From 73d2f44d74b716fd646fdd21e898c37ae8bd33d6 Mon Sep 17 00:00:00 2001 From: plasmagoat Date: Tue, 29 Jul 2025 16:58:09 +0200 Subject: [PATCH 3/8] updated docs --- docs/README.md | 48 +- docs/current-deployment.md | 4 +- docs/fleet-overview.md | 10 +- docs/nodes.md | 2 +- docs/services.md | 126 +++-- pkgs/default.nix | 2 +- pkgs/homelab-docs.nix | 841 ------------------------------- pkgs/homelab-docs/default.nix | 50 ++ pkgs/homelab-docs/deployment.nix | 89 ++++ pkgs/homelab-docs/fleet.nix | 91 ++++ pkgs/homelab-docs/main.nix | 73 +++ pkgs/homelab-docs/nodes.nix | 123 +++++ pkgs/homelab-docs/readme.nix | 124 +++++ pkgs/homelab-docs/services.nix | 270 ++++++++++ 14 files changed, 940 insertions(+), 913 deletions(-) delete mode 100644 pkgs/homelab-docs.nix create mode 100644 pkgs/homelab-docs/default.nix create mode 100644 pkgs/homelab-docs/deployment.nix create mode 100644 pkgs/homelab-docs/fleet.nix create mode 100644 pkgs/homelab-docs/main.nix create mode 100644 pkgs/homelab-docs/nodes.nix create mode 100644 pkgs/homelab-docs/readme.nix create mode 100644 pkgs/homelab-docs/services.nix diff --git a/docs/README.md b/docs/README.md index 2ea873c..17e36a8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,8 +2,8 @@ > Auto-generated documentation for the homelab deployment > -> Generated on: tir 29 jul 16:25:52 CEST 2025 -> Source: /home/plasmagoat/homelab +> Generated on: $(date) +> Source: $(pwd) ## πŸ“š Documentation Files @@ -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:25:55 CEST 2025 +- **Generated**: tir 29 jul 16:57:16 CEST 2025 ## πŸ› οΈ Management Tools @@ -60,6 +60,40 @@ homelab-generate-docs /path/to/output - `colmena apply` - Deploy configuration changes - `colmena build` - Build configurations without deploying +## 🎯 Integration with CI/CD + +### GitHub Actions Example + +```yaml +name: Generate Documentation +on: + push: + branches: [ main ] + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v24 + - name: Generate docs + run: nix develop --command homelab-generate-docs ./docs + - name: Commit docs + run: | + git add docs/ + git commit -m "docs: update homelab documentation" || exit 0 + git push +``` + +### Manual Generation + +```bash +# From your homelab directory +nix develop +homelab-generate-docs ./docs +git add docs/ && git commit -m "Update docs" +``` + --- *This documentation reflects the live state of your homelab deployment as evaluated by colmena.* diff --git a/docs/current-deployment.md b/docs/current-deployment.md index 1767502..2f1541c 100644 --- a/docs/current-deployment.md +++ b/docs/current-deployment.md @@ -2,8 +2,8 @@ > Current homelab deployment configuration > -> Generated on: tir 29 jul 16:25:46 CEST 2025 -> Working directory: /home/plasmagoat/homelab +> Generated on: $(date) +> Working directory: $(pwd) ## Deployment Summary diff --git a/docs/fleet-overview.md b/docs/fleet-overview.md index 601ce6c..5982210 100644 --- a/docs/fleet-overview.md +++ b/docs/fleet-overview.md @@ -2,8 +2,8 @@ > Auto-generated fleet overview > -> Generated on: tir 29 jul 16:25:32 CEST 2025 -> Source: /home/plasmagoat/homelab +> Generated on: $(date) +> Source: $(pwd) ## Fleet Statistics @@ -28,12 +28,6 @@ | `photos` | 1 | minio | | `sandbox` | 0 | | -### Environment Distribution - -| Environment | Node Count | -|-------------|------------| -| production | 2 | - --- *Fleet overview generated from colmena evaluation* diff --git a/docs/nodes.md b/docs/nodes.md index 25bdbb8..87cade4 100644 --- a/docs/nodes.md +++ b/docs/nodes.md @@ -2,7 +2,7 @@ > Detailed per-node configuration > -> Generated on: tir 29 jul 16:25:40 CEST 2025 +> Generated on: $(date) ## Node: photos diff --git a/docs/services.md b/docs/services.md index e528e6f..a953b35 100644 --- a/docs/services.md +++ b/docs/services.md @@ -2,7 +2,7 @@ > Available services and their configuration options > -> Generated on: tir 29 jul 16:25:43 CEST 2025 +> Generated on: $(date) This document catalogs all available homelab services, their configuration options, and integration capabilities. @@ -10,6 +10,17 @@ This document catalogs all available homelab services, their configuration optio **Total Available Services:** 4 +## Service Integration Matrix + +| Service | Monitoring | Logging | Proxy | Auth Default | +|---------|------------|---------|-------|--------------| +| `gatus` | ❌ | ❌ | ❌ | 🌐 | +| `grafana` | ❌ | ❌ | ❌ | 🌐 | +| `minio` | ❌ | ❌ | ❌ | 🌐 | +| `prometheus` | ❌ | ❌ | ❌ | 🌐 | + +**Legend:** βœ… = Enabled by default, ❌ = Available but disabled, πŸ”’ = Auth required, 🌐 = Public access + ## Service Reference ### gatus @@ -18,7 +29,15 @@ This document catalogs all available homelab services, their configuration optio **Default Port:** `8080` -**Current Deployments:** 0 instance(s) +**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 @@ -27,6 +46,11 @@ 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 }; ``` @@ -47,17 +71,6 @@ homelab.services.gatus = { }; ``` -#### Complete Example - -```nix -# Full configuration example for gatus -homelab.services.gatus = { - enable = true; - port = 8080; - description = "Gatus Status Page"; -}; -``` - --- ### grafana @@ -66,7 +79,15 @@ homelab.services.gatus = { **Default Port:** `3000` -**Current Deployments:** 0 instance(s) +**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 @@ -75,17 +96,11 @@ homelab.services.grafana = { enable = true; port = 3000; description = "Grafana Metrics Dashboard"; -}; -``` -#### Complete Example - -```nix -# Full configuration example for grafana -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 }; ``` @@ -97,7 +112,15 @@ homelab.services.grafana = { **Default Port:** `9000` -**Current Deployments:** 1 instance(s) +**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 @@ -106,6 +129,11 @@ 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 }; ``` @@ -123,17 +151,6 @@ homelab.services.minio = { }; ``` -#### Complete Example - -```nix -# Full configuration example for minio -homelab.services.minio = { - enable = true; - port = 9000; - description = "minio"; -}; -``` - --- ### prometheus @@ -142,7 +159,15 @@ homelab.services.minio = { **Default Port:** `9090` -**Current Deployments:** 0 instance(s) +**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 @@ -151,6 +176,11 @@ 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 }; ``` @@ -174,28 +204,17 @@ homelab.services.prometheus = { }; ``` -#### Complete Example - -```nix -# Full configuration example for prometheus -homelab.services.prometheus = { - enable = true; - port = 9090; - description = "Prometheus Monitoring Server"; -}; -``` - --- ## Integration Summary ### Available Integration Types -| Integration | Purpose | Configuration | -|-------------|---------|---------------| -| **Monitoring** | Prometheus metrics + health checks | `monitoring.enable = true` | -| **Logging** | Centralized log collection | `logging.enable = true` | -| **Proxy** | Reverse proxy with SSL + auth | `proxy.enable = true` | +| 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` | ### Integration Benefits @@ -203,6 +222,7 @@ homelab.services.prometheus = { - **πŸ“Š 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 --- diff --git a/pkgs/default.nix b/pkgs/default.nix index b589408..657eca0 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -3,5 +3,5 @@ pkgs: { # example = pkgs.callPackage ./example { }; ente-web = pkgs.callPackage ./ente-web.nix {}; - homelab-docs = pkgs.callPackage ./homelab-docs.nix {}; + homelab-docs = pkgs.callPackage ./homelab-docs {}; } diff --git a/pkgs/homelab-docs.nix b/pkgs/homelab-docs.nix deleted file mode 100644 index 3d5c13d..0000000 --- a/pkgs/homelab-docs.nix +++ /dev/null @@ -1,841 +0,0 @@ -# homelab-docs.nix - Standalone documentation generator package -{ - lib, - stdenv, - writeShellScriptBin, - jq, - nixfmt, -}: let - # Main documentation generator script - docsGenerator = writeShellScriptBin "homelab-generate-docs" '' - #!/usr/bin/env bash - set -euo pipefail - - # Colors - BLUE='\033[0;34m' - GREEN='\033[0;32m' - YELLOW='\033[1;33m' - RED='\033[0;31m' - NC='\033[0m' - - info() { echo -e "''${BLUE}$1''${NC}"; } - success() { echo -e "''${GREEN}$1''${NC}"; } - warn() { echo -e "''${YELLOW}$1''${NC}"; } - error() { echo -e "''${RED}$1''${NC}"; } - - # Configuration - DOCS_DIR="''${1:-./docs}" - - info "πŸ“š Generating homelab documentation..." - echo " Output directory: $DOCS_DIR" - echo - - # Check if we're in a directory with a flake - if [[ ! -f flake.nix ]]; then - error "No flake.nix found in current directory" - echo "Please run this command from your homelab flake directory" - exit 1 - fi - - # Check if colmena is available - if ! command -v colmena >/dev/null 2>&1; then - error "colmena command not found." - echo "Please ensure colmena is available in your environment" - echo "Add it to your devShell or install it globally" - exit 1 - fi - - # Create docs directory - mkdir -p "$DOCS_DIR" - - # Generate fleet overview - info " 🌐 Generating fleet overview..." - homelab-docs-fleet > "$DOCS_DIR/fleet-overview.md" - - # Generate node documentation - info " πŸ–₯️ Generating node configurations..." - homelab-docs-nodes > "$DOCS_DIR/nodes.md" - - # Generate service documentation - info " βš™οΈ Generating service configurations..." - homelab-docs-services > "$DOCS_DIR/services.md" - - # Generate current deployment - info " 🏠 Generating current deployment..." - homelab-docs-deployment > "$DOCS_DIR/current-deployment.md" - - # Generate README - info " πŸ“‹ Generating README..." - homelab-docs-readme > "$DOCS_DIR/README.md" - - success "βœ… Documentation generated successfully!" - echo - echo "Generated files:" - echo " 🌐 fleet-overview.md - Fleet statistics and overview" - echo " πŸ–₯️ nodes.md - Per-node configurations" - echo " βš™οΈ services.md - Service configurations" - echo " 🏠 current-deployment.md - Current deployment state" - echo " πŸ“‹ README.md - Documentation index" - echo - echo "πŸ’‘ Tip: Add these files to your repository and set up GitHub Actions" - echo " to automatically regenerate documentation on changes!" - ''; - - # Fleet overview generator - fleetDocsGenerator = writeShellScriptBin "homelab-docs-fleet" '' - #!/usr/bin/env bash - set -euo pipefail - - cat << EOF - # Homelab Fleet Overview - - > Auto-generated fleet overview - > - > Generated on: $(date) - > Source: $(pwd) - - ## Fleet Statistics - - EOF - - # Get basic fleet stats - echo "### Basic Information" - echo - - fleet_stats=$(colmena eval -E '{ nodes, pkgs, lib, ... }: { - totalNodes = lib.length (lib.attrNames nodes); - nodeNames = lib.attrNames nodes; - }') - - total_nodes=$(echo "$fleet_stats" | ${jq}/bin/jq -r '.totalNodes') - node_names=$(echo "$fleet_stats" | ${jq}/bin/jq -r '.nodeNames[]' | tr '\n' ' ') - - echo "| Metric | Value |" - echo "|--------|-------|" - echo "| Total Nodes | $total_nodes |" - echo "| Node Names | $node_names |" - echo - - # Get homelab-enabled nodes - echo "### Homelab Configuration" - echo - - homelab_info=$(colmena eval -E '{ nodes, pkgs, lib, ... }: { - homelabNodes = lib.mapAttrs (name: node: { - enabled = node.config.homelab.enable or false; - hostname = node.config.homelab.hostname or null; - environment = node.config.homelab.environment or null; - }) nodes; - }') - - echo "| Node | Homelab Enabled | Hostname | Environment |" - echo "|------|----------------|----------|-------------|" - - echo "$homelab_info" | ${jq}/bin/jq -r '.homelabNodes | to_entries[] | - [.key, (.value.enabled | tostring), (.value.hostname // "N/A"), (.value.environment // "N/A")] | - @tsv' | while IFS=$'\t' read -r node enabled hostname environment; do - enabled_icon=$(if [[ "$enabled" == "true" ]]; then echo "βœ…"; else echo "❌"; fi) - echo "| \`$node\` | $enabled_icon | $hostname | $environment |" - done - - echo - - # Get service distribution - echo "### Service Distribution" - echo - - service_info=$(colmena eval -E '{ nodes, pkgs, lib, ... }: - lib.mapAttrs (name: node: - if (node.config.homelab.enable or false) then { - serviceCount = lib.length (lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {}))); - serviceNames = lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {})); - } else { - serviceCount = 0; - serviceNames = []; - } - ) nodes') - - echo "| Node | Service Count | Services |" - echo "|------|---------------|----------|" - - echo "$service_info" | ${jq}/bin/jq -r 'to_entries[] | - [.key, (.value.serviceCount | tostring), (.value.serviceNames | join(", "))] | - @tsv' | while IFS=$'\t' read -r node count services; do - echo "| \`$node\` | $count | $services |" - done - - echo - - # Environment distribution - echo "### Environment Distribution" - echo - - env_distribution=$(echo "$homelab_info" | ${jq}/bin/jq -r ' - [.homelabNodes | to_entries[] | select(.value.enabled == true) | .value.environment // "unknown"] | - group_by(.) | - map({environment: .[0], count: length}) | - .[]') - - if [[ -n "$env_distribution" ]]; then - echo "| Environment | Node Count |" - echo "|-------------|------------|" - - echo "$env_distribution" | ${jq}/bin/jq -r '[.environment, (.count | tostring)] | @tsv' | \ - while IFS=$'\t' read -r env count; do - echo "| $env | $count |" - done - else - echo "No homelab-enabled nodes found." - fi - - echo - echo "---" - echo - echo "*Fleet overview generated from colmena evaluation*" - ''; - - # Node documentation generator - nodeDocsGenerator = writeShellScriptBin "homelab-docs-nodes" '' - #!/usr/bin/env bash - set -euo pipefail - - cat << EOF - # Node Configurations - - > Detailed per-node configuration - > - > Generated on: $(date) - - EOF - - # Get all node information - node_info=$(colmena eval -E '{ nodes, pkgs, lib, ... }: - lib.mapAttrs (name: node: { - # Basic system info - nixosVersion = node.config.system.nixos.version; - hostName = node.config.networking.hostName; - system = node.config.nixpkgs.system; - - # Homelab config (safe extraction) - homelab = if (node.config.homelab.enable or false) then { - enabled = true; - hostname = node.config.homelab.hostname or null; - domain = node.config.homelab.domain or null; - externalDomain = node.config.homelab.externalDomain or null; - environment = node.config.homelab.environment or null; - location = node.config.homelab.location or null; - tags = node.config.homelab.tags or []; - } else { - enabled = false; - }; - - # Services (safe extraction) - services = if (node.config.homelab.enable or false) then - lib.mapAttrs (svcName: svc: { - enabled = svc.enable or false; - port = svc.port or null; - description = svc.description or svcName; - tags = svc.tags or []; - }) (node.config.homelab.services or {}) - else {}; - }) nodes') - - echo "$node_info" | ${jq}/bin/jq -r 'to_entries[] | .key' | while read -r node; do - echo "## Node: $node" - echo - - # Basic system information - echo "### System Information" - echo - - nixos_version=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].nixosVersion") - hostname=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].hostName") - system=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].system") - - echo "| Property | Value |" - echo "|----------|-------|" - echo "| NixOS Version | \`$nixos_version\` |" - echo "| Hostname | \`$hostname\` |" - echo "| System | \`$system\` |" - echo - - # Homelab configuration - homelab_enabled=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.enabled") - - if [[ "$homelab_enabled" == "true" ]]; then - echo "### Homelab Configuration" - echo - - hl_hostname=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.hostname // \"N/A\"") - hl_domain=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.domain // \"N/A\"") - hl_external=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.externalDomain // \"N/A\"") - hl_env=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.environment // \"N/A\"") - hl_location=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.location // \"N/A\"") - hl_tags=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.tags | join(\", \")") - - echo "| Property | Value |" - echo "|----------|-------|" - echo "| Homelab Hostname | \`$hl_hostname\` |" - echo "| Domain | \`$hl_domain\` |" - echo "| External Domain | \`$hl_external\` |" - echo "| Environment | \`$hl_env\` |" - echo "| Location | \`$hl_location\` |" - echo "| Tags | $hl_tags |" - echo - - # Services - echo "### Services" - echo - - services_data=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].services") - service_count=$(echo "$services_data" | ${jq}/bin/jq 'length') - - if [[ "$service_count" -gt 0 ]]; then - echo "| Service | Enabled | Port | Description | Tags |" - echo "|---------|---------|------|-------------|------|" - - echo "$services_data" | ${jq}/bin/jq -r 'to_entries[] | - [.key, (.value.enabled | tostring), (.value.port // "N/A" | tostring), (.value.description // "N/A"), (.value.tags | join(", "))] | - @tsv' | while IFS=$'\t' read -r service enabled port description tags; do - enabled_icon=$(if [[ "$enabled" == "true" ]]; then echo "βœ…"; else echo "❌"; fi) - echo "| \`$service\` | $enabled_icon | $port | $description | $tags |" - done - else - echo "No services configured." - fi - else - echo "### Homelab Configuration" - echo - echo "❌ Homelab is not enabled on this node." - fi - - echo - echo "---" - echo - done - ''; - - # Service documentation generator - refocused on service capabilities - serviceDocsGenerator = writeShellScriptBin "homelab-docs-services" '' - #!/usr/bin/env bash - set -euo pipefail - - cat << EOF - # Service Catalog - - > Available services and their configuration options - > - > Generated on: $(date) - - This document catalogs all available homelab services, their configuration options, and integration capabilities. - - EOF - - # Get all services and their configurations - 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 []; - - # 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 {}; - }; - - 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; - additionalSubdomains = service.proxy.additionalSubdomains or []; - }; - - # Service-specific options (everything else) - serviceSpecific = removeAttrs service [ - "enable" "port" "description" "tags" - "monitoring" "logging" "proxy" - ]; - }; - }) (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: - 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; - in { - inherit serviceName; - config = canonicalConfig; - deploymentCount = lib.length (lib.filter (inst: inst.config.enable) instances); - availableOn = lib.unique (map (inst: inst.nodeName or "unknown") enabledInstances); - } - ) serviceGroups; - - in { - services = uniqueServices; - totalUniqueServices = lib.length (lib.attrNames uniqueServices); - }') - - total_services=$(echo "$services_catalog" | ${jq}/bin/jq -r '.totalUniqueServices') - - echo "## Overview" - echo - echo "**Total Available Services:** $total_services" - echo - echo "## Service Reference" - echo - - # Process each service - echo "$services_catalog" | ${jq}/bin/jq -r '.services | to_entries[] | .key' | sort | while read -r service; do - echo "### $service" - echo - - # Get service details - service_data=$(echo "$services_catalog" | ${jq}/bin/jq -r ".services[\"$service\"]") - - 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') - - 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)" - echo - - # Core Configuration - echo "#### Core Configuration" - 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 "\`\`\`" - 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 "\`\`\`" - echo - fi - - # Integration Options - 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') - - if [[ "$monitoring_enabled" == "true" || "$logging_enabled" == "true" || "$proxy_enabled" == "true" ]]; then - echo "#### Available Integrations" - echo - fi - - # Monitoring Integration - 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') - extra_labels=$(echo "$service_data" | ${jq}/bin/jq -r '.config.monitoring.extraLabels') - - echo "##### πŸ“Š Monitoring" - echo - echo "- **Metrics Endpoint:** \`$metrics_path\`" - echo "- **Health Check:** \`$health_path\`" - if [[ "$extra_labels" != "{}" ]]; then - echo "- **Default Labels:** $(echo "$extra_labels" | ${jq}/bin/jq -r 'to_entries[] | "\(.key)=\(.value)"' | paste -sd, -)" - fi - echo - echo "\`\`\`nix" - echo "homelab.services.$service.monitoring = {" - echo " enable = true;" - echo " metrics.path = \"$metrics_path\";" - echo " healthCheck.path = \"$health_path\";" - if [[ "$extra_labels" != "{}" ]]; then - echo " extraLabels = $extra_labels;" - fi - echo "};" - echo "\`\`\`" - echo - fi - - # Logging Integration - if [[ "$logging_enabled" == "true" ]]; then - log_files=$(echo "$service_data" | ${jq}/bin/jq -r '.config.logging.files[]?') - log_labels=$(echo "$service_data" | ${jq}/bin/jq -r '.config.logging.extraLabels') - - echo "##### πŸ“ Logging" - echo - if [[ -n "$log_files" ]]; then - echo "- **Log Files:**" - echo "$log_files" | while read -r file; do - echo " - \`$file\`" - done - fi - if [[ "$log_labels" != "{}" ]]; then - echo "- **Default Labels:** $(echo "$log_labels" | ${jq}/bin/jq -r 'to_entries[] | "\(.key)=\(.value)"' | paste -sd, -)" - fi - echo - echo "\`\`\`nix" - echo "homelab.services.$service.logging = {" - echo " enable = true;" - if [[ -n "$log_files" ]]; then - echo " files = [" - echo "$log_files" | while read -r file; do - echo " \"$file\"" - done - echo " ];" - fi - if [[ "$log_labels" != "{}" ]]; then - echo " extraLabels = $log_labels;" - fi - echo "};" - echo "\`\`\`" - echo - fi - - # Proxy Integration - 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') - additional_subdomains=$(echo "$service_data" | ${jq}/bin/jq -r '.config.proxy.additionalSubdomains') - - echo "##### πŸ”€ Reverse Proxy" - echo - echo "- **Primary Subdomain:** \`$subdomain\`" - echo "- **Authentication Required:** $(if [[ "$enable_auth" == "true" ]]; then echo "βœ… Yes"; else echo "❌ No"; fi)" - if [[ "$additional_subdomains" != "[]" && "$additional_subdomains" != "null" ]]; then - echo "- **Additional Subdomains:** Available" - fi - echo - echo "\`\`\`nix" - echo "homelab.services.$service.proxy = {" - echo " enable = true;" - echo " subdomain = \"$subdomain\";" - echo " enableAuth = $enable_auth;" - if [[ "$additional_subdomains" != "[]" && "$additional_subdomains" != "null" ]]; then - echo " additionalSubdomains = [" - echo " # Configure additional proxy entries as needed" - echo " ];" - fi - echo "};" - echo "\`\`\`" - echo - fi - - # Usage Examples - echo "#### Complete Example" - echo - echo "\`\`\`nix" - echo "# Full configuration example for $service" - echo "homelab.services.$service = {" - echo " enable = true;" - if [[ "$port" != "N/A" ]]; then - echo " port = $port;" - fi - echo " description = \"$description\";" - - # Add integration examples - if [[ "$monitoring_enabled" == "true" ]]; then - echo " " - echo " # Monitoring integration" - echo " monitoring.enable = true;" - fi - - if [[ "$logging_enabled" == "true" ]]; then - echo " " - echo " # Logging integration" - echo " logging.enable = true;" - fi - - if [[ "$proxy_enabled" == "true" ]]; then - echo " " - echo " # Reverse proxy integration" - echo " proxy = {" - echo " enable = true;" - echo " subdomain = \"$subdomain\";" - echo " enableAuth = $enable_auth;" - echo " };" - fi - - echo "};" - echo "\`\`\`" - echo - echo "---" - echo - done - - echo "## Integration Summary" - echo - echo "### Available Integration Types" - echo - echo "| Integration | Purpose | Configuration |" - echo "|-------------|---------|---------------|" - echo "| **Monitoring** | Prometheus metrics + health checks | \`monitoring.enable = true\` |" - echo "| **Logging** | Centralized log collection | \`logging.enable = true\` |" - echo "| **Proxy** | Reverse proxy with SSL + auth | \`proxy.enable = true\` |" - echo - echo "### Integration Benefits" - 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 - echo "---" - echo - echo "*This service catalog is generated from actual service configurations across your homelab fleet.*" - ''; - - # Current deployment generator - deploymentDocsGenerator = writeShellScriptBin "homelab-docs-deployment" '' - #!/usr/bin/env bash - set -euo pipefail - - cat << EOF - # Current Deployment State - - > Current homelab deployment configuration - > - > Generated on: $(date) - > Working directory: $(pwd) - - ## Deployment Summary - - EOF - - # Get deployment summary - deployment_summary=$(colmena eval -E '{ nodes, pkgs, lib, ... }: - let - homelabNodes = lib.filterAttrs (name: node: node.config.homelab.enable or false) nodes; - allServices = lib.flatten (lib.mapAttrsToList (nodeName: node: - lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {})) - ) homelabNodes); - in { - totalNodes = lib.length (lib.attrNames nodes); - homelabEnabledNodes = lib.length (lib.attrNames homelabNodes); - uniqueServices = lib.length (lib.unique allServices); - totalServiceInstances = lib.length allServices; - nodeNames = lib.attrNames nodes; - homelabNodeNames = lib.attrNames homelabNodes; - }') - - total_nodes=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.totalNodes') - homelab_nodes=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.homelabEnabledNodes') - unique_services=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.uniqueServices') - service_instances=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.totalServiceInstances') - - echo "| Metric | Count |" - echo "|--------|-------|" - echo "| Total Nodes | $total_nodes |" - echo "| Homelab-Enabled Nodes | $homelab_nodes |" - echo "| Unique Services | $unique_services |" - echo "| Service Instances | $service_instances |" - echo - - echo "## Node Status" - echo - - # Get detailed node status - node_status=$(colmena eval -E '{ nodes, pkgs, lib, ... }: - lib.mapAttrs (name: node: { - homelabEnabled = node.config.homelab.enable or false; - environment = node.config.homelab.environment or "unknown"; - serviceCount = if (node.config.homelab.enable or false) then - lib.length (lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {}))) - else 0; - monitoringEnabled = if (node.config.homelab.enable or false) then - node.config.homelab.monitoring.enable or false - else false; - backupsEnabled = if (node.config.homelab.enable or false) then - node.config.homelab.backups.enable or false - else false; - proxyEnabled = if (node.config.homelab.enable or false) then - node.config.homelab.reverseProxy.enable or false - else false; - }) nodes') - - echo "| Node | Homelab | Environment | Services | Monitoring | Backups | Proxy |" - echo "|------|---------|-------------|----------|------------|---------|-------|" - - echo "$node_status" | ${jq}/bin/jq -r 'to_entries[] | - [.key, (.value.homelabEnabled | if . then "βœ…" else "❌" end), .value.environment, (.value.serviceCount | tostring), - (.value.monitoringEnabled | if . then "βœ…" else "❌" end), - (.value.backupsEnabled | if . then "βœ…" else "❌" end), - (.value.proxyEnabled | if . then "βœ…" else "❌" end)] | - @tsv' | while IFS=$'\t' read -r node homelab env services monitoring backups proxy; do - echo "| \`$node\` | $homelab | $env | $services | $monitoring | $backups | $proxy |" - done - - echo - echo "---" - echo - echo "*Deployment state extracted from live colmena configuration*" - ''; - - # README generator - readmeGenerator = writeShellScriptBin "homelab-docs-readme" '' - #!/usr/bin/env bash - set -euo pipefail - - cat << EOF - # Homelab Documentation - - > Auto-generated documentation for the homelab deployment - > - > Generated on: $(date) - > Source: $(pwd) - - ## πŸ“š Documentation Files - - This documentation is automatically generated from your colmena flake configuration. - - ### πŸ“Š Overview Documents - - **[Fleet Overview](fleet-overview.md)** - High-level fleet statistics and service distribution - - **[Current Deployment](current-deployment.md)** - Current deployment state and node status - - ### πŸ“– Detailed Configuration - - **[Node Configurations](nodes.md)** - Per-node detailed configuration and services - - **[Service Configurations](services.md)** - Service configurations across the fleet - - ## πŸš€ Quick Actions - - ### View Current Status - \`\`\`bash - # Service status across fleet (if homelab CLI is available) - homelab services --global - - # Backup status - homelab backups --global - - # Overall status - homelab status - \`\`\` - - ### Update Documentation - \`\`\`bash - # Regenerate all documentation - homelab-generate-docs ./docs - - # Generate in different directory - homelab-generate-docs /path/to/output - \`\`\` - - ## πŸ“‹ Quick Stats - - EOF - - # Add live stats - quick_stats=$(colmena eval -E '{ nodes, pkgs, lib, ... }: - let - homelabNodes = lib.filterAttrs (name: node: node.config.homelab.enable or false) nodes; - in { - totalNodes = lib.length (lib.attrNames nodes); - homelabNodes = lib.length (lib.attrNames homelabNodes); - }') - - total_nodes=$(echo "$quick_stats" | ${jq}/bin/jq -r '.totalNodes') - homelab_nodes=$(echo "$quick_stats" | ${jq}/bin/jq -r '.homelabNodes') - - echo "- **Total Nodes**: $total_nodes" - echo "- **Homelab-Enabled Nodes**: $homelab_nodes" - echo "- **Generated**: $(date)" - echo - echo "## πŸ› οΈ Management Tools" - echo - echo "### Documentation Commands" - echo "- \`homelab-generate-docs\` - Regenerate this documentation" - echo "- \`homelab-docs-fleet\` - Generate fleet overview only" - echo "- \`homelab-docs-nodes\` - Generate node configurations only" - echo "- \`homelab-docs-services\` - Generate service configurations only" - echo "- \`homelab-docs-deployment\` - Generate deployment state only" - echo - echo "### Colmena Commands" - echo "- \`colmena eval\` - Evaluate flake expressions" - echo "- \`colmena apply\` - Deploy configuration changes" - echo "- \`colmena build\` - Build configurations without deploying" - echo - echo "---" - echo - echo "*This documentation reflects the live state of your homelab deployment as evaluated by colmena.*" - ''; -in - stdenv.mkDerivation { - pname = "homelab-docs"; - version = "1.0.0"; - - dontUnpack = true; - dontBuild = true; - - installPhase = '' - mkdir -p $out/bin - - # Install all the generators - cp ${docsGenerator}/bin/homelab-generate-docs $out/bin/ - cp ${fleetDocsGenerator}/bin/homelab-docs-fleet $out/bin/ - cp ${nodeDocsGenerator}/bin/homelab-docs-nodes $out/bin/ - cp ${serviceDocsGenerator}/bin/homelab-docs-services $out/bin/ - cp ${deploymentDocsGenerator}/bin/homelab-docs-deployment $out/bin/ - cp ${readmeGenerator}/bin/homelab-docs-readme $out/bin/ - - # Make sure they're executable - chmod +x $out/bin/* - ''; - - meta = with lib; { - description = "Documentation generator for homelab colmena deployments"; - longDescription = '' - A collection of tools to generate comprehensive documentation - for homelab deployments managed with colmena. Extracts configuration - from flakes and generates markdown documentation. - ''; - license = licenses.mit; - maintainers = []; - platforms = platforms.all; - }; - } diff --git a/pkgs/homelab-docs/default.nix b/pkgs/homelab-docs/default.nix new file mode 100644 index 0000000..30dc69e --- /dev/null +++ b/pkgs/homelab-docs/default.nix @@ -0,0 +1,50 @@ +# homelab-docs.nix - Main documentation generator package +{ + lib, + stdenv, + writeShellScriptBin, + jq, + nixfmt, +}: let + # Import individual CLI generators + docsGenerator = import ./main.nix {inherit writeShellScriptBin;}; + fleetDocsGenerator = import ./fleet.nix {inherit writeShellScriptBin jq;}; + nodeDocsGenerator = import ./nodes.nix {inherit writeShellScriptBin jq;}; + serviceDocsGenerator = import ./services.nix {inherit writeShellScriptBin jq;}; + deploymentDocsGenerator = import ./deployment.nix {inherit writeShellScriptBin jq;}; + readmeGenerator = import ./readme.nix {inherit writeShellScriptBin jq;}; +in + stdenv.mkDerivation { + pname = "homelab-docs"; + version = "1.0.0"; + + dontUnpack = true; + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin + + # Install all the generators + cp ${docsGenerator}/bin/homelab-generate-docs $out/bin/ + cp ${fleetDocsGenerator}/bin/homelab-docs-fleet $out/bin/ + cp ${nodeDocsGenerator}/bin/homelab-docs-nodes $out/bin/ + cp ${serviceDocsGenerator}/bin/homelab-docs-services $out/bin/ + cp ${deploymentDocsGenerator}/bin/homelab-docs-deployment $out/bin/ + cp ${readmeGenerator}/bin/homelab-docs-readme $out/bin/ + + # Make sure they're executable + chmod +x $out/bin/* + ''; + + meta = with lib; { + description = "Documentation generator for homelab colmena deployments"; + longDescription = '' + A collection of tools to generate comprehensive documentation + for homelab deployments managed with colmena. Extracts configuration + from flakes and generates markdown documentation. + ''; + license = licenses.mit; + maintainers = []; + platforms = platforms.all; + }; + } diff --git a/pkgs/homelab-docs/deployment.nix b/pkgs/homelab-docs/deployment.nix new file mode 100644 index 0000000..f9636a7 --- /dev/null +++ b/pkgs/homelab-docs/deployment.nix @@ -0,0 +1,89 @@ +# homelab-docs-deployment.nix - Deployment documentation generator CLI +{ + writeShellScriptBin, + jq, +}: +writeShellScriptBin "homelab-docs-deployment" '' + #!/usr/bin/env bash + set -euo pipefail + + cat << 'EOF' + # Current Deployment State + + > Current homelab deployment configuration + > + > Generated on: $(date) + > Working directory: $(pwd) + + ## Deployment Summary + + EOF + + # Get deployment summary + deployment_summary=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + let + homelabNodes = lib.filterAttrs (name: node: node.config.homelab.enable or false) nodes; + allServices = lib.flatten (lib.mapAttrsToList (nodeName: node: + lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {})) + ) homelabNodes); + in { + totalNodes = lib.length (lib.attrNames nodes); + homelabEnabledNodes = lib.length (lib.attrNames homelabNodes); + uniqueServices = lib.length (lib.unique allServices); + totalServiceInstances = lib.length allServices; + nodeNames = lib.attrNames nodes; + homelabNodeNames = lib.attrNames homelabNodes; + }') + + total_nodes=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.totalNodes') + homelab_nodes=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.homelabEnabledNodes') + unique_services=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.uniqueServices') + service_instances=$(echo "$deployment_summary" | ${jq}/bin/jq -r '.totalServiceInstances') + + echo "| Metric | Count |" + echo "|--------|-------|" + echo "| Total Nodes | $total_nodes |" + echo "| Homelab-Enabled Nodes | $homelab_nodes |" + echo "| Unique Services | $unique_services |" + echo "| Service Instances | $service_instances |" + echo + + echo "## Node Status" + echo + + # Get detailed node status + node_status=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + lib.mapAttrs (name: node: { + homelabEnabled = node.config.homelab.enable or false; + environment = node.config.homelab.environment or "unknown"; + serviceCount = if (node.config.homelab.enable or false) then + lib.length (lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {}))) + else 0; + monitoringEnabled = if (node.config.homelab.enable or false) then + node.config.homelab.monitoring.enable or false + else false; + backupsEnabled = if (node.config.homelab.enable or false) then + node.config.homelab.backups.enable or false + else false; + proxyEnabled = if (node.config.homelab.enable or false) then + node.config.homelab.reverseProxy.enable or false + else false; + }) nodes') + + echo "| Node | Homelab | Environment | Services | Monitoring | Backups | Proxy |" + echo "|------|---------|-------------|----------|------------|---------|-------|" + + echo "$node_status" | ${jq}/bin/jq -r 'to_entries[] | + [.key, (.value.homelabEnabled | if . then "βœ…" else "❌" end), .value.environment, (.value.serviceCount | tostring), + (.value.monitoringEnabled | if . then "βœ…" else "❌" end), + (.value.backupsEnabled | if . then "βœ…" else "❌" end), + (.value.proxyEnabled | if . then "βœ…" else "❌" end)] | + @tsv' | while IFS=$'\t' read -r node homelab env services monitoring backups proxy; do + echo "| \`$node\` | $homelab | $env | $services | $monitoring | $backups | $proxy |" + done + + echo + echo "---" + echo + echo "*Deployment state extracted from live colmena configuration*" +'' diff --git a/pkgs/homelab-docs/fleet.nix b/pkgs/homelab-docs/fleet.nix new file mode 100644 index 0000000..8ca3066 --- /dev/null +++ b/pkgs/homelab-docs/fleet.nix @@ -0,0 +1,91 @@ +{ + writeShellScriptBin, + jq, +}: +writeShellScriptBin "homelab-docs-fleet" '' + #!/usr/bin/env bash + set -euo pipefail + + cat << 'EOF' + # Homelab Fleet Overview + + > Auto-generated fleet overview + > + > Generated on: $(date) + > Source: $(pwd) + + ## Fleet Statistics + + EOF + + # Get basic fleet stats + echo "### Basic Information" + echo + + fleet_stats=$(colmena eval -E '{ nodes, pkgs, lib, ... }: { + totalNodes = lib.length (lib.attrNames nodes); + nodeNames = lib.attrNames nodes; + }') + + total_nodes=$(echo "$fleet_stats" | ${jq}/bin/jq -r '.totalNodes') + node_names=$(echo "$fleet_stats" | ${jq}/bin/jq -r '.nodeNames[]' | tr '\n' ' ') + + echo "| Metric | Value |" + echo "|--------|-------|" + echo "| Total Nodes | $total_nodes |" + echo "| Node Names | $node_names |" + echo + + # Get homelab-enabled nodes + echo "### Homelab Configuration" + echo + + homelab_info=$(colmena eval -E '{ nodes, pkgs, lib, ... }: { + homelabNodes = lib.mapAttrs (name: node: { + enabled = node.config.homelab.enable or false; + hostname = node.config.homelab.hostname or null; + environment = node.config.homelab.environment or null; + }) nodes; + }') + + echo "| Node | Homelab Enabled | Hostname | Environment |" + echo "|------|----------------|----------|-------------|" + + echo "$homelab_info" | ${jq}/bin/jq -r '.homelabNodes | to_entries[] | + [.key, (.value.enabled | tostring), (.value.hostname // "N/A"), (.value.environment // "N/A")] | + @tsv' | while IFS=$'\t' read -r node enabled hostname environment; do + enabled_icon=$(if [[ "$enabled" == "true" ]]; then echo "βœ…"; else echo "❌"; fi) + echo "| \`$node\` | $enabled_icon | $hostname | $environment |" + done + + echo + + # Get service distribution + echo "### Service Distribution" + echo + + service_info=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + lib.mapAttrs (name: node: + if (node.config.homelab.enable or false) then { + serviceCount = lib.length (lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {}))); + serviceNames = lib.attrNames (lib.filterAttrs (n: v: v.enable or false) (node.config.homelab.services or {})); + } else { + serviceCount = 0; + serviceNames = []; + } + ) nodes') + + echo "| Node | Service Count | Services |" + echo "|------|---------------|----------|" + + echo "$service_info" | ${jq}/bin/jq -r 'to_entries[] | + [.key, (.value.serviceCount | tostring), (.value.serviceNames | join(", "))] | + @tsv' | while IFS=$'\t' read -r node count services; do + echo "| \`$node\` | $count | $services |" + done + + echo + echo "---" + echo + echo "*Fleet overview generated from colmena evaluation*" +'' diff --git a/pkgs/homelab-docs/main.nix b/pkgs/homelab-docs/main.nix new file mode 100644 index 0000000..e3389d2 --- /dev/null +++ b/pkgs/homelab-docs/main.nix @@ -0,0 +1,73 @@ +{writeShellScriptBin}: +writeShellScriptBin "homelab-generate-docs" '' + #!/usr/bin/env bash + set -euo pipefail + + # Colors + BLUE='\033[0;34m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + RED='\033[0;31m' + NC='\033[0m' + + info() { echo -e "''${BLUE}$1''${NC}"; } + success() { echo -e "''${GREEN}$1''${NC}"; } + warn() { echo -e "''${YELLOW}$1''${NC}"; } + error() { echo -e "''${RED}$1''${NC}"; } + + # Configuration + DOCS_DIR="''${1:-./docs}" + + info "πŸ“š Generating homelab documentation..." + echo " Output directory: $DOCS_DIR" + echo + + # Check if we're in a directory with a flake + if [[ ! -f flake.nix ]]; then + error "No flake.nix found in current directory" + echo "Please run this command from your homelab flake directory" + exit 1 + fi + + # Check if colmena is available + if ! command -v colmena >/dev/null 2>&1; then + error "colmena command not found." + echo "Please ensure colmena is available in your environment" + exit 1 + fi + + # Create docs directory + mkdir -p "$DOCS_DIR" + + # Generate fleet overview + info " 🌐 Generating fleet overview..." + homelab-docs-fleet > "$DOCS_DIR/fleet-overview.md" + + # Generate node documentation + info " πŸ–₯️ Generating node configurations..." + homelab-docs-nodes > "$DOCS_DIR/nodes.md" + + # Generate service documentation + info " βš™οΈ Generating service configurations..." + homelab-docs-services > "$DOCS_DIR/services.md" + + # Generate current deployment + info " 🏠 Generating current deployment..." + homelab-docs-deployment > "$DOCS_DIR/current-deployment.md" + + # Generate README + info " πŸ“‹ Generating README..." + homelab-docs-readme > "$DOCS_DIR/README.md" + + success "βœ… Documentation generated successfully!" + echo + echo "Generated files:" + echo " 🌐 fleet-overview.md - Fleet statistics and overview" + echo " πŸ–₯️ nodes.md - Per-node configurations" + echo " βš™οΈ services.md - Service configurations" + echo " 🏠 current-deployment.md - Current deployment state" + echo " πŸ“‹ README.md - Documentation index" + echo + echo "πŸ’‘ Tip: Add these files to your repository and set up GitHub Actions" + echo " to automatically regenerate documentation on changes!" +'' diff --git a/pkgs/homelab-docs/nodes.nix b/pkgs/homelab-docs/nodes.nix new file mode 100644 index 0000000..b47fd05 --- /dev/null +++ b/pkgs/homelab-docs/nodes.nix @@ -0,0 +1,123 @@ +{ + writeShellScriptBin, + jq, +}: +writeShellScriptBin "homelab-docs-nodes" '' + #!/usr/bin/env bash + set -euo pipefail + + cat << 'EOF' + # Node Configurations + + > Detailed per-node configuration + > + > Generated on: $(date) + + EOF + + # Get all node information + node_info=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + lib.mapAttrs (name: node: { + # Basic system info + nixosVersion = node.config.system.nixos.version; + hostName = node.config.networking.hostName; + system = node.config.nixpkgs.system; + + # Homelab config (safe extraction) + homelab = if (node.config.homelab.enable or false) then { + enabled = true; + hostname = node.config.homelab.hostname or null; + domain = node.config.homelab.domain or null; + externalDomain = node.config.homelab.externalDomain or null; + environment = node.config.homelab.environment or null; + location = node.config.homelab.location or null; + tags = node.config.homelab.tags or []; + } else { + enabled = false; + }; + + # Services (safe extraction) + services = if (node.config.homelab.enable or false) then + lib.mapAttrs (svcName: svc: { + enabled = svc.enable or false; + port = svc.port or null; + description = svc.description or svcName; + tags = svc.tags or []; + }) (node.config.homelab.services or {}) + else {}; + }) nodes') + + echo "$node_info" | ${jq}/bin/jq -r 'to_entries[] | .key' | while read -r node; do + echo "## Node: $node" + echo + + # Basic system information + echo "### System Information" + echo + + nixos_version=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].nixosVersion") + hostname=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].hostName") + system=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].system") + + echo "| Property | Value |" + echo "|----------|-------|" + echo "| NixOS Version | \`$nixos_version\` |" + echo "| Hostname | \`$hostname\` |" + echo "| System | \`$system\` |" + echo + + # Homelab configuration + homelab_enabled=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.enabled") + + if [[ "$homelab_enabled" == "true" ]]; then + echo "### Homelab Configuration" + echo + + hl_hostname=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.hostname // \"N/A\"") + hl_domain=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.domain // \"N/A\"") + hl_external=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.externalDomain // \"N/A\"") + hl_env=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.environment // \"N/A\"") + hl_location=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.location // \"N/A\"") + hl_tags=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].homelab.tags | join(\", \")") + + echo "| Property | Value |" + echo "|----------|-------|" + echo "| Homelab Hostname | \`$hl_hostname\` |" + echo "| Domain | \`$hl_domain\` |" + echo "| External Domain | \`$hl_external\` |" + echo "| Environment | \`$hl_env\` |" + echo "| Location | \`$hl_location\` |" + echo "| Tags | $hl_tags |" + echo + + # Services + echo "### Services" + echo + + services_data=$(echo "$node_info" | ${jq}/bin/jq -r ".[\"$node\"].services") + service_count=$(echo "$services_data" | ${jq}/bin/jq 'length') + + if [[ "$service_count" -gt 0 ]]; then + echo "| Service | Enabled | Port | Description | Tags |" + echo "|---------|---------|------|-------------|------|" + + echo "$services_data" | ${jq}/bin/jq -r 'to_entries[] | + [.key, (.value.enabled | tostring), (.value.port // "N/A" | tostring), (.value.description // "N/A"), (.value.tags | join(", "))] | + @tsv' | while IFS=$'\t' read -r service enabled port description tags; do + enabled_icon=$(if [[ "$enabled" == "true" ]]; then echo "βœ…"; else echo "❌"; fi) + echo "| \`$service\` | $enabled_icon | $port | $description | $tags |" + done + else + echo "No services configured." + fi + else + echo "### Homelab Configuration" + echo + echo "❌ Homelab is not enabled on this node." + fi + + echo + echo "---" + echo + done +'' diff --git a/pkgs/homelab-docs/readme.nix b/pkgs/homelab-docs/readme.nix new file mode 100644 index 0000000..7a0891f --- /dev/null +++ b/pkgs/homelab-docs/readme.nix @@ -0,0 +1,124 @@ +# homelab-docs-readme.nix - README generator CLI +{ + writeShellScriptBin, + jq, +}: +writeShellScriptBin "homelab-docs-readme" '' + #!/usr/bin/env bash + set -euo pipefail + + cat << 'EOF' + # Homelab Documentation + + > Auto-generated documentation for the homelab deployment + > + > Generated on: $(date) + > Source: $(pwd) + + ## πŸ“š Documentation Files + + This documentation is automatically generated from your colmena flake configuration. + + ### πŸ“Š Overview Documents + - **[Fleet Overview](fleet-overview.md)** - High-level fleet statistics and service distribution + - **[Current Deployment](current-deployment.md)** - Current deployment state and node status + + ### πŸ“– Detailed Configuration + - **[Node Configurations](nodes.md)** - Per-node detailed configuration and services + - **[Service Configurations](services.md)** - Service configurations across the fleet + + ## πŸš€ Quick Actions + + ### View Current Status + \`\`\`bash + # Service status across fleet (if homelab CLI is available) + homelab services --global + + # Backup status + homelab backups --global + + # Overall status + homelab status + \`\`\` + + ### Update Documentation + \`\`\`bash + # Regenerate all documentation + homelab-generate-docs ./docs + + # Generate in different directory + homelab-generate-docs /path/to/output + \`\`\` + + ## πŸ“‹ Quick Stats + + EOF + + # Add live stats + quick_stats=$(colmena eval -E '{ nodes, pkgs, lib, ... }: + let + homelabNodes = lib.filterAttrs (name: node: node.config.homelab.enable or false) nodes; + in { + totalNodes = lib.length (lib.attrNames nodes); + homelabNodes = lib.length (lib.attrNames homelabNodes); + }') + + total_nodes=$(echo "$quick_stats" | ${jq}/bin/jq -r '.totalNodes') + homelab_nodes=$(echo "$quick_stats" | ${jq}/bin/jq -r '.homelabNodes') + + echo "- **Total Nodes**: $total_nodes" + echo "- **Homelab-Enabled Nodes**: $homelab_nodes" + echo "- **Generated**: $(date)" + echo + echo "## πŸ› οΈ Management Tools" + echo + echo "### Documentation Commands" + echo "- \`homelab-generate-docs\` - Regenerate this documentation" + echo "- \`homelab-docs-fleet\` - Generate fleet overview only" + echo "- \`homelab-docs-nodes\` - Generate node configurations only" + echo "- \`homelab-docs-services\` - Generate service configurations only" + echo "- \`homelab-docs-deployment\` - Generate deployment state only" + echo + echo "### Colmena Commands" + echo "- \`colmena eval\` - Evaluate flake expressions" + echo "- \`colmena apply\` - Deploy configuration changes" + echo "- \`colmena build\` - Build configurations without deploying" + echo + echo "## 🎯 Integration with CI/CD" + echo + echo "### GitHub Actions Example" + echo + echo "\`\`\`yaml" + echo "name: Generate Documentation" + echo "on:" + echo " push:" + echo " branches: [ main ]" + echo "" + echo "jobs:" + echo " docs:" + echo " runs-on: ubuntu-latest" + echo " steps:" + echo " - uses: actions/checkout@v4" + echo " - uses: cachix/install-nix-action@v24" + echo " - name: Generate docs" + echo " run: nix develop --command homelab-generate-docs ./docs" + echo " - name: Commit docs" + echo " run: |" + echo " git add docs/" + echo " git commit -m \"docs: update homelab documentation\" || exit 0" + echo " git push" + echo "\`\`\`" + echo + echo "### Manual Generation" + echo + echo "\`\`\`bash" + echo "# From your homelab directory" + echo "nix develop" + echo "homelab-generate-docs ./docs" + echo "git add docs/ && git commit -m \"Update docs\"" + echo "\`\`\`" + echo + echo "---" + echo + echo "*This documentation reflects the live state of your homelab deployment as evaluated by colmena.*" +'' diff --git a/pkgs/homelab-docs/services.nix b/pkgs/homelab-docs/services.nix new file mode 100644 index 0000000..7e6c8a3 --- /dev/null +++ b/pkgs/homelab-docs/services.nix @@ -0,0 +1,270 @@ +# homelab-docs-services.nix - Service documentation generator CLI +{ + writeShellScriptBin, + jq, +}: +writeShellScriptBin "homelab-docs-services" '' + #!/usr/bin/env bash + set -euo pipefail + + cat << 'EOF' + # Service Catalog + + > Available services and their configuration options + > + > Generated on: $(date) + + This document catalogs all available homelab services, their configuration options, and integration capabilities. + + EOF + + # Get all services and their configurations + 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 []; + + # 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 {}; + }; + + 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: + 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; + 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); + } + ) serviceGroups; + + in { + services = uniqueServices; + totalUniqueServices = lib.length (lib.attrNames uniqueServices); + }') + + total_services=$(echo "$services_catalog" | ${jq}/bin/jq -r '.totalUniqueServices') + + echo "## Overview" + echo + echo "**Total Available Services:** $total_services" + echo + + # Create a summary table of services and their default integrations + echo "## Service Integration Matrix" + echo + echo "| Service | Monitoring | Logging | Proxy | Auth Default |" + echo "|---------|------------|---------|-------|--------------|" + + echo "$services_catalog" | ${jq}/bin/jq -r '.services | to_entries[] | .key' | 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') + + 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) + + echo "| \`$service\` | $monitoring_icon | $logging_icon | $proxy_icon | $auth_icon |" + done + + echo + echo "**Legend:** βœ… = Enabled by default, ❌ = Available but disabled, πŸ”’ = Auth required, 🌐 = Public access" + echo + + echo "## Service Reference" + echo + + # Process each service + echo "$services_catalog" | ${jq}/bin/jq -r '.services | to_entries[] | .key' | sort | while read -r service; do + echo "### $service" + echo + + # Get service details + service_data=$(echo "$services_catalog" | ${jq}/bin/jq -r ".services[\"$service\"]") + + 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\` |" + else + echo "| πŸ“Š Monitoring | ❌ Disabled | Available but requires \`monitoring.enable = true\` |" + 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" + 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 "};" + 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 "\`\`\`" + echo + fi + + echo "---" + echo + done + + echo "## Integration Summary" + echo + echo "### Available Integration Types" + 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 + echo "### Integration Benefits" + 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 + echo "---" + echo + echo "*This service catalog is generated from actual service configurations across your homelab fleet.*" +'' From 85526567310957f6112d6c4ca862377861384903 Mon Sep 17 00:00:00 2001 From: plasmagoat Date: Wed, 30 Jul 2025 00:22:33 +0200 Subject: [PATCH 4/8] 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 5/8] 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; -} From f9e276282a896c335157c843388c0beb73872eab Mon Sep 17 00:00:00 2001 From: plasmagoat Date: Wed, 30 Jul 2025 01:56:53 +0200 Subject: [PATCH 6/8] docs --- docs/README.md | 30 +++--------------------------- docs/current-deployment.md | 4 ++-- docs/fleet-overview.md | 4 ++-- docs/nodes.md | 2 +- docs/services.md | 2 +- flake.lock | 6 +++--- flake.nix | 2 +- pkgs/homelab-docs/deployment.nix | 4 ++-- pkgs/homelab-docs/fleet.nix | 4 ++-- pkgs/homelab-docs/nodes.nix | 4 ++-- pkgs/homelab-docs/readme.nix | 10 +++++----- pkgs/homelab-docs/services.nix | 4 ++-- 12 files changed, 26 insertions(+), 50 deletions(-) diff --git a/docs/README.md b/docs/README.md index 9984a6d..ce7e48e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,7 @@ > Auto-generated documentation for the homelab deployment > -> Generated on: $(date) +> Generated on: $(date -R) > Source: $(pwd) ## πŸ“š Documentation Files @@ -44,7 +44,7 @@ homelab-generate-docs /path/to/output - **Total Nodes**: 2 - **Homelab-Enabled Nodes**: 2 -- **Generated**: ons 30 jul 00:20:46 CEST 2025 +- **Generated**: ons 30 jul 01:56:04 CEST 2025 ## πŸ› οΈ Management Tools @@ -64,35 +64,11 @@ homelab-generate-docs /path/to/output ### GitHub Actions Example -```yaml -name: Generate Documentation -on: - push: - branches: [ main ] -jobs: - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v24 - - name: Generate docs - run: nix develop --command homelab-generate-docs ./docs - - name: Commit docs - run: | - git add docs/ - git commit -m "docs: update homelab documentation" || exit 0 - git push -``` ### Manual Generation -```bash -# From your homelab directory -nix develop -homelab-generate-docs ./docs -git add docs/ && git commit -m "Update docs" -``` + --- diff --git a/docs/current-deployment.md b/docs/current-deployment.md index c43eccf..9fe7850 100644 --- a/docs/current-deployment.md +++ b/docs/current-deployment.md @@ -2,8 +2,8 @@ > Current homelab deployment configuration > -> Generated on: $(date) -> Working directory: $(pwd) +> Generated on: Wed, 30 Jul 2025 01:55:52 +0200 +> Working directory: /home/plasmagoat/homelab ## Deployment Summary diff --git a/docs/fleet-overview.md b/docs/fleet-overview.md index 866c8bd..b8b7c7e 100644 --- a/docs/fleet-overview.md +++ b/docs/fleet-overview.md @@ -2,8 +2,8 @@ > Auto-generated fleet overview > -> Generated on: $(date) -> Source: $(pwd) +> Generated on: Wed, 30 Jul 2025 01:55:28 +0200 +> Source: /home/plasmagoat/homelab ## Fleet Statistics diff --git a/docs/nodes.md b/docs/nodes.md index 90a635a..9ef180a 100644 --- a/docs/nodes.md +++ b/docs/nodes.md @@ -2,7 +2,7 @@ > Detailed per-node configuration > -> Generated on: $(date) +> Generated on: Wed, 30 Jul 2025 01:55:38 +0200 ## Node: photos diff --git a/docs/services.md b/docs/services.md index 7e3571b..7d60e4a 100644 --- a/docs/services.md +++ b/docs/services.md @@ -2,7 +2,7 @@ > Complete service documentation with core options, feature integrations, and smart defaults > -> Generated on: $(date) +> Generated on: Wed, 30 Jul 2025 01:55:42 +0200 This document provides comprehensive documentation for homelab services, organized by: - **Core Service Options**: The main service configuration diff --git a/flake.lock b/flake.lock index 66740fb..e46dbf1 100644 --- a/flake.lock +++ b/flake.lock @@ -156,11 +156,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1750134718, - "narHash": "sha256-v263g4GbxXv87hMXMCpjkIxd/viIF7p3JpJrwgKdNiI=", + "lastModified": 1753694789, + "narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9e83b64f727c88a7711a2c463a7b16eedb69a84c", + "rev": "dc9637876d0dcc8c9e5e22986b857632effeb727", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index c183ee9..961a270 100644 --- a/flake.nix +++ b/flake.nix @@ -78,7 +78,7 @@ echo "🏠 Homelab Development Environment" echo "Available commands:" echo " colmena apply - Deploy all hosts" - echo " colmena apply --on HOST - Deploy specific host" + echo " colmena apply --on @tag - Deploy specific tagged hosts" echo " sops secrets/secrets.yaml - Edit secrets" echo "" ''; diff --git a/pkgs/homelab-docs/deployment.nix b/pkgs/homelab-docs/deployment.nix index f9636a7..8d7e18a 100644 --- a/pkgs/homelab-docs/deployment.nix +++ b/pkgs/homelab-docs/deployment.nix @@ -7,12 +7,12 @@ writeShellScriptBin "homelab-docs-deployment" '' #!/usr/bin/env bash set -euo pipefail - cat << 'EOF' + cat << EOF # Current Deployment State > Current homelab deployment configuration > - > Generated on: $(date) + > Generated on: $(date -R) > Working directory: $(pwd) ## Deployment Summary diff --git a/pkgs/homelab-docs/fleet.nix b/pkgs/homelab-docs/fleet.nix index 8ca3066..fc14680 100644 --- a/pkgs/homelab-docs/fleet.nix +++ b/pkgs/homelab-docs/fleet.nix @@ -6,12 +6,12 @@ writeShellScriptBin "homelab-docs-fleet" '' #!/usr/bin/env bash set -euo pipefail - cat << 'EOF' + cat << EOF # Homelab Fleet Overview > Auto-generated fleet overview > - > Generated on: $(date) + > Generated on: $(date -R) > Source: $(pwd) ## Fleet Statistics diff --git a/pkgs/homelab-docs/nodes.nix b/pkgs/homelab-docs/nodes.nix index b47fd05..7ea32fd 100644 --- a/pkgs/homelab-docs/nodes.nix +++ b/pkgs/homelab-docs/nodes.nix @@ -6,12 +6,12 @@ writeShellScriptBin "homelab-docs-nodes" '' #!/usr/bin/env bash set -euo pipefail - cat << 'EOF' + cat << EOF # Node Configurations > Detailed per-node configuration > - > Generated on: $(date) + > Generated on: $(date -R) EOF diff --git a/pkgs/homelab-docs/readme.nix b/pkgs/homelab-docs/readme.nix index 505d465..68ac51b 100644 --- a/pkgs/homelab-docs/readme.nix +++ b/pkgs/homelab-docs/readme.nix @@ -12,7 +12,7 @@ writeShellScriptBin "homelab-docs-readme" '' > Auto-generated documentation for the homelab deployment > - > Generated on: $(date) + > Generated on: $(date -R) > Source: $(pwd) ## πŸ“š Documentation Files @@ -88,7 +88,7 @@ writeShellScriptBin "homelab-docs-readme" '' echo echo "### GitHub Actions Example" echo - echo "\`\`\`yaml" + echo "```yaml" echo "name: Generate Documentation" echo "on:" echo " push:" @@ -107,16 +107,16 @@ writeShellScriptBin "homelab-docs-readme" '' echo " git add docs/" echo " git commit -m \"docs: update homelab documentation\" || exit 0" echo " git push" - echo "\`\`\`" + echo "```" echo echo "### Manual Generation" echo - echo "\`\`\`bash" + echo "```bash" echo "# From your homelab directory" echo "nix develop" echo "homelab-generate-docs ./docs" echo "git add docs/ && git commit -m \"Update docs\"" - echo "\`\`\`" + echo "```" echo echo "---" echo diff --git a/pkgs/homelab-docs/services.nix b/pkgs/homelab-docs/services.nix index 808c746..2fed267 100644 --- a/pkgs/homelab-docs/services.nix +++ b/pkgs/homelab-docs/services.nix @@ -6,12 +6,12 @@ writeShellScriptBin "homelab-docs-services" '' #!/usr/bin/env bash set -euo pipefail - cat << 'EOF' + cat << EOF # Service Catalog > Complete service documentation with core options, feature integrations, and smart defaults > - > Generated on: $(date) + > Generated on: $(date -R) This document provides comprehensive documentation for homelab services, organized by: - **Core Service Options**: The main service configuration From 6ba25b90a9778e771830e8240562e2fd52010d40 Mon Sep 17 00:00:00 2001 From: plasmagoat Date: Wed, 30 Jul 2025 02:13:32 +0200 Subject: [PATCH 7/8] yay --- docs/README.md | 32 ++++++++++++++++++++++++++++---- docs/current-deployment.md | 2 +- docs/fleet-overview.md | 2 +- docs/nodes.md | 2 +- docs/services.md | 2 +- pkgs/homelab-docs/readme.nix | 20 ++++++++++---------- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/docs/README.md b/docs/README.md index ce7e48e..9aca520 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,8 +2,8 @@ > Auto-generated documentation for the homelab deployment > -> Generated on: $(date -R) -> Source: $(pwd) +> Generated on: Wed, 30 Jul 2025 02:13:08 +0200 +> Source: /home/plasmagoat/homelab ## πŸ“š Documentation Files @@ -44,7 +44,7 @@ homelab-generate-docs /path/to/output - **Total Nodes**: 2 - **Homelab-Enabled Nodes**: 2 -- **Generated**: ons 30 jul 01:56:04 CEST 2025 +- **Generated**: Wed, 30 Jul 2025 02:13:11 +0200 ## πŸ› οΈ Management Tools @@ -64,11 +64,35 @@ homelab-generate-docs /path/to/output ### GitHub Actions Example +```yaml +name: Generate Documentation +on: + push: + branches: [ main ] +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v24 + - name: Generate docs + run: nix develop --command homelab-generate-docs ./docs + - name: Commit docs + run: | + git add docs/ + git commit -m "docs: update homelab documentation" || exit 0 + git push +``` ### Manual Generation - +```bash +# From your homelab directory +nix develop +homelab-generate-docs ./docs +git add docs/ && git commit -m "Update docs" +``` --- diff --git a/docs/current-deployment.md b/docs/current-deployment.md index 9fe7850..fbfabf3 100644 --- a/docs/current-deployment.md +++ b/docs/current-deployment.md @@ -2,7 +2,7 @@ > Current homelab deployment configuration > -> Generated on: Wed, 30 Jul 2025 01:55:52 +0200 +> Generated on: Wed, 30 Jul 2025 02:13:01 +0200 > Working directory: /home/plasmagoat/homelab ## Deployment Summary diff --git a/docs/fleet-overview.md b/docs/fleet-overview.md index b8b7c7e..92f2991 100644 --- a/docs/fleet-overview.md +++ b/docs/fleet-overview.md @@ -2,7 +2,7 @@ > Auto-generated fleet overview > -> Generated on: Wed, 30 Jul 2025 01:55:28 +0200 +> Generated on: Wed, 30 Jul 2025 02:12:41 +0200 > Source: /home/plasmagoat/homelab ## Fleet Statistics diff --git a/docs/nodes.md b/docs/nodes.md index 9ef180a..de23565 100644 --- a/docs/nodes.md +++ b/docs/nodes.md @@ -2,7 +2,7 @@ > Detailed per-node configuration > -> Generated on: Wed, 30 Jul 2025 01:55:38 +0200 +> Generated on: Wed, 30 Jul 2025 02:12:50 +0200 ## Node: photos diff --git a/docs/services.md b/docs/services.md index 7d60e4a..f7cba44 100644 --- a/docs/services.md +++ b/docs/services.md @@ -2,7 +2,7 @@ > Complete service documentation with core options, feature integrations, and smart defaults > -> Generated on: Wed, 30 Jul 2025 01:55:42 +0200 +> Generated on: Wed, 30 Jul 2025 02:12:53 +0200 This document provides comprehensive documentation for homelab services, organized by: - **Core Service Options**: The main service configuration diff --git a/pkgs/homelab-docs/readme.nix b/pkgs/homelab-docs/readme.nix index 68ac51b..ea72b8f 100644 --- a/pkgs/homelab-docs/readme.nix +++ b/pkgs/homelab-docs/readme.nix @@ -7,7 +7,7 @@ writeShellScriptBin "homelab-docs-readme" '' #!/usr/bin/env bash set -euo pipefail - cat << 'EOF' + cat << EOF # Homelab Documentation > Auto-generated documentation for the homelab deployment @@ -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 @@ -68,7 +68,7 @@ writeShellScriptBin "homelab-docs-readme" '' echo "- **Total Nodes**: $total_nodes" echo "- **Homelab-Enabled Nodes**: $homelab_nodes" - echo "- **Generated**: $(date)" + echo "- **Generated**: $(date -R)" echo echo "## πŸ› οΈ Management Tools" echo @@ -88,7 +88,7 @@ writeShellScriptBin "homelab-docs-readme" '' echo echo "### GitHub Actions Example" echo - echo "```yaml" + echo "\`\`\`yaml" echo "name: Generate Documentation" echo "on:" echo " push:" @@ -107,16 +107,16 @@ writeShellScriptBin "homelab-docs-readme" '' echo " git add docs/" echo " git commit -m \"docs: update homelab documentation\" || exit 0" echo " git push" - echo "```" + echo "\`\`\`" echo echo "### Manual Generation" echo - echo "```bash" + echo "\`\`\`bash" echo "# From your homelab directory" echo "nix develop" echo "homelab-generate-docs ./docs" echo "git add docs/ && git commit -m \"Update docs\"" - echo "```" + echo "\`\`\`" echo echo "---" echo From 3082c898c703696ba8247787cdcdb6e6e428a1d6 Mon Sep 17 00:00:00 2001 From: Forgejo Bot Date: Wed, 30 Jul 2025 06:00:55 +0000 Subject: [PATCH 8/8] feat: automated changes --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index e46dbf1..300e8c9 100644 --- a/flake.lock +++ b/flake.lock @@ -156,11 +156,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1753694789, - "narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=", + "lastModified": 1750134718, + "narHash": "sha256-v263g4GbxXv87hMXMCpjkIxd/viIF7p3JpJrwgKdNiI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "dc9637876d0dcc8c9e5e22986b857632effeb727", + "rev": "9e83b64f727c88a7711a2c463a7b16eedb69a84c", "type": "github" }, "original": { @@ -204,11 +204,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1753795159, - "narHash": "sha256-0fOuNh5MefjES+ie0zV3mVMSs1RwXhVIxcNQuu+Q4g4=", + "lastModified": 1753851946, + "narHash": "sha256-4mPb6o8uWlVdAchtkcF81gjJbQFuCZTz2s/n8neYLx8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5a012ffbe2494cb777ec3dbace5811f927bddc72", + "rev": "bf8fe59e5830c2afc6961f11ac941660d6f3fa80", "type": "github" }, "original": {