graphing the topology of my homelab with a custom DSL
  • Python 95.8%
  • Vim Script 2.1%
  • Just 1.6%
  • Shell 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-11-02 20:13:12 -07:00
docs bump: version 0.1.0 → 0.3.0 2026-10-07 12:45:16 -07:00
src/homelab_graph bump: version 0.1.0 → 0.3.0 2026-10-07 12:45:16 -07:00
syntax vim-syntax: auto-generate homelab.vim from grammar definitions 2026-10-02 09:51:49 -07:00
tests cli: add sync subcommand for one-shot graph updates 2026-10-07 12:43:30 -07:00
.coverage tests: comprehensive edge-case coverage for all modules 2026-10-02 09:58:26 -07:00
.gitignore cli: add shell completion subcommand with just recipes 2026-10-02 10:30:24 -07:00
.python-version initial uv project setup 2026-10-02 09:23:13 -07:00
AGENTS.md docs: add AGENTS.md with project structure and useful commands 2026-10-07 12:44:28 -07:00
homelab-graph-wrapper.sh docs: simplify homelab-graph skill to use wrapper script 2026-10-02 12:55:55 -07:00
Justfile cli: add shell completion subcommand with just recipes 2026-10-02 10:30:24 -07:00
pyproject.toml bump: version 0.1.0 → 0.3.0 2026-10-07 12:45:16 -07:00
README.md docs: update README, man page, and agent skill for new queries 2026-10-02 11:40:43 -07:00
topology.homelab parser: switch to Lark EBNF grammar with dashed identifiers 2026-10-02 09:34:44 -07:00
uv.lock update dependencies 2026-11-02 20:13:12 -07:00

homelab-graph

A declarative DSL for describing homelab infrastructure as a dependency graph, with real-time file watching, linting, and embedded graph queries.

Open-WebUI runs on Kubernetes in namespace ai
Traefik proxies to Open-WebUI on port 8080 via https
Kubernetes is powered by etherium

Features

  • .homelab files — YAML frontmatter for entity metadata, plain English for relationships
  • 28 verb phrases — compute, network, storage, identity, backup, hardware, observability
  • 19 modifier types — ports, VLANs, protocols, paths, modes, schedules, and more
  • Lark EBNF parser — grammar-driven, order-independent modifiers, precise error reporting
  • NetworkX + SQLite — embedded graph store, no external dependencies
  • Real-time watcher — watchdog with debounce, lint-gated sync
  • Graph queries — blast radius, dependency chains, SPOF detection, backup audits, 6 new operational queries
  • CLI — unified homelab-graph command with 6 subcommands
  • Vim syntax — auto-generated from grammar
  • 146 tests — comprehensive coverage

Quick Start

# Install
git clone https://github.com/colin/graph-homelab
cd graph-homelab
just setup

# Parse a topology file
homelab-graph parse topology.homelab

# Lint for errors
homelab-graph lint topology.homelab

# Watch for changes (auto-syncs to graph)
homelab-graph watch

# Query the graph
homelab-graph query --blast-radius netherite
homelab-graph query --chain kubernetes
homelab-graph query --spofs
homelab-graph query --unbacked
homelab-graph query --on-node netherite
homelab-graph query --shares TrueNAS
homelab-graph query --backup-target PBS
homelab-graph query --notification-channel NTFY
homelab-graph query --namespace ai
homelab-graph query --affected-by "netherite disk full"

# Generate man pages
just man-pages

File Format

.homelab files

---
# Entity declarations (YAML frontmatter)
hosts:
  netherite: { ip: 10.0.0.10, os: debian, cores: 16 }
  etherium:   { ip: 10.0.0.11, os: proxmox, cores: 32 }
services:
  Open-WebUI: { repo: ghcr.io/open-webui/open-webui }
  Authentik:  { version: 2024.4 }
  Traefik:    { internal: false }
  Prometheus: { port: 9090 }
storage:
  TrueNAS: { pool: tank, capacity: 40TB }
  PBS:     { type: proxmox-backup }
---
# Relationships (plain English)
Open-WebUI runs on Kubernetes in namespace ai
Authentik runs on netherite
Kubernetes is powered by etherium
Traefik proxies to Open-WebUI on port 8080 via https
Plex mounts TrueNAS at /mnt/media as ro
netherite is plugged into USW-Lite-16 on port 8 on vlan 50
Kubernetes scraped by Prometheus interval 15s

Entity names

Spaces become dashes in the graph:

Open WebUI    →  Open-WebUI
my service    →  my-service

Modifiers

Modifiers are prepositional phrases that attach to relationships:

Pattern Property Example
on port <int> port on port 8080
on vlan <int> vlan on vlan 50
on outlet <int> outlet on outlet 2
at u <int> rack_unit at u 12
via <word> / over <word> protocol via https, over ipsec
at <path> path at /mnt/media
as <mode> mode as ro, as rw
in namespace <word> namespace in namespace ai
on interface <word> interface on interface eth0
using driver <word> driver using driver local
scoped to <word> scope scoped to email
with role <word> role with role admin
encrypted with <word> encryption encrypted with aes256
channel <word> channel channel alerts
with severity <word> severity with severity high
interval <int>s interval_sec interval 15s
retaining <int> retention_copies retaining 7
every <schedule> schedule every daily, every 0_2_*_*_*

Modifiers can appear in any order:

Traefik proxies to App on port 443 via https
Traefik proxies to App via https on port 443
# Both parse identically

Verbs

Category Phrases Canonical
Compute runs on, hosted on, clusters with, orchestrated by RUNS_ON, CLUSTERS_WITH, ORCHESTRATED_BY
Network proxies to, routes to, tunnels through, resolves through, listens on PROXIES_TO, TUNNELS_THROUGH, RESOLVES_THROUGH, LISTENS_ON
Storage mounts, reads from, persists to, shares via MOUNTS, PERSISTS_TO, SHARES_VIA
Identity logs in with, authenticates via, syncs users from, fetches secrets from AUTHENTICATES_VIA, SYNCS_USERS_FROM, FETCHES_SECRETS_FROM
Backup backs up to, replicates to BACKS_UP_TO, REPLICATES_TO
Hardware powered by, plugged into, located in, mounted in POWERED_BY, PLUGGED_INTO, LOCATED_IN
Observability monitored by, scraped by, notifies via MONITORED_BY, NOTIFIES_VIA

CLI

$ homelab-graph --help

Usage: homelab-graph [OPTIONS] COMMAND [ARGS]...

Commands:
  complete    Print shell completion script for the given shell.
  grammar     Print the EBNF grammar for .homelab files.
  lint        Lint a .homelab file for semantic errors.
  parse       Parse a .homelab file and print the parsed structure.
  query       Query the homelab graph for insights.
  version     Print the package version.
  vim-syntax  Generate Vim syntax highlighting for .homelab files.
  watch       Watch a .homelab file for changes and auto-sync to the graph.

Subcommands

grammar — Print the EBNF grammar

homelab-graph grammar

Useful for AI agents to understand all valid syntax.

complete — Print shell completion script

homelab-graph complete zsh   # bash, zsh, fish, powershell

Or generate all completions with just completions.

parse — Print parsed entities and triplets

homelab-graph parse topology.homelab
homelab-graph parse topology.homelab --json  # JSON output

lint — Validate topology file

homelab-graph lint topology.homelab
homelab-graph lint --strict topology.homelab  # undeclared entities = errors

watch — Auto-reload on file changes

homelab-graph watch
homelab-graph watch -f topology.homelab -d homelab_graph.sqlite --debounce 0.5 --strict

query — Graph queries

homelab-graph query --blast-radius netherite
homelab-graph query --chain kubernetes
homelab-graph query --spofs
homelab-graph query --unbacked
homelab-graph query --on-node netherite
homelab-graph query --shares TrueNAS
homelab-graph query --backup-target PBS
homelab-graph query --notification-channel NTFY
homelab-graph query --namespace ai
homelab-graph query --affected-by "netherite disk full"
homelab-graph query --blast-radius X --json  # JSON output

New query flags

Flag Purpose Example
--on-node <host> Services running on a host --on-node netherite
--shares <resource> Services sharing a resource --shares TrueNAS
--backup-target <target> Services backing up to a target --backup-target PBS
--notification-channel <ch> Services alerting via a channel --notification-channel NTFY
--namespace <name> Services in a K8s namespace --namespace ai
--affected-by <problem> Map problem → impacted services --affected-by "disk full"

vim-syntax — Generate Vim/Neovim syntax file

homelab-graph vim-syntax
homelab-graph vim-syntax ~/.config/nvim/syntax/homelab.vim

version — Print version

homelab-graph version

Architecture

topology.homelab
    │
    ▼
┌─────────────────┐
│  Lark Parser    │  EBNF grammar → Triplet AST
└─────────────────┘
    │
    ▼
┌─────────────────┐
│  Linter         │  Port/VLAN ranges, self-refs, typo detection
└─────────────────┘
    │
    ▼
┌─────────────────┐
│  NetworkX +     │  MultiDiGraph persisted to SQLite
│  SQLite         │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│  homelab-graph  │  CLI with query, blast radius, SPOF detection
└─────────────────┘

Development

just setup          # Install all dependencies
just check          # lint + ty + test
just lint           # ruff check + format
just lint-fix       # auto-fix ruff issues
just format         # ruff format
just test           # pytest
just test-cov       # pytest with coverage
just build          # uv build
just man-pages      # generate man pages
just clean          # remove build artifacts

Schema

Nodes

(:Component {
    id: STRING,       # slugified name
    name: STRING,     # display name
    kind: STRING,     # host, service, storage, component
    metadata: STRING  # JSON object
})

Relationships

Edge Type Description Properties
[:RUNS_ON] Service runs on host —
[:CLUSTERS_WITH] Nodes in cluster —
[:ORCHESTRATED_BY] Workload → control plane role, namespace
[:PROXIES_TO] Reverse proxy → backend port, protocol
[:TUNNELS_THROUGH] Host → VPN/overlay interface
[:RESOLVES_THROUGH] Subnet → DNS server port
[:LISTENS_ON] Service → port/VLAN port, vlan, interface
[:MOUNTS] Service → storage path, mode, driver
[:PERSISTS_TO] Service → database driver
[:SHARES_VIA] Storage → protocol —
[:BACKS_UP_TO] Service → backup target schedule, retention_copies, encryption
[:REPLICATES_TO] Primary → secondary schedule, encryption
[:AUTHENTICATES_VIA] Service → IdP protocol, scope
[:SYNCS_USERS_FROM] IdP → directory —
[:FETCHES_SECRETS_FROM] Service → secret store role
[:POWERED_BY] Host → UPS/PDU outlet
[:PLUGGED_INTO] Host → switch port, vlan
[:LOCATED_IN] Host → rack rack_unit
[:MONITORED_BY] Host → monitor interval_sec
[:NOTIFIES_VIA] Monitor → channel channel, severity

Lint Rules

Check Level Detail
Port range ERROR 1 ≤ port ≤ 65535
VLAN range ERROR 1 ≤ vlan ≤ 4094
Outlet range ERROR 1 ≤ outlet ≤ 255
Mount mode ERROR must be ro or rw
Self-reference ERROR subject ≠ target
Undeclared entity WARNING with fuzzy difflib suggestion
Undeclared entity (strict) ERROR same as above

Vim/Neovim Setup

# Generate syntax file
homelab-graph vim-syntax ~/.config/nvim/syntax/homelab.vim

# Create ftdetect directory
mkdir -p ~/.config/nvim/ftdetect

# Add filetype detection
echo 'autocmd BufRead,BufNewFile *.homelab set filetype=homelab' \
    >> ~/.config/nvim/ftdetect/homelab.vim

Shell Completion

# Generate and source zsh completion
eval "$(homelab-graph complete zsh)"

# Or generate a script for your shell config
homelab-graph complete zsh > ~/.local/share/zsh/site-functions/_homelab-graph
homelab-graph complete bash > ~/.local/share/bash-completion/completions/homelab-graph
homelab-graph complete fish > ~/.config/fish/completions/homelab-graph.fish

# Generate all at once
just completions

License

MIT