From 1caf5aa3e047b66c31a84389370c4765e69a13d9 Mon Sep 17 00:00:00 2001 From: alexion Date: Tue, 15 Sep 2026 12:20:55 -0400 Subject: [PATCH] feat(nixos): add pikachu homepage guest --- configs/nixos/AGENTS.md | 2 + configs/nixos/guests/actualbudget.nix | 8 ++ configs/nixos/guests/copyparty.nix | 9 ++ configs/nixos/guests/homepage.nix | 32 +++++ configs/nixos/guests/navidrome.nix | 8 ++ configs/nixos/hosts/pikachu/default.nix | 6 + configs/nixos/modules/services/homepage.nix | 132 ++++++++++++++++++++ configs/nixos/registry/homepage.nix | 45 +++++++ docs/TODO.md | 3 +- 9 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 configs/nixos/guests/homepage.nix create mode 100644 configs/nixos/modules/services/homepage.nix create mode 100644 configs/nixos/registry/homepage.nix diff --git a/configs/nixos/AGENTS.md b/configs/nixos/AGENTS.md index c127e3a..198aa9b 100644 --- a/configs/nixos/AGENTS.md +++ b/configs/nixos/AGENTS.md @@ -55,5 +55,7 @@ - On Pikachu, `nix copy --to ssh://pikachu` accepted a small `nix store add-file` probe, but rejected locally built system paths that lacked trusted signatures. Use a sudo `nix-store --import` path for unsigned deploy closures. +- Public DNS entries are not managed through the NixOS configuration. + Do not describe NixOS route choices as public-DNS management. - Do not hand-author `hardware-configuration.nix` for a host that has not been generated on that machine. Generate it on the target system or live ISO, then stage it before evaluating the flake. diff --git a/configs/nixos/guests/actualbudget.nix b/configs/nixos/guests/actualbudget.nix index 4310651..10a973a 100644 --- a/configs/nixos/guests/actualbudget.nix +++ b/configs/nixos/guests/actualbudget.nix @@ -25,6 +25,14 @@ in } ]; + registry.homepage.${config.networking.hostName}.actualbudget = { + tab = "Apps"; + section = "Other"; + name = "Actual Budget"; + description = "Finance"; + icon = "actual-budget.png"; + }; + guests.actualbudget = { interior = { modules.services.actualbudget = { diff --git a/configs/nixos/guests/copyparty.nix b/configs/nixos/guests/copyparty.nix index 17de0f9..6af6bd9 100644 --- a/configs/nixos/guests/copyparty.nix +++ b/configs/nixos/guests/copyparty.nix @@ -90,6 +90,15 @@ in } ]; + registry.homepage.${config.networking.hostName}.copyparty = { + tab = "Apps"; + section = "Files"; + name = "Copyparty"; + description = "Files"; + icon = "copyparty.svg"; + href = lib.mkIf cfg.reverseProxy.enable "https://${cfg.reverseProxy.route.domain}/?h"; + }; + guests.copyparty = { interior.modules.services.copyparty = { enable = true; diff --git a/configs/nixos/guests/homepage.nix b/configs/nixos/guests/homepage.nix new file mode 100644 index 0000000..2a8b009 --- /dev/null +++ b/configs/nixos/guests/homepage.nix @@ -0,0 +1,32 @@ +{ + config, + lib, + ... +}: +let + cfg = config.guests.homepage; +in +{ + options.guests.homepage = { + allowedHosts = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "home" ]; + description = "Host headers accepted by Homepage."; + }; + }; + + config = lib.mkIf cfg.enable { + guests.homepage = { + interior.modules.services.homepage = { + enable = true; + inherit (cfg) allowedHosts; + }; + + limits = { + memory = lib.mkDefault "1G"; + cpu = lib.mkDefault "100%"; + tasksMax = lib.mkDefault 256; + }; + }; + }; +} diff --git a/configs/nixos/guests/navidrome.nix b/configs/nixos/guests/navidrome.nix index fa66667..5f15023 100644 --- a/configs/nixos/guests/navidrome.nix +++ b/configs/nixos/guests/navidrome.nix @@ -48,6 +48,14 @@ in } ]; + registry.homepage.${config.networking.hostName}.navidrome = { + tab = "Apps"; + section = "Media"; + name = "Navidrome"; + description = "Music"; + icon = "navidrome.png"; + }; + guests.navidrome = { interior.modules.services.navidrome.enable = true; diff --git a/configs/nixos/hosts/pikachu/default.nix b/configs/nixos/hosts/pikachu/default.nix index 6ed01b0..0497f93 100644 --- a/configs/nixos/hosts/pikachu/default.nix +++ b/configs/nixos/hosts/pikachu/default.nix @@ -144,6 +144,12 @@ address = "10.23.20.109"; }; + guests.homepage = { + enable = true; + vlan = 20; + address = "10.23.20.46"; + }; + guests.copyparty = { enable = true; vlan = 20; diff --git a/configs/nixos/modules/services/homepage.nix b/configs/nixos/modules/services/homepage.nix new file mode 100644 index 0000000..49e3414 --- /dev/null +++ b/configs/nixos/modules/services/homepage.nix @@ -0,0 +1,132 @@ +{ + config, + lib, + registry, + ... +}: +let + cfg = config.modules.services.homepage; + + mkRegistryPath = registryName: host: entry: "registry.${registryName}.${host}.${entry}"; + + homepageEntries = lib.concatLists ( + lib.mapAttrsToList ( + host: entries: + lib.mapAttrsToList (entry: homepage: { + inherit host entry homepage; + composite = registry.join host entry; + }) entries + ) registry.homepage + ); + + mkHref = item: + if item.homepage.href != null then + item.homepage.href + else if (item.composite.route or null) != null && item.composite.route.port != null then + "https://${item.composite.route.domain}" + else + throw "${mkRegistryPath "homepage" item.host item.entry} needs href or a matching TCP route entry"; + + mkServiceAttrs = item: + lib.filterAttrs (_: value: value != null) { + href = mkHref item; + inherit (item.homepage) description icon; + }; + + sortedEntries = lib.sort (left: right: left.homepage.name < right.homepage.name) homepageEntries; + + insertEntry = acc: item: + acc + // { + ${item.homepage.tab} = (acc.${item.homepage.tab} or { }) + // { + ${item.homepage.section} = (acc.${item.homepage.tab}.${item.homepage.section} or [ ]) ++ [ + { ${item.homepage.name} = mkServiceAttrs item; } + ]; + }; + }; + + entriesByTab = lib.foldl' insertEntry { } sortedEntries; + + tabOrder = [ + "Apps" + "Download" + "System" + ]; + + orderedTabs = tabOrder ++ lib.filter (tab: !lib.elem tab tabOrder) (lib.attrNames entriesByTab); + + mkTab = tab: + let + sections = entriesByTab.${tab}; + in + { + ${tab} = map (section: { ${section} = sections.${section}; }) (lib.attrNames sections); + }; + + homepageServices = map mkTab (lib.filter (tab: lib.hasAttr tab entriesByTab) orderedTabs); +in +{ + options.modules.services.homepage = { + enable = lib.mkEnableOption "Homepage Dashboard"; + + listenPort = lib.mkOption { + type = lib.types.port; + default = 80; + description = "Port for Homepage to bind to."; + }; + + allowedHosts = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "home" ]; + description = "Host headers accepted by Homepage."; + }; + }; + + config = lib.mkIf cfg.enable { + services.homepage-dashboard = { + enable = true; + openFirewall = true; + inherit (cfg) listenPort; + allowedHosts = lib.concatStringsSep "," cfg.allowedHosts; + services = homepageServices; + settings = { + title = "Tracion"; + theme = "dark"; + color = "slate"; + showStats = false; + hideVersion = true; + bookmarksStyle = "icons"; + useEqualHeights = true; + layout = { + Apps = { + style = "row"; + columns = 1; + tab = "Apps"; + Media = { + style = "row"; + columns = 2; + }; + Files = { + style = "row"; + columns = 2; + }; + Other = { + style = "row"; + columns = 3; + iconsOnly = true; + }; + }; + Download = { + style = "row"; + columns = 1; + tab = "Download"; + }; + System = { + tab = "System"; + }; + }; + }; + }; + }; +} diff --git a/configs/nixos/registry/homepage.nix b/configs/nixos/registry/homepage.nix new file mode 100644 index 0000000..78926cb --- /dev/null +++ b/configs/nixos/registry/homepage.nix @@ -0,0 +1,45 @@ +{ lib, ... }: +{ + type = lib.types.submodule { + options = { + tab = lib.mkOption { + type = lib.types.str; + example = "Apps"; + description = "Homepage tab that contains this entry."; + }; + + section = lib.mkOption { + type = lib.types.str; + example = "Media"; + description = "Homepage section that contains this entry."; + }; + + name = lib.mkOption { + type = lib.types.str; + example = "Navidrome"; + description = "Homepage display name for this entry."; + }; + + description = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "Music"; + description = "Homepage description for this entry."; + }; + + icon = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "navidrome.png"; + description = "Homepage icon for this entry."; + }; + + href = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "https://music.alexion.dev"; + description = "Homepage link override for this entry."; + }; + }; + }; +} diff --git a/docs/TODO.md b/docs/TODO.md index d096a55..3a01d21 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -50,10 +50,9 @@ - immich - romm - gitea -- vikunja (?) - nginxproxymanager (gradual migration to nginx guest) - adguard -- homepage + - manage DNS rewrites for local service names such as `home` ## raichu proxmox -> nixos - dwn torrent (vpn)