{ config, lib, pkgs, ... }: let cfg = config.services.radicale; domain = config.networking.domain; subdomain = cfg.reverseProxy.subdomain; fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain; port = 5232; inherit (lib) concatLines mkDefault mkIf mkOption types ; inherit (lib.utils) mkReverseProxyOption ; in { options.services.radicale = { users = mkOption { type = types.listOf types.str; default = [ ]; example = [ "alice" "bob" ]; description = "List of users for Radicale. Each user must have a corresponding entry in the SOPS file under 'radicale/'"; }; reverseProxy = mkReverseProxyOption "Radicale" "dav"; }; config = mkIf cfg.enable { services.radicale = { settings = { server = { hosts = [ "${if cfg.reverseProxy.enable then "127.0.0.1" else "0.0.0.0"}:${builtins.toString port}" ]; max_connections = mkDefault 20; max_content_length = mkDefault 500000000; timeout = mkDefault 30; }; auth = { type = "htpasswd"; delay = mkDefault 1; htpasswd_filename = config.sops.templates."radicale/users".path; htpasswd_encryption = mkDefault "sha512"; }; storage = { filesystem_folder = mkDefault "/var/lib/radicale/collections"; }; }; }; services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable { "${fqdn}" = { forceSSL = cfg.reverseProxy.forceSSL; enableACME = cfg.reverseProxy.forceSSL; locations = { "/" = { proxyPass = "http://localhost:${builtins.toString port}/"; extraConfig = '' proxy_set_header X-Script-Name /; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass_header Authorization; ''; }; "/.well-known/caldav" = { return = "301 $scheme://$host/"; }; "/.well-known/carddav" = { return = "301 $scheme://$host/"; }; }; }; }; environment.systemPackages = [ pkgs.openssl ]; sops = let owner = "radicale"; group = "radicale"; mode = "0440"; mkSecrets = users: builtins.listToAttrs ( map (user: { name = "radicale/${user}"; value = { inherit owner group mode; }; }) users ); mkTemplate = users: { inherit owner group mode; content = concatLines (map (user: "${user}:${config.sops.placeholder."radicale/${user}"}") users); }; in { secrets = mkSecrets cfg.users; templates."radicale/users" = mkTemplate cfg.users; }; }; }