modules/socketActivation: misc improvement

This commit is contained in:
Oystein Kristoffer Tveit 2024-06-10 00:59:41 +02:00
parent 0ea7d0961d
commit a2cd4d6ad1
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
1 changed files with 77 additions and 31 deletions

View File

@ -1,19 +1,18 @@
{ config, pkgs, lib, ... }: { options, config, pkgs, lib, ... }:
let let
inherit (lib) mkOption types mdDoc; inherit (lib) mkOption types mdDoc;
cfg = lib.filterAttrs (_: value: value.enable) config.local.socketActivation;
in in
{ {
options.local.socketActivation = mkOption { options.socketActivation = mkOption {
type = types.attrsOf (types.submodule ({ name, ... }: { type = types.attrsOf (types.submodule ({ name, ... }: {
options = { options = {
enable = lib.mkEnableOption "socket activation for <name>"; enable = lib.mkEnableOption "socket activation for a systemd service";
service = mkOption { service = mkOption {
type = types.str; type = types.str;
default = name; default = name;
defaultText = "<name>"; defaultText = "<name>";
example = "myservice"; example = "gitea";
description = mdDoc "Systemd service name"; description = mdDoc "Systemd service name";
}; };
@ -23,7 +22,6 @@ in
example = false; example = false;
description = mdDoc '' description = mdDoc ''
Whether to isolate the network of the original service. Whether to isolate the network of the original service.
This is recommended, but might be impractical if the original This is recommended, but might be impractical if the original
service also uses networking for its own operation. service also uses networking for its own operation.
''; '';
@ -34,29 +32,28 @@ in
example = "localhost:8080"; example = "localhost:8080";
description = mdDoc '' description = mdDoc ''
Socket that the original service is listening to. Socket that the original service is listening to.
This could be an INET/6 or UNIX socket.
This could be a TCP or UNIX socket.
''; '';
}; };
newSocketAddress = mkOption { newSocketAddress = mkOption {
type = with types; either str port; type = with types; either str port;
default = "/run/${name}.socket";
defaultText = "/run/<name>.socket";
example = "localhost:8080"; example = "localhost:8080";
description = mdDoc '' description = mdDoc ''
Addres of the new systemd socket. Address of the new systemd socket.
This could be an INET/6 or UNIX socket.
This could be a TCP or UNIX socket.
''; '';
}; };
connectionsMax = mkOption { connectionsMax = mkOption {
type = types.int; type = types.ints.unsigned;
default = 256; default = 256;
example = 1024; example = 1024;
description = mdDoc '' description = mdDoc ''
Sets the maximum number of simultaneous connections. Sets the maximum number of simultaneous connections.
If the limit of concurrent connections is reached further connections will be refused. If the limit of concurrent connections is reached further connections will be refused.
See <https://www.freedesktop.org/software/systemd/man/systemd-socket-proxyd.html#--connections-max=> See <https://www.freedesktop.org/software/systemd/man/systemd-socket-proxyd.html#--connections-max=>
''; '';
}; };
@ -68,38 +65,87 @@ in
description = mdDoc '' description = mdDoc ''
Amount of inactivity time, before systemd shuts down the service. Amount of inactivity time, before systemd shuts down the service.
If this is set to `null`, the service will never stop. If this is set to `null`, the service will never stop.
See <https://www.freedesktop.org/software/systemd/man/systemd-socket-proxyd.html#--exit-idle-time=> See <https://www.freedesktop.org/software/systemd/man/systemd-socket-proxyd.html#--exit-idle-time=>
''; '';
}; };
openFirewall = mkOption {
type = types.bool;
default = false;
example = true;
description = mdDoc ''
Whether to open the firewall for {var}`newSocketAddress`
'';
};
createNginxUpstream = mkOption {
type = types.bool;
default = true;
example = false;
description = mdDoc ''
Whether to create an http upstream entry for {var}`newSocketAddress` in nginx.
See <https://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream>
'';
};
extraSocketConfig = mkOption {
type = types.attrs; # TODO: not exactly sure how to type this,
# maybe reexposing this through the module is a bad idea?
default = { };
example = {
KeepAlive = true;
MaxConnectionsPerSource = 3;
Priority = 2;
};
description = mdDoc ''
Extra configuration to go into the [socket] section of the systemd unit.
See <https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html>
'';
};
}; };
})); }));
description = mdDoc "Forcefully socket activated systemd services"; description = mdDoc "Socket activated services using {command}`systemd-socket-proxyd`.";
default = { }; default = { };
}; };
config = { config = let
assertions = lib.mapAttrsToList (name: value: { activeServices = lib.filterAttrs (_: value: value.enable) config.socketActivation;
# NOTE: This assertion is missing a lot of invalid cases. foreachService = f: lib.mapAttrsToList f activeServices;
# The original socket address could've been "localhost:1234" and now only 1234, in {
assertions = foreachService (name: value: {
# NOTE: This assertion is just covering for the most basic invalid usecases, but misses a lot.
# The original socket address could've been "localhost:1234" and now only "1234",
# while still meaning the same thing. # while still meaning the same thing.
# Also, if the originalSocketAddress and newSocketAddress is the same UNIX socket path assertion = lib.any (b: b) [
# it doesn't matter whether they're in different namespaces AFAIK, they'll still clash. value.privateNamespace
assertion = !value.privateNamespace -> (value.originalSocketAddress != value.newSocketAddress); # If this is a UNIX socket, the paths themself
# would clash even if in a private namespace
(lib.hasPrefix "/" value.originalSocketAddress)
(lib.hasPrefix "@" value.originalSocketAddress)
] -> (value.originalSocketAddress != value.newSocketAddress);
message = '' message = ''
The new proxied socket address of "${name}" clashes with its original socket address. The new proxied socket of "${name}" has a new socket address of "${toString value.newSocketAddress}",
Either enable `privateNamespace` to isolate the original service' network, or use a separate which clashes with its original address of "${toString value.originalSocketAddress}".
socket address. Either enable privateNamespace to isolate the original service' network, or use a separate socket address.
''; '';
}) cfg; });
systemd = lib.mkMerge ((lib.flip lib.mapAttrsToList) cfg (name: value: let services.nginx.upstreams = let
servicesWithNginxUpstream = lib.filterAttrs (_: value: value.createNginxUpstream) activeServices;
in lib.mapAttrsToList (name: value: let
protocol = if lib.any (p: lib.hasPrefix p value.newSocketAddress) [ "/" "@" ] then "unix" else "http";
in {
${name}.servers."${protocol}:${value.newSocketAddress}" = { };
}) servicesWithNginxUpstream;
systemd = lib.mkMerge (foreachService (name: value: let
originalService = config.systemd.services.${value.service}; originalService = config.systemd.services.${value.service};
in { in {
sockets."${name}-proxy" = { sockets."${name}-proxy" = {
wantedBy = [ "sockets.target" ]; wantedBy = [ "sockets.target" ];
socketConfig = { socketConfig = {
ListenStream = value.newSocketAddress; ListenStream = value.newSocketAddress;
MaxConnections = value.connectionsMax;
}; };
}; };
@ -110,9 +156,7 @@ in
]; ];
after = requires; after = requires;
unitConfig = lib.mkIf value.privateNamespace { unitConfig.JoinsNamespaceOf = lib.mkIf value.privateNamespace "${value.service}.service";
JoinsNamespaceOf = "${value.service}.service";
};
serviceConfig = { serviceConfig = {
ExecStart = let ExecStart = let
@ -120,7 +164,7 @@ in
exit-idle-time = if value.exitIdleTime != null then value.exitIdleTime else "infinity"; exit-idle-time = if value.exitIdleTime != null then value.exitIdleTime else "infinity";
connections-max = value.connectionsMax; connections-max = value.connectionsMax;
}; };
in ''${pkgs.systemd}/lib/systemd/systemd-socket-proxyd ${args} "${cfg.${name}.originalSocketAddress}"''; in ''${pkgs.systemd}/lib/systemd/systemd-socket-proxyd ${args} "${value.originalSocketAddress}"'';
CapabilityBoundingSet = ""; CapabilityBoundingSet = "";
LockPersonality = true; LockPersonality = true;
@ -171,4 +215,6 @@ in
}; };
})); }));
}; };
meta.maintainers = with lib.maintainers; [ h7x4 ];
} }