1{ config, lib, options, pkgs, ... }:
2
3with lib;
4let
5 cfg = config.services.galene;
6 opt = options.services.galene;
7 defaultstateDir = "/var/lib/galene";
8 defaultrecordingsDir = "${cfg.stateDir}/recordings";
9 defaultgroupsDir = "${cfg.stateDir}/groups";
10 defaultdataDir = "${cfg.stateDir}/data";
11in
12{
13 options = {
14 services.galene = {
15 enable = mkEnableOption (lib.mdDoc "Galene Service");
16
17 stateDir = mkOption {
18 default = defaultstateDir;
19 type = types.str;
20 description = lib.mdDoc ''
21 The directory where Galene stores its internal state. If left as the default
22 value this directory will automatically be created before the Galene server
23 starts, otherwise the sysadmin is responsible for ensuring the directory
24 exists with appropriate ownership and permissions.
25 '';
26 };
27
28 user = mkOption {
29 type = types.str;
30 default = "galene";
31 description = lib.mdDoc "User account under which galene runs.";
32 };
33
34 group = mkOption {
35 type = types.str;
36 default = "galene";
37 description = lib.mdDoc "Group under which galene runs.";
38 };
39
40 insecure = mkOption {
41 type = types.bool;
42 default = false;
43 description = lib.mdDoc ''
44 Whether Galene should listen in http or in https. If left as the default
45 value (false), Galene needs to be fed a private key and a certificate.
46 '';
47 };
48
49 certFile = mkOption {
50 type = types.nullOr types.str;
51 default = null;
52 example = "/path/to/your/cert.pem";
53 description = lib.mdDoc ''
54 Path to the server's certificate. The file is copied at runtime to
55 Galene's data directory where it needs to reside.
56 '';
57 };
58
59 keyFile = mkOption {
60 type = types.nullOr types.str;
61 default = null;
62 example = "/path/to/your/key.pem";
63 description = lib.mdDoc ''
64 Path to the server's private key. The file is copied at runtime to
65 Galene's data directory where it needs to reside.
66 '';
67 };
68
69 httpAddress = mkOption {
70 type = types.str;
71 default = "";
72 description = lib.mdDoc "HTTP listen address for galene.";
73 };
74
75 httpPort = mkOption {
76 type = types.port;
77 default = 8443;
78 description = lib.mdDoc "HTTP listen port.";
79 };
80
81 staticDir = mkOption {
82 type = types.str;
83 default = "${cfg.package.static}/static";
84 defaultText = literalExpression ''"''${package.static}/static"'';
85 example = "/var/lib/galene/static";
86 description = lib.mdDoc "Web server directory.";
87 };
88
89 recordingsDir = mkOption {
90 type = types.str;
91 default = defaultrecordingsDir;
92 defaultText = literalExpression ''"''${config.${opt.stateDir}}/recordings"'';
93 example = "/var/lib/galene/recordings";
94 description = lib.mdDoc "Recordings directory.";
95 };
96
97 dataDir = mkOption {
98 type = types.str;
99 default = defaultdataDir;
100 defaultText = literalExpression ''"''${config.${opt.stateDir}}/data"'';
101 example = "/var/lib/galene/data";
102 description = lib.mdDoc "Data directory.";
103 };
104
105 groupsDir = mkOption {
106 type = types.str;
107 default = defaultgroupsDir;
108 defaultText = literalExpression ''"''${config.${opt.stateDir}}/groups"'';
109 example = "/var/lib/galene/groups";
110 description = lib.mdDoc "Web server directory.";
111 };
112
113 package = mkOption {
114 default = pkgs.galene;
115 defaultText = literalExpression "pkgs.galene";
116 type = types.package;
117 description = lib.mdDoc ''
118 Package for running Galene.
119 '';
120 };
121 };
122 };
123
124 config = mkIf cfg.enable {
125 assertions = [
126 {
127 assertion = cfg.insecure || (cfg.certFile != null && cfg.keyFile != null);
128 message = ''
129 Galene needs both certFile and keyFile defined for encryption, or
130 the insecure flag.
131 '';
132 }
133 ];
134
135 systemd.services.galene = {
136 description = "galene";
137 after = [ "network.target" ];
138 wantedBy = [ "multi-user.target" ];
139
140 preStart = ''
141 ${optionalString (cfg.insecure != true) ''
142 install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.certFile} ${cfg.dataDir}/cert.pem
143 install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.keyFile} ${cfg.dataDir}/key.pem
144 ''}
145 '';
146
147 serviceConfig = mkMerge [
148 {
149 Type = "simple";
150 User = cfg.user;
151 Group = cfg.group;
152 WorkingDirectory = cfg.stateDir;
153 ExecStart = ''${cfg.package}/bin/galene \
154 ${optionalString (cfg.insecure) "-insecure"} \
155 -data ${cfg.dataDir} \
156 -groups ${cfg.groupsDir} \
157 -recordings ${cfg.recordingsDir} \
158 -static ${cfg.staticDir}'';
159 Restart = "always";
160 # Upstream Requirements
161 LimitNOFILE = 65536;
162 StateDirectory = [ ] ++
163 optional (cfg.stateDir == defaultstateDir) "galene" ++
164 optional (cfg.dataDir == defaultdataDir) "galene/data" ++
165 optional (cfg.groupsDir == defaultgroupsDir) "galene/groups" ++
166 optional (cfg.recordingsDir == defaultrecordingsDir) "galene/recordings";
167
168 # Hardening
169 CapabilityBoundingSet = [ "" ];
170 DeviceAllow = [ "" ];
171 LockPersonality = true;
172 MemoryDenyWriteExecute = true;
173 NoNewPrivileges = true;
174 PrivateDevices = true;
175 PrivateTmp = true;
176 PrivateUsers = true;
177 ProcSubset = "pid";
178 ProtectClock = true;
179 ProtectControlGroups = true;
180 ProtectHome = true;
181 ProtectHostname = true;
182 ProtectKernelLogs = true;
183 ProtectKernelModules = true;
184 ProtectKernelTunables = true;
185 ProtectProc = "invisible";
186 ProtectSystem = "strict";
187 ReadWritePaths = cfg.recordingsDir;
188 RemoveIPC = true;
189 RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
190 RestrictNamespaces = true;
191 RestrictRealtime = true;
192 RestrictSUIDSGID = true;
193 SystemCallArchitectures = "native";
194 SystemCallFilter = [ "@system-service" "~@privileged" ];
195 UMask = "0077";
196 }
197 ];
198 };
199
200 users.users = mkIf (cfg.user == "galene")
201 {
202 galene = {
203 description = "galene Service";
204 group = cfg.group;
205 isSystemUser = true;
206 };
207 };
208
209 users.groups = mkIf (cfg.group == "galene") {
210 galene = { };
211 };
212 };
213 meta.maintainers = with lib.maintainers; [ rgrunbla ];
214}