1{ config, pkgs, lib, ... }:
2
3with lib;
4
5let
6 dataDir = "/var/lib/mautrix-telegram";
7 registrationFile = "${dataDir}/telegram-registration.yaml";
8 cfg = config.services.mautrix-telegram;
9 settingsFormat = pkgs.formats.json {};
10 settingsFile =
11 settingsFormat.generate "mautrix-telegram-config.json" cfg.settings;
12
13in {
14 options = {
15 services.mautrix-telegram = {
16 enable = mkEnableOption "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge";
17
18 settings = mkOption rec {
19 apply = recursiveUpdate default;
20 inherit (settingsFormat) type;
21 default = {
22 homeserver = {
23 software = "standard";
24 };
25
26 appservice = rec {
27 database = "sqlite:///${dataDir}/mautrix-telegram.db";
28 database_opts = {};
29 hostname = "0.0.0.0";
30 port = 8080;
31 address = "http://localhost:${toString port}";
32 };
33
34 bridge = {
35 permissions."*" = "relaybot";
36 relaybot.whitelist = [ ];
37 double_puppet_server_map = {};
38 login_shared_secret_map = {};
39 };
40
41 logging = {
42 version = 1;
43
44 formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";
45
46 handlers.console = {
47 class = "logging.StreamHandler";
48 formatter = "precise";
49 };
50
51 loggers = {
52 mau.level = "INFO";
53 telethon.level = "INFO";
54
55 # prevent tokens from leaking in the logs:
56 # https://github.com/tulir/mautrix-telegram/issues/351
57 aiohttp.level = "WARNING";
58 };
59
60 # log to console/systemd instead of file
61 root = {
62 level = "INFO";
63 handlers = [ "console" ];
64 };
65 };
66 };
67 example = literalExpression ''
68 {
69 homeserver = {
70 address = "http://localhost:8008";
71 domain = "public-domain.tld";
72 };
73
74 appservice.public = {
75 prefix = "/public";
76 external = "https://public-appservice-address/public";
77 };
78
79 bridge.permissions = {
80 "example.com" = "full";
81 "@admin:example.com" = "admin";
82 };
83 telegram = {
84 connection.use_ipv6 = true;
85 };
86 }
87 '';
88 description = ''
89 {file}`config.yaml` configuration as a Nix attribute set.
90 Configuration options should match those described in
91 [example-config.yaml](https://github.com/mautrix/telegram/blob/master/mautrix_telegram/example-config.yaml).
92
93 Secret tokens should be specified using {option}`environmentFile`
94 instead of this world-readable attribute set.
95 '';
96 };
97
98 environmentFile = mkOption {
99 type = types.nullOr types.path;
100 default = null;
101 description = ''
102 File containing environment variables to be passed to the mautrix-telegram service,
103 in which secret tokens can be specified securely by defining values for e.g.
104 `MAUTRIX_TELEGRAM_APPSERVICE_AS_TOKEN`,
105 `MAUTRIX_TELEGRAM_APPSERVICE_HS_TOKEN`,
106 `MAUTRIX_TELEGRAM_TELEGRAM_API_ID`,
107 `MAUTRIX_TELEGRAM_TELEGRAM_API_HASH` and optionally
108 `MAUTRIX_TELEGRAM_TELEGRAM_BOT_TOKEN`.
109
110 These environment variables can also be used to set other options by
111 replacing hierarchy levels by `.`, converting the name to uppercase
112 and prepending `MAUTRIX_TELEGRAM_`.
113 For example, the first value above maps to
114 {option}`settings.appservice.as_token`.
115
116 The environment variable values can be prefixed with `json::` to have
117 them be parsed as JSON. For example, `login_shared_secret_map` can be
118 set as follows:
119 `MAUTRIX_TELEGRAM_BRIDGE_LOGIN_SHARED_SECRET_MAP=json::{"example.com":"secret"}`.
120 '';
121 };
122
123 serviceDependencies = mkOption {
124 type = with types; listOf str;
125 default = optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
126 defaultText = literalExpression ''
127 optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit
128 '';
129 description = ''
130 List of Systemd services to require and wait for when starting the application service.
131 '';
132 };
133 };
134 };
135
136 config = mkIf cfg.enable {
137 systemd.services.mautrix-telegram = {
138 description = "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge.";
139
140 wantedBy = [ "multi-user.target" ];
141 wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
142 after = [ "network-online.target" ] ++ cfg.serviceDependencies;
143 path = [ pkgs.lottieconverter pkgs.ffmpeg-full ];
144
145 # mautrix-telegram tries to generate a dotfile in the home directory of
146 # the running user if using a postgresql database:
147 #
148 # File "python3.10/site-packages/asyncpg/connect_utils.py", line 257, in _dot_postgre>
149 # return (pathlib.Path.home() / '.postgresql' / filename).resolve()
150 # File "python3.10/pathlib.py", line 1000, in home
151 # return cls("~").expanduser()
152 # File "python3.10/pathlib.py", line 1440, in expanduser
153 # raise RuntimeError("Could not determine home directory.")
154 # RuntimeError: Could not determine home directory.
155 environment.HOME = dataDir;
156
157 preStart = ''
158 # generate the appservice's registration file if absent
159 if [ ! -f '${registrationFile}' ]; then
160 ${pkgs.mautrix-telegram}/bin/mautrix-telegram \
161 --generate-registration \
162 --config='${settingsFile}' \
163 --registration='${registrationFile}'
164 fi
165 '' + lib.optionalString (pkgs.mautrix-telegram ? alembic) ''
166 # run automatic database init and migration scripts
167 ${pkgs.mautrix-telegram.alembic}/bin/alembic -x config='${settingsFile}' upgrade head
168 '';
169
170 serviceConfig = {
171 Type = "simple";
172 Restart = "always";
173
174 ProtectSystem = "strict";
175 ProtectHome = true;
176 ProtectKernelTunables = true;
177 ProtectKernelModules = true;
178 ProtectControlGroups = true;
179
180 DynamicUser = true;
181 PrivateTmp = true;
182 WorkingDirectory = pkgs.mautrix-telegram; # necessary for the database migration scripts to be found
183 StateDirectory = baseNameOf dataDir;
184 UMask = "0027";
185 EnvironmentFile = cfg.environmentFile;
186
187 ExecStart = ''
188 ${pkgs.mautrix-telegram}/bin/mautrix-telegram \
189 --config='${settingsFile}'
190 '';
191 };
192 };
193 };
194
195 meta.maintainers = with maintainers; [ pacien vskilet ];
196}