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}