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}