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}