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