+19
-3
machines/terebithia/default.nix
+19
-3
machines/terebithia/default.nix
···
477
477
"block-tracking" = {
478
478
name = "Block Player Tracking";
479
479
description = "Disable real-time player location updates";
480
-
paths = [ "/sse" "/sse/*" "/tiles/*/markers/pl3xmap_players.json" ];
480
+
paths = [
481
+
"/sse"
482
+
"/sse/*"
483
+
"/tiles/*/markers/pl3xmap_players.json"
484
+
];
481
485
redact."/tiles/settings.json" = [ "players" ];
482
486
};
483
487
};
···
489
493
domain = "serif.blue";
490
494
secretsFile = config.age.secrets.tranquil-pds.path;
491
495
availableUserDomains = [ "serif.blue" ];
492
-
requireInviteCode = true;
493
-
496
+
requireInviteCode = false;
497
+
498
+
# Email configuration
499
+
mail = {
500
+
enable = true;
501
+
fromAddress = "noreply@serif.blue";
502
+
fromName = "Serif PDS";
503
+
smtp = {
504
+
host = "smtp.mailchannels.net";
505
+
port = 587;
506
+
username = "kieranklukascontracting";
507
+
};
508
+
};
509
+
494
510
# Use Backblaze B2 instead of local MinIO
495
511
minio.enable = false;
496
512
s3 = {
+76
-31
modules/nixos/services/tranquil-pds.nix
+76
-31
modules/nixos/services/tranquil-pds.nix
···
120
120
default = false;
121
121
description = "Require invite codes for account creation";
122
122
};
123
+
124
+
mail = {
125
+
enable = lib.mkOption {
126
+
type = lib.types.bool;
127
+
default = false;
128
+
description = "Enable email notifications";
129
+
};
130
+
131
+
fromAddress = lib.mkOption {
132
+
type = lib.types.str;
133
+
default = "noreply@${cfg.domain}";
134
+
description = "Email sender address";
135
+
};
136
+
137
+
fromName = lib.mkOption {
138
+
type = lib.types.str;
139
+
default = "Serif PDS";
140
+
description = "Email sender name";
141
+
};
142
+
143
+
smtp = {
144
+
host = lib.mkOption {
145
+
type = lib.types.str;
146
+
default = "smtp.mailchannels.net";
147
+
description = "SMTP server hostname";
148
+
};
149
+
150
+
port = lib.mkOption {
151
+
type = lib.types.port;
152
+
default = 587;
153
+
description = "SMTP server port";
154
+
};
155
+
156
+
username = lib.mkOption {
157
+
type = lib.types.str;
158
+
description = "SMTP username (set in secrets file with SMTP_USERNAME)";
159
+
};
160
+
161
+
tls = lib.mkOption {
162
+
type = lib.types.bool;
163
+
default = true;
164
+
description = "Use STARTTLS";
165
+
};
166
+
};
167
+
};
123
168
};
124
169
125
170
config = lib.mkIf cfg.enable {
···
153
198
rootCredentialsFile = cfg.secretsFile;
154
199
};
155
200
201
+
# Configure msmtp for email sending
202
+
programs.msmtp = lib.mkIf cfg.mail.enable {
203
+
enable = true;
204
+
accounts.default = {
205
+
auth = true;
206
+
tls = cfg.mail.smtp.tls;
207
+
tls_starttls = cfg.mail.smtp.tls;
208
+
host = cfg.mail.smtp.host;
209
+
port = cfg.mail.smtp.port;
210
+
from = cfg.mail.fromAddress;
211
+
user = cfg.mail.smtp.username;
212
+
passwordeval = "${pkgs.coreutils}/bin/cat ${cfg.secretsFile} | ${pkgs.gnugrep}/bin/grep SMTP_PASSWORD | ${pkgs.coreutils}/bin/cut -d= -f2";
213
+
};
214
+
};
215
+
156
216
systemd.services.tranquil-pds = {
157
217
description = "Tranquil PDS - AT Protocol Personal Data Server";
158
218
wantedBy = [ "multi-user.target" ];
···
184
244
}
185
245
// lib.optionalAttrs cfg.redis.enable {
186
246
REDIS_URL = "redis://localhost:6379";
247
+
}
248
+
// lib.optionalAttrs cfg.mail.enable {
249
+
MAIL_FROM_ADDRESS = cfg.mail.fromAddress;
250
+
MAIL_FROM_NAME = cfg.mail.fromName;
251
+
SENDMAIL_PATH = "${pkgs.msmtp}/bin/msmtp";
187
252
};
188
253
189
254
serviceConfig = {
···
210
275
211
276
services.caddy.virtualHosts."${cfg.domain}" = {
212
277
extraConfig = ''
213
-
tls {
214
-
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
215
-
}
216
-
header {
217
-
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
218
-
}
219
-
220
-
# Serve ASCII banner for root path
221
-
handle / {
222
-
header Content-Type "text/plain; charset=utf-8"
223
-
respond `
224
-
_____ ______ _____ _____ ______ ____ _ _ _ ______
225
-
/ ____| ____| __ \|_ _| ____| | _ \| | | | | | ____|
226
-
| (___ | |__ | |__) | | | | |__ | |_) | | | | | | |__
227
-
\___ \| __| | _ / | | | __| | _ <| | | | | | __|
228
-
____) | |____| | \ \ _| |_| | | |_) | |____| |__| | |____
229
-
|_____/|______|_| \_\_____|_| |____/|______|\____/|______|
230
-
231
-
AT Protocol Personal Data Server
232
-
233
-
This is a PDS instance running on ${cfg.domain}
234
-
235
-
Powered by Tranquil PDS
236
-
https://tangled.org/lewis.moe/bspds-sandbox/
237
-
` 200
238
-
}
239
-
240
-
reverse_proxy localhost:${toString cfg.port} {
241
-
header_up X-Forwarded-Proto {scheme}
242
-
header_up X-Forwarded-For {remote}
243
-
}
278
+
tls {
279
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
280
+
}
281
+
header {
282
+
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
283
+
}
284
+
285
+
reverse_proxy localhost:${toString cfg.port} {
286
+
header_up X-Forwarded-Proto {scheme}
287
+
header_up X-Forwarded-For {remote}
288
+
}
244
289
'';
245
290
};
246
291
+35
packages/mailchannels-sendmail.nix
+35
packages/mailchannels-sendmail.nix
···
1
+
{ pkgs, lib, mailchannelsApiKey }:
2
+
3
+
pkgs.writeShellScriptBin "mailchannels-sendmail" ''
4
+
# Sendmail-compatible wrapper for MailChannels API
5
+
# Reads email from stdin and sends via MailChannels
6
+
7
+
set -euo pipefail
8
+
9
+
# Read the email from stdin
10
+
EMAIL_CONTENT=$(cat)
11
+
12
+
# Extract headers and body
13
+
FROM=$(echo "$EMAIL_CONTENT" | grep -i "^From:" | head -1 | sed 's/^From: //')
14
+
TO=$(echo "$EMAIL_CONTENT" | grep -i "^To:" | head -1 | sed 's/^To: //')
15
+
SUBJECT=$(echo "$EMAIL_CONTENT" | grep -i "^Subject:" | head -1 | sed 's/^Subject: //')
16
+
BODY=$(echo "$EMAIL_CONTENT" | sed -n '/^$/,$p' | tail -n +2)
17
+
18
+
# Send via MailChannels API
19
+
${pkgs.curl}/bin/curl -X POST https://api.mailchannels.net/tx/v1/send \
20
+
-H "Content-Type: application/json" \
21
+
-H "X-API-Key: ${mailchannelsApiKey}" \
22
+
-d @- <<EOF
23
+
{
24
+
"personalizations": [{
25
+
"to": [{"email": "$TO"}]
26
+
}],
27
+
"from": {"email": "$FROM"},
28
+
"subject": "$SUBJECT",
29
+
"content": [{
30
+
"type": "text/plain",
31
+
"value": "$BODY"
32
+
}]
33
+
}
34
+
EOF
35
+
''
secrets/tranquil-pds.age
secrets/tranquil-pds.age
This is a binary file and will not be displayed.