The cache client is now the KV client (core/cache/kv.py, kv_client),
configured by KV_URL. rate_limit_check allows the request and logs when KV
is unreachable, and the RPC cache treats an outage as a miss. cloud_billing
reads KV directly instead of a missing usage_tracker.redis attribute; the
NFT refresh enqueues through an arq pool.
Co-authored-by: Hanzo Dev <[email protected]>
luxd serves /v1 only — server/http/server.go pins baseURL="/v1" — so every
/ext call in this repo was a silent no-op. The fleet collector's _rpc()
returns None on non-200, so the lux.cloud dashboard rendered nodes_rpc_ok:0,
version:null and every node rpc_reachable:false while the fleet was in fact
running luxd/1.36.2 (mainnet), 1.36.24 (testnet), 1.36.25 (devnet).
Fixing the path alone would not have helped: the collector addressed each
validator by a hardcoded public LoadBalancer IP, and there are now zero
LoadBalancer Services in lux-mainnet/testnet/devnet. Those IPs time out. So
node addressing is no longer configured — it is derived from the cluster on
every collection, and the one way to reach a validator is the lux-k8s API
server's pod proxy, since luxd RPC is cluster-internal.
That collapses the old MODE A / MODE B split into a single path: read the CR
for the declared shape, the pods for phase/readiness/IP, then each pod's
JSON-RPC through the proxy. One credential authenticates both. Deleting the
endpoint ConfigMap and its 15 baked-in IPs removes the failure mode where
infrastructure moves and a dashboard quietly reports zeros.
Probes move to /v1/health/liveness, not /v1/health: a degraded-but-running
validator answers /v1/health with 503, so probing it would restart nodes that
are merely behind. Any fleet from charts/lux could never have gone Ready.
LuxNode.external_ip becomes .ip — with no public per-pod endpoint, the honest
value is the pod IP.
Avalanche endpoints keep /ext; that is their API, not ours.
Measured, with controls: pod-proxy POST /v1/info -> 200 on all 15 pods;
/ext/health -> 404; /zzz/health -> 404; nonexistent pod -> 404. A test asserts
every probe path is under /v1 and fails if /ext returns.
Co-authored-by: Hanzo Dev <[email protected]>
Names the two halves for what they are. The repo already holds both (12407e9 merged
bootnode/bootnode into this history); this makes the frontend directory say so, so the layout
reads the way the owner describes the system: backend and frontend separate, one repo.
git mv throughout — 187 renames, history preserved. Every path reference follows: hanzo.yml
build context and dockerfile, compose.yml, compose.prod.yml, start.sh, LLM.md,
infra/k8s/web-deployments.yaml, infra/cloud/deploy.sh, and the Dockerfile's own COPY paths.
Image and container names (bootnode-web, the -web tag suffix) are deliberately NOT renamed here:
those are a deploy contract the App CR and ingress bind to on port 3001, and breaking them is a
separate, staged change.
Console builds green after the move; the Go tree builds.
Co-authored-by: Hanzo Dev <[email protected]>
The shell and design system half of the console merge. lux.cloud's look and
feel comes across as tokens and shell geometry, so all 67 existing routes
inherit it without one page being edited, and the per-deployment brand is
replaced by a hostname resolve.
DESIGN SYSTEM — lux.cloud's palette becomes the token layer
app/globals.css now states lux.cloud's scheme in the variables Tailwind
already reads: #000 ground, #f5f5f5 ink, 55% muted, 8% hairline / 18% strong
border, #1a1a1a active fill, its status greens/ambers/reds, 10px/16px radii,
clamp() display type, -0.01em tracking. Light is a faithful inversion, kept
because the app ships a theme toggle. Because these are the same variables
`bg-background` / `border-border` / `text-muted-foreground` always resolved
to, every page changed appearance without changing.
The shared primitives then say it in their own geometry: Card is lux's
`.panel` (16px, hairline, no shadow — a shadow is invisible on black and
grime on white), Table is `.vtable` (12px uppercase heads, border on the TOP
of each row so the header needs no rule and the table ends flush), Badge is
`.pill` with the status dot, Button and Input are `.btn` and `.stake-input`.
SHELL
One header for marketing and console, as on lux.cloud — 57px, blurred, one
hairline, brand lockup with the retracting wordmark, actions hard right,
collapsing behind a single hamburger below `lg` into an OPAQUE sheet (a
nested backdrop-filter re-roots the backdrop, the bug lux.cloud documents).
ConsoleLayout adds the one thing the console needs: a 236px sticky rail that
becomes a horizontally-scrolling strip on a phone — 7% of a 390x844 screen
measured, against the 27% a wrapped grid of pills costs.
Deleted: unified-navigation.tsx, a second console-only top bar with its own
logo, an org switcher whose options changed nothing, and a bell wired to no
notification source. The hard-coded `pl-64 pt-16` offsets are now the
--header-h / --sidebar-w tokens, so the header and rail cannot disagree.
Loading, empty and error are three components in one file instead of nine
hand-rolled spinners at four sizes, and the callback page's `bg-green-100` /
`text-red-600` — the only two colours in the app that ignored the tokens and
so stayed light-mode on a black page — are gone.
WHITE-LABEL BY HOSTNAME — one image, five hosts
lib/brand.ts resolves brand, theme, API origin, IAM issuer and OIDC client
from window.location.hostname. Every triple was verified against the live
/v1/iam/oauth/authorize (302 = registered, 400 = not):
id.bootno.de bootnode-platform bootno.de/auth/callback 302
hanzo.id hanzo-web3 web3.hanzo.ai/auth/callback 302
lux.id lux-cloud lux.cloud/auth/callback 302
zoolabs.id zoo-cloud zoo.cloud/auth/callback 302
zoolabs.id zoo-cloud cloud.zoo.network/auth/callback 302
zoolabs.id zoo-web3 cloud.zoo.network/auth/callback 400 ← was shipping
Deleted, because all three were the same defect — a value picked at deploy
time while ONE deployment serves five hosts:
• public/config.json + config.example.json (the shipped catalog had the
400 client above, and named apiUrls whose hosts do not resolve)
• the Dockerfile's NEXT_PUBLIC_BRAND / _API_URL / _WS_URL, which were also
inert — the runtime is the Go static binary, which never reads env
• four near-identical Deployments differing only in those variables, now
one, with the nine ingress rules pointing at the single service
• NEXT_PUBLIC_AUTH_MODE and four NEXT_PUBLIC_* docs URLs
Matching is exact-or-dot-suffix, never substring: `includes("lux.cloud")`
hands lux.cloud's OIDC client to lux.cloud.attacker.example.
Each brand also gets its own mark. public/logo/{brand}-icon.svg existed for
all five and nothing used them — every brand pointed at one shared
/logo.svg. bootnode's is fill="currentColor" and lux's is fill="white",
which as an <img> (an isolated document) render invisible on black and on
white respectively, so those two are painted in the current text colour via
a CSS mask — which is what lux.cloud does with its own pyramid.
FIXED ALONG THE WAY
• AuthProvider's local getApiUrl() shadowed the import at the top of the
file and its last line called itself — infinite recursion on localhost,
127.0.0.1, 192.168.* and 10.*. Every checkAuth/login/register in local
development overflowed the stack. Deleted; lib/api's resolver is the one.
• The console nav could not reach eight of its own pages —
/dashboard/settings, assets/{tokens,nfts}, infrastructure/{lux,services,
observability}, /chains, /agents rendered and were linked from nowhere.
• No row was ever marked current: the image serves clean URLs as directory
indexes, so the pathname is `/dashboard/` and an equality test never
matched. Nested routes also lit two rows at once. Both are one selection
rule now — longest matching href wins.
• Two dead links in the chrome: /products (a directory of pages with no
index — the static server answers it with the home page) and
/docs/tutorials (a plain 404).
TESTS — 26, node:test with native TS stripping, no new dependency
The five-host contract including the substring-matching attack; the nav
reaching every built route; the trailing-slash and most-specific-row rules;
and a whole-source dead-link check that derives routes by walking app/ so it
cannot go stale. That check holds the shell at ZERO dead links and ratchets
the 36 that predate this work in the docs and product pages — a test that
fails on the day it is written gets deleted, one that stops the bleeding
gets kept.
VERIFIED
pnpm build exit 0, 77/77 pages, route set byte-identical to the baseline
taken before any change. In a real browser against the built static export:
body #000 / #f5f5f5, --border rgba(255,255,255,.08), header 57px sticky
blur(24px), sidebar 236px sticky at top:57px, exactly one active row with
#1a1a1a fill and aria-current, the phone strip 7% of the viewport scrolling
inside itself with the body never scrolling sideways, the sheet opaque with
no nested backdrop-filter, and the favicon, mark and "Sign in with Bootnode
ID" all coming from the resolved brand.
Not deployed. No page bodies touched.
The api crashed on import after a half-finished model refactor, and the qdrant
healthcheck used a binary not in the image.
- compose.yml: qdrant ships no wget/curl, so its healthcheck always failed and
blocked the api (depends_on service_healthy). Dropped the broken healthcheck and
gate the api on qdrant service_started (qdrant itself serves fine on :6333).
- db/__init__.py: reconcile to the real models — drop removed Node + Cluster
(now CRDs) and MPCWalletModel; export the actual set (User, OrgCluster,
SmartWallet, Subscription, TeamMember, etc.).
- api/mpc + api/keys: MPCWalletModel -> SmartWallet (the renamed model).
API now boots healthy: GET /health 200 {"status":"healthy","version":"2.0.0"},
/docs serving, 42 node/chain/fleet endpoints live (fleets deploy/scale, presets,
pod health/logs). Full local stack up: postgres, redis, clickhouse, qdrant,
meilisearch, api.
Note (follow-up, not blocking boot): api/nodes/db_nodes.py + db/seed_nodes.py
still import removed Node/Cluster models — dead code to remove or port to the
fleet/CRD path; the web frontend build fails on a stale pnpm lockfile.
Co-authored-by: Hanzo Dev <[email protected]>
- Add Explorer product page with branded block explorer deployment, contract
verification, Etherscan-compatible API, and multi-chain support
- Add MPC product page with deep Lux MPC integration: CGGMP21 (ECDSA),
FROST (EdDSA), LSS resharing, 15+ chain support, threshold signing API
- Update landing page with 6-product grid (Node, Data, Wallets, Explorer,
MPC, Rollups) and 8 API cards including Explorer API and MPC Signing
- Update navbar and command menu with Explorer and MPC products
- Add Lux fleet management dashboard, observability, and services pages
- Add AI chat component and unified navigation with org switcher
- Add Kubernetes operator (Rust) for multi-chain node lifecycle management
- Add Lux Helm charts with genesis configs for devnet/testnet/mainnet
- Add docker-release.yml CI/CD pipeline for automated DOKS deployment
- Add monitoring, network policies, web deployments, and ingress updates
- Refactor API: fleet management, billing tracker, observability endpoints
- Update brand config, API client, and infrastructure manifests
- Add k8s configs for production deployment
- Use shared postgres/datastore in hanzo namespace
- Fix image registry to ghcr.io/hanzoai/bootnode
- Add deploy script with shared infra support
- Simplify compose.yml for local development