summary refs log tree commit diff
diff options
context:
space:
mode:
authorSefa Eyeoglu2024-11-14 17:29:11 +0100
committerGitHub2024-11-14 17:29:11 +0100
commit1d95cb5fa7a38717172e090d54338ca8daef8a86 (patch)
treef5ad69ecfe671776b3edc2d8d85bbcaf9db7e218
parentf304923acb532c4f32d309ddf206454230ac01d5 (diff)
parent9563f469c17e05274be15ee13d276f3e6fb4fbcc (diff)
nixos/duckdns: init module (#294489)
-rw-r--r--maintainers/maintainer-list.nix6
-rw-r--r--nixos/modules/services/misc/duckdns.nix125
2 files changed, 131 insertions, 0 deletions
diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix
index 679b95a2d217..d975ebfc7ea5 100644
--- a/maintainers/maintainer-list.nix
+++ b/maintainers/maintainer-list.nix
@@ -15915,6 +15915,12 @@
     githubId = 30374463;
     name = "Michal S.";
   };
+  notthebee = {
+    email = "moe@notthebe.ee";
+    github = "notthebee";
+    githubId = 30384331;
+    name = "Wolfgang";
+  };
   notthemessiah = {
     email = "brian.cohen.88@gmail.com";
     github = "NOTtheMessiah";
diff --git a/nixos/modules/services/misc/duckdns.nix b/nixos/modules/services/misc/duckdns.nix
new file mode 100644
index 000000000000..a4ea0a09e313
--- /dev/null
+++ b/nixos/modules/services/misc/duckdns.nix
@@ -0,0 +1,125 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+let
+  cfg = config.services.duckdns;
+  duckdns = pkgs.writeShellScriptBin "duckdns" ''
+    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=\"")
+    IPV4=$(echo "$DRESPONSE" | awk 'NR==2')
+    IPV6=$(echo "$DRESPONSE" | awk 'NR==3')
+    RESPONSE=$(echo "$DRESPONSE" | awk 'NR==1')
+    IPCHANGE=$(echo "$DRESPONSE" | awk 'NR==4')
+
+    if [[ "$RESPONSE" = "OK" ]] && [[ "$IPCHANGE" = "UPDATED" ]]; then
+        if [[ "$IPV4" != "" ]] && [[ "$IPV6" == "" ]]; then
+            echo "Your IP was updated at $(date) to IPv4: $IPV4"
+        elif [[ "$IPV4" == "" ]] && [[ "$IPV6" != "" ]]; then
+            echo "Your IP was updated at $(date) to IPv6: $IPV6"
+        else
+            echo "Your IP was updated at $(date) to IPv4: $IPV4 & IPv6 to: $IPV6"
+        fi
+    elif [[ "$RESPONSE" = "OK" ]] && [[ "$IPCHANGE" = "NOCHANGE" ]]; then
+        echo "DuckDNS request at $(date) successful. IP(s) unchanged."
+    else
+        echo -e "Something went wrong, please check your settings\nThe response returned was:\n$DRESPONSE\n"
+        exit 1
+    fi
+  '';
+in
+{
+  options.services.duckdns = {
+    enable = lib.mkEnableOption "DuckDNS Dynamic DNS Client";
+    tokenFile = lib.mkOption {
+      default = null;
+      type = lib.types.path;
+      description = ''
+        The path to a file containing the token
+        used to authenticate with DuckDNS.
+      '';
+    };
+
+    domains = lib.mkOption {
+      default = null;
+      type = lib.types.nullOr (lib.types.listOf lib.types.str);
+      example = [ "examplehost" ];
+      description = ''
+        The domain(s) to update in DuckDNS
+        (without the .duckdns.org suffix)
+      '';
+    };
+
+    domainsFile = lib.mkOption {
+      default = null;
+      type = lib.types.nullOr lib.types.path;
+      example = lib.literalExpression ''
+        pkgs.writeText "duckdns-domains.txt" '''
+          examplehost
+          examplehost2
+          examplehost3
+        '''
+      '';
+      description = ''
+        The path to a file containing a
+        newline-separated list of DuckDNS
+        domain(s) to be updated
+        (without the .duckdns.org suffix)
+      '';
+    };
+
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.domains != null || cfg.domainsFile != null;
+        message = "Either services.duckdns.domains or services.duckdns.domainsFile has to be defined";
+      }
+      {
+        assertion = !(cfg.domains != null && cfg.domainsFile != null);
+        message = "services.duckdns.domains and services.duckdns.domainsFile can't both be defined at the same time";
+      }
+      {
+        assertion = (cfg.tokenFile != null);
+        message = "services.duckdns.tokenFile has to be defined";
+      }
+    ];
+
+    environment.systemPackages = [ duckdns ];
+
+    systemd.services.duckdns = {
+      description = "DuckDNS Dynamic DNS Client";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      startAt = "*:0/5";
+      path = [
+        pkgs.gnused
+        pkgs.systemd
+        pkgs.curl
+        pkgs.gawk
+        duckdns
+      ];
+      serviceConfig = {
+        Type = "simple";
+        LoadCredential = [
+          "DUCKDNS_TOKEN_FILE:${cfg.tokenFile}"
+        ] ++ lib.optionals (cfg.domainsFile != null) [ "DUCKDNS_DOMAINS_FILE:${cfg.domainsFile}" ];
+        DynamicUser = true;
+      };
+      script = ''
+        export DUCKDNS_TOKEN=$(systemd-creds cat DUCKDNS_TOKEN_FILE)
+        ${lib.optionalString (cfg.domains != null) ''
+          export DUCKDNS_DOMAINS='${lib.strings.concatStringsSep "," cfg.domains}'
+        ''}
+        ${lib.optionalString (cfg.domainsFile != null) ''
+          export DUCKDNS_DOMAINS=$(systemd-creds cat DUCKDNS_DOMAINS_FILE | sed -z 's/\n/,/g')
+        ''}
+        exec ${lib.getExe duckdns}
+      '';
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ notthebee ];
+}