1{ pkgs, lib, ... }:
2let
3 pns = lib.py.data.services;
4 mail = lib.py.data.mail;
5 marvin = "http://${lib.py.data.hosts.marvin.ts.ip4}";
6 marvinIP = lib.py.data.hosts.marvin.ts.ip4;
7 tsNet = lib.py.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-20250207163903-69a970c84556"
17 "github.com/mholt/caddy-l4@v0.0.0-20250428144642-57989befb7e6"
18 "github.com/mohammed90/caddy-git-fs@v0.0.0-20240805164056-529acecd1830"
19 ];
20 hash = "sha256-NPMrt7hARdnXP3xh4TRsc+TMpuXZzwY6zdrTw6E3nSM=";
21 };
22 email = "pyrox@pyrox.dev";
23 virtualHosts = {
24 # Redirect old domains -> pyrox.dev
25 "blog.pyrox.dev" = {
26 serverAliases = [
27 "www.pyrox.dev"
28 "thehedgehog.me"
29 ];
30 extraConfig = ''
31 redir https://pyrox.dev{uri} permanent
32 '';
33 };
34 "pyrox.dev" = {
35 extraConfig = ''
36 route {
37 header /.well-known/matrix/* Access-Control-Allow-Origin *
38 reverse_proxy /.well-known/matrix/* http://100.123.15.72:6922
39 redir /.well-known/carddav https://cloud.pyrox.dev/.well-known/carddav temporary
40 redir /.well-known/caldav https://cloud.pyrox.dev/.well-known/caldav temporary
41 header /.well-known/openpgpkey/* Access-Control-Allow-Origin *
42 header /.well-known/openpgpkey/hu/* application/octet-stream
43 respond /.well-known/openpgpkey/*/policy 200
44 header /.well-known/fursona Content-Type application/json
45 header {
46 X-Content-Type-Options nosniff
47 Permissions-Policy accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), unload=(),
48 +Permissions-Policy display-capture=(), encrypted-media=(), fullscreen=(), geolocation=(),
49 +Permissions-Policy gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(),
50 +Permissions-Policy payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(),
51 +Permissions-Policy sync-xhr=(self), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(),
52 +Permissions-Policy clipboard-write=(), gamepad=(), hid=(), idle-detection=(), interest-cohort=(), serial=()
53 X-Frame-Options SAMEORIGIN
54 Referrer-Policy origin
55 -Server
56 }
57 file_server {
58 fs blog-repo
59 hide .git
60 precompressed br gzip
61 }
62 }
63 '';
64 };
65
66 # Authentication
67 ${pns.pocket-id.extUrl} = {
68 extraConfig = ''
69 reverse_proxy / ${marvin}:${toString pns.pocket-id.anubis} {
70 header_up X-Real-IP {remote_host}
71 header_up X-Http-Version {http.request.proto}
72 }
73 '';
74 };
75
76 # Vaultwarden
77 ${pns.vaultwarden.extUrl} = {
78 extraConfig = ''
79 header / {
80 Strict-Transport-Security "max-age=31536000;"
81 X-XSS-Protection "0"
82 X-Frame-Options "DENY"
83 X-Robots-Tag "noindex, nofollow"
84 X-Content-Type-Options "nosniff"
85 -Server
86 -X-Powered-By
87 -Last-Modified
88 }
89 reverse_proxy ${marvin}:${toString pns.vaultwarden.anubis} {
90 header_up X-Real-IP {remote_host}
91 header_up X-Http-Version {http.request.proto}
92 }
93 '';
94 };
95
96 # Cinny + Conduit
97 ${pns.matrix-server.extUrl} = {
98 extraConfig = ''
99 handle /_matrix/* {
100 reverse_proxy ${marvin}:${toString pns.matrix-server.port}
101 }
102 handle {
103 root * /var/www/cinny/dist/
104 try_files {path} / index.html
105 file_server
106 }
107 '';
108 };
109 # Jellyfin
110 ${pns.jellyfin.extUrl} = {
111 extraConfig = ''
112 @blocked not remote_ip 100.64.0.0/10 private_ranges
113 reverse_proxy ${marvin}:${toString pns.jellyfin.port}
114 handle /metrics* {
115 respond @blocked "Access Denied" 403
116 }
117 '';
118 };
119
120 # MTA-STS Setup for mailserver
121 "mta-sts.pyrox.dev" = {
122 extraConfig = ''
123 header Content-Type text/plain; charset=utf-8
124 respond /.well-known/mta-sts.txt <<END
125 version: STSv1
126 mode: enforce
127 mx: mail.pyrox.dev
128 mx:mail2.pyrox.dev
129 max_age: 2419200
130 END 200
131 '';
132 };
133
134 # Yourmother.website
135 "yourmother.website" = {
136 extraConfig = ''
137 header Content-Type text/html
138 respond 200 {
139 body `<!DOCTYPE html>
140 <html>
141 <head>
142 <meta http-equiv="Refresh" content="0; url=https://youtube.com/watch?v=oHg5SJYRHA0" />
143 </head>
144 </html>`
145 }
146 '';
147 };
148
149 # OpenPGP WKD stuff
150 "openpgpkey.pyrox.dev" = {
151 serverAliases = [ "openpgpkey.thehedgehog.me" ];
152 extraConfig = ''
153 respond /.well-known/openpgpkey/{labels.1}.{labels.0}/policy 200
154 header Access-Control-Allow-Origin *
155 header /.well-known/openpgpkey/{labels.1}.{labels.0}/hu/* Content-Type application/octet-stream
156 file_server {
157 fs blog-repo
158 }
159 '';
160 };
161
162 # Metrics
163 ":6899" = {
164 extraConfig = ''
165 metrics /metrics
166 '';
167 };
168 # SIMPLE HOSTS
169
170 # Forgejo
171 ${pns.git.extUrl} = {
172 extraConfig = ''
173 reverse_proxy ${marvin}:${toString pns.git.anubis} {
174 header_up X-Real-Ip {remote_host}
175 header_up X-Http-Version {http.request.proto}
176 }
177 '';
178 };
179
180 # Grafana
181 ${pns.grafana.extUrl} = {
182 extraConfig = ''
183 reverse_proxy ${marvin}:${toString pns.grafana.anubis} {
184 header_up X-Real-Ip {remote_host}
185 header_up X-Http-Version {http.request.proto}
186 }
187 '';
188 };
189
190 # Miniflux
191 ${pns.miniflux.extUrl} = {
192 extraConfig = ''
193 reverse_proxy ${marvin}:${toString pns.miniflux.anubis} {
194 header_up X-Real-Ip {remote_host}
195 header_up X-Http-Version {http.request.proto}
196 }
197 '';
198 };
199
200 # Nextcloud
201 ${pns.nextcloud.extUrl} = {
202 extraConfig = ''
203 reverse_proxy ${marvin}:${toString pns.nextcloud.anubis} {
204 header_up X-Real-Ip {remote_host}
205 header_up X-Http-Version {http.request.proto}
206 }
207 '';
208 };
209
210 # Nextcloud-Office(Collabora)
211 ${pns.nextcloud-office.extUrl} = {
212 extraConfig = ''
213 reverse_proxy ${marvin}:${toString pns.nextcloud-office.anubis} {
214 header_up X-Real-Ip {remote_host}
215 header_up X-Http-Version {http.request.proto}
216 }
217 '';
218 };
219
220 # Planka
221 ${pns.planka.extUrl} = {
222 extraConfig = ''
223 reverse_proxy ${marvin}:${toString pns.planka.anubis} {
224 header_up X-Real-Ip {remote_host}
225 header_up X-Http-Version {http.request.proto}
226 }
227 '';
228 };
229
230 # Pingvin Share
231 ${pns.pingvin-share.extUrl} = {
232 extraConfig = ''
233 reverse_proxy /api/* ${marvin}:${toString pns.pingvin-share.be-anubis} {
234 header_up X-Real-IP {remote_host}
235 header_up X-Http-Version {http.request.proto}
236 }
237 reverse_proxy /* ${marvin}:${toString pns.pingvin-share.anubis} {
238 header_up X-Real-IP {remote_host}
239 header_up X-Http-Version {http.request.proto}
240 }
241 '';
242 };
243
244 # Simple Tailscale Hosts
245
246 # Deemix
247 "${pns.deemix.tsHost}.${tsNet}" = {
248 extraConfig = ''
249 bind tailscale/${pns.deemix.tsHost}
250 tailscale_auth
251 reverse_proxy ${marvin}:${toString pns.deemix.port}
252 '';
253 };
254 # Pinchflat
255 "${pns.pinchflat.tsHost}.${tsNet}" = {
256 extraConfig = ''
257 bind tailscale/${pns.pinchflat.tsHost}
258 tailscale_auth
259 reverse_proxy ${marvin}:${toString pns.pinchflat.port}
260 '';
261 };
262 # "mail.pyrox.dev:80" = {
263 # extraConfig = ''
264 # reverse_proxy ${marvin}:${mail.intHTTP}
265 # '';
266 # };
267 };
268 # Mail Config
269 globalConfig = ''
270 filesystem blog-repo git ${marvin}:${toString pns.git.port}/pyrox/new-blog {
271 ref refs/heads/pages
272 refresh_period 10m
273 }
274 '';
275 # layer4 {
276 # 0.0.0.0:465 {
277 # route {
278 # proxy {
279 # proxy_protocol v2
280 # upstream ${marvinIP}:${mail.intSMTPS}
281 # }
282 # }
283 # }
284 # 0.0.0.0:993 {
285 # route {
286 # proxy {
287 # proxy_protocol v2
288 # upstream ${marvinIP}:${mail.intIMAPS}
289 # }
290 # }
291 # }
292 # 0.0.0.0:4190 {
293 # route {
294 # proxy {
295 # proxy_protocol v2
296 # upstream ${marvinIP}:${mail.intManageSieve}
297 # }
298 # }
299 # }
300 # }
301 };
302 systemd.services.caddy.serviceConfig.CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
303 systemd.services.caddy.serviceConfig.AmbientCapabilities = "CAP_NET_BIND_SERVICE";
304}