dump
Some checks failed
Test / tests (push) Failing after 16m54s
/ OpenTofu (push) Successful in 17s

This commit is contained in:
plasmagoat 2025-11-18 20:00:39 +01:00
parent 6ba25b90a9
commit 0f49c6c37c
35 changed files with 747 additions and 120 deletions

View file

@ -24,7 +24,7 @@ with lib; let
enhancer = entry:
entry
// {
_upstream = "http://${entry.host}:${toString entry.port}${entry.path or ""}";
_upstream = "http://${entry.host}:${toString entry.port}";
_fqdn = "${entry.subdomain}.${entry._nodeConfig.config.homelab.externalDomain or homelabCfg.externalDomain}";
_internal = "${entry.host}:${toString entry.port}";
};

View file

@ -0,0 +1,162 @@
{
config,
lib,
pkgs,
...
}:
with lib; let
serviceName = "alertmanager";
cfg = config.homelab.services.${serviceName};
homelabCfg = config.homelab;
in {
imports = [
(import ../lib/features/monitoring.nix serviceName)
(import ../lib/features/logging.nix serviceName)
(import ../lib/features/proxy.nix serviceName)
];
# Core service options
options.homelab.services.${serviceName} = {
enable = mkEnableOption "Vault Warden";
description = mkOption {
type = types.str;
default = "Vault Warden";
};
port = mkOption {
type = types.port;
default = 9093;
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = ''
Whether to open the ports specified in `port` and `webPort` in the firewall.
'';
};
environmentFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
example = "/var/lib/vaultwarden.env";
description = ''
Additional environment file as defined in {manpage}`systemd.exec(5)`.
Secrets like {env}`ADMIN_TOKEN` and {env}`SMTP_PASSWORD`
should be passed to the service without adding them to the world-readable Nix store.
Note that this file needs to be available on the host on which `vaultwarden` is running.
As a concrete example, to make the Admin UI available (from which new users can be invited initially),
the secret {env}`ADMIN_TOKEN` needs to be defined as described
[here](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page):
```
# Admin secret token, see
# https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page
ADMIN_TOKEN=...copy-paste a unique generated secret token here...
```
'';
};
systemdServices = mkOption {
type = types.listOf types.str;
default = [
"vaultwarden.service"
"vaultwarden"
];
description = "Systemd services to monitor";
};
};
# Service configuration with smart defaults
config = mkIf cfg.enable (mkMerge [
{
services.prometheus.alertmanager = {
enable = true;
openFirewall = cfg.openFirewall;
environmentFile = alertmanagerEnv;
webExternalUrl = "http://monitor.lab:9093"; # optional but helpful
configuration = {
route = {
receiver = "null";
group_by = ["alertname"];
group_wait = "10s";
group_interval = "5m";
repeat_interval = "4h";
routes = [
{
receiver = "telegram";
matchers = [
"severity =~ \"warning|critical\""
];
group_wait = "10s";
continue = true;
}
];
};
receivers = [
{name = "null";}
{
name = "telegram";
telegram_configs = [
{
api_url = "https://api.telegram.org";
bot_token = "$TELEGRAM_BOT_TOKEN";
chat_id = -1002642560007;
message_thread_id = 4;
parse_mode = "HTML";
send_resolved = true;
message = "{{ template \"telegram.message\". }}";
}
];
}
];
templates = [
(pkgs.writeText "telegram.tmpl" (builtins.readFile ./provisioning/templates/telegram.tmpl))
# (pkgs.writeText "telegram.markdown.v2.tmpl" (builtins.readFile ./provisioning/templates/telegram.markdown.v2.tmpl))
];
};
};
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.port];
}
{
homelab.services.${serviceName}.monitoring = {
metrics.path = "/metrics";
healthCheck.path = "/healthz";
healthCheck.conditions = ["[STATUS] == 200" "[RESPONSE_TIME] < 1000"];
extraLabels = {
component = "example";
};
};
}
{
# homelab.services.${serviceName}.logging = {
# files = ["/var/log/example/log.log"];
# # parsing = {
# # regex = "^ts=(?P<timestamp>[^ ]+) caller=(?P<caller>[^ ]+) level=(?P<level>\\w+) msg=\"(?P<message>[^\"]*)\"";
# # extractFields = ["level" "caller"];
# # };
# extraLabels = {
# component = "example";
# application = "example";
# };
# };
}
{
homelab.services.${serviceName}.proxy = {
enableAuth = true;
};
}
]);
}

View file

@ -0,0 +1,96 @@
{
config,
lib,
...
}:
with lib; let
serviceName = "caddy";
cfg = config.homelab.services.${serviceName};
homelabCfg = config.homelab;
allProxyEntries = homelabCfg.reverseProxy.global.allEntries;
generateVirtualHosts = entries:
listToAttrs (map (entry: {
name = entry._fqdn;
value = {
extraConfig = ''
reverse_proxy ${entry._upstream}
'';
};
})
entries);
in {
imports = [
(import ../lib/features/monitoring.nix serviceName)
(import ../lib/features/logging.nix serviceName)
];
# Core service options
options.homelab.services.${serviceName} = {
enable = mkEnableOption "Caddy web server";
description = mkOption {
type = types.str;
default = "Caddy web server";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = ''
Whether to open the ports specified in `port` and `webPort` in the firewall.
'';
};
systemdServices = mkOption {
type = types.listOf types.str;
default = [
"caddy.service"
"caddy"
];
description = "Systemd services to monitor";
};
};
# Service configuration with smart defaults
config = mkIf cfg.enable (mkMerge [
{
services.caddy = {
enable = true;
virtualHosts = generateVirtualHosts allProxyEntries;
};
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [80 443];
}
{
# homelab.services.${serviceName}.monitoring = {
# metrics.path = "/metrics";
# healthCheck.path = "/healthz";
# healthCheck.conditions = ["[STATUS] == 200" "[RESPONSE_TIME] < 1000"];
# extraLabels = {
# component = "example";
# };
# };
}
{
# homelab.services.${serviceName}.logging = {
# files = ["/var/log/example/log.log"];
# # parsing = {
# # regex = "^ts=(?P<timestamp>[^ ]+) caller=(?P<caller>[^ ]+) level=(?P<level>\\w+) msg=\"(?P<message>[^\"]*)\"";
# # extractFields = ["level" "caller"];
# # };
# extraLabels = {
# component = "example";
# application = "example";
# };
# };
}
{
# homelab.services.${serviceName}.proxy = {
# enableAuth = true;
# };
}
]);
}

View file

@ -4,6 +4,26 @@
./gatus.nix
./prometheus.nix
./grafana.nix
./example.nix
./vaultwarden.nix
# ./monitoring/loki.nix
#
#
# TODO
#
# ./alertmanager.nix
# ./dnsmasq.nix
# ./authelia.nix
# ./lldap.nix
# ./roundcube.nix
# ./mailserver.nix
./caddy.nix
# ./traefik.nix
# ./ente-photos.nix
# ./forgejo.nix
# ./forgejo-runner.nix
# ./jellyfin.nix
# ./arr.nix
#
];
}

View file

@ -0,0 +1,86 @@
{
config,
lib,
pkgs,
...
}:
with lib; let
serviceName = "example";
cfg = config.homelab.services.${serviceName};
homelabCfg = config.homelab;
in {
imports = [
(import ../lib/features/monitoring.nix serviceName)
(import ../lib/features/logging.nix serviceName)
(import ../lib/features/proxy.nix serviceName)
];
# Core service options
options.homelab.services.${serviceName} = {
enable = mkEnableOption "Example Homelab Service";
description = mkOption {
type = types.str;
default = "Example Homelab Service";
};
port = mkOption {
type = types.port;
default = 1234;
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = ''
Whether to open the ports specified in `port` and `webPort` in the firewall.
'';
};
systemdServices = mkOption {
type = types.listOf types.str;
default = [
"example.service"
"example"
];
description = "Systemd services to monitor";
};
};
# Service configuration with smart defaults
config = mkIf cfg.enable (mkMerge [
{
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.port];
}
{
homelab.services.${serviceName}.monitoring = {
metrics.path = "/metrics";
healthCheck.path = "/healthz";
healthCheck.conditions = ["[STATUS] == 200" "[RESPONSE_TIME] < 1000"];
extraLabels = {
component = "example";
};
};
}
{
homelab.services.${serviceName}.logging = {
files = ["/var/log/example/log.log"];
# parsing = {
# regex = "^ts=(?P<timestamp>[^ ]+) caller=(?P<caller>[^ ]+) level=(?P<level>\\w+) msg=\"(?P<message>[^\"]*)\"";
# extractFields = ["level" "caller"];
# };
extraLabels = {
component = "example";
application = "example";
};
};
}
{
homelab.services.${serviceName}.proxy = {
enableAuth = true;
};
}
]);
}

View file

@ -1,60 +0,0 @@
{
config,
lib,
...
}:
with lib; let
cfg = config.homelab.services.monitoring-stack;
in {
imports = [
./prometheus.nix
./alertmanager.nix
./grafana.nix
];
options.homelab.services.monitoring-stack = {
enable = mkEnableOption "Complete monitoring stack (Prometheus + Alertmanager + Grafana)";
prometheus = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable Prometheus";
};
};
alertmanager = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable Alertmanager";
};
};
grafana = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable Grafana";
};
};
};
config = mkIf cfg.enable {
# Enable services based on configuration
homelab.services.prometheus.enable = mkDefault cfg.prometheus.enable;
homelab.services.alertmanager.enable = mkDefault cfg.alertmanager.enable;
homelab.services.grafana.enable = mkDefault cfg.grafana.enable;
# Configure Prometheus to use Alertmanager if both are enabled
homelab.services.prometheus.alertmanager = mkIf (cfg.prometheus.enable && cfg.alertmanager.enable) {
enable = true;
url = "http://localhost:${toString config.homelab.services.alertmanager.port}";
};
# Configure Grafana to use Prometheus if both are enabled
homelab.services.grafana.datasources.prometheus = mkIf (cfg.prometheus.enable && cfg.grafana.enable) {
url = "http://localhost:${toString config.homelab.services.prometheus.port}";
};
};
}

View file

@ -11,7 +11,6 @@ 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;
jobGroups = groupBy (m: m.jobName) allMetrics;

View file

@ -0,0 +1,137 @@
{
config,
lib,
pkgs,
...
}:
with lib; let
serviceName = "vaultwarden";
cfg = config.homelab.services.${serviceName};
homelabCfg = config.homelab;
in {
imports = [
(import ../lib/features/monitoring.nix serviceName)
(import ../lib/features/logging.nix serviceName)
(import ../lib/features/proxy.nix serviceName)
];
# Core service options
options.homelab.services.${serviceName} = {
enable = mkEnableOption "Vault Warden";
description = mkOption {
type = types.str;
default = "Vault Warden";
};
port = mkOption {
type = types.port;
default = 8222;
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = ''
Whether to open the ports specified in `port` and `webPort` in the firewall.
'';
};
environmentFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
example = "/var/lib/vaultwarden.env";
description = ''
Additional environment file as defined in {manpage}`systemd.exec(5)`.
Secrets like {env}`ADMIN_TOKEN` and {env}`SMTP_PASSWORD`
should be passed to the service without adding them to the world-readable Nix store.
Note that this file needs to be available on the host on which `vaultwarden` is running.
As a concrete example, to make the Admin UI available (from which new users can be invited initially),
the secret {env}`ADMIN_TOKEN` needs to be defined as described
[here](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page):
```
# Admin secret token, see
# https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page
ADMIN_TOKEN=...copy-paste a unique generated secret token here...
```
'';
};
systemdServices = mkOption {
type = types.listOf types.str;
default = [
"vaultwarden.service"
"vaultwarden"
];
description = "Systemd services to monitor";
};
};
# Service configuration with smart defaults
config = mkIf cfg.enable (mkMerge [
{
services.vaultwarden = {
enable = true;
config = {
DOMAIN = "https://bitwarden.example.com";
SIGNUPS_ALLOWED = false;
ROCKET_ADDRESS = "0.0.0.0";
ROCKET_PORT = cfg.port;
ROCKET_LOG = "critical";
# This example assumes a mailserver running on localhost,
# thus without transport encryption.
# If you use an external mail server, follow:
# https://github.com/dani-garcia/vaultwarden/wiki/SMTP-configuration
# SMTP_HOST = "127.0.0.1";
# SMTP_PORT = 25;
# SMTP_SSL = false;
# SMTP_FROM = "admin@bitwarden.example.com";
# SMTP_FROM_NAME = "example.com Bitwarden server";
ADMIN_TOKEN = "1234";
};
environmentFile = cfg.environmentFile;
};
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.port];
}
{
# homelab.services.${serviceName}.monitoring = {
# metrics.path = "/metrics";
# healthCheck.path = "/healthz";
# healthCheck.conditions = ["[STATUS] == 200" "[RESPONSE_TIME] < 1000"];
# extraLabels = {
# component = "example";
# };
# };
}
{
# homelab.services.${serviceName}.logging = {
# files = ["/var/log/example/log.log"];
# # parsing = {
# # regex = "^ts=(?P<timestamp>[^ ]+) caller=(?P<caller>[^ ]+) level=(?P<level>\\w+) msg=\"(?P<message>[^\"]*)\"";
# # extractFields = ["level" "caller"];
# # };
# extraLabels = {
# component = "example";
# application = "example";
# };
# };
}
{
homelab.services.${serviceName}.proxy = {
enableAuth = true;
};
}
]);
}