fedora-csb-system-manager
  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          Media = {
 62            style = "row";
 63            columns = 3;
 64          };
 65        }
 66        {
 67          "Download Management" = {
 68            style = "row";
 69            columns = 3;
 70          };
 71        }
 72        {
 73          "Torrent Clients" = {
 74            style = "row";
 75            columns = 2;
 76          };
 77        }
 78        {
 79          Synchronization = {
 80            style = "row";
 81            columns = 4;
 82          };
 83        }
 84        {
 85          Infrastructure = {
 86            style = "row";
 87            columns = 2;
 88          };
 89        }
 90      ];
 91    };
 92
 93    services = [
 94      {
 95        Media = [
 96          {
 97            Jellyfin = {
 98              description = "Media Server";
 99              href = "https://jellyfin.sbr.pm";
100              icon = "jellyfin.png";
101              ping = "https://jellyfin.sbr.pm";
102              statusStyle = "dot";
103            };
104          }
105          {
106            Jellyseerr = {
107              description = "Media Requests";
108              href = "https://jellyseerr.sbr.pm";
109              icon = "jellyseerr.png";
110              ping = "https://jellyseerr.sbr.pm";
111              statusStyle = "dot";
112            };
113          }
114          {
115            Navidrome = {
116              description = "Music Streaming";
117              href = "https://music.sbr.pm";
118              icon = "navidrome.png";
119              ping = "https://music.sbr.pm";
120              statusStyle = "dot";
121            };
122          }
123          {
124            Audiobookshelf = {
125              description = "Podcasts & Audiobooks";
126              href = "https://podcasts.sbr.pm";
127              icon = "audiobookshelf.png";
128              ping = "https://podcasts.sbr.pm";
129              statusStyle = "dot";
130            };
131          }
132          {
133            Immich = {
134              description = "Photo Management";
135              href = "https://immich.sbr.pm";
136              icon = "immich.png";
137              ping = "https://immich.sbr.pm";
138              statusStyle = "dot";
139            };
140          }
141        ];
142      }
143      {
144        "Download Management" = [
145          {
146            Sonarr = {
147              description = "TV Shows";
148              href = "https://sonarr.sbr.pm";
149              icon = "sonarr.png";
150              ping = "https://sonarr.sbr.pm";
151              statusStyle = "dot";
152              widget = {
153                type = "sonarr";
154                url = "https://sonarr.sbr.pm";
155                key = "{{HOMEPAGE_FILE_SONARR_KEY}}";
156              };
157            };
158          }
159          {
160            Radarr = {
161              description = "Movies";
162              href = "https://radarr.sbr.pm";
163              icon = "radarr.png";
164              ping = "https://radarr.sbr.pm";
165              statusStyle = "dot";
166              widget = {
167                type = "radarr";
168                url = "https://radarr.sbr.pm";
169                key = "{{HOMEPAGE_FILE_RADARR_KEY}}";
170              };
171            };
172          }
173          {
174            Lidarr = {
175              description = "Music";
176              href = "https://lidarr.sbr.pm";
177              icon = "lidarr.png";
178              ping = "https://lidarr.sbr.pm";
179              statusStyle = "dot";
180              widget = {
181                type = "lidarr";
182                url = "https://lidarr.sbr.pm";
183                key = "{{HOMEPAGE_FILE_LIDARR_KEY}}";
184              };
185            };
186          }
187          {
188            Bazarr = {
189              description = "Subtitles";
190              href = "https://bazarr.sbr.pm";
191              icon = "bazarr.png";
192              ping = "https://bazarr.sbr.pm";
193              statusStyle = "dot";
194            };
195          }
196          {
197            Prowlarr = {
198              description = "Indexer Manager";
199              href = "https://prowlarr.sbr.pm";
200              icon = "prowlarr.png";
201              ping = "https://prowlarr.sbr.pm";
202              statusStyle = "dot";
203            };
204          }
205        ];
206      }
207      {
208        "Torrent Clients" = [
209          {
210            Transmission = {
211              description = "General Downloads";
212              href = "https://transmission.sbr.pm";
213              icon = "transmission.png";
214              ping = "https://transmission.sbr.pm";
215              statusStyle = "dot";
216            };
217          }
218          {
219            "Transmission Music" = {
220              description = "Music Downloads";
221              href = "https://transmission-music.sbr.pm";
222              icon = "transmission.png";
223              ping = "https://transmission-music.sbr.pm";
224              statusStyle = "dot";
225            };
226          }
227        ];
228      }
229      {
230        Synchronization = syncthingServices;
231      }
232      {
233        Infrastructure = [
234          {
235            Grafana = {
236              description = "Monitoring";
237              href = "https://grafana.sbr.pm";
238              icon = "grafana.png";
239              ping = "https://grafana.sbr.pm";
240              statusStyle = "dot";
241            };
242          }
243          {
244            "Home Assistant" = {
245              description = "Home Automation";
246              href = "https://home.sbr.pm";
247              icon = "home-assistant.png";
248              ping = "https://home.sbr.pm";
249              statusStyle = "dot";
250            };
251          }
252          {
253            Traefik = {
254              description = "Reverse Proxy";
255              href = "https://traefik.sbr.pm";
256              icon = "traefik.png";
257              ping = "https://traefik.sbr.pm";
258              statusStyle = "dot";
259            };
260          }
261        ];
262      }
263      {
264        "Other Services" = [
265          {
266            Paperless = {
267              description = "Document Management";
268              href = "https://paperless.sbr.pm";
269              icon = "paperless.png";
270              ping = "https://paperless.sbr.pm";
271              statusStyle = "dot";
272            };
273          }
274          {
275            N8N = {
276              description = "Workflow Automation";
277              href = "https://n8n.sbr.pm";
278              icon = "n8n.png";
279              ping = "https://n8n.sbr.pm";
280              statusStyle = "dot";
281            };
282          }
283          {
284            Kiwix = {
285              description = "Offline Wikipedia";
286              href = "https://kiwix.sbr.pm";
287              icon = "kiwix.png";
288              ping = "https://kiwix.sbr.pm";
289              statusStyle = "dot";
290            };
291          }
292        ];
293      }
294    ];
295
296    bookmarks = [
297      {
298        Development = [
299          {
300            GitHub = [
301              {
302                abbr = "GH";
303                href = "https://github.com/vdemeester";
304              }
305            ];
306          }
307          {
308            "SourceHut" = [
309              {
310                abbr = "SH";
311                href = "https://sr.ht/~vdemeester";
312              }
313            ];
314          }
315          {
316            Codeberg = [
317              {
318                abbr = "CB";
319                href = "https://codeberg.org/vdemeester";
320              }
321            ];
322          }
323        ];
324      }
325      {
326        NixOS = [
327          {
328            "NixOS Search" = [
329              {
330                abbr = "NS";
331                href = "https://search.nixos.org";
332              }
333            ];
334          }
335          {
336            "NixOS Packages" = [
337              {
338                abbr = "NP";
339                href = "https://search.nixos.org/packages";
340              }
341            ];
342          }
343          {
344            "NixOS Options" = [
345              {
346                abbr = "NO";
347                href = "https://search.nixos.org/options";
348              }
349            ];
350          }
351          {
352            "Home Manager Options" = [
353              {
354                abbr = "HM";
355                href = "https://home-manager-options.extranix.com";
356              }
357            ];
358          }
359          {
360            "NixOS Wiki" = [
361              {
362                abbr = "NW";
363                href = "https://wiki.nixos.org";
364              }
365            ];
366          }
367        ];
368      }
369      {
370        Tools = [
371          {
372            "Claude Code" = [
373              {
374                abbr = "CC";
375                href = "https://claude.ai/code";
376              }
377            ];
378          }
379          {
380            "QMK Configurator" = [
381              {
382                abbr = "QMK";
383                href = "https://config.qmk.fm";
384              }
385            ];
386          }
387          {
388            "QMK Docs" = [
389              {
390                abbr = "QD";
391                href = "https://docs.qmk.fm";
392              }
393            ];
394          }
395          {
396            "Keymap Drawer" = [
397              {
398                abbr = "KD";
399                href = "https://caksoylar.github.io/keymap-drawer";
400              }
401            ];
402          }
403        ];
404      }
405      {
406        Documentation = [
407          {
408            "Homepage Docs" = [
409              {
410                abbr = "HP";
411                href = "https://gethomepage.dev";
412              }
413            ];
414          }
415          {
416            "Traefik Docs" = [
417              {
418                abbr = "TR";
419                href = "https://doc.traefik.io/traefik/";
420              }
421            ];
422          }
423          {
424            "Tekton Docs" = [
425              {
426                abbr = "TK";
427                href = "https://tekton.dev/docs/";
428              }
429            ];
430          }
431        ];
432      }
433    ];
434
435    widgets = [
436      {
437        resources = {
438          cpu = true;
439          memory = true;
440          disk = "/";
441        };
442      }
443      {
444        search = {
445          provider = "duckduckgo";
446          target = "_blank";
447        };
448      }
449    ];
450  };
451
452  # Create homepage group for secret file access
453  users.groups.homepage = { };
454
455  # Open firewall for local access
456  networking.firewall.allowedTCPPorts = [ 3001 ];
457
458  # Allow requests from all rhea domains and IPs (from globals.nix)
459  systemd.services.homepage-dashboard = {
460    environment = {
461      HOMEPAGE_ALLOWED_HOSTS = lib.mkForce allowedHosts;
462      HOMEPAGE_FILE_SONARR_KEY = config.age.secrets."exportarr-sonarr-apikey".path;
463      HOMEPAGE_FILE_RADARR_KEY = config.age.secrets."exportarr-radarr-apikey".path;
464      HOMEPAGE_FILE_LIDARR_KEY = config.age.secrets."exportarr-lidarr-apikey".path;
465    };
466    serviceConfig = {
467      SupplementaryGroups = [ "homepage" ];
468    };
469  };
470}