-
-
Notifications
You must be signed in to change notification settings - Fork 14.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nixos/radicale: add settings option #120440
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,90 +3,198 @@ | |
with lib; | ||
|
||
let | ||
|
||
cfg = config.services.radicale; | ||
|
||
confFile = pkgs.writeText "radicale.conf" cfg.config; | ||
|
||
defaultPackage = if versionAtLeast config.system.stateVersion "20.09" then { | ||
pkg = pkgs.radicale3; | ||
text = "pkgs.radicale3"; | ||
} else if versionAtLeast config.system.stateVersion "17.09" then { | ||
pkg = pkgs.radicale2; | ||
text = "pkgs.radicale2"; | ||
} else { | ||
pkg = pkgs.radicale1; | ||
text = "pkgs.radicale1"; | ||
format = pkgs.formats.ini { | ||
listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault { }); | ||
}; | ||
in | ||
|
||
{ | ||
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; | ||
|
||
options = { | ||
services.radicale.enable = mkOption { | ||
type = types.bool; | ||
default = false; | ||
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 = '' | ||
Enable Radicale CalDAV and CardDAV server. | ||
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. | ||
''; | ||
}; | ||
|
||
services.radicale.package = mkOption { | ||
type = types.package; | ||
default = defaultPackage.pkg; | ||
defaultText = defaultPackage.text; | ||
settings = mkOption { | ||
type = format.type; | ||
default = { }; | ||
description = '' | ||
Radicale package to use. This defaults to version 1.x if | ||
<literal>system.stateVersion < 17.09</literal>, version 2.x if | ||
<literal>17.09 ≤ system.stateVersion < 20.09</literal>, and | ||
version 3.x otherwise. | ||
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"; | ||
}; | ||
''; | ||
}; | ||
|
||
services.radicale.config = mkOption { | ||
type = types.str; | ||
default = ""; | ||
rights = mkOption { | ||
type = format.type; | ||
description = '' | ||
Radicale configuration, this will set the service | ||
configuration file. | ||
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"; | ||
}; | ||
''; | ||
}; | ||
|
||
services.radicale.extraArgs = mkOption { | ||
extraArgs = mkOption { | ||
type = types.listOf types.str; | ||
default = []; | ||
description = "Extra arguments passed to the Radicale daemon."; | ||
}; | ||
}; | ||
|
||
config = mkIf cfg.enable { | ||
environment.systemPackages = [ cfg.package ]; | ||
assertions = [ | ||
{ | ||
assertion = cfg.settings == { } || cfg.config == ""; | ||
message = '' | ||
The options services.radicale.config and services.radicale.settings | ||
are mutually exclusive. | ||
''; | ||
} | ||
]; | ||
|
||
users.users.radicale = | ||
{ uid = config.ids.uids.radicale; | ||
description = "radicale user"; | ||
home = "/var/lib/radicale"; | ||
createHome = true; | ||
}; | ||
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; }; | ||
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 " " ([ | ||
"${cfg.package}/bin/radicale" "-C" confFile | ||
"${pkg}/bin/radicale" "-C" confFile | ||
] ++ ( | ||
map escapeShellArg cfg.extraArgs | ||
)); | ||
User = "radicale"; | ||
Group = "radicale"; | ||
StateDirectory = "radicale/collections"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Link to upstream documentation with comment so we can track changes would be useful. Additionally, will these hardening options break older versions of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They will only break configurations where data is stored at a non-standard location. |
||
StateDirectoryMode = "0750"; | ||
# Hardening | ||
CapabilityBoundingSet = [ "" ]; | ||
dotlambda marked this conversation as resolved.
Show resolved
Hide resolved
|
||
DeviceAllow = [ "/dev/stdin" ]; | ||
DevicePolicy = "strict"; | ||
IPAddressAllow = mkIf bindLocalhost "localhost"; | ||
IPAddressDeny = mkIf bindLocalhost "any"; | ||
Comment on lines
+166
to
+167
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this problematic? One scenario I can think of is a hook accessing the (non-local) network. Does anyone do that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't feel comfortable putting a hostname in there, but maybe that's just the networker in me speaking. But making it contingent on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not actually a hostname, it's a special name interpreted as |
||
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 ]; | ||
meta.maintainers = with lib.maintainers; [ aneeshusa infinisil dotlambda ]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still needed? Presumably for the older versions of
radicale
? If so, can set add a comment about when you would use which option and how they are mutually exclusive. Maybe an assertion.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
settings
should also work with older versions, but I keptconfig
for backwards compatibility with existingconfiguration.nix
sThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with this is that third-party modules can't rely on
settings
. Every module that wants to read or writesettings
options has the chance to not work in case the user doesn't usesettings
. And it's even worse: A third-party module writing tosettings
makes thesettings == {}
check fail, meaning the usersconfig
option gets ignored! Because of this I think it might be better to actually remove this option.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not true: If both
config
andsettings
are set an assertion is triggerred.Nevertheless, I'm all in favor of removing
config
but it might make sense to first mark it as deprecated usinglib.warn
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed