244 lines
6.0 KiB
Nix
244 lines
6.0 KiB
Nix
{
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
nginxEnabled = config.mod.nginx.enable;
|
|
|
|
script = pkgs.writeShellScript "bt-reset" ''
|
|
set -euo pipefail
|
|
export PATH="${
|
|
lib.makeBinPath [
|
|
pkgs.bluez
|
|
pkgs.util-linux
|
|
pkgs.kmod
|
|
pkgs.gnugrep
|
|
pkgs.coreutils
|
|
]
|
|
}"
|
|
|
|
logger -t bt-reset "Starting Bluetooth adapter reset..."
|
|
|
|
# Exit early if the adapter is already present and running
|
|
if hciconfig hci0 2>/dev/null | grep -q "UP RUNNING"; then
|
|
logger -t bt-reset "hci0 is already UP RUNNING — nothing to do"
|
|
exit 0
|
|
fi
|
|
|
|
# If hci0 exists but isn't UP, try bringing it up
|
|
if hciconfig hci0 2>/dev/null; then
|
|
logger -t bt-reset "hci0 exists but not running — bringing it up"
|
|
hciconfig hci0 up || true
|
|
sleep 2
|
|
|
|
if hciconfig hci0 2>/dev/null | grep -q "UP RUNNING"; then
|
|
logger -t bt-reset "hci0 is UP now"
|
|
systemctl restart bluetooth.service
|
|
logger -t bt-reset "bluetooth.service restarted — done"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# Hard reset: reload the btusb kernel module (works for USB adapters)
|
|
logger -t bt-reset "hci0 missing — reloading btusb module..."
|
|
modprobe -r btusb 2>/dev/null || true
|
|
sleep 3
|
|
modprobe btusb
|
|
sleep 3
|
|
|
|
if hciconfig hci0 2>/dev/null; then
|
|
hciconfig hci0 up
|
|
logger -t bt-reset "hci0 restored after module reload"
|
|
else
|
|
logger -t bt-reset "ERROR: hci0 not found after module reload"
|
|
exit 1
|
|
fi
|
|
|
|
# Restart the bluetooth systemd service so bluetoothd picks up the adapter
|
|
systemctl restart bluetooth.service
|
|
logger -t bt-reset "bluetooth.service restarted — done"
|
|
'';
|
|
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"
|
|
];
|
|
};
|
|
};
|
|
|
|
services = {
|
|
blueman.enable = true;
|
|
|
|
nginx = lib.mkIf nginxEnabled {
|
|
recommendedProxySettings = true;
|
|
|
|
virtualHosts."ha.ppp.pm" = {
|
|
forceSSL = true;
|
|
useACMEHost = "ha.ppp.pm";
|
|
|
|
extraConfig = ''
|
|
proxy_buffering off;
|
|
'';
|
|
|
|
locations."/" = {
|
|
proxyPass = "http://127.0.0.1:8123";
|
|
proxyWebsockets = true;
|
|
};
|
|
};
|
|
};
|
|
|
|
# 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" ];
|
|
};
|
|
|
|
bt-reset = {
|
|
description = "Reset Bluetooth adapter";
|
|
after = [ "bluetooth.service" ];
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
ExecStart = script;
|
|
|
|
Restart = "on-failure";
|
|
RestartSec = "10s";
|
|
StartLimitIntervalSec = "120";
|
|
StartLimitBurst = 3;
|
|
};
|
|
};
|
|
};
|
|
|
|
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";
|
|
};
|
|
|
|
timerConfig = {
|
|
Unit = "update-hetzner-dns.service";
|
|
OnCalendar = "*-*-* *:00/30:00";
|
|
Persistent = true;
|
|
};
|
|
|
|
wantedBy = [ "timers.target" ];
|
|
};
|
|
};
|
|
|
|
services = {
|
|
"update-hetzner-dns" = {
|
|
unitConfig = {
|
|
Description = "updates Hetzner DNS records";
|
|
};
|
|
|
|
serviceConfig = {
|
|
Type = "exec";
|
|
EnvironmentFile = config.age.secrets.hetzner-dns.path;
|
|
};
|
|
|
|
path = [
|
|
pkgs.curl
|
|
pkgs.coreutils
|
|
pkgs.jq
|
|
];
|
|
|
|
script = ''
|
|
SUBDOMAINS="ha komga"
|
|
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";
|
|
};
|
|
};
|
|
};
|
|
}
|