homelab/modules/nixos/global-config.nix
plasmagoat bcbcc8b17b
Some checks failed
Test / tests (push) Has been cancelled
/ OpenTofu (push) Has been cancelled
homelab framework module init (everything is a mess)
2025-07-28 02:05:13 +02:00

462 lines
13 KiB
Nix

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