at 25.11-pre 3.7 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7let 8 cfg = config.services.cloudflare-dyndns; 9in 10{ 11 options = { 12 services.cloudflare-dyndns = { 13 enable = lib.mkEnableOption "Cloudflare Dynamic DNS Client"; 14 15 package = lib.mkPackageOption pkgs "cloudflare-dyndns" { }; 16 17 apiTokenFile = lib.mkOption { 18 type = lib.types.pathWith { 19 absolute = true; 20 inStore = false; 21 }; 22 23 description = '' 24 The path to a file containing the CloudFlare API token. 25 ''; 26 }; 27 28 domains = lib.mkOption { 29 type = lib.types.listOf lib.types.str; 30 default = [ ]; 31 description = '' 32 List of domain names to update records for. 33 ''; 34 }; 35 36 frequency = lib.mkOption { 37 type = lib.types.nullOr lib.types.str; 38 default = "*:0/5"; 39 description = '' 40 Run cloudflare-dyndns with the given frequency (see 41 {manpage}`systemd.time(7)` for the format). 42 If null, do not run automatically. 43 ''; 44 }; 45 46 proxied = lib.mkOption { 47 type = lib.types.bool; 48 default = false; 49 description = '' 50 Whether this is a DNS-only record, or also being proxied through CloudFlare. 51 ''; 52 }; 53 54 ipv4 = lib.mkOption { 55 type = lib.types.bool; 56 default = true; 57 description = '' 58 Whether to enable setting IPv4 A records. 59 ''; 60 }; 61 62 ipv6 = lib.mkOption { 63 type = lib.types.bool; 64 default = false; 65 description = '' 66 Whether to enable setting IPv6 AAAA records. 67 ''; 68 }; 69 70 deleteMissing = lib.mkOption { 71 type = lib.types.bool; 72 default = false; 73 description = '' 74 Whether to delete the record when no IP address is found. 75 ''; 76 }; 77 }; 78 }; 79 80 config = lib.mkIf cfg.enable { 81 systemd.services.cloudflare-dyndns = 82 { 83 description = "CloudFlare Dynamic DNS Client"; 84 after = [ "network.target" ]; 85 wantedBy = [ "multi-user.target" ]; 86 87 environment = { 88 CLOUDFLARE_DOMAINS = toString cfg.domains; 89 }; 90 91 serviceConfig = { 92 Type = "simple"; 93 DynamicUser = true; 94 StateDirectory = "cloudflare-dyndns"; 95 Environment = [ "XDG_CACHE_HOME=%S/cloudflare-dyndns/.cache" ]; 96 LoadCredential = [ 97 "apiToken:${cfg.apiTokenFile}" 98 ]; 99 }; 100 101 script = 102 let 103 args = 104 [ "--cache-file /var/lib/cloudflare-dyndns/ip.cache" ] 105 ++ (if cfg.ipv4 then [ "-4" ] else [ "-no-4" ]) 106 ++ (if cfg.ipv6 then [ "-6" ] else [ "-no-6" ]) 107 ++ lib.optional cfg.deleteMissing "--delete-missing" 108 ++ lib.optional cfg.proxied "--proxied"; 109 in 110 '' 111 export CLOUDFLARE_API_TOKEN_FILE=''${CREDENTIALS_DIRECTORY}/apiToken 112 113 # Added 2025-03-10: `cfg.apiTokenFile` used to be passed as an 114 # `EnvironmentFile` to the service, which required it to be of 115 # the form "CLOUDFLARE_API_TOKEN=" rather than just the secret. 116 # If we detect this legacy usage, error out. 117 token=$(< "''${CLOUDFLARE_API_TOKEN_FILE}") 118 if [[ $token == CLOUDFLARE_API_TOKEN* ]]; then 119 echo "Error: your api token starts with 'CLOUDFLARE_API_TOKEN='. Remove that, and instead specify just the token." >&2 120 exit 1 121 fi 122 123 exec ${lib.getExe cfg.package} ${toString args} 124 ''; 125 } 126 // lib.optionalAttrs (cfg.frequency != null) { 127 startAt = cfg.frequency; 128 }; 129 }; 130}