{ config, lib, pkgs, ... }: let cfg = config.my.profiles.homepage; dashboardPort = 8082; dashboardHost = config.networking.domain; dashboardUrl = "http://${dashboardHost}:${toString dashboardPort}"; manualServices = import ./manual-services.nix; manualWidgets = import ./manual-widgets.nix; groupedServices = lib.foldl' ( acc: entry: acc // { ${entry.group} = (acc.${entry.group} or [ ]) ++ [ entry ]; } ) { } config.my.homepage.services; homepageServices = lib.mapAttrsToList (group: entries: { ${group} = map ( entry: let service = builtins.removeAttrs entry [ "group" "name" ]; in { ${entry.name} = lib.filterAttrs (_: value: value != null) service; } ) entries; }) groupedServices; # homepage-dashboard keeps only the first services.yaml list entry with a # given group name (first match wins), so module-contributed and manual # groups sharing a name (e.g. "Services") must be merged here rather than # simply concatenated, or the later ones are silently dropped. mergeGroups = lists: let merged = lib.foldl' ( acc: groupAttrs: let groupName = builtins.head (builtins.attrNames groupAttrs); in acc // { ${groupName} = (acc.${groupName} or [ ]) ++ groupAttrs.${groupName}; } ) { } lists; in lib.mapAttrsToList (name: entries: { ${name} = entries; }) merged; in { options.my.homepage.services = with lib; mkOption { type = types.listOf ( types.submodule { options = { group = mkOption { type = types.str; description = "Homepage service group"; }; name = mkOption { type = types.str; description = "Homepage service name"; }; description = mkOption { type = types.str; description = "Homepage service description"; }; href = mkOption { type = types.str; description = "Homepage service URL"; }; icon = mkOption { type = types.nullOr types.str; default = null; description = "Optional Homepage service icon"; }; }; } ); default = [ ]; description = "Merged homepage service metadata contributed by repo modules."; }; options.my.homepage.widgets = with lib; mkOption { type = types.listOf types.attrs; default = [ ]; description = "Widget definitions passed directly to services.homepage-dashboard.widgets. Each entry is an attrset like { resources = { cpu = true; memory = true; }; }."; }; options.my.profiles.homepage = with lib; { enable = mkEnableOption "getHomepage.dev Dashboard"; }; config = lib.mkIf cfg.enable { networking.firewall.allowedTCPPorts = [ dashboardPort ]; services.homepage-dashboard = { enable = true; listenPort = dashboardPort; allowedHosts = "${dashboardHost}:${toString dashboardPort},192.168.178.65:${toString dashboardPort},localhost:${toString dashboardPort},127.0.0.1:${toString dashboardPort},jupiter:${toString dashboardPort},jupiter.solar.internal:${toString dashboardPort}"; bookmarks = import ./bookmarks.nix; services = mergeGroups (homepageServices ++ manualServices); widgets = config.my.homepage.widgets ++ manualWidgets; }; users.users.finn.packages = with pkgs; [ homepage-dashboard ]; programs.chromium.homepageLocation = dashboardUrl; }; }