mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-11-10 01:33:11 +01:00
560 lines
22 KiB
Nix
560 lines
22 KiB
Nix
{
|
|
lib,
|
|
config,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.services.mautrix-discord;
|
|
dataDir = cfg.dataDir;
|
|
format = pkgs.formats.yaml { };
|
|
|
|
registrationFile = "${dataDir}/discord-registration.yaml";
|
|
|
|
settingsFile = "${dataDir}/config.yaml";
|
|
settingsFileUnformatted = format.generate "discord-config-unsubstituted.yaml" cfg.settings;
|
|
in
|
|
{
|
|
options = {
|
|
services.mautrix-discord = {
|
|
enable = lib.mkEnableOption "Mautrix-Discord, a Matrix-Discord puppeting/relay-bot bridge";
|
|
|
|
package = lib.mkPackageOption pkgs "mautrix-discord" { };
|
|
|
|
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 = ''
|
|
fullDataDiration.
|
|
See [example-config.yaml](https://github.com/mautrix/discord/blob/main/example-config.yaml)
|
|
for more information.
|
|
'';
|
|
};
|
|
|
|
appservice = lib.mkOption {
|
|
type = lib.types.attrs;
|
|
default = {
|
|
address = "http://localhost:29334";
|
|
hostname = "0.0.0.0";
|
|
port = 29334;
|
|
database = {
|
|
type = "sqlite3";
|
|
uri = "file:/var/lib/mautrix-discord/mautrix-discord.db?_txlock=immediate";
|
|
max_open_conns = 20;
|
|
max_idle_conns = 2;
|
|
max_conn_idle_time = null;
|
|
max_conn_lifetime = null;
|
|
};
|
|
id = "discord";
|
|
bot = {
|
|
username = "discordbot";
|
|
displayname = "Discord bridge bot";
|
|
avatar = "mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC";
|
|
};
|
|
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";
|
|
};
|
|
defaultText = lib.literalExpression ''
|
|
{
|
|
address = "http://localhost:29334";
|
|
hostname = "0.0.0.0";
|
|
port = 29334;
|
|
database = {
|
|
type = "sqlite3";
|
|
uri = "file:''${config.services.mautrix-discord.dataDir}/mautrix-discord.db?_txlock=immediate";
|
|
max_open_conns = 20;
|
|
max_idle_conns = 2;
|
|
max_conn_idle_time = null;
|
|
max_conn_lifetime = null;
|
|
};
|
|
id = "discord";
|
|
bot = {
|
|
username = "discordbot";
|
|
displayname = "Discord bridge bot";
|
|
avatar = "mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC";
|
|
};
|
|
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 [example-config.yaml](https://github.com/mautrix/discord/blob/main/example-config.yaml)
|
|
for more information.
|
|
'';
|
|
};
|
|
|
|
bridge = lib.mkOption {
|
|
type = lib.types.attrs;
|
|
default = {
|
|
username_template = "discord_{{.}}";
|
|
displayname_template = "{{if .Webhook}}Webhook{{else}}{{or .GlobalName .Username}}{{if .Bot}} (bot){{end}}{{end}}";
|
|
channel_name_template = "{{if or (eq .Type 3) (eq .Type 4)}}{{.Name}}{{else}}#{{.Name}}{{end}}";
|
|
guild_name_template = "{{.Name}}";
|
|
private_chat_portal_meta = "default";
|
|
public_address = null;
|
|
avatar_proxy_key = "generate";
|
|
portal_message_buffer = 128;
|
|
startup_private_channel_create_limit = 5;
|
|
delivery_receipts = false;
|
|
message_status_events = false;
|
|
message_error_notices = true;
|
|
restricted_rooms = true;
|
|
autojoin_thread_on_open = true;
|
|
embed_fields_as_tables = true;
|
|
mute_channels_on_create = false;
|
|
sync_direct_chat_list = false;
|
|
resend_bridge_info = false;
|
|
custom_emoji_reactions = true;
|
|
delete_portal_on_channel_delete = false;
|
|
delete_guild_on_leave = true;
|
|
federate_rooms = true;
|
|
prefix_webhook_messages = true;
|
|
enable_webhook_avatars = false;
|
|
use_discord_cdn_upload = true;
|
|
#proxy =
|
|
cache_media = "unencrypted";
|
|
direct_media = {
|
|
enabled = false;
|
|
#server_name = "discord-media.example.com";
|
|
#well_known_response =
|
|
allow_proxy = true;
|
|
server_key = "generate";
|
|
};
|
|
animated_sticker = {
|
|
target = "webp";
|
|
args = {
|
|
width = 320;
|
|
height = 320;
|
|
fps = 25;
|
|
};
|
|
};
|
|
double_puppet_server_map = {
|
|
#"example.com" = "https://example.com";
|
|
};
|
|
double_puppet_allow_discovery = false;
|
|
login_shared_secret_map = {
|
|
#"example.com" = "foobar";
|
|
};
|
|
command_prefix = "!discord";
|
|
management_room_text = {
|
|
welcome = "Hello, I'm a Discord bridge bot.";
|
|
welcome_connected = "Use `help` for help.";
|
|
welcome_unconnected = "Use `help` for help or `login` to log in.";
|
|
additional_help = "";
|
|
};
|
|
backfill = {
|
|
forward_limits = {
|
|
initial = {
|
|
dm = 0;
|
|
channel = 0;
|
|
thread = 0;
|
|
};
|
|
missed = {
|
|
dm = 0;
|
|
channel = 0;
|
|
thread = 0;
|
|
};
|
|
max_guild_members = -1;
|
|
};
|
|
};
|
|
encryption = {
|
|
allow = false;
|
|
default = false;
|
|
appservice = false;
|
|
msc4190 = 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";
|
|
#"example.com" = "user";
|
|
#"@admin:example.com": "admin";
|
|
};
|
|
};
|
|
description = ''
|
|
Bridge configuration.
|
|
See [example-config.yaml](https://github.com/mautrix/discord/blob/main/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 [example-config.yaml](https://github.com/mautrix/discord/blob/main/example-config.yaml)
|
|
for more information.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
homeserver = {
|
|
address = "http://localhost:8008";
|
|
domain = "public-domain.tld";
|
|
};
|
|
|
|
appservice.public = {
|
|
prefix = "/public";
|
|
external = "https://public-appservice-address/public";
|
|
};
|
|
|
|
bridge.permissions = {
|
|
"example.com" = "user";
|
|
"@admin:example.com" = "admin";
|
|
};
|
|
}
|
|
'';
|
|
description = ''
|
|
{file}`config.yaml` configuration as a Nix attribute set.
|
|
Configuration options should match those described in
|
|
[example-config.yaml](https://github.com/mautrix/discord/blob/main/example-config.yaml).
|
|
'';
|
|
};
|
|
|
|
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-discord";
|
|
defaultText = "/var/lib/mautrix-discord";
|
|
description = ''
|
|
Directory to store the bridge's configuration and database files.
|
|
This directory will be created if it does not exist.
|
|
'';
|
|
};
|
|
|
|
# TODO: Get upstream to add an environment File option. Refer to https://github.com/NixOS/nixpkgs/pull/404871#issuecomment-2895663652 and https://github.com/mautrix/discord/issues/187
|
|
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-discord.dataDir`.
|
|
Can be used for storing the secrets without making them available in the Nix store.
|
|
For example, you can set `services.mautrix-discord.settings.appservice.as_token = "$MAUTRIX_DISCORD_APPSERVICE_AS_TOKEN"`
|
|
and then specify `MAUTRIX_DISCORD_APPSERVICE_AS_TOKEN="{token}"` in the environment file.
|
|
This value will get substituted into the configuration file as a token.
|
|
'';
|
|
};
|
|
|
|
serviceUnit = lib.mkOption {
|
|
type = lib.types.str;
|
|
readOnly = true;
|
|
default = "mautrix-discord.service";
|
|
description = ''
|
|
The systemd unit (a service or a target) for other services to depend on if they
|
|
need to be started after matrix-synapse.
|
|
This option is useful as the actual parent unit for all matrix-synapse processes
|
|
changes when configuring workers.
|
|
'';
|
|
};
|
|
|
|
registrationServiceUnit = lib.mkOption {
|
|
type = lib.types.str;
|
|
readOnly = true;
|
|
default = "mautrix-discord-registration.service";
|
|
description = ''
|
|
The registration service that generates the registration file.
|
|
Systemd unit (a service or a target) for other services to depend on if they
|
|
need to be started after mautrix-discord registration service.
|
|
This option is useful as the actual parent unit for all matrix-synapse processes
|
|
changes when configuring workers.
|
|
'';
|
|
};
|
|
|
|
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.enable "matrix-conduit.service")
|
|
++ (lib.lists.optional config.services.dendrite.enable "dendrite.service");
|
|
|
|
defaultText = ''
|
|
[ cfg.registrationServiceUnit ] ++
|
|
(lib.lists.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit) ++
|
|
(lib.lists.optional config.services.matrix-conduit.enable "matrix-conduit.service") ++
|
|
(lib.lists.optional 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-discord.settings.homeserver.domain` and
|
|
`services.mautrix-discord.settings.homeserver.address` have to be set.
|
|
'';
|
|
}
|
|
{
|
|
assertion = cfg.settings.bridge.permissions or { } != { };
|
|
message = ''
|
|
The option `services.mautrix-discord.settings.bridge.permissions` has to be set.
|
|
'';
|
|
}
|
|
];
|
|
|
|
users.users.mautrix-discord = {
|
|
isSystemUser = true;
|
|
group = "mautrix-discord";
|
|
extraGroups = [ "mautrix-discord-registration" ];
|
|
home = dataDir;
|
|
description = "Mautrix-Discord bridge user";
|
|
};
|
|
|
|
users.groups.mautrix-discord = { };
|
|
users.groups.mautrix-discord-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-discord mautrix-discord -"
|
|
];
|
|
|
|
systemd.services = {
|
|
matrix-synapse = lib.mkIf cfg.registerToSynapse {
|
|
serviceConfig.SupplementaryGroups = [ "mautrix-discord-registration" ];
|
|
# Make synapse depend on the registration service when auto-registering
|
|
wants = [ "mautrix-discord-registration.service" ];
|
|
after = [ "mautrix-discord-registration.service" ];
|
|
};
|
|
|
|
mautrix-discord-registration = {
|
|
description = "Mautrix-Discord 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-discord \
|
|
--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 --generate-registration does not affect config.yaml
|
|
cp '${settingsFile}' '${settingsFile}.tmp'
|
|
echo "Generating registration file"
|
|
mautrix-discord \
|
|
--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-discord-registration '${registrationFile}'
|
|
chmod 640 '${registrationFile}'
|
|
'';
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
UMask = 27;
|
|
|
|
User = "mautrix-discord";
|
|
Group = "mautrix-discord";
|
|
|
|
SystemCallFilter = [ "@system-service" ];
|
|
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
|
|
ReadWritePaths = [ dataDir ];
|
|
StateDirectory = "mautrix-discord";
|
|
EnvironmentFile = cfg.environmentFile;
|
|
};
|
|
|
|
restartTriggers = [ settingsFileUnformatted ];
|
|
};
|
|
|
|
mautrix-discord = {
|
|
description = "Mautrix-Discord, a Matrix-Discord puppeting/relaybot bridge";
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
|
|
after = [ "network-online.target" ] ++ cfg.serviceDependencies;
|
|
path = [
|
|
pkgs.lottieconverter
|
|
pkgs.ffmpeg-headless
|
|
];
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "mautrix-discord";
|
|
Group = "mautrix-discord";
|
|
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 ];
|
|
};
|
|
};
|
|
|
|
meta = {
|
|
maintainers = with lib.maintainers; [
|
|
mistyttm
|
|
];
|
|
};
|
|
};
|
|
}
|