feat(web02): Deploy mattermost on chat.dgnum.eu
All checks were successful
Run pre-commit on all files / pre-commit (push) Successful in 27s
Run pre-commit on all files / pre-commit (pull_request) Successful in 27s
Check meta / check_dns (push) Successful in 15s
Check meta / check_meta (push) Successful in 16s
Check meta / check_dns (pull_request) Successful in 15s
Check meta / check_meta (pull_request) Successful in 15s
Check workflows / check_workflows (pull_request) Successful in 18s
Build all the nodes / ap01 (pull_request) Successful in 39s
Build all the nodes / bridge01 (pull_request) Successful in 45s
Build all the nodes / cof02 (pull_request) Successful in 51s
Build all the nodes / geo01 (pull_request) Successful in 51s
Build all the nodes / build01 (pull_request) Successful in 59s
Build all the nodes / geo02 (pull_request) Successful in 51s
Build all the nodes / netaccess01 (pull_request) Successful in 22s
Build all the nodes / hypervisor01 (pull_request) Successful in 49s
Build all the nodes / hypervisor02 (pull_request) Successful in 48s
Build all the nodes / hypervisor03 (pull_request) Successful in 45s
Build all the nodes / netcore00 (pull_request) Successful in 23s
Build all the nodes / netcore01 (pull_request) Successful in 22s
Build all the nodes / netcore02 (pull_request) Successful in 23s
Build all the nodes / compute01 (pull_request) Successful in 1m18s
Build all the nodes / lab-router01 (pull_request) Successful in 52s
Build all the nodes / iso (pull_request) Successful in 1m0s
Build the shell / build-shell (pull_request) Successful in 22s
Build all the nodes / tower01 (pull_request) Successful in 46s
Build all the nodes / vault01 (pull_request) Successful in 1m0s
Build all the nodes / krz01 (pull_request) Successful in 1m39s
Build all the nodes / rescue01 (pull_request) Successful in 1m11s
Build all the nodes / storage01 (pull_request) Successful in 1m15s
Build all the nodes / web03 (pull_request) Successful in 59s
Build all the nodes / web01 (pull_request) Successful in 1m11s
Build all the nodes / web02 (pull_request) Successful in 3m59s

This commit is contained in:
Tom Hubrecht 2024-10-17 23:39:02 +02:00
parent fb5a0ae7eb
commit af1cf7606d
Signed by: thubrecht
SSH key fingerprint: SHA256:r+nK/SIcWlJ0zFZJGHtlAoRwq1Rm+WcKAm5ADYMoQPc
8 changed files with 1427 additions and 0 deletions

View file

@ -15,6 +15,7 @@ lib.extra.mkConfig {
"cas-eleves"
# "kadenios"
"django-apps"
"mattermost"
];
extraConfig = {

View file

@ -0,0 +1,348 @@
From ec1ffa5fc38cdd6d0cc5e8dea07d9a4730fc5a32 Mon Sep 17 00:00:00 2001
From: hbarral <hbarral@clipper.ens.fr>
Date: Fri, 25 Oct 2024 06:50:47 +0200
Subject: [PATCH 1/2] Login page: ENS theme This commit should be cherry-picked
on top of of new Mattermost releases
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Théophile Bastian <contact@tobast.fr>
---
.../channels/src/components/login/login.scss | 87 +++++++++++++++++-
.../channels/src/components/login/login.tsx | 78 +++++++++++++---
webapp/channels/src/images/emailLogo.png | Bin 0 -> 605 bytes
webapp/channels/src/images/gitlabLogo.png | Bin 0 -> 1655 bytes
webapp/channels/src/images/noaccountLogo.png | Bin 0 -> 693 bytes
5 files changed, 153 insertions(+), 12 deletions(-)
create mode 100644 webapp/channels/src/images/emailLogo.png
create mode 100644 webapp/channels/src/images/gitlabLogo.png
create mode 100644 webapp/channels/src/images/noaccountLogo.png
diff --git a/webapp/channels/src/components/login/login.scss b/webapp/channels/src/components/login/login.scss
index e64aebccb4..ec7dafdafb 100644
--- a/webapp/channels/src/components/login/login.scss
+++ b/webapp/channels/src/components/login/login.scss
@@ -3,6 +3,10 @@
@use 'utils/mixins';
+$clipper_color: #548;
+$email_color: #4d8844;
+$exte_color: #886344;
+
.login-body {
display: flex;
flex-direction: column;
@@ -130,6 +134,88 @@
margin: 20px 0 10px;
}
+ .collapsed {
+ display: none;
+ }
+
+ .login-btn {
+ @include mixins.primary-button;
+ @include mixins.button-large;
+
+ display: flex;
+ height: 64px;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+ background-color: red;
+ text-decoration: none;
+
+ .text {
+ display: flex;
+ width: 80%;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-evenly;
+
+ & > span {
+ display: inline-block;
+
+ &:nth-child(2) {
+ font-size: 0.8em;
+ font-style: italic;
+ font-weight: normal;
+ }
+ }
+ }
+
+ .icon {
+ position: relative;
+ display: inline-block;
+ width: 42px;
+ height: 42px;
+ margin-right: 12px;
+ background-size: 100% 100%;
+ text-align: center;
+ }
+ }
+
+ .login-body-card-auth-clipper {
+ background-color: $clipper_color;
+
+ &:hover {
+ background-color: darken($clipper_color, 10%);
+ }
+
+ .icon {
+ background-image: url('../../images/gitlabLogo.png');
+ }
+ }
+
+ .login-body-card-auth-mail-toggle {
+ background-color: $email_color;
+
+ &:hover {
+ background-color: darken($email_color, 10%);
+ }
+
+ .icon {
+ background-image: url('../../images/emailLogo.png');
+ }
+ }
+
+ .login-body-card-auth-exte {
+ background-color: $exte_color;
+ margin-top: 10px;
+
+ &:hover {
+ background-color: darken($exte_color, 10%);
+ }
+
+ .icon {
+ background-image: url('../../images/noaccountLogo.png');
+ }
+ }
+
.login-body-card-form {
.login-body-card-form-input {
margin-top: 22px;
@@ -161,7 +246,7 @@
display: flex;
height: 1px;
justify-content: center;
- margin: 40px 0;
+ margin: 20px 0;
background: rgba(var(--center-channel-color-rgb), 0.08);
text-align: center;
diff --git a/webapp/channels/src/components/login/login.tsx b/webapp/channels/src/components/login/login.tsx
index 5668c029a7..d5e2794584 100644
--- a/webapp/channels/src/components/login/login.tsx
+++ b/webapp/channels/src/components/login/login.tsx
@@ -121,6 +121,8 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
const [alertBanner, setAlertBanner] = useState<AlertBannerProps | null>(null);
const [hasError, setHasError] = useState(false);
const [isMobileView, setIsMobileView] = useState(false);
+ const [showEmailForm, setShowEmailForm] = useState(false);
+
const enableCustomBrand = EnableCustomBrand === 'true';
const enableLdap = EnableLdap === 'true';
@@ -460,6 +462,10 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
};
}, []);
+ function toggleEmailForm() {
+ setShowEmailForm(!showEmailForm);
+ }
+
if (initializing) {
return (<LoadingScreen/>);
}
@@ -780,6 +786,8 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
);
}
+ const doShowEmailForm = (showEmailForm) ? '' : ' collapsed';
+
if (!enableBaseLogin && !enableExternalSignup) {
return (
<ColumnLayout
@@ -848,13 +856,50 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
onDismiss={alertBanner.onDismiss ?? dismissAlert}
/>
)}
+ {enableExternalSignup && (
+ <a
+ className='login-body-card-auth-clipper login-btn'
+ id='gitlab'
+ href={Client4.getOAuthRoute() + '/gitlab/login'}
+ >
+ <span className='icon'/>
+ <span className='text'>
+ <span>
+ {formatMessage({id: 'login.clipper', defaultMessage: 'Clipper'})}
+ </span>
+ <span>
+ {formatMessage({id: 'login.clipper_help', defaultMessage: '("Gitlab" sur mobile)'})}
+ </span>
+ </span>
+ </a>
+ )}
+ {enableBaseLogin && enableExternalSignup && (
+ <div className='login-body-card-form-divider'>
+ <span className='login-body-card-form-divider-label'>
+ {formatMessage({id: 'login.or', defaultMessage: 'or log in with'})}
+ </span>
+ </div>
+ )}
+ {enableBaseLogin && (
+ <div
+ className='login-body-card-auth-mail-toggle login-btn'
+ onClick={toggleEmailForm}
+ >
+ <span className='icon'/>
+ <span className='text'>
+ <span>
+ {formatMessage({id: 'login.email', defaultMessage: 'Adresse email'})}
+ </span>
+ </span>
+ </div>
+ )}
{enableBaseLogin && (
<form
onSubmit={(event: FormEvent<HTMLFormElement>) => {
preSubmit(event as unknown as React.MouseEvent);
}}
>
- <div className='login-body-card-form'>
+ <div className={'login-body-card-form' + doShowEmailForm}>
<Input
ref={loginIdInput}
name='loginId'
@@ -895,16 +940,27 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
</span>
</div>
)}
- {enableExternalSignup && (
- <div className={classNames('login-body-card-form-login-options', {column: !enableBaseLogin})}>
- {getExternalLoginOptions().map((option) => (
- <ExternalLoginButton
- key={option.id}
- direction={enableBaseLogin ? undefined : 'column'}
- {...option}
- />
- ))}
- </div>
+ {enableBaseLogin && (
+ <a
+ className='login-body-card-auth-exte login-btn'
+ href='@exte_login@'
+ >
+ <span className='icon'/>
+ <span className='text'>
+ <span>
+ {formatMessage({
+ id: 'login.noClipperAccount',
+ defaultMessage: 'Pas de compte Clipper ?',
+ })}
+ </span>
+ <span>
+ {formatMessage({
+ id: 'login.createExte',
+ defaultMessage: 'Demander un compte extérieur',
+ })}
+ </span>
+ </span>
+ </a>
)}
</div>
</div>
diff --git a/webapp/channels/src/images/emailLogo.png b/webapp/channels/src/images/emailLogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..098922ea6e827eca14dad9810cb953079a1a623d
GIT binary patch
literal 605
zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4i*Lm28M*4p$rTROiAAEE)2Od`|PeWFfecy
zctjR6Fz6|RFk{71`!WUw2KEw9Usv|WENsGR>R+M-zcDZ{zVmc(49U3nc80EpbD%`q
z{)Ie(EuBqGY~4qV1X>SXnswG}_QkD|z0oGSN;Yar#caOmqc+)<O{}@8$x)z9da`ZX
zYuW2>|Gk@6z5YSvZQ18<Z0+xt&nvbQ)CGaH@_`2yxVG=Oy0FzT@rK=TUC&p2mb?F2
z&h{ysHRCVq<tLUK-|#+|94q+kXV=|5{&RV(w=f>QQ|;~Uv*Bp<ns4$slNY-Ef4)26
z-2F?UmSH>l&;DyPP1~{MZxV;@e6?3kO!iLeQJJ^X<Lg1Cl|hFKo2?&7sF@l~$<#JB
zci!_#<k6!`OHZ0q%1ymtU#YsYLPk{kOtI|Ee{HPO?)F=LeDTYz)00p6ru^ikCstQ8
ze|&TAu|tdPuH>2DSN!@ut)aMrGdF)-{NeR8S#BM5o#u2%tANFG!TxGK&eh!yEL9d7
zS#qp-vmwx;VZjEew%$vOKMrT{O!7E!<J%^Q?xhdeH#lAt4C7VP*dX_wVT~o<;Zse%
zXQC7Movhcq+0d!W;ePpoRaRSK^0LIpTq!H&uf0N%?l(-X%DKFAcpJ3v5r=fE`$qkM
z#Ru>3AHV8hVb{_2xWS>9?X$|dlz9fzUpVx#6@SnE=@)eU(-nEOmG)<Y>gO!`YvaGt
zAy7_4!^OqIdxy*AKcR~R1ryD#DqWS=xSR<Bf0JvOmpne>k^WoC8<cE3UHx3vIVCg!
E0QZ>*HUIzs
literal 0
HcmV?d00001
diff --git a/webapp/channels/src/images/gitlabLogo.png b/webapp/channels/src/images/gitlabLogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..bdc4422783b804cbaf45f13219354f38e080b654
GIT binary patch
literal 1655
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
literal 0
HcmV?d00001
diff --git a/webapp/channels/src/images/noaccountLogo.png b/webapp/channels/src/images/noaccountLogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..b51eda513122fe8da10f2fe896b78f380811cc61
GIT binary patch
literal 693
zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4i*Lm28M*4p$rTROiAAEE)2Od`|PeWFfecy
zctjR6Fz6|RFk{71`!WUw2KEw9Usv|WENmijEG}<O{bFEXa`SX?49U3ncE*0c!v+G!
zlvCHSBrWxJ7vK&ycbVU4+97^IXyc|5SJ9w*4Qp(i1!phU;W(z3w$|a*n^(t!LNDCf
zDy#10xHIpn<@cX8-|x=Xc(r7*pVddPUn)<0W^PP4If>PH*%Os7v(0K<+89(=HI>=E
zyf92~@oHpYk#zUHYwd2Be(1sq<CH@(u5r8dcQ`A0cCMXes3~+bw)WV0!2>t){;+o#
zDp;&PT+_73WOwcH;wc)F0zOLy?6_Zhn4fh*>#X@Ku?a#9jVf3ENI3=Rei3oib&Oe{
z)uP<=>G|!=zar1)z7z~r`ty0-{fY6i4kCsu`f&^TlXCuV&~DI3>iLx5Ui2jGRbjJB
zQL^potIBSZc2Ada-Su@ni$j#Z^cRo7zSORspJrcKm^)@#8!<Ue|C{K)A&KRoSi=wF
z{mG>V6a_rr<gMdzv?@@lwEdI*z@=Kv;O@DGTMSEl-g+6k^)gBrZr0Y7FkwqLaA@6@
zCD$by<WAc&EcmKkz~OXm$J0*peE}N#Djs)6pL@mW<SkXydqAb>^#_wXjxKXP@Ep+o
z9#eRV(a?~6PSH`@^$a$}LT)Z@TF;vN9~3^%_{LFFB<OUsE$N7&{GQ@W+xnB%@-kt1
zZpVuL9O!<xIckNzO^Dv{V{J>G+_1dcr|B#uRO|gLbWzFkxzR?o!BQTMcD^h3>GHEY
zxIXPQ+vEpnXUY>dTFzqKdB5J|cJsq6Uq9CHi-}ZkSah>l^{ST4?U>7JD;Mr{eL73_
ucFfgvr$eewzlz_NksW*AQ+@6q@1JbVm+nZK2==9blB1`qpUXO@geCxL=_bDb
literal 0
HcmV?d00001
--
2.49.0

View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2025 hbarral <hbarral@clipper.ens.fr>
SPDX-License-Identifier: MIT

View file

@ -0,0 +1,25 @@
From 4b0ad3a8398511843393ea92cc12f0666465a13f Mon Sep 17 00:00:00 2001
From: hbarral <hbarral@clipper.ens.fr>
Date: Sat, 7 Dec 2024 12:24:09 +0100
Subject: [PATCH 2/2] Remove annoying "FREE EDITION" badge from webapp UI This
commit should be cherry-picked on top of new Mattermost releases.
---
.../product_branding_team_edition.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/product_branding_team_edition.tsx b/webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/product_branding_team_edition.tsx
index 2773ba5184..018454c880 100644
--- a/webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/product_branding_team_edition.tsx
+++ b/webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/product_branding_team_edition.tsx
@@ -43,7 +43,6 @@ const ProductBrandingTeamEdition = (): JSX.Element => {
width={116}
height={20}
/>
- <Badge>{'FREE EDITION'}</Badge>
</ProductBrandingTeamEditionContainer>
);
};
--
2.49.0

View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2025 hbarral <hbarral@clipper.ens.fr>
SPDX-License-Identifier: MIT

View file

@ -0,0 +1,50 @@
# SPDX-FileCopyrightText: 2024 Tom Hubrecht <tom.hubrecht@dgnum.eu>
#
# SPDX-License-Identifier: EUPL-1.2
{ pkgs, ... }:
let
host = "chat.dgnum.eu";
port = 8065;
exte_login = "https://demarches.dgnum.eu/commencer/demande-compte-merle";
in
{
imports = [ ./module.nix ];
services.mattermost = {
enable = true;
inherit port;
package = pkgs.mattermostLatest.overrideAttrs (old: {
webapp = old.webapp.overrideAttrs (wOld: {
nativeBuildInputs = wOld.nativeBuildInputs ++ [
pkgs.git
pkgs.gnused
];
postPatch =
(wOld.postPatch or "")
+ ''
sed -i 's/GitLab/Clipper/g' channels/src/i18n/*.json
git apply -p2 < ${pkgs.replaceVars ./0001-Login-page-ENS-theme.patch { inherit exte_login; }}
git apply -p2 < ${./0002-Remove-annoying-FREE-EDITION-badge-from-webapp-UI.patch}
'';
});
});
siteName = "Merle [Discussions ENS]";
siteUrl = "https://${host}";
mutableConfig = true;
database.peerAuth = true;
};
dgn-web.simpleProxies.mattermost = {
inherit host port;
};
}

View file

@ -0,0 +1,996 @@
# SPDX-FileCopyrightText: The nixpkgs contributors
#
# SPDX-License-Identifier: MIT
{
config,
pkgs,
lib,
...
}:
let
inherit (lib.strings)
hasInfix
hasSuffix
escapeURL
concatStringsSep
escapeShellArg
escapeShellArgs
versionAtLeast
optionalString
;
inherit (lib.meta) getExe;
inherit (lib.lists) singleton;
inherit (lib.attrsets) mapAttrsToList recursiveUpdate optionalAttrs;
inherit (lib.options) mkOption mkPackageOption mkEnableOption;
inherit (lib.modules)
mkRenamedOptionModule
mkMerge
mkIf
mkDefault
;
inherit (lib.trivial) warnIf throwIf;
inherit (lib) types;
cfg = config.services.mattermost;
# The directory to store mutable data within dataDir.
mutableDataDir = "${cfg.dataDir}/data";
# The plugin directory. Note that this is the *pre-unpack* plugin directory,
# since Mattermost looks in mutableDataDir for a directory called "plugins".
# If Mattermost is installed with plugins defined in a Nix configuration, the plugins
# are symlinked here. Otherwise, this is a real directory and the tarballs are uploaded here.
pluginTarballDir = "${mutableDataDir}/plugins";
# We need a different unpack directory for Mattermost to sync things to at launch,
# since the above may be a symlink to the store.
pluginUnpackDir = "${mutableDataDir}/.plugins";
# Mattermost uses this as a staging directory to unpack plugins, among possibly other things.
# Ensure that it's inside mutableDataDir since it can get rather large.
tempDir = "${mutableDataDir}/tmp";
# Creates a database URI.
mkDatabaseUri =
{
scheme,
user ? null,
password ? null,
escapeUserAndPassword ? true,
host ? null,
escapeHost ? true,
port ? null,
path ? null,
query ? { },
}:
let
nullToEmpty = val: if val == null then "" else toString val;
# Converts a list of URI attrs to a query string.
toQuery = mapAttrsToList (
name: value: if value == null then null else (escapeURL name) + "=" + (escapeURL (toString value))
);
schemePart = if scheme == null then "" else "${escapeURL scheme}://";
userPart =
let
realUser = if escapeUserAndPassword then escapeURL user else user;
realPassword = if escapeUserAndPassword then escapeURL password else password;
in
if user == null && password == null then
""
else if user != null && password != null then
"${realUser}:${realPassword}"
else if user != null then
realUser
else
throw "Either user or username and password must be provided";
hostPart =
let
realHost = if escapeHost then escapeURL (nullToEmpty host) else nullToEmpty host;
in
if userPart == "" then realHost else "@" + realHost;
portPart = if port == null then "" else ":" + (toString port);
pathPart = if path == null then "" else "/" + path;
queryPart = if query == { } then "" else "?" + concatStringsSep "&" (toQuery query);
in
schemePart + userPart + hostPart + portPart + pathPart + queryPart;
database =
let
hostIsPath = hasInfix "/" cfg.database.host;
in
if cfg.database.driver == "postgres" then
if cfg.database.peerAuth then
mkDatabaseUri {
scheme = cfg.database.driver;
inherit (cfg.database) user;
path = escapeURL cfg.database.name;
query = {
host = cfg.database.socketPath;
} // cfg.database.extraConnectionOptions;
}
else
mkDatabaseUri {
scheme = cfg.database.driver;
inherit (cfg.database) user password;
host = if hostIsPath then null else cfg.database.host;
port = if hostIsPath then null else cfg.database.port;
path = escapeURL cfg.database.name;
query =
optionalAttrs hostIsPath { inherit (cfg.database) host; } // cfg.database.extraConnectionOptions;
}
else if cfg.database.driver == "mysql" then
if cfg.database.peerAuth then
mkDatabaseUri {
scheme = null;
inherit (cfg.database) user;
escapeUserAndPassword = false;
host = "unix(${cfg.database.socketPath})";
escapeHost = false;
path = escapeURL cfg.database.name;
query = cfg.database.extraConnectionOptions;
}
else
mkDatabaseUri {
scheme = null;
inherit (cfg.database) user password;
escapeUserAndPassword = false;
host =
if hostIsPath then
"unix(${cfg.database.host})"
else
"tcp(${cfg.database.host}:${toString cfg.database.port})";
escapeHost = false;
path = escapeURL cfg.database.name;
query = cfg.database.extraConnectionOptions;
}
else
throw "Invalid database driver: ${cfg.database.driver}";
mattermostPluginDerivations = map (
plugin:
pkgs.stdenvNoCC.mkDerivation {
name = "${cfg.package.name}-plugin";
installPhase = ''
runHook preInstall
mkdir -p $out/share
ln -sf ${plugin} $out/share/plugin.tar.gz
runHook postInstall
'';
dontUnpack = true;
dontPatch = true;
dontConfigure = true;
dontBuild = true;
preferLocalBuild = true;
}
) cfg.plugins;
mattermostPlugins =
if mattermostPluginDerivations == [ ] then
null
else
pkgs.stdenvNoCC.mkDerivation {
name = "${cfg.package.name}-plugins";
nativeBuildInputs = [ pkgs.autoPatchelfHook ] ++ mattermostPluginDerivations;
buildInputs = [ cfg.package ];
installPhase = ''
runHook preInstall
mkdir -p $out
plugins=(${
escapeShellArgs (map (plugin: "${plugin}/share/plugin.tar.gz") mattermostPluginDerivations)
})
for plugin in "''${plugins[@]}"; do
hash="$(sha256sum "$plugin" | awk '{print $1}')"
mkdir -p "$hash"
tar -C "$hash" -xzf "$plugin"
autoPatchelf "$hash"
GZIP_OPT=-9 tar -C "$hash" -cvzf "$out/$hash.tar.gz" .
rm -rf "$hash"
done
runHook postInstall
'';
dontUnpack = true;
dontPatch = true;
dontConfigure = true;
dontBuild = true;
preferLocalBuild = true;
};
mattermostConfWithoutPlugins = recursiveUpdate {
ServiceSettings = {
SiteURL = cfg.siteUrl;
ListenAddress = "${cfg.host}:${toString cfg.port}";
LocalModeSocketLocation = cfg.socket.path;
EnableLocalMode = cfg.socket.enable;
EnableSecurityFixAlert = cfg.telemetry.enableSecurityAlerts;
};
TeamSettings.SiteName = cfg.siteName;
SqlSettings.DriverName = cfg.database.driver;
SqlSettings.DataSource =
if cfg.database.fromEnvironment then
null
else
warnIf (!cfg.database.peerAuth && cfg.database.password != null) ''
Database password is set in Mattermost config! This password will end up in the Nix store.
You may be able to simply set the following, if the database is on the same host
and peer authentication is enabled:
services.mattermost.database.peerAuth = true;
Note that this is the default if you set system.stateVersion to 25.05 or later
and the database host is localhost.
Alternatively, you can write the following to ${
if cfg.environmentFile == null then "your environment file" else cfg.environmentFile
}:
MM_SQLSETTINGS_DATASOURCE=${database}
Then set the following options:
services.mattermost.environmentFile = "<your environment file>";
services.mattermost.database.fromEnvironment = true;
'' database;
# Note that the plugin tarball directory is not configurable, and is expected to be in FileSettings.Directory/plugins.
FileSettings.Directory = mutableDataDir;
PluginSettings.Directory = "${pluginUnpackDir}/server";
PluginSettings.ClientDirectory = "${pluginUnpackDir}/client";
LogSettings = {
FileLocation = cfg.logDir;
# Reaches out to Mattermost's servers for telemetry; disable it by default.
# https://docs.mattermost.com/configure/environment-configuration-settings.html#enable-diagnostics-and-error-reporting
EnableDiagnostics = cfg.telemetry.enableDiagnostics;
};
} cfg.settings;
mattermostConf = recursiveUpdate mattermostConfWithoutPlugins (
if mattermostPlugins == null then
{ }
else
{
PluginSettings = {
Enable = true;
};
}
);
format = pkgs.formats.json { };
finalConfig = format.generate "mattermost-config.json" mattermostConf;
in
{
disabledModules = [ "services/web-apps/mattermost.nix" ];
imports = [
(mkRenamedOptionModule
[
"services"
"mattermost"
"listenAddress"
]
[
"services"
"mattermost"
"host"
]
)
(mkRenamedOptionModule
[
"services"
"mattermost"
"localDatabaseCreate"
]
[
"services"
"mattermost"
"database"
"create"
]
)
(mkRenamedOptionModule
[
"services"
"mattermost"
"localDatabasePassword"
]
[
"services"
"mattermost"
"database"
"password"
]
)
(mkRenamedOptionModule
[
"services"
"mattermost"
"localDatabaseUser"
]
[
"services"
"mattermost"
"database"
"user"
]
)
(mkRenamedOptionModule
[
"services"
"mattermost"
"localDatabaseName"
]
[
"services"
"mattermost"
"database"
"name"
]
)
(mkRenamedOptionModule
[
"services"
"mattermost"
"extraConfig"
]
[
"services"
"mattermost"
"settings"
]
)
(mkRenamedOptionModule
[
"services"
"mattermost"
"statePath"
]
[
"services"
"mattermost"
"dataDir"
]
)
];
options = {
services.mattermost = {
enable = mkEnableOption "Mattermost chat server";
package = mkPackageOption pkgs "mattermost" { };
siteUrl = mkOption {
type = types.str;
example = "https://chat.example.com";
description = ''
URL this Mattermost instance is reachable under, without trailing slash.
'';
};
siteName = mkOption {
type = types.str;
default = "Mattermost";
description = "Name of this Mattermost site.";
};
host = mkOption {
type = types.str;
default = "127.0.0.1";
example = "0.0.0.0";
description = ''
Host or address that this Mattermost instance listens on.
'';
};
port = mkOption {
type = types.port;
default = 8065;
description = ''
Port for Mattermost server to listen on.
'';
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/mattermost";
description = ''
Mattermost working directory.
'';
};
socket = {
enable = mkEnableOption "Mattermost control socket";
path = mkOption {
type = types.path;
default = "${cfg.dataDir}/mattermost.sock";
defaultText = ''''${config.mattermost.dataDir}/mattermost.sock'';
description = ''
Default location for the Mattermost control socket used by `mmctl`.
'';
};
export = mkEnableOption "Export socket control to system environment variables";
};
logDir = mkOption {
type = types.path;
default =
if versionAtLeast config.system.stateVersion "25.05" then
"/var/log/mattermost"
else
"${cfg.dataDir}/logs";
defaultText = ''
if versionAtLeast config.system.stateVersion "25.05" then "/var/log/mattermost"
else "''${config.services.mattermost.dataDir}/logs";
'';
description = ''
Mattermost log directory.
'';
};
configDir = mkOption {
type = types.path;
default =
if versionAtLeast config.system.stateVersion "25.05" then
"/etc/mattermost"
else
"${cfg.dataDir}/config";
defaultText = ''
if versionAtLeast config.system.stateVersion "25.05" then
"/etc/mattermost"
else
"''${config.services.mattermost.dataDir}/config";
'';
description = ''
Mattermost config directory.
'';
};
mutableConfig = mkOption {
type = types.bool;
default = false;
description = ''
Whether the Mattermost config.json is writeable by Mattermost.
Most of the settings can be edited in the system console of
Mattermost if this option is enabled. A template config using
the options specified in services.mattermost will be generated
but won't be overwritten on changes or rebuilds.
If this option is disabled, persistent changes in the system
console won't be possible (the default). If a config.json is
present, it will be overwritten at service start!
'';
};
preferNixConfig = mkOption {
type = types.bool;
default = versionAtLeast config.system.stateVersion "25.05";
defaultText = ''
versionAtLeast config.system.stateVersion "25.05";
'';
description = ''
If both mutableConfig and this option are set, the Nix configuration
will take precedence over any settings configured in the server
console.
'';
};
plugins = mkOption {
type = with types; listOf (either path package);
default = [ ];
example = "[ ./com.github.moussetc.mattermost.plugin.giphy-2.0.0.tar.gz ]";
description = ''
Plugins to add to the configuration. Overrides any installed if non-null.
This is a list of paths to .tar.gz files or derivations evaluating to
.tar.gz files. You can use `mattermost.buildPlugin` to build plugins;
see the NixOS documentation for more details.
'';
};
pluginsBundle = mkOption {
type = with types; nullOr package;
default = mattermostPlugins;
defaultText = ''
All entries in {config}`services.mattermost.plugins`, repacked
'';
description = ''
Derivation building to a directory of plugin tarballs.
This overrides {option}`services.mattermost.plugins` if provided.
'';
};
telemetry = {
enableSecurityAlerts = mkOption {
type = types.bool;
default = true;
description = ''
True if we should enable security update checking. This reaches out to Mattermost's servers:
https://docs.mattermost.com/manage/telemetry.html#security-update-check-feature
'';
};
enableDiagnostics = mkOption {
type = types.bool;
default = false;
description = ''
True if we should enable sending diagnostic data. This reaches out to Mattermost's servers:
https://docs.mattermost.com/manage/telemetry.html#error-and-diagnostics-reporting-feature
'';
};
};
environment = mkOption {
type = with types; attrsOf (either int str);
default = { };
description = ''
Extra environment variables to export to the Mattermost process
from the systemd unit configuration.
'';
example = {
MM_SERVICESETTINGS_SITEURL = "http://example.com";
};
};
environmentFile = mkOption {
type = with types; nullOr path;
default = null;
description = ''
Environment file (see {manpage}`systemd.exec(5)`
"EnvironmentFile=" section for the syntax) which sets config options
for mattermost (see [the Mattermost documentation](https://docs.mattermost.com/configure/configuration-settings.html#environment-variables)).
Settings defined in the environment file will overwrite settings
set via Nix or via the {option}`services.mattermost.extraConfig`
option.
Useful for setting config options without their value ending up in the
(world-readable) Nix store, e.g. for a database password.
'';
};
database = {
driver = mkOption {
type = types.enum [
"postgres"
"mysql"
];
default = "postgres";
description = ''
The database driver to use (Postgres or MySQL).
'';
};
create = mkOption {
type = types.bool;
default = true;
description = ''
Create a local PostgreSQL or MySQL database for Mattermost automatically.
'';
};
peerAuth = mkOption {
type = types.bool;
default = versionAtLeast config.system.stateVersion "25.05" && cfg.database.host == "localhost";
defaultText = ''
versionAtLeast config.system.stateVersion "25.05" && config.services.mattermost.database.host == "localhost"
'';
description = ''
If set, will use peer auth instead of connecting to a Postgres server.
Use services.mattermost.database.socketPath to configure the socket path.
'';
};
socketPath = mkOption {
type = types.path;
default =
if cfg.database.driver == "postgres" then "/run/postgresql" else "/run/mysqld/mysqld.sock";
defaultText = ''
if config.services.mattermost.database.driver == "postgres" then "/run/postgresql" else "/run/mysqld/mysqld.sock";
'';
description = ''
The database (Postgres or MySQL) socket path.
'';
};
fromEnvironment = mkOption {
type = types.bool;
default = false;
description = ''
Use services.mattermost.environmentFile to configure the database instead of writing the database URI
to the Nix store. Useful if you use password authentication with peerAuth set to false.
'';
};
name = mkOption {
type = types.str;
default = "mattermost";
description = ''
Local Mattermost database name.
'';
};
host = mkOption {
type = types.str;
default = "localhost";
example = "127.0.0.1";
description = ''
Host to use for the database. Can also be set to a path if you'd like to connect
to a socket using a username and password.
'';
};
port = mkOption {
type = types.port;
default = if cfg.database.driver == "postgres" then 5432 else 3306;
defaultText = ''
if config.services.mattermost.database.type == "postgres" then 5432 else 3306
'';
example = 3306;
description = ''
Port to use for the database.
'';
};
user = mkOption {
type = types.str;
default = "mattermost";
description = ''
Local Mattermost database username.
'';
};
password = mkOption {
type = types.str;
default = "mmpgsecret";
description = ''
Password for local Mattermost database user. If set and peerAuth is not true,
will cause a warning nagging you to use environmentFile instead since it will
end up in the Nix store.
'';
};
extraConnectionOptions = mkOption {
type = with types; attrsOf (either int str);
default =
if cfg.database.driver == "postgres" then
{
sslmode = "disable";
connect_timeout = 60;
}
else if cfg.database.driver == "mysql" then
{
charset = "utf8mb4,utf8";
writeTimeout = "60s";
readTimeout = "60s";
}
else
throw "Invalid database driver ${cfg.database.driver}";
defaultText = ''
if config.mattermost.database.driver == "postgres" then
{
sslmode = "disable";
connect_timeout = 60;
}
else if config.mattermost.database.driver == "mysql" then
{
charset = "utf8mb4,utf8";
writeTimeout = "60s";
readTimeout = "60s";
}
else
throw "Invalid database driver";
'';
description = ''
Extra options that are placed in the connection URI's query parameters.
'';
};
};
user = mkOption {
type = types.str;
default = "mattermost";
description = ''
User which runs the Mattermost service.
'';
};
group = mkOption {
type = types.str;
default = "mattermost";
description = ''
Group which runs the Mattermost service.
'';
};
settings = mkOption {
inherit (format) type;
default = { };
description = ''
Additional configuration options as Nix attribute set in config.json schema.
'';
};
matterircd = {
enable = mkEnableOption "Mattermost IRC bridge";
package = mkPackageOption pkgs "matterircd" { };
parameters = mkOption {
type = types.listOf types.str;
default = [ ];
example = [
"-mmserver chat.example.com"
"-bind [::]:6667"
];
description = ''
Set commandline parameters to pass to matterircd. See
<https://github.com/42wim/matterircd#usage> for more information.
'';
};
};
};
};
config = mkMerge [
(mkIf cfg.enable {
users.users = {
${cfg.user} = {
inherit (cfg) group;
uid = mkIf (cfg.user == "mattermost") config.ids.uids.mattermost;
home = cfg.dataDir;
isSystemUser = true;
packages = [ cfg.package ];
};
};
users.groups = {
${cfg.group} = {
gid = mkIf (cfg.group == "mattermost") config.ids.gids.mattermost;
};
};
services.postgresql = mkIf (cfg.database.driver == "postgres" && cfg.database.create) {
enable = true;
ensureDatabases = singleton cfg.database.name;
ensureUsers = singleton {
name =
throwIf
(cfg.database.peerAuth && (cfg.database.user != cfg.user || cfg.database.name != cfg.database.user))
''
Mattermost database peer auth is enabled and the user, database user, or database name mismatch.
Peer authentication will not work.
''
cfg.database.user;
ensureDBOwnership = true;
};
};
services.mysql = mkIf (cfg.database.driver == "mysql" && cfg.database.create) {
enable = true;
package = mkDefault pkgs.mariadb;
ensureDatabases = singleton cfg.database.name;
ensureUsers = singleton {
name = cfg.database.user;
ensurePermissions = {
"${cfg.database.name}.*" = "ALL PRIVILEGES";
};
};
settings = rec {
mysqld = {
collation-server = mkDefault "utf8mb4_general_ci";
init-connect = mkDefault "SET NAMES utf8mb4";
character-set-server = mkDefault "utf8mb4";
};
mysqld_safe = mysqld;
};
};
environment = {
variables = mkIf cfg.socket.export {
MMCTL_LOCAL = "true";
MMCTL_LOCAL_SOCKET_PATH = cfg.socket.path;
};
};
systemd.tmpfiles.rules =
[
"d ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} - -"
"d ${cfg.logDir} 0750 ${cfg.user} ${cfg.group} - -"
"d ${cfg.configDir} 0750 ${cfg.user} ${cfg.group} - -"
"d ${mutableDataDir} 0750 ${cfg.user} ${cfg.group} - -"
# Make sure tempDir exists and is not a symlink.
"R- ${tempDir} - - - - -"
"d= ${tempDir} 0750 ${cfg.user} ${cfg.group} - -"
# Ensure that pluginUnpackDir is a directory.
# Don't remove or clean it out since it should be persistent, as this is where plugins are unpacked.
"d= ${pluginUnpackDir} 0750 ${cfg.user} ${cfg.group} - -"
# Ensure that the plugin directories exist.
"d= ${mattermostConf.PluginSettings.Directory} 0750 ${cfg.user} ${cfg.group} - -"
"d= ${mattermostConf.PluginSettings.ClientDirectory} 0750 ${cfg.user} ${cfg.group} - -"
# Link in some of the immutable data directories.
"L+ ${cfg.dataDir}/bin - - - - ${cfg.package}/bin"
"L+ ${cfg.dataDir}/fonts - - - - ${cfg.package}/fonts"
"L+ ${cfg.dataDir}/i18n - - - - ${cfg.package}/i18n"
"L+ ${cfg.dataDir}/templates - - - - ${cfg.package}/templates"
"L+ ${cfg.dataDir}/client - - - - ${cfg.package}/client"
]
++ (
if cfg.pluginsBundle == null then
# Create the plugin tarball directory to allow plugin uploads.
[
"d= ${pluginTarballDir} 0750 ${cfg.user} ${cfg.group} - -"
]
else
# Symlink the plugin tarball directory, removing anything existing, since it's managed by Nix.
[ "L+ ${pluginTarballDir} - - - - ${cfg.pluginsBundle}" ]
);
systemd.services.mattermost = rec {
description = "Mattermost chat service";
wantedBy = [ "multi-user.target" ];
after = mkMerge [
[ "network.target" ]
(mkIf (cfg.database.driver == "postgres" && cfg.database.create) [ "postgresql.service" ])
(mkIf (cfg.database.driver == "mysql" && cfg.database.create) [ "mysql.service" ])
];
requires = after;
environment = mkMerge [
{
# Use tempDir as this can get rather large, especially if Mattermost unpacks a large number of plugins.
TMPDIR = tempDir;
}
cfg.environment
];
preStart =
''
dataDir=${escapeShellArg cfg.dataDir}
configDir=${escapeShellArg cfg.configDir}
logDir=${escapeShellArg cfg.logDir}
package=${escapeShellArg cfg.package}
nixConfig=${escapeShellArg finalConfig}
''
+ optionalString (versionAtLeast config.system.stateVersion "25.05") ''
# Migrate configs in the pre-25.05 directory structure.
oldConfig="$dataDir/config/config.json"
newConfig="$configDir/config.json"
if [ "$oldConfig" != "$newConfig" ] && [ -f "$oldConfig" ] && [ ! -f "$newConfig" ]; then
# Migrate the legacy config location to the new config location
echo "Moving legacy config at $oldConfig to $newConfig" >&2
mkdir -p "$configDir"
mv "$oldConfig" "$newConfig"
touch "$configDir/.initial-created"
fi
# Logs too.
oldLogs="$dataDir/logs"
newLogs="$logDir"
if [ "$oldLogs" != "$newLogs" ] && [ -d "$oldLogs" ] && [ ! -f "$newLogs/.initial-created" ]; then
# Migrate the legacy log location to the new log location.
# Allow this to fail if there aren't any logs to move.
echo "Moving legacy logs at $oldLogs to $newLogs" >&2
mkdir -p "$newLogs"
mv "$oldLogs"/* "$newLogs" || true
touch "$newLogs/.initial-created"
fi
''
+ optionalString (!cfg.mutableConfig) ''
${getExe pkgs.jq} -s '.[0] * .[1]' "$package/config/config.json" "$nixConfig" > "$configDir/config.json"
''
+ optionalString cfg.mutableConfig ''
if [ ! -e "$configDir/.initial-created" ]; then
${getExe pkgs.jq} -s '.[0] * .[1]' "$package/config/config.json" "$nixConfig" > "$configDir/config.json"
touch "$configDir/.initial-created"
fi
''
+ optionalString (cfg.mutableConfig && cfg.preferNixConfig) ''
echo "$(${getExe pkgs.jq} -s '.[0] * .[1]' "$configDir/config.json" "$nixConfig")" > "$configDir/config.json"
'';
serviceConfig = mkMerge [
{
User = cfg.user;
Group = cfg.group;
ExecStart = "${getExe cfg.package} --config ${cfg.configDir}/config.json";
ReadWritePaths = [
cfg.dataDir
cfg.logDir
cfg.configDir
];
UMask = "0027";
Restart = "always";
RestartSec = 10;
LimitNOFILE = 49152;
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RestrictNamespaces = true;
RestrictSUIDSGID = true;
EnvironmentFile = cfg.environmentFile;
WorkingDirectory = cfg.dataDir;
}
(mkIf (cfg.dataDir == "/var/lib/mattermost") {
StateDirectory = baseNameOf cfg.dataDir;
StateDirectoryMode = "0750";
})
(mkIf (cfg.logDir == "/var/log/mattermost") {
LogsDirectory = baseNameOf cfg.logDir;
LogsDirectoryMode = "0750";
})
(mkIf (cfg.configDir == "/etc/mattermost") {
ConfigurationDirectory = baseNameOf cfg.configDir;
ConfigurationDirectoryMode = "0750";
})
];
unitConfig.JoinsNamespaceOf = mkMerge [
(mkIf (cfg.database.driver == "postgres" && cfg.database.create) [ "postgresql.service" ])
(mkIf (cfg.database.driver == "mysql" && cfg.database.create) [ "mysql.service" ])
];
};
assertions = [
{
# Make sure the URL doesn't have a trailing slash
assertion = !(hasSuffix "/" cfg.siteUrl);
message = ''
services.mattermost.siteUrl should not have a trailing "/".
'';
}
{
# Make sure this isn't a host/port pair
assertion = !(hasInfix ":" cfg.host && !(hasInfix "[" cfg.host) && !(hasInfix "]" cfg.host));
message = ''
services.mattermost.host should not include a port. Use services.mattermost.host for the address
or hostname, and services.mattermost.port to specify the port separately.
'';
}
];
})
(mkIf cfg.matterircd.enable {
systemd.services.matterircd = {
description = "Mattermost IRC bridge service";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = "nobody";
Group = "nogroup";
ExecStart = "${getExe cfg.matterircd.package} ${escapeShellArgs cfg.matterircd.parameters}";
WorkingDirectory = "/tmp";
PrivateTmp = true;
Restart = "always";
RestartSec = "5";
};
};
})
];
meta.maintainers = with lib.maintainers; [ numinit ];
}

View file

@ -7,6 +7,7 @@
versions = [
# Supported nixpkgs versions
"unstable"
"22.11"
"23.11"
"24.05"
"24.11"