Self-host your own digital island
at main 10 kB view raw
1{ config, pkgs, lib, ... }: 2 3with lib; 4let 5 cfg = config.eilean; 6 turnSharedSecretFile = "/run/matrix-synapse/turn-shared-secret"; 7 domain = config.networking.domain; 8 subdomain = "matrix.${domain}"; 9in { 10 options.eilean.matrix = { 11 enable = mkEnableOption "matrix"; 12 turn = mkOption { 13 type = types.bool; 14 default = true; 15 }; 16 registrationSecretFile = mkOption { 17 type = types.nullOr types.str; 18 default = null; 19 }; 20 bridges = { 21 whatsapp = mkOption { 22 type = types.bool; 23 default = false; 24 description = "Enable WhatsApp bridge."; 25 }; 26 signal = mkOption { 27 type = types.bool; 28 default = false; 29 description = "Enable Signal bridge."; 30 }; 31 instagram = mkOption { 32 type = types.bool; 33 default = false; 34 description = "Enable Instagram bridge."; 35 }; 36 messenger = mkOption { 37 type = types.bool; 38 default = false; 39 description = "Enable Facebook Messenger bridge."; 40 }; 41 }; 42 }; 43 44 config = mkIf cfg.matrix.enable { 45 services.postgresql.enable = true; 46 services.postgresql.package = pkgs.postgresql_13; 47 services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" '' 48 CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse'; 49 CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" 50 TEMPLATE template0 51 LC_COLLATE = "C" 52 LC_CTYPE = "C"; 53 ''; 54 55 security.acme-eon.nginxCerts = lib.mkIf cfg.acme-eon [ domain subdomain ]; 56 57 services.nginx = { 58 enable = true; 59 # only recommendedProxySettings and recommendedGzipSettings are strictly required, 60 # but the rest make sense as well 61 recommendedTlsSettings = true; 62 recommendedOptimisation = true; 63 recommendedGzipSettings = true; 64 recommendedProxySettings = true; 65 66 virtualHosts = { 67 # This host section can be placed on a different host than the rest, 68 # i.e. to delegate from the host being accessible as ${domain} 69 # to another host actually running the Matrix homeserver. 70 "${domain}" = { 71 enableACME = lib.mkIf (!cfg.acme-eon) true; 72 forceSSL = true; 73 74 locations."= /.well-known/matrix/server".extraConfig = let 75 # use 443 instead of the default 8448 port to unite 76 # the client-server and server-server port for simplicity 77 server = { "m.server" = "${subdomain}:443"; }; 78 in '' 79 default_type application/json; 80 return 200 '${builtins.toJSON server}'; 81 ''; 82 locations."= /.well-known/matrix/client".extraConfig = let 83 client = { 84 "m.homeserver" = { "base_url" = "https://${subdomain}"; }; 85 "m.identity_server" = { "base_url" = "https://vector.im"; }; 86 }; 87 # ACAO required to allow element-web on any URL to request this json file 88 # set other headers due to https://github.com/yandex/gixy/blob/master/docs/en/plugins/addheaderredefinition.md 89 in '' 90 default_type application/json; 91 add_header Access-Control-Allow-Origin *; 92 add_header Strict-Transport-Security max-age=31536000 always; 93 add_header X-Frame-Options SAMEORIGIN always; 94 add_header X-Content-Type-Options nosniff always; 95 add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; frame-src 'self'; frame-ancestors 'self'; form-action 'self';" always; 96 add_header Referrer-Policy 'same-origin'; 97 return 200 '${builtins.toJSON client}'; 98 ''; 99 }; 100 101 # Reverse proxy for Matrix client-server and server-server communication 102 "${subdomain}" = { 103 enableACME = lib.mkIf (!cfg.acme-eon) true; 104 forceSSL = true; 105 106 # Or do a redirect instead of the 404, or whatever is appropriate for you. 107 # But do not put a Matrix Web client here! See the Element web section below. 108 locations."/".extraConfig = '' 109 return 404; 110 ''; 111 112 # forward all Matrix API calls to the synapse Matrix homeserver 113 locations."~ ^(\\/_matrix|\\/_synapse\\/client)" = { 114 proxyPass = "http://127.0.0.1:8008"; 115 }; 116 }; 117 }; 118 }; 119 120 services.matrix-synapse = { 121 enable = true; 122 settings = mkMerge [ 123 { 124 server_name = domain; 125 enable_registration = true; 126 registration_requires_token = true; 127 registration_shared_secret_path = cfg.matrix.registrationSecretFile; 128 listeners = [{ 129 port = 8008; 130 bind_addresses = [ "::1" "127.0.0.1" ]; 131 type = "http"; 132 tls = false; 133 x_forwarded = true; 134 resources = [{ 135 names = [ "client" "federation" ]; 136 compress = false; 137 }]; 138 }]; 139 max_upload_size = "100M"; 140 app_service_config_files = (optional cfg.matrix.bridges.instagram 141 "/var/lib/mautrix-instagram/instagram-registration.yaml") 142 ++ (optional cfg.matrix.bridges.messenger 143 "/var/lib/mautrix-messenger/messenger-registration.yaml"); 144 } 145 (mkIf cfg.matrix.turn { 146 turn_uris = with config.services.coturn; [ 147 "turn:${realm}:3478?transport=udp" 148 "turn:${realm}:3478?transport=tcp" 149 "turns:${realm}:5349?transport=udp" 150 "turns:${realm}:5349?transport=tcp" 151 ]; 152 turn_user_lifetime = "1h"; 153 }) 154 ]; 155 extraConfigFiles = mkIf cfg.matrix.turn ([ turnSharedSecretFile ]); 156 }; 157 158 systemd.services.matrix-synapse-turn-shared-secret-generator = 159 mkIf cfg.matrix.turn { 160 description = "Generate matrix synapse turn shared secret config file"; 161 script = '' 162 mkdir -p "$(dirname '${turnSharedSecretFile}')" 163 echo "turn_shared_secret: $(cat '${config.services.coturn.static-auth-secret-file}')" > '${turnSharedSecretFile}' 164 chmod 770 '${turnSharedSecretFile}' 165 chown ${config.systemd.services.matrix-synapse.serviceConfig.User}:${config.systemd.services.matrix-synapse.serviceConfig.Group} '${turnSharedSecretFile}' 166 ''; 167 serviceConfig.Type = "oneshot"; 168 serviceConfig.RemainAfterExit = true; 169 after = [ "coturn-static-auth-secret-generator.service" ]; 170 requires = [ "coturn-static-auth-secret-generator.service" ]; 171 }; 172 systemd.services."matrix-synapse".after = mkIf cfg.matrix.turn 173 [ "matrix-synapse-turn-shared-secret-generator.service" ]; 174 systemd.services."matrix-synapse".requires = mkIf cfg.matrix.turn 175 [ "matrix-synapse-turn-shared-secret-generator.service" ]; 176 177 systemd.services.matrix-synapse.serviceConfig.SupplementaryGroups = 178 # remove after https://github.com/NixOS/nixpkgs/pull/311681/files 179 (optional cfg.matrix.bridges.whatsapp 180 config.systemd.services.mautrix-whatsapp.serviceConfig.Group) 181 ++ (optional cfg.matrix.bridges.instagram 182 config.systemd.services.mautrix-instagram.serviceConfig.Group) 183 ++ (optional cfg.matrix.bridges.messenger 184 config.systemd.services.mautrix-messenger.serviceConfig.Group); 185 186 services.mautrix-whatsapp = mkIf cfg.matrix.bridges.whatsapp { 187 enable = true; 188 settings.homeserver.address = "https://${subdomain}"; 189 settings.homeserver.domain = domain; 190 settings.appservice.hostname = "localhost"; 191 settings.appservice.address = "http://localhost:29318"; 192 settings.bridge.personal_filtering_spaces = true; 193 settings.bridge.history_sync.backfill = false; 194 settings.bridge.permissions."@${config.eilean.username}:${domain}" = 195 "admin"; 196 settings.bridge.encryption.allow = true; 197 settings.bridge.encryption.default = true; 198 }; 199 # using https://github.com/NixOS/nixpkgs/pull/277368 200 services.mautrix-signal = mkIf cfg.matrix.bridges.signal { 201 enable = true; 202 settings.homeserver.address = "https://${subdomain}"; 203 settings.homeserver.domain = domain; 204 settings.appservice.hostname = "localhost"; 205 settings.appservice.address = "http://localhost:29328"; 206 settings.bridge.personal_filtering_spaces = true; 207 settings.bridge.permissions."@${config.eilean.username}:${domain}" = 208 "admin"; 209 settings.bridge.encryption.allow = true; 210 settings.bridge.encryption.default = true; 211 }; 212 # TODO replace with upstreamed mautrix-meta 213 services.mautrix-instagram = mkIf cfg.matrix.bridges.instagram { 214 enable = true; 215 settings.homeserver.address = "https://${subdomain}"; 216 settings.homeserver.domain = domain; 217 settings.appservice.hostname = "localhost"; 218 settings.appservice.address = "http://localhost:29319"; 219 settings.bridge.personal_filtering_spaces = true; 220 settings.bridge.backfill.enabled = false; 221 settings.bridge.permissions."@${config.eilean.username}:${domain}" = 222 "admin"; 223 settings.bridge.encryption.allow = true; 224 settings.bridge.encryption.default = true; 225 }; 226 services.mautrix-messenger = mkIf cfg.matrix.bridges.messenger { 227 enable = true; 228 settings.homeserver.address = "https://${subdomain}"; 229 settings.homeserver.domain = domain; 230 settings.appservice.hostname = "localhost"; 231 settings.appservice.address = "http://localhost:29320"; 232 settings.bridge.personal_filtering_spaces = true; 233 settings.bridge.backfill.enabled = false; 234 settings.bridge.permissions."@${config.eilean.username}:${domain}" = 235 "admin"; 236 settings.bridge.encryption.allow = true; 237 settings.bridge.encryption.default = true; 238 }; 239 240 eilean.turn.enable = mkIf cfg.matrix.turn true; 241 242 eilean.dns.enable = true; 243 eilean.services.dns.zones.${domain}.records = [{ 244 name = "matrix"; 245 type = "CNAME"; 246 value = cfg.domainName; 247 }]; 248 }; 249}