From 8462b3a5a820411eacec2945e343abd0e8f7310e Mon Sep 17 00:00:00 2001 From: alexion Date: Tue, 13 Oct 2026 20:33:25 -0400 Subject: [PATCH] refactor(copyparty): separate deployment and service policy Move Copyparty rendering into shared dotfiles utilities, keep guest wiring focused on host/container plumbing, and let the service module derive effective volume policy. --- configs/nixos/guests/copyparty.nix | 104 ++++---- configs/nixos/hosts/pikachu/default.nix | 16 +- configs/nixos/modules/services/copyparty.nix | 243 +++++++++---------- src/dotfiles/default.nix | 2 +- src/dotfiles/{util.nix => util/default.nix} | 3 +- src/dotfiles/util/format-copyparty.nix | 58 +++++ 6 files changed, 226 insertions(+), 200 deletions(-) rename src/dotfiles/{util.nix => util/default.nix} (94%) create mode 100644 src/dotfiles/util/format-copyparty.nix diff --git a/configs/nixos/guests/copyparty.nix b/configs/nixos/guests/copyparty.nix index fd4d9b7..1716289 100644 --- a/configs/nixos/guests/copyparty.nix +++ b/configs/nixos/guests/copyparty.nix @@ -5,57 +5,13 @@ }: let cfg = config.guests.copyparty; # NOLINT(let_order) - - volumeType = lib.types.submodule { - options = { - hostPath = lib.mkOption { - type = lib.types.str; - description = "Host path bind-mounted into the Copyparty guest for this volume."; - }; - - path = lib.mkOption { - type = lib.types.str; - description = "Guest path served by this Copyparty volume."; - }; - - access = lib.mkOption { - type = lib.types.attrsOf (lib.types.listOf lib.types.str); - default = { }; - description = "Copyparty access map from permission string to account names."; - }; - - readOnly = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Serve this Copyparty volume read-only."; - }; - - flags = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = "Copyparty volume flags."; - }; - }; - }; in { options.guests.copyparty = { - accountName = lib.mkOption { - type = lib.types.str; - default = "alexion"; - description = "Copyparty account name configured for password authentication."; - }; - passwordSecret = lib.mkOption { type = lib.types.str; - default = "services/copyparty/users/alexion/password"; - description = "SOPS secret name containing the Copyparty account password."; - }; - - stateDir = lib.mkOption { - type = lib.types.str; - default = "/var/lib/copyparty"; - description = "Guest path where Copyparty stores service state."; + default = "services/copyparty/users/${config.user.name}/password"; + description = "SOPS secret name that contains the Copyparty account password."; }; statePath = lib.mkOption { @@ -72,7 +28,39 @@ in }; volumes = lib.mkOption { - type = lib.types.attrsOf volumeType; + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + hostPath = lib.mkOption { + type = lib.types.str; + description = "Host path bind-mounted into the Copyparty guest for this volume."; + }; + + path = lib.mkOption { + type = lib.types.str; + description = "Guest path served by this Copyparty volume."; + }; + + access = lib.mkOption { + type = lib.types.attrsOf (lib.types.listOf lib.types.str); + default = { }; + description = "Copyparty access map from permission string to account names."; + }; + + readOnly = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Serve this Copyparty volume read-only."; + }; + + flags = lib.mkOption { + type = lib.types.attrs; + default = { }; + description = "Copyparty volume flags."; + }; + }; + } + ); default = { }; description = "Copyparty volumes keyed by their URL path."; }; @@ -102,24 +90,26 @@ in guests.copyparty = { interior.modules.services.copyparty = { enable = true; - inherit (cfg) accountName stateDir; passwordFile = "/run/secrets/${cfg.passwordSecret}"; reverseProxy = { enable = cfg.reverseProxy.enable; inherit (cfg.reverseProxy) trustedSources; }; - volumes = lib.mapAttrs (_urlPath: volume: { - inherit (volume) - path - access - flags - readOnly - ; - }) cfg.volumes; + volumes = lib.mapAttrs ( + _urlPath: volume: + { + inherit (volume) + path + readOnly + ; + } + // lib.optionalAttrs (volume.access != { }) { inherit (volume) access; } + // lib.optionalAttrs (volume.flags != { }) { inherit (volume) flags; } + ) cfg.volumes; }; mounts = { - ${cfg.stateDir} = { + "/var/lib/copyparty" = { directory = cfg.statePath; owner = config.registry."stable-uid".${config.networking.hostName}.copyparty; group = "storage"; diff --git a/configs/nixos/hosts/pikachu/default.nix b/configs/nixos/hosts/pikachu/default.nix index 62c0be8..b7bdf4d 100644 --- a/configs/nixos/hosts/pikachu/default.nix +++ b/configs/nixos/hosts/pikachu/default.nix @@ -129,35 +129,21 @@ "/" = { hostPath = "/pikachu/data/copyparty/root"; path = "/srv"; - access.r = [ "alexion" ]; readOnly = true; }; "/inbox" = { hostPath = "/pikachu/__inbox"; path = "/pikachu/__inbox"; - access.A = [ "alexion" ]; - flags = [ - "d2d" - "d2t" - "dthumb" - ]; }; "/media" = { hostPath = "/pikachu/media"; path = "/pikachu/media"; - access.A = [ "alexion" ]; - flags = [ - "d2d" - "d2t" - "dthumb" - ]; }; "/opds" = { hostPath = "/pikachu/data/opds"; path = "/pikachu/data/opds"; - access.r = [ "alexion" ]; readOnly = true; - flags = [ "opds" ]; + flags.opds = true; }; }; }; diff --git a/configs/nixos/modules/services/copyparty.nix b/configs/nixos/modules/services/copyparty.nix index 25c1459..0461df3 100644 --- a/configs/nixos/modules/services/copyparty.nix +++ b/configs/nixos/modules/services/copyparty.nix @@ -2,147 +2,135 @@ config, lib, pkgs, + util, ... }: let cfg = config.modules.services.copyparty; # NOLINT(let_order) - volumeType = lib.types.submodule { - options = { - path = lib.mkOption { - type = lib.types.str; - description = "Filesystem path served by this volume."; - }; + accountName = config.user.name; - access = lib.mkOption { - type = lib.types.attrsOf (lib.types.listOf lib.types.str); - default = { }; - example = { - A = [ "alexion" ]; - }; - description = "Copyparty access map from permission string to account names."; - }; + serviceGroup = "storage"; - readOnly = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Serve this volume read-only."; - }; + serviceStateDir = "/var/lib/copyparty"; - flags = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - example = [ "dthumb" ]; - description = "Copyparty volume flags."; - }; - }; + serviceUser = "copyparty"; + + writableVolumes = lib.filter (volume: !volume.readOnly) (lib.attrValues cfg.volumes); + + port = 3923; + defaultAccess = { + A = [ accountName ]; }; - configScript = pkgs.writeShellScript "copyparty-config" '' - set -eu + defaultWritableFlags = { + d2d = true; + d2t = true; + dthumb = true; + }; - umask 077 - password="$(${pkgs.coreutils}/bin/tr -d '\n' < "$CREDENTIALS_DIRECTORY/password")" + credentialHash = builtins.substring 0 16 (builtins.hashString "sha256" accountName); - cat > /run/copyparty/copyparty.conf <