SPB Git forge
1commits 1branches 0releases
84.0 KBsize
maindefault branch
1 h agolast push
Python 99.8%
37.4 KB · 583 lines python
Raw Blame History
1#!/usr/bin/env python32"""MacLustr public website (multi-page). Stdlib only (Python 3.9+).3Runs on m2u64, serves http://0.0.0.0:8004  ->  ngrok www.maclustr.io."""4import json, os, html5from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer6from urllib.parse import urlparse78HERE = os.path.dirname(os.path.abspath(__file__))9PORT = 800410CONTACT = "spbou4@protonmail.com"11NODES = json.load(open(os.path.join(HERE, "nodes.json")))12TIER_ORDER = {"ULTRA": 0, "MAX": 1, "PRO": 2, "BASE": 3}13TCOLOR = {"ULTRA": "#5B21B6", "MAX": "#7C3AED", "PRO": "#2563EB", "BASE": "#0891B2"}1415def esc(s): return html.escape(str(s))16def gb_to_tb(g): return f"{g/1024:.1f} TB" if g >= 1024 else f"{g} GB"17def store_gb(v):18    v = (v or "").strip()19    if v.endswith("TB"): return int(float(v.replace("TB", "").strip())) * 102420    if v.endswith("GB"): return int(float(v.replace("GB", "").strip()))21    return 02223# ---- modern line icons (Lucide-style, stroke=currentColor) ----24_P = {25"cpu":'<rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M9 2v2M15 2v2M9 20v2M15 20v2M20 9h2M20 14h2M2 9h2M2 14h2"/>',26"server":'<rect x="2" y="3" width="20" height="8" rx="2"/><rect x="2" y="13" width="20" height="8" rx="2"/><path d="M6 7h.01M6 17h.01"/>',27"cloud":'<path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/>',28"zap":'<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8Z"/>',29"lock":'<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',30"command":'<path d="M15 6a3 3 0 1 1 3 3h-3V6ZM9 6a3 3 0 1 0-3 3h3V6ZM15 18a3 3 0 1 0 3-3h-3v3ZM9 18a3 3 0 1 1-3-3h3v3Z"/><rect x="9" y="9" width="6" height="6"/>',31"mail":'<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 5L2 7"/>',32"arrow":'<path d="M5 12h14M13 6l6 6-6 6"/>',33"layers":'<path d="m12 2 9 5-9 5-9-5 9-5Z"/><path d="m3 12 9 5 9-5"/><path d="m3 17 9 5 9-5"/>',34"db":'<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/>',35"globe":'<circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20Z"/>',36"check":'<path d="M20 6 9 17l-5-5"/>',37"gauge":'<path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/><path d="M13.4 12.6 19 7"/><path d="M3.5 18a10 10 0 1 1 17 0"/>',38"gpu":'<rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="9" cy="12" r="2.5"/><path d="M15 10h4M15 14h4M2 18v2"/>',39"chip":'<rect x="5" y="5" width="14" height="14" rx="2"/><path d="M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3"/>',40"hd":'<rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 21V11a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v10"/><path d="M6 14h.01"/>',41}42def icon(name, size=24, cls=""):43    return (f'<svg class="{cls}" width="{size}" height="{size}" viewBox="0 0 24 24" fill="none" '44            f'stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">{_P.get(name,"")}</svg>')4546CSS = """47:root{--bg:#fafbfd;--card:#fff;--ink:#0e1016;--ink2:#3a3f4b;--mut:#737a89;--line:#edeff4;48--acc:#7c3aed;--acc2:#2f6bff;--ring:rgba(124,58,237,.14);49--sh:0 1px 2px rgba(16,24,40,.04),0 1px 3px rgba(16,24,40,.06);50--sh2:0 12px 40px rgba(16,24,40,.10);--r:22px}51*{box-sizing:border-box;margin:0;padding:0}52html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}53body{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text",Helvetica,Arial,sans-serif;54background:var(--bg);color:var(--ink);line-height:1.6;-webkit-font-smoothing:antialiased;letter-spacing:-.01em}55a{color:inherit;text-decoration:none}56svg{display:block}57.wrap{max-width:1080px;margin:0 auto;padding:0 20px}58/* nav */59.nav{position:sticky;top:0;z-index:20;background:rgba(250,251,253,.72);backdrop-filter:saturate(180%) blur(16px);border-bottom:1px solid var(--line)}60.nav .in{max-width:1080px;margin:0 auto;padding:13px 20px;display:flex;align-items:center;gap:12px}61.nav .logo{width:32px;height:32px;border-radius:9px;box-shadow:var(--sh)}62.nav .nm{font-weight:800;font-size:17px;letter-spacing:-.02em}63.nav .links{margin-left:auto;display:flex;align-items:center;gap:6px}64.nav .links a{padding:8px 13px;border-radius:11px;font-size:14px;font-weight:600;color:var(--mut);transition:.15s}65.nav .links a:hover{color:var(--ink);background:#f1f2f7}66.nav .links a.on{color:var(--ink);background:#eef0f6}67.nav .cta{margin-left:6px;background:var(--ink);color:#fff!important;padding:9px 16px!important;border-radius:11px;font-weight:700}68.nav .cta:hover{background:#000;opacity:1}69@media(max-width:640px){.nav .links a:not(.cta){display:none}}70/* generic */71.kick{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;font-weight:700;letter-spacing:.3px;color:var(--acc);72background:var(--ring);padding:6px 13px;border-radius:999px}73.eyebrow{text-align:center}74h1{font-size:clamp(40px,7.5vw,74px);font-weight:830;letter-spacing:-.035em;line-height:1.02}75h2{font-size:clamp(28px,5vw,42px);font-weight:800;letter-spacing:-.03em;line-height:1.08}76.grad{background:linear-gradient(105deg,var(--acc),var(--acc2));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}77.lead{font-size:clamp(16px,2.4vw,20px);color:var(--mut);max-width:660px;line-height:1.55}78.section{padding:62px 0}79.center{text-align:center}.mx{margin-left:auto;margin-right:auto}80.btn{display:inline-flex;align-items:center;gap:9px;font-weight:750;font-size:15.5px;padding:14px 24px;border-radius:14px;transition:.15s}81.btn.p{background:var(--ink);color:#fff;box-shadow:var(--sh2)}82.btn.p:hover{transform:translateY(-1px)}83.btn.g{background:var(--card);color:var(--ink);border:1px solid var(--line);box-shadow:var(--sh)}84.btn.g:hover{background:#f6f7fb}85.row-btn{display:flex;gap:12px;flex-wrap:wrap}86.hero{padding:74px 0 24px;text-align:center}87.hero .logo{width:84px;height:84px;border-radius:22px;box-shadow:var(--sh2);margin:0 auto 24px}88.hero .lead{margin:20px auto 0}89.hero .row-btn{justify-content:center;margin-top:32px}90/* stats */91.stats{display:grid;grid-template-columns:repeat(5,1fr);gap:13px;margin-top:54px}92@media(max-width:780px){.stats{grid-template-columns:repeat(2,1fr)}}93.stat{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:22px 16px;text-align:center;box-shadow:var(--sh)}94.stat .ic{color:var(--acc);margin:0 auto 10px;width:24px}95.stat .n{font-size:clamp(24px,4.6vw,34px);font-weight:830;letter-spacing:-.03em;font-variant-numeric:tabular-nums}96.stat .l{font-size:12px;font-weight:650;text-transform:uppercase;letter-spacing:.5px;color:var(--mut);margin-top:3px}97/* feature cards */98.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:36px}99@media(max-width:820px){.cards{grid-template-columns:1fr}}100.fcard{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:26px;box-shadow:var(--sh);transition:.18s}101.fcard:hover{box-shadow:var(--sh2);transform:translateY(-2px)}102.fcard .ic{width:50px;height:50px;border-radius:15px;display:flex;align-items:center;justify-content:center;color:#fff;103background:linear-gradient(135deg,var(--acc),var(--acc2));box-shadow:0 6px 16px var(--ring);margin-bottom:16px}104.fcard h3{font-size:20px;font-weight:780;letter-spacing:-.02em}105.fcard p{color:var(--mut);font-size:14.8px;margin-top:8px}106.fcard ul{list-style:none;margin-top:14px;display:grid;gap:8px}107.fcard li{display:flex;gap:9px;font-size:14px;color:var(--ink2)}108.fcard li svg{color:var(--acc);flex:0 0 auto;margin-top:3px}109/* nodes */110.tier{display:flex;align-items:center;gap:12px;margin:34px 0 14px}111.tier .pill{display:inline-flex;align-items:center;color:#fff;font-weight:750;font-size:13px;padding:6px 15px;border-radius:10px}112.tier .d{color:var(--mut);font-size:13.5px}113.nodes{display:grid;grid-template-columns:repeat(auto-fill,minmax(252px,1fr));gap:14px}114.node{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:18px;box-shadow:var(--sh);position:relative;overflow:hidden}115.node:before{content:"";position:absolute;left:0;top:0;bottom:0;width:4px;background:var(--tc)}116.node .h{display:flex;align-items:baseline;gap:8px}117.node .nm{font-size:22px;font-weight:820;letter-spacing:-.02em}118.node .md{margin-left:auto;font-size:12px;color:var(--mut);font-weight:600}119.node .ch{font-size:12.5px;color:var(--ink2);font-weight:700;margin-top:1px}120.node .sp{display:grid;grid-template-columns:1fr 1fr;gap:6px 16px;margin-top:14px;font-size:13.5px}121.node .sp>div{display:flex;justify-content:space-between;border-bottom:1px solid var(--line);padding-bottom:5px}122.node .sp span{color:var(--mut)}.node .sp b{font-weight:700;font-variant-numeric:tabular-nums}123/* split / about */124.split{display:grid;grid-template-columns:1.1fr .9fr;gap:40px;align-items:center}125@media(max-width:820px){.split{grid-template-columns:1fr;gap:24px}}126.mini{display:grid;grid-template-columns:1fr 1fr;gap:12px}127.mini .stat{padding:18px 14px;text-align:left}.mini .stat .ic{margin:0 0 8px}128/* steps */129.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:34px;counter-reset:s}130@media(max-width:820px){.steps{grid-template-columns:1fr}}131.step{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:24px;box-shadow:var(--sh)}132.step .no{width:34px;height:34px;border-radius:11px;background:var(--ring);color:var(--acc);font-weight:800;display:flex;align-items:center;justify-content:center;margin-bottom:13px}133.step h3{font-size:18px;font-weight:760}.step p{color:var(--mut);font-size:14.5px;margin-top:6px}134/* contact band */135.band{background:linear-gradient(140deg,#15102a,#1f1640 55%,#10213f);color:#fff;border-radius:30px;padding:clamp(36px,6vw,64px);text-align:center;box-shadow:var(--sh2)}136.band h2{color:#fff}.band p{color:#cdc8e6;max-width:620px;margin:14px auto 0;font-size:17px}137.band .mail{display:inline-flex;align-items:center;gap:11px;margin-top:28px;background:#fff;color:#15102a;font-weight:800;138font-size:clamp(15px,3.6vw,21px);padding:16px 28px;border-radius:15px;word-break:break-all}139.chips{display:flex;gap:9px;justify-content:center;flex-wrap:wrap;margin-top:22px}140.chips span{background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.16);padding:7px 15px;border-radius:999px;font-size:13.5px;font-weight:600}141/* pricing plans */142.plans{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:36px}143@media(max-width:900px){.plans{grid-template-columns:repeat(2,1fr)}}144@media(max-width:520px){.plans{grid-template-columns:1fr}}145.plan{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:24px;box-shadow:var(--sh);display:flex;flex-direction:column}146.plan.hot{border-color:transparent;background:linear-gradient(160deg,#1a1230,#231a40);color:#fff;box-shadow:var(--sh2)}147.plan .tg{display:inline-flex;align-self:flex-start;color:#fff;font-weight:750;font-size:12px;padding:5px 12px;border-radius:8px;letter-spacing:.4px}148.plan h3{font-size:21px;font-weight:800;margin-top:14px;letter-spacing:-.02em}149.plan .from{font-size:12.5px;color:var(--mut);margin-top:14px;font-weight:600}150.plan.hot .from{color:#bdb6da}151.plan .price{font-size:40px;font-weight:840;letter-spacing:-.03em;line-height:1}152.plan .price small{font-size:15px;font-weight:700;color:var(--mut)}153.plan.hot .price small{color:#bdb6da}154.plan p{color:var(--mut);font-size:13.8px;margin-top:12px}155.plan.hot p{color:#cdc8e6}156.plan ul{list-style:none;margin-top:14px;display:grid;gap:7px;flex:1}157.plan li{display:flex;gap:8px;font-size:13.3px;color:var(--ink2)}158.plan.hot li{color:#e7e2f5}159.plan li svg{color:var(--acc);flex:0 0 auto;margin-top:2px}160.plan.hot li svg{color:#b794ff}161.plan a.cta2{margin-top:18px;text-align:center;background:var(--ink);color:#fff;font-weight:750;padding:12px;border-radius:12px;font-size:14.5px}162.plan.hot a.cta2{background:#fff;color:#1a1230}163/* pricing table */164.ptable{margin-top:18px;border:1px solid var(--line);border-radius:18px;overflow:hidden;box-shadow:var(--sh);background:var(--card)}165.pscroll{overflow-x:auto}166table.pg{border-collapse:collapse;width:100%;min-width:620px;font-size:14px}167table.pg th,table.pg td{text-align:right;padding:13px 14px;border-bottom:1px solid var(--line);white-space:nowrap}168table.pg th:first-child,table.pg td:first-child,table.pg th:nth-child(2),table.pg td:nth-child(3){text-align:left}169table.pg thead th{font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--mut);font-weight:750;background:#fafbfd}170table.pg tbody tr:last-child td{border-bottom:0}171table.pg tbody tr:hover{background:#fafbfd}172table.pg .node-nm{font-weight:800;letter-spacing:-.01em}173table.pg .tg{display:inline-block;color:#fff;font-size:11px;font-weight:750;padding:3px 9px;border-radius:7px}174table.pg .spec{color:var(--mut);font-variant-numeric:tabular-nums}175table.pg .ml{font-weight:800;font-variant-numeric:tabular-nums}176table.pg .aws{color:var(--mut);text-decoration:line-through;font-variant-numeric:tabular-nums}177table.pg .num{font-variant-numeric:tabular-nums}178.note{color:var(--mut);font-size:12.5px;margin-top:14px;line-height:1.6}179/* whole-cluster banner */180.clusterbox{background:linear-gradient(140deg,#15102a,#1f1640 55%,#10213f);color:#fff;border-radius:28px;padding:clamp(28px,5vw,52px);box-shadow:var(--sh2);margin-top:34px}181.clusterbox .kick{background:rgba(255,255,255,.14);color:#fff}182.clusterbox h2{color:#fff;margin-top:14px}183.clusterbox .sub2{color:#cdc8e6;margin-top:10px;font-size:16.5px;max-width:640px}184.cl-prices{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:26px}185@media(max-width:680px){.cl-prices{grid-template-columns:repeat(2,1fr)}}186.cl-prices>div{background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.15);border-radius:16px;padding:18px 16px}187.cl-prices .hl{background:#fff;color:#15102a;border-color:#fff}188.cl-prices .l{font-size:11.5px;font-weight:750;text-transform:uppercase;letter-spacing:.5px;opacity:.82}189.cl-prices .n{font-size:clamp(22px,3.6vw,32px);font-weight:840;letter-spacing:-.03em;margin-top:5px;font-variant-numeric:tabular-nums}190.cl-prices .p{font-size:12px;opacity:.7;margin-top:2px}191.cl-incl{display:flex;flex-wrap:wrap;gap:8px;margin-top:24px}192.cl-incl span{background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.16);padding:7px 14px;border-radius:999px;font-size:13px;font-weight:600}193.clusterbox .btn{margin-top:26px;background:#fff;color:#15102a}194.clusterbox .fine{color:#b3aace;font-size:12.5px;margin-top:16px;line-height:1.6}195/* footer */196footer{border-top:1px solid var(--line);margin-top:30px}197footer .in{max-width:1080px;margin:0 auto;padding:30px 20px;display:flex;gap:14px;align-items:center;flex-wrap:wrap;color:var(--mut);font-size:13.5px}198footer .nm{display:flex;align-items:center;gap:9px;font-weight:750;color:var(--ink)}199footer .nm img{width:24px;height:24px;border-radius:7px}200footer .lk{margin-left:auto;display:flex;gap:18px}201footer .lk a:hover{color:var(--ink)}202"""203204def nav(active):205    def lk(href, label, key):206        return f'<a href="{href}" class="{"on" if active==key else ""}">{label}</a>'207    return f"""<div class="nav"><div class="in">208<a href="/" style="display:flex;align-items:center;gap:11px"><img class="logo" src="/logo.svg" alt=""><span class="nm">MacLustr</span></a>209<nav class="links">{lk('/','Home','home')}{lk('/nodes','Nodes','nodes')}{lk('/services','Services','services')}{lk('/pricing','Pricing','pricing')}{lk('/about','About','about')}210<a href="/contact" class="cta">Request access</a></nav></div></div>"""211212def footer():213    return f"""<footer><div class="in">214<span class="nm"><img src="/logo.svg" alt="">MacLustr</span>215<span>Self-hosted Apple Silicon compute cluster · {len(NODES)} nodes</span>216<span class="lk"><a href="/nodes">Nodes</a><a href="/services">Services</a><a href="/about">About</a><a href="mailto:{CONTACT}">Contact</a></span>217</div></footer>"""218219def shell(title, active, body, desc=""):220    return f"""<!doctype html><html lang="en"><head><meta charset="utf-8">221<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">222<meta name="theme-color" content="#fafbfd"><meta name="description" content="{esc(desc)}">223<title>{esc(title)}</title><style>{CSS}</style></head><body>224{nav(active)}{body}{footer()}</body></html>"""225226def totals():227    return dict(228        n=len(NODES),229        cpu=sum(n["ncpu"] for n in NODES),230        gpu=sum(n["gpu"] for n in NODES),231        ne=sum(n["ne"] for n in NODES),232        ram=sum(n["mem"] for n in NODES),233        store=sum(store_gb(n["internal"]) + store_gb(n["external"]) for n in NODES),234    )235236def stat(ic, n, l):237    return f'<div class="stat"><div class="ic">{icon(ic)}</div><div class="n">{n}</div><div class="l">{l}</div></div>'238239def stats_block():240    t = totals()241    return (f'<div class="stats">'242            f'{stat("server",t["n"],"Nodes")}'243            f'{stat("cpu",t["cpu"],"CPU cores")}'244            f'{stat("chip",t["ram"],"GB unified RAM")}'245            f'{stat("gpu",t["gpu"],"GPU cores")}'246            f'{stat("hd",gb_to_tb(t["store"]),"SSD storage")}'247            f'</div>')248249def node_card(n):250    storage = f'{n["internal"]} SSD' + (f' + {n["external"]} ext' if n["external"] else "")251    return (f'<div class="node" style="--tc:{TCOLOR.get(n["tier"],"#888")}">'252            f'<div class="h"><span class="nm">{esc(n["alias"])}</span><span class="md">{esc(n["model"])}</span></div>'253            f'<div class="ch">{esc(n["chip"])}</div>'254            f'<div class="sp">'255            f'<div><span>CPU</span><b>{n["ncpu"]}c</b></div>'256            f'<div><span>GPU</span><b>{n["gpu"]}c</b></div>'257            f'<div><span>RAM</span><b>{n["mem"]} GB</b></div>'258            f'<div><span>Neural</span><b>{n["ne"]}c</b></div>'259            f'<div style="grid-column:1/3"><span>Storage</span><b>{esc(storage)}</b></div>'260            f'</div></div>')261262def nodes_grouped():263    order = sorted(NODES, key=lambda n: (TIER_ORDER.get(n["tier"], 9), -n["ncpu"], -n["mem"]))264    tiers = {}265    for n in order: tiers.setdefault(n["tier"], []).append(n)266    desc = {"ULTRA": "Mac Studio · M-series Ultra — heaviest compute & training",267            "MAX": "Mac Studio / MacBook Pro · M-series Max — high-throughput workloads",268            "PRO": "Mac mini · M-series Pro — balanced services & build agents",269            "BASE": "Mac mini / MacBook Air · M-series — lightweight always-on services"}270    out = ""271    for t, lst in tiers.items():272        out += (f'<div class="tier"><span class="pill" style="background:{TCOLOR.get(t,"#666")}">{esc(t)}</span>'273                f'<span class="d">{len(lst)} node{"s" if len(lst)>1 else ""} · {esc(desc.get(t,""))}</span></div>'274                f'<div class="nodes">{"".join(node_card(n) for n in lst)}</div>')275    return out276277# ---------- pages ----------278def page_home():279    t = totals()280    body = f"""<div class="wrap">281<div class="hero">282  <img class="logo" src="/logo.svg" alt="MacLustr">283  <div class="eyebrow"><span class="kick">{icon('command',16)} Self-hosted · Apple Silicon</span></div>284  <h1 style="margin-top:18px">The <span class="grad">self-hosted</span><br>Apple Silicon cluster</h1>285  <p class="lead">A private cluster of {t['n']} Apple Silicon Macs — Mac Studio, MacBook Pro, Mac mini and MacBook Air — networked for distributed compute, hosting and private cloud.</p>286  <div class="row-btn"><a class="btn p" href="/contact">Request access {icon('arrow',18)}</a><a class="btn g" href="/nodes">Explore the nodes</a></div>287  {stats_block()}288</div>289290<div class="section center">291  <div class="eyebrow"><span class="kick">Why MacLustr</span></div>292  <h2 style="margin-top:14px">Real hardware, not a rented VM</h2>293  <p class="lead mx" style="margin-top:12px">We pool genuine Apple Silicon — {t['cpu']} CPU cores, {t['ram']} GB unified memory, {t['gpu']} GPU cores and {t['ne']} Neural Engine cores — into one cluster you fully control.</p>294  <div class="cards" style="text-align:left">295    <div class="fcard"><div class="ic">{icon('zap')}</div><h3>Distributed compute</h3><p>Spread heavy jobs across {t['n']} machines — ML training & inference, simulation, batch processing and rendering.</p></div>296    <div class="fcard"><div class="ic">{icon('cloud')}</div><h3>Private cloud</h3><p>Your own always-on infrastructure: APIs, databases, model servers and storage on dedicated hardware.</p></div>297    <div class="fcard"><div class="ic">{icon('command')}</div><h3>Native macOS</h3><p>True Apple Silicon for macOS/iOS builds, CI agents and anything that must run on real Macs.</p></div>298  </div>299</div>300301<div class="section">{contact_band()}</div>302</div>"""303    return shell("MacLustr — Self-Hosted Apple Silicon Compute Cluster", "home", body,304                 f"MacLustr is a self-hosted cluster of {t['n']} Apple Silicon Macs for compute, hosting and private cloud.")305306def page_nodes():307    t = totals()308    body = f"""<div class="wrap">309<div class="section" style="padding-bottom:10px;text-align:center">310  <div class="eyebrow"><span class="kick">{icon('layers',16)} The fleet</span></div>311  <h2 style="margin-top:14px">Every node, fully specced</h2>312  <p class="lead mx" style="margin-top:12px">{t['n']} dedicated Apple Silicon Macs, grouped by tier. Pick the one that matches your workload.</p>313</div>314<div style="padding-bottom:30px">{stats_block()}</div>315<div style="padding-bottom:50px">{nodes_grouped()}</div>316<div class="section" style="padding-top:0">{contact_band()}</div>317</div>"""318    return shell("Nodes — MacLustr", "nodes", body, "Full specifications of every MacLustr cluster node.")319320def page_services():321    body = f"""<div class="wrap">322<div class="section" style="padding-bottom:10px;text-align:center">323  <div class="eyebrow"><span class="kick">{icon('server',16)} Services</span></div>324  <h2 style="margin-top:14px">What you can run on MacLustr</h2>325  <p class="lead mx" style="margin-top:12px">Three ways to put our Apple Silicon to work — rent it by the job, host a service, or get a private slice of cloud.</p>326  <div class="cards" style="text-align:left">327    <div class="fcard"><div class="ic">{icon('gauge')}</div><h3>Compute</h3>328      <p>Rent cores and GPU / Neural Engine time for demanding jobs.</p>329      <ul><li>{icon('check',16)} ML training & inference</li><li>{icon('check',16)} Simulation & data processing</li><li>{icon('check',16)} Batch jobs & rendering</li></ul></div>330    <div class="fcard"><div class="ic">{icon('globe')}</div><h3>Hosting</h3>331      <p>Run a service on a dedicated node with a real public endpoint.</p>332      <ul><li>{icon('check',16)} Websites & APIs</li><li>{icon('check',16)} Bots & background workers</li><li>{icon('check',16)} Public HTTPS endpoint</li></ul></div>333    <div class="fcard"><div class="ic">{icon('lock')}</div><h3>Private cloud</h3>334      <p>An isolated, always-on slice of the cluster that's yours.</p>335      <ul><li>{icon('check',16)} Storage & databases</li><li>{icon('check',16)} Model & app servers</li><li>{icon('check',16)} Dedicated, isolated resources</li></ul></div>336  </div>337</div>338339<div class="section" style="padding-top:20px">340  <div class="center"><div class="eyebrow"><span class="kick">How it works</span></div>341  <h2 style="margin-top:14px">From request to running</h2></div>342  <div class="steps">343    <div class="step"><div class="no">1</div><h3>Tell us your workload</h3><p>Email a short description — what you want to run, rough resources and how long.</p></div>344    <div class="step"><div class="no">2</div><h3>We match a node</h3><p>We pick the right tier — Ultra, Max, Pro or Base — and set up access.</p></div>345    <div class="step"><div class="no">3</div><h3>You go live</h3><p>Get credentials / an endpoint and start running. Monitor each node's live page anytime.</p></div>346  </div>347</div>348349<div class="section" style="padding-top:0">{contact_band()}</div>350</div>"""351    return shell("Services — MacLustr", "services", body, "Compute, hosting and private cloud on the MacLustr Apple Silicon cluster.")352353def page_about():354    t = totals()355    # model breakdown356    counts = {}357    for n in NODES: counts[n["model"]] = counts.get(n["model"], 0) + 1358    chips = " · ".join(f"{v}× {k}" for k, v in sorted(counts.items(), key=lambda x: -x[1]))359    body = f"""<div class="wrap">360<div class="section" style="padding-bottom:10px">361  <div class="split">362    <div>363      <div class="eyebrow" style="text-align:left"><span class="kick">{icon('cpu',16)} About</span></div>364      <h2 style="margin-top:16px">A cluster built from real Macs</h2>365      <p class="lead" style="margin-top:14px">MacLustr is a self-hosted cluster of {t['n']} Apple Silicon Macs running native macOS. Instead of renting generic cloud VMs, we pool genuine Mac hardware — fast unified memory, powerful GPUs and Neural Engines, and energy-efficient Apple Silicon — into one cluster you can run heavy workloads on.</p>366      <p class="lead" style="margin-top:12px">The fleet spans {esc(chips)} — from top-tier Ultra Studios for training down to efficient minis and Airs for always-on services.</p>367      <div class="row-btn" style="margin-top:24px"><a class="btn p" href="/contact">Get in touch {icon('arrow',18)}</a><a class="btn g" href="/nodes">See all nodes</a></div>368    </div>369    <div class="mini">370      {stat("cpu",t["cpu"],"CPU cores")}371      {stat("chip",t["ram"],"GB RAM")}372      {stat("gpu",t["gpu"],"GPU cores")}373      {stat("command",t["ne"],"Neural cores")}374      {stat("server",t["n"],"Nodes")}375      {stat("hd",gb_to_tb(t["store"]),"SSD")}376    </div>377  </div>378</div>379380<div class="section center">381  <div class="eyebrow"><span class="kick">Principles</span></div>382  <h2 style="margin-top:14px">Why self-hosted</h2>383  <div class="cards" style="text-align:left">384    <div class="fcard"><div class="ic">{icon('lock')}</div><h3>Yours, end to end</h3><p>Dedicated hardware you control — no noisy neighbours, no surprise migrations.</p></div>385    <div class="fcard"><div class="ic">{icon('zap')}</div><h3>Efficient & fast</h3><p>Apple Silicon delivers huge unified memory and performance per watt.</p></div>386    <div class="fcard"><div class="ic">{icon('gauge')}</div><h3>Transparent</h3><p>Every node has a live status page — see real CPU, memory and disk anytime.</p></div>387  </div>388</div>389390<div class="section" style="padding-top:0">{contact_band()}</div>391</div>"""392    return shell("About — MacLustr", "about", body, "About the MacLustr self-hosted Apple Silicon cluster.")393394def page_contact():395    body = f"""<div class="wrap">396<div class="section" style="padding-bottom:10px;text-align:center">397  <div class="eyebrow"><span class="kick">{icon('mail',16)} Contact</span></div>398  <h2 style="margin-top:14px">Let's find the right node for you</h2>399  <p class="lead mx" style="margin-top:12px">Need compute, hosting or private cloud on one of our nodes? Send a short note about your workload and we'll get back to you.</p>400</div>401<div class="section" style="padding-top:10px">{contact_band()}</div>402<div class="section center" style="padding-top:0">403  <h2 style="font-size:24px">What to include</h2>404  <div class="cards" style="text-align:left;margin-top:24px">405    <div class="fcard"><div class="ic">{icon('gauge')}</div><h3>Your workload</h3><p>What you want to run — compute job, a service to host, or a private cloud space.</p></div>406    <div class="fcard"><div class="ic">{icon('chip')}</div><h3>Rough resources</h3><p>Cores, memory, GPU, storage — even a ballpark helps us match a tier.</p></div>407    <div class="fcard"><div class="ic">{icon('db')}</div><h3>Duration</h3><p>One-off job or always-on? Let us know the timeframe.</p></div>408  </div>409</div>410</div>"""411    return shell("Contact — MacLustr", "contact", body, f"Contact MacLustr at {CONTACT} for compute, hosting and cloud.")412413# ---- pricing (USD). MacLustr rate ~= 10% below a comparable AWS EC2 Mac Dedicated Host. ----414# rate = MacLustr $/h ; AWS est. $/h = rate / 0.9 ; day = rate*24 ; month = rate*730415RATES = {416    "m3u96a": 8.44, "m3u96b": 8.44, "m2u64": 7.47,417    "m4m64a": 7.47, "m4m64b": 7.47, "m4bp48": 6.72, "m4m36": 5.41, "m4bp36": 5.41,418    "m2m32": 1.40, "m2m32b": 1.40, "m2m32c": 1.40, "m1m32": 1.35,419    "m4mc": 0.77, "m2m16": 0.99, "m4ma": 1.11, "m4mb": 0.96,420    "m3ba24": 0.59, "m3ba16": 0.51, "m2m8a": 0.43, "m2m8b": 0.43,421}422PLANS = [423    ("ULTRA", "#5B21B6", "AI &amp; heavy compute", 7.50, True,424     "M-series Ultra &amp; M4 Max with 64–96&nbsp;GB unified memory. Built for model training, large-context inference and the heaviest jobs.",425     ["64–96 GB unified memory", "Up to 60 GPU + 32 Neural cores", "Dedicated, isolated host", "24h minimum"]),426    ("MAX", "#7C3AED", "High-throughput power", 5.50, False,427     "M4 Max nodes with 36–48&nbsp;GB. Strong GPU and compute for demanding production workloads.",428     ["36–48 GB unified memory", "40 GPU cores · 16 CPU cores", "Latest-gen Apple Silicon", "Dedicated host"]),429    ("STUDIO", "#2563EB", "Dedicated mid-tier", 1.35, False,430     "M2 / M1 Max Studios with ~32&nbsp;GB. Reliable dedicated power at an accessible price.",431     ["30–32 GB unified memory", "24–30 GPU cores", "Always-on dedicated node", "Great price/perf"]),432    ("WORKER", "#0891B2", "CI · hosting · light AI", 0.43, False,433     "Mac mini &amp; MacBook Air (M-series Pro/Base). Perfect for CI/CD, web hosting, agents and light inference.",434     ["8–24 GB unified memory", "Mac mini / MacBook Air", "Web &amp; API hosting, CI runners", "Pay only for what you run"]),435]436437def fmt(v): return f"${v:,.2f}"438def fmt0(v): return f"${v:,.0f}"439440def plan_card(p):441    tier, color, tag, frm, hot, desc, feats = p442    feats_html = "".join(f"<li>{icon('check',16)} {f}</li>" for f in feats)443    return (f'<div class="plan{" hot" if hot else ""}">'444            f'<span class="tg" style="background:{color}">{tier}</span>'445            f'<h3>{tag}</h3>'446            f'<div class="from">from</div>'447            f'<div class="price">{fmt(frm)}<small>/hour</small></div>'448            f'<p>{desc}</p><ul>{feats_html}</ul>'449            f'<a class="cta2" href="/contact">Request access</a></div>')450451def pricing_rows():452    rows = ""453    order = sorted(NODES, key=lambda n: -RATES.get(n["slug"], 0))454    for n in order:455        h = RATES.get(n["slug"], 0)456        aws = h / 0.9457        day = h * 24458        month = h * 730459        spec = f'{n["ncpu"]}C / {n["gpu"]}G / {n["mem"]}GB'460        rows += (f'<tr><td class="node-nm">{esc(n["alias"])}</td>'461                 f'<td><span class="tg" style="background:{TCOLOR.get(n["tier"],"#666")}">{esc(n["tier"])}</span></td>'462                 f'<td class="spec">{esc(spec)}</td>'463                 f'<td class="aws">{fmt(aws)}</td>'464                 f'<td class="ml">{fmt(h)}</td>'465                 f'<td class="num">{fmt(day)}</td>'466                 f'<td class="num">{fmt0(month)}/mo</td></tr>')467    return rows468469def whole_cluster():470    compute_h = sum(RATES.values())471    storage_mo = 35000 * 0.05 + 45000 * 0.02   # 35 TB SSD + 45 TB HDD external472    total_h = compute_h + storage_mo / 730473    day = total_h * 24474    month = total_h * 730475    year = total_h * 8760476    t = totals()477    def cell(label, val, per, hl=False):478        return (f'<div class="{ "hl" if hl else "" }"><div class="l">{label}</div>'479                f'<div class="n">{val}</div><div class="p">{per}</div></div>')480    return f"""<div class="clusterbox">481  <span class="kick">{icon('layers',16)} Whole infrastructure</span>482  <h2>Rent the entire cluster</h2>483  <p class="sub2">Every node and all external storage, exclusively yours — a turn-key private Apple Silicon cloud.</p>484  <div class="cl-prices">485    {cell("Hourly", fmt(total_h), "per hour")}486    {cell("Daily", fmt0(day), "24 hours")}487    {cell("Monthly", fmt0(month), "730 hours", hl=True)}488    {cell("Yearly", fmt0(year), "8,760 hours")}489  </div>490  <div class="cl-incl"><span>{t['n']} nodes</span><span>{t['cpu']} CPU cores</span><span>{t['ram']} GB RAM</span><span>{t['gpu']} GPU cores</span><span>{t['ne']} Neural cores</span><span>35 TB SSD + 45 TB HDD external</span></div>491  <a class="btn p" href="/contact">Reserve the full cluster {icon('arrow',18)}</a>492  <p class="fine">Includes every node (with their internal SSDs) plus the full 80 TB external storage pool — 35 TB SSD and 45 TB HDD. Annual and long-term commitments qualify for a custom discount.</p>493</div>"""494495def page_pricing():496    body = f"""<div class="wrap">497<div class="section" style="padding-bottom:6px;text-align:center">498  <div class="eyebrow"><span class="kick">{icon('gauge',16)} Pricing</span></div>499  <h2 style="margin-top:14px">Dedicated Apple Silicon, <span class="grad">~10% below AWS</span></h2>500  <p class="lead mx" style="margin-top:12px">Transparent per-node pricing, benchmarked against AWS EC2 Mac Dedicated Hosts (24-hour minimum). Rent by the hour, day, month or year.</p>501</div>502503{whole_cluster()}504505<div class="section" style="padding-top:48px"><div class="center"><div class="eyebrow"><span class="kick">{icon('server',16)} By the node</span></div>506<h2 style="margin-top:14px">Or pick a plan</h2></div>507<div class="plans">{"".join(plan_card(p) for p in PLANS)}</div></div>508509<div class="section" style="padding-top:36px">510  <div class="center"><div class="eyebrow"><span class="kick">{icon('layers',16)} Full price list</span></div>511  <h2 style="margin-top:14px">Every node, per hour</h2>512  <p class="lead mx" style="margin-top:12px">Hourly rate, plus 24-hour and 730-hour (monthly) totals. AWS column shows a comparable EC2 Mac Dedicated Host estimate.</p></div>513  <div class="ptable"><div class="pscroll"><table class="pg">514    <thead><tr><th>Node</th><th>Tier</th><th>Specs (CPU/GPU/RAM)</th><th>AWS est.</th><th>MacLustr /h</th><th>/day (24h)</th><th>/month (730h)</th></tr></thead>515    <tbody>{pricing_rows()}</tbody>516  </table></div></div>517  <p class="note">Prices in USD. Specs shown as CPU cores / GPU cores / unified memory. <b>MacLustr /h</b> is our rate; <b>AWS est.</b> is a comparable AWS EC2 Mac Dedicated Host (on-demand, 24h minimum) — MacLustr runs about 10% below. Monthly assumes 730 hours of continuous use; short jobs are billed hourly. Custom and long-term plans available on request.</p>518</div>519520<div class="section" style="padding-top:10px">521  <div class="center"><div class="eyebrow"><span class="kick">{icon('hd',16)} Storage add-ons</span></div>522  <h2 style="margin-top:14px">Need more space?</h2>523  <p class="lead mx" style="margin-top:12px">Attach external storage to any node — fast SSD for hot data, high-capacity HDD for archives and backups. Pay per gigabyte, per month.</p></div>524  <div class="cards" style="grid-template-columns:1fr 1fr">525    <div class="fcard"><div class="ic">{icon('hd')}</div>526      <h3>External SSD</h3>527      <p>Fast SSD storage for active datasets, databases and model weights.</p>528      <div style="font-size:36px;font-weight:840;letter-spacing:-.03em;margin-top:16px">$0.05<small style="font-size:15px;color:var(--mut);font-weight:700"> / GB · month</small></div>529      <ul><li>{icon('check',16)} ≈ $50 per TB · month</li><li>{icon('check',16)} Up to 35 TB available</li><li>{icon('check',16)} High throughput, low latency</li></ul>530    </div>531    <div class="fcard"><div class="ic">{icon('db')}</div>532      <h3>External HDD</h3>533      <p>High-capacity spinning disk for archives, backups and cold storage.</p>534      <div style="font-size:36px;font-weight:840;letter-spacing:-.03em;margin-top:16px">$0.02<small style="font-size:15px;color:var(--mut);font-weight:700"> / GB · month</small></div>535      <ul><li>{icon('check',16)} ≈ $20 per TB · month</li><li>{icon('check',16)} Up to 45 TB available</li><li>{icon('check',16)} Ideal for backups &amp; archives</li></ul>536    </div>537  </div>538  <p class="note">Storage add-ons are billed per GB-month and attach to any rented node. Total external pools available: <b>35 TB SSD</b> and <b>45 TB HDD</b>. Larger or dedicated volumes available on request.</p>539</div>540541<div class="section" style="padding-top:0">{contact_band()}</div>542</div>"""543    return shell("Pricing — MacLustr", "pricing", body,544                 "MacLustr pricing: dedicated Apple Silicon nodes ~10% below comparable AWS EC2 Mac Dedicated Hosts.")545546def contact_band():547    return f"""<div class="band">548  <h2>Need compute, hosting or cloud?</h2>549  <p>If you need any kind of computation, hosting or private cloud on one of our nodes, write to us — describe your workload and we'll find the right node.</p>550  <a class="mail" href="mailto:{CONTACT}?subject=MacLustr%20%E2%80%94%20compute%2Fhosting%2Fcloud%20request">{icon('mail',20)}{CONTACT}</a>551  <div class="chips"><span>Compute jobs</span><span>ML training / inference</span><span>Web & API hosting</span><span>Private cloud</span><span>macOS CI</span></div>552</div>"""553554ROUTES = {"": page_home, "nodes": page_nodes, "services": page_services,555          "pricing": page_pricing, "about": page_about, "contact": page_contact}556557LOGO_PATH = os.path.join(HERE, "logo.svg")558LOGO = open(LOGO_PATH).read() if os.path.exists(LOGO_PATH) else "<svg/>"559560class H(BaseHTTPRequestHandler):561    def log_message(self, *a): pass562    def _send(self, code, body, ctype="text/html; charset=utf-8"):563        b = body.encode() if isinstance(body, str) else body564        self.send_response(code)565        self.send_header("Content-Type", ctype)566        self.send_header("Content-Length", str(len(b)))567        self.send_header("Cache-Control", "no-store")568        self.end_headers()569        try: self.wfile.write(b)570        except (BrokenPipeError, ConnectionResetError): pass571    def do_GET(self):572        path = urlparse(self.path).path.strip("/")573        if path in ("health", "healthz"): return self._send(200, "ok", "text/plain")574        if path == "logo.svg": return self._send(200, LOGO, "image/svg+xml")575        if path in ROUTES: return self._send(200, ROUTES[path]())576        nf = '<div class="wrap section center"><h2>Page not found</h2><p class="lead mx" style="margin-top:12px">Sorry, that page does not exist.</p><div class="row-btn" style="justify-content:center;margin-top:22px"><a class="btn p" href="/">Back home</a></div></div>'577        return self._send(404, shell("Not found — MacLustr", "", nf))578579if __name__ == "__main__":580    srv = ThreadingHTTPServer(("0.0.0.0", PORT), H)581    print(f"MacLustr landing on :{PORT} ({len(NODES)} nodes)")582    srv.serve_forever()583