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