2021-12-05 21:56:22 +00:00
|
|
|
{ config, lib, options, pkgs, ... }:
|
2020-12-30 20:22:51 +00:00
|
|
|
|
|
|
|
with lib;
|
|
|
|
let
|
|
|
|
cfg = config.services.galene;
|
2021-12-05 21:56:22 +00:00
|
|
|
opt = options.services.galene;
|
2020-12-30 20:22:51 +00:00
|
|
|
defaultstateDir = "/var/lib/galene";
|
|
|
|
defaultrecordingsDir = "${cfg.stateDir}/recordings";
|
|
|
|
defaultgroupsDir = "${cfg.stateDir}/groups";
|
|
|
|
defaultdataDir = "${cfg.stateDir}/data";
|
|
|
|
in
|
|
|
|
{
|
|
|
|
options = {
|
|
|
|
services.galene = {
|
|
|
|
enable = mkEnableOption "Galene Service.";
|
|
|
|
|
|
|
|
stateDir = mkOption {
|
|
|
|
default = defaultstateDir;
|
|
|
|
type = types.str;
|
|
|
|
description = ''
|
|
|
|
The directory where Galene stores its internal state. If left as the default
|
|
|
|
value this directory will automatically be created before the Galene server
|
|
|
|
starts, otherwise the sysadmin is responsible for ensuring the directory
|
|
|
|
exists with appropriate ownership and permissions.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
user = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "galene";
|
|
|
|
description = "User account under which galene runs.";
|
|
|
|
};
|
|
|
|
|
|
|
|
group = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "galene";
|
|
|
|
description = "Group under which galene runs.";
|
|
|
|
};
|
|
|
|
|
|
|
|
insecure = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
description = ''
|
|
|
|
Whether Galene should listen in http or in https. If left as the default
|
|
|
|
value (false), Galene needs to be fed a private key and a certificate.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
certFile = mkOption {
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
|
|
|
example = "/path/to/your/cert.pem";
|
|
|
|
description = ''
|
|
|
|
Path to the server's certificate. The file is copied at runtime to
|
|
|
|
Galene's data directory where it needs to reside.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
keyFile = mkOption {
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
|
|
|
example = "/path/to/your/key.pem";
|
|
|
|
description = ''
|
|
|
|
Path to the server's private key. The file is copied at runtime to
|
|
|
|
Galene's data directory where it needs to reside.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
httpAddress = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "";
|
|
|
|
description = "HTTP listen address for galene.";
|
|
|
|
};
|
|
|
|
|
|
|
|
httpPort = mkOption {
|
|
|
|
type = types.port;
|
|
|
|
default = 8443;
|
|
|
|
description = "HTTP listen port.";
|
|
|
|
};
|
|
|
|
|
|
|
|
staticDir = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "${cfg.package.static}/static";
|
2021-10-03 16:06:03 +00:00
|
|
|
defaultText = literalExpression ''"''${package.static}/static"'';
|
2020-12-30 20:22:51 +00:00
|
|
|
example = "/var/lib/galene/static";
|
|
|
|
description = "Web server directory.";
|
|
|
|
};
|
|
|
|
|
|
|
|
recordingsDir = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = defaultrecordingsDir;
|
2021-12-05 21:56:22 +00:00
|
|
|
defaultText = literalExpression ''"''${config.${opt.stateDir}}/recordings"'';
|
2020-12-30 20:22:51 +00:00
|
|
|
example = "/var/lib/galene/recordings";
|
|
|
|
description = "Recordings directory.";
|
|
|
|
};
|
|
|
|
|
|
|
|
dataDir = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = defaultdataDir;
|
2021-12-05 21:56:22 +00:00
|
|
|
defaultText = literalExpression ''"''${config.${opt.stateDir}}/data"'';
|
2020-12-30 20:22:51 +00:00
|
|
|
example = "/var/lib/galene/data";
|
|
|
|
description = "Data directory.";
|
|
|
|
};
|
|
|
|
|
|
|
|
groupsDir = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = defaultgroupsDir;
|
2021-12-05 21:56:22 +00:00
|
|
|
defaultText = literalExpression ''"''${config.${opt.stateDir}}/groups"'';
|
2020-12-30 20:22:51 +00:00
|
|
|
example = "/var/lib/galene/groups";
|
|
|
|
description = "Web server directory.";
|
|
|
|
};
|
|
|
|
|
|
|
|
package = mkOption {
|
|
|
|
default = pkgs.galene;
|
2021-10-03 16:06:03 +00:00
|
|
|
defaultText = literalExpression "pkgs.galene";
|
2020-12-30 20:22:51 +00:00
|
|
|
type = types.package;
|
|
|
|
description = ''
|
|
|
|
Package for running Galene.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
config = mkIf cfg.enable {
|
|
|
|
assertions = [
|
|
|
|
{
|
|
|
|
assertion = cfg.insecure || (cfg.certFile != null && cfg.keyFile != null);
|
|
|
|
message = ''
|
|
|
|
Galene needs both certFile and keyFile defined for encryption, or
|
|
|
|
the insecure flag.
|
|
|
|
'';
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
systemd.services.galene = {
|
|
|
|
description = "galene";
|
|
|
|
after = [ "network.target" ];
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
|
|
|
|
preStart = ''
|
2021-02-19 20:26:08 +00:00
|
|
|
${optionalString (cfg.insecure != true) ''
|
|
|
|
install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.certFile} ${cfg.dataDir}/cert.pem
|
|
|
|
install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.keyFile} ${cfg.dataDir}/key.pem
|
|
|
|
''}
|
2020-12-30 20:22:51 +00:00
|
|
|
'';
|
|
|
|
|
|
|
|
serviceConfig = mkMerge [
|
|
|
|
{
|
|
|
|
Type = "simple";
|
|
|
|
User = cfg.user;
|
|
|
|
Group = cfg.group;
|
|
|
|
WorkingDirectory = cfg.stateDir;
|
|
|
|
ExecStart = ''${cfg.package}/bin/galene \
|
|
|
|
${optionalString (cfg.insecure) "-insecure"} \
|
|
|
|
-data ${cfg.dataDir} \
|
|
|
|
-groups ${cfg.groupsDir} \
|
|
|
|
-recordings ${cfg.recordingsDir} \
|
|
|
|
-static ${cfg.staticDir}'';
|
|
|
|
Restart = "always";
|
|
|
|
# Upstream Requirements
|
|
|
|
LimitNOFILE = 65536;
|
|
|
|
StateDirectory = [ ] ++
|
|
|
|
optional (cfg.stateDir == defaultstateDir) "galene" ++
|
|
|
|
optional (cfg.dataDir == defaultdataDir) "galene/data" ++
|
|
|
|
optional (cfg.groupsDir == defaultgroupsDir) "galene/groups" ++
|
|
|
|
optional (cfg.recordingsDir == defaultrecordingsDir) "galene/recordings";
|
2022-01-22 11:58:23 +00:00
|
|
|
|
|
|
|
# Hardening
|
|
|
|
CapabilityBoundingSet = [ "" ];
|
|
|
|
DeviceAllow = [ "" ];
|
|
|
|
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 = cfg.recordingsDir;
|
|
|
|
RemoveIPC = true;
|
|
|
|
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
|
|
|
RestrictNamespaces = true;
|
|
|
|
RestrictRealtime = true;
|
|
|
|
RestrictSUIDSGID = true;
|
|
|
|
SystemCallArchitectures = "native";
|
|
|
|
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
|
|
|
|
UMask = "0077";
|
2020-12-30 20:22:51 +00:00
|
|
|
}
|
|
|
|
];
|
|
|
|
};
|
|
|
|
|
|
|
|
users.users = mkIf (cfg.user == "galene")
|
|
|
|
{
|
|
|
|
galene = {
|
|
|
|
description = "galene Service";
|
|
|
|
group = cfg.group;
|
|
|
|
isSystemUser = true;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
users.groups = mkIf (cfg.group == "galene") {
|
|
|
|
galene = { };
|
|
|
|
};
|
|
|
|
};
|
|
|
|
meta.maintainers = with lib.maintainers; [ rgrunbla ];
|
|
|
|
}
|