at master 9.3 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7let 8 cfg = config.services.ddclient; 9 boolToStr = bool: if bool then "yes" else "no"; 10 dataDir = "/var/lib/ddclient"; 11 StateDirectory = builtins.baseNameOf dataDir; 12 RuntimeDirectory = StateDirectory; 13 14 configFile' = pkgs.writeText "ddclient.conf" '' 15 # This file can be used as a template for configFile or is automatically generated by Nix options. 16 cache=${dataDir}/ddclient.cache 17 foreground=YES 18 ${lib.optionalString (cfg.use != "") "use=${cfg.use}"} 19 ${lib.optionalString (cfg.use == "" && cfg.usev4 != "") "usev4=${cfg.usev4}"} 20 ${lib.optionalString (cfg.use == "" && cfg.usev6 != "") "usev6=${cfg.usev6}"} 21 ${lib.optionalString (cfg.username != "") "login=${cfg.username}"} 22 ${ 23 if cfg.protocol == "nsupdate" then 24 "/run/${RuntimeDirectory}/ddclient.key" 25 else if (cfg.passwordFile != null) then 26 "password=@password_placeholder@" 27 else if (cfg.secretsFile != null) then 28 "@secrets_placeholder@" 29 else 30 "" 31 } 32 protocol=${cfg.protocol} 33 ${lib.optionalString (cfg.script != "") "script=${cfg.script}"} 34 ${lib.optionalString (cfg.server != "") "server=${cfg.server}"} 35 ${lib.optionalString (cfg.zone != "") "zone=${cfg.zone}"} 36 ssl=${boolToStr cfg.ssl} 37 wildcard=YES 38 quiet=${boolToStr cfg.quiet} 39 verbose=${boolToStr cfg.verbose} 40 ${cfg.extraConfig} 41 ${lib.concatStringsSep "," cfg.domains} 42 ''; 43 configFile = if (cfg.configFile != null) then cfg.configFile else configFile'; 44 45 preStart = '' 46 install --mode=600 --owner=$USER ${configFile} /run/${RuntimeDirectory}/ddclient.conf 47 ${lib.optionalString (cfg.configFile == null) ( 48 if (cfg.protocol == "nsupdate") then 49 '' 50 install --mode=600 --owner=$USER ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key 51 '' 52 else if (cfg.passwordFile != null) then 53 '' 54 "${pkgs.replace-secret}/bin/replace-secret" "@password_placeholder@" "${cfg.passwordFile}" "/run/${RuntimeDirectory}/ddclient.conf" 55 '' 56 else if (cfg.secretsFile != null) then 57 '' 58 "${pkgs.replace-secret}/bin/replace-secret" "@secrets_placeholder@" "${cfg.secretsFile}" "/run/${RuntimeDirectory}/ddclient.conf" 59 '' 60 else 61 '' 62 sed -i '/^password=@password_placeholder@$/d' /run/${RuntimeDirectory}/ddclient.conf 63 '' 64 )} 65 ''; 66in 67{ 68 69 imports = [ 70 (lib.mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ] ( 71 config: 72 let 73 value = lib.getAttrFromPath [ "services" "ddclient" "domain" ] config; 74 in 75 lib.optional (value != "") value 76 )) 77 (lib.mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "") 78 (lib.mkRemovedOptionModule [ 79 "services" 80 "ddclient" 81 "password" 82 ] "Use services.ddclient.passwordFile instead.") 83 (lib.mkRemovedOptionModule [ "services" "ddclient" "ipv6" ] "") 84 ]; 85 86 ###### interface 87 88 options = { 89 90 services.ddclient = with lib.types; { 91 92 enable = lib.mkOption { 93 default = false; 94 type = bool; 95 description = '' 96 Whether to synchronise your machine's IP address with a dynamic DNS provider (e.g. dyndns.org). 97 ''; 98 }; 99 100 package = lib.mkOption { 101 type = package; 102 default = pkgs.ddclient; 103 defaultText = lib.literalExpression "pkgs.ddclient"; 104 description = '' 105 The ddclient executable package run by the service. 106 ''; 107 }; 108 109 domains = lib.mkOption { 110 default = [ "" ]; 111 type = listOf str; 112 description = '' 113 Domain name(s) to synchronize. 114 ''; 115 }; 116 117 username = lib.mkOption { 118 # For `nsupdate` username contains the path to the nsupdate executable 119 default = lib.optionalString ( 120 config.services.ddclient.protocol == "nsupdate" 121 ) "${pkgs.bind.dnsutils}/bin/nsupdate"; 122 defaultText = ""; 123 type = str; 124 description = '' 125 User name. 126 ''; 127 }; 128 129 passwordFile = lib.mkOption { 130 default = null; 131 type = nullOr str; 132 description = '' 133 A file containing the password or a TSIG key in named format when using the nsupdate protocol. 134 ''; 135 }; 136 137 secretsFile = lib.mkOption { 138 default = null; 139 type = nullOr str; 140 description = '' 141 A file containing the secrets for the dynamic DNS provider. 142 This file should contain lines of valid secrets in the format specified by the ddclient documentation. 143 If this option is set, it overrides the `passwordFile` option. 144 ''; 145 }; 146 147 interval = lib.mkOption { 148 default = "10min"; 149 type = str; 150 description = '' 151 The interval at which to run the check and update. 152 See {command}`man 7 systemd.time` for the format. 153 ''; 154 }; 155 156 configFile = lib.mkOption { 157 default = null; 158 type = nullOr path; 159 description = '' 160 Path to configuration file. 161 When set this overrides the generated configuration from module options. 162 ''; 163 example = "/root/nixos/secrets/ddclient.conf"; 164 }; 165 166 protocol = lib.mkOption { 167 default = "dyndns2"; 168 type = str; 169 description = '' 170 Protocol to use with dynamic DNS provider (see <https://ddclient.net/protocols.html> ). 171 ''; 172 }; 173 174 server = lib.mkOption { 175 default = ""; 176 type = str; 177 description = '' 178 Server address. 179 ''; 180 }; 181 182 ssl = lib.mkOption { 183 default = true; 184 type = bool; 185 description = '' 186 Whether to use SSL/TLS to connect to dynamic DNS provider. 187 ''; 188 }; 189 190 quiet = lib.mkOption { 191 default = false; 192 type = bool; 193 description = '' 194 Print no messages for unnecessary updates. 195 ''; 196 }; 197 198 script = lib.mkOption { 199 default = ""; 200 type = str; 201 description = '' 202 script as required by some providers. 203 ''; 204 }; 205 206 use = lib.mkOption { 207 default = ""; 208 type = str; 209 description = '' 210 Method to determine the IP address to send to the dynamic DNS provider. 211 ''; 212 }; 213 usev4 = lib.mkOption { 214 default = "webv4, webv4=ipify-ipv4"; 215 type = str; 216 description = '' 217 Method to determine the IPv4 address to send to the dynamic DNS provider. Only used if `use` is not set. 218 ''; 219 }; 220 usev6 = lib.mkOption { 221 default = "webv6, webv6=ipify-ipv6"; 222 type = str; 223 description = '' 224 Method to determine the IPv6 address to send to the dynamic DNS provider. Only used if `use` is not set. 225 ''; 226 }; 227 228 verbose = lib.mkOption { 229 default = false; 230 type = bool; 231 description = '' 232 Print verbose information. 233 ''; 234 }; 235 236 zone = lib.mkOption { 237 default = ""; 238 type = str; 239 description = '' 240 zone as required by some providers. 241 ''; 242 }; 243 244 extraConfig = lib.mkOption { 245 default = ""; 246 type = lines; 247 description = '' 248 Extra configuration. Contents will be added verbatim to the configuration file. 249 250 ::: {.note} 251 `daemon` should not be added here because it does not work great with the systemd-timer approach the service uses. 252 ::: 253 ''; 254 }; 255 }; 256 }; 257 258 ###### implementation 259 260 config = lib.mkIf config.services.ddclient.enable { 261 warnings = 262 lib.optional (cfg.use != "") 263 "Setting `use` is deprecated, ddclient now supports `usev4` and `usev6` for separate IPv4/IPv6 configuration."; 264 265 assertions = [ 266 { 267 assertion = !((cfg.passwordFile != null) && (cfg.secretsFile != null)); 268 message = "You cannot use both services.ddclient.passwordFile and services.ddclient.secretsFile at the same time."; 269 } 270 { 271 assertion = (cfg.protocol != "nsupdate") || (cfg.secretsFile == null); 272 message = "You cannot use services.ddclient.secretsFile when services.ddclient.protocol is \"nsupdate\". Use services.ddclient.passwordFile instead."; 273 } 274 ]; 275 276 systemd.services.ddclient = { 277 description = "Dynamic DNS Client"; 278 wantedBy = [ "multi-user.target" ]; 279 after = [ "network.target" ]; 280 restartTriggers = lib.optional (cfg.configFile != null) cfg.configFile; 281 path = lib.optional ( 282 lib.hasPrefix "if," cfg.use || lib.hasPrefix "ifv4," cfg.usev4 || lib.hasPrefix "ifv6," cfg.usev6 283 ) pkgs.iproute2; 284 285 serviceConfig = { 286 DynamicUser = true; 287 RuntimeDirectoryMode = "0700"; 288 inherit RuntimeDirectory; 289 inherit StateDirectory; 290 Type = "oneshot"; 291 ExecStartPre = [ "!${pkgs.writeShellScript "ddclient-prestart" preStart}" ]; 292 ExecStart = "${lib.getExe cfg.package} -file /run/${RuntimeDirectory}/ddclient.conf"; 293 }; 294 }; 295 296 systemd.timers.ddclient = { 297 description = "Run ddclient"; 298 wantedBy = [ "timers.target" ]; 299 timerConfig = { 300 OnBootSec = cfg.interval; 301 OnUnitInactiveSec = cfg.interval; 302 }; 303 }; 304 }; 305}