nixos/mautrix-discord: refactor (#469813)

This commit is contained in:
Guillaume Girol
2026-06-06 12:41:16 +00:00
committed by GitHub
4 changed files with 335 additions and 313 deletions
+36
View File
@@ -65,6 +65,42 @@
"module-services-keycloak-unix-socket": [
"index.html#module-services-keycloak-unix-socket"
],
"module-services-mautrix-discord": [
"index.html#module-services-mautrix-discord"
],
"module-services-mautrix-discord-advanced": [
"index.html#module-services-mautrix-discord-advanced"
],
"module-services-mautrix-discord-authentication": [
"index.html#module-services-mautrix-discord-authentication"
],
"module-services-mautrix-discord-backfill": [
"index.html#module-services-mautrix-discord-backfill"
],
"module-services-mautrix-discord-basic-example": [
"index.html#module-services-mautrix-discord-basic-example"
],
"module-services-mautrix-discord-basic-usage": [
"index.html#module-services-mautrix-discord-basic-usage"
],
"module-services-mautrix-discord-double-puppet": [
"index.html#module-services-mautrix-discord-double-puppet"
],
"module-services-mautrix-discord-encryption": [
"index.html#module-services-mautrix-discord-encryption"
],
"module-services-mautrix-discord-server-defaults": [
"index.html#module-services-mautrix-discord-server-defaults"
],
"module-services-mautrix-discord-setup": [
"index.html#module-services-mautrix-discord-setup"
],
"module-services-mautrix-discord-synapse": [
"index.html#module-services-mautrix-discord-synapse"
],
"module-services-mautrix-discord-troubleshooting": [
"index.html#module-services-mautrix-discord-troubleshooting"
],
"module-services-tandoor-recipes-migrating-media-option-move": [
"index.html#module-services-tandoor-recipes-migrating-media-option-move",
"index.html#module-services-tandoor-recipes-migrating-media-option-1"
@@ -0,0 +1,156 @@
# Mautrix-Discord {#module-services-mautrix-discord}
*Source:* {file}`modules/services/matrix/mautrix-discord`
*Upstream documentation:* <https://docs.mau.fi/bridges/go/discord/index.html>
[Mautrix-Discord](https://github.com/mautrix/discord) is a Matrix-Discord bridge.
## Basic Usage {#module-services-mautrix-discord-basic-usage}
The common setup is to enable the bridge, point it at your homeserver, and set the permissions you want to allow:
1. Set `services.mautrix-discord.enable` to `true`.
2. Set `services.mautrix-discord.settings.homeserver.address` and `services.mautrix-discord.settings.homeserver.domain`.
3. Override `services.mautrix-discord.settings.bridge.permissions` if the default relay permissions do not fit your deployment.
The module provides sensible defaults for the appservice listener, registration tokens, and relay permissions.
### Basic Example {#module-services-mautrix-discord-basic-example}
```nix
{
services.mautrix-discord = {
enable = true;
registerToSynapse = true;
settings = {
homeserver = {
address = "http://localhost:8008";
domain = "example.com";
};
bridge.permissions = {
"example.com" = "user";
"@admin:example.com" = "admin";
};
};
};
}
```
### Server Defaults {#module-services-mautrix-discord-server-defaults}
By default, the bridge listens on `http://localhost:29334` and generates its appservice tokens automatically.
## Authentication {#module-services-mautrix-discord-authentication}
If you want to store the bridge database outside the default SQLite file, set `settings.appservice.database` to use PostgreSQL instead of SQLite:
```nix
{
services.mautrix-discord.settings.appservice.database = {
type = "postgres";
uri = "postgresql:///mautrix-discord?host=/run/postgresql";
};
}
```
If the connection needs a password, combine it with `services.mautrix-discord.environmentFile`:
```nix
{
services.mautrix-discord = {
environmentFile = "/run/secrets/mautrix-discord-env";
settings.appservice.database.uri = "postgresql://mautrix:$DB_PASSWORD@localhost/mautrix-discord";
};
}
```
Use `services.mautrix-discord.environmentFile` for any secret you do not want in the Nix store.
This includes database passwords, shared secrets, and similar values.
Example:
```nix
{
services.mautrix-discord = {
environmentFile = "/run/secrets/mautrix-discord-env";
settings.bridge.login_shared_secret_map = {
"example.com" = "$SHARED_SECRET";
};
};
}
```
## Bridge Setup {#module-services-mautrix-discord-setup}
After the service starts, open a chat with `@discordbot:example.com`, send `login`, and follow the instructions to link your Discord account.
For more detail, see the [Mautrix-Discord documentation](https://docs.mau.fi/bridges/go/discord/index.html).
## Advanced Configuration {#module-services-mautrix-discord-advanced}
The upstream default configuration is available at [example-config.yaml](https://github.com/mautrix/discord/blob/main/example-config.yaml). To print the generated default configuration from the package, run:
```bash
nix-shell -p mautrix-discord --run "mautrix-discord -e"
```
### Encryption {#module-services-mautrix-discord-encryption}
```nix
{
services.mautrix-discord.settings.bridge.encryption = {
allow = true;
default = true;
require = false;
};
}
```
Encryption needs additional bridge-side setup. See the [bridge encryption documentation](https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html) for details.
### Backfill {#module-services-mautrix-discord-backfill}
```nix
{
services.mautrix-discord.settings.bridge.backfill.forward_limits.initial = {
dm = 50;
channel = 50;
thread = 50;
};
}
```
### Double Puppeting {#module-services-mautrix-discord-double-puppet}
```nix
{
services.mautrix-discord = {
environmentFile = "/run/secrets/mautrix-discord-env";
settings.bridge.login_shared_secret_map = {
"example.com" = "$SHARED_SECRET";
};
};
}
```
where `/run/secrets/mautrix-discord-env` contains
```
SHARED_SECRET=aProtectedSecret
```
## Synapse Integration {#module-services-mautrix-discord-synapse}
When `services.mautrix-discord.registerToSynapse` is `true`, the bridge writes its registration file automatically and Synapse picks it up.
If Synapse is enabled, this option defaults to `true`.
## Troubleshooting {#module-services-mautrix-discord-troubleshooting}
- View logs with `journalctl -u mautrix-discord.service -f`.
- Check `systemctl status mautrix-discord` if the bridge does not start.
- Verify the homeserver can reach the configured appservice address.
- Ensure the registration file exists and Synapse can read it.
For more help, see the
[Mautrix-Discord documentation](https://docs.mau.fi/bridges/go/discord/index.html)
or the support room at [#discord:maunium.net](https://matrix.to/#/#discord:maunium.net).
+79 -310
View File
@@ -5,15 +5,59 @@
...
}:
let
defaultDataDir = "/var/lib/mautrix-discord";
cfg = config.services.mautrix-discord;
dataDir = cfg.dataDir;
format = pkgs.formats.yaml { };
serviceDependencies = [
"mautrix-discord-registration.service"
]
++ (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");
registrationFile = "${dataDir}/discord-registration.yaml";
settingsFile = "${dataDir}/config.yaml";
settingsFileUnformatted = format.generate "discord-config-unsubstituted.yaml" cfg.settings;
default_token = "This value is generated when generating the registration";
settingsDefault = {
homeserver = {
address = "";
domain = "";
};
appservice = {
address = "http://localhost:29334";
hostname = "0.0.0.0";
port = 29334;
database = {
type = "sqlite3";
uri = "file:${defaultDataDir}/mautrix-discord.db?_txlock=immediate";
};
id = "discord";
bot = {
username = "discordbot";
displayname = "Discord bridge bot";
avatar = "mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC";
};
as_token = default_token;
hs_token = default_token;
};
bridge.permissions."*" = "relay";
logging = {
min_level = "info";
writers = [
{
type = "stdout";
format = "pretty-colored";
time_format = " ";
}
];
};
};
in
{
options = {
@@ -23,238 +67,14 @@ in
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 = { };
apply = lib.recursiveUpdate settingsDefault;
type = format.type;
default = settingsDefault;
example = lib.literalExpression ''
{
homeserver = {
address = "http://localhost:8008";
domain = "public-domain.tld";
};
appservice.public = {
prefix = "/public";
external = "https://public-appservice-address/public";
domain = "example.com";
};
bridge.permissions = {
@@ -265,8 +85,12 @@ in
'';
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).
Secret tokens should be specified using {option}`environmentFile`
instead of this world-readable attribute set.
'';
};
@@ -282,12 +106,9 @@ in
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.
'';
default = defaultDataDir;
defaultText = defaultDataDir;
description = "Directory to store the bridge's data.";
};
# 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
@@ -295,92 +116,31 @@ in
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.
File containing environment variables for secret substitution.
Variables in the config like `$VARIABLE` will be replaced.
'';
};
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.
'';
cfg.settings.homeserver.address or "" != "" && cfg.settings.homeserver.domain or "" != "";
message = "services.mautrix-discord.settings.homeserver.{address,domain} must 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 ];
@@ -392,7 +152,9 @@ in
systemd.services = {
matrix-synapse = lib.mkIf cfg.registerToSynapse {
serviceConfig.SupplementaryGroups = [ "mautrix-discord-registration" ];
serviceConfig.SupplementaryGroups = [
"mautrix-discord"
];
# Make synapse depend on the registration service when auto-registering
wants = [ "mautrix-discord-registration.service" ];
after = [ "mautrix-discord-registration.service" ];
@@ -471,15 +233,18 @@ in
'${settingsFile}' '${registrationFile}' > '${registrationFile}.tmp'
mv '${registrationFile}.tmp' '${registrationFile}'
# Application services should not be rate limited by default.
yq -Y '.rate_limited = false' '${registrationFile}' > '${registrationFile}.tmp'
mv '${registrationFile}.tmp' '${registrationFile}'
umask $old_umask
chown :mautrix-discord-registration '${registrationFile}'
chmod 640 '${registrationFile}'
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
UMask = 27;
UMask = "027";
User = "mautrix-discord";
Group = "mautrix-discord";
@@ -501,8 +266,8 @@ in
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;
wants = [ "network-online.target" ] ++ serviceDependencies;
after = [ "network-online.target" ] ++ serviceDependencies;
path = [
pkgs.lottieconverter
pkgs.ffmpeg-headless
@@ -536,6 +301,8 @@ in
ProtectHostname = true;
ProtectClock = true;
UMask = "027";
SystemCallArchitectures = "native";
SystemCallErrorNumber = "EPERM";
SystemCallFilter = "@system-service";
@@ -546,10 +313,12 @@ in
};
};
meta = {
maintainers = with lib.maintainers; [
mistyttm
];
};
};
meta = {
maintainers = with lib.maintainers; [
mistyttm
];
doc = ./mautrix-discord.md;
};
}
+64 -3
View File
@@ -1,4 +1,4 @@
{ pkgs, lib, ... }:
{ pkgs, ... }:
let
homeserverUrl = "http://homeserver:8008";
in
@@ -53,6 +53,7 @@ in
appservice = {
address = "http://homeserver:8009";
hostname = "0.0.0.0";
port = 8009;
id = "discord";
bot = {
@@ -60,8 +61,6 @@ in
displayname = "Discord bridge bot";
avatar = "mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC";
};
# Don't override as_token/hs_token - let them use the default placeholder
# which will trigger automatic generation
database = {
type = "sqlite3";
@@ -75,6 +74,17 @@ in
"*" = "relay";
};
};
logging = {
min_level = "info";
writers = [
{
type = "stdout";
format = "pretty-colored";
time_format = " ";
}
];
};
};
};
@@ -157,6 +167,57 @@ in
with subtest("verify registration file was created"):
homeserver.wait_until_succeeds("test -f /var/lib/mautrix-discord/discord-registration.yaml")
# Verify the module wrote the expected bridge configuration.
config_homeserver_address = homeserver.succeed("yq -r '.homeserver.address' /var/lib/mautrix-discord/config.yaml").strip()
config_homeserver_domain = homeserver.succeed("yq -r '.homeserver.domain' /var/lib/mautrix-discord/config.yaml").strip()
config_appservice_address = homeserver.succeed("yq -r '.appservice.address' /var/lib/mautrix-discord/config.yaml").strip()
config_appservice_hostname = homeserver.succeed("yq -r '.appservice.hostname' /var/lib/mautrix-discord/config.yaml").strip()
config_appservice_port = homeserver.succeed("yq -r '.appservice.port' /var/lib/mautrix-discord/config.yaml").strip()
config_appservice_id = homeserver.succeed("yq -r '.appservice.id' /var/lib/mautrix-discord/config.yaml").strip()
config_bot_username = homeserver.succeed("yq -r '.appservice.bot.username' /var/lib/mautrix-discord/config.yaml").strip()
config_bot_displayname = homeserver.succeed("yq -r '.appservice.bot.displayname' /var/lib/mautrix-discord/config.yaml").strip()
config_bot_avatar = homeserver.succeed("yq -r '.appservice.bot.avatar' /var/lib/mautrix-discord/config.yaml").strip()
config_database_type = homeserver.succeed("yq -r '.appservice.database.type' /var/lib/mautrix-discord/config.yaml").strip()
config_database_uri = homeserver.succeed("yq -r '.appservice.database.uri' /var/lib/mautrix-discord/config.yaml").strip()
config_permission = homeserver.succeed("yq -r '.bridge.permissions[\"*\"]' /var/lib/mautrix-discord/config.yaml").strip()
config_logging_min_level = homeserver.succeed("yq -r '.logging.min_level' /var/lib/mautrix-discord/config.yaml").strip()
config_logging_writer_type = homeserver.succeed("yq -r '.logging.writers[0].type' /var/lib/mautrix-discord/config.yaml").strip()
config_logging_writer_format = homeserver.succeed("yq -r '.logging.writers[0].format' /var/lib/mautrix-discord/config.yaml").strip()
reg_rate_limited = homeserver.succeed("yq -r '.rate_limited' /var/lib/mautrix-discord/discord-registration.yaml").strip()
assert config_homeserver_address == "http://homeserver:8008", \
f"Unexpected homeserver address: {config_homeserver_address}"
assert config_homeserver_domain == "homeserver", \
f"Unexpected homeserver domain: {config_homeserver_domain}"
assert config_appservice_address == "http://homeserver:8009", \
f"Unexpected appservice address: {config_appservice_address}"
assert config_appservice_hostname == "0.0.0.0", \
f"Unexpected appservice hostname: {config_appservice_hostname}"
assert config_appservice_port == "8009", \
f"Unexpected appservice port: {config_appservice_port}"
assert config_appservice_id == "discord", \
f"Unexpected appservice id: {config_appservice_id}"
assert config_bot_username == "discordbot", \
f"Unexpected bot username: {config_bot_username}"
assert config_bot_displayname == "Discord bridge bot", \
f"Unexpected bot displayname: {config_bot_displayname}"
assert config_bot_avatar == "mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC", \
f"Unexpected bot avatar: {config_bot_avatar}"
assert config_database_type == "sqlite3-fk-wal", \
f"Unexpected database type: {config_database_type}"
assert config_database_uri == "file:/var/lib/mautrix-discord/mautrix-discord.db?_txlock=immediate", \
f"Unexpected database uri: {config_database_uri}"
assert config_permission == "relay", \
f"Unexpected default permission mapping: {config_permission}"
assert config_logging_min_level == "info", \
f"Unexpected logging min_level: {config_logging_min_level}"
assert config_logging_writer_type == "stdout", \
f"Unexpected logging writer type: {config_logging_writer_type}"
assert config_logging_writer_format == "pretty-colored", \
f"Unexpected logging writer format: {config_logging_writer_format}"
assert reg_rate_limited == "false", \
f"Registration file should disable rate limiting by default, got: {reg_rate_limited}"
# Verify tokens were generated and are not default values
config_as_token = homeserver.succeed("yq -r '.appservice.as_token' /var/lib/mautrix-discord/config.yaml").strip()
config_hs_token = homeserver.succeed("yq -r '.appservice.hs_token' /var/lib/mautrix-discord/config.yaml").strip()