{ config, lib, ... }: let cfg = config.guests.copyparty; # NOLINT(let_order) stateDir = "/var/lib/copyparty"; in { options.guests.copyparty = { passwordSecret = lib.mkOption { type = lib.types.str; default = "services/copyparty/users/${config.user.name}/password"; description = "SOPS secret name that contains the Copyparty account password."; }; statePath = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "/srv/copyparty"; description = "Host path bind-mounted as Copyparty's state directory."; }; reverseProxy.trustedSources = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "IP ranges that may supply Copyparty's forwarded client IP header."; }; volumes = lib.mkOption { 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."; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = cfg.statePath != null; message = "guests.copyparty.statePath must point at the host directory that stores Copyparty state."; } { assertion = cfg.volumes != { }; message = "guests.copyparty.volumes must declare the host's served Copyparty volumes."; } ]; registry.homepage.${config.networking.hostName}.copyparty = { tab = "Apps"; section = "Files"; name = "Copyparty"; description = "Files on ${config.networking.hostName}"; icon = "copyparty.svg"; href = lib.mkIf cfg.reverseProxy.enable "https://${cfg.reverseProxy.route.domain}/?h"; }; guests.copyparty = { interior.modules.services.copyparty = { enable = true; passwordFile = "/run/secrets/${cfg.passwordSecret}"; reverseProxy = { enable = cfg.reverseProxy.enable; inherit (cfg.reverseProxy) trustedSources; }; 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 = { ${stateDir} = { directory = cfg.statePath; owner = config.registry."stable-uid".${config.networking.hostName}.copyparty; group = "storage"; mode = "0770"; }; ${"/run/secrets/${cfg.passwordSecret}"} = { file = config.sops.secrets.${cfg.passwordSecret}.path; readOnly = true; }; } // lib.mapAttrs' ( _urlPath: volume: lib.nameValuePair volume.path { directory = volume.hostPath; owner = "root"; group = "storage"; mode = "2770"; } ) cfg.volumes; limits = { memory = lib.mkDefault "2G"; cpu = lib.mkDefault "200%"; tasksMax = lib.mkDefault 512; }; }; sops.secrets.${cfg.passwordSecret} = { owner = "root"; group = "storage"; mode = "0440"; }; }; }