at 23.11-pre 13 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 = lib.literalExpression "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 Additional 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 environmentFile = mkOption { 188 type = types.nullOr types.path; 189 default = null; 190 description = lib.mdDoc '' 191 Environment file (see {manpage}`systemd.exec(5)` 192 "EnvironmentFile=" section for the syntax) which sets config options 193 for mattermost (see [the mattermost documentation](https://docs.mattermost.com/configure/configuration-settings.html#environment-variables)). 194 195 Settings defined in the environment file will overwrite settings 196 set via nix or via the {option}`services.mattermost.extraConfig` 197 option. 198 199 Useful for setting config options without their value ending up in the 200 (world-readable) nix store, e.g. for a database password. 201 ''; 202 }; 203 204 localDatabaseCreate = mkOption { 205 type = types.bool; 206 default = true; 207 description = lib.mdDoc '' 208 Create a local PostgreSQL database for Mattermost automatically. 209 ''; 210 }; 211 212 localDatabaseName = mkOption { 213 type = types.str; 214 default = "mattermost"; 215 description = lib.mdDoc '' 216 Local Mattermost database name. 217 ''; 218 }; 219 220 localDatabaseUser = mkOption { 221 type = types.str; 222 default = "mattermost"; 223 description = lib.mdDoc '' 224 Local Mattermost database username. 225 ''; 226 }; 227 228 localDatabasePassword = mkOption { 229 type = types.str; 230 default = "mmpgsecret"; 231 description = lib.mdDoc '' 232 Password for local Mattermost database user. 233 ''; 234 }; 235 236 user = mkOption { 237 type = types.str; 238 default = "mattermost"; 239 description = lib.mdDoc '' 240 User which runs the Mattermost service. 241 ''; 242 }; 243 244 group = mkOption { 245 type = types.str; 246 default = "mattermost"; 247 description = lib.mdDoc '' 248 Group which runs the Mattermost service. 249 ''; 250 }; 251 252 matterircd = { 253 enable = mkEnableOption (lib.mdDoc "Mattermost IRC bridge"); 254 package = mkOption { 255 type = types.package; 256 default = pkgs.matterircd; 257 defaultText = lib.literalExpression "pkgs.matterircd"; 258 description = lib.mdDoc "matterircd derivation to use."; 259 }; 260 parameters = mkOption { 261 type = types.listOf types.str; 262 default = [ ]; 263 example = [ "-mmserver chat.example.com" "-bind [::]:6667" ]; 264 description = lib.mdDoc '' 265 Set commandline parameters to pass to matterircd. See 266 https://github.com/42wim/matterircd#usage for more information. 267 ''; 268 }; 269 }; 270 }; 271 }; 272 273 config = mkMerge [ 274 (mkIf cfg.enable { 275 users.users = optionalAttrs (cfg.user == "mattermost") { 276 mattermost = { 277 group = cfg.group; 278 uid = config.ids.uids.mattermost; 279 home = cfg.statePath; 280 }; 281 }; 282 283 users.groups = optionalAttrs (cfg.group == "mattermost") { 284 mattermost.gid = config.ids.gids.mattermost; 285 }; 286 287 services.postgresql.enable = cfg.localDatabaseCreate; 288 289 # The systemd service will fail to execute the preStart hook 290 # if the WorkingDirectory does not exist 291 system.activationScripts.mattermost = '' 292 mkdir -p "${cfg.statePath}" 293 ''; 294 295 systemd.services.mattermost = { 296 description = "Mattermost chat service"; 297 wantedBy = [ "multi-user.target" ]; 298 after = [ "network.target" "postgresql.service" ]; 299 300 preStart = '' 301 mkdir -p "${cfg.statePath}"/{data,config,logs,plugins} 302 mkdir -p "${cfg.statePath}/plugins"/{client,server} 303 ln -sf ${cfg.package}/{bin,fonts,i18n,templates,client} "${cfg.statePath}" 304 '' + lib.optionalString (mattermostPlugins != null) '' 305 rm -rf "${cfg.statePath}/data/plugins" 306 ln -sf ${mattermostPlugins}/data/plugins "${cfg.statePath}/data" 307 '' + lib.optionalString (!cfg.mutableConfig) '' 308 rm -f "${cfg.statePath}/config/config.json" 309 ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.statePath}/config/config.json" 310 '' + lib.optionalString cfg.mutableConfig '' 311 if ! test -e "${cfg.statePath}/config/.initial-created"; then 312 rm -f ${cfg.statePath}/config/config.json 313 ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.statePath}/config/config.json" 314 touch "${cfg.statePath}/config/.initial-created" 315 fi 316 '' + lib.optionalString (cfg.mutableConfig && cfg.preferNixConfig) '' 317 new_config="$(${pkgs.jq}/bin/jq -s '.[0] * .[1]' "${cfg.statePath}/config/config.json" ${mattermostConfJSON})" 318 319 rm -f "${cfg.statePath}/config/config.json" 320 echo "$new_config" > "${cfg.statePath}/config/config.json" 321 '' + lib.optionalString cfg.localDatabaseCreate (createDb {}) + '' 322 # Don't change permissions recursively on the data, current, and symlinked directories (see ln -sf command above). 323 # This dramatically decreases startup times for installations with a lot of files. 324 find . -maxdepth 1 -not -name data -not -name client -not -name templates -not -name i18n -not -name fonts -not -name bin -not -name . \ 325 -exec chown "${cfg.user}:${cfg.group}" -R {} \; -exec chmod u+rw,g+r,o-rwx -R {} \; 326 327 chown "${cfg.user}:${cfg.group}" "${cfg.statePath}/data" . 328 chmod u+rw,g+r,o-rwx "${cfg.statePath}/data" . 329 ''; 330 331 serviceConfig = { 332 PermissionsStartOnly = true; 333 User = cfg.user; 334 Group = cfg.group; 335 ExecStart = "${cfg.package}/bin/mattermost"; 336 WorkingDirectory = "${cfg.statePath}"; 337 Restart = "always"; 338 RestartSec = "10"; 339 LimitNOFILE = "49152"; 340 EnvironmentFile = cfg.environmentFile; 341 }; 342 unitConfig.JoinsNamespaceOf = mkIf cfg.localDatabaseCreate "postgresql.service"; 343 }; 344 }) 345 (mkIf cfg.matterircd.enable { 346 systemd.services.matterircd = { 347 description = "Mattermost IRC bridge service"; 348 wantedBy = [ "multi-user.target" ]; 349 serviceConfig = { 350 User = "nobody"; 351 Group = "nogroup"; 352 ExecStart = "${cfg.matterircd.package}/bin/matterircd ${escapeShellArgs cfg.matterircd.parameters}"; 353 WorkingDirectory = "/tmp"; 354 PrivateTmp = true; 355 Restart = "always"; 356 RestartSec = "5"; 357 }; 358 }; 359 }) 360 ]; 361}