426 lines
13 KiB
Nix
426 lines
13 KiB
Nix
{ pkgs, lib, config, ... }:
|
|
let
|
|
hostname = "matrix";
|
|
fqdn = "${hostname}.cloonar.com";
|
|
baseUrl = "https://${fqdn}";
|
|
clientConfig = {
|
|
"m.homeserver".base_url = baseUrl;
|
|
# MAS auth issuer discovery (MSC2965)
|
|
"org.matrix.msc2965.authentication" = {
|
|
issuer = baseUrl + "/";
|
|
account = baseUrl + "/account";
|
|
};
|
|
};
|
|
serverConfig."m.server" = "${fqdn}:443";
|
|
mkWellKnown = data: ''
|
|
default_type application/json;
|
|
add_header Access-Control-Allow-Origin *;
|
|
return 200 '${builtins.toJSON data}';
|
|
'';
|
|
|
|
masUpstreamId = "01KJPRKN397E5N8D0CA2Z3TJ7Y";
|
|
elementWebClientId = "01KJPVT5D54NRAY7AJY6PZEN0D";
|
|
masPackage = pkgs.matrix-authentication-service;
|
|
|
|
synapseMasConfig = pkgs.writeText "synapse-mas-config.yaml" ''
|
|
matrix_authentication_service:
|
|
enabled: true
|
|
endpoint: "http://127.0.0.1:8081"
|
|
secret_path: ${config.sops.secrets.mas-matrix-secret-synapse.path}
|
|
'';
|
|
in {
|
|
# Secrets for MAS
|
|
sops.secrets.mas-encryption-key = { owner = "mas"; };
|
|
sops.secrets.mas-matrix-secret = { owner = "mas"; };
|
|
sops.secrets.mas-authelia-client-secret = { owner = "mas"; };
|
|
sops.secrets.mas-rsa-key = { owner = "mas"; };
|
|
# Synapse also needs the shared secret
|
|
sops.secrets.mas-matrix-secret-synapse = {
|
|
owner = "matrix-synapse";
|
|
key = "mas-matrix-secret";
|
|
};
|
|
|
|
sops.secrets.mautrix-whatsapp-env = { };
|
|
sops.secrets.mautrix-signal-env = { };
|
|
sops.secrets.mautrix-discord-env = { };
|
|
sops.secrets.mautrix-mattermost-env = { };
|
|
|
|
# MAS system user
|
|
users.users.mas = {
|
|
isSystemUser = true;
|
|
group = "mas";
|
|
home = "/var/lib/mas";
|
|
};
|
|
users.groups.mas = { };
|
|
|
|
# PostgreSQL databases for Synapse and MAS
|
|
services.postgresql = {
|
|
enable = true;
|
|
# Synapse requires C locale for correct collation behavior
|
|
initdbArgs = [ "--lc-collate=C" "--lc-ctype=C" ];
|
|
ensureDatabases = [ "matrix-synapse" "mas" ];
|
|
ensureUsers = [
|
|
{
|
|
name = "matrix-synapse";
|
|
ensureDBOwnership = true;
|
|
}
|
|
{
|
|
name = "mas";
|
|
ensureDBOwnership = true;
|
|
}
|
|
];
|
|
};
|
|
|
|
services.postgresqlBackup.enable = true;
|
|
services.postgresqlBackup.databases = [ "matrix-synapse" "mas" ];
|
|
|
|
# Matrix Authentication Service (MAS)
|
|
systemd.services.matrix-authentication-service = {
|
|
description = "Matrix Authentication Service";
|
|
after = [ "postgresql.service" "network.target" ];
|
|
before = [ "matrix-synapse.service" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "mas";
|
|
Group = "mas";
|
|
RuntimeDirectory = "mas";
|
|
RuntimeDirectoryMode = "0755";
|
|
StateDirectory = "mas";
|
|
StateDirectoryMode = "0750";
|
|
ExecStart = "${masPackage}/bin/mas-cli server --config /run/mas/config.yaml";
|
|
Restart = "on-failure";
|
|
RestartSec = "5s";
|
|
};
|
|
|
|
preStart = ''
|
|
# Read secrets from SOPS-managed files
|
|
ENCRYPTION_KEY=$(cat ${config.sops.secrets.mas-encryption-key.path})
|
|
MATRIX_SECRET=$(cat ${config.sops.secrets.mas-matrix-secret.path})
|
|
CLIENT_SECRET=$(cat ${config.sops.secrets.mas-authelia-client-secret.path})
|
|
|
|
# Write MAS config with secrets interpolated
|
|
cat > /run/mas/config.yaml <<MASEOF
|
|
http:
|
|
public_base: ${baseUrl}/
|
|
listeners:
|
|
- name: web
|
|
resources:
|
|
- name: discovery
|
|
- name: human
|
|
- name: oauth
|
|
- name: compat
|
|
- name: graphql
|
|
- name: assets
|
|
binds:
|
|
- address: "127.0.0.1:8081"
|
|
|
|
database:
|
|
uri: postgresql:///mas?host=/run/postgresql
|
|
|
|
matrix:
|
|
homeserver: cloonar.com
|
|
endpoint: "http://[::1]:8008"
|
|
secret: "$MATRIX_SECRET"
|
|
|
|
upstream_oauth2:
|
|
providers:
|
|
- id: ${masUpstreamId}
|
|
synapse_idp_id: oidc-authelia
|
|
human_name: Authelia
|
|
issuer: https://auth.cloonar.com
|
|
client_id: synapse
|
|
client_secret: "$CLIENT_SECRET"
|
|
token_endpoint_auth_method: client_secret_post
|
|
scope: "openid email profile"
|
|
claims_imports:
|
|
localpart:
|
|
action: force
|
|
template: "{{ user.email | split('@') | first }}"
|
|
displayname:
|
|
action: suggest
|
|
template: "{{ user.name }}"
|
|
email:
|
|
action: force
|
|
template: "{{ user.email }}"
|
|
set_email_verification: always
|
|
|
|
clients:
|
|
- client_id: ${elementWebClientId}
|
|
client_auth_method: none
|
|
redirect_uris:
|
|
- https://element.cloonar.com/
|
|
- https://element.cloonar.com/?no_universal_links=true
|
|
|
|
passwords:
|
|
enabled: true
|
|
schemes:
|
|
- version: 1
|
|
algorithm: bcrypt
|
|
|
|
secrets:
|
|
encryption: "$ENCRYPTION_KEY"
|
|
keys:
|
|
- kid: mas-rsa-key
|
|
key_file: ${config.sops.secrets.mas-rsa-key.path}
|
|
|
|
telemetry:
|
|
tracing:
|
|
exporter: none
|
|
metrics:
|
|
exporter: none
|
|
MASEOF
|
|
'';
|
|
};
|
|
|
|
# Synapse homeserver
|
|
services.matrix-synapse = {
|
|
enable = true;
|
|
extraConfigFiles = [ "${synapseMasConfig}" ];
|
|
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";
|
|
};
|
|
};
|
|
|
|
allow_guest_access = false;
|
|
|
|
# MSC4190: device management for appservices (required for encrypted bridges with MAS)
|
|
experimental_features = {
|
|
msc4190_enabled = true;
|
|
msc3202_device_masquerading = true;
|
|
};
|
|
};
|
|
};
|
|
|
|
# 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).
|
|
# Synapse depends on MAS for auth delegation
|
|
systemd.services.matrix-synapse.after = [ "matrix-authentication-service.service" ];
|
|
systemd.services.matrix-synapse.wants = [ "matrix-authentication-service.service" ];
|
|
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";
|
|
};
|
|
"org.matrix.msc2965.authentication" = {
|
|
issuer = "https://matrix.cloonar.com/";
|
|
account = "https://matrix.cloonar.com/account";
|
|
};
|
|
};
|
|
oidc_static_clients = {
|
|
"https://matrix.cloonar.com/" = {
|
|
client_id = elementWebClientId;
|
|
};
|
|
};
|
|
disable_custom_urls = true;
|
|
disable_3pid_login = true;
|
|
default_country_code = "AT";
|
|
};
|
|
};
|
|
};
|
|
|
|
# Synapse + MAS 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;
|
|
|
|
# MAS compatibility endpoints (must be before /_matrix catch-all)
|
|
locations."~ ^/_matrix/client/(r0|v3)/login$".proxyPass = "http://127.0.0.1:8081";
|
|
locations."~ ^/_matrix/client/(r0|v3)/logout$".proxyPass = "http://127.0.0.1:8081";
|
|
locations."~ ^/_matrix/client/(r0|v3)/refresh$".proxyPass = "http://127.0.0.1:8081";
|
|
|
|
# MAS own endpoints
|
|
locations."/authorize".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/oauth2".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/.well-known/openid-configuration".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/.well-known/webfinger".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/assets".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/graphql".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/account".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/upstream".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/register".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/consent".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/recovery".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/login".proxyPass = "http://127.0.0.1:8081";
|
|
locations."/change-password".proxyPass = "http://127.0.0.1:8081";
|
|
|
|
# Synapse endpoints
|
|
locations."/_matrix".proxyPass = "http://[::1]:8008";
|
|
locations."/_synapse/client".proxyPass = "http://[::1]:8008";
|
|
};
|
|
|
|
# Internal proxy for bridges: routes login/auth to MAS, everything else to Synapse.
|
|
# Bridges connect here instead of directly to Synapse, which no longer serves
|
|
# /_matrix/client/v3/login when MAS is enabled.
|
|
services.nginx.virtualHosts."matrix-internal" = {
|
|
listen = [{ addr = "127.0.0.1"; port = 8009; }];
|
|
locations."~ ^/_matrix/client/(r0|v3)/login$".proxyPass = "http://127.0.0.1:8081";
|
|
locations."~ ^/_matrix/client/(r0|v3)/logout$".proxyPass = "http://127.0.0.1:8081";
|
|
locations."~ ^/_matrix/client/(r0|v3)/refresh$".proxyPass = "http://127.0.0.1:8081";
|
|
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://127.0.0.1:8009";
|
|
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";
|
|
msc4190 = true;
|
|
};
|
|
};
|
|
};
|
|
|
|
# Signal bridge
|
|
services.mautrix-signal = {
|
|
enable = true;
|
|
registerToSynapse = true;
|
|
environmentFile = config.sops.secrets.mautrix-signal-env.path;
|
|
settings = {
|
|
homeserver = {
|
|
address = "http://127.0.0.1:8009";
|
|
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";
|
|
msc4190 = true;
|
|
};
|
|
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://127.0.0.1:8009";
|
|
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";
|
|
msc4190 = true;
|
|
};
|
|
};
|
|
};
|
|
|
|
# Mattermost bridge
|
|
services.mautrix-mattermost = {
|
|
enable = true;
|
|
registerToSynapse = true;
|
|
environmentFile = config.sops.secrets.mautrix-mattermost-env.path;
|
|
settings = {
|
|
homeserver = {
|
|
address = "http://[::1]:8008";
|
|
domain = "cloonar.com";
|
|
};
|
|
bridge = {
|
|
command_prefix = "!mm";
|
|
permissions."*" = "relay";
|
|
permissions."cloonar.com" = "user";
|
|
relay.enabled = true;
|
|
};
|
|
appservice = {
|
|
as_token = "$MAUTRIX_MATTERMOST_AS_TOKEN";
|
|
hs_token = "$MAUTRIX_MATTERMOST_HS_TOKEN";
|
|
};
|
|
encryption = {
|
|
allow = true;
|
|
default = true;
|
|
require = true;
|
|
pickle_key = "$MAUTRIX_MATTERMOST_PICKLE_KEY";
|
|
};
|
|
};
|
|
};
|
|
|
|
}
|