150 lines
6.9 KiB
Markdown
150 lines
6.9 KiB
Markdown
# Homelab Dashboard
|
|
|
|
A small, self-contained single-page dashboard that links all your homelab services.
|
|
Static site (HTML + CSS + JS), served by a lightweight **nginx** image. The optional
|
|
calendar widget needs the container backend to refresh Google/ICS feeds, but the
|
|
rest of the dashboard is pure static files.
|
|
|
|
## Services
|
|
|
|
Internal: Open WebUI, ComfyUI, Gitea, Jellyfin, Immich, Home Assistant,
|
|
Audio Server / Dubplate, Nextcloud, Vaultwarden, Nginx Proxy Manager, Netbird.
|
|
|
|
External: gitlab-ixsol, gem360.
|
|
|
|
## Features
|
|
|
|
- Top widgets: **Today** (calendar events for today, with in-progress highlighting),
|
|
**Netbird** status (reachability + latency), **Git activity** (recent commits from
|
|
Gitea and GitLab — 3 rows visible, scroll for more), **GPU + Ollama** (live AMD
|
|
ROCm stats: utilization, VRAM, temperature, power; plus loaded Ollama models
|
|
with their GPU/CPU split)
|
|
- Inline **Open WebUI prompt line with model selection** in the Open WebUI card
|
|
(requires `OPENWEBUI_TOKEN`)
|
|
- Responsive grid of service cards with icons, categories and **real online status**
|
|
(server-side health checks every minute: green = online, amber = 5xx error,
|
|
red = unreachable; hover the badge for latency/details)
|
|
- **Open** (new tab) + **Copy URL** per service
|
|
- Live search / filter across names, categories and URLs
|
|
- Live local clock
|
|
- Calendar section aggregating multiple ICS feeds (refreshed every 15 min in the container)
|
|
- Dark, themeable design; respects `prefers-reduced-motion`
|
|
|
|
## Run with Docker
|
|
|
|
```bash
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Then open <http://localhost:8888>.
|
|
|
|
The dashboard is static, but the widgets are populated by a small Node.js updater
|
|
(`api/update-data.js`) that runs inside the container and aggregates ICS feeds,
|
|
Netbird status, git activity and Open WebUI models every 15 minutes. A second
|
|
Node process (`api/server.js`) proxies chat prompts to Open WebUI.
|
|
|
|
### Environment variables
|
|
|
|
Secrets (API keys) live in **`.env`** (gitignored) — docker compose reads it
|
|
automatically. Non-secret settings go directly in `docker-compose.yml`:
|
|
|
|
| Variable | Purpose |
|
|
| --- | --- |
|
|
| `TZ` | Timezone used for the "Today" widget (e.g. `Europe/Vienna`) |
|
|
| `OPENWEBUI_TOKEN` | Open WebUI API key — enables the prompt line + model list (**set in `.env`**) |
|
|
| `GITEA_TOKEN` | Gitea API token — private repo stats (public repos work without it) |
|
|
| `GITLAB_TOKEN` | GitLab API token — private project stats (public projects work without it) |
|
|
| `GITLAB_PROXY` | Forward proxy (HTTP CONNECT) for all GitLab requests — git stats **and** the online badge (**set in `.env`**). Change the URL when the proxy stack moves; unset = direct connection. With the proxy on the docker host use `http://host.docker.internal:8118` (the container resolves `host.docker.internal` via `extra_hosts`)
|
|
| `GITEA_URL` / `GITLAB_URL` / `NETBIRD_URL` / `OPENWEBUI_URL` | Override the default service URLs |
|
|
| `GPU_STATS_URL` | GPU stats upstream (default: the `gpu-stats` compose service) |
|
|
| `OLLAMA_URL` | Ollama instance for loaded-model stats (default: `http://100.103.83.12:11435`) |
|
|
| `OUTLOOK_CLIENT_ID` | Entra ID app client ID for the unread-mail widget (**set in `.env`**) |
|
|
| `OUTLOOK_TENANT` | Azure tenant (default: `organizations`; set to your Directory (tenant) ID if the generic endpoint returns AADSTS50059) |
|
|
|
|
### Outlook unread mail
|
|
|
|
The Outlook widget (and the unread count) uses the Microsoft Graph API with a
|
|
one-time device login. Setup:
|
|
|
|
1. **Register an app** in Microsoft Entra ID (portal.azure.com → Entra ID →
|
|
App registrations → New registration): any name (e.g. "Homelab Dashboard"),
|
|
account type "Accounts in this organizational directory only", no redirect URI.
|
|
2. In the app: **Authentication → Allow public client flows → Yes**, then
|
|
**API permissions → Add → Microsoft Graph → Delegated → Mail.Read**.
|
|
3. Copy the **Application (client) ID** — from App registrations → your app →
|
|
Overview. Beware: this is *not* the "Directory (tenant) ID" and not the
|
|
"Object ID". A valid-format but wrong GUID produces AADSTS50059
|
|
("No tenant-identifying information found"). Put it in `.env` as
|
|
`OUTLOOK_CLIENT_ID=`.
|
|
4. Set `OUTLOOK_TENANT` in `.env` to your **Directory (tenant) ID**. Some
|
|
directories are not resolvable through the generic `organizations`
|
|
endpoint (also AADSTS50059 with a valid client ID) — the tenant-specific
|
|
endpoint always works.
|
|
4. `docker compose up -d --build`
|
|
5. Log in once:
|
|
```bash
|
|
docker exec -it homelab-dashboard node /opt/dashboard/outlook-auth.js
|
|
```
|
|
Open the printed URL, enter the code and sign in with your work account.
|
|
The refresh token is persisted in the `outlook-tokens` volume.
|
|
|
|
`api/update-outlook.js` then refreshes unread mail every minute (caching the
|
|
access token, and re-running `outlook-auth.js` is only needed if login is
|
|
revoked). If your tenant requires admin consent, ask your admin to grant it
|
|
for `Mail.Read`.
|
|
|
|
### GPU stats service
|
|
|
|
The `gpu-stats` compose service runs [`gpu_stats.py`](./gpu_stats.py), a tiny HTTP
|
|
server that queries `rocm-smi`. It needs the host's ROCm install and GPU device
|
|
nodes, so the compose file mounts `/opt/rocm` and passes `/dev/kfd` + `/dev/dri`.
|
|
Set `GPU_NAME` in `docker-compose.yml` to your card's name (rocm-smi can't read it
|
|
inside the container without `libdrm_amdgpu`). The widget polls it every 5 s
|
|
through `/api/gpu-stats`.
|
|
|
|
To run it on the host instead: `python3 gpu_stats.py`, set
|
|
`GPU_STATS_URL=http://host.docker.internal:9101/stats` on the dashboard service
|
|
and add `extra_hosts: ["host.docker.internal:host-gateway"]`.
|
|
|
|
### Without Docker (quick dev)
|
|
|
|
Any static file server works for the dashboard itself, but the widgets will
|
|
show errors because the data fetcher and chat proxy run server-side:
|
|
|
|
```bash
|
|
# from this directory
|
|
python3 -m http.server 8888
|
|
# or: npx serve . -l 8888
|
|
```
|
|
|
|
## Edit services
|
|
|
|
All services live in one array at the top of [`script.js`](./script.js).
|
|
Add / remove / reorder entries there:
|
|
|
|
```js
|
|
{ section: "internal", name: "My Service", url: "http://10.0.0.5:9000", cat: "infra", icon: "⚙️" }
|
|
```
|
|
|
|
`section` is either `"internal"` or `"external"`.
|
|
`cat` is any key from the `CATEGORIES` map (or add your own).
|
|
|
|
## Build the image manually
|
|
|
|
```bash
|
|
docker build -t homelab-dashboard .
|
|
docker run -d --name homelab-dashboard -p 8888:80 --restart unless-stopped homelab-dashboard
|
|
```
|
|
|
|
## Notes
|
|
|
|
- Online badges are real health checks: `api/update-health.js` (cron, every minute)
|
|
pings each service URL from the container with a 5 s timeout and writes
|
|
`health.json`; the frontend refreshes badges every 30 s. Any HTTP response below
|
|
500 counts as online (401/404 still means the server is up); 5xx shows as
|
|
"Error", timeouts/DNS failures as "Offline". TLS certificate validity is ignored
|
|
(reachability only).
|
|
- Calendar dependencies are bundled in `api/node_modules` so the Docker build works
|
|
offline. To update them, run `npm install` in the `api/` directory.
|
|
- Port 8888 is published by the Compose file; change it there if needed.
|