Self-host your own digital island
1{ lib, config, pkgs, ... }:
2let
3 cfg = config.services.mautrix-messenger;
4 dataDir = "/var/lib/mautrix-messenger";
5 registrationFile = "${dataDir}/messenger-registration.yaml";
6 settingsFile = "${dataDir}/config.json";
7 settingsFileUnsubstituted =
8 settingsFormat.generate "mautrix-messenger-config-unsubstituted.json"
9 cfg.settings;
10 settingsFormat = pkgs.formats.json { };
11 appservicePort = 29320;
12
13 mkDefaults = lib.mapAttrsRecursive (n: v: lib.mkDefault v);
14 defaultConfig = {
15 homeserver.address = "http://localhost:8448";
16 meta.mode = "messenger";
17 appservice = {
18 hostname = "[::]";
19 port = appservicePort;
20 database.type = "sqlite3";
21 database.uri = "${dataDir}/mautrix-messenger.db";
22 id = "messenger";
23 bot.username = "messengerbot";
24 bot.displayname = "Messenger Bridge Bot";
25 bot.avatar = "mxc://maunium.net/ygtkteZsXnGJLJHRchUwYWak";
26 as_token = "";
27 hs_token = "";
28 };
29 bridge = {
30 username_template = "messenger_{{.}}";
31 double_puppet_server_map = { };
32 login_shared_secret_map = { };
33 permissions."*" = "relay";
34 relay.enabled = true;
35 };
36 logging = {
37 min_level = "info";
38 writers = lib.singleton {
39 type = "stdout";
40 format = "pretty-colored";
41 time_format = " ";
42 };
43 };
44 };
45
46in {
47 options.services.mautrix-messenger = {
48 enable = lib.mkEnableOption (lib.mdDoc
49 "mautrix-messenger, a puppeting/relaybot bridge between Matrix and Messenger.");
50
51 settings = lib.mkOption {
52 type = settingsFormat.type;
53 default = defaultConfig;
54 description = lib.mdDoc ''
55 {file}`config.yaml` configuration as a Nix attribute set.
56 Configuration options should match those described in
57 [example-config.yaml](https://github.com/mautrix/messenger/blob/master/example-config.yaml).
58 '';
59 example = {
60 appservice = {
61 database = {
62 type = "postgres";
63 uri = "postgresql:///mautrix_messenger?host=/run/postgresql";
64 };
65 id = "messenger";
66 ephemeral_events = false;
67 };
68 bridge = {
69 history_sync = { request_full_sync = true; };
70 private_chat_portal_meta = true;
71 mute_bridging = true;
72 encryption = {
73 allow = true;
74 default = true;
75 require = true;
76 };
77 provisioning = { shared_secret = "disable"; };
78 permissions = { "example.com" = "user"; };
79 };
80 };
81 };
82
83 serviceDependencies = lib.mkOption {
84 type = with lib.types; listOf str;
85 default = lib.optional config.services.matrix-synapse.enable
86 config.services.matrix-synapse.serviceUnit;
87 defaultText = lib.literalExpression ''
88 optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnits
89 '';
90 description = lib.mdDoc ''
91 List of Systemd services to require and wait for when starting the application service.
92 '';
93 };
94 };
95
96 config = lib.mkIf cfg.enable {
97
98 users.users.mautrix-messenger = {
99 isSystemUser = true;
100 group = "mautrix-messenger";
101 home = dataDir;
102 description = "Mautrix-Messenger bridge user";
103 };
104
105 users.groups.mautrix-messenger = { };
106
107 services.mautrix-messenger.settings = lib.mkMerge (map mkDefaults [
108 defaultConfig
109 # Note: this is defined here to avoid the docs depending on `config`
110 {
111 homeserver.domain = config.services.matrix-synapse.settings.server_name;
112 }
113 ]);
114
115 systemd.services.mautrix-messenger = {
116 description = "Mautrix-Messenger Service - A Messenger bridge for Matrix";
117
118 wantedBy = [ "multi-user.target" ];
119 wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
120 after = [ "network-online.target" ] ++ cfg.serviceDependencies;
121
122 preStart = ''
123 # substitute the settings file by environment variables
124 # in this case read from EnvironmentFile
125 test -f '${settingsFile}' && rm -f '${settingsFile}'
126 old_umask=$(umask)
127 umask 0177
128 ${pkgs.envsubst}/bin/envsubst \
129 -o '${settingsFile}' \
130 -i '${settingsFileUnsubstituted}'
131 umask $old_umask
132
133 # generate the appservice's registration file if absent
134 if [ ! -f '${registrationFile}' ]; then
135 ${pkgs.mautrix-meta}/bin/mautrix-meta \
136 --generate-registration \
137 --config='${settingsFile}' \
138 --registration='${registrationFile}'
139 fi
140 chmod 640 ${registrationFile}
141
142 umask 0177
143 ${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token
144 | .[0].appservice.hs_token = .[1].hs_token
145 | .[0]' '${settingsFile}' '${registrationFile}' \
146 > '${settingsFile}.tmp'
147 mv '${settingsFile}.tmp' '${settingsFile}'
148 umask $old_umask
149 '';
150
151 serviceConfig = {
152 User = "mautrix-messenger";
153 Group = "mautrix-messenger";
154 StateDirectory = baseNameOf dataDir;
155 WorkingDirectory = dataDir;
156 ExecStart = ''
157 ${pkgs.mautrix-meta}/bin/mautrix-meta \
158 --config='${settingsFile}' \
159 --registration='${registrationFile}'
160 '';
161 LockPersonality = true;
162 MemoryDenyWriteExecute = true;
163 NoNewPrivileges = true;
164 PrivateDevices = true;
165 PrivateTmp = true;
166 PrivateUsers = true;
167 ProtectClock = true;
168 ProtectControlGroups = true;
169 ProtectHome = true;
170 ProtectHostname = true;
171 ProtectKernelLogs = true;
172 ProtectKernelModules = true;
173 ProtectKernelTunables = true;
174 ProtectSystem = "strict";
175 Restart = "on-failure";
176 RestartSec = "30s";
177 RestrictRealtime = true;
178 RestrictSUIDSGID = true;
179 SystemCallArchitectures = "native";
180 SystemCallErrorNumber = "EPERM";
181 SystemCallFilter = [ "@system-service" ];
182 Type = "simple";
183 UMask = 27;
184 };
185 restartTriggers = [ settingsFileUnsubstituted ];
186 };
187 };
188}