main
1{
2 lib,
3 config,
4 globals,
5 ...
6}:
7let
8 rheaIPs = globals.machines.rhea.net.ips;
9 rheaVPNIPs = globals.machines.rhea.net.vpn.ips;
10 rheaDomains = globals.machines.rhea.net.names;
11
12 # Build allowed hosts list from all rhea addresses
13 allowedHosts = lib.concatStringsSep "," (
14 rheaDomains
15 ++ rheaIPs
16 ++ rheaVPNIPs
17 ++ [
18 "127.0.0.1"
19 "localhost"
20 "homepage.sbr.pm"
21 ]
22 );
23
24 # Extract syncthing machines from globals
25 syncthingMachines = lib.filterAttrs (
26 _name: machine: machine ? syncthing && machine.syncthing ? folders
27 ) globals.machines;
28
29 # Generate syncthing service entries
30 syncthingServices = lib.mapAttrsToList (name: _machine: {
31 "Syncthing (${name})" = {
32 description = "Syncthing on ${name}";
33 href = "https://syncthing.sbr.pm/${name}/";
34 icon = "syncthing.png";
35 ping = "https://syncthing.sbr.pm/${name}/";
36 statusStyle = "dot";
37 };
38 }) syncthingMachines;
39in
40{
41 # Homepage Dashboard - Homelab Services Overview
42 # https://gethomepage.dev
43 #
44 # This module provides a centralized dashboard for monitoring homelab services.
45 # Services are configured with widgets for real-time statistics where supported.
46 #
47 # TODO: Migrate Homepage to aion when that server is set up for web services
48
49 services.homepage-dashboard = {
50 enable = true;
51 listenPort = 3001;
52
53 settings = {
54 title = "Homelab";
55 favicon = "https://gethomepage.dev/img/favicon.ico";
56 theme = "dark";
57 color = "slate";
58
59 layout = [
60 {
61 "Quick Links" = {
62 style = "row";
63 columns = 5;
64 };
65 }
66 {
67 Media = {
68 style = "row";
69 columns = 3;
70 };
71 }
72 {
73 "Download Management" = {
74 style = "row";
75 columns = 3;
76 };
77 }
78 {
79 "Torrent Clients" = {
80 style = "row";
81 columns = 2;
82 };
83 }
84 {
85 Synchronization = {
86 style = "row";
87 columns = 4;
88 };
89 }
90 {
91 Infrastructure = {
92 style = "row";
93 columns = 2;
94 };
95 }
96 ];
97 };
98
99 services = [
100 {
101 "Quick Links" = [
102 {
103 "Notifications" = {
104 description = "GitHub Notifications";
105 href = "https://github.com/notifications";
106 icon = "github.png";
107 };
108 }
109 {
110 "Issues" = {
111 description = "GitHub Issues";
112 href = "https://github.com/issues";
113 icon = "github.png";
114 };
115 }
116 {
117 "Pull Requests" = {
118 description = "GitHub PRs";
119 href = "https://github.com/pulls";
120 icon = "github.png";
121 };
122 }
123 {
124 "NixOS Packages" = {
125 description = "Package Search";
126 href = "https://search.nixos.org/packages";
127 icon = "nixos.png";
128 };
129 }
130 {
131 "NixOS Options" = {
132 description = "Options Search";
133 href = "https://search.nixos.org/options";
134 icon = "nixos.png";
135 };
136 }
137 {
138 "Home Manager" = {
139 description = "Options Search";
140 href = "https://home-manager-options.extranix.com";
141 icon = "nixos.png";
142 };
143 }
144 {
145 SourceHut = {
146 description = "~vdemeester";
147 href = "https://sr.ht/~vdemeester";
148 icon = "si-sourcehut";
149 };
150 }
151 {
152 Codeberg = {
153 description = "vdemeester";
154 href = "https://codeberg.org/vdemeester";
155 icon = "codeberg.png";
156 };
157 }
158 {
159 "Reading Triage" = {
160 description = "Triage Report";
161 href = "https://reading.sbr.pm/triage-report.html";
162 icon = "si-bookstack";
163 };
164 }
165 {
166 Calendar = {
167 description = "Google Calendar";
168 href = "https://calendar.google.com";
169 icon = "google-calendar.png";
170 };
171 }
172 {
173 Gmail = {
174 description = "Google Mail";
175 href = "https://mail.google.com";
176 icon = "gmail.png";
177 };
178 }
179 ];
180 }
181 {
182 Media = [
183 {
184 Jellyfin = {
185 description = "Media Server";
186 href = "https://jellyfin.sbr.pm";
187 icon = "jellyfin.png";
188 ping = "https://jellyfin.sbr.pm";
189 statusStyle = "dot";
190 };
191 }
192 {
193 Jellyseerr = {
194 description = "Media Requests";
195 href = "https://jellyseerr.sbr.pm";
196 icon = "jellyseerr.png";
197 ping = "https://jellyseerr.sbr.pm";
198 statusStyle = "dot";
199 };
200 }
201 {
202 Navidrome = {
203 description = "Music Streaming";
204 href = "https://music.sbr.pm";
205 icon = "navidrome.png";
206 ping = "https://music.sbr.pm";
207 statusStyle = "dot";
208 };
209 }
210 {
211 Audiobookshelf = {
212 description = "Podcasts & Audiobooks";
213 href = "https://podcasts.sbr.pm";
214 icon = "audiobookshelf.png";
215 ping = "https://podcasts.sbr.pm";
216 statusStyle = "dot";
217 };
218 }
219 {
220 Immich = {
221 description = "Photo Management";
222 href = "https://immich.sbr.pm";
223 icon = "immich.png";
224 ping = "https://immich.sbr.pm";
225 statusStyle = "dot";
226 };
227 }
228 ];
229 }
230 {
231 "Download Management" = [
232 {
233 Sonarr = {
234 description = "TV Shows";
235 href = "https://sonarr.sbr.pm";
236 icon = "sonarr.png";
237 ping = "https://sonarr.sbr.pm";
238 statusStyle = "dot";
239 widget = {
240 type = "sonarr";
241 url = "https://sonarr.sbr.pm";
242 key = "{{HOMEPAGE_FILE_SONARR_KEY}}";
243 };
244 };
245 }
246 {
247 Radarr = {
248 description = "Movies";
249 href = "https://radarr.sbr.pm";
250 icon = "radarr.png";
251 ping = "https://radarr.sbr.pm";
252 statusStyle = "dot";
253 widget = {
254 type = "radarr";
255 url = "https://radarr.sbr.pm";
256 key = "{{HOMEPAGE_FILE_RADARR_KEY}}";
257 };
258 };
259 }
260 {
261 Lidarr = {
262 description = "Music";
263 href = "https://lidarr.sbr.pm";
264 icon = "lidarr.png";
265 ping = "https://lidarr.sbr.pm";
266 statusStyle = "dot";
267 widget = {
268 type = "lidarr";
269 url = "https://lidarr.sbr.pm";
270 key = "{{HOMEPAGE_FILE_LIDARR_KEY}}";
271 };
272 };
273 }
274 {
275 Bazarr = {
276 description = "Subtitles";
277 href = "https://bazarr.sbr.pm";
278 icon = "bazarr.png";
279 ping = "https://bazarr.sbr.pm";
280 statusStyle = "dot";
281 };
282 }
283 {
284 Prowlarr = {
285 description = "Indexer Manager";
286 href = "https://prowlarr.sbr.pm";
287 icon = "prowlarr.png";
288 ping = "https://prowlarr.sbr.pm";
289 statusStyle = "dot";
290 };
291 }
292 ];
293 }
294 {
295 "Torrent Clients" = [
296 {
297 Transmission = {
298 description = "General Downloads";
299 href = "https://transmission.sbr.pm";
300 icon = "transmission.png";
301 ping = "https://transmission.sbr.pm";
302 statusStyle = "dot";
303 };
304 }
305 {
306 "Transmission Music" = {
307 description = "Music Downloads";
308 href = "https://transmission-music.sbr.pm";
309 icon = "transmission.png";
310 ping = "https://transmission-music.sbr.pm";
311 statusStyle = "dot";
312 };
313 }
314 ];
315 }
316 {
317 Synchronization = syncthingServices;
318 }
319 {
320 Infrastructure = [
321 {
322 Grafana = {
323 description = "Monitoring";
324 href = "https://grafana.sbr.pm";
325 icon = "grafana.png";
326 ping = "https://grafana.sbr.pm";
327 statusStyle = "dot";
328 };
329 }
330 {
331 "Home Assistant" = {
332 description = "Home Automation";
333 href = "https://home.sbr.pm";
334 icon = "home-assistant.png";
335 ping = "https://home.sbr.pm";
336 statusStyle = "dot";
337 };
338 }
339 {
340 Traefik = {
341 description = "Reverse Proxy";
342 href = "https://traefik.sbr.pm";
343 icon = "traefik.png";
344 ping = "https://traefik.sbr.pm";
345 statusStyle = "dot";
346 };
347 }
348 ];
349 }
350 {
351 "Other Services" = [
352 {
353 Paperless = {
354 description = "Document Management";
355 href = "https://paperless.sbr.pm";
356 icon = "paperless.png";
357 ping = "https://paperless.sbr.pm";
358 statusStyle = "dot";
359 };
360 }
361 {
362 N8N = {
363 description = "Workflow Automation";
364 href = "https://n8n.sbr.pm";
365 icon = "n8n.png";
366 ping = "https://n8n.sbr.pm";
367 statusStyle = "dot";
368 };
369 }
370 {
371 Kiwix = {
372 description = "Offline Wikipedia";
373 href = "https://kiwix.sbr.pm";
374 icon = "kiwix.png";
375 ping = "https://kiwix.sbr.pm";
376 statusStyle = "dot";
377 };
378 }
379 ];
380 }
381 ];
382
383 bookmarks = [
384 {
385 Development = [
386 {
387 GitHub = [
388 {
389 abbr = "GH";
390 href = "https://github.com/vdemeester";
391 }
392 ];
393 }
394 {
395 "SourceHut" = [
396 {
397 abbr = "SH";
398 href = "https://sr.ht/~vdemeester";
399 }
400 ];
401 }
402 {
403 Codeberg = [
404 {
405 abbr = "CB";
406 href = "https://codeberg.org/vdemeester";
407 }
408 ];
409 }
410 ];
411 }
412 {
413 NixOS = [
414 {
415 "NixOS Search" = [
416 {
417 abbr = "NS";
418 href = "https://search.nixos.org";
419 }
420 ];
421 }
422 {
423 "NixOS Packages" = [
424 {
425 abbr = "NP";
426 href = "https://search.nixos.org/packages";
427 }
428 ];
429 }
430 {
431 "NixOS Options" = [
432 {
433 abbr = "NO";
434 href = "https://search.nixos.org/options";
435 }
436 ];
437 }
438 {
439 "Home Manager Options" = [
440 {
441 abbr = "HM";
442 href = "https://home-manager-options.extranix.com";
443 }
444 ];
445 }
446 {
447 "NixOS Wiki" = [
448 {
449 abbr = "NW";
450 href = "https://wiki.nixos.org";
451 }
452 ];
453 }
454 ];
455 }
456 {
457 Tools = [
458 {
459 "Claude Code" = [
460 {
461 abbr = "CC";
462 href = "https://claude.ai/code";
463 }
464 ];
465 }
466 {
467 "QMK Configurator" = [
468 {
469 abbr = "QMK";
470 href = "https://config.qmk.fm";
471 }
472 ];
473 }
474 {
475 "QMK Docs" = [
476 {
477 abbr = "QD";
478 href = "https://docs.qmk.fm";
479 }
480 ];
481 }
482 {
483 "Keymap Drawer" = [
484 {
485 abbr = "KD";
486 href = "https://caksoylar.github.io/keymap-drawer";
487 }
488 ];
489 }
490 ];
491 }
492 {
493 Documentation = [
494 {
495 "Homepage Docs" = [
496 {
497 abbr = "HP";
498 href = "https://gethomepage.dev";
499 }
500 ];
501 }
502 {
503 "Traefik Docs" = [
504 {
505 abbr = "TR";
506 href = "https://doc.traefik.io/traefik/";
507 }
508 ];
509 }
510 {
511 "Tekton Docs" = [
512 {
513 abbr = "TK";
514 href = "https://tekton.dev/docs/";
515 }
516 ];
517 }
518 ];
519 }
520 ];
521
522 widgets = [
523 {
524 resources = {
525 cpu = true;
526 memory = true;
527 disk = "/";
528 };
529 }
530 {
531 search = {
532 provider = "duckduckgo";
533 target = "_blank";
534 };
535 }
536 ];
537 };
538
539 # Create homepage group for secret file access
540 users.groups.homepage = { };
541
542 # Open firewall for local access
543 networking.firewall.allowedTCPPorts = [ 3001 ];
544
545 # Allow requests from all rhea domains and IPs (from globals.nix)
546 systemd.services.homepage-dashboard = {
547 environment = {
548 HOMEPAGE_ALLOWED_HOSTS = lib.mkForce allowedHosts;
549 HOMEPAGE_FILE_SONARR_KEY = config.age.secrets."exportarr-sonarr-apikey".path;
550 HOMEPAGE_FILE_RADARR_KEY = config.age.secrets."exportarr-radarr-apikey".path;
551 HOMEPAGE_FILE_LIDARR_KEY = config.age.secrets."exportarr-lidarr-apikey".path;
552 };
553 serviceConfig = {
554 SupplementaryGroups = [ "homepage" ];
555 };
556 };
557}