1{ pkgs, self, ... }:
2let
3 pns = self.lib.data.services;
4 inherit (self.lib.data) mail;
5 marvin = "http://${self.lib.data.hosts.marvin.ts.ip4}";
6 marvinIP = self.lib.data.hosts.marvin.ts.ip4;
7 inherit (self.lib.data) tsNet;
8in
9{
10 services.caddy = {
11 enable = true;
12 package = pkgs.caddy.withPlugins {
13 plugins = [
14 "github.com/caddy-dns/desec@v1.0.1"
15 "github.com/greenpau/caddy-security@v1.1.31"
16 "github.com/tailscale/caddy-tailscale@v0.0.0-20251016213337-01d084e119cb"
17 "github.com/mholt/caddy-l4@v0.0.0-20251001194302-2e3e6cf60b25"
18 "github.com/mohammed90/caddy-git-fs@v0.0.0-20240805164056-529acecd1830"
19 ];
20 hash = "sha256-kvChIK67UKn5vMFMcLszSl5AfW1BNHTRm1aXX5t5Wyc=";
21 };
22 email = "pyrox@pyrox.dev";
23 virtualHosts = {
24 "mail.pyrox.dev" = { };
25 # Redirect old domains -> pyrox.dev
26 "blog.pyrox.dev" = {
27 serverAliases = [
28 "www.pyrox.dev"
29 "thehedgehog.me"
30 ];
31 extraConfig = ''
32 redir https://pyrox.dev{uri} permanent
33 '';
34 };
35 "pyrox.dev" = {
36 extraConfig = ''
37 route {
38 header /.well-known/matrix/* Access-Control-Allow-Origin *
39 reverse_proxy /.well-known/matrix/* http://100.123.15.72:6922
40 redir /.well-known/carddav https://cloud.pyrox.dev/.well-known/carddav temporary
41 redir /.well-known/caldav https://cloud.pyrox.dev/.well-known/caldav temporary
42 header /.well-known/openpgpkey/* Access-Control-Allow-Origin *
43 header /.well-known/openpgpkey/hu/* application/octet-stream
44 respond /.well-known/openpgpkey/*/policy 200
45 header /.well-known/fursona Content-Type application/json
46 header {
47 X-Content-Type-Options nosniff
48 Permissions-Policy accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), unload=(),
49 +Permissions-Policy display-capture=(), encrypted-media=(), fullscreen=(), geolocation=(),
50 +Permissions-Policy gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(),
51 +Permissions-Policy payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(),
52 +Permissions-Policy sync-xhr=(self), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(),
53 +Permissions-Policy clipboard-write=(), gamepad=(), hid=(), idle-detection=(), interest-cohort=(), serial=()
54 X-Frame-Options SAMEORIGIN
55 Referrer-Policy origin
56 -Server
57 }
58 file_server {
59 fs blog-repo
60 hide .git
61 precompressed br gzip
62 }
63 }
64 '';
65 };
66
67 # Authentication
68 ${pns.pocket-id.extUrl} = {
69 extraConfig = ''
70 reverse_proxy ${marvin}:${toString pns.pocket-id.port}
71 '';
72 };
73
74 # Vaultwarden
75 ${pns.vaultwarden.extUrl} = {
76 extraConfig = ''
77 header / {
78 Strict-Transport-Security "max-age=31536000;"
79 X-XSS-Protection "0"
80 X-Frame-Options "DENY"
81 X-Robots-Tag "noindex, nofollow"
82 X-Content-Type-Options "nosniff"
83 -Server
84 -X-Powered-By
85 -Last-Modified
86 }
87 reverse_proxy ${marvin}:${toString pns.vaultwarden.anubis} {
88 header_up X-Real-IP {remote_host}
89 header_up X-Http-Version {http.request.proto}
90 }
91 '';
92 };
93
94 # Cinny + Conduit
95 ${pns.matrix-server.extUrl} = {
96 extraConfig = ''
97 handle /_matrix/* {
98 reverse_proxy ${marvin}:${toString pns.matrix-server.port}
99 }
100 handle {
101 root * /var/www/cinny/dist/
102 try_files {path} / index.html
103 file_server
104 }
105 '';
106 };
107 # Jellyfin
108 ${pns.jellyfin.extUrl} = {
109 extraConfig = ''
110 @blocked not remote_ip 100.64.0.0/10 private_ranges
111 reverse_proxy ${marvin}:${toString pns.jellyfin.port}
112 handle /metrics* {
113 respond @blocked "Access Denied" 403
114 }
115 '';
116 };
117
118 # Yourmother.website
119 "yourmother.website" = {
120 extraConfig = ''
121 header Content-Type text/html
122 respond 200 {
123 body `<!DOCTYPE html>
124 <html>
125 <head>
126 <meta http-equiv="Refresh" content="0; url=https://youtube.com/watch?v=oHg5SJYRHA0" />
127 </head>
128 </html>`
129 }
130 '';
131 };
132
133 # OpenPGP WKD stuff
134 "openpgpkey.pyrox.dev" = {
135 serverAliases = [ "openpgpkey.thehedgehog.me" ];
136 extraConfig = ''
137 respond /.well-known/openpgpkey/{labels.1}.{labels.0}/policy 200
138 header Access-Control-Allow-Origin *
139 header /.well-known/openpgpkey/{labels.1}.{labels.0}/hu/* Content-Type application/octet-stream
140 file_server {
141 fs blog-repo
142 }
143 '';
144 };
145
146 # Metrics
147 ":6899" = {
148 extraConfig = ''
149 metrics /metrics
150 '';
151 };
152 # SIMPLE HOSTS
153
154 # Forgejo
155 ${pns.git.extUrl} = {
156 extraConfig = ''
157 reverse_proxy ${marvin}:${toString pns.git.anubis} {
158 header_up X-Real-Ip {remote_host}
159 header_up X-Http-Version {http.request.proto}
160 }
161 '';
162 };
163
164 # Grafana
165 ${pns.grafana.extUrl} = {
166 extraConfig = ''
167 reverse_proxy ${marvin}:${toString pns.grafana.anubis} {
168 header_up X-Real-Ip {remote_host}
169 header_up X-Http-Version {http.request.proto}
170 }
171 '';
172 };
173
174 # Miniflux
175 ${pns.miniflux.extUrl} = {
176 extraConfig = ''
177 reverse_proxy ${marvin}:${toString pns.miniflux.anubis} {
178 header_up X-Real-Ip {remote_host}
179 header_up X-Http-Version {http.request.proto}
180 }
181 '';
182 };
183
184 # Nextcloud
185 ${pns.nextcloud.extUrl} = {
186 extraConfig = ''
187 reverse_proxy ${marvin}:${toString pns.nextcloud.anubis} {
188 header_up X-Real-Ip {remote_host}
189 header_up X-Http-Version {http.request.proto}
190 }
191 '';
192 };
193
194 # Nextcloud-Office(Collabora)
195 ${pns.nextcloud-office.extUrl} = {
196 extraConfig = ''
197 reverse_proxy ${marvin}:${toString pns.nextcloud-office.anubis} {
198 header_up X-Real-Ip {remote_host}
199 header_up X-Http-Version {http.request.proto}
200 }
201 '';
202 };
203
204 # Planka
205 ${pns.planka.extUrl} = {
206 extraConfig = ''
207 reverse_proxy ${marvin}:${toString pns.planka.anubis} {
208 header_up X-Real-Ip {remote_host}
209 header_up X-Http-Version {http.request.proto}
210 }
211 '';
212 };
213
214 # Pingvin Share
215 ${pns.pingvin-share.extUrl} = {
216 extraConfig = ''
217 reverse_proxy /api/* ${marvin}:${toString pns.pingvin-share.be-anubis} {
218 header_up X-Real-IP {remote_host}
219 header_up X-Http-Version {http.request.proto}
220 }
221 reverse_proxy /* ${marvin}:${toString pns.pingvin-share.anubis} {
222 header_up X-Real-IP {remote_host}
223 header_up X-Http-Version {http.request.proto}
224 }
225 '';
226 };
227 # Tangled Services
228 ${pns.tangled-knot.extUrl} = {
229 extraConfig = ''
230 reverse_proxy ${marvin}:${toString pns.tangled-knot.port}
231 '';
232 };
233 ${pns.tangled-spindle.extUrl} = {
234 extraConfig = ''
235 reverse_proxy ${marvin}:${toString pns.tangled-spindle.port}
236 '';
237 };
238
239 # Simple Tailscale Hosts
240
241 # Deemix
242 "${pns.deemix.tsHost}.${tsNet}" = {
243 extraConfig = ''
244 bind tailscale/${pns.deemix.tsHost}
245 tailscale_auth
246 reverse_proxy ${marvin}:${toString pns.deemix.port}
247 '';
248 };
249 # Pinchflat
250 "${pns.pinchflat.tsHost}.${tsNet}" = {
251 extraConfig = ''
252 bind tailscale/${pns.pinchflat.tsHost}
253 tailscale_auth
254 reverse_proxy ${marvin}:${toString pns.pinchflat.port}
255 '';
256 };
257
258 "http://mail.pyrox.dev" = {
259 serverAliases = [
260 "http://mta-sts.pyrox.dev"
261 "http://autodiscover.pyrox.dev"
262 "http://autoconfig.pyrox.dev"
263 "http://dav.pyrox.dev"
264 ];
265 extraConfig = ''
266 reverse_proxy 127.0.0.1:${toString mail.intHTTP} {
267 transport http {
268 proxy_protocol v2
269 }
270 }
271
272 '';
273 };
274 };
275 # Mail Config
276 globalConfig = ''
277 filesystem blog-repo git ${marvin}:${toString pns.git.port}/pyrox/new-blog {
278 ref refs/heads/pages
279 refresh_period 10m
280 }
281 servers :80 {
282 listener_wrappers {
283 layer4 {
284 @maildomains http host mail.pyrox.dev mta-sts.pyrox.dev autoconfig.pyrox.dev autodiscover.pyrox.dev dav.pyrox.dev
285 route @maildomains {
286 subroute {
287 @a http
288 route @a {
289 proxy {
290 proxy_protocol v2
291 upstream 127.0.0.1:${toString mail.intHTTP}
292 }
293 }
294 }
295 }
296 }
297 http_redirect
298 }
299 }
300 servers :443 {
301 listener_wrappers {
302 layer4 {
303 @maildomains tls sni mail.pyrox.dev mta-sts.pyrox.dev autoconfig.pyrox.dev autodiscover.pyrox.dev dav.pyrox.dev
304 route @maildomains {
305 proxy {
306 proxy_protocol v2
307 upstream 127.0.0.1:${toString mail.intHTTPS}
308 }
309 }
310 }
311 tls
312 }
313 }
314 layer4 {
315 :22 {
316 @a ssh
317 route @a {
318 proxy {
319 upstream ${marvinIP}:2222
320 }
321 }
322 }
323 :25 {
324 route {
325 proxy {
326 proxy_protocol v2
327 upstream 127.0.0.1:40025
328 }
329 }
330 }
331 :143 {
332 route {
333 proxy {
334 proxy_protocol v2
335 upstream 127.0.0.1:${toString mail.intIMAP}
336 }
337 }
338 }
339 :465 {
340 route {
341 proxy {
342 proxy_protocol v2
343 upstream 127.0.0.1:${toString mail.intSMTPS}
344 }
345 }
346 }
347 :587 {
348 route {
349 proxy {
350 proxy_protocol v2
351 upstream 127.0.0.1:${toString mail.intSMTP}
352 }
353 }
354 }
355 :993 {
356 route {
357 proxy {
358 proxy_protocol v2
359 upstream 127.0.0.1:${toString mail.intIMAPS}
360 }
361 }
362 }
363 :4190 {
364 route {
365 proxy {
366 proxy_protocol v2
367 upstream 127.0.0.1:${toString mail.intManageSieve}
368 }
369 }
370 }
371 }
372 '';
373 };
374 systemd.services.caddy.serviceConfig.CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
375 systemd.services.caddy.serviceConfig.AmbientCapabilities = "CAP_NET_BIND_SERVICE";
376}