Skip to content

Commit

Permalink
mattermost: support plugins with refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
numinit committed Dec 29, 2022
1 parent dab0d24 commit 6b42e85
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 13 deletions.
111 changes: 105 additions & 6 deletions nixos/modules/services/web-apps/mattermost.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,90 @@ with lib;
let
cfg = config.services.mattermost;

mattermostConf = recursiveUpdate
# The directory to store mutable data within dataDir.
mutableDataDir = "${cfg.dataDir}/data";

# The plugin directory. Note that this is the *post-unpack* plugin directory,
# since Mattermost unpacks plugins to put them there. (Hence, mutable data.)
pluginDir = "${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";

mattermostPluginDerivations = with pkgs;
map (plugin: stdenv.mkDerivation {
name = "mattermost-plugin";
installPhase = ''
mkdir -p $out/share
cp ${plugin} $out/share/plugin.tar.gz
'';
dontUnpack = true;
dontPatch = true;
dontConfigure = true;
dontBuild = true;
preferLocalBuild = true;
}) cfg.plugins;

mattermostPlugins = with pkgs;
if mattermostPluginDerivations == [] then null
else stdenv.mkDerivation {
name = "${cfg.package.name}-plugins";
nativeBuildInputs = [
autoPatchelfHook
] ++ mattermostPluginDerivations;
buildInputs = [
cfg.package
];
installPhase = ''
mkdir -p $out
plugins=(${escapeShellArgs (map (plugin: "${plugin}/share/plugin.tar.gz") mattermostPluginDerivations)})
for plugin in "''${plugins[@]}"; do
hash="$(sha256sum "$plugin" | cut -d' ' -f1)"
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
'';

dontUnpack = true;
dontPatch = true;
dontConfigure = true;
dontBuild = true;
preferLocalBuild = true;
};

mattermostConfWithoutPlugins = recursiveUpdate
{
ServiceSettings.SiteURL = cfg.siteUrl;
ServiceSettings.ListenAddress = "${cfg.listenAddress}:${toString cfg.port}";
TeamSettings.SiteName = cfg.siteName;
SqlSettings.DriverName = "postgres";
SqlSettings.DataSource = "postgres:///${cfg.database.name}?host=/run/postgresql";
FileSettings.Directory = cfg.dataDir;
PluginSettings.Directory = "${cfg.dataDir}/plugins/server";
PluginSettings.ClientDirectory = "${cfg.dataDir}/plugins/client";
PluginSettings.Directory = "${pluginDir}/server";
PluginSettings.ClientDirectory = "${pluginDir}/client";
LogSettings.FileLocation = cfg.logDir;
}
cfg.settings;

mattermostConf = recursiveUpdate
mattermostConfWithoutPlugins
(
if mattermostPlugins == null then {}
else {
PluginSettings = {
Enable = true;
};
}
);

mattermostConfJSON = (pkgs.formats.json { }).generate "mattermost-config.json" mattermostConf;
in
{
imports = [
(mkRemovedOptionModule [ "services" "mattermost" "plugins" ] "Plugin support has been removed as its fundamentally to get it working without patchelfing and patching Mattermost's Go code.")
(mkRemovedOptionModule [ "services" "mattermost" "matterircd" "enable" ] "This option has been removed as it shouldn't be part of the Mattermost module.")
(mkRemovedOptionModule [ "services" "mattermost" "matterircd" "package" ] "This option has been removed as it shouldn't be part of the Mattermost module.")
(mkRemovedOptionModule [ "services" "mattermost" "matterircd" "parameters" ] "This option has been removed as it shouldn't be part of the Mattermost module.")
Expand Down Expand Up @@ -92,6 +157,17 @@ in
};
};

plugins = mkOption {
type = types.listOf (types.oneOf [types.path types.package]);
default = [];
example = "[ ./com.github.moussetc.mattermost.plugin.giphy-2.0.0.tar.gz ]";
description = lib.mdDoc ''
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.
'';
};

siteUrl = mkOption {
type = types.str;
example = "https://chat.example.com";
Expand All @@ -116,7 +192,7 @@ in
but won't be overwritten on changes or rebuilds.
If this option is disabled, changes in the system console won't
be possible (default). If an config.json is present, it will be
be possible (default). If a config.json is present, it will be
overwritten!
'';
};
Expand Down Expand Up @@ -161,20 +237,43 @@ in
"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} - -"

# Remove and recreate tempDir.
"R ${tempDir} - - - - -"
"d ${tempDir} 0750 ${cfg.user} ${cfg.group} - -"

# Ensure that pluginDir is a directory, as it could be a symlink on prior versions.
"r ${pluginDir} - - - - -"
"d ${pluginDir} 0750 ${cfg.user} ${cfg.group} - -"
"d ${mattermostConf.PluginSettings.Directory} 0750 ${cfg.user} ${cfg.group} - -"
"d ${mattermostConf.PluginSettings.ClientDirectory} 0750 ${cfg.user} ${cfg.group} - -"

"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 mattermostPlugins == null then
# Create the plugin tarball directory if it's a symlink.
[
"r ${cfg.dataDir}/plugins - - - - -"
"d ${cfg.dataDir}/plugins 0750 ${cfg.user} ${cfg.group} - -"
]
else
# Symlink the plugin tarball directory.
["L+ ${cfg.dataDir}/plugins - - - - ${mattermostPlugins}"]
);

systemd.services.mattermost = {
description = "Mattermost chat service";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "postgresql.service" ];
requires = [ "network.target" "postgresql.service" ];

# Use tempDir as this can get rather large, especially if Mattermost unpacks a large number of plugins.
environment.TMPDIR = tempDir;

preStart = lib.optionalString (!cfg.mutableConfig) ''
${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.configDir}/config.json"
'' + lib.optionalString cfg.mutableConfig ''
Expand Down
55 changes: 48 additions & 7 deletions nixos/tests/mattermost.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
mostlyMutable = makeMattermost {
mutableConfig = true;
preferNixConfig = true;
settings.PluginSettings.AutomaticPrepackagedPlugins = false;
plugins = [
(pkgs.fetchurl {
url = "https://github.com/matterpoll/matterpoll/releases/download/v1.5.0/com.github.matterpoll.matterpoll-1.5.0.tar.gz";
sha256 = "1nvgxfza2pfc9ggrr6l3m3hadfy93iid05h3spbhvfh9s1vnmx00";
})
];
};
immutable = makeMattermost {
mutableConfig = false;
Expand All @@ -47,30 +54,44 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:

testScript =
let
expectConfig = jqExpression: pkgs.writeShellScript "expect-config" ''
expectMattermostUp = pkgs.writeShellScript "expect-mattermost-up" ''
set -euo pipefail
echo "Expecting config to match: "${lib.escapeShellArg jqExpression} >&2
curl ${lib.escapeShellArg url} >/dev/null
'';

expectConfig = jqExpression: pkgs.writeShellScript "expect-config" ''
set -euo pipefail
config="$(curl ${lib.escapeShellArg "${url}/api/v4/config/client?format=old"})"
echo "Config: $(echo "$config" | ${pkgs.jq}/bin/jq)" >&2
[[ "$(echo "$config" | ${pkgs.jq}/bin/jq -r ${lib.escapeShellArg ".SiteName == $siteName and .Version == ($mattermostName / $sep)[-1] and (${jqExpression})"} --arg siteName ${lib.escapeShellArg siteName} --arg mattermostName ${lib.escapeShellArg pkgs.mattermost.name} --arg sep '-')" = "true" ]]
[[ "$(echo "$config" | ${pkgs.jq}/bin/jq -r ${lib.escapeShellArg ".SiteName == $siteName and .Version == ($mattermostName / $sep)[-1] and (${jqExpression})"} --arg siteName ${lib.escapeShellArg siteName} --arg mattermostName ${lib.escapeShellArg pkgs.mattermost.name} --arg sep '-')" == "true" ]]
'';

setConfig = jqExpression: pkgs.writeShellScript "set-config" ''
set -euo pipefail
mattermostConfig=/etc/mattermost/config.json
newConfig="$(${pkgs.jq}/bin/jq -r ${lib.escapeShellArg jqExpression} $mattermostConfig)"
sudo -u mattermost echo "$newConfig" > "$mattermostConfig"
truncate -s 0 "$mattermostConfig"
echo "$newConfig" >> "$mattermostConfig"
'';

expectPlugins = jqExpressionOrStatusCode: pkgs.writeShellScript "expect-plugins" ''
set -euo pipefail
${if builtins.isInt jqExpressionOrStatusCode then ''
code="$(curl -s -o /dev/null -w "%{http_code}" ${lib.escapeShellArg "${url}/api/v4/plugins/webapp"})"
[[ "$code" == ${lib.escapeShellArg (toString jqExpressionOrStatusCode)} ]]
'' else ''
plugins="$(curl ${lib.escapeShellArg "${url}/api/v4/plugins/webapp"})"
[[ "$(echo "$plugins" | ${pkgs.jq}/bin/jq -r ${lib.escapeShellArg "(${jqExpressionOrStatusCode})"})" == "true" ]]
''}
'';
in
''
start_all()
## Mutable node tests ##
mutable.start()
mutable.wait_for_unit("mattermost.service")
mutable.wait_for_open_port(8065)
# Get the initial config
mutable.succeed("${expectMattermostUp}")
mutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://search.nixos.org"''}")
# Edit the config
Expand All @@ -82,27 +103,43 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
# AboutLink and HelpLink should be changed
mutable.succeed("${expectConfig ''.AboutLink == "https://mattermost.com" and .HelpLink == "https://nixos.org/nixos/manual"''}")
# No plugins.
mutable.succeed("${expectPlugins ''length == 0''}")
mutable.shutdown()
## Mostly mutable node tests ##
mostlyMutable.start()
mostlyMutable.wait_for_unit("mattermost.service")
mostlyMutable.wait_for_open_port(8065)
# Get the initial config
mostlyMutable.succeed("${expectMattermostUp}")
mostlyMutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org"''}")
# No plugins.
mostlyMutable.succeed("${expectPlugins ''length == 0''}")
# Edit the config
mostlyMutable.succeed("${setConfig ''.SupportSettings.AboutLink = "https://mattermost.com"''}")
mostlyMutable.succeed("${setConfig ''.SupportSettings.HelpLink = "https://nixos.org/nixos/manual"''}")
mostlyMutable.succeed("${setConfig ''.PluginSettings.PluginStates."com.github.matterpoll.matterpoll".Enable = true''}")
mostlyMutable.systemctl("restart mattermost.service")
mostlyMutable.wait_for_open_port(8065)
# AboutLink should be overridden by NixOS configuration; HelpLink should be what we set above
mostlyMutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://nixos.org/nixos/manual"''}")
# Single plugin that's now enabled.
mostlyMutable.succeed("${expectPlugins ''length == 1''}")
mostlyMutable.shutdown()
## Immutable node tests ##
immutable.start()
immutable.wait_for_unit("mattermost.service")
immutable.wait_for_open_port(8065)
# Get the initial config
immutable.succeed("${expectMattermostUp}")
immutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://search.nixos.org"''}")
# Edit the config
Expand All @@ -113,5 +150,9 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
# Our edits should be ignored on restart
immutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://search.nixos.org"''}")
# No plugins.
immutable.succeed("${expectPlugins ''length == 0''}")
immutable.shutdown()
'';
})

0 comments on commit 6b42e85

Please sign in to comment.