#!/usr/bin/env bash set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" INVENTORY_FILE="${HOMELAB_INVENTORY_FILE:-${REPO_ROOT}/homelab.yml}" FORMAT="${1:-text}" inventory() { local path="$1" local fallback="$2" if command -v ruby >/dev/null 2>&1 && [ -f "$INVENTORY_FILE" ]; then value="$( ruby -ryaml -e ' data = YAML.load_file(ARGV[0]) value = ARGV[1].split(".").reduce(data) { |memo, key| memo.respond_to?(:[]) ? memo[key] : nil } puts value if value.is_a?(String) || value.is_a?(Integer) ' "$INVENTORY_FILE" "$path" 2>/dev/null || true )" if [ -n "$value" ]; then printf '%s\n' "$value" return 0 fi fi printf '%s\n' "$fallback" } domain="$(inventory domain.base lab2025.duckdns.org)" debian="$(inventory hosts.debian.lan_ip 192.168.100.73)" debian_ts="$(inventory hosts.debian.tailscale_ip 100.85.138.30)" rpi="$(inventory hosts.rpi4.lan_ip 192.168.100.89)" oci="$(inventory hosts.oci_edge.public_ip 132.145.170.74)" oci_ts="$(inventory hosts.oci_edge.tailscale_ip 100.118.255.19)" pimox="$(inventory hosts.opi5_pimox.lan_ip 192.168.100.80)" traefik="$(inventory network.metallb.traefik_ip 192.168.100.240)" registry="$(inventory services.local_registry.endpoint 192.168.100.73:30500)" gitea_http="$(inventory services.gitea.http_port 3000)" gitea_ssh="$(inventory services.gitea.ssh_port 32222)" pihole_web="$(inventory services.rpi_dns.pihole_web_port 8081)" kuma="$(inventory services.rpi_dns.uptime_kuma_port 3001)" if [ "$FORMAT" = "--dot" ] || [ "$FORMAT" = "dot" ]; then cat < "OCI edge ${oci}"; "OCI edge ${oci}" -> "Traefik ${traefik}"; "OCI edge ${oci}" -> "Debian Tailscale ${debian_ts}"; "Debian ${debian}" -> "Gitea :${gitea_http}/git"; "Debian ${debian}" -> "Local registry ${registry}"; "Debian ${debian}" -> "Kubernetes control plane"; "Debian ${debian}" -> "Pimox ${pimox}"; "Pimox ${pimox}" -> "Worker VMs"; "Worker VMs" -> "Kubernetes apps"; "Kubernetes apps" -> "Traefik ${traefik}"; "Gitea :${gitea_http}/git" -> "Argo CD"; "Argo CD" -> "Kubernetes apps"; "Clients" -> "Pi-hole ${rpi}:53"; "Pi-hole ${rpi}:53" -> "Unbound"; "Pi-hole ${rpi}:53" -> "Public DNS fallback"; "Uptime Kuma ${rpi}:${kuma}" -> "Public URLs"; "Uptime Kuma ${rpi}:${kuma}" -> "LAN services"; } EOF exit 0 fi cat < ${domain} -> OCI edge ${oci} / tailscale ${oci_ts} -> Traefik LoadBalancer ${traefik} -> Kubernetes ingress routes -> website, demos, Heimdall, observability, Argo CD, arr routes Git and automation Debian ${debian} -> Gitea HTTP :${gitea_http}/git and SSH :${gitea_ssh} -> Gitea Actions runner -> local registry ${registry} -> jeannie orchestration Gitea repository -> Argo CD applications -> Kubernetes manifests under apps/ -> edge/provisioning/cluster/platform Terraform stacks Cluster foundation Debian ${debian} -> kubeadm control plane -> container registry -> provisioning HTTP/TFTP Orange Pi Pimox ${pimox} -> worker VM template -> worker VM clones -> Kubernetes worker capacity DNS and always-on health Clients and lab hosts -> RPi4 Pi-hole ${rpi}:53 -> Unbound resolver -> public fallback DNS if Unbound is unhealthy RPi4 -> Pi-hole web ${rpi}:${pihole_web} -> Uptime Kuma ${rpi}:${kuma} -> monitors public URLs, LAN services, DNS, Traefik, Gitea, arr Observability and operations Prometheus stack -> nodes, pods, Traefik, website probes, Gitea/Pi-hole scrape coverage -> homelab-alerts -> Grafana dashboards jeannie status -> host/bootstrap checks -> local services -> RPi DNS and Kuma -> Pimox and Kubernetes -> platform/apps -> edge/public URLs Security learning loop security-lab namespace -> isolated OWASP practice targets -> ZAP/nuclei/trivy/lynis helper commands -> report-only findings before manual fixes Use './jeannie map --dot' to emit a Graphviz DOT version. EOF