manatee: Update monitoring in home-assistant

- Add all disks to smartd
- Generate home-assistant config in nix
- Add metrics for all HDDs
This commit is contained in:
Alexander Heldt
2026-05-30 16:54:40 +00:00
parent d291633fe2
commit 4a63c4eb5e
4 changed files with 353 additions and 141 deletions
+188 -141
View File
@@ -6,8 +6,42 @@
}:
let
nginxEnabled = config.mod.nginx.enable;
cfg = config.mod.home-assistant;
script = pkgs.writeShellScript "bt-reset" ''
configFile = pkgs.writeText "ha-configuration.yaml" ''
# Loads default set of integrations. Do not remove.
default_config:
http:
use_x_forwarded_for: true
trusted_proxies:
- 127.0.0.1
# Load frontend themes from the themes folder
frontend:
themes: !include_dir_merge_named themes
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
recorder:
purge_keep_days: 365
alert:
fridge_door:
name: Fridge is open
done_message: Fride is closed
entity_id: binary_sensor.kyldorr
state: "on"
repeat: 2
skip_first: true
notifiers:
- mobile_app_pixel_9_pro
${cfg.extraConfig}'';
btResetScript = pkgs.writeShellScript "bt-reset" ''
set -euo pipefail
export PATH="${
lib.makeBinPath [
@@ -62,181 +96,194 @@ let
'';
in
{
mod.homepage.services = [
{
name = "Home Assistant";
port = 8123;
description = "Home automation";
}
];
hardware.bluetooth.enable = true;
virtualisation.oci-containers = {
backend = "podman";
containers.homeassistant = {
image = "ghcr.io/home-assistant/home-assistant:stable";
volumes = [
"/home/alex/.config/home-assistant:/config"
# Pass in bluetooth
"/run/dbus:/run/dbus:ro"
];
environment.TZ = "Europe/Stockholm";
extraOptions = [
"--network=host"
# Allows HA to perform low-level network operations (scan/reset adapter)
"--cap-add=NET_ADMIN"
"--cap-add=NET_RAW"
# Pass in Zigbee antenna
"--device=/dev/serial/by-id/usb-Nabu_Casa_ZBT-2_9C139EAAD464-if00:/dev/ttyACM0"
];
options = {
mod.home-assistant = {
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Extra YAML to append to Home Assistant's configuration.yaml";
};
};
};
services = {
blueman.enable = true;
config = {
mod.homepage.services = [
{
name = "Home Assistant";
port = 8123;
description = "Home automation";
}
];
nginx = lib.mkIf nginxEnabled {
recommendedProxySettings = true;
hardware.bluetooth.enable = true;
virtualHosts."ha.ppp.pm" = {
forceSSL = true;
useACMEHost = "ha.ppp.pm";
virtualisation.oci-containers = {
backend = "podman";
extraConfig = ''
proxy_buffering off;
'';
containers.homeassistant = {
image = "ghcr.io/home-assistant/home-assistant:stable";
locations."/" = {
proxyPass = "http://127.0.0.1:8123";
proxyWebsockets = true;
};
volumes = [
"/home/alex/.config/home-assistant:/config"
"${configFile}:/config/configuration.yaml:ro"
# Pass in bluetooth
"/run/dbus:/run/dbus:ro"
];
environment.TZ = "Europe/Stockholm";
extraOptions = [
"--network=host"
# Allows HA to perform low-level network operations (scan/reset adapter)
"--cap-add=NET_ADMIN"
"--cap-add=NET_RAW"
# Pass in Zigbee antenna
"--device=/dev/serial/by-id/usb-Nabu_Casa_ZBT-2_9C139EAAD464-if00:/dev/ttyACM0"
];
};
};
# Trigger reset via udev when hci0 disappears
udev.extraRules = ''
ACTION=="remove", SUBSYSTEM=="bluetooth", KERNEL=="hci0", \
TAG+="systemd", ENV{SYSTEMD_WANTS}+="bt-reset.service"
'';
};
systemd = {
services = {
# Trigger reset on bluetoothd failure
bluetooth = {
unitConfig.OnFailure = [ "bt-reset.service" ];
};
blueman.enable = true;
bt-reset = {
description = "Reset Bluetooth adapter";
after = [ "bluetooth.service" ];
nginx = lib.mkIf nginxEnabled {
recommendedProxySettings = true;
serviceConfig = {
Type = "oneshot";
ExecStart = script;
virtualHosts."ha.ppp.pm" = {
forceSSL = true;
useACMEHost = "ha.ppp.pm";
Restart = "on-failure";
RestartSec = "10s";
StartLimitIntervalSec = "120";
StartLimitBurst = 3;
};
};
};
extraConfig = ''
proxy_buffering off;
'';
timers.bt-reset = {
description = "Periodically reset Bluetooth adapter";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5min"; # first run 5 min after boot
OnUnitActiveSec = "4h"; # then every 4 hours
RandomizedDelaySec = "5min";
};
};
user = {
timers = {
"update-hetzner-dns" = {
unitConfig = {
Description = "updates Hetzner DNS records";
locations."/" = {
proxyPass = "http://127.0.0.1:8123";
proxyWebsockets = true;
};
timerConfig = {
Unit = "update-hetzner-dns.service";
OnCalendar = "*-*-* *:00/30:00";
Persistent = true;
};
wantedBy = [ "timers.target" ];
};
};
# Trigger reset via udev when hci0 disappears
udev.extraRules = ''
ACTION=="remove", SUBSYSTEM=="bluetooth", KERNEL=="hci0", \
TAG+="systemd", ENV{SYSTEMD_WANTS}+="bt-reset.service"
'';
};
systemd = {
services = {
"update-hetzner-dns" = {
unitConfig = {
Description = "updates Hetzner DNS records";
};
# Trigger reset on bluetoothd failure
bluetooth = {
unitConfig.OnFailure = [ "bt-reset.service" ];
};
bt-reset = {
description = "Reset Bluetooth adapter";
after = [ "bluetooth.service" ];
serviceConfig = {
Type = "exec";
EnvironmentFile = config.age.secrets.hetzner-dns.path;
Type = "oneshot";
ExecStart = btResetScript;
Restart = "on-failure";
RestartSec = "10s";
StartLimitIntervalSec = "120";
StartLimitBurst = 3;
};
};
};
path = [
pkgs.curl
pkgs.coreutils
pkgs.jq
];
timers.bt-reset = {
description = "Periodically reset Bluetooth adapter";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5min"; # first run 5 min after boot
OnUnitActiveSec = "4h"; # then every 4 hours
RandomizedDelaySec = "5min";
};
};
script = ''
SUBDOMAINS="ha komga romm"
INTERFACE="enp3s0"
user = {
timers = {
"update-hetzner-dns" = {
unitConfig = {
Description = "updates Hetzner DNS records";
};
CURRENT_IP=$(curl -s --fail --interface "$INTERFACE" ifconfig.me)
timerConfig = {
Unit = "update-hetzner-dns.service";
OnCalendar = "*-*-* *:00/30:00";
Persistent = true;
};
for SUBDOMAIN in $SUBDOMAINS; do
LAST_IP_FILE="/tmp/hetzner-dns-''${SUBDOMAIN}-ip"
wantedBy = [ "timers.target" ];
};
};
LAST_IP=""
if [[ -f "$LAST_IP_FILE" ]]; then
LAST_IP=$(cat "$LAST_IP_FILE")
fi
services = {
"update-hetzner-dns" = {
unitConfig = {
Description = "updates Hetzner DNS records";
};
if [[ "$CURRENT_IP" == "$LAST_IP" ]]; then
echo "$SUBDOMAIN: IP unchanged, NOOP update."
else
echo "$SUBDOMAIN: Updating IP"
serviceConfig = {
Type = "exec";
EnvironmentFile = config.age.secrets.hetzner-dns.path;
};
JSON_BODY=$(jq -n --arg ip "$CURRENT_IP" '{records: [{value: $ip}]}')
path = [
pkgs.curl
pkgs.coreutils
pkgs.jq
];
curl \
--fail \
-X POST \
-H "Authorization: Bearer $HETZNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$JSON_BODY" \
"https://api.hetzner.cloud/v1/zones/ppp.pm/rrsets/''${SUBDOMAIN}/A/actions/set_records" \
&& echo $CURRENT_IP > $LAST_IP_FILE
fi
done
'';
script = ''
SUBDOMAINS="ha komga romm"
INTERFACE="enp3s0"
CURRENT_IP=$(curl -s --fail --interface "$INTERFACE" ifconfig.me)
for SUBDOMAIN in $SUBDOMAINS; do
LAST_IP_FILE="/tmp/hetzner-dns-''${SUBDOMAIN}-ip"
LAST_IP=""
if [[ -f "$LAST_IP_FILE" ]]; then
LAST_IP=$(cat "$LAST_IP_FILE")
fi
if [[ "$CURRENT_IP" == "$LAST_IP" ]]; then
echo "$SUBDOMAIN: IP unchanged, NOOP update."
else
echo "$SUBDOMAIN: Updating IP"
JSON_BODY=$(jq -n --arg ip "$CURRENT_IP" '{records: [{value: $ip}]}')
curl \
--fail \
-X POST \
-H "Authorization: Bearer $HETZNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$JSON_BODY" \
"https://api.hetzner.cloud/v1/zones/ppp.pm/rrsets/''${SUBDOMAIN}/A/actions/set_records" \
&& echo $CURRENT_IP > $LAST_IP_FILE
fi
done
'';
};
};
};
};
};
age = {
secrets = {
"hetzner-dns" = {
file = ../../../../secrets/manatee/hetzner-dns.age;
owner = "alex";
group = "users";
age = {
secrets = {
"hetzner-dns" = {
file = ../../../../secrets/manatee/hetzner-dns.age;
owner = "alex";
group = "users";
};
};
};
};