136 lines
4.6 KiB
Nix
136 lines
4.6 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
let
|
|
cfg = config.sane.services.dyn-dns;
|
|
# getIp = pkgs.writeShellScript "dyn-dns-query-wan" ''
|
|
# # preferred method and fallback
|
|
# # OPNsense router broadcasts its UPnP endpoint every 30s
|
|
# timeout 60 ${lib.getExe pkgs.sane-scripts.ip-check} --json || \
|
|
# ${lib.getExe pkgs.sane-scripts.ip-check} --json --no-upnp
|
|
# '';
|
|
getIp = "${lib.getExe pkgs.sane-scripts.ip-check} --json";
|
|
in
|
|
{
|
|
options = {
|
|
sane.services.dyn-dns = {
|
|
enable = mkEnableOption "keep track of the public WAN address of this machine, as viewed externally";
|
|
|
|
ipPath = mkOption {
|
|
default = "/var/lib/uninsane/wan.txt";
|
|
type = types.str;
|
|
description = "where to store the latest WAN IPv4 address";
|
|
};
|
|
|
|
upnpPath = mkOption {
|
|
default = "/var/lib/uninsane/upnp.txt";
|
|
type = types.str;
|
|
description = ''
|
|
where to store the address of the UPNP device (if any) that can be used to create port forwards.
|
|
'';
|
|
};
|
|
|
|
ipCmd = mkOption {
|
|
default = getIp;
|
|
type = types.path;
|
|
description = "command to run to query the current WAN IP";
|
|
};
|
|
|
|
interval = mkOption {
|
|
type = types.str;
|
|
default = "10min";
|
|
description = "systemd time string for how frequently to re-check the IP";
|
|
};
|
|
|
|
restartOnChange = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = "list of systemd units to restart when the IP changes";
|
|
};
|
|
requireForStart = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = "list of systemd units that should not be started until after we know an IP";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
systemd.services.dyn-dns = {
|
|
description = "update this host's record of its WAN IP";
|
|
serviceConfig.Type = "oneshot";
|
|
restartTriggers = [(builtins.toJSON cfg)];
|
|
|
|
after = [ "network.target" ];
|
|
|
|
serviceConfig.Restart = "on-failure";
|
|
serviceConfig.RestartSec = "60s";
|
|
# XXX(2024-11-11): OPNsense *used* to broadcast its UPnP endpoint every 30s; now it's every 5-10m!
|
|
serviceConfig.TimeoutStartSec = "600s";
|
|
|
|
script = let
|
|
jq = lib.getExe pkgs.jq;
|
|
in ''
|
|
set -e
|
|
mkdir -p "$(dirname '${cfg.ipPath}')"
|
|
mkdir -p "$(dirname '${cfg.upnpPath}')"
|
|
newIpDetails=$(${cfg.ipCmd})
|
|
newIp=$(echo "$newIpDetails" | ${jq} -r ".wan")
|
|
newUpnp=$(echo "$newIpDetails" | ${jq} -r ".upnp")
|
|
oldIp=$(cat '${cfg.ipPath}' || echo)
|
|
oldUpnp=$(cat '${cfg.upnpPath}' || echo)
|
|
|
|
# systemd path units are triggered on any file write action,
|
|
# regardless of content change. so only update the file if our IP *actually* changed
|
|
if [[ -n "$newIp" && "$newIp" != "$oldIp" ]]; then
|
|
echo "$newIp" > '${cfg.ipPath}'
|
|
echo "WAN ip changed $oldIp -> $newIp"
|
|
fi
|
|
|
|
if [[ -n "$newUpnp" && "$newUpnp" != "$oldUpnp" ]]; then
|
|
echo "$newUpnp" > '${cfg.upnpPath}'
|
|
echo "UPNP changed $oldUpnp -> $newUpnp"
|
|
fi
|
|
|
|
[[ -f '${cfg.ipPath}' && -f '${cfg.upnpPath}' ]]
|
|
'';
|
|
};
|
|
|
|
systemd.timers.dyn-dns = {
|
|
wantedBy = [ "dyn-dns.service" ];
|
|
timerConfig.OnUnitInactiveSec = cfg.interval;
|
|
};
|
|
|
|
systemd.targets.dyn-dns-exists = {
|
|
# consumers depend directly on this target for permission to *start*
|
|
# once active, this target should never de-activate
|
|
description = "initial acquisition of dynamic DNS data";
|
|
wants = [ "dyn-dns.service" ];
|
|
after = [ "dyn-dns.service" ];
|
|
wantedBy = cfg.requireForStart;
|
|
before = cfg.requireForStart; # prevent user service from starting until after we have dyn dns
|
|
};
|
|
|
|
systemd.targets.dyn-dns-events = {
|
|
# consumers depend on this to be restarted whenever DNS changes (after acquisition)
|
|
description = "event system that notifies via restart whenever dynamic DNS mapping changes";
|
|
requiredBy = cfg.restartOnChange; # this just propagates the restart events to the user service
|
|
};
|
|
|
|
systemd.paths.dyn-dns-changed = {
|
|
wantedBy = cfg.restartOnChange;
|
|
wants = [ "dyn-dns.service" ];
|
|
before = [
|
|
"dyn-dns.service"
|
|
"dyn-dns-events.target"
|
|
];
|
|
pathConfig.PathChanged = [ cfg.ipPath ];
|
|
};
|
|
systemd.services.dyn-dns-changed = {
|
|
description = "dynamic DNS change notifier";
|
|
serviceConfig.Type = "oneshot";
|
|
serviceConfig.ExecStart = "${lib.getExe' pkgs.systemd "systemctl"} restart dyn-dns-events.target";
|
|
};
|
|
};
|
|
}
|