system-manager-wakasu
1{
2 libx,
3 globals,
4 lib,
5 pkgs,
6 config,
7 ...
8}:
9{
10 age.secrets."gandi.env" = {
11 file = ../../secrets/rhea/gandi.env.age;
12 mode = "400";
13 owner = "traefik";
14 group = "traefik";
15 };
16
17 users.users.vincent.linger = true;
18
19 services = {
20 traefik = {
21 enable = true;
22
23 staticConfigOptions = {
24 # Entry points
25 entryPoints = {
26 web = {
27 address = ":80";
28 http.redirections.entryPoint = {
29 to = "websecure";
30 scheme = "https";
31 };
32 };
33 websecure = {
34 address = ":443";
35 };
36 };
37
38 # Certificate resolver using Gandi DNS
39 certificatesResolvers.letsencrypt = {
40 acme = {
41 email = "vincent@sbr.pm";
42 storage = "/var/lib/traefik/acme.json";
43 dnsChallenge = {
44 provider = "gandiv5";
45 delayBeforeCheck = "0s";
46 resolvers = [
47 "1.1.1.1:53"
48 "8.8.8.8:53"
49 ];
50 };
51 };
52 };
53 };
54
55 # Dynamic configuration using module option
56 dynamicConfigOptions =
57 let
58 # Filter machines that have syncthing configured
59 syncthingMachines = lib.filterAttrs (
60 _name: machine: machine ? syncthing && machine.syncthing ? folders
61 ) globals.machines;
62
63 # Generate routers for syncthing hosts
64 syncthingRouters = lib.mapAttrs' (
65 name: _machine:
66 lib.nameValuePair "syncthing-${name}" {
67 rule = "Host(`syncthing.sbr.pm`) && PathPrefix(`/${name}`) || Host(`s.sbr.pm`) && PathPrefix(`/${name}`)";
68 service = "syncthing-${name}";
69 entryPoints = [ "websecure" ];
70 middlewares = [ "syncthing-${name}-strip" ];
71 tls = {
72 certResolver = "letsencrypt";
73 };
74 }
75 ) syncthingMachines;
76
77 # Generate services for syncthing hosts
78 syncthingServices = lib.mapAttrs' (
79 name: machine:
80 lib.nameValuePair "syncthing-${name}" {
81 loadBalancer = {
82 servers = [
83 { url = "http://${builtins.head machine.net.vpn.ips}:8384"; }
84 ];
85 };
86 }
87 ) syncthingMachines;
88
89 # Generate middleware for path stripping
90 syncthingMiddlewares = lib.mapAttrs' (
91 name: _machine:
92 lib.nameValuePair "syncthing-${name}-strip" {
93 stripPrefix = {
94 prefixes = [ "/${name}" ];
95 };
96 }
97 ) syncthingMachines;
98 in
99 {
100 http = {
101 routers = syncthingRouters // {
102 jellyfin = {
103 rule = "Host(`jellyfin.sbr.pm`)";
104 service = "jellyfin";
105 entryPoints = [ "websecure" ];
106 tls = {
107 certResolver = "letsencrypt";
108 };
109 };
110 jellyseerr = {
111 rule = "Host(`jellyseerr.sbr.pm`)";
112 service = "jellyseerr";
113 entryPoints = [ "websecure" ];
114 tls = {
115 certResolver = "letsencrypt";
116 };
117 };
118 sonarr = {
119 rule = "Host(`sonarr.sbr.pm`)";
120 service = "sonarr";
121 entryPoints = [ "websecure" ];
122 tls = {
123 certResolver = "letsencrypt";
124 };
125 };
126 radarr = {
127 rule = "Host(`radarr.sbr.pm`)";
128 service = "radarr";
129 entryPoints = [ "websecure" ];
130 tls = {
131 certResolver = "letsencrypt";
132 };
133 };
134 lidarr = {
135 rule = "Host(`lidarr.sbr.pm`)";
136 service = "lidarr";
137 entryPoints = [ "websecure" ];
138 tls = {
139 certResolver = "letsencrypt";
140 };
141 };
142 bazarr = {
143 rule = "Host(`bazarr.sbr.pm`)";
144 service = "bazarr";
145 entryPoints = [ "websecure" ];
146 tls = {
147 certResolver = "letsencrypt";
148 };
149 };
150 transmission = {
151 rule = "Host(`transmission.sbr.pm`) || Host(`t.sbr.pm`)";
152 service = "transmission";
153 entryPoints = [ "websecure" ];
154 tls = {
155 certResolver = "letsencrypt";
156 };
157 };
158 };
159 services = syncthingServices // {
160 jellyfin = {
161 loadBalancer = {
162 servers = [
163 { url = "http://localhost:8096"; }
164 ];
165 };
166 };
167 jellyseerr = {
168 loadBalancer = {
169 servers = [
170 { url = "http://localhost:5055"; }
171 ];
172 };
173 };
174 sonarr = {
175 loadBalancer = {
176 servers = [
177 { url = "http://localhost:8989"; }
178 ];
179 };
180 };
181 radarr = {
182 loadBalancer = {
183 servers = [
184 { url = "http://localhost:7878"; }
185 ];
186 };
187 };
188 lidarr = {
189 loadBalancer = {
190 servers = [
191 { url = "http://localhost:8686"; }
192 ];
193 };
194 };
195 bazarr = {
196 loadBalancer = {
197 servers = [
198 { url = "http://localhost:6767"; }
199 ];
200 };
201 };
202 transmission = {
203 loadBalancer = {
204 servers = [
205 { url = "http://localhost:9091"; }
206 ];
207 };
208 };
209 };
210 middlewares = syncthingMiddlewares;
211 };
212 };
213 };
214
215 wireguard = {
216 enable = true;
217 ips = libx.wg-ips globals.machines.rhea.net.vpn.ips;
218 endpoint = "${globals.net.vpn.endpoint}";
219 endpointPublicKey = "${globals.machines.kerkouane.net.vpn.pubkey}";
220 };
221 # smartd = {
222 # enable = true;
223 # devices = [ { device = "/dev/nvme0n1"; } ];
224 # };
225 samba.settings = {
226 settings = {
227 "backup" = {
228 path = "/neo/backup";
229 public = true;
230 browseable = "yes";
231 "read only" = "no";
232 "guest ok" = "yes";
233 writable = true;
234 comment = "backup";
235 "create mask" = "0644";
236 "directory mask" = "0755";
237 "force user" = "vincent";
238 "force group" = "users";
239 };
240 "documents" = {
241 path = "/neo/documents";
242 public = true;
243 browseable = "yes";
244 "read only" = "no";
245 "guest ok" = "yes";
246 writable = true;
247 comment = "documents";
248 "create mask" = "0644";
249 "directory mask" = "0755";
250 "force user" = "vincent";
251 "force group" = "users";
252 };
253 "downloads" = {
254 path = "/neo/downloads";
255 public = true;
256 browseable = "yes";
257 "read only" = "no";
258 "guest ok" = "yes";
259 writable = true;
260 comment = "downloads";
261 "create mask" = "0644";
262 "directory mask" = "0755";
263 "force user" = "vincent";
264 "force group" = "users";
265 };
266 "music" = {
267 path = "/neo/music";
268 public = true;
269 browseable = "yes";
270 "read only" = "no";
271 "guest ok" = "yes";
272 writable = true;
273 comment = "music";
274 "create mask" = "0644";
275 "directory mask" = "0755";
276 "force user" = "vincent";
277 "force group" = "users";
278 };
279 "pictures" = {
280 path = "/neo/pictures";
281 public = true;
282 browseable = "yes";
283 "read only" = "no";
284 "guest ok" = "yes";
285 writable = true;
286 comment = "pictures";
287 "create mask" = "0644";
288 "directory mask" = "0755";
289 "force user" = "vincent";
290 "force group" = "users";
291 };
292 "videos" = {
293 path = "/neo/videos";
294 public = true;
295 browseable = "yes";
296 "read only" = "no";
297 "guest ok" = "yes";
298 writable = true;
299 comment = "videos";
300 "create mask" = "0644";
301 "directory mask" = "0755";
302 "force user" = "vincent";
303 "force group" = "users";
304 };
305 };
306 };
307 nfs.server = {
308 enable = true;
309 exports = ''
310 /neo 192.168.1.0/24(rw,fsid=0,no_subtree_check) 10.100.0.0/24(rw,fsid=0,no_subtree_check)
311 /neo/backup 192.168.1.0/24(rw,fsid=1,no_subtree_check) 10.100.0.0/24(rw,fsid=1,no_subtree_check)
312 /neo/documents 192.168.1.0/24(rw,fsid=2,no_subtree_check) 10.100.0.0/24(rw,fsid=2,no_subtree_check)
313 /neo/downloads 192.168.1.0/24(rw,fsid=2,no_subtree_check) 10.100.0.0/24(rw,fsid=2,no_subtree_check)
314 /neo/music 192.168.1.0/24(rw,fsid=2,no_subtree_check) 10.100.0.0/24(rw,fsid=2,no_subtree_check)
315 /neo/pictures 192.168.1.0/24(rw,fsid=2,no_subtree_check) 10.100.0.0/24(rw,fsid=2,no_subtree_check)
316 /neo/videos 192.168.1.0/24(rw,fsid=2,no_subtree_check) 10.100.0.0/24(rw,fsid=2,no_subtree_check)
317 '';
318 };
319 # immich = {
320 # enable = true;
321 # user = "vincent";
322 # group = "users";
323 # mediaLocation = "/neo/pictures/photos";
324 # };
325 jellyfin = {
326 enable = true;
327 user = "vincent";
328 group = "users";
329 openFirewall = true;
330 };
331 jellyseerr = {
332 enable = true;
333 openFirewall = true;
334 };
335 aria2 = {
336 # FIXME: make sure aria2 runs as user vincent
337 enable = true;
338 openPorts = true;
339 settings = {
340 max-concurrent-downloads = 20;
341 dir = "/neo/downloads";
342 };
343 rpcSecretFile = "${pkgs.writeText "aria" "aria2rpc\n"}"; # FIXME: use secrets for this somehow
344 };
345 transmission = {
346 enable = true;
347 user = "vincent";
348 group = "users";
349 openFirewall = true;
350 package = pkgs.transmission_4;
351 openRPCPort = true; # Open firewall for RPC
352 home = "/neo/torrents";
353 settings = {
354 # Override default settings
355 incomplete-dir-enabled = true;
356 rpc-bind-address = "0.0.0.0"; # Bind to own IP
357 rpc-host-whitelist = "localhost,t.sbr.pm,transmission.sbr.pm,rhea.home,rhea.vpn,rhea.sbr.pm,192.168.1.50,10.100.0.50";
358 rpc-host-whitelist-enabled = true;
359 rpc-whitelist-enabled = true;
360 rpc-whitelist = "127.0.0.1,192.168.1.*,10.100.0.*"; # Whitelist your remote machine (10.0.0.1 in this example)
361 rpc-username = "transmission";
362 rpc-password = "transmission";
363 };
364 };
365 sonarr = {
366 enable = true;
367 user = "vincent";
368 group = "users";
369 openFirewall = true;
370 };
371 radarr = {
372 enable = true;
373 user = "vincent";
374 group = "users";
375 openFirewall = true;
376 };
377 bazarr = {
378 enable = true;
379 user = "vincent";
380 group = "users";
381 openFirewall = true;
382 };
383 prowlarr = {
384 enable = true;
385 openFirewall = true;
386 };
387 readarr = {
388 enable = true;
389 user = "vincent";
390 group = "users";
391 openFirewall = true;
392 };
393 lidarr = {
394 enable = true;
395 user = "vincent";
396 group = "users";
397 openFirewall = true;
398 };
399 };
400
401 security.acme = {
402 acceptTerms = true;
403 email = "vincent@sbr.pm";
404 };
405
406 networking.useDHCP = lib.mkDefault true;
407
408 # Open firewall for Traefik
409 networking.firewall.allowedTCPPorts = [
410 80
411 443
412 ];
413
414 # Environment file for Gandi API key (managed by agenix)
415 systemd.services.traefik.serviceConfig = {
416 EnvironmentFile = config.age.secrets."gandi.env".path;
417 };
418
419 environment.systemPackages = with pkgs; [
420 lm_sensors
421 gnumake
422 ];
423
424}