nixos ddclient: support multiple domains and run via systemd timer

a) Some providers can update multiple domains - support that.

b) Make "zone" and "script" configurable. Some providers require these.

c) Instead of leaving the ddclient daemon running all the time, use a systemd
timer to kick it off.

d) Don't use a predefined user - run everything via DynamicUser

e) Add documentation

+69 -55
+2 -2
nixos/modules/misc/ids.nix
··· 56 56 #dialout = 27; # unused 57 57 polkituser = 28; 58 58 #utmp = 29; # unused 59 - ddclient = 30; 59 + # ddclient = 30; # converted to DynamicUser = true 60 60 davfs2 = 31; 61 61 #disnix = 33; # unused 62 62 osgi = 34; ··· 343 343 dialout = 27; 344 344 #polkituser = 28; # currently unused, polkitd doesn't need a group 345 345 utmp = 29; 346 - ddclient = 30; 346 + # ddclient = 30; # converted to DynamicUser = true 347 347 davfs2 = 31; 348 348 disnix = 33; 349 349 osgi = 34;
+2
nixos/modules/rename.nix
··· 22 22 (config: 23 23 let enabled = getAttrFromPath [ "services" "printing" "gutenprint" ] config; 24 24 in if enabled then [ pkgs.gutenprint ] else [ ])) 25 + (mkRenamedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ]) 26 + (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "") 25 27 (mkRenamedOptionModule [ "services" "elasticsearch" "host" ] [ "services" "elasticsearch" "listenAddress" ]) 26 28 (mkRenamedOptionModule [ "services" "graphite" "api" "host" ] [ "services" "graphite" "api" "listenAddress" ]) 27 29 (mkRenamedOptionModule [ "services" "graphite" "web" "host" ] [ "services" "graphite" "web" "listenAddress" ])
+59 -53
nixos/modules/services/networking/ddclient.nix
··· 3 3 let 4 4 cfg = config.services.ddclient; 5 5 boolToStr = bool: if bool then "yes" else "no"; 6 + dataDir = "/var/lib/ddclient"; 6 7 7 8 configText = '' 8 9 # This file can be used as a template for configFile or is automatically generated by Nix options. 9 - daemon=${toString cfg.interval} 10 - cache=${cfg.homeDir}/ddclient.cache 11 - pid=/run/ddclient/ddclient.pid 12 - foreground=NO 10 + cache=${dataDir}/ddclient.cache 11 + foreground=YES 13 12 use=${cfg.use} 14 13 login=${cfg.username} 15 14 password=${cfg.password} 16 15 protocol=${cfg.protocol} 17 - ${let server = cfg.server; in 18 - lib.optionalString (server != "") "server=${server}"} 16 + ${lib.optionalString (cfg.script != "") "script=${cfg.script}"} 17 + ${lib.optionalString (cfg.server != "") "server=${cfg.server}"} 18 + ${lib.optionalString (cfg.zone != "") "zone=${cfg.zone}"} 19 19 ssl=${boolToStr cfg.ssl} 20 20 wildcard=YES 21 21 quiet=${boolToStr cfg.quiet} 22 22 verbose=${boolToStr cfg.verbose} 23 - ${cfg.domain} 23 + ${lib.concatStringsSep "," cfg.domains} 24 24 ${cfg.extraConfig} 25 25 ''; 26 26 ··· 44 44 ''; 45 45 }; 46 46 47 - homeDir = mkOption { 48 - default = "/var/lib/ddclient"; 49 - type = str; 50 - description = "Home directory for the daemon user."; 51 - }; 52 - 53 - domain = mkOption { 54 - default = ""; 55 - type = str; 47 + domains = mkOption { 48 + default = [ "" ]; 49 + type = listOf str; 56 50 description = '' 57 - Domain name to synchronize. 51 + Domain name(s) to synchronize. 58 52 ''; 59 53 }; 60 54 ··· 62 56 default = ""; 63 57 type = str; 64 58 description = '' 65 - Username. 59 + User name. 66 60 ''; 67 61 }; 68 62 ··· 75 69 }; 76 70 77 71 interval = mkOption { 78 - default = 600; 79 - type = int; 80 - description = "The interval at which to run the check and update."; 72 + default = "10min"; 73 + type = str; 74 + description = '' 75 + The interval at which to run the check and update. 76 + See <command>man 7 systemd.time</command> for the format. 77 + ''; 81 78 }; 82 79 83 80 configFile = mkOption { ··· 95 92 default = "dyndns2"; 96 93 type = str; 97 94 description = '' 98 - Protocol to use with dynamic DNS provider (see http://sourceforge.net/apps/trac/ddclient/wiki/Protocols). 95 + Protocol to use with dynamic DNS provider (see https://sourceforge.net/p/ddclient/wiki/protocols). 99 96 ''; 100 97 }; 101 98 ··· 115 112 ''; 116 113 }; 117 114 118 - extraConfig = mkOption { 115 + 116 + quiet = mkOption { 117 + default = false; 118 + type = bool; 119 + description = '' 120 + Print no messages for unnecessary updates. 121 + ''; 122 + }; 123 + 124 + script = mkOption { 119 125 default = ""; 120 - type = lines; 126 + type = str; 121 127 description = '' 122 - Extra configuration. Contents will be added verbatim to the configuration file. 128 + script as required by some providers. 123 129 ''; 124 130 }; 125 131 ··· 139 145 ''; 140 146 }; 141 147 142 - quiet = mkOption { 143 - default = false; 144 - type = bool; 148 + zone = mkOption { 149 + default = ""; 150 + type = str; 145 151 description = '' 146 - Print no messages for unnecessary updates. 152 + zone as required by some providers. 153 + ''; 154 + }; 155 + 156 + extraConfig = mkOption { 157 + default = ""; 158 + type = lines; 159 + description = '' 160 + Extra configuration. Contents will be added verbatim to the configuration file. 147 161 ''; 148 162 }; 149 163 }; ··· 153 167 ###### implementation 154 168 155 169 config = mkIf config.services.ddclient.enable { 156 - 157 - users = { 158 - extraGroups.ddclient.gid = config.ids.gids.ddclient; 159 - 160 - extraUsers.ddclient = { 161 - uid = config.ids.uids.ddclient; 162 - description = "ddclient daemon user"; 163 - group = "ddclient"; 164 - home = cfg.homeDir; 165 - createHome = true; 166 - }; 167 - }; 168 - 169 170 environment.etc."ddclient.conf" = { 170 171 enable = cfg.configFile == "/etc/ddclient.conf"; 171 - uid = config.ids.uids.ddclient; 172 - gid = config.ids.gids.ddclient; 173 172 mode = "0600"; 174 173 text = configText; 175 174 }; ··· 180 179 after = [ "network.target" ]; 181 180 restartTriggers = [ config.environment.etc."ddclient.conf".source ]; 182 181 183 - serviceConfig = { 184 - RuntimeDirectory = "ddclient"; 185 - # we cannot run in forking mode as it swallows all the program output 186 - Type = "simple"; 187 - User = "ddclient"; 188 - Group = "ddclient"; 189 - ExecStart = "${lib.getBin pkgs.ddclient}/bin/ddclient -foreground -file ${cfg.configFile}"; 190 - ProtectSystem = "full"; 191 - PrivateTmp = true; 182 + serviceConfig = rec { 183 + DynamicUser = true; 184 + RuntimeDirectory = StateDirectory; 185 + StateDirectory = builtins.baseNameOf dataDir; 186 + Type = "oneshot"; 187 + ExecStartPre = "!${lib.getBin pkgs.coreutils}/bin/install -m666 ${cfg.configFile} /run/${RuntimeDirectory}/ddclient.conf"; 188 + ExecStart = "${lib.getBin pkgs.ddclient}/bin/ddclient -file /run/${RuntimeDirectory}/ddclient.conf"; 189 + }; 190 + }; 191 + 192 + systemd.timers.ddclient = { 193 + description = "Run ddclient"; 194 + wantedBy = [ "timers.target" ]; 195 + timerConfig = { 196 + OnBootSec = cfg.interval; 197 + OnUnitInactiveSec = cfg.interval; 192 198 }; 193 199 }; 194 200 };
+6
pkgs/tools/networking/ddclient/default.nix
··· 9 9 sha256 = "1j8zdn7fy7i0bjk3jf0hxnbnshc2yf054vxq64imxdpfd7n5zgfy"; 10 10 }; 11 11 12 + # perl packages by default get devdoc which isn't present 12 13 outputs = [ "out" ]; 13 14 14 15 buildInputs = with perlPackages; [ IOSocketSSL DigestSHA1 ]; ··· 25 26 ''; 26 27 27 28 installPhase = '' 29 + runHook preInstall 30 + 28 31 install -Dm755 ddclient $out/bin/ddclient 32 + install -Dm644 -t $out/share/doc/ddclient COP* ChangeLog README.* RELEASENOTE 33 + 34 + runHook postInstall 29 35 ''; 30 36 31 37 # there are no tests distributed with ddclient