{ config, lib, pkgs, ... }: let cfg = config.modules.services.copyparty; # NOLINT(let_order) in { options.modules.services.copyparty = { enable = lib.mkEnableOption "Copyparty"; port = lib.mkOption { type = lib.types.port; default = 3923; description = "Port Copyparty listens on."; }; user = lib.mkOption { type = lib.types.str; default = "copyparty"; description = "User account that runs Copyparty."; }; group = lib.mkOption { type = lib.types.str; default = "storage"; description = "Group account that owns Copyparty's writable state and served files."; }; accountName = lib.mkOption { type = lib.types.str; default = "alexion"; description = "Copyparty account name configured for password authentication."; }; passwordFile = lib.mkOption { type = lib.types.str; description = "Runtime file containing the Copyparty account password."; }; stateDir = lib.mkOption { type = lib.types.str; default = "/var/lib/copyparty"; description = "Directory where Copyparty stores service state."; }; volumes = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule { options = { path = lib.mkOption { type = lib.types.externalPath; description = "Filesystem path served by this volume."; }; 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."; }; readOnly = lib.mkOption { type = lib.types.bool; default = false; description = "Serve this volume read-only."; }; flags = lib.mkOption { type = lib.types.attrs; default = { }; example = { dthumb = true; }; description = "Copyparty volume flags."; }; }; } ); default = { }; description = "Copyparty volumes keyed by their URL path."; }; reverseProxy = { enable = lib.mkEnableOption "reverse-proxy trust settings for Copyparty"; trustedSources = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "IP ranges that may supply Copyparty's forwarded client IP header."; }; }; }; config = lib.mkMerge [ { registry."stable-uid".${config.networking.hostName}.copyparty = 20002; } (lib.mkIf cfg.enable { assertions = [ { assertion = cfg.volumes != { }; message = "modules.services.copyparty.volumes must declare at least one served volume."; } { assertion = !cfg.reverseProxy.enable || cfg.reverseProxy.trustedSources != [ ]; message = "modules.services.copyparty.reverseProxy.trustedSources must name at least one trusted proxy source when reverse-proxy support is enabled."; } ]; services.copyparty = { enable = true; inherit (cfg) group stateDir user ; package = pkgs.copyparty-min; settings = { i = "0.0.0.0"; p = cfg.port; usernames = true; "http-only" = true; "no-crt" = true; "no-thumb" = true; "no-snap" = true; "no-rescan" = true; "ses-db" = "${cfg.stateDir}/sessions.db"; "chpw-db" = "${cfg.stateDir}/chpw.json"; "shr-db" = "${cfg.stateDir}/shares.db"; } // lib.optionalAttrs cfg.reverseProxy.enable { rproxy = 1; "xff-hdr" = "x-forwarded-for"; }; accounts.${cfg.accountName}.passwordFile = cfg.passwordFile; globalExtraConfig = lib.optionalString cfg.reverseProxy.enable ( lib.concatMapStringsSep "\n" (source: "xff-src: ${source}") cfg.reverseProxy.trustedSources ); volumes = lib.mapAttrs (_urlPath: volume: { inherit (volume) access flags path readOnly ; }) cfg.volumes; }; users.users.${cfg.user}.uid = config.registry."stable-uid".${config.networking.hostName}.copyparty; networking.firewall.allowedTCPPorts = [ cfg.port ]; }) ]; }