grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / web-apps / mattermost.nix
blobfee0ec2d641d23cbb146ca568eacdcc429bff398
1 { config, pkgs, lib, ... }:
3 with lib;
5 let
7   cfg = config.services.mattermost;
9   database = "postgres://${cfg.localDatabaseUser}:${cfg.localDatabasePassword}@localhost:5432/${cfg.localDatabaseName}?sslmode=disable&connect_timeout=10";
11   postgresPackage = config.services.postgresql.package;
13   createDb = {
14     statePath ? cfg.statePath,
15     localDatabaseUser ? cfg.localDatabaseUser,
16     localDatabasePassword ? cfg.localDatabasePassword,
17     localDatabaseName ? cfg.localDatabaseName,
18     useSudo ? true
19   }: ''
20     if ! test -e ${escapeShellArg "${statePath}/.db-created"}; then
21       ${lib.optionalString useSudo "${pkgs.sudo}/bin/sudo -u ${escapeShellArg config.services.postgresql.superUser} \\"}
22         ${postgresPackage}/bin/psql postgres -c \
23           "CREATE ROLE ${localDatabaseUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${localDatabasePassword}'"
24       ${lib.optionalString useSudo "${pkgs.sudo}/bin/sudo -u ${escapeShellArg config.services.postgresql.superUser} \\"}
25         ${postgresPackage}/bin/createdb \
26           --owner ${escapeShellArg localDatabaseUser} ${escapeShellArg localDatabaseName}
27       touch ${escapeShellArg "${statePath}/.db-created"}
28     fi
29   '';
31   mattermostPluginDerivations = with pkgs;
32     map (plugin: stdenv.mkDerivation {
33       name = "mattermost-plugin";
34       installPhase = ''
35         mkdir -p $out/share
36         cp ${plugin} $out/share/plugin.tar.gz
37       '';
38       dontUnpack = true;
39       dontPatch = true;
40       dontConfigure = true;
41       dontBuild = true;
42       preferLocalBuild = true;
43     }) cfg.plugins;
45   mattermostPlugins = with pkgs;
46     if mattermostPluginDerivations == [] then null
47     else stdenv.mkDerivation {
48       name = "${cfg.package.name}-plugins";
49       nativeBuildInputs = [
50         autoPatchelfHook
51       ] ++ mattermostPluginDerivations;
52       buildInputs = [
53         cfg.package
54       ];
55       installPhase = ''
56         mkdir -p $out/data/plugins
57         plugins=(${escapeShellArgs (map (plugin: "${plugin}/share/plugin.tar.gz") mattermostPluginDerivations)})
58         for plugin in "''${plugins[@]}"; do
59           hash="$(sha256sum "$plugin" | cut -d' ' -f1)"
60           mkdir -p "$hash"
61           tar -C "$hash" -xzf "$plugin"
62           autoPatchelf "$hash"
63           GZIP_OPT=-9 tar -C "$hash" -cvzf "$out/data/plugins/$hash.tar.gz" .
64           rm -rf "$hash"
65         done
66       '';
68       dontUnpack = true;
69       dontPatch = true;
70       dontConfigure = true;
71       dontBuild = true;
72       preferLocalBuild = true;
73     };
75   mattermostConfWithoutPlugins = recursiveUpdate
76     { ServiceSettings.SiteURL = cfg.siteUrl;
77       ServiceSettings.ListenAddress = cfg.listenAddress;
78       TeamSettings.SiteName = cfg.siteName;
79       SqlSettings.DriverName = "postgres";
80       SqlSettings.DataSource = database;
81       PluginSettings.Directory = "${cfg.statePath}/plugins/server";
82       PluginSettings.ClientDirectory = "${cfg.statePath}/plugins/client";
83     }
84     cfg.extraConfig;
86   mattermostConf = recursiveUpdate
87     mattermostConfWithoutPlugins
88     (
89       lib.optionalAttrs (mattermostPlugins != null) {
90         PluginSettings = {
91           Enable = true;
92         };
93       }
94     );
96   mattermostConfJSON = pkgs.writeText "mattermost-config.json" (builtins.toJSON mattermostConf);
101   options = {
102     services.mattermost = {
103       enable = mkEnableOption "Mattermost chat server";
105       package = mkPackageOption pkgs "mattermost" { };
107       statePath = mkOption {
108         type = types.str;
109         default = "/var/lib/mattermost";
110         description = "Mattermost working directory";
111       };
113       siteUrl = mkOption {
114         type = types.str;
115         example = "https://chat.example.com";
116         description = ''
117           URL this Mattermost instance is reachable under, without trailing slash.
118         '';
119       };
121       siteName = mkOption {
122         type = types.str;
123         default = "Mattermost";
124         description = "Name of this Mattermost site.";
125       };
127       listenAddress = mkOption {
128         type = types.str;
129         default = ":8065";
130         example = "[::1]:8065";
131         description = ''
132           Address and port this Mattermost instance listens to.
133         '';
134       };
136       mutableConfig = mkOption {
137         type = types.bool;
138         default = false;
139         description = ''
140           Whether the Mattermost config.json is writeable by Mattermost.
142           Most of the settings can be edited in the system console of
143           Mattermost if this option is enabled. A template config using
144           the options specified in services.mattermost will be generated
145           but won't be overwritten on changes or rebuilds.
147           If this option is disabled, changes in the system console won't
148           be possible (default). If an config.json is present, it will be
149           overwritten!
150         '';
151       };
153       preferNixConfig = mkOption {
154         type = types.bool;
155         default = false;
156         description = ''
157           If both mutableConfig and this option are set, the Nix configuration
158           will take precedence over any settings configured in the server
159           console.
160         '';
161       };
163       extraConfig = mkOption {
164         type = types.attrs;
165         default = { };
166         description = ''
167           Additional configuration options as Nix attribute set in config.json schema.
168         '';
169       };
171       plugins = mkOption {
172         type = types.listOf (types.oneOf [types.path types.package]);
173         default = [];
174         example = "[ ./com.github.moussetc.mattermost.plugin.giphy-2.0.0.tar.gz ]";
175         description = ''
176           Plugins to add to the configuration. Overrides any installed if non-null.
177           This is a list of paths to .tar.gz files or derivations evaluating to
178           .tar.gz files.
179         '';
180       };
181       environmentFile = mkOption {
182         type = types.nullOr types.path;
183         default = null;
184         description = ''
185           Environment file (see {manpage}`systemd.exec(5)`
186           "EnvironmentFile=" section for the syntax) which sets config options
187           for mattermost (see [the mattermost documentation](https://docs.mattermost.com/configure/configuration-settings.html#environment-variables)).
189           Settings defined in the environment file will overwrite settings
190           set via nix or via the {option}`services.mattermost.extraConfig`
191           option.
193           Useful for setting config options without their value ending up in the
194           (world-readable) nix store, e.g. for a database password.
195         '';
196       };
198       localDatabaseCreate = mkOption {
199         type = types.bool;
200         default = true;
201         description = ''
202           Create a local PostgreSQL database for Mattermost automatically.
203         '';
204       };
206       localDatabaseName = mkOption {
207         type = types.str;
208         default = "mattermost";
209         description = ''
210           Local Mattermost database name.
211         '';
212       };
214       localDatabaseUser = mkOption {
215         type = types.str;
216         default = "mattermost";
217         description = ''
218           Local Mattermost database username.
219         '';
220       };
222       localDatabasePassword = mkOption {
223         type = types.str;
224         default = "mmpgsecret";
225         description = ''
226           Password for local Mattermost database user.
227         '';
228       };
230       user = mkOption {
231         type = types.str;
232         default = "mattermost";
233         description = ''
234           User which runs the Mattermost service.
235         '';
236       };
238       group = mkOption {
239         type = types.str;
240         default = "mattermost";
241         description = ''
242           Group which runs the Mattermost service.
243         '';
244       };
246       matterircd = {
247         enable = mkEnableOption "Mattermost IRC bridge";
248         package = mkPackageOption pkgs "matterircd" { };
249         parameters = mkOption {
250           type = types.listOf types.str;
251           default = [ ];
252           example = [ "-mmserver chat.example.com" "-bind [::]:6667" ];
253           description = ''
254             Set commandline parameters to pass to matterircd. See
255             https://github.com/42wim/matterircd#usage for more information.
256           '';
257         };
258       };
259     };
260   };
262   config = mkMerge [
263     (mkIf cfg.enable {
264       users.users = optionalAttrs (cfg.user == "mattermost") {
265         mattermost = {
266           group = cfg.group;
267           uid = config.ids.uids.mattermost;
268           home = cfg.statePath;
269         };
270       };
272       users.groups = optionalAttrs (cfg.group == "mattermost") {
273         mattermost.gid = config.ids.gids.mattermost;
274       };
276       services.postgresql.enable = cfg.localDatabaseCreate;
278       # The systemd service will fail to execute the preStart hook
279       # if the WorkingDirectory does not exist
280       systemd.tmpfiles.settings."10-mattermost".${cfg.statePath}.d = { };
282       systemd.services.mattermost = {
283         description = "Mattermost chat service";
284         wantedBy = [ "multi-user.target" ];
285         after = [ "network.target" "postgresql.service" ];
287         preStart = ''
288           mkdir -p "${cfg.statePath}"/{data,config,logs,plugins}
289           mkdir -p "${cfg.statePath}/plugins"/{client,server}
290           ln -sf ${cfg.package}/{bin,fonts,i18n,templates,client} "${cfg.statePath}"
291         '' + lib.optionalString (mattermostPlugins != null) ''
292           rm -rf "${cfg.statePath}/data/plugins"
293           ln -sf ${mattermostPlugins}/data/plugins "${cfg.statePath}/data"
294         '' + lib.optionalString (!cfg.mutableConfig) ''
295           rm -f "${cfg.statePath}/config/config.json"
296           ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.statePath}/config/config.json"
297         '' + lib.optionalString cfg.mutableConfig ''
298           if ! test -e "${cfg.statePath}/config/.initial-created"; then
299             rm -f ${cfg.statePath}/config/config.json
300             ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.statePath}/config/config.json"
301             touch "${cfg.statePath}/config/.initial-created"
302           fi
303         '' + lib.optionalString (cfg.mutableConfig && cfg.preferNixConfig) ''
304           new_config="$(${pkgs.jq}/bin/jq -s '.[0] * .[1]' "${cfg.statePath}/config/config.json" ${mattermostConfJSON})"
306           rm -f "${cfg.statePath}/config/config.json"
307           echo "$new_config" > "${cfg.statePath}/config/config.json"
308         '' + lib.optionalString cfg.localDatabaseCreate (createDb {}) + ''
309           # Don't change permissions recursively on the data, current, and symlinked directories (see ln -sf command above).
310           # This dramatically decreases startup times for installations with a lot of files.
311           find . -maxdepth 1 -not -name data -not -name client -not -name templates -not -name i18n -not -name fonts -not -name bin -not -name . \
312             -exec chown "${cfg.user}:${cfg.group}" -R {} \; -exec chmod u+rw,g+r,o-rwx -R {} \;
314           chown "${cfg.user}:${cfg.group}" "${cfg.statePath}/data" .
315           chmod u+rw,g+r,o-rwx "${cfg.statePath}/data" .
316         '';
318         serviceConfig = {
319           PermissionsStartOnly = true;
320           User = cfg.user;
321           Group = cfg.group;
322           ExecStart = "${cfg.package}/bin/mattermost";
323           WorkingDirectory = "${cfg.statePath}";
324           Restart = "always";
325           RestartSec = "10";
326           LimitNOFILE = "49152";
327           EnvironmentFile = cfg.environmentFile;
328         };
329         unitConfig.JoinsNamespaceOf = mkIf cfg.localDatabaseCreate "postgresql.service";
330       };
331     })
332     (mkIf cfg.matterircd.enable {
333       systemd.services.matterircd = {
334         description = "Mattermost IRC bridge service";
335         wantedBy = [ "multi-user.target" ];
336         serviceConfig = {
337           User = "nobody";
338           Group = "nogroup";
339           ExecStart = "${cfg.matterircd.package}/bin/matterircd ${escapeShellArgs cfg.matterircd.parameters}";
340           WorkingDirectory = "/tmp";
341           PrivateTmp = true;
342           Restart = "always";
343           RestartSec = "5";
344         };
345       };
346     })
347   ];