Skip to content
zurdi15Public

About

The bridge between you and your homelab

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

bifrost logo

Bifrost

The bridge between you and your homelab.

CI Release License

Bifrost is a self-hosted homelab dashboard built around two containers. bifrost-hub (FastAPI + SQLite + a Vue 3 SPA) receives agent reports, stores state and metric history, and serves the dashboard. bifrost-agent is a tiny static Go binary (alpine + smartmontools image), one per node: it collects system metrics, discovers local Docker containers, reads disk SMART health, and detects Kubernetes clusters running on the node. Agents connect outbound to the hub over a persistent WebSocket: no open ports on your nodes, and node-down detection is instant (socket close + application heartbeat).

Features

  • Live nodes — CPU, memory, load, network, temperatures streamed in real time; history at three resolutions (raw/1m/1h); per-node filesystems and SMART disk health.
  • Docker services — every container on every node becomes a dashboard card with live state, health, usage, an auto-resolved icon and (usually) a working URL. Zero labels needed in the common case.
  • Kubernetes — clusters are auto-detected by the agent; Ingresses, Services and CronJobs (with run history) appear alongside Docker services.
  • Gateway — bifrost derives one https://<name>.<your-domain> URL per service and publishes the routing table for your reverse proxy to consume. A Caddy + sidecar example makes the whole homelab reachable with wildcard TLS (examples/gateway).
  • Endpoints — agentless nodes (HAOS boxes, printers, routers) monitored by hub-side http/tcp/ping checks, with latency history — and routed like any other service.
  • Node UIs — NAS portals and similar get their own card, a link on the node, a derived route and their real favicon.
  • Service health checks — routed URLs are probed every minute: status, latency and TLS certificate expiry (warning at ≤ 14 days).
  • Image update watch — running tags are compared against ghcr.io and Docker Hub every 6 h; cards grow a quiet package glyph when a newer semver tag exists.
  • Speedtest — per-node download/upload/latency, run by the agent on demand.
  • Control plane (opt-in) — restart, stop, pull, logs and more for every Docker container and Kubernetes workload from the dashboard, gated by an operator key; changes that must last (versions, replicas, new services) go through your GitOps repo, never around it (docs/control-plane.md).
  • Alerts — rules by event kind and severity, delivered via ntfy, webhook or Telegram, with cooldowns and a test button.
  • Bookmarks & widgets — YAML-synced bookmarks and an ambient rail (clock, weather, Home Assistant entities).
  • The details — dark-first hand-built design system, CSS-only animations, EN/ES, drag-to-reorder edit mode, status as dots + tooltips everywhere.

Screenshots

Dashboard — services grouped, node cards with live front-panel LEDs

Nodes — live gauges, endpoints, a down node

Gateway — routed services and exclusions with reasons

Node detail — metrics, speedtest, filesystems, history

Storage — SMART health across nodes

Quick start

# Hub — one instance, anywhere
services:
  bifrost-hub:
    image: ghcr.io/zurdi15/bifrost-hub:latest
    ports: ["8000:8000"]
    environment:
      BIFROST_ENROLL_TOKEN: change-me   # openssl rand -hex 32
    volumes: ["./data:/data"]
    restart: unless-stopped
# Agent — one per node
services:
  bifrost-agent:
    image: ghcr.io/zurdi15/bifrost-agent:latest
    pid: host
    uts: host
    environment:
      BIFROST_AGENT_HUB_URL: http://hub.example:8000
      BIFROST_AGENT_ENROLL_TOKEN: change-me
      HOST_PROC: /host/proc
      HOST_SYS: /host/sys
      HOST_ROOT: /host/rootfs
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/host/rootfs:ro
      - /etc/machine-id:/etc/machine-id:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped
docker compose up -d

Open http://localhost:8000 on the hub: nodes appear in the dashboard as they connect, with the same enroll token on both sides. The commented compose files — SMART devices, node UI, bookmarks — are examples/docker-compose.hub.yml and examples/docker-compose.agent.yml.

Configuration

Hub settings use the BIFROST_ prefix, agent settings the BIFROST_AGENT_ prefix. The full, commented list is in env.template.

Hub (BIFROST_*):

Variable Default Description
ENROLL_TOKEN change-me Shared secret agents enroll with
AUTO_APPROVE true Auto-approve enrolling nodes
SERVICE_DOMAIN (empty) Enables URL derivation + gateway feed
TELEGRAM_BOT_TOKEN (empty) For the telegram alert notifier (rule target = chat id)
DATA_DIR /data SQLite + bookmarks location
BOOKMARKS_FILE <data>/bookmarks.yml Declarative bookmarks, synced both ways
METRICS_INTERVAL_S … 10/60/1800 Agent cadences, pushed on connect
OPERATOR_KEY (empty) Turns the control plane on: write actions ask for this key once per session (docs/control-plane.md)
GITOPS_REPO / GITOPS_TOKEN (empty) Your GitOps repo: versions, replicas and new services become commits there; drift between Git and what runs is shown on every card

Agent (BIFROST_AGENT_*):

Variable Default Description
HUB_URL (required) Where the hub lives
ENROLL_TOKEN (required) Must match the hub
NODE_NAME hostname Override the node name
UI_PORT / UI_URL (empty) Declare the node's own web UI
SPEEDTEST_URL (built-in) Download target for the speedtest (pick a mirror near you)
METRICS_INTERVAL (from hub) Local sampling override
CONTROL false Let the hub restart / stop / pull / deploy containers on this node
HOST_CONTROL false Let the hub reboot or shut the host down (needs privileged: true)

Data

The hub keeps two SQLite databases under DATA_DIR — bifrost.db (state) and metrics.db (history) — plus bookmarks.yml. Migrations run automatically when the hub starts. To back it up, stop the hub first: tarring a live WAL database gives you a torn copy. Agents are stateless; a node's identity derives from its machine-id fingerprint.

Reverse proxy & authentication

The dashboard has no authentication: protect it at the network layer (Tailscale, or a reverse proxy with auth). Agent enrollment is token-authenticated, and with AUTO_APPROVE=false new nodes wait as pending until you approve them in the UI. Agents hold a persistent WebSocket to the hub, so anything you put in front of the hub needs WebSocket upgrade support.

How discovery works

Docker services

The agent mounts docker.sock read-only and keeps the hub current three ways: a full container list on connect, the Docker event stream (start/die/health) for instant card updates, and periodic stats samples (CPU/mem per container). Containers with no routable port (databases, workers) still get cards — they just don't get URLs.

For containers running with network_mode: host the agent additionally inspects the image's EXPOSE list: those ports are listening on the host itself, so they count as routable — no labels needed for things like NAS backup daemons.

Kubernetes

The agent recognizes k3s / kubeadm / k0s on the node and hands the hub a kubeconfig when it can read one, plus the addresses the host answers on; the hub picks the first it can reach (resolved node name, reported IPs, WebSocket peer) and falls back through the rest if the API ever stops connecting. Cards of a cluster that has not synced for a while turn grey (unknown) with a notice, instead of reporting stale replica counts as health. The hub then talks to the API server directly: Ingresses define service URLs, Services map workloads, CronJobs feed the Jobs view with run history. System namespaces (kube-system, argocd, cert-manager, …) are hidden by default; per-workload annotations (below) tune the rest.

Route derivation (the zero-label philosophy)

Set one variable on the hub and every service gets a URL by convention:

environment:
  BIFROST_SERVICE_DOMAIN: example.net
Source Rule Route
Docker container exactly one published TCP port (or EXPOSE on host-net) https://<container>.example.net → node:port
Docker container several ports excluded until you pick one with bifrost.port
k8s Ingress its real host already routed by the cluster; shown, never overridden
Endpoint node its http/tcp check target https://<endpoint>.example.net → target host:port
Agent node with a UI ui_port https://<node>.example.net → node:ui_port

Conflict rules, in order: hand-written proxy vhosts always win (the sync skips their hostnames) → explicit bifrost.url beats convention → derived names yield to k8s Ingress hosts → duplicate hostnames keep the first claimant and the rest are excluded. The Gateway view shows every routed service (with live check status), every cluster Ingress, and every excluded container with the reason, so nothing fails silently.

Labels are the escape hatch, not the requirement:

Docker label / k8s annotation Effect
bifrost.url explicit URL — wins over everything
bifrost.port pick the routable port when several are published
bifrost.path redirect / to a subpath (e.g. /admin/)
bifrost.expose false opts out of routing
bifrost.hide hide the card from the dashboard
bifrost.name / bifrost.icon / bifrost.group card cosmetics

On Kubernetes the routing half is the Ingress's job; the cosmetic ones (bifrost.name/icon/group/hide) work as annotations on the workload.

The hub publishes the result at GET /api/v1/gateway/routes?domain=<domain> — a stable, minimal feed (host/node/port/path) meant for reverse-proxy sync. examples/gateway ships a Caddy setup with a caddy-sync sidecar: wildcard DNS record, DNS-01 wildcard certs, generated handle blocks hot-reloaded through Caddy's admin API, and a catch-all fallthrough (e.g. to a k8s ingress controller).

Endpoints (agentless nodes)

Some things can't run an agent. Add them as endpoints (Nodes → Endpoints, in edit mode, or POST /api/v1/nodes/endpoints) with an http, tcp or ping check. The hub probes them, keeps latency history, alerts on transitions — and because the check target names a host and port, the endpoint joins the gateway feed like any container.

Node UIs

A node whose OS has a web portal (NAS boxes, routers) can declare it:

environment:
  BIFROST_AGENT_UI_PORT: "9999"        # → http://<node>:9999
  # or the full thing, if port alone isn't enough:
  BIFROST_AGENT_UI_URL: https://nas.example.net

(Also settable from the node detail view or PATCH /api/v1/nodes/{uuid}; agent env wins — it's declarative config.) The node then gets: a link on its node card and detail header, a machine-styled card in the dashboard's nodes group (front-panel LEDs driven by the node's real CPU and network telemetry), a derived gateway route, and its portal's actual favicon — the hub fetches /favicon.svg|ico|png, apple-touch-icon.png, then falls back to parsing the portal's <link rel="icon">.

Design principles

  • Infrastructure-agnostic. Any mix of bare Docker hosts, NAS boxes, Raspberry Pis and Kubernetes clusters. Bifrost adapts to what it finds.
  • Push, not pull. Agents dial the hub. Nodes never expose anything.
  • Zero labels by default. Convention derives the URL; labels exist only for the exceptions.
  • Open by design. The dashboard has no authentication — protect it at the network layer (Tailscale, reverse proxy). Agent enrollment is token-authenticated.
  • One binary, one socket, zero state. Agents are stateless; identity derives from the host machine-id fingerprint.
  • Beautiful on purpose. A hand-built design system, CSS-only animations, dark-first.

Development

Requirements: uv, Go 1.23+ and Node 22+.

./dev.sh          # hub :8000 + frontend :5173, both with hot reload
./dev.sh --seed   # same, filled with lifelike fixtures: fake agents, nodes, services, disks, bookmarks, k8s jobs
cd agent && BIFROST_AGENT_HUB_URL=http://localhost:8000 BIFROST_AGENT_ENROLL_TOKEN=change-me go run ./cmd/bifrost-agent   # a real agent against the local hub
hub/       Python 3.13 · FastAPI · SQLAlchemy 2 · Alembic · uv
agent/     Go · gopsutil · coder/websocket
frontend/  Vue 3 · Vite · TypeScript · Pinia · Tailwind 4 · Storybook
docker/    Multi-arch Dockerfiles (amd64 + arm64)
examples/  docker-compose examples, gateway (Caddy + sync), k8s RBAC
docs/      Architecture, protocol, agent, k8s, HAOS, roadmap

Use the dashboard at http://localhost:5173; the API and its docs live at http://localhost:8000/api/docs. Tests: cd hub && uv run pytest · cd agent && go test ./... · cd frontend && npm test. Deeper docs — architecture, protocol, agent, Kubernetes, HAOS, roadmap — live in docs/.

License

MIT — see LICENSE.

About

The bridge between you and your homelab

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages