1 { config, pkgs, lib, ... }:
3 cfg = config.services.ddclient;
4 boolToStr = bool: if bool then "yes" else "no";
5 dataDir = "/var/lib/ddclient";
6 StateDirectory = builtins.baseNameOf dataDir;
7 RuntimeDirectory = StateDirectory;
9 configFile' = pkgs.writeText "ddclient.conf" ''
10 # This file can be used as a template for configFile or is automatically generated by Nix options.
11 cache=${dataDir}/ddclient.cache
13 ${lib.optionalString (cfg.use != "") "use=${cfg.use}"}
14 ${lib.optionalString (cfg.use == "" && cfg.usev4 != "") "usev4=${cfg.usev4}"}
15 ${lib.optionalString (cfg.use == "" && cfg.usev6 != "") "usev6=${cfg.usev6}"}
17 password=${if cfg.protocol == "nsupdate" then "/run/${RuntimeDirectory}/ddclient.key" else "@password_placeholder@"}
18 protocol=${cfg.protocol}
19 ${lib.optionalString (cfg.script != "") "script=${cfg.script}"}
20 ${lib.optionalString (cfg.server != "") "server=${cfg.server}"}
21 ${lib.optionalString (cfg.zone != "") "zone=${cfg.zone}"}
22 ssl=${boolToStr cfg.ssl}
24 quiet=${boolToStr cfg.quiet}
25 verbose=${boolToStr cfg.verbose}
27 ${lib.concatStringsSep "," cfg.domains}
29 configFile = if (cfg.configFile != null) then cfg.configFile else configFile';
32 install --mode=600 --owner=$USER ${configFile} /run/${RuntimeDirectory}/ddclient.conf
33 ${lib.optionalString (cfg.configFile == null) (if (cfg.protocol == "nsupdate") then ''
34 install --mode=600 --owner=$USER ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key
35 '' else if (cfg.passwordFile != null) then ''
36 "${pkgs.replace-secret}/bin/replace-secret" "@password_placeholder@" "${cfg.passwordFile}" "/run/${RuntimeDirectory}/ddclient.conf"
38 sed -i '/^password=@password_placeholder@$/d' /run/${RuntimeDirectory}/ddclient.conf
45 (lib.mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ]
47 let value = lib.getAttrFromPath [ "services" "ddclient" "domain" ] config;
48 in lib.optional (value != "") value))
49 (lib.mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
50 (lib.mkRemovedOptionModule [ "services" "ddclient" "password" ] "Use services.ddclient.passwordFile instead.")
51 (lib.mkRemovedOptionModule [ "services" "ddclient" "ipv6" ] "")
58 services.ddclient = with lib.types; {
60 enable = lib.mkOption {
64 Whether to synchronise your machine's IP address with a dynamic DNS provider (e.g. dyndns.org).
68 package = lib.mkOption {
70 default = pkgs.ddclient;
71 defaultText = lib.literalExpression "pkgs.ddclient";
73 The ddclient executable package run by the service.
77 domains = lib.mkOption {
81 Domain name(s) to synchronize.
85 username = lib.mkOption {
86 # For `nsupdate` username contains the path to the nsupdate executable
87 default = lib.optionalString (config.services.ddclient.protocol == "nsupdate") "${pkgs.bind.dnsutils}/bin/nsupdate";
95 passwordFile = lib.mkOption {
99 A file containing the password or a TSIG key in named format when using the nsupdate protocol.
103 interval = lib.mkOption {
107 The interval at which to run the check and update.
108 See {command}`man 7 systemd.time` for the format.
112 configFile = lib.mkOption {
116 Path to configuration file.
117 When set this overrides the generated configuration from module options.
119 example = "/root/nixos/secrets/ddclient.conf";
122 protocol = lib.mkOption {
126 Protocol to use with dynamic DNS provider (see https://ddclient.net/protocols.html ).
130 server = lib.mkOption {
142 Whether to use SSL/TLS to connect to dynamic DNS provider.
146 quiet = lib.mkOption {
150 Print no messages for unnecessary updates.
154 script = lib.mkOption {
158 script as required by some providers.
166 Method to determine the IP address to send to the dynamic DNS provider.
169 usev4 = lib.mkOption {
170 default = "webv4, webv4=checkip.dyndns.com/, webv4-skip='Current IP Address: '";
173 Method to determine the IPv4 address to send to the dynamic DNS provider. Only used if `use` is not set.
176 usev6 = lib.mkOption {
177 default = "webv6, webv6=checkipv6.dyndns.com/, webv6-skip='Current IP Address: '";
180 Method to determine the IPv6 address to send to the dynamic DNS provider. Only used if `use` is not set.
184 verbose = lib.mkOption {
188 Print verbose information.
192 zone = lib.mkOption {
196 zone as required by some providers.
200 extraConfig = lib.mkOption {
204 Extra configuration. Contents will be added verbatim to the configuration file.
207 `daemon` should not be added here because it does not work great with the systemd-timer approach the service uses.
215 ###### implementation
217 config = lib.mkIf config.services.ddclient.enable {
218 warnings = lib.optional (cfg.use != "") "Setting `use` is deprecated, ddclient now supports `usev4` and `usev6` for separate IPv4/IPv6 configuration.";
220 systemd.services.ddclient = {
221 description = "Dynamic DNS Client";
222 wantedBy = [ "multi-user.target" ];
223 after = [ "network.target" ];
224 restartTriggers = lib.optional (cfg.configFile != null) cfg.configFile;
225 path = lib.optional (lib.hasPrefix "if," cfg.use) pkgs.iproute2;
229 RuntimeDirectoryMode = "0700";
230 inherit RuntimeDirectory;
231 inherit StateDirectory;
233 ExecStartPre = [ "!${pkgs.writeShellScript "ddclient-prestart" preStart}" ];
234 ExecStart = "${lib.getExe cfg.package} -file /run/${RuntimeDirectory}/ddclient.conf";
238 systemd.timers.ddclient = {
239 description = "Run ddclient";
240 wantedBy = [ "timers.target" ];
242 OnBootSec = cfg.interval;
243 OnUnitInactiveSec = cfg.interval;