1{ config, pkgs, lib, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.mattermost;
8
9 defaultConfig = builtins.fromJSON (builtins.replaceStrings [ "\\u0026" ] [ "&" ]
10 (readFile "${pkgs.mattermost}/config/config.json")
11 );
12
13 database = "postgres://${cfg.localDatabaseUser}:${cfg.localDatabasePassword}@localhost:5432/${cfg.localDatabaseName}?sslmode=disable&connect_timeout=10";
14
15 mattermostConf = foldl recursiveUpdate defaultConfig
16 [ { ServiceSettings.SiteURL = cfg.siteUrl;
17 ServiceSettings.ListenAddress = cfg.listenAddress;
18 TeamSettings.SiteName = cfg.siteName;
19 SqlSettings.DriverName = "postgres";
20 SqlSettings.DataSource = database;
21 }
22 cfg.extraConfig
23 ];
24
25 mattermostConfJSON = pkgs.writeText "mattermost-config-raw.json" (builtins.toJSON mattermostConf);
26
27in
28
29{
30 options = {
31 services.mattermost = {
32 enable = mkEnableOption "Mattermost chat server";
33
34 statePath = mkOption {
35 type = types.str;
36 default = "/var/lib/mattermost";
37 description = "Mattermost working directory";
38 };
39
40 siteUrl = mkOption {
41 type = types.str;
42 example = "https://chat.example.com";
43 description = ''
44 URL this Mattermost instance is reachable under, without trailing slash.
45 '';
46 };
47
48 siteName = mkOption {
49 type = types.str;
50 default = "Mattermost";
51 description = "Name of this Mattermost site.";
52 };
53
54 listenAddress = mkOption {
55 type = types.str;
56 default = ":8065";
57 example = "[::1]:8065";
58 description = ''
59 Address and port this Mattermost instance listens to.
60 '';
61 };
62
63 mutableConfig = mkOption {
64 type = types.bool;
65 default = false;
66 description = ''
67 Whether the Mattermost config.json is writeable by Mattermost.
68
69 Most of the settings can be edited in the system console of
70 Mattermost if this option is enabled. A template config using
71 the options specified in services.mattermost will be generated
72 but won't be overwritten on changes or rebuilds.
73
74 If this option is disabled, changes in the system console won't
75 be possible (default). If an config.json is present, it will be
76 overwritten!
77 '';
78 };
79
80 extraConfig = mkOption {
81 type = types.attrs;
82 default = { };
83 description = ''
84 Addtional configuration options as Nix attribute set in config.json schema.
85 '';
86 };
87
88 localDatabaseCreate = mkOption {
89 type = types.bool;
90 default = true;
91 description = ''
92 Create a local PostgreSQL database for Mattermost automatically.
93 '';
94 };
95
96 localDatabaseName = mkOption {
97 type = types.str;
98 default = "mattermost";
99 description = ''
100 Local Mattermost database name.
101 '';
102 };
103
104 localDatabaseUser = mkOption {
105 type = types.str;
106 default = "mattermost";
107 description = ''
108 Local Mattermost database username.
109 '';
110 };
111
112 localDatabasePassword = mkOption {
113 type = types.str;
114 default = "mmpgsecret";
115 description = ''
116 Password for local Mattermost database user.
117 '';
118 };
119
120 user = mkOption {
121 type = types.str;
122 default = "mattermost";
123 description = ''
124 User which runs the Mattermost service.
125 '';
126 };
127
128 group = mkOption {
129 type = types.str;
130 default = "mattermost";
131 description = ''
132 Group which runs the Mattermost service.
133 '';
134 };
135
136 matterircd = {
137 enable = mkEnableOption "Mattermost IRC bridge";
138 parameters = mkOption {
139 type = types.listOf types.str;
140 default = [ ];
141 example = [ "-mmserver chat.example.com" "-bind [::]:6667" ];
142 description = ''
143 Set commandline parameters to pass to matterircd. See
144 https://github.com/42wim/matterircd#usage for more information.
145 '';
146 };
147 };
148 };
149 };
150
151 config = mkMerge [
152 (mkIf cfg.enable {
153 users.users = optionalAttrs (cfg.user == "mattermost") {
154 mattermost = {
155 group = cfg.group;
156 uid = config.ids.uids.mattermost;
157 home = cfg.statePath;
158 };
159 };
160
161 users.groups = optionalAttrs (cfg.group == "mattermost") {
162 mattermost.gid = config.ids.gids.mattermost;
163 };
164
165 services.postgresql.enable = cfg.localDatabaseCreate;
166
167 # The systemd service will fail to execute the preStart hook
168 # if the WorkingDirectory does not exist
169 system.activationScripts.mattermost = ''
170 mkdir -p ${cfg.statePath}
171 '';
172
173 systemd.services.mattermost = {
174 description = "Mattermost chat service";
175 wantedBy = [ "multi-user.target" ];
176 after = [ "network.target" "postgresql.service" ];
177
178 preStart = ''
179 mkdir -p ${cfg.statePath}/{data,config,logs}
180 ln -sf ${pkgs.mattermost}/{bin,fonts,i18n,templates,client} ${cfg.statePath}
181 '' + lib.optionalString (!cfg.mutableConfig) ''
182 rm -f ${cfg.statePath}/config/config.json
183 cp ${mattermostConfJSON} ${cfg.statePath}/config/config.json
184 ${pkgs.mattermost}/bin/mattermost config migrate ${cfg.statePath}/config/config.json ${database}
185 '' + lib.optionalString cfg.mutableConfig ''
186 if ! test -e "${cfg.statePath}/config/.initial-created"; then
187 rm -f ${cfg.statePath}/config/config.json
188 cp ${mattermostConfJSON} ${cfg.statePath}/config/config.json
189 touch ${cfg.statePath}/config/.initial-created
190 fi
191 '' + lib.optionalString cfg.localDatabaseCreate ''
192 if ! test -e "${cfg.statePath}/.db-created"; then
193 ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
194 ${config.services.postgresql.package}/bin/psql postgres -c \
195 "CREATE ROLE ${cfg.localDatabaseUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${cfg.localDatabasePassword}'"
196 ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
197 ${config.services.postgresql.package}/bin/createdb \
198 --owner ${cfg.localDatabaseUser} ${cfg.localDatabaseName}
199 touch ${cfg.statePath}/.db-created
200 fi
201 '' + ''
202 chown ${cfg.user}:${cfg.group} -R ${cfg.statePath}
203 chmod u+rw,g+r,o-rwx -R ${cfg.statePath}
204 '';
205
206 serviceConfig = {
207 PermissionsStartOnly = true;
208 User = cfg.user;
209 Group = cfg.group;
210 ExecStart = "${pkgs.mattermost}/bin/mattermost" +
211 (lib.optionalString (!cfg.mutableConfig) " -c ${database}");
212 WorkingDirectory = "${cfg.statePath}";
213 Restart = "always";
214 RestartSec = "10";
215 LimitNOFILE = "49152";
216 };
217 unitConfig.JoinsNamespaceOf = mkIf cfg.localDatabaseCreate "postgresql.service";
218 };
219 })
220 (mkIf cfg.matterircd.enable {
221 systemd.services.matterircd = {
222 description = "Mattermost IRC bridge service";
223 wantedBy = [ "multi-user.target" ];
224 serviceConfig = {
225 User = "nobody";
226 Group = "nogroup";
227 ExecStart = "${pkgs.matterircd}/bin/matterircd ${concatStringsSep " " cfg.matterircd.parameters}";
228 WorkingDirectory = "/tmp";
229 PrivateTmp = true;
230 Restart = "always";
231 RestartSec = "5";
232 };
233 };
234 })
235 ];
236}