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