Files
Nix-Vibe/docs/homepage-dashboard.md
T
petere eb08cd4282 Nix-Vibe public snapshot (squashed history)
Current state of main at 0240060 feat(hp-laptop): install TeleportFling from its flake. History intentionally
collapsed to a single commit; this repo mirrors only the latest state.
2026-09-19 13:53:39 +01:00

8.4 KiB

Homepage Dashboard — Adding Machines, Tabs & Services

This guide covers how to add new machines, tabs, and services to the Homepage dashboards on homeserver-1 (and, by extension, any host that imports the Homepage module).

Overview

Homepage (https://gethomepage.dev) is a self-hosted dashboard. All dashboard configuration lives in Nix and is rendered to YAML files that Homepage reads at /etc/homepage-dashboard/.

Item Location
Homepage NixOS module (service wrapper) modules/services/homepage.nix
Dashboard definition for homeserver-1 hosts/homeserver-1/homepage.nix
Deployed config files /etc/homepage-dashboard/{settings,services,widgets}.yaml
API keys / secrets secrets.yaml (SOPS) → HOMEPAGE_VAR_* env vars

The dashboard definition is split from the host config so it's easy to edit:

  • hosts/homeserver-1/configuration.nix enables Glances + firewall and imports:
    imports = [
      # ...
      ./homepage.nix
    ];
    

Applying Changes

After editing homepage.nix:

nixos-rebuild switch --target-host petere@homeserver-1 --flake .#homeserver-1 --use-remote-sudo

Important: Homepage reads its config files when the service starts. After a nixos-rebuild, the unit's definition usually changes, but if only the contents of the config files changed (e.g. just editing homepage.nix), the service may keep the old config in memory. If your changes don't appear, restart it:

ssh petere@homeserver-1 "sudo systemctl restart homepage-dashboard"

Structure of the Dashboard Definition

hosts/homeserver-1/homepage.nix has three main parts:

services.homepage = {
  enable = true;
  port = 8082;
  allowedHosts = [ ... ];       # Host header values Homepage responds to
  environmentFiles = [ ... ];   # SOPS-secret env file with HOMEPAGE_VAR_* keys

  settings = {
    title = "HomeServer";
    # ...
    layout = { ... };           # Controls grouping, tabs, and column widths
  };

  services = [ ... ];           # The service groups and tiles (services.yaml)
  widgets = [ ... ];            # Header info widgets (resources, search, clock)
};

Tabs

Tabs are enabled by adding a tab field to a group's layout entry.

  • Groups with the same tab value appear on that tab.
  • Groups with no tab appear on every tab.
  • Tabs are sorted by their order in the layout block.
  • Each tab is deep-linkable: #monitoring, #homeserver-1, etc.

Current tabs:

Monitoring            (default tab)
├── Homeserver-1 Monitoring   → 5 Glances tiles (localhost)
└── MCF Server Monitoring     → 5 Glances tiles (mcf-server via tailnet)

Homeserver-1  (#homeserver-1)
├── Media     → Jellyfin, Immich
└── System    → Backrest, Pocket ID, Tailscale

MCF Server    (reserved; uncomment the placeholder to enable)

Adding a Service

  1. Add the service tile to the relevant group in the services list:

    services = [
      # ...
      {
        System = [
          # ...existing tiles...
          {
            MyService = {
              icon = "sh-myservice";
              href = "http://homeserver-1.gerbil-opah.ts.net:<port>";
              description = "What it does";
              siteMonitor = "http://127.0.0.1:<port>";   # green/red status
              widget = {                                  # optional live stats
                type = "myservice";
                url = "http://127.0.0.1:<port>";
                key = "{{HOMEPAGE_VAR_MYSERVICE_API_KEY}}"; # only if it needs a key
              };
            };
          }
        ];
      }
    ];
    
  2. Set the layout so the group renders where you want (columns = how many tiles per row; groups span the full width with style = "row" when they're a single top-level group):

    layout = {
      # ...
      MyService = {             # or add to an existing group's entry
        tab = "Homeserver-1";
        style = "row";
        columns = 4;
      };
    };
    
  3. API keys: never hardcode secrets. Add the value to secrets.yaml under the machine section and reference it via the environment file. The secret homeserver-1/homepage-env already provides HOMEPAGE_VAR_JELLYFIN_API_KEY, HOMEPAGE_VAR_IMMICH_API_KEY, HOMEPAGE_VAR_TAILSCALE_API_KEY and HOMEPAGE_VAR_TAILSCALE_DEVICEID. To add another:

    sops --set '["homeserver-1"]["homepage-env"] "HOMEPAGE_VAR_MYSERVICE_API_KEY=<value>"' secrets.yaml
    

    (This replaces the whole env file — include every existing HOMEPAGE_VAR_* line when setting it.)

Adding a Tab

Add a new group to services and give it a tab in the layout:

# services
{
  "My New Group" = [
    { "MyService" = { href = "..."; }; }
  ];
}

# layout
"My New Group" = {
  tab = "My Tab";
  style = "row";
  columns = 4;
};

Adding a New Machine to the Dashboard

To monitor another machine (e.g. richmond-server):

1. Enable Glances on the target machine

Add to that host's configuration.nix (Glances exposes system stats to the dashboard):

services.glances = {
  enable = true;
  port = 61208;
  extraArgs = [ "--webserver" ];
};

# Expose on Tailscale only
networking.firewall.interfaces.tailscale.allowedTCPPorts = [ 61208 ];

2. Add the monitoring group

In hosts/homeserver-1/homepage.nix, add a group with Glances tiles. The group name must be unique — Homepage's widget proxy resolves widget config by leaf group name, so duplicate group names cause one machine's stats to display on another's tiles.

# services
{
  "Richmond Server Monitoring" = [
    { "System" = { widget = { type = "glances"; url = "http://richmond-server.gerbil-opah.ts.net:61208"; version = 4; metric = "info"; }; }; }
    { "CPU" = { widget = { type = "glances"; url = "http://richmond-server.gerbil-opah.ts.net:61208"; version = 4; metric = "cpu"; }; }; }
    { "Memory" = { widget = { type = "glances"; url = "http://richmond-server.gerbil-opah.ts.net:61208"; version = 4; metric = "memory"; }; }; }
    { "Disk" = { widget = { type = "glances"; url = "http://richmond-server.gerbil-opah.ts.net:61208"; version = 4; metric = "fs:/"; }; }; }
    { "Processes" = { widget = { type = "glances"; url = "http://richmond-server.gerbil-opah.ts.net:61208"; version = 4; metric = "process"; }; }; }
  ];
}

# layout
"Richmond Server Monitoring" = {
  tab = "Monitoring";
  style = "row";
  columns = 5;
};

3. Add non-monitoring services for that machine

Add a group and give it its own tab (or reuse an existing one):

# services
{
  "Richmond Server Apps" = [
    { "Ntfy" = { icon = "sh-ntfy"; href = "http://richmond-server.gerbil-opah.ts.net:8080"; }; }
  ];
}

# layout
"Richmond Server Apps" = {
  tab = "Richmond Server";
  style = "row";
  columns = 4;
};

4. (Optional) Tailscale widget for the machine

Each machine's Tailscale node can be shown with its own widget, using the machine's numeric device ID (find it via the Tailscale API/admin console):

{
  "Richmond Server" = [
    {
      Tailscale = {
        icon = "sh-tailscale";
        href = "https://login.tailscale.com/admin/machines";
        widget = {
          type = "tailscale";
          deviceid = "<numeric-device-id>";       # NOT the ...CNTRL value
          key = "{{HOMEPAGE_VAR_TAILSCALE_API_KEY}}";
        };
      };
    }
  ];
}

Troubleshooting

Symptom Cause / Fix
Changes not appearing Service not restarted — run sudo systemctl restart homepage-dashboard
t.metric is undefined Glances widget missing the metric field — every glances tile needs metric (e.g. cpu, memory, fs:/, process, info)
no manageable device matching this ID found Tailscale widget deviceid is wrong — must be the numeric device ID, not the ...CNTRL value
Two machines show the same stats Duplicate group names — every group (especially monitoring) needs a unique name
Host validation failed The Host header isn't in allowedHosts — add the hostname (with port) to allowedHosts
API keys showing in config Keys must come from the SOPS env file (HOMEPAGE_VAR_*), never hardcoded

Glances Metrics

The Glances widget (type = "glances") requires a version and metric:

  • version = 4 for Glances v4.x (installed)
  • metric: info (system summary), cpu, memory, process, containers, fs:/ (disk usage), network:<iface>, sensor:<id>, disk:<id>, gpu:<id>