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 }
363 ];
364
365 bookmarks = [
366 {
367 Development = [
368 {
369 GitHub = [
370 {
371 abbr = "GH";
372 href = "https://github.com/vdemeester";
373 }
374 ];
375 }
376 {
377 "SourceHut" = [
378 {
379 abbr = "SH";
380 href = "https://sr.ht/~vdemeester";
381 }
382 ];
383 }
384 {
385 Codeberg = [
386 {
387 abbr = "CB";
388 href = "https://codeberg.org/vdemeester";
389 }
390 ];
391 }
392 ];
393 }
394 {
395 NixOS = [
396 {
397 "NixOS Search" = [
398 {
399 abbr = "NS";
400 href = "https://search.nixos.org";
401 }
402 ];
403 }
404 {
405 "NixOS Packages" = [
406 {
407 abbr = "NP";
408 href = "https://search.nixos.org/packages";
409 }
410 ];
411 }
412 {
413 "NixOS Options" = [
414 {
415 abbr = "NO";
416 href = "https://search.nixos.org/options";
417 }
418 ];
419 }
420 {
421 "Home Manager Options" = [
422 {
423 abbr = "HM";
424 href = "https://home-manager-options.extranix.com";
425 }
426 ];
427 }
428 {
429 "NixOS Wiki" = [
430 {
431 abbr = "NW";
432 href = "https://wiki.nixos.org";
433 }
434 ];
435 }
436 ];
437 }
438 {
439 Tools = [
440 {
441 "Claude Code" = [
442 {
443 abbr = "CC";
444 href = "https://claude.ai/code";
445 }
446 ];
447 }
448 {
449 "QMK Configurator" = [
450 {
451 abbr = "QMK";
452 href = "https://config.qmk.fm";
453 }
454 ];
455 }
456 {
457 "QMK Docs" = [
458 {
459 abbr = "QD";
460 href = "https://docs.qmk.fm";
461 }
462 ];
463 }
464 {
465 "Keymap Drawer" = [
466 {
467 abbr = "KD";
468 href = "https://caksoylar.github.io/keymap-drawer";
469 }
470 ];
471 }
472 ];
473 }
474 {
475 Documentation = [
476 {
477 "Homepage Docs" = [
478 {
479 abbr = "HP";
480 href = "https://gethomepage.dev";
481 }
482 ];
483 }
484 {
485 "Traefik Docs" = [
486 {
487 abbr = "TR";
488 href = "https://doc.traefik.io/traefik/";
489 }
490 ];
491 }
492 {
493 "Tekton Docs" = [
494 {
495 abbr = "TK";
496 href = "https://tekton.dev/docs/";
497 }
498 ];
499 }
500 ];
501 }
502 ];
503
504 widgets = [
505 {
506 resources = {
507 cpu = true;
508 memory = true;
509 disk = "/";
510 };
511 }
512 {
513 search = {
514 provider = "duckduckgo";
515 target = "_blank";
516 };
517 }
518 ];
519 };
520
521 # Create homepage group for secret file access
522 users.groups.homepage = { };
523
524 # Open firewall for local access
525 networking.firewall.allowedTCPPorts = [ 3001 ];
526
527 # Allow requests from all rhea domains and IPs (from globals.nix)
528 systemd.services.homepage-dashboard = {
529 environment = {
530 HOMEPAGE_ALLOWED_HOSTS = lib.mkForce allowedHosts;
531 HOMEPAGE_FILE_SONARR_KEY = config.age.secrets."exportarr-sonarr-apikey".path;
532 HOMEPAGE_FILE_RADARR_KEY = config.age.secrets."exportarr-radarr-apikey".path;
533 HOMEPAGE_FILE_LIDARR_KEY = config.age.secrets."exportarr-lidarr-apikey".path;
534 };
535 serviceConfig = {
536 SupplementaryGroups = [ "homepage" ];
537 };
538 };
539}