feat(nixos): add pikachu homepage guest

This commit is contained in:
2026-09-15 12:20:55 -04:00
parent fca37666d3
commit 1caf5aa3e0
9 changed files with 243 additions and 2 deletions

View File

@@ -55,5 +55,7 @@
- On Pikachu, `nix copy --to ssh://pikachu` accepted a small `nix store add-file` probe, - 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. but rejected locally built system paths that lacked trusted signatures.
Use a sudo `nix-store --import` path for unsigned deploy closures. 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. - 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. Generate it on the target system or live ISO, then stage it before evaluating the flake.

View File

@@ -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 = { guests.actualbudget = {
interior = { interior = {
modules.services.actualbudget = { modules.services.actualbudget = {

View File

@@ -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 = { guests.copyparty = {
interior.modules.services.copyparty = { interior.modules.services.copyparty = {
enable = true; enable = true;

View File

@@ -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;
};
};
};
}

View File

@@ -48,6 +48,14 @@ in
} }
]; ];
registry.homepage.${config.networking.hostName}.navidrome = {
tab = "Apps";
section = "Media";
name = "Navidrome";
description = "Music";
icon = "navidrome.png";
};
guests.navidrome = { guests.navidrome = {
interior.modules.services.navidrome.enable = true; interior.modules.services.navidrome.enable = true;

View File

@@ -144,6 +144,12 @@
address = "10.23.20.109"; address = "10.23.20.109";
}; };
guests.homepage = {
enable = true;
vlan = 20;
address = "10.23.20.46";
};
guests.copyparty = { guests.copyparty = {
enable = true; enable = true;
vlan = 20; vlan = 20;

View File

@@ -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";
};
};
};
};
};
}

View File

@@ -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.";
};
};
};
}

View File

@@ -50,10 +50,9 @@
- immich - immich
- romm - romm
- gitea - gitea
- vikunja (?)
- nginxproxymanager (gradual migration to nginx guest) - nginxproxymanager (gradual migration to nginx guest)
- adguard - adguard
- homepage - manage DNS rewrites for local service names such as `home`
## raichu proxmox -> nixos ## raichu proxmox -> nixos
- dwn torrent (vpn) - dwn torrent (vpn)