Commit 254363a16321

Vincent Demeester <vincent@sbr.pm>
2026-01-02 09:19:11
feat(ntfy): Add token authentication support
- Secure private ntfy instances with Bearer token authentication - Maintain backward compatibility for unauthenticated usage - Preserve systemd sandboxing with read-only secret access Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent e065edb
Changed files (5)
modules
audible-sync
music-playlist-dl
nix-flake-updater
systems
tools
nix-flake-update
modules/audible-sync/default.nix
@@ -90,6 +90,12 @@ in
         default = "audible-sync";
         description = "ntfy topic for notifications";
       };
+
+      tokenFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "Path to file containing ntfy authentication token (optional)";
+      };
     };
   };
 
@@ -134,10 +140,25 @@ in
         # Notifications on success (if enabled)
         ExecStartPost = mkIf cfg.notification.enable (
           pkgs.writeShellScript "audible-sync-notify-success" ''
-            ${pkgs.curl}/bin/curl -H "Title: Audible Sync Complete" \
-              -H "Tags: white_check_mark,books" \
-              -d "Successfully synced Audible library to ${cfg.outputDir}" \
-              ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+            ${
+              if cfg.notification.tokenFile != null then
+                ''
+                  ${pkgs.curl}/bin/curl \
+                    -H "Authorization: Bearer $(${pkgs.coreutils}/bin/tr -d '\n' < ${cfg.notification.tokenFile})" \
+                    -H "Title: Audible Sync Complete" \
+                    -H "Tags: white_check_mark,books" \
+                    -d "Successfully synced Audible library to ${cfg.outputDir}" \
+                    ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+                ''
+              else
+                ''
+                  ${pkgs.curl}/bin/curl \
+                    -H "Title: Audible Sync Complete" \
+                    -H "Tags: white_check_mark,books" \
+                    -d "Successfully synced Audible library to ${cfg.outputDir}" \
+                    ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+                ''
+            }
           ''
         );
 
@@ -164,6 +185,10 @@ in
           cfg.outputDir
           cfg.tempDir
         ];
+        # Allow access to token file if configured
+        BindReadOnlyPaths = mkIf (cfg.notification.enable && cfg.notification.tokenFile != null) [
+          cfg.notification.tokenFile
+        ];
       };
 
       # Notify on failure (if enabled)
@@ -176,12 +201,32 @@ in
       serviceConfig = {
         Type = "oneshot";
         ExecStart = pkgs.writeShellScript "audible-sync-notify-failure" ''
-          ${pkgs.curl}/bin/curl -H "Title: Audible Sync Failed" \
-            -H "Priority: high" \
-            -H "Tags: warning,books" \
-            -d "Audible sync failed. Check logs: journalctl -u audible-sync" \
-            ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+          ${
+            if cfg.notification.tokenFile != null then
+              ''
+                ${pkgs.curl}/bin/curl \
+                  -H "Authorization: Bearer $(${pkgs.coreutils}/bin/tr -d '\n' < ${cfg.notification.tokenFile})" \
+                  -H "Title: Audible Sync Failed" \
+                  -H "Priority: high" \
+                  -H "Tags: warning,books" \
+                  -d "Audible sync failed. Check logs: journalctl -u audible-sync" \
+                  ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+              ''
+            else
+              ''
+                ${pkgs.curl}/bin/curl \
+                  -H "Title: Audible Sync Failed" \
+                  -H "Priority: high" \
+                  -H "Tags: warning,books" \
+                  -d "Audible sync failed. Check logs: journalctl -u audible-sync" \
+                  ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+              ''
+          }
         '';
+        # Allow access to token file if configured
+        BindReadOnlyPaths = mkIf (cfg.notification.tokenFile != null) [
+          cfg.notification.tokenFile
+        ];
       };
     };
   };
modules/music-playlist-dl/default.nix
@@ -76,6 +76,12 @@ in
         default = "homelab";
         description = "ntfy topic for notifications";
       };
+
+      tokenFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "Path to file containing ntfy authentication token (optional)";
+      };
     };
   };
 
@@ -113,10 +119,25 @@ in
         # Notifications on success (if enabled)
         ExecStartPost = mkIf cfg.notification.enable (
           pkgs.writeShellScript "music-playlist-dl-notify-success" ''
-            ${pkgs.curl}/bin/curl -H "Title: Music Playlist Download Complete" \
-              -H "Tags: musical_note,headphones" \
-              -d "Successfully downloaded music podcasts and updated playlists" \
-              ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+            ${
+              if cfg.notification.tokenFile != null then
+                ''
+                  ${pkgs.curl}/bin/curl \
+                    -H "Authorization: Bearer $(${pkgs.coreutils}/bin/tr -d '\n' < ${cfg.notification.tokenFile})" \
+                    -H "Title: Music Playlist Download Complete" \
+                    -H "Tags: musical_note,headphones" \
+                    -d "Successfully downloaded music podcasts and updated playlists" \
+                    ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+                ''
+              else
+                ''
+                  ${pkgs.curl}/bin/curl \
+                    -H "Title: Music Playlist Download Complete" \
+                    -H "Tags: musical_note,headphones" \
+                    -d "Successfully downloaded music podcasts and updated playlists" \
+                    ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+                ''
+            }
           ''
         );
 
@@ -148,11 +169,27 @@ in
       serviceConfig = {
         Type = "oneshot";
         ExecStart = pkgs.writeShellScript "music-playlist-dl-notify-failure" ''
-          ${pkgs.curl}/bin/curl -H "Title: Music Playlist Download Failed" \
-            -H "Priority: high" \
-            -H "Tags: warning,musical_note" \
-            -d "Music playlist download failed. Check logs: journalctl -u music-playlist-dl" \
-            ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+          ${
+            if cfg.notification.tokenFile != null then
+              ''
+                ${pkgs.curl}/bin/curl \
+                  -H "Authorization: Bearer $(${pkgs.coreutils}/bin/tr -d '\n' < ${cfg.notification.tokenFile})" \
+                  -H "Title: Music Playlist Download Failed" \
+                  -H "Priority: high" \
+                  -H "Tags: warning,musical_note" \
+                  -d "Music playlist download failed. Check logs: journalctl -u music-playlist-dl" \
+                  ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+              ''
+            else
+              ''
+                ${pkgs.curl}/bin/curl \
+                  -H "Title: Music Playlist Download Failed" \
+                  -H "Priority: high" \
+                  -H "Tags: warning,musical_note" \
+                  -d "Music playlist download failed. Check logs: journalctl -u music-playlist-dl" \
+                  ${cfg.notification.ntfyUrl}/${cfg.notification.topic}
+              ''
+          }
         '';
       };
     };
modules/nix-flake-updater/default.nix
@@ -20,6 +20,7 @@ let
     export NTFY_SERVER="${cfg.ntfyServer}"
     export BUILD_SYSTEMS="${toString cfg.buildSystems}"
     export DRY_RUN="${toString cfg.dryRun}"
+    ${optionalString (cfg.ntfyTokenFile != null) ''export NTFY_TOKEN_FILE="${cfg.ntfyTokenFile}"''}
 
     # Execute the packaged update script (already has tools in PATH)
     exec ${pkgs.nix-flake-update}/bin/nix-flake-update
@@ -85,6 +86,12 @@ in
       description = "ntfy server URL";
     };
 
+    ntfyTokenFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = "Path to file containing ntfy authentication token (optional)";
+    };
+
     dryRun = mkOption {
       type = types.bool;
       default = false;
systems/aion/extra.nix
@@ -99,6 +99,7 @@ in
         enable = true;
         ntfyUrl = "https://ntfy.sbr.pm";
         topic = "homelab";
+        tokenFile = config.age.secrets."ntfy-token".path;
       };
     };
 
@@ -226,6 +227,7 @@ in
         enable = true;
         ntfyUrl = "https://ntfy.sbr.pm";
         topic = "homelab";
+        tokenFile = config.age.secrets."ntfy-token".path;
       };
     };
 
tools/nix-flake-update/nix-flake-update.sh
@@ -11,6 +11,7 @@ GIT_REMOTE="${GIT_REMOTE:-origin}"
 BRANCH_PREFIX="${BRANCH_PREFIX:-flake-update-}"
 NTFY_TOPIC="${NTFY_TOPIC:-nix-updates}"
 NTFY_SERVER="${NTFY_SERVER:-https://ntfy.sh}"
+NTFY_TOKEN_FILE="${NTFY_TOKEN_FILE:-}"
 BUILD_SYSTEMS="${BUILD_SYSTEMS:-}"
 DRY_RUN="${DRY_RUN:-false}"
 
@@ -27,12 +28,24 @@ notify() {
   local message="$3"
   local tags="$4"
 
-  curl -s \
-    -H "Title: $title" \
-    -H "Priority: $priority" \
-    -H "Tags: $tags" \
-    -d "$message" \
-    "$NTFY_SERVER/$NTFY_TOPIC" || true
+  if [ -n "$NTFY_TOKEN_FILE" ] && [ -f "$NTFY_TOKEN_FILE" ]; then
+    # Use authentication token
+    curl -s \
+      -H "Authorization: Bearer $(tr -d '\n' < "$NTFY_TOKEN_FILE")" \
+      -H "Title: $title" \
+      -H "Priority: $priority" \
+      -H "Tags: $tags" \
+      -d "$message" \
+      "$NTFY_SERVER/$NTFY_TOPIC" || true
+  else
+    # No authentication
+    curl -s \
+      -H "Title: $title" \
+      -H "Priority: $priority" \
+      -H "Tags: $tags" \
+      -d "$message" \
+      "$NTFY_SERVER/$NTFY_TOPIC" || true
+  fi
 }
 
 cleanup() {