at 23.05-pre 12 kB view raw
1{ config, pkgs, lib, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.mattermost; 8 9 database = "postgres://${cfg.localDatabaseUser}:${cfg.localDatabasePassword}@localhost:5432/${cfg.localDatabaseName}?sslmode=disable&connect_timeout=10"; 10 11 postgresPackage = config.services.postgresql.package; 12 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 ''; 30 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; 44 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 ''; 67 68 dontUnpack = true; 69 dontPatch = true; 70 dontConfigure = true; 71 dontBuild = true; 72 preferLocalBuild = true; 73 }; 74 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; 85 86 mattermostConf = recursiveUpdate 87 mattermostConfWithoutPlugins 88 ( 89 if mattermostPlugins == null then {} 90 else { 91 PluginSettings = { 92 Enable = true; 93 }; 94 } 95 ); 96 97 mattermostConfJSON = pkgs.writeText "mattermost-config.json" (builtins.toJSON mattermostConf); 98 99in 100 101{ 102 options = { 103 services.mattermost = { 104 enable = mkEnableOption (lib.mdDoc "Mattermost chat server"); 105 106 package = mkOption { 107 type = types.package; 108 default = pkgs.mattermost; 109 defaultText = "pkgs.mattermost"; 110 description = lib.mdDoc "Mattermost derivation to use."; 111 }; 112 113 statePath = mkOption { 114 type = types.str; 115 default = "/var/lib/mattermost"; 116 description = lib.mdDoc "Mattermost working directory"; 117 }; 118 119 siteUrl = mkOption { 120 type = types.str; 121 example = "https://chat.example.com"; 122 description = lib.mdDoc '' 123 URL this Mattermost instance is reachable under, without trailing slash. 124 ''; 125 }; 126 127 siteName = mkOption { 128 type = types.str; 129 default = "Mattermost"; 130 description = lib.mdDoc "Name of this Mattermost site."; 131 }; 132 133 listenAddress = mkOption { 134 type = types.str; 135 default = ":8065"; 136 example = "[::1]:8065"; 137 description = lib.mdDoc '' 138 Address and port this Mattermost instance listens to. 139 ''; 140 }; 141 142 mutableConfig = mkOption { 143 type = types.bool; 144 default = false; 145 description = lib.mdDoc '' 146 Whether the Mattermost config.json is writeable by Mattermost. 147 148 Most of the settings can be edited in the system console of 149 Mattermost if this option is enabled. A template config using 150 the options specified in services.mattermost will be generated 151 but won't be overwritten on changes or rebuilds. 152 153 If this option is disabled, changes in the system console won't 154 be possible (default). If an config.json is present, it will be 155 overwritten! 156 ''; 157 }; 158 159 preferNixConfig = mkOption { 160 type = types.bool; 161 default = false; 162 description = lib.mdDoc '' 163 If both mutableConfig and this option are set, the Nix configuration 164 will take precedence over any settings configured in the server 165 console. 166 ''; 167 }; 168 169 extraConfig = mkOption { 170 type = types.attrs; 171 default = { }; 172 description = lib.mdDoc '' 173 Addtional configuration options as Nix attribute set in config.json schema. 174 ''; 175 }; 176 177 plugins = mkOption { 178 type = types.listOf (types.oneOf [types.path types.package]); 179 default = []; 180 example = "[ ./com.github.moussetc.mattermost.plugin.giphy-2.0.0.tar.gz ]"; 181 description = lib.mdDoc '' 182 Plugins to add to the configuration. Overrides any installed if non-null. 183 This is a list of paths to .tar.gz files or derivations evaluating to 184 .tar.gz files. 185 ''; 186 }; 187 188 localDatabaseCreate = mkOption { 189 type = types.bool; 190 default = true; 191 description = lib.mdDoc '' 192 Create a local PostgreSQL database for Mattermost automatically. 193 ''; 194 }; 195 196 localDatabaseName = mkOption { 197 type = types.str; 198 default = "mattermost"; 199 description = lib.mdDoc '' 200 Local Mattermost database name. 201 ''; 202 }; 203 204 localDatabaseUser = mkOption { 205 type = types.str; 206 default = "mattermost"; 207 description = lib.mdDoc '' 208 Local Mattermost database username. 209 ''; 210 }; 211 212 localDatabasePassword = mkOption { 213 type = types.str; 214 default = "mmpgsecret"; 215 description = lib.mdDoc '' 216 Password for local Mattermost database user. 217 ''; 218 }; 219 220 user = mkOption { 221 type = types.str; 222 default = "mattermost"; 223 description = lib.mdDoc '' 224 User which runs the Mattermost service. 225 ''; 226 }; 227 228 group = mkOption { 229 type = types.str; 230 default = "mattermost"; 231 description = lib.mdDoc '' 232 Group which runs the Mattermost service. 233 ''; 234 }; 235 236 matterircd = { 237 enable = mkEnableOption (lib.mdDoc "Mattermost IRC bridge"); 238 package = mkOption { 239 type = types.package; 240 default = pkgs.matterircd; 241 defaultText = "pkgs.matterircd"; 242 description = lib.mdDoc "matterircd derivation to use."; 243 }; 244 parameters = mkOption { 245 type = types.listOf types.str; 246 default = [ ]; 247 example = [ "-mmserver chat.example.com" "-bind [::]:6667" ]; 248 description = lib.mdDoc '' 249 Set commandline parameters to pass to matterircd. See 250 https://github.com/42wim/matterircd#usage for more information. 251 ''; 252 }; 253 }; 254 }; 255 }; 256 257 config = mkMerge [ 258 (mkIf cfg.enable { 259 users.users = optionalAttrs (cfg.user == "mattermost") { 260 mattermost = { 261 group = cfg.group; 262 uid = config.ids.uids.mattermost; 263 home = cfg.statePath; 264 }; 265 }; 266 267 users.groups = optionalAttrs (cfg.group == "mattermost") { 268 mattermost.gid = config.ids.gids.mattermost; 269 }; 270 271 services.postgresql.enable = cfg.localDatabaseCreate; 272 273 # The systemd service will fail to execute the preStart hook 274 # if the WorkingDirectory does not exist 275 system.activationScripts.mattermost = '' 276 mkdir -p "${cfg.statePath}" 277 ''; 278 279 systemd.services.mattermost = { 280 description = "Mattermost chat service"; 281 wantedBy = [ "multi-user.target" ]; 282 after = [ "network.target" "postgresql.service" ]; 283 284 preStart = '' 285 mkdir -p "${cfg.statePath}"/{data,config,logs,plugins} 286 mkdir -p "${cfg.statePath}/plugins"/{client,server} 287 ln -sf ${cfg.package}/{bin,fonts,i18n,templates,client} "${cfg.statePath}" 288 '' + lib.optionalString (mattermostPlugins != null) '' 289 rm -rf "${cfg.statePath}/data/plugins" 290 ln -sf ${mattermostPlugins}/data/plugins "${cfg.statePath}/data" 291 '' + lib.optionalString (!cfg.mutableConfig) '' 292 rm -f "${cfg.statePath}/config/config.json" 293 ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.statePath}/config/config.json" 294 '' + lib.optionalString cfg.mutableConfig '' 295 if ! test -e "${cfg.statePath}/config/.initial-created"; then 296 rm -f ${cfg.statePath}/config/config.json 297 ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.statePath}/config/config.json" 298 touch "${cfg.statePath}/config/.initial-created" 299 fi 300 '' + lib.optionalString (cfg.mutableConfig && cfg.preferNixConfig) '' 301 new_config="$(${pkgs.jq}/bin/jq -s '.[0] * .[1]' "${cfg.statePath}/config/config.json" ${mattermostConfJSON})" 302 303 rm -f "${cfg.statePath}/config/config.json" 304 echo "$new_config" > "${cfg.statePath}/config/config.json" 305 '' + lib.optionalString cfg.localDatabaseCreate (createDb {}) + '' 306 # Don't change permissions recursively on the data, current, and symlinked directories (see ln -sf command above). 307 # This dramatically decreases startup times for installations with a lot of files. 308 find . -maxdepth 1 -not -name data -not -name client -not -name templates -not -name i18n -not -name fonts -not -name bin -not -name . \ 309 -exec chown "${cfg.user}:${cfg.group}" -R {} \; -exec chmod u+rw,g+r,o-rwx -R {} \; 310 311 chown "${cfg.user}:${cfg.group}" "${cfg.statePath}/data" . 312 chmod u+rw,g+r,o-rwx "${cfg.statePath}/data" . 313 ''; 314 315 serviceConfig = { 316 PermissionsStartOnly = true; 317 User = cfg.user; 318 Group = cfg.group; 319 ExecStart = "${cfg.package}/bin/mattermost"; 320 WorkingDirectory = "${cfg.statePath}"; 321 Restart = "always"; 322 RestartSec = "10"; 323 LimitNOFILE = "49152"; 324 }; 325 unitConfig.JoinsNamespaceOf = mkIf cfg.localDatabaseCreate "postgresql.service"; 326 }; 327 }) 328 (mkIf cfg.matterircd.enable { 329 systemd.services.matterircd = { 330 description = "Mattermost IRC bridge service"; 331 wantedBy = [ "multi-user.target" ]; 332 serviceConfig = { 333 User = "nobody"; 334 Group = "nogroup"; 335 ExecStart = "${cfg.matterircd.package}/bin/matterircd ${escapeShellArgs cfg.matterircd.parameters}"; 336 WorkingDirectory = "/tmp"; 337 PrivateTmp = true; 338 Restart = "always"; 339 RestartSec = "5"; 340 }; 341 }; 342 }) 343 ]; 344}