Skip to content
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/xandikos: use systemd socket activation #253634

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 72 additions & 15 deletions nixos/modules/services/networking/xandikos.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,36 @@ with lib;

let
cfg = config.services.xandikos;

nginxProxyAddress =
let
first = head (toList cfg.address);
in
if hasInfix "/" first then
"unix:${first}"
else
first
;
in
{

imports = [
(mkRemovedOptionModule [ "services" "xandikos" "port" ] "Use services.xandikos.address")
];

options = {
services.xandikos = {
enable = mkEnableOption "Xandikos CalDAV and CardDAV server";

package = mkPackageOption pkgs "xandikos" { };

address = mkOption {
type = types.str;
default = "localhost";
description = ''
The IP address on which Xandikos will listen.
By default listens on localhost.
systemd ListenStream where Xandikos shall listen, see {manpage}`systemd.socket(5)`
'';
};

port = mkOption {
type = types.port;
default = 8080;
description = "The port of the Xandikos web application";
type = with types; oneOf [ str (listOf str) ];
default = [ "127.0.0.1:8080" "[::1]:8080" ];
example = [ "0.0.0.0:8080" "[::]:8080" "/run/xandikos/socket" ];
};

routePrefix = mkOption {
Expand Down Expand Up @@ -78,6 +86,19 @@ in
};
};

metrics = {
enable = mkEnableOption "Xandikos' metrics";

address = mkOption {
description = ''
systemd ListenStream where the Xandikos metrics shall listen, see {manpage}`systemd.socket(5)`
'';
type = with types; oneOf [ str (listOf str) ];
default = [ "127.0.0.1:8081" "[::1]:8081" ];
example = [ "0.0.0.0:8081" "[::]:8081" "/run/xandikos/metrics.socket" ];
};
};

};

};
Expand All @@ -87,10 +108,15 @@ in
{
meta.maintainers = with lib.maintainers; [ _0x4A6F ];

systemd.sockets.xandikos = {
wantedBy = [ "sockets.target" ];
socketConfig.ListenStream = cfg.address;
};

systemd.services.xandikos = {
description = "A Simple Calendar and Contact Server";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
requires = [ "xandikos.socket" ];
after = [ "xandikos.socket" ];

serviceConfig = {
User = "xandikos";
Expand All @@ -100,6 +126,19 @@ in
StateDirectory = "xandikos";
StateDirectoryMode = "0700";
PrivateDevices = true;
# Systemd socket activation was broken time and again and bugs with
# default listening addresses introduced. See
# * https://github.com/jelmer/xandikos/issues/134
# * https://github.com/jelmer/xandikos/pull/133
# * https://github.com/jelmer/xandikos/pull/136
# * https://github.com/jelmer/xandikos/pull/155
# * https://github.com/jelmer/xandikos/issues/260
# * https://github.com/jelmer/xandikos/pull/262
# Additionally --metrics-port reuses --listen-address, which
# defaults to 0.0.0.0/::. To avoid all this behaviour having
# unwanted side-effects we run it in a private network namespace.
# The metrics are made accesible through `xandikos-metrics.socket`.
PrivateNetwork = true;
# Sandboxing
CapabilityBoundingSet = "CAP_NET_RAW CAP_NET_ADMIN";
ProtectSystem = "strict";
Expand All @@ -117,22 +156,40 @@ in
ExecStart = ''
${cfg.package}/bin/xandikos \
--directory /var/lib/xandikos \
--listen-address ${cfg.address} \
--port ${toString cfg.port} \
--route-prefix ${cfg.routePrefix} \
${lib.concatStringsSep " " cfg.extraOptions}
'';
};
};
}

(mkIf cfg.metrics.enable {
systemd.sockets.xandikos-metrics = {
wantedBy = [ "sockets.target" ];
socketConfig.ListenStream = cfg.metrics.address;
};

systemd.services.xandikos-metrics = {
description = "A proxy to Xandikos' metrics";
# see https://www.freedesktop.org/software/systemd/man/systemd-socket-proxyd.html#Namespace%20Example
requires = [ "xandikos.service" "xandikos-metrics.socket" ];
after = [ "xandikos.service" "xandikos-metrics.socket" ];
unitConfig.JoinsNamespaceOf = "xandikos.service";
serviceConfig = {
Type = "notify";
ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd localhost:8081";
PrivateNetwork = true; # required by JoinsNamespaceOf
};
};
})

(
mkIf cfg.nginx.enable {
services.nginx = {
enable = true;
virtualHosts."${cfg.nginx.hostName}" = {
locations."/" = {
proxyPass = "http://${cfg.address}:${toString cfg.port}/";
proxyPass = "http://${nginxProxyAddress}";
};
};
};
Expand Down
35 changes: 19 additions & 16 deletions nixos/tests/xandikos.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,30 @@ import ./make-test-python.nix (
nodes = {
xandikos_client = {};
xandikos_default = {
networking.firewall.allowedTCPPorts = [ 8080 ];
services.xandikos.enable = true;
networking.firewall.allowedTCPPorts = [ 8080 8081 ];
services.xandikos = {
enable = true;
metrics.enable = true;
};
};
xandikos_proxy = {
networking.firewall.allowedTCPPorts = [ 80 8080 ];
services.xandikos.enable = true;
services.xandikos.address = "localhost";
services.xandikos.port = 8080;
services.xandikos.routePrefix = "/xandikos-prefix/";
services.xandikos.extraOptions = [
"--defaults"
];
services.xandikos = {
enable = true;
address = [ "127.0.0.1:8080" "[::1]:8080" "/run/xandikos/socket" ];
routePrefix = "/xandikos-prefix/";
extraOptions = [
"--defaults"
];
};
services.nginx = {
enable = true;
recommendedProxySettings = true;
virtualHosts."xandikos" = {
serverName = "xandikos.local";
basicAuth.xandikos = "snakeOilPassword";
locations."/xandikos/" = {
proxyPass = "http://localhost:8080/xandikos-prefix/";
proxyPass = "http://unix:/run/xandikos/socket:/xandikos-prefix/";
};
};
};
Expand All @@ -39,24 +43,23 @@ import ./make-test-python.nix (
start_all()

with subtest("Xandikos default"):
xandikos_default.wait_for_unit("multi-user.target")
xandikos_default.wait_for_unit("xandikos.service")
xandikos_default.wait_for_open_port(8080)
xandikos_default.wait_for_unit("sockets.target")
xandikos_default.succeed("curl --fail http://localhost:8080/")
xandikos_default.succeed(
"curl -s --fail --location http://localhost:8080/ | grep -i Xandikos"
)
xandikos_default.succeed("curl -s --fail --location http://localhost:8081/metrics")
xandikos_client.wait_for_unit("network.target")
xandikos_client.fail("curl --fail http://xandikos_default:8080/")
xandikos_client.fail("curl --fail http://xandikos_default:8081/metrics")

with subtest("Xandikos proxy"):
xandikos_proxy.wait_for_unit("multi-user.target")
xandikos_proxy.wait_for_unit("xandikos.service")
xandikos_proxy.wait_for_open_port(8080)
xandikos_proxy.wait_for_unit("sockets.target")
xandikos_proxy.succeed("curl --fail http://localhost:8080/")
xandikos_proxy.succeed(
"curl -s --fail --location http://localhost:8080/ | grep -i Xandikos"
)
xandikos_client.fail("curl --fail http://xandikos_default:8081/metrics")
xandikos_client.wait_for_unit("network.target")
xandikos_client.fail("curl --fail http://xandikos_proxy:8080/")
xandikos_client.succeed(
Expand Down
1 change: 1 addition & 0 deletions pkgs/servers/xandikos/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ python3Packages.buildPythonApplication rec {
icalendar
jinja2
multidict
systemd
vobject
];

Expand Down