diff --git a/README.md b/README.md index f7ffe9e..f9f3f69 100644 --- a/README.md +++ b/README.md @@ -280,6 +280,7 @@ Run a read-only health snapshot from the Debian server with: ./jeannie resource-budget ./jeannie artifact-cache status ./jeannie golden-ledger check +./jeannie route-inventory ./jeannie workers list ./jeannie change-journal list ./jeannie map @@ -343,6 +344,10 @@ cache. Use `artifact-cache instructions` for client configuration snippets. reviewable record of template VMID, storage, OS release, Kubernetes pins, runtime versions, and build metadata for Pimox worker golden images. +`route-inventory` reports Kubernetes ingress hosts, paths, backend services, +TLS coverage, and visible Uptime Kuma monitor coverage so stale routes and +missing monitors are easier to spot. + `workers` provides named Kubernetes/Pimox worker lifecycle operations: `list`, `start`, `stop`, `drain`, `uncordon`, `recreate-plan`, and `rebalance`. Use `recreate-plan ` before replacing one broken Pimox worker so the diff --git a/README.md.tmpl b/README.md.tmpl index a0de89d..7913cb6 100644 --- a/README.md.tmpl +++ b/README.md.tmpl @@ -280,6 +280,7 @@ Run a read-only health snapshot from the Debian server with: ./{{ main_script }} resource-budget ./{{ main_script }} artifact-cache status ./{{ main_script }} golden-ledger check +./{{ main_script }} route-inventory ./{{ main_script }} workers list ./{{ main_script }} change-journal list ./{{ main_script }} map @@ -343,6 +344,10 @@ cache. Use `artifact-cache instructions` for client configuration snippets. reviewable record of template VMID, storage, OS release, Kubernetes pins, runtime versions, and build metadata for Pimox worker golden images. +`route-inventory` reports Kubernetes ingress hosts, paths, backend services, +TLS coverage, and visible Uptime Kuma monitor coverage so stale routes and +missing monitors are easier to spot. + `workers` provides named Kubernetes/Pimox worker lifecycle operations: `list`, `start`, `stop`, `drain`, `uncordon`, `recreate-plan`, and `rebalance`. Use `recreate-plan ` before replacing one broken Pimox worker so the diff --git a/docs/jeannie.1.md b/docs/jeannie.1.md index 0468e71..e64f90f 100644 --- a/docs/jeannie.1.md +++ b/docs/jeannie.1.md @@ -97,6 +97,10 @@ Docker Hub pull-through image caching. `golden-ledger {show|check}` : Show or validate the Pimox golden image version ledger. +`route-inventory` +: Report Kubernetes ingress hosts, paths, backend services, TLS coverage, and +visible Uptime Kuma monitor coverage. + `workers ` : Manage Kubernetes/Pimox worker lifecycle operations. Commands include `list`, `start`, `stop`, `drain`, `uncordon`, `recreate-plan`, and `rebalance`. diff --git a/jeannie b/jeannie index e762d0f..4103b06 100755 --- a/jeannie +++ b/jeannie @@ -6011,6 +6011,11 @@ golden_ledger() { "${REPO_ROOT}/scripts/golden-ledger" "${@:2}" } +route_inventory() { + require_debian_server "route-inventory" + "${REPO_ROOT}/scripts/route-inventory" +} + workers_manage() { require_debian_server "workers" "${REPO_ROOT}/scripts/workers" "${@:2}" @@ -6085,6 +6090,9 @@ case "${1:-}" in golden-ledger) golden_ledger "$@" ;; + route-inventory) + route_inventory + ;; workers) workers_manage "$@" ;; @@ -6242,7 +6250,7 @@ case "${1:-}" in echo "Log: ${JEANNIE_LOG_FILE}" ;; *) - echo "Usage: $0 {up|plan [all|provisioning|cluster|platform|apps|edge]|rebuild-cluster|stop-cluster|start-cluster|status|capacity|recover-plan|gitops-status|cert-check|release-snapshot|backup-status|synthetic-checks|resource-budget|artifact-cache {status|up|down|instructions}|golden-ledger {show|check}|workers |change-journal {list|path}|map [--dot]|validate|access-audit|kubeconfig-readonly|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-restore|drill-gitea-restore|drill-pihole-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|state-backup|fix-debian-docker-root|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|security-scan|security-prepare|security-zap|security-k8s|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-runtime|security-attack-path|openwrt|nuke}" + echo "Usage: $0 {up|plan [all|provisioning|cluster|platform|apps|edge]|rebuild-cluster|stop-cluster|start-cluster|status|capacity|recover-plan|gitops-status|cert-check|release-snapshot|backup-status|synthetic-checks|resource-budget|artifact-cache {status|up|down|instructions}|golden-ledger {show|check}|route-inventory|workers |change-journal {list|path}|map [--dot]|validate|access-audit|kubeconfig-readonly|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-restore|drill-gitea-restore|drill-pihole-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|state-backup|fix-debian-docker-root|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|security-scan|security-prepare|security-zap|security-k8s|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-runtime|security-attack-path|openwrt|nuke}" exit 1 ;; esac diff --git a/scripts/route-inventory b/scripts/route-inventory new file mode 100755 index 0000000..eae3f22 --- /dev/null +++ b/scripts/route-inventory @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -euo pipefail + +KUBECONFIG_PATH="${KUBECONFIG:-${LAB_KUBECONFIG_PATH:-/home/jv/.kube/config}}" +KUMA_MONITORS_FILE="${LAB_UPTIME_KUMA_MONITORS_FILE:-infra/rpi-services/uptime-kuma-monitors.json}" +INGRESS_JSON="" + +cleanup() { + if [ -n "$INGRESS_JSON" ] && [ -f "$INGRESS_JSON" ]; then + rm -f "$INGRESS_JSON" + fi +} +trap cleanup EXIT + +section() { + printf '\n== %s ==\n' "$1" +} + +if ! command -v kubectl >/dev/null 2>&1 || [ ! -s "$KUBECONFIG_PATH" ]; then + echo "kubectl and kubeconfig are required for route-inventory." >&2 + exit 1 +fi +if ! kubectl --kubeconfig "$KUBECONFIG_PATH" get --raw=/readyz >/dev/null 2>&1; then + echo "Kubernetes API is unavailable." >&2 + exit 1 +fi + +section "Ingress Routes" +INGRESS_JSON="$(mktemp "${TMPDIR:-/tmp}/homelab-ingress.XXXXXX.json")" +kubectl --kubeconfig "$KUBECONFIG_PATH" get ingress -A -o json >"$INGRESS_JSON" +python3 - "$INGRESS_JSON" "$KUMA_MONITORS_FILE" <<'PY' +import json +import sys + +ingress_file, monitors_file = sys.argv[1:3] +with open(ingress_file, encoding="utf-8") as handle: + doc = json.load(handle) +monitors = [] +try: + with open(monitors_file, encoding="utf-8") as handle: + data = json.load(handle) + for item in data if isinstance(data, list) else data.get("monitors", []): + url = item.get("url") or item.get("hostname") or "" + monitors.append(url) +except (OSError, json.JSONDecodeError): + pass + +print(f"{'NAMESPACE':20} {'NAME':28} {'HOST':36} {'PATH':16} {'SERVICE':28} {'TLS':6} {'KUMA':6}") +missing = [] +for ingress in doc.get("items", []): + ns = ingress["metadata"]["namespace"] + name = ingress["metadata"]["name"] + tls_hosts = { + host + for tls in ingress.get("spec", {}).get("tls", []) + for host in tls.get("hosts", []) + } + for rule in ingress.get("spec", {}).get("rules", []): + host = rule.get("host", "") + paths = rule.get("http", {}).get("paths", []) + if not paths: + paths = [{"path": "/", "backend": {}}] + for path in paths: + backend = path.get("backend", {}).get("service", {}) + service = backend.get("name", "") + port = backend.get("port", {}).get("number") or backend.get("port", {}).get("name") or "" + route_path = path.get("path", "/") + tls = "yes" if host in tls_hosts else "no" + kuma = "yes" if any(host and host in monitor for monitor in monitors) else "no" + print(f"{ns:20} {name:28} {host:36} {route_path:16} {(service + ':' + str(port)):28} {tls:6} {kuma:6}") + if kuma == "no": + missing.append(f"{host}{route_path}") + +if missing: + print("\nRoutes missing visible Uptime Kuma monitor coverage:") + for route in sorted(set(missing)): + print(f" {route}") +PY + +section "Services Behind Ingress" +kubectl --kubeconfig "$KUBECONFIG_PATH" get svc -A \ + -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,TYPE:.spec.type,CLUSTER-IP:.spec.clusterIP,PORTS:.spec.ports[*].port' | + sed -n '1,120p'