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}