#!/usr/bin/env bash # Merge one or more estate.json files into an Open Line estate quote. # Usage: bash estate-audit-merge.sh host1/estate.json host2/estate.json set -u if [ $# -lt 1 ]; then echo "Usage: bash estate-audit-merge.sh reports/*/estate.json" >&2 exit 2 fi python3 - "$@" <<'PY' import json, sys, collections paths = sys.argv[1:] hosts = [] for p in paths: try: with open(p) as f: hosts.append(json.load(f)) except Exception as e: print(f"skip {p}: {e}", file=sys.stderr) n = len(hosts) distros = collections.Counter() inbox = collections.Counter() addons = collections.Counter() wp = 0 k8s = 0 ai = 0 edge = 0 ceph = 0 mail = 0 cores = 0.0 mem = 0.0 names = [] for h in hosts: names.append(h.get("hostname") or "?") osid = (h.get("os") or {}).get("id") or "unknown" distros[osid] += 1 cap = h.get("capacity") or {} try: cores += float(cap.get("cores") or 0) except: pass try: mem += float(cap.get("mem_gb") or 0) except: pass ol = h.get("open_line") or {} for x in filter(None, (ol.get("inbox") or "").split(",")): inbox[x.strip()] += 1 for x in filter(None, (ol.get("addons") or "").split(",")): x = x.strip() addons[x] += 1 if x.startswith("wordpress"): try: wp += int(x.split(":")[-1]) except: wp += 1 if x == "kubernetes": k8s += 1 if x == "private-ai": ai += 1 if x in ("edge-device", "edge"): edge += 1 if x == "ceph": ceph += 1 if x == "mail": mail += 1 st = h.get("stack") or {} if st.get("kubernetes"): k8s = max(k8s, 1) BENCH, WORK, LINE = 4950, 9850, 19500 if n <= 5: pkg, base = "Open Bench", BENCH elif n <= 15: pkg, base = "Open Workshop", WORK else: pkg, base = "Open Line", LINE if k8s or ai or ceph: if pkg == "Open Bench": pkg, base = "Open Workshop", WORK add_zar = 0 lines = [] if wp: if wp >= 5: add_zar += 3950 lines.append(f" WordPress {wp} sites starting from R3950 (5-site bundle)") else: add_zar += 1450 * wp lines.append(f" WordPress {wp} site(s) starting from R{1450*wp}") if mail: add_zar += 1950 lines.append(" Mail starting from R1950") if k8s and pkg == "Open Bench": add_zar += 4950 lines.append(" Kubernetes starting from R4950") elif k8s and pkg != "Open Bench": lines.append(" Kubernetes included on Workshop/Line (first cluster)") if k8s > 1: extra = k8s - 1 add_zar += 4950 * extra lines.append(f" Extra k8s clusters x{extra} starting from R{4950*extra}") if ai: add_zar += 4500 lines.append(" Private AI node starting from R4500") if edge: add_zar += 2950 lines.append(f" Edge fleet ({edge} device(s) toward a 25-pack) starting from R2950") if ceph: add_zar += 6500 lines.append(" Ceph starting from R6500") total = base + add_zar print("CatchupWeb Open Line — estate merge") print(f"Hosts: {n} cores~{cores:.0f} RAM~{mem:.0f}G") print("Distros: " + ", ".join(f"{k} x{v}" for k,v in distros.most_common())) print("In-box: " + (", ".join(f"{k}({v})" for k,v in inbox.most_common()) or "none")) print("Suggested package: " + pkg) print(f" Base starting from R{base}") if lines: print("Add-ons:") print("\n".join(lines)) print(f"ESTATE STARTING FROM R{total} excl VAT") print("Hours extra R1650 · 10-hour pack R11500") print("https://www.catchupweb.co.za/open-line/") print("Hosts: " + ", ".join(names[:20]) + ("…" if n>20 else "")) PY