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