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 systemd.services.galene = {
132 description = "galene";
133 after = [ "network.target" ];
134 wantedBy = [ "multi-user.target" ];
135
136 preStart = ''
137 ${optionalString (cfg.insecure != true && cfg.certFile != null && cfg.keyFile != null) ''
138 install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.certFile} ${cfg.dataDir}/cert.pem
139 install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.keyFile} ${cfg.dataDir}/key.pem
140 ''}
141 '';
142
143 serviceConfig = mkMerge [
144 {
145 Type = "simple";
146 User = cfg.user;
147 Group = cfg.group;
148 WorkingDirectory = cfg.stateDir;
149 ExecStart = ''
150 ${cfg.package}/bin/galene \
151 ${optionalString (cfg.insecure) "-insecure"} \
152 -http ${cfg.httpAddress}:${toString cfg.httpPort} \
153 -turn ${cfg.turnAddress} \
154 -data ${cfg.dataDir} \
155 -groups ${cfg.groupsDir} \
156 -recordings ${cfg.recordingsDir} \
157 -static ${cfg.staticDir}'';
158 Restart = "always";
159 # Upstream Requirements
160 LimitNOFILE = 65536;
161 StateDirectory =
162 [ ]
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 = [
190 "AF_INET"
191 "AF_INET6"
192 "AF_NETLINK"
193 ];
194 RestrictNamespaces = true;
195 RestrictRealtime = true;
196 RestrictSUIDSGID = true;
197 SystemCallArchitectures = "native";
198 SystemCallFilter = [
199 "@system-service"
200 "~@privileged"
201 ];
202 UMask = "0077";
203 }
204 ];
205 };
206
207 users.users = mkIf (cfg.user == "galene") {
208 galene = {
209 description = "galene Service";
210 group = cfg.group;
211 isSystemUser = true;
212 };
213 };
214
215 users.groups = mkIf (cfg.group == "galene") {
216 galene = { };
217 };
218 };
219 meta.maintainers = with lib.maintainers; [ rgrunbla ];
220}