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 header_up X-Real-IP {remote_host}
72 header_up X-Http-Version {http.request.proto}
73 }
74 '';
75 };
76
77 # Vaultwarden
78 ${pns.vaultwarden.extUrl} = {
79 extraConfig = ''
80 header / {
81 Strict-Transport-Security "max-age=31536000;"
82 X-XSS-Protection "0"
83 X-Frame-Options "DENY"
84 X-Robots-Tag "noindex, nofollow"
85 X-Content-Type-Options "nosniff"
86 -Server
87 -X-Powered-By
88 -Last-Modified
89 }
90 reverse_proxy ${marvin}:${toString pns.vaultwarden.anubis} {
91 header_up X-Real-IP {remote_host}
92 header_up X-Http-Version {http.request.proto}
93 }
94 '';
95 };
96
97 # Cinny + Conduit
98 ${pns.matrix-server.extUrl} = {
99 extraConfig = ''
100 handle /_matrix/* {
101 reverse_proxy ${marvin}:${toString pns.matrix-server.port}
102 }
103 handle {
104 root * /var/www/cinny/dist/
105 try_files {path} / index.html
106 file_server
107 }
108 '';
109 };
110 # Jellyfin
111 ${pns.jellyfin.extUrl} = {
112 extraConfig = ''
113 @blocked not remote_ip 100.64.0.0/10 private_ranges
114 reverse_proxy ${marvin}:${toString pns.jellyfin.port}
115 handle /metrics* {
116 respond @blocked "Access Denied" 403
117 }
118 '';
119 };
120
121 # Yourmother.website
122 "yourmother.website" = {
123 extraConfig = ''
124 header Content-Type text/html
125 respond 200 {
126 body `<!DOCTYPE html>
127 <html>
128 <head>
129 <meta http-equiv="Refresh" content="0; url=https://youtube.com/watch?v=oHg5SJYRHA0" />
130 </head>
131 </html>`
132 }
133 '';
134 };
135
136 # OpenPGP WKD stuff
137 "openpgpkey.pyrox.dev" = {
138 serverAliases = [ "openpgpkey.thehedgehog.me" ];
139 extraConfig = ''
140 respond /.well-known/openpgpkey/{labels.1}.{labels.0}/policy 200
141 header Access-Control-Allow-Origin *
142 header /.well-known/openpgpkey/{labels.1}.{labels.0}/hu/* Content-Type application/octet-stream
143 file_server {
144 fs blog-repo
145 }
146 '';
147 };
148
149 # Metrics
150 ":6899" = {
151 extraConfig = ''
152 metrics /metrics
153 '';
154 };
155 # SIMPLE HOSTS
156
157 # Forgejo
158 ${pns.git.extUrl} = {
159 extraConfig = ''
160 reverse_proxy ${marvin}:${toString pns.git.anubis} {
161 header_up X-Real-Ip {remote_host}
162 header_up X-Http-Version {http.request.proto}
163 }
164 '';
165 };
166
167 # Grafana
168 ${pns.grafana.extUrl} = {
169 extraConfig = ''
170 reverse_proxy ${marvin}:${toString pns.grafana.anubis} {
171 header_up X-Real-Ip {remote_host}
172 header_up X-Http-Version {http.request.proto}
173 }
174 '';
175 };
176
177 # Miniflux
178 ${pns.miniflux.extUrl} = {
179 extraConfig = ''
180 reverse_proxy ${marvin}:${toString pns.miniflux.anubis} {
181 header_up X-Real-Ip {remote_host}
182 header_up X-Http-Version {http.request.proto}
183 }
184 '';
185 };
186
187 # Nextcloud
188 ${pns.nextcloud.extUrl} = {
189 extraConfig = ''
190 reverse_proxy ${marvin}:${toString pns.nextcloud.anubis} {
191 header_up X-Real-Ip {remote_host}
192 header_up X-Http-Version {http.request.proto}
193 }
194 '';
195 };
196
197 # Nextcloud-Office(Collabora)
198 ${pns.nextcloud-office.extUrl} = {
199 extraConfig = ''
200 reverse_proxy ${marvin}:${toString pns.nextcloud-office.anubis} {
201 header_up X-Real-Ip {remote_host}
202 header_up X-Http-Version {http.request.proto}
203 }
204 '';
205 };
206
207 # Planka
208 ${pns.planka.extUrl} = {
209 extraConfig = ''
210 reverse_proxy ${marvin}:${toString pns.planka.anubis} {
211 header_up X-Real-Ip {remote_host}
212 header_up X-Http-Version {http.request.proto}
213 }
214 '';
215 };
216
217 # Pingvin Share
218 ${pns.pingvin-share.extUrl} = {
219 extraConfig = ''
220 reverse_proxy /api/* ${marvin}:${toString pns.pingvin-share.be-anubis} {
221 header_up X-Real-IP {remote_host}
222 header_up X-Http-Version {http.request.proto}
223 }
224 reverse_proxy /* ${marvin}:${toString pns.pingvin-share.anubis} {
225 header_up X-Real-IP {remote_host}
226 header_up X-Http-Version {http.request.proto}
227 }
228 '';
229 };
230 # Tangled Services
231 ${pns.tangled-knot.extUrl} = {
232 extraConfig = ''
233 reverse_proxy ${marvin}:${toString pns.tangled-knot.port}
234 '';
235 };
236 ${pns.tangled-spindle.extUrl} = {
237 extraConfig = ''
238 reverse_proxy ${marvin}:${toString pns.tangled-spindle.port}
239 '';
240 };
241
242 # Simple Tailscale Hosts
243
244 # Deemix
245 "${pns.deemix.tsHost}.${tsNet}" = {
246 extraConfig = ''
247 bind tailscale/${pns.deemix.tsHost}
248 tailscale_auth
249 reverse_proxy ${marvin}:${toString pns.deemix.port}
250 '';
251 };
252 # Pinchflat
253 "${pns.pinchflat.tsHost}.${tsNet}" = {
254 extraConfig = ''
255 bind tailscale/${pns.pinchflat.tsHost}
256 tailscale_auth
257 reverse_proxy ${marvin}:${toString pns.pinchflat.port}
258 '';
259 };
260
261 "http://mail.pyrox.dev" = {
262 serverAliases = [
263 "http://mta-sts.pyrox.dev"
264 "http://autodiscover.pyrox.dev"
265 "http://autoconfig.pyrox.dev"
266 "http://dav.pyrox.dev"
267 ];
268 extraConfig = ''
269 reverse_proxy 127.0.0.1:${toString mail.intHTTP} {
270 transport http {
271 proxy_protocol v2
272 }
273 }
274
275 '';
276 };
277 };
278 # Mail Config
279 globalConfig = ''
280 filesystem blog-repo git ${marvin}:${toString pns.git.port}/pyrox/new-blog {
281 ref refs/heads/pages
282 refresh_period 10m
283 }
284 servers :80 {
285 listener_wrappers {
286 layer4 {
287 @maildomains http host mail.pyrox.dev mta-sts.pyrox.dev autoconfig.pyrox.dev autodiscover.pyrox.dev dav.pyrox.dev
288 route @maildomains {
289 subroute {
290 @a http
291 route @a {
292 proxy {
293 proxy_protocol v2
294 upstream 127.0.0.1:${toString mail.intHTTP}
295 }
296 }
297 }
298 }
299 }
300 http_redirect
301 }
302 }
303 servers :443 {
304 listener_wrappers {
305 layer4 {
306 @maildomains tls sni mail.pyrox.dev mta-sts.pyrox.dev autoconfig.pyrox.dev autodiscover.pyrox.dev dav.pyrox.dev
307 route @maildomains {
308 proxy {
309 proxy_protocol v2
310 upstream 127.0.0.1:${toString mail.intHTTPS}
311 }
312 }
313 }
314 tls
315 }
316 }
317 layer4 {
318 :22 {
319 @a ssh
320 route @a {
321 proxy {
322 upstream ${marvinIP}:2222
323 }
324 }
325 }
326 :25 {
327 route {
328 proxy {
329 proxy_protocol v2
330 upstream 127.0.0.1:40025
331 }
332 }
333 }
334 :143 {
335 route {
336 proxy {
337 proxy_protocol v2
338 upstream 127.0.0.1:${toString mail.intIMAP}
339 }
340 }
341 }
342 :465 {
343 route {
344 proxy {
345 proxy_protocol v2
346 upstream 127.0.0.1:${toString mail.intSMTPS}
347 }
348 }
349 }
350 :587 {
351 route {
352 proxy {
353 proxy_protocol v2
354 upstream 127.0.0.1:${toString mail.intSMTP}
355 }
356 }
357 }
358 :993 {
359 route {
360 proxy {
361 proxy_protocol v2
362 upstream 127.0.0.1:${toString mail.intIMAPS}
363 }
364 }
365 }
366 :4190 {
367 route {
368 proxy {
369 proxy_protocol v2
370 upstream 127.0.0.1:${toString mail.intManageSieve}
371 }
372 }
373 }
374 }
375 '';
376 };
377 systemd.services.caddy.serviceConfig.CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
378 systemd.services.caddy.serviceConfig.AmbientCapabilities = "CAP_NET_BIND_SERVICE";
379}