nixos/hosts/fw/modules/web/matrix.nix
2026-03-02 08:25:58 +01:00

227 lines
6.3 KiB
Nix

{ pkgs, lib, config, ... }:
let
hostname = "matrix";
fqdn = "${hostname}.cloonar.com";
baseUrl = "https://${fqdn}";
clientConfig."m.homeserver".base_url = baseUrl;
serverConfig."m.server" = "${fqdn}:443";
mkWellKnown = data: ''
default_type application/json;
add_header Access-Control-Allow-Origin *;
return 200 '${builtins.toJSON data}';
'';
in {
# Secrets for Synapse
sops.secrets.synapse-oidc-client-secret = {
owner = "matrix-synapse";
};
sops.secrets.mautrix-whatsapp-env = { };
sops.secrets.mautrix-signal-env = { };
sops.secrets.mautrix-discord-env = { };
# PostgreSQL database for Synapse
services.postgresql = {
enable = true;
# Synapse requires C locale for correct collation behavior
initdbArgs = [ "--lc-collate=C" "--lc-ctype=C" ];
ensureDatabases = [ "matrix-synapse" ];
ensureUsers = [
{
name = "matrix-synapse";
ensureDBOwnership = true;
}
];
};
services.postgresqlBackup.enable = true;
services.postgresqlBackup.databases = [ "matrix-synapse" ];
# Synapse homeserver
services.matrix-synapse = {
enable = true;
settings = {
server_name = "cloonar.com";
public_baseurl = baseUrl;
listeners = [
{
port = 8008;
bind_addresses = [ "::1" ];
type = "http";
tls = false;
x_forwarded = true;
resources = [
{
compress = true;
names = [ "client" "federation" ];
}
];
}
];
database = {
name = "psycopg2";
args = {
host = "/run/postgresql";
database = "matrix-synapse";
user = "matrix-synapse";
};
};
# Disable registration - users created via OIDC
enable_registration = false;
allow_guest_access = false;
# OIDC SSO via Authelia
oidc_providers = [
{
idp_id = "authelia";
idp_name = "Authelia";
discover = true;
issuer = "https://auth.cloonar.com";
user_profile_method = "userinfo_endpoint";
client_id = "synapse";
client_secret_path = config.sops.secrets.synapse-oidc-client-secret.path;
scopes = [ "openid" "profile" "email" ];
allow_existing_users = true;
user_mapping_provider.config = {
subject_claim = "sub";
localpart_template = "{{ user.email | localpart_from_email }}";
display_name_template = "{{ user.name }}";
email_template = "{{ user.email }}";
};
}
];
};
};
# Synapse runs inside an isolated microVM, so PrivateUsers provides minimal
# additional security. Disabling it allows Synapse to read bridge registration
# files via SupplementaryGroups (user namespace blocks mapped GIDs otherwise).
systemd.services.matrix-synapse.serviceConfig.PrivateUsers = lib.mkForce false;
# Element Web client
services.nginx.virtualHosts."element.cloonar.com" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
root = pkgs.element-web.override {
conf = {
default_theme = "dark";
default_server_config = {
"m.homeserver" = {
base_url = "https://matrix.cloonar.com";
server_name = "cloonar.com";
};
};
disable_custom_urls = true;
disable_3pid_login = true;
default_country_code = "AT";
};
};
};
# Synapse nginx reverse proxy
services.nginx.virtualHosts."${fqdn}" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
locations."/".extraConfig = ''
return 404;
'';
locations."= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig;
locations."= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig;
locations."/_matrix".proxyPass = "http://[::1]:8008";
locations."/_synapse/client".proxyPass = "http://[::1]:8008";
};
#
# Mautrix bridges (using NixOS modules)
# Modules handle users, groups, registration files, Synapse integration,
# and service ordering automatically via registerToSynapse.
#
# WhatsApp bridge
services.mautrix-whatsapp = {
enable = true;
registerToSynapse = true;
environmentFile = config.sops.secrets.mautrix-whatsapp-env.path;
settings = {
homeserver = {
address = "http://[::1]:8008";
domain = "cloonar.com";
};
bridge = {
command_prefix = "!wa";
permissions."*" = "relay";
permissions."cloonar.com" = "user";
relay.enabled = true;
};
encryption = {
allow = true;
default = true;
require = true;
pickle_key = "$MAUTRIX_WHATSAPP_PICKLE_KEY";
};
};
};
# Signal bridge
services.mautrix-signal = {
enable = true;
registerToSynapse = true;
environmentFile = config.sops.secrets.mautrix-signal-env.path;
settings = {
homeserver = {
address = "http://[::1]:8008";
domain = "cloonar.com";
};
bridge = {
command_prefix = "!signal";
permissions."*" = "relay";
permissions."cloonar.com" = "user";
relay.enabled = true;
};
encryption = {
allow = true;
default = true;
require = true;
pickle_key = "$MAUTRIX_SIGNAL_PICKLE_KEY";
};
matrix.sync_direct_chat_list = true;
};
};
# Discord bridge
services.mautrix-discord = {
enable = true;
registerToSynapse = true;
environmentFile = config.sops.secrets.mautrix-discord-env.path;
settings = {
homeserver = {
address = "http://[::1]:8008";
domain = "cloonar.com";
};
bridge = {
command_prefix = "!discord";
permissions."*" = "relay";
permissions."cloonar.com" = "user";
relay.enabled = true;
};
# Override dummy token defaults so env var substitution writes real tokens
# into the config and registration file (module defaults are placeholder strings)
appservice = {
as_token = "$MAUTRIX_DISCORD_AS_TOKEN";
hs_token = "$MAUTRIX_DISCORD_HS_TOKEN";
};
encryption = {
allow = true;
default = true;
require = true;
pickle_key = "$MAUTRIX_DISCORD_PICKLE_KEY";
};
};
};
}