{ pkgs, lib, config, ... }: with lib; let cfg = config.fudo.chat; in { options.fudo.chat = { enable = mkEnableOption "Enable chat server"; hostname = mkOption { type = types.str; description = "Hostname at which this chat server is accessible."; example = "chat.mydomain.com"; }; site-name = mkOption { type = types.str; description = "The name of this chat server."; example = "My Fancy Chat Site"; }; smtp-server = mkOption { type = types.str; description = "SMTP server to use for sending notification emails."; example = "mail.my-site.com"; }; smtp-user = mkOption { type = types.str; description = "Username with which to connect to the SMTP server."; }; smtp-password-file = mkOption { type = types.str; description = "Path to a file containing the password to use while connecting to the SMTP server."; }; state-directory = mkOption { type = types.str; description = "Path at which to store server state data."; default = "/var/lib/mattermost"; }; database = mkOption { type = (types.submodule { options = { name = mkOption { type = types.str; description = "Database name."; }; hostname = mkOption { type = types.str; description = "Database host."; }; user = mkOption { type = types.str; description = "Database user."; }; password-file = mkOption { type = types.str; description = "Path to file containing database password."; }; }; }); description = "Database configuration."; example = { name = "my_database"; hostname = "my.database.com"; user = "db_user"; password-file = /path/to/some/file.pw; }; }; }; config = mkIf cfg.enable (let pkg = pkgs.mattermost; default-config = builtins.fromJSON (readFile "${pkg}/config/config.json"); modified-config = recursiveUpdate default-config { ServiceSettings.SiteURL = "https://${cfg.hostname}"; ServiceSettings.ListenAddress = "127.0.0.1:8065"; TeamSettings.SiteName = cfg.site-name; EmailSettings = { RequireEmailVerification = true; SMTPServer = cfg.smtp-server; SMTPPort = 587; EnableSMTPAuth = true; SMTPUsername = cfg.smtp-user; SMTPPassword = (fileContents cfg.smtp-password-file); SendEmailNotifications = true; ConnectionSecurity = "STARTTLS"; FeedbackEmail = "chat@fudo.org"; FeedbackName = "Admin"; }; EnableEmailInvitations = true; SqlSettings.DriverName = "postgres"; SqlSettings.DataSource = "postgres://${cfg.database.user}:${ fileContents cfg.database.password-file }@${cfg.database.hostname}:5432/${cfg.database.name}"; }; mattermost-config-file = pkgs.writeText "mattermost-config.json" (builtins.toJSON modified-config); mattermost-user = "mattermost"; mattermost-group = "mattermost"; in { users = { users = { ${mattermost-user} = { isSystemUser = true; group = mattermost-group; }; }; groups = { ${mattermost-group} = { members = [ mattermost-user ]; }; }; }; system.activationScripts.mattermost = '' mkdir -p ${cfg.state-directory} ''; systemd.services.mattermost = { description = "Mattermost Chat Server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; preStart = '' mkdir -p ${cfg.state-directory}/config cp ${mattermost-config-file} ${cfg.state-directory}/config/config.json ln -sf ${pkg}/bin ${cfg.state-directory} ln -sf ${pkg}/fonts ${cfg.state-directory} ln -sf ${pkg}/i18n ${cfg.state-directory} ln -sf ${pkg}/templates ${cfg.state-directory} cp -uRL ${pkg}/client ${cfg.state-directory} chown -R ${mattermost-user}:${mattermost-group} ${cfg.state-directory} chmod u+w -R ${cfg.state-directory}/client chmod o-rwx -R ${cfg.state-directory} ''; serviceConfig = { PermissionsStartOnly = true; ExecStart = "${pkg}/bin/mattermost"; WorkingDirectory = cfg.state-directory; Restart = "always"; RestartSec = "10"; LimitNOFILE = "49152"; User = mattermost-user; Group = mattermost-group; }; }; security.acme.certs.${cfg.hostname}.email = config.fudo.common.admin-email; services.nginx = { enable = true; appendHttpConfig = '' proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mattermost_cache:10m max_size=3g inactive=120m use_temp_path=off; ''; virtualHosts = { "${cfg.hostname}" = { enableACME = true; forceSSL = true; locations."/" = { proxyPass = "http://127.0.0.1:8065"; extraConfig = '' client_max_body_size 50M; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-By $server_addr:$server_port; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Frame-Options SAMEORIGIN; proxy_buffers 256 16k; proxy_buffer_size 16k; proxy_read_timeout 600s; proxy_cache mattermost_cache; proxy_cache_revalidate on; proxy_cache_min_uses 2; proxy_cache_use_stale timeout; proxy_cache_lock on; proxy_http_version 1.1; ''; }; locations."~ /api/v[0-9]+/(users/)?websocket$" = { proxyPass = "http://127.0.0.1:8065"; extraConfig = '' proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; client_max_body_size 50M; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-By $server_addr:$server_port; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Frame-Options SAMEORIGIN; proxy_buffers 256 16k; proxy_buffer_size 16k; client_body_timeout 60; send_timeout 300; lingering_timeout 5; proxy_connect_timeout 90; proxy_send_timeout 300; proxy_read_timeout 90s; ''; }; }; }; }; }); }