feat: add mautrix mattermost
This commit is contained in:
parent
7882b04089
commit
d725df2606
7 changed files with 570 additions and 42 deletions
400
utils/modules/mautrix-mattermost.nix
Normal file
400
utils/modules/mautrix-mattermost.nix
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.mautrix-mattermost;
|
||||
dataDir = cfg.dataDir;
|
||||
format = pkgs.formats.yaml { };
|
||||
|
||||
registrationFile = "${dataDir}/mattermost-registration.yaml";
|
||||
|
||||
settingsFile = "${dataDir}/config.yaml";
|
||||
settingsFileUnformatted = format.generate "mattermost-config-unsubstituted.yaml" cfg.settings;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.mautrix-mattermost = {
|
||||
enable = lib.mkEnableOption "Mautrix-Mattermost, a Matrix-Mattermost puppeting/relay-bot bridge";
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.mautrix-mattermost;
|
||||
defaultText = lib.literalExpression "pkgs.mautrix-mattermost";
|
||||
description = "The mautrix-mattermost package to use.";
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = format.type;
|
||||
|
||||
config = {
|
||||
_module.args = { inherit cfg lib; };
|
||||
};
|
||||
|
||||
options = {
|
||||
homeserver = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {
|
||||
software = "standard";
|
||||
status_endpoint = null;
|
||||
message_send_checkpoint_endpoint = null;
|
||||
async_media = false;
|
||||
websocket = false;
|
||||
ping_interval_seconds = 0;
|
||||
};
|
||||
description = ''
|
||||
Homeserver configuration.
|
||||
See the mautrix-mattermost example-config.yaml for more information.
|
||||
'';
|
||||
};
|
||||
|
||||
appservice = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {
|
||||
address = "http://localhost:29335";
|
||||
hostname = "0.0.0.0";
|
||||
port = 29335;
|
||||
database = {
|
||||
type = "sqlite3";
|
||||
uri = "file:${dataDir}/mautrix-mattermost.db?_txlock=immediate";
|
||||
max_open_conns = 20;
|
||||
max_idle_conns = 2;
|
||||
max_conn_idle_time = null;
|
||||
max_conn_lifetime = null;
|
||||
};
|
||||
id = "mattermost";
|
||||
bot = {
|
||||
username = "mattermostbot";
|
||||
displayname = "Mattermost bridge bot";
|
||||
avatar = "";
|
||||
};
|
||||
ephemeral_events = true;
|
||||
async_transactions = false;
|
||||
as_token = "This value is generated when generating the registration";
|
||||
hs_token = "This value is generated when generating the registration";
|
||||
};
|
||||
description = ''
|
||||
Appservice configuration.
|
||||
See the mautrix-mattermost example-config.yaml for more information.
|
||||
'';
|
||||
};
|
||||
|
||||
bridge = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {
|
||||
username_template = "mattermost_{{.}}";
|
||||
command_prefix = "!mm";
|
||||
double_puppet_server_map = { };
|
||||
double_puppet_allow_discovery = false;
|
||||
login_shared_secret_map = { };
|
||||
management_room_text = {
|
||||
welcome = "Hello, I'm a Mattermost bridge bot.";
|
||||
welcome_connected = "Use `help` for help.";
|
||||
welcome_unconnected = "Use `help` for help or `login` to log in.";
|
||||
additional_help = "";
|
||||
};
|
||||
encryption = {
|
||||
allow = false;
|
||||
default = false;
|
||||
appservice = false;
|
||||
require = false;
|
||||
allow_key_sharing = false;
|
||||
plaintext_mentions = false;
|
||||
delete_keys = {
|
||||
delete_outbound_on_ack = false;
|
||||
dont_store_outbound = false;
|
||||
ratchet_on_decrypt = false;
|
||||
delete_fully_used_on_decrypt = false;
|
||||
delete_prev_on_new_session = false;
|
||||
delete_on_device_delete = false;
|
||||
periodically_delete_expired = false;
|
||||
delete_outdated_inbound = false;
|
||||
};
|
||||
verification_levels = {
|
||||
receive = "unverified";
|
||||
send = "unverified";
|
||||
share = "cross-signed-tofu";
|
||||
};
|
||||
rotation = {
|
||||
enable_custom = false;
|
||||
milliseconds = 604800000;
|
||||
messages = 100;
|
||||
disable_device_change_key_rotation = false;
|
||||
};
|
||||
};
|
||||
provisioning = {
|
||||
prefix = "/_matrix/provision";
|
||||
shared_secret = "generate";
|
||||
debug_endpoints = false;
|
||||
};
|
||||
permissions = {
|
||||
"*" = "relay";
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Bridge configuration.
|
||||
See the mautrix-mattermost example-config.yaml for more information.
|
||||
'';
|
||||
};
|
||||
logging = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {
|
||||
min_level = "info";
|
||||
writers = lib.singleton {
|
||||
type = "stdout";
|
||||
format = "pretty-colored";
|
||||
time_format = " ";
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Logging configuration.
|
||||
See the mautrix-mattermost example-config.yaml for more information.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
description = ''
|
||||
{file}`config.yaml` configuration as a Nix attribute set.
|
||||
'';
|
||||
};
|
||||
|
||||
registerToSynapse = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.matrix-synapse.enable;
|
||||
defaultText = lib.literalExpression "config.services.matrix-synapse.enable";
|
||||
description = ''
|
||||
Whether to add the bridge's app service registration file to
|
||||
`services.matrix-synapse.settings.app_service_config_files`.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/var/lib/mautrix-mattermost";
|
||||
description = ''
|
||||
Directory to store the bridge's configuration and database files.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
File containing environment variables to substitute when copying the configuration
|
||||
out of Nix store to the `services.mautrix-mattermost.dataDir`.
|
||||
Can be used for storing the secrets without making them available in the Nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
serviceUnit = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
readOnly = true;
|
||||
default = "mautrix-mattermost.service";
|
||||
description = "The systemd unit for the bridge service.";
|
||||
};
|
||||
|
||||
registrationServiceUnit = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
readOnly = true;
|
||||
default = "mautrix-mattermost-registration.service";
|
||||
description = "The registration service that generates the registration file.";
|
||||
};
|
||||
|
||||
serviceDependencies = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [
|
||||
cfg.registrationServiceUnit
|
||||
]
|
||||
++ (lib.lists.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit)
|
||||
++ (lib.lists.optional (config.services ? matrix-conduit && config.services.matrix-conduit.enable) "matrix-conduit.service")
|
||||
++ (lib.lists.optional (config.services ? dendrite && config.services.dendrite.enable) "dendrite.service");
|
||||
description = ''
|
||||
List of Systemd services to require and wait for when starting the application service.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
cfg.settings.homeserver.domain or "" != "" && cfg.settings.homeserver.address or "" != "";
|
||||
message = ''
|
||||
The options with information about the homeserver:
|
||||
`services.mautrix-mattermost.settings.homeserver.domain` and
|
||||
`services.mautrix-mattermost.settings.homeserver.address` have to be set.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = cfg.settings.bridge.permissions or { } != { };
|
||||
message = ''
|
||||
The option `services.mautrix-mattermost.settings.bridge.permissions` has to be set.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
users.users.mautrix-mattermost = {
|
||||
isSystemUser = true;
|
||||
group = "mautrix-mattermost";
|
||||
extraGroups = [ "mautrix-mattermost-registration" ];
|
||||
home = dataDir;
|
||||
description = "Mautrix-Mattermost bridge user";
|
||||
};
|
||||
|
||||
users.groups.mautrix-mattermost = { };
|
||||
users.groups.mautrix-mattermost-registration = {
|
||||
members = lib.lists.optional config.services.matrix-synapse.enable "matrix-synapse";
|
||||
};
|
||||
|
||||
services.matrix-synapse = lib.mkIf cfg.registerToSynapse {
|
||||
settings.app_service_config_files = [ registrationFile ];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.dataDir} 770 mautrix-mattermost mautrix-mattermost -"
|
||||
];
|
||||
|
||||
systemd.services = {
|
||||
matrix-synapse = lib.mkIf cfg.registerToSynapse {
|
||||
serviceConfig.SupplementaryGroups = [ "mautrix-mattermost-registration" ];
|
||||
wants = [ "mautrix-mattermost-registration.service" ];
|
||||
after = [ "mautrix-mattermost-registration.service" ];
|
||||
};
|
||||
|
||||
mautrix-mattermost-registration = {
|
||||
description = "Mautrix-Mattermost registration generation service";
|
||||
|
||||
wantedBy = lib.mkIf cfg.registerToSynapse [ "multi-user.target" ];
|
||||
before = lib.mkIf cfg.registerToSynapse [ "matrix-synapse.service" ];
|
||||
|
||||
path = [
|
||||
pkgs.yq
|
||||
pkgs.envsubst
|
||||
cfg.package
|
||||
];
|
||||
|
||||
script = ''
|
||||
# substitute the settings file by environment variables
|
||||
# in this case read from EnvironmentFile
|
||||
rm -f '${settingsFile}'
|
||||
old_umask=$(umask)
|
||||
umask 0177
|
||||
envsubst \
|
||||
-o '${settingsFile}' \
|
||||
-i '${settingsFileUnformatted}'
|
||||
config_has_tokens=$(yq '.appservice | has("as_token") and has("hs_token")' '${settingsFile}')
|
||||
registration_already_exists=$([[ -f '${registrationFile}' ]] && echo "true" || echo "false")
|
||||
echo "There are tokens in the config: $config_has_tokens"
|
||||
echo "Registration already existed: $registration_already_exists"
|
||||
# tokens not configured from config/environment file, and registration file
|
||||
# is already generated, override tokens in config to make sure they are not lost
|
||||
if [[ $config_has_tokens == "false" && $registration_already_exists == "true" ]]; then
|
||||
echo "Copying as_token, hs_token from registration into configuration"
|
||||
yq -sY '.[0].appservice.as_token = .[1].as_token
|
||||
| .[0].appservice.hs_token = .[1].hs_token
|
||||
| .[0]' '${settingsFile}' '${registrationFile}' \
|
||||
> '${settingsFile}.tmp'
|
||||
mv '${settingsFile}.tmp' '${settingsFile}'
|
||||
fi
|
||||
# make sure --generate-registration does not affect config.yaml
|
||||
cp '${settingsFile}' '${settingsFile}.tmp'
|
||||
echo "Generating registration file"
|
||||
mautrix-mattermost \
|
||||
--generate-registration \
|
||||
--config='${settingsFile}.tmp' \
|
||||
--registration='${registrationFile}'
|
||||
rm '${settingsFile}.tmp'
|
||||
# no tokens configured, and new were just generated by generate registration for first time
|
||||
if [[ $config_has_tokens == "false" && $registration_already_exists == "false" ]]; then
|
||||
echo "Copying newly generated as_token, hs_token from registration into configuration"
|
||||
yq -sY '.[0].appservice.as_token = .[1].as_token
|
||||
| .[0].appservice.hs_token = .[1].hs_token
|
||||
| .[0]' '${settingsFile}' '${registrationFile}' \
|
||||
> '${settingsFile}.tmp'
|
||||
mv '${settingsFile}.tmp' '${settingsFile}'
|
||||
fi
|
||||
# Make sure correct tokens are in the registration file
|
||||
if [[ $config_has_tokens == "true" || $registration_already_exists == "true" ]]; then
|
||||
echo "Copying as_token, hs_token from configuration to the registration file"
|
||||
yq -sY '.[1].as_token = .[0].appservice.as_token
|
||||
| .[1].hs_token = .[0].appservice.hs_token
|
||||
| .[1]' '${settingsFile}' '${registrationFile}' \
|
||||
> '${registrationFile}.tmp'
|
||||
mv '${registrationFile}.tmp' '${registrationFile}'
|
||||
fi
|
||||
umask $old_umask
|
||||
chown :mautrix-mattermost-registration '${registrationFile}'
|
||||
chmod 640 '${registrationFile}'
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
UMask = 27;
|
||||
|
||||
User = "mautrix-mattermost";
|
||||
Group = "mautrix-mattermost";
|
||||
|
||||
SystemCallFilter = [ "@system-service" ];
|
||||
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
|
||||
ReadWritePaths = [ dataDir ];
|
||||
StateDirectory = "mautrix-mattermost";
|
||||
EnvironmentFile = cfg.environmentFile;
|
||||
};
|
||||
|
||||
restartTriggers = [ settingsFileUnformatted ];
|
||||
};
|
||||
|
||||
mautrix-mattermost = {
|
||||
description = "Mautrix-Mattermost, a Matrix-Mattermost puppeting/relaybot bridge";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
|
||||
after = [ "network-online.target" ] ++ cfg.serviceDependencies;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "mautrix-mattermost";
|
||||
Group = "mautrix-mattermost";
|
||||
PrivateUsers = true;
|
||||
Restart = "on-failure";
|
||||
RestartSec = 30;
|
||||
WorkingDirectory = dataDir;
|
||||
ExecStart = ''
|
||||
${lib.getExe cfg.package} \
|
||||
--config='${settingsFile}'
|
||||
'';
|
||||
EnvironmentFile = cfg.environmentFile;
|
||||
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
RestrictSUIDSGID = true;
|
||||
RestrictRealtime = true;
|
||||
LockPersonality = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectHostname = true;
|
||||
ProtectClock = true;
|
||||
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
SystemCallFilter = "@system-service";
|
||||
ReadWritePaths = [ cfg.dataDir ];
|
||||
};
|
||||
|
||||
restartTriggers = [ settingsFileUnformatted ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ self: super: {
|
|||
openaudible = (super.callPackage ../pkgs/openaudible.nix { });
|
||||
openmanus = (super.callPackage ../pkgs/openmanus.nix { });
|
||||
ai-mailer = self.callPackage ../pkgs/ai-mailer.nix { };
|
||||
mautrix-mattermost = self.callPackage ../pkgs/mautrix-mattermost { };
|
||||
claude-code = self.callPackage ../pkgs/claude-code { claude-code = super.claude-code; };
|
||||
|
||||
# Python packages
|
||||
|
|
|
|||
30
utils/pkgs/mautrix-mattermost/default.nix
Normal file
30
utils/pkgs/mautrix-mattermost/default.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{ lib, buildGo126Module, fetchFromGitHub, olm }:
|
||||
|
||||
buildGo126Module rec {
|
||||
pname = "mautrix-mattermost";
|
||||
version = "0-unstable-2026-03-01";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "bostrot";
|
||||
repo = "mautrix-mattermost";
|
||||
rev = "f7996f0e4acd68b24f2a1a88961712682b6017a5";
|
||||
hash = "sha256-J8CJd0tsTLHJRyRVP8fVnzsCS5VV9iXr1epA6P2Qec4=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-r4mmSEzx/oSv0OutLuXe7LwODUJaSwuQ/CNFZNqw5+c=";
|
||||
|
||||
buildInputs = [ olm ];
|
||||
|
||||
# Disable CGO except for olm
|
||||
env.CGO_ENABLED = 1;
|
||||
|
||||
doCheck = false;
|
||||
|
||||
meta = with lib; {
|
||||
description = "A Matrix-Mattermost puppeting bridge based on mautrix-go";
|
||||
homepage = "https://github.com/bostrot/mautrix-mattermost";
|
||||
license = licenses.agpl3Plus;
|
||||
maintainers = [ ];
|
||||
mainProgram = "mautrix-mattermost";
|
||||
};
|
||||
}
|
||||
64
utils/pkgs/mautrix-mattermost/update.sh
Executable file
64
utils/pkgs/mautrix-mattermost/update.sh
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p nix-prefetch-github jq cacert
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
repo_root="$(cd ../../.. && pwd)"
|
||||
|
||||
owner="bostrot"
|
||||
repo="mautrix-mattermost"
|
||||
|
||||
# Get latest commit from GitHub
|
||||
echo "Fetching latest commit from $owner/$repo..."
|
||||
commit_info=$(curl -s "https://api.github.com/repos/$owner/$repo/commits?per_page=1")
|
||||
rev=$(echo "$commit_info" | jq -r '.[0].sha')
|
||||
date=$(echo "$commit_info" | jq -r '.[0].commit.committer.date' | cut -dT -f1)
|
||||
echo "Latest commit: $rev ($date)"
|
||||
|
||||
# Update rev in default.nix
|
||||
sed -i "s|rev = \".*\";|rev = \"$rev\";|" default.nix
|
||||
sed -i "s|version = \".*\";|version = \"0-unstable-$date\";|" default.nix
|
||||
|
||||
# Fetch source hash
|
||||
echo "Fetching source hash..."
|
||||
prefetch_output=$(nix-prefetch-github "$owner" "$repo" --rev "$rev" --json 2>/dev/null)
|
||||
src_hash=$(echo "$prefetch_output" | jq -r '.hash')
|
||||
echo "Source hash: $src_hash"
|
||||
sed -i "s|hash = \"sha256-.*\";|hash = \"$src_hash\";|" default.nix
|
||||
|
||||
# Set placeholder vendorHash to trigger build failure
|
||||
sed -i "s|vendorHash = \"sha256-.*\";|vendorHash = \"sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\";|" default.nix
|
||||
|
||||
# Build to get the correct vendorHash
|
||||
echo "Building to determine vendorHash..."
|
||||
cd "$repo_root"
|
||||
build_output=$(nix build --impure --no-link --expr 'with import <nixpkgs> { config.permittedInsecurePackages = ["olm-3.2.16"]; }; callPackage ./utils/pkgs/mautrix-mattermost {}' 2>&1 || true)
|
||||
|
||||
vendor_hash=$(echo "$build_output" | grep -oP "got:\s+sha256-[A-Za-z0-9+/=]+" | tail -1 | awk '{print $2}')
|
||||
|
||||
if [ -z "$vendor_hash" ]; then
|
||||
echo "Error: Could not determine vendorHash from build output"
|
||||
echo "Build output:"
|
||||
echo "$build_output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "vendorHash: $vendor_hash"
|
||||
cd "$repo_root/utils/pkgs/mautrix-mattermost"
|
||||
sed -i "s|vendorHash = \"sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\";|vendorHash = \"$vendor_hash\";|" default.nix
|
||||
|
||||
# Verify the build works
|
||||
echo ""
|
||||
echo "Verifying build..."
|
||||
cd "$repo_root"
|
||||
if nix build --impure --no-link --expr 'with import <nixpkgs> { config.permittedInsecurePackages = ["olm-3.2.16"]; }; callPackage ./utils/pkgs/mautrix-mattermost {}'; then
|
||||
echo ""
|
||||
echo "Successfully updated mautrix-mattermost to $rev ($date)"
|
||||
echo " Source hash: $src_hash"
|
||||
echo " vendorHash: $vendor_hash"
|
||||
else
|
||||
echo ""
|
||||
echo "Build failed after updating hashes"
|
||||
exit 1
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue