summary refs log blame commit diff
path: root/nixos/modules/services/networking/radicale.nix
blob: 8c632c319d3c08940df0fca88988a59fb7247783 (plain) (tree)
1
2
3
4
5
6
7
8
9
                           



         

                                 

                                                                                 
    
 











                                                            

                                                                                          











                                                                              
 


                       
                      



                                                                          


         


                         
                      















                                                                                        


         

                         
                      






















                                                                                                    
         
      
 
                          
                                    


                                                                     


                            































                                                                                                  
 
                                                        
 
                                                         
 
                                 
                                                           
                                   
                                      
                                         
                       
                                           
                                             


                                          

                           


































                                                                              
        
      
    
 
                                                                             
 
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.radicale;

  format = pkgs.formats.ini {
    listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault { });
  };

  pkg = if isNull cfg.package then
    pkgs.radicale
  else
    cfg.package;

  confFile = if cfg.settings == { } then
    pkgs.writeText "radicale.conf" cfg.config
  else
    format.generate "radicale.conf" cfg.settings;

  rightsFile = format.generate "radicale.rights" cfg.rights;

  bindLocalhost = cfg.settings != { } && !hasAttrByPath [ "server" "hosts" ] cfg.settings;

in {
  options.services.radicale = {
    enable = mkEnableOption "Radicale CalDAV and CardDAV server";

    package = mkOption {
      description = "Radicale package to use.";
      # Default cannot be pkgs.radicale because non-null values suppress
      # warnings about incompatible configuration and storage formats.
      type = with types; nullOr package // { inherit (package) description; };
      default = null;
      defaultText = "pkgs.radicale";
    };

    config = mkOption {
      type = types.str;
      default = "";
      description = ''
        Radicale configuration, this will set the service
        configuration file.
        This option is mutually exclusive with <option>settings</option>.
        This option is deprecated.  Use <option>settings</option> instead.
      '';
    };

    settings = mkOption {
      type = format.type;
      default = { };
      description = ''
        Configuration for Radicale. See
        <link xlink:href="https://radicale.org/3.0.html#documentation/configuration" />.
        This option is mutually exclusive with <option>config</option>.
      '';
      example = literalExample ''
        server = {
          hosts = [ "0.0.0.0:5232" "[::]:5232" ];
        };
        auth = {
          type = "htpasswd";
          htpasswd_filename = "/etc/radicale/users";
          htpasswd_encryption = "bcrypt";
        };
        storage = {
          filesystem_folder = "/var/lib/radicale/collections";
        };
      '';
    };

    rights = mkOption {
      type = format.type;
      description = ''
        Configuration for Radicale's rights file. See
        <link xlink:href="https://radicale.org/3.0.html#documentation/authentication-and-rights" />.
        This option only works in conjunction with <option>settings</option>.
        Setting this will also set <option>settings.rights.type</option> and
        <option>settings.rights.file</option> to approriate values.
      '';
      default = { };
      example = literalExample ''
        root = {
          user = ".+";
          collection = "";
          permissions = "R";
        };
        principal = {
          user = ".+";
          collection = "{user}";
          permissions = "RW";
        };
        calendars = {
          user = ".+";
          collection = "{user}/[^/]+";
          permissions = "rw";
        };
      '';
    };

    extraArgs = mkOption {
      type = types.listOf types.str;
      default = [];
      description = "Extra arguments passed to the Radicale daemon.";
    };
  };

  config = mkIf cfg.enable {
    assertions = [
      {
        assertion = cfg.settings == { } || cfg.config == "";
        message = ''
          The options services.radicale.config and services.radicale.settings
          are mutually exclusive.
        '';
      }
    ];

    warnings = optional (isNull cfg.package && versionOlder config.system.stateVersion "17.09") ''
      The configuration and storage formats of your existing Radicale
      installation might be incompatible with the newest version.
      For upgrade instructions see
      https://radicale.org/2.1.html#documentation/migration-from-1xx-to-2xx.
      Set services.radicale.package to suppress this warning.
    '' ++ optional (isNull cfg.package && versionOlder config.system.stateVersion "20.09") ''
      The configuration format of your existing Radicale installation might be
      incompatible with the newest version.  For upgrade instructions see
      https://github.com/Kozea/Radicale/blob/3.0.6/NEWS.md#upgrade-checklist.
      Set services.radicale.package to suppress this warning.
    '' ++ optional (cfg.config != "") ''
      The option services.radicale.config is deprecated.
      Use services.radicale.settings instead.
    '';

    services.radicale.settings.rights = mkIf (cfg.rights != { }) {
      type = "from_file";
      file = toString rightsFile;
    };

    environment.systemPackages = [ pkg ];

    users.users.radicale.uid = config.ids.uids.radicale;

    users.groups.radicale.gid = config.ids.gids.radicale;

    systemd.services.radicale = {
      description = "A Simple Calendar and Contact Server";
      after = [ "network.target" ];
      requires = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        ExecStart = concatStringsSep " " ([
          "${pkg}/bin/radicale" "-C" confFile
        ] ++ (
          map escapeShellArg cfg.extraArgs
        ));
        User = "radicale";
        Group = "radicale";
        StateDirectory = "radicale/collections";
        StateDirectoryMode = "0750";
        # Hardening
        CapabilityBoundingSet = [ "" ];
        DeviceAllow = [ "/dev/stdin" ];
        DevicePolicy = "strict";
        IPAddressAllow = mkIf bindLocalhost "localhost";
        IPAddressDeny = mkIf bindLocalhost "any";
        LockPersonality = true;
        MemoryDenyWriteExecute = true;
        NoNewPrivileges = true;
        PrivateDevices = true;
        PrivateTmp = true;
        PrivateUsers = true;
        ProcSubset = "pid";
        ProtectClock = true;
        ProtectControlGroups = true;
        ProtectHome = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectProc = "invisible";
        ProtectSystem = "strict";
        ReadWritePaths = lib.optional
          (hasAttrByPath [ "storage" "filesystem_folder" ] cfg.settings)
          cfg.settings.storage.filesystem_folder;
        RemoveIPC = true;
        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
        SystemCallArchitectures = "native";
        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
        UMask = "0027";
      };
    };
  };

  meta.maintainers = with lib.maintainers; [ aneeshusa infinisil dotlambda ];
}