From 0153ffe5617dcd324dc719626e71e3593cc107b8 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Sun, 25 Oct 2026 10:58:33 +0800 Subject: [PATCH 1/7] feat(nemesis): manage slskd declaratively --- nix/hosts.nix | 1 + nix/modules/slskd.nix | 76 +++++++++++++++++++++++++++++++++++++++++++ sops/slskd.yaml | 20 ++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 nix/modules/slskd.nix create mode 100644 sops/slskd.yaml diff --git a/nix/hosts.nix b/nix/hosts.nix index 0996036b..54f9cd8f 100644 --- a/nix/hosts.nix +++ b/nix/hosts.nix @@ -133,6 +133,7 @@ in cfg.modules.nixos.xremap cfg.modules.nixos.steam cfg.modules.nixos.prismlauncher + cfg.modules.nixos.slskd cfg.modules.nixos.gtnh-server cfg.modules.nixos.gtnh-backups ( diff --git a/nix/modules/slskd.nix b/nix/modules/slskd.nix new file mode 100644 index 00000000..57b1d62e --- /dev/null +++ b/nix/modules/slskd.nix @@ -0,0 +1,76 @@ +{ config, ... }: +let + cfg = config.flake; + inherit (cfg.paths) root; +in +{ + config.flake.modules.nixos.slskd = + { config, primaryUser, ... }: + let + sopsFile = root + "/sops/slskd.yaml"; + in + { + services.slskd = { + enable = true; + group = "users"; + environmentFile = config.sops.templates."slskd.env".path; + openFirewall = false; + settings = { + directories = { + incomplete = "/var/lib/slskd/incomplete"; + downloads = "/var/lib/slskd/downloads"; + }; + flags.force_share_scan = false; + rooms = [ ]; + shares = { + directories = [ ]; + filters = [ ]; + }; + soulseek = { + description = "rrv.sh"; + listen_port = 50300; + }; + web = { + ip_address = "127.0.0.1"; + port = 5030; + https.disabled = true; + }; + }; + }; + + sops = { + secrets = { + "slskd/soulseek-username".sopsFile = sopsFile; + "slskd/soulseek-password".sopsFile = sopsFile; + "slskd/web-username".sopsFile = sopsFile; + "slskd/web-password" = { + inherit sopsFile; + owner = primaryUser.name; + }; + }; + templates."slskd.env" = { + owner = "slskd"; + group = "users"; + mode = "0400"; + content = '' + SLSKD_SLSK_USERNAME=${config.sops.placeholder."slskd/soulseek-username"} + SLSKD_SLSK_PASSWORD=${config.sops.placeholder."slskd/soulseek-password"} + SLSKD_USERNAME=${config.sops.placeholder."slskd/web-username"} + SLSKD_PASSWORD=${config.sops.placeholder."slskd/web-password"} + ''; + }; + }; + + systemd = { + services.slskd.serviceConfig = { + StateDirectoryMode = "0750"; + UMask = "0007"; + WorkingDirectory = "${config.services.slskd.package}/lib/slskd"; + }; + tmpfiles.rules = [ + "d /var/lib/slskd/incomplete 0770 slskd users -" + "d /var/lib/slskd/downloads 0770 slskd users -" + ]; + }; + }; +} diff --git a/sops/slskd.yaml b/sops/slskd.yaml new file mode 100644 index 00000000..090583c3 --- /dev/null +++ b/sops/slskd.yaml @@ -0,0 +1,20 @@ +slskd: + soulseek-username: ENC[AES256_GCM,data:1ThTrtOh9v+D+bFHyg==,iv:hceVl+vCBGu2E9O8mWfpyRB1z669UYh4RMoI2SqK1Mk=,tag:nhJNUZVILcp+NCV99OH41Q==,type:str] + soulseek-password: ENC[AES256_GCM,data:bWXJpSnIOvEc1QXUKm0AdP509tXoghi07pz402r1Hns0BGr6CN/K8cPit2Y=,iv:kyOIEp7FyIwE18B7gW3dsVw/P0SqoSGHGQG5fnr7AeY=,tag:NJSLIhM8D53NScN1oEpD2g==,type:str] + web-username: ENC[AES256_GCM,data:IaDZYxQ=,iv:FrgChB4v/5ul7BC8GqweGl//LCOc3xrHKMEY2d9pDP8=,tag:vE3+BvgJo42rjrsSuRprGw==,type:str] + web-password: ENC[AES256_GCM,data:vO5DhpcHgDwmolACPiyixZg1Jm10sp0BJHagL2zt8zDpQT1/gn2OSpz7D58=,iv:AD//s4t9PnUQ7xgLDbD1v3UXSf9eHLLzGKmUzN0tD1E=,tag:dOc5qbax30zOwoTC5Lk7uw==,type:str] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArWUl5dTVDWFNhK003Q3Bu + bWY4RzN6dFUrMzVBSC9LUFF4bW1XQ1BVL1VnClpzZTUxWjhyblUySnFDUVNCRGow + S2lLWGdwdE9adEoyMzZXbE5WSTdqeTQKLS0tIGE1ZUl5UnZvWjNZSGVZQ282dmh0 + Nkg5SGJDQ1NIYzJtbnU5dXFWQ3lQZ28KZER5mRr650FJ2fHipze2KmFkvZ7hMJBw + cieRZ8yRR3NFskMFDI+wp8zga+zK/P97sf11zni5YjmsW+8Pr7iUZw== + -----END AGE ENCRYPTED FILE----- + recipient: age12l33pas8eptwjc7ewux3d8snyzfzwz0tn9qg5kw8le79fswmjgjqdjgyy6 + lastmodified: "2026-10-25T02:56:20Z" + mac: ENC[AES256_GCM,data:ybmoyd01UZiNyCQn5dnoLIOPpThsYLTDCOARag+qM43jNPGfmJ9fNehwPfXtmMqPsonvex7cnf4DGcbAj3ITthuv41r5j8Ilchs15If3JFObNTpnKdtqNaB9BQVA5JQTEeuXDvrFwDz69ka/3XS4i7QVDroicMOu9+UXQltlRSE=,iv:ZTJmCmZQ74n4cWNhSOnRVzUvh6yBgVFZ4UMIlZLSIsI=,tag:dMkLkG9hyYxGf2h/4JNnIA==,type:str] + unencrypted_suffix: _unencrypted + version: 3.13.3 From 7bd6141c0dacafc54f6542010f248e9fd03cb8f1 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Sun, 25 Oct 2026 11:00:38 +0800 Subject: [PATCH 2/7] sops: update soulseek creds --- sops/slskd.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sops/slskd.yaml b/sops/slskd.yaml index 090583c3..b5738bed 100644 --- a/sops/slskd.yaml +++ b/sops/slskd.yaml @@ -1,6 +1,6 @@ slskd: - soulseek-username: ENC[AES256_GCM,data:1ThTrtOh9v+D+bFHyg==,iv:hceVl+vCBGu2E9O8mWfpyRB1z669UYh4RMoI2SqK1Mk=,tag:nhJNUZVILcp+NCV99OH41Q==,type:str] - soulseek-password: ENC[AES256_GCM,data:bWXJpSnIOvEc1QXUKm0AdP509tXoghi07pz402r1Hns0BGr6CN/K8cPit2Y=,iv:kyOIEp7FyIwE18B7gW3dsVw/P0SqoSGHGQG5fnr7AeY=,tag:NJSLIhM8D53NScN1oEpD2g==,type:str] + soulseek-username: ENC[AES256_GCM,data:M1GIx0Mdugejbte2ms01b46Y/hVMCF7DAEqbSuCZH/ou4mpNBWI=,iv:aHEnudPQeTjapcdgHBDiZsnQXPrvka97dRLtyPDEOzw=,tag:PZooZrEIe6++HGSwzSnQjw==,type:str] + soulseek-password: ENC[AES256_GCM,data:uVBxu5dsn/QsIC38oBCTLabtMiRs0/dh2iSAcxPdEB2O,iv:6/Zm3JMoAUqljezcEii3slPD+nbfsyrNj2mQJDjF2u4=,tag:0Pg/mjAQf1FKcQNEQg8IVQ==,type:str] web-username: ENC[AES256_GCM,data:IaDZYxQ=,iv:FrgChB4v/5ul7BC8GqweGl//LCOc3xrHKMEY2d9pDP8=,tag:vE3+BvgJo42rjrsSuRprGw==,type:str] web-password: ENC[AES256_GCM,data:vO5DhpcHgDwmolACPiyixZg1Jm10sp0BJHagL2zt8zDpQT1/gn2OSpz7D58=,iv:AD//s4t9PnUQ7xgLDbD1v3UXSf9eHLLzGKmUzN0tD1E=,tag:dOc5qbax30zOwoTC5Lk7uw==,type:str] sops: @@ -14,7 +14,7 @@ sops: cieRZ8yRR3NFskMFDI+wp8zga+zK/P97sf11zni5YjmsW+8Pr7iUZw== -----END AGE ENCRYPTED FILE----- recipient: age12l33pas8eptwjc7ewux3d8snyzfzwz0tn9qg5kw8le79fswmjgjqdjgyy6 - lastmodified: "2026-10-25T02:56:20Z" - mac: ENC[AES256_GCM,data:ybmoyd01UZiNyCQn5dnoLIOPpThsYLTDCOARag+qM43jNPGfmJ9fNehwPfXtmMqPsonvex7cnf4DGcbAj3ITthuv41r5j8Ilchs15If3JFObNTpnKdtqNaB9BQVA5JQTEeuXDvrFwDz69ka/3XS4i7QVDroicMOu9+UXQltlRSE=,iv:ZTJmCmZQ74n4cWNhSOnRVzUvh6yBgVFZ4UMIlZLSIsI=,tag:dMkLkG9hyYxGf2h/4JNnIA==,type:str] + lastmodified: "2026-10-25T03:12:39Z" + mac: ENC[AES256_GCM,data:Qs+1a36ow4NdaINiK5OWbMj8I4adYu0Sx5WlfvLCbBoVDHab/xPpLE86xw2nWOkiUrv8ytsyK/852uLVOU8U+YuwbSvdqa2hI+35pLH1NYWAcpGFCIAeyghipmVGtBmmlL9OUz6MPes+//NMBBMjQhllh0jZSUkEPzrWMUBr9zU=,iv:AlMnommszmxTYsOzaPeRZc/JnSaUEpbQPKkdG0G/ZeE=,tag:iRLaBQKv2bOgSw9hxvZMog==,type:str] unencrypted_suffix: _unencrypted version: 3.13.3 From 5119c74bf6583ccf9904821ce9c8ab37f363deb7 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Sun, 25 Oct 2026 11:13:50 +0800 Subject: [PATCH 3/7] feat(slskd): share completed downloads --- nix/modules/slskd.nix | 43 ++++++++++++++----------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/nix/modules/slskd.nix b/nix/modules/slskd.nix index 57b1d62e..1aca637b 100644 --- a/nix/modules/slskd.nix +++ b/nix/modules/slskd.nix @@ -5,36 +5,24 @@ let in { config.flake.modules.nixos.slskd = - { config, primaryUser, ... }: + { config, ... }: let + directories = { + downloads = "/var/lib/slskd/downloads"; + incomplete = "/var/lib/slskd/incomplete"; + }; sopsFile = root + "/sops/slskd.yaml"; in { services.slskd = { enable = true; + # The default group is slskd. Use users so the primary user and MusicGrabber can access completed files. group = "users"; environmentFile = config.sops.templates."slskd.env".path; - openFirewall = false; + openFirewall = true; settings = { - directories = { - incomplete = "/var/lib/slskd/incomplete"; - downloads = "/var/lib/slskd/downloads"; - }; - flags.force_share_scan = false; - rooms = [ ]; - shares = { - directories = [ ]; - filters = [ ]; - }; - soulseek = { - description = "rrv.sh"; - listen_port = 50300; - }; - web = { - ip_address = "127.0.0.1"; - port = 5030; - https.disabled = true; - }; + inherit directories; + shares.directories = [ directories.downloads ]; }; }; @@ -43,15 +31,12 @@ in "slskd/soulseek-username".sopsFile = sopsFile; "slskd/soulseek-password".sopsFile = sopsFile; "slskd/web-username".sopsFile = sopsFile; - "slskd/web-password" = { - inherit sopsFile; - owner = primaryUser.name; - }; + "slskd/web-password".sopsFile = sopsFile; }; templates."slskd.env" = { owner = "slskd"; - group = "users"; - mode = "0400"; + # Restart slskd after credential changes so it reloads the rendered environment file. + restartUnits = [ "slskd.service" ]; content = '' SLSKD_SLSK_USERNAME=${config.sops.placeholder."slskd/soulseek-username"} SLSKD_SLSK_PASSWORD=${config.sops.placeholder."slskd/soulseek-password"} @@ -68,8 +53,8 @@ in WorkingDirectory = "${config.services.slskd.package}/lib/slskd"; }; tmpfiles.rules = [ - "d /var/lib/slskd/incomplete 0770 slskd users -" - "d /var/lib/slskd/downloads 0770 slskd users -" + "d ${directories.incomplete} 0770 slskd users -" + "d ${directories.downloads} 0770 slskd users -" ]; }; }; From 594791e5b3533f71be4533359f59746b6c72ced0 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Sun, 25 Oct 2026 11:15:00 +0800 Subject: [PATCH 4/7] fix(slskd): keep shared downloads writable --- nix/modules/slskd.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nix/modules/slskd.nix b/nix/modules/slskd.nix index 1aca637b..14cd1e8a 100644 --- a/nix/modules/slskd.nix +++ b/nix/modules/slskd.nix @@ -5,7 +5,7 @@ let in { config.flake.modules.nixos.slskd = - { config, ... }: + { config, lib, ... }: let directories = { downloads = "/var/lib/slskd/downloads"; @@ -48,6 +48,8 @@ in systemd = { services.slskd.serviceConfig = { + # Nixpkgs makes every shared directory read-only, but this directory also receives completed downloads. + ReadOnlyPaths = lib.mkForce [ ]; StateDirectoryMode = "0750"; UMask = "0007"; WorkingDirectory = "${config.services.slskd.package}/lib/slskd"; From 4ae9722d1eac4f8615b056ca4e89ec4fe5872711 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Sun, 25 Oct 2026 11:16:59 +0800 Subject: [PATCH 5/7] fix(sops): use valid anonymous Soulseek username --- sops/slskd.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sops/slskd.yaml b/sops/slskd.yaml index b5738bed..3676e4fc 100644 --- a/sops/slskd.yaml +++ b/sops/slskd.yaml @@ -1,5 +1,5 @@ slskd: - soulseek-username: ENC[AES256_GCM,data:M1GIx0Mdugejbte2ms01b46Y/hVMCF7DAEqbSuCZH/ou4mpNBWI=,iv:aHEnudPQeTjapcdgHBDiZsnQXPrvka97dRLtyPDEOzw=,tag:PZooZrEIe6++HGSwzSnQjw==,type:str] + soulseek-username: ENC[AES256_GCM,data:feMMR/aqs/EnAkKPPUuDGrM69UE=,iv:qwI7hMw//4YhfMQhhnWCMA2+Ak2NweDn5D1foVfWc4M=,tag:YcvfRSgo1ohnhJNBCB4Psw==,type:str] soulseek-password: ENC[AES256_GCM,data:uVBxu5dsn/QsIC38oBCTLabtMiRs0/dh2iSAcxPdEB2O,iv:6/Zm3JMoAUqljezcEii3slPD+nbfsyrNj2mQJDjF2u4=,tag:0Pg/mjAQf1FKcQNEQg8IVQ==,type:str] web-username: ENC[AES256_GCM,data:IaDZYxQ=,iv:FrgChB4v/5ul7BC8GqweGl//LCOc3xrHKMEY2d9pDP8=,tag:vE3+BvgJo42rjrsSuRprGw==,type:str] web-password: ENC[AES256_GCM,data:vO5DhpcHgDwmolACPiyixZg1Jm10sp0BJHagL2zt8zDpQT1/gn2OSpz7D58=,iv:AD//s4t9PnUQ7xgLDbD1v3UXSf9eHLLzGKmUzN0tD1E=,tag:dOc5qbax30zOwoTC5Lk7uw==,type:str] @@ -14,7 +14,7 @@ sops: cieRZ8yRR3NFskMFDI+wp8zga+zK/P97sf11zni5YjmsW+8Pr7iUZw== -----END AGE ENCRYPTED FILE----- recipient: age12l33pas8eptwjc7ewux3d8snyzfzwz0tn9qg5kw8le79fswmjgjqdjgyy6 - lastmodified: "2026-10-25T03:12:39Z" - mac: ENC[AES256_GCM,data:Qs+1a36ow4NdaINiK5OWbMj8I4adYu0Sx5WlfvLCbBoVDHab/xPpLE86xw2nWOkiUrv8ytsyK/852uLVOU8U+YuwbSvdqa2hI+35pLH1NYWAcpGFCIAeyghipmVGtBmmlL9OUz6MPes+//NMBBMjQhllh0jZSUkEPzrWMUBr9zU=,iv:AlMnommszmxTYsOzaPeRZc/JnSaUEpbQPKkdG0G/ZeE=,tag:iRLaBQKv2bOgSw9hxvZMog==,type:str] + lastmodified: "2026-10-25T03:16:54Z" + mac: ENC[AES256_GCM,data:h0mRaqJNO9+osHptEtnQRfK1letH0juPFL8D4pEi42yDMRpiVwUs+dJEzyc+K2z9fYNRlQ1z29gQgOet0hVIJsmGsHeL6Z4THYGCdhIB2hY1+XB0F+ONTj/AQvZXBEzcJkzFwI0UiJtE0ZEDL4QRPIMrusVd8InmODvp3LKhhj8=,iv:g2XiVPR32lpBivGIcO4qTeQxxaZLmoU7xXW1TcDhnqg=,tag:J3CY/y/eeJ7VZfjyN0TeZA==,type:str] unencrypted_suffix: _unencrypted version: 3.13.3 From 8863e12ee3e9e86cfffae7e5d2a4ed8b96011838 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Sun, 25 Oct 2026 11:28:54 +0800 Subject: [PATCH 6/7] feat(alpha): run native slskd --- nix/hosts.nix | 1 + nix/modules/slskd.nix | 193 +++++++++++++++++++++++++++++++----------- sops/slskd-alpha.yaml | 20 +++++ 3 files changed, 166 insertions(+), 48 deletions(-) create mode 100644 sops/slskd-alpha.yaml diff --git a/nix/hosts.nix b/nix/hosts.nix index 54f9cd8f..8ff7d6a4 100644 --- a/nix/hosts.nix +++ b/nix/hosts.nix @@ -31,6 +31,7 @@ in cfg.modules.darwin.user-primary cfg.modules.darwin.agent-browser-shared cfg.modules.darwin.rosetta-builder + cfg.modules.darwin.slskd ]; }; kikir = { diff --git a/nix/modules/slskd.nix b/nix/modules/slskd.nix index 14cd1e8a..32b5b16d 100644 --- a/nix/modules/slskd.nix +++ b/nix/modules/slskd.nix @@ -4,60 +4,157 @@ let inherit (cfg.paths) root; in { - config.flake.modules.nixos.slskd = - { config, lib, ... }: - let - directories = { - downloads = "/var/lib/slskd/downloads"; - incomplete = "/var/lib/slskd/incomplete"; - }; - sopsFile = root + "/sops/slskd.yaml"; - in - { - services.slskd = { - enable = true; - # The default group is slskd. Use users so the primary user and MusicGrabber can access completed files. - group = "users"; - environmentFile = config.sops.templates."slskd.env".path; - openFirewall = true; - settings = { + config.flake.modules = { + darwin.slskd = + { + config, + pkgs, + primaryUser, + ... + }: + let + version = "0.26.0"; + stateDir = "${config.users.users.${primaryUser.name}.home}/Library/Application Support/slskd"; + directories = { + downloads = "${stateDir}/downloads"; + incomplete = "${stateDir}/incomplete"; + }; + sopsFile = root + "/sops/slskd-alpha.yaml"; + package = pkgs.stdenvNoCC.mkDerivation { + pname = "slskd"; + inherit version; + src = pkgs.fetchzip { + url = "https://github.com/slskd/slskd/releases/download/${version}/slskd-${version}-osx-arm64.zip"; + hash = "sha256-vdoJy2ZpElYJA43mNZfExNK4Mpnw8ApVxVDzPq1s97I="; + stripRoot = false; + }; + installPhase = '' + runHook preInstall + mkdir -p "$out/libexec/slskd" + cp -R . "$out/libexec/slskd/" + chmod +x "$out/libexec/slskd/slskd" + runHook postInstall + ''; + meta = { + description = "Modern client-server application for the Soulseek file-sharing network"; + homepage = "https://github.com/slskd/slskd"; + license = pkgs.lib.licenses.agpl3Only; + platforms = [ "aarch64-darwin" ]; + }; + }; + settings = (pkgs.formats.yaml { }).generate "slskd.yml" { inherit directories; + flags.no_version_check = true; shares.directories = [ directories.downloads ]; + web = { + ip_address = "127.0.0.1"; + https.disabled = true; + }; + }; + program = pkgs.writeShellScript "slskd" '' + set -euo pipefail + umask 0077 + mkdir -p ${pkgs.lib.escapeShellArg directories.incomplete} ${pkgs.lib.escapeShellArg directories.downloads} + export SLSKD_SLSK_USERNAME="$(<${config.sops.secrets."slskd/soulseek-username".path})" + export SLSKD_SLSK_PASSWORD="$(<${config.sops.secrets."slskd/soulseek-password".path})" + export SLSKD_USERNAME="$(<${config.sops.secrets."slskd/web-username".path})" + export SLSKD_PASSWORD="$(<${config.sops.secrets."slskd/web-password".path})" + cd ${package}/libexec/slskd + exec ${package}/libexec/slskd/slskd \ + --app-dir ${pkgs.lib.escapeShellArg stateDir} \ + --config ${settings} + ''; + in + { + launchd.user.agents.slskd = { + serviceConfig = { + ProgramArguments = [ "${program}" ]; + RunAtLoad = true; + KeepAlive.SuccessfulExit = false; + ProcessType = "Background"; + ThrottleInterval = 5; + # Change the plist when credentials change so launchd restarts slskd with the new values. + EnvironmentVariables.SLSKD_SECRETS_HASH = + config.sops.secrets."slskd/soulseek-username".sopsFileHash; + }; + managedBy = "flake.modules.darwin.slskd"; }; - }; - sops = { - secrets = { - "slskd/soulseek-username".sopsFile = sopsFile; - "slskd/soulseek-password".sopsFile = sopsFile; - "slskd/web-username".sopsFile = sopsFile; - "slskd/web-password".sopsFile = sopsFile; - }; - templates."slskd.env" = { - owner = "slskd"; - # Restart slskd after credential changes so it reloads the rendered environment file. - restartUnits = [ "slskd.service" ]; - content = '' - SLSKD_SLSK_USERNAME=${config.sops.placeholder."slskd/soulseek-username"} - SLSKD_SLSK_PASSWORD=${config.sops.placeholder."slskd/soulseek-password"} - SLSKD_USERNAME=${config.sops.placeholder."slskd/web-username"} - SLSKD_PASSWORD=${config.sops.placeholder."slskd/web-password"} - ''; + sops.secrets = { + "slskd/soulseek-username" = { + inherit sopsFile; + owner = primaryUser.name; + }; + "slskd/soulseek-password" = { + inherit sopsFile; + owner = primaryUser.name; + }; + "slskd/web-username" = { + inherit sopsFile; + owner = primaryUser.name; + }; + "slskd/web-password" = { + inherit sopsFile; + owner = primaryUser.name; + }; }; }; - systemd = { - services.slskd.serviceConfig = { - # Nixpkgs makes every shared directory read-only, but this directory also receives completed downloads. - ReadOnlyPaths = lib.mkForce [ ]; - StateDirectoryMode = "0750"; - UMask = "0007"; - WorkingDirectory = "${config.services.slskd.package}/lib/slskd"; - }; - tmpfiles.rules = [ - "d ${directories.incomplete} 0770 slskd users -" - "d ${directories.downloads} 0770 slskd users -" - ]; + nixos.slskd = + { config, lib, ... }: + let + directories = { + downloads = "/var/lib/slskd/downloads"; + incomplete = "/var/lib/slskd/incomplete"; + }; + sopsFile = root + "/sops/slskd.yaml"; + in + { + services.slskd = { + enable = true; + # The default group is slskd. Use users so the primary user and MusicGrabber can access completed files. + group = "users"; + environmentFile = config.sops.templates."slskd.env".path; + openFirewall = true; + settings = { + inherit directories; + shares.directories = [ directories.downloads ]; + }; + }; + + sops = { + secrets = { + "slskd/soulseek-username".sopsFile = sopsFile; + "slskd/soulseek-password".sopsFile = sopsFile; + "slskd/web-username".sopsFile = sopsFile; + "slskd/web-password".sopsFile = sopsFile; + }; + templates."slskd.env" = { + owner = "slskd"; + # Restart slskd after credential changes so it reloads the rendered environment file. + restartUnits = [ "slskd.service" ]; + content = '' + SLSKD_SLSK_USERNAME=${config.sops.placeholder."slskd/soulseek-username"} + SLSKD_SLSK_PASSWORD=${config.sops.placeholder."slskd/soulseek-password"} + SLSKD_USERNAME=${config.sops.placeholder."slskd/web-username"} + SLSKD_PASSWORD=${config.sops.placeholder."slskd/web-password"} + ''; + }; + }; + + systemd = { + services.slskd.serviceConfig = { + # Nixpkgs makes every shared directory read-only, but this directory also receives completed downloads. + ReadOnlyPaths = lib.mkForce [ ]; + StateDirectoryMode = "0750"; + UMask = "0007"; + WorkingDirectory = "${config.services.slskd.package}/lib/slskd"; + }; + tmpfiles.rules = [ + "d ${directories.incomplete} 0770 slskd users -" + "d ${directories.downloads} 0770 slskd users -" + ]; + }; }; - }; + }; } diff --git a/sops/slskd-alpha.yaml b/sops/slskd-alpha.yaml new file mode 100644 index 00000000..4575cb34 --- /dev/null +++ b/sops/slskd-alpha.yaml @@ -0,0 +1,20 @@ +slskd: + soulseek-username: ENC[AES256_GCM,data:FlMnJQ3PvfXKUq34pPTNpwBybWQ=,iv:NBroOxt/N/ZgIAlE/qjEdrnF+DCGoIgam71qzIxmQcI=,tag:1mv4UFQHgmCPNAyiOSy7HA==,type:str] + soulseek-password: ENC[AES256_GCM,data:QAc7OzXro3Yh7rw0EkoUX9ff+BJujFuv9pFf2Ne+5fkmUqywMvQ1ozbA0Ww=,iv:LzFt8Y/BXezN8o8ciaW8nrfjMr+oh2h6KrOK2iv8mjU=,tag:GfsWFzfchuOvTVJiH+IHXw==,type:str] + web-username: ENC[AES256_GCM,data:j86kzAk=,iv:hGmxmezunMYSziiu4zVJPc3sGG9nxwNypGA6VQDQZQY=,tag:/xTwTnuIWAjQXXwAvuRsBA==,type:str] + web-password: ENC[AES256_GCM,data:kHbl4cQMiO+/ISn/VL6i4YjH8ghyIMJ7kdHvsQwK85Z+KfL46TqRQmJY3hU=,iv:UOsAZb6m4we6vLEBqXxm/z6/nXSThzScNYmqgyEX/68=,tag:TbzHLaVMZn+cCb2cd76Kzg==,type:str] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxOWlmTGRMa0FmV2hWdUlp + RkV5a0ZRMXVmc01EMWNvRUpoTXhsT2hQb0FnCnM5MXVMR3FkclVqTTVZb3VWdTJk + L0tWYVplbHBqcEx0N0hQYmN0b3pIU28KLS0tIGYxWXNyMUoxOUx1OVY5cUNUSFY2 + YlU1aDU4NDU3Q1RPQVk4TWs1dXhiMDQKE5xprmpEw2QNA+wT/hvVPZiW1AyLPgKD + iJtFOiQGHzDG8WgrP9ffl0jNwkXCXII86z4TSqk9ccRjO6aFBL88EQ== + -----END AGE ENCRYPTED FILE----- + recipient: age12l33pas8eptwjc7ewux3d8snyzfzwz0tn9qg5kw8le79fswmjgjqdjgyy6 + lastmodified: "2026-10-25T03:24:13Z" + mac: ENC[AES256_GCM,data:QZ7jrVSHM0fKkMqUV65ljtOz5Z1DKiAuQa9vnbKp8cpsM4UHVag7yeDc5nQbtGU7dWvU0scLsTzuCGfks5eyGPNR70UYx6LB2pc2J/rtlkTxJuKQ1ThWtWpJTVCUA8bOqwptwWa2qXFLlXjXUZtOajh6U3o+g6ooB8UqfB/SNMk=,iv:QTapNFvuvMnZodypfmjMQwFU2v/NSG0wLHO4c04xLIU=,tag:EhQmExrm443H0or1PbSCyg==,type:str] + unencrypted_suffix: _unencrypted + version: 3.13.3 From f9d30ee20e1a117f4a06ae9378b685a9943dc5d6 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Sun, 25 Oct 2026 11:49:09 +0800 Subject: [PATCH 7/7] refactor(slskd): share credentials across hosts --- nix/modules/slskd.nix | 76 +++++++++++++++++++------------------------ sops/slskd-alpha.yaml | 20 ------------ 2 files changed, 34 insertions(+), 62 deletions(-) delete mode 100644 sops/slskd-alpha.yaml diff --git a/nix/modules/slskd.nix b/nix/modules/slskd.nix index 32b5b16d..59903089 100644 --- a/nix/modules/slskd.nix +++ b/nix/modules/slskd.nix @@ -2,6 +2,7 @@ let cfg = config.flake; inherit (cfg.paths) root; + sopsFile = root + "/sops/slskd.yaml"; in { config.flake.modules = { @@ -19,7 +20,6 @@ in downloads = "${stateDir}/downloads"; incomplete = "${stateDir}/incomplete"; }; - sopsFile = root + "/sops/slskd-alpha.yaml"; package = pkgs.stdenvNoCC.mkDerivation { pname = "slskd"; inherit version; @@ -46,58 +46,49 @@ in inherit directories; flags.no_version_check = true; shares.directories = [ directories.downloads ]; - web = { - ip_address = "127.0.0.1"; - https.disabled = true; - }; }; - program = pkgs.writeShellScript "slskd" '' - set -euo pipefail - umask 0077 - mkdir -p ${pkgs.lib.escapeShellArg directories.incomplete} ${pkgs.lib.escapeShellArg directories.downloads} - export SLSKD_SLSK_USERNAME="$(<${config.sops.secrets."slskd/soulseek-username".path})" - export SLSKD_SLSK_PASSWORD="$(<${config.sops.secrets."slskd/soulseek-password".path})" - export SLSKD_USERNAME="$(<${config.sops.secrets."slskd/web-username".path})" - export SLSKD_PASSWORD="$(<${config.sops.secrets."slskd/web-password".path})" - cd ${package}/libexec/slskd - exec ${package}/libexec/slskd/slskd \ - --app-dir ${pkgs.lib.escapeShellArg stateDir} \ - --config ${settings} - ''; in { launchd.user.agents.slskd = { serviceConfig = { - ProgramArguments = [ "${program}" ]; + ProgramArguments = [ + "${pkgs.writeShellScript "slskd" '' + set -euo pipefail + umask 0077 + mkdir -p ${pkgs.lib.escapeShellArg directories.incomplete} ${pkgs.lib.escapeShellArg directories.downloads} + export SLSKD_SLSK_USERNAME="$(<${ + config.sops.secrets."slskd/soulseek-username".path + })-${config.networking.hostName}" + export SLSKD_SLSK_PASSWORD="$(<${config.sops.secrets."slskd/soulseek-password".path})" + export SLSKD_USERNAME="$(<${config.sops.secrets."slskd/web-username".path})" + export SLSKD_PASSWORD="$(<${config.sops.secrets."slskd/web-password".path})" + cd ${package}/libexec/slskd + exec ${package}/libexec/slskd/slskd \ + --app-dir ${pkgs.lib.escapeShellArg stateDir} \ + --config ${settings} + ''}" + ]; RunAtLoad = true; KeepAlive.SuccessfulExit = false; - ProcessType = "Background"; - ThrottleInterval = 5; # Change the plist when credentials change so launchd restarts slskd with the new values. EnvironmentVariables.SLSKD_SECRETS_HASH = config.sops.secrets."slskd/soulseek-username".sopsFileHash; }; - managedBy = "flake.modules.darwin.slskd"; }; - sops.secrets = { - "slskd/soulseek-username" = { - inherit sopsFile; - owner = primaryUser.name; - }; - "slskd/soulseek-password" = { - inherit sopsFile; - owner = primaryUser.name; - }; - "slskd/web-username" = { - inherit sopsFile; - owner = primaryUser.name; - }; - "slskd/web-password" = { - inherit sopsFile; - owner = primaryUser.name; - }; - }; + sops.secrets = + let + mkSecret = _: { + inherit sopsFile; + owner = primaryUser.name; + }; + in + pkgs.lib.genAttrs [ + "slskd/soulseek-username" + "slskd/soulseek-password" + "slskd/web-username" + "slskd/web-password" + ] mkSecret; }; nixos.slskd = @@ -107,7 +98,6 @@ in downloads = "/var/lib/slskd/downloads"; incomplete = "/var/lib/slskd/incomplete"; }; - sopsFile = root + "/sops/slskd.yaml"; in { services.slskd = { @@ -134,7 +124,9 @@ in # Restart slskd after credential changes so it reloads the rendered environment file. restartUnits = [ "slskd.service" ]; content = '' - SLSKD_SLSK_USERNAME=${config.sops.placeholder."slskd/soulseek-username"} + SLSKD_SLSK_USERNAME=${ + config.sops.placeholder."slskd/soulseek-username" + }-${config.networking.hostName} SLSKD_SLSK_PASSWORD=${config.sops.placeholder."slskd/soulseek-password"} SLSKD_USERNAME=${config.sops.placeholder."slskd/web-username"} SLSKD_PASSWORD=${config.sops.placeholder."slskd/web-password"} diff --git a/sops/slskd-alpha.yaml b/sops/slskd-alpha.yaml deleted file mode 100644 index 4575cb34..00000000 --- a/sops/slskd-alpha.yaml +++ /dev/null @@ -1,20 +0,0 @@ -slskd: - soulseek-username: ENC[AES256_GCM,data:FlMnJQ3PvfXKUq34pPTNpwBybWQ=,iv:NBroOxt/N/ZgIAlE/qjEdrnF+DCGoIgam71qzIxmQcI=,tag:1mv4UFQHgmCPNAyiOSy7HA==,type:str] - soulseek-password: ENC[AES256_GCM,data:QAc7OzXro3Yh7rw0EkoUX9ff+BJujFuv9pFf2Ne+5fkmUqywMvQ1ozbA0Ww=,iv:LzFt8Y/BXezN8o8ciaW8nrfjMr+oh2h6KrOK2iv8mjU=,tag:GfsWFzfchuOvTVJiH+IHXw==,type:str] - web-username: ENC[AES256_GCM,data:j86kzAk=,iv:hGmxmezunMYSziiu4zVJPc3sGG9nxwNypGA6VQDQZQY=,tag:/xTwTnuIWAjQXXwAvuRsBA==,type:str] - web-password: ENC[AES256_GCM,data:kHbl4cQMiO+/ISn/VL6i4YjH8ghyIMJ7kdHvsQwK85Z+KfL46TqRQmJY3hU=,iv:UOsAZb6m4we6vLEBqXxm/z6/nXSThzScNYmqgyEX/68=,tag:TbzHLaVMZn+cCb2cd76Kzg==,type:str] -sops: - age: - - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxOWlmTGRMa0FmV2hWdUlp - RkV5a0ZRMXVmc01EMWNvRUpoTXhsT2hQb0FnCnM5MXVMR3FkclVqTTVZb3VWdTJk - L0tWYVplbHBqcEx0N0hQYmN0b3pIU28KLS0tIGYxWXNyMUoxOUx1OVY5cUNUSFY2 - YlU1aDU4NDU3Q1RPQVk4TWs1dXhiMDQKE5xprmpEw2QNA+wT/hvVPZiW1AyLPgKD - iJtFOiQGHzDG8WgrP9ffl0jNwkXCXII86z4TSqk9ccRjO6aFBL88EQ== - -----END AGE ENCRYPTED FILE----- - recipient: age12l33pas8eptwjc7ewux3d8snyzfzwz0tn9qg5kw8le79fswmjgjqdjgyy6 - lastmodified: "2026-10-25T03:24:13Z" - mac: ENC[AES256_GCM,data:QZ7jrVSHM0fKkMqUV65ljtOz5Z1DKiAuQa9vnbKp8cpsM4UHVag7yeDc5nQbtGU7dWvU0scLsTzuCGfks5eyGPNR70UYx6LB2pc2J/rtlkTxJuKQ1ThWtWpJTVCUA8bOqwptwWa2qXFLlXjXUZtOajh6U3o+g6ooB8UqfB/SNMk=,iv:QTapNFvuvMnZodypfmjMQwFU2v/NSG0wLHO4c04xLIU=,tag:EhQmExrm443H0or1PbSCyg==,type:str] - unencrypted_suffix: _unencrypted - version: 3.13.3