|
| 1 | +{ |
| 2 | + config, |
| 3 | + pkgs, |
| 4 | + lib, |
| 5 | + ... |
| 6 | +}: |
| 7 | +let |
| 8 | + cfg = config.services.duckdns; |
| 9 | + duckdns = pkgs.writeShellScriptBin "duckdns" '' |
| 10 | + DRESPONSE=$(curl -sS --max-time 60 --no-progress-meter -k -K- <<< "url = \"https://www.duckdns.org/update?verbose=true&domains=$DUCKDNS_DOMAINS&token=$DUCKDNS_TOKEN&ip=\"") |
| 11 | + IPV4=$(echo "$DRESPONSE" | awk 'NR==2') |
| 12 | + IPV6=$(echo "$DRESPONSE" | awk 'NR==3') |
| 13 | + RESPONSE=$(echo "$DRESPONSE" | awk 'NR==1') |
| 14 | + IPCHANGE=$(echo "$DRESPONSE" | awk 'NR==4') |
| 15 | +
|
| 16 | + if [[ "$RESPONSE" = "OK" ]] && [[ "$IPCHANGE" = "UPDATED" ]]; then |
| 17 | + if [[ "$IPV4" != "" ]] && [[ "$IPV6" == "" ]]; then |
| 18 | + echo "Your IP was updated at $(date) to IPv4: $IPV4" |
| 19 | + elif [[ "$IPV4" == "" ]] && [[ "$IPV6" != "" ]]; then |
| 20 | + echo "Your IP was updated at $(date) to IPv6: $IPV6" |
| 21 | + else |
| 22 | + echo "Your IP was updated at $(date) to IPv4: $IPV4 & IPv6 to: $IPV6" |
| 23 | + fi |
| 24 | + elif [[ "$RESPONSE" = "OK" ]] && [[ "$IPCHANGE" = "NOCHANGE" ]]; then |
| 25 | + echo "DuckDNS request at $(date) successful. IP(s) unchanged." |
| 26 | + else |
| 27 | + echo -e "Something went wrong, please check your settings\nThe response returned was:\n$DRESPONSE\n" |
| 28 | + exit 1 |
| 29 | + fi |
| 30 | + ''; |
| 31 | +in |
| 32 | +{ |
| 33 | + options.services.duckdns = { |
| 34 | + enable = lib.mkEnableOption "DuckDNS Dynamic DNS Client"; |
| 35 | + tokenFile = lib.mkOption { |
| 36 | + default = null; |
| 37 | + type = lib.types.path; |
| 38 | + description = '' |
| 39 | + The path to a file containing the token |
| 40 | + used to authenticate with DuckDNS. |
| 41 | + ''; |
| 42 | + }; |
| 43 | + |
| 44 | + domains = lib.mkOption { |
| 45 | + default = null; |
| 46 | + type = lib.types.nullOr (lib.types.listOf lib.types.str); |
| 47 | + example = [ "examplehost" ]; |
| 48 | + description = '' |
| 49 | + The domain(s) to update in DuckDNS |
| 50 | + (without the .duckdns.org suffix) |
| 51 | + ''; |
| 52 | + }; |
| 53 | + |
| 54 | + domainsFile = lib.mkOption { |
| 55 | + default = null; |
| 56 | + type = lib.types.nullOr lib.types.path; |
| 57 | + example = lib.literalExpression '' |
| 58 | + pkgs.writeText "duckdns-domains.txt" ''' |
| 59 | + examplehost |
| 60 | + examplehost2 |
| 61 | + examplehost3 |
| 62 | + ''' |
| 63 | + ''; |
| 64 | + description = '' |
| 65 | + The path to a file containing a |
| 66 | + newline-separated list of DuckDNS |
| 67 | + domain(s) to be updated |
| 68 | + (without the .duckdns.org suffix) |
| 69 | + ''; |
| 70 | + }; |
| 71 | + |
| 72 | + }; |
| 73 | + |
| 74 | + config = lib.mkIf cfg.enable { |
| 75 | + assertions = [ |
| 76 | + { |
| 77 | + assertion = cfg.domains != null || cfg.domainsFile != null; |
| 78 | + message = "Either services.duckdns.domains or services.duckdns.domainsFile has to be defined"; |
| 79 | + } |
| 80 | + { |
| 81 | + assertion = !(cfg.domains != null && cfg.domainsFile != null); |
| 82 | + message = "services.duckdns.domains and services.duckdns.domainsFile can't both be defined at the same time"; |
| 83 | + } |
| 84 | + { |
| 85 | + assertion = (cfg.tokenFile != null); |
| 86 | + message = "services.duckdns.tokenFile has to be defined"; |
| 87 | + } |
| 88 | + ]; |
| 89 | + |
| 90 | + environment.systemPackages = [ duckdns ]; |
| 91 | + |
| 92 | + systemd.services.duckdns = { |
| 93 | + description = "DuckDNS Dynamic DNS Client"; |
| 94 | + after = [ "network.target" ]; |
| 95 | + wantedBy = [ "multi-user.target" ]; |
| 96 | + startAt = "*:0/5"; |
| 97 | + path = [ |
| 98 | + pkgs.gnused |
| 99 | + pkgs.systemd |
| 100 | + pkgs.curl |
| 101 | + pkgs.gawk |
| 102 | + duckdns |
| 103 | + ]; |
| 104 | + serviceConfig = { |
| 105 | + Type = "simple"; |
| 106 | + LoadCredential = [ |
| 107 | + "DUCKDNS_TOKEN_FILE:${cfg.tokenFile}" |
| 108 | + ] ++ lib.optionals (cfg.domainsFile != null) [ "DUCKDNS_DOMAINS_FILE:${cfg.domainsFile}" ]; |
| 109 | + DynamicUser = true; |
| 110 | + }; |
| 111 | + script = '' |
| 112 | + export DUCKDNS_TOKEN=$(systemd-creds cat DUCKDNS_TOKEN_FILE) |
| 113 | + ${lib.optionalString (cfg.domains != null) '' |
| 114 | + export DUCKDNS_DOMAINS='${lib.strings.concatStringsSep "," cfg.domains}' |
| 115 | + ''} |
| 116 | + ${lib.optionalString (cfg.domainsFile != null) '' |
| 117 | + export DUCKDNS_DOMAINS=$(systemd-creds cat DUCKDNS_DOMAINS_FILE | sed -z 's/\n/,/g') |
| 118 | + ''} |
| 119 | + exec ${lib.getExe duckdns} |
| 120 | + ''; |
| 121 | + }; |
| 122 | + }; |
| 123 | + |
| 124 | + meta.maintainers = with lib.maintainers; [ notthebee ]; |
| 125 | +} |
0 commit comments