The v1.3.1 image build reached the runner (lux-build works) but failed two
ways that the local build now proves fixed:
1. Dockerfile cloned private luxfi/{indexer,graph} over unauthenticated
https -> 'could not read Username'. Those clones were vestigial: explorer
has no replace directive, so under -mod=mod graph v1.2.3 + indexer v1.4.5
resolve from the module cache, not siblings. Drop the clones; add the
node-style BuildKit-secret git auth (ghtok) + GOPRIVATE/GONOSUMDB/GONOPROXY
before go mod download so private modules resolve.
2. GHCR push would be denied (repo-scoped GITHUB_TOKEN). Log in with the
cross-org PAT (GH_TOKEN/UNIVERSE_PAT) and stamp
org.opencontainers.image.source so the package stays linked.
Verified locally: docker buildx build --secret id=ghtok,... compiles the
unified FE+indexer+graph binary green (go mod download 74s, go build 98s,
image exported).
The Go binary is now a data-layer service only. The `/` catch-all that
served an embedded SPA shell is gone; v1.3.0 keeps just the endpoints
the FE depends on:
- /v1/* — indexer + blockscout-compat REST APIs
- /health — health probe
- /envs.js — runtime brand/chain config (window.ENV)
- /icon.svg, /logo.svg — per-host brand SVGs (overlay-friendly)
- /icon-{slug}.svg, /logo-{slug}.svg — extra named brand assets
Every other path returns 404 so the IngressRoute hands it off to the
per-brand Blockscout FE Deployment (ghcr.io/{brand-org}/explore-{env}).
Concrete deltas:
- frontend.go: drop handleSPA, writeIndex, f.index; replace with
handleNamedBrandAsset + brandAssetNames discovery
- frontend_test.go: TestFrontendNoSPA + TestFrontendBrandAssetsServed
replace the index-html overlay tests
- Dockerfile: drop Next.js frontend build stage; runtime image is just
the Go binary + brand SVGs (no embedded HTML)
- static/index.html: removed (60-byte placeholder)
The per-brand FE Deployments + IngressRoute patches that complete this
split live in the cluster (lux-mainnet + lux-testnet namespaces). Source
of the FE images: luxfi/explore via Dockerfile.branded.
Replace the default currency symbol + network ID
(8675312) defaults with Lux mainnet defaults (LUX, 96369). The
explorer binary white-labels per-tenant via runtime env, not via
-specific build defaults.
Two related changes that get the explorer SPA rendering real chain
data without waiting for upstream luxfi/explore to ship a Next.js
static export.
1. Runtime SPA overlay (config.go, frontend.go)
New cfg.StaticDir (also EXPLORER_STATIC_DIR env var). When set,
the frontend prefers the on-disk copy of any static asset over the
embedded FS — so a ConfigMap-mounted SPA shell replaces the
embedded 60-byte placeholder without rebuilding the binary. The
embedded FS remains the source of truth at build time and the
fallback when no overlay is configured.
handleSPA + serveBrandFile both check overlay > embedded. Tests
cover overlay-present, overlay-missing-file fallback, no-overlay,
and env-var-only configuration.
2. Substantive SPA shell (static/index.html)
Replaced the 60-byte stub (<div id="root"></div>) with a vanilla
JS SPA — ~7KB, zero dependencies, no React/Next.js. Reads
window.ENV from /envs.js, fetches /v1/indexer/{slug}/{stats,blocks,
transactions}, renders KPIs + Latest Blocks + Latest Transactions
with per-chain switching and brand color/title overrides. Polls
every 15s. Stores selected chain in URL hash + localStorage.
This is the explorer's default UI until upstream luxfi/explore
ships output: 'export' and the Dockerfile's optional pnpm-build
overlay activates.
Dockerfile: drop the leftover merge-conflict markers around the SPA
build stage, keep the explanatory comment.
Tests: 4 new in frontend_test.go covering overlay precedence + env-var
fallback. All existing tests (registry, realtime) still pass.
Two related fixes for running the explorer against a mainnet-fork
devnet:
1. Honor the per-chain `indexer.start_block` field from chains.yaml
when the indexer DB is fresh. Without this the indexer always
backfills from block 0, which on a mainnet fork is 25M+ blocks of
irrelevant pre-fork history. Now an operator can set:
chains:
- slug: evm
rpc: http://anvil:8545
indexer:
start_block: 25162000
and the indexer skips to that block on first run. Once any rows
exist in evm_blocks, start_block is ignored and indexing resumes
from MAX(number)+1 as before — backward-compatible for
fresh-genesis chains.
2. Default the bundled SPA's NETWORK_ID to 8675312 (the Liquid
devnet chain ID). The Dockerfile previously inherited
luxfi/explore's default of 8675311 — a stale leftover from before
the devnet chainId migration. Adds NETWORK_ID / NETWORK_NAME /
CURRENCY_SYMBOL as build-args so the same Dockerfile produces
correct images for devnet/testnet/mainnet.
Requires luxfi/indexer #8 (StartBlock field on evm.Config) — bumped
via `go mod edit` to the post-merge commit d5e65d4b50c1.
Module renamed to github.com/luxfi/explorer (was -unified). Imports
luxfi/indexer/{daemon,evm,storage,explorer} and luxfi/graph/{engine,
indexer,storage} as libraries; embeds the luxfi/explore SPA via go:embed
all:static.
A new ChainSupervisor wraps the existing ChainRegistry: adding a chain
(via config / mDNS / admin API) spawns a per-chain indexer goroutine plus
one graph engine + indexer per enabled subgraph; removing cancels them
and frees the routes from a sync.Map dispatcher mounted on /v1/indexer/,
/v1/explorer/ (legacy alias), and /v1/graph/.
Config schema is fully per-chain: indexer poll interval, list of graph
subgraphs (each can pick a built-in schema like amm, securities, fhe…),
brand (name/coin/accent + on-disk icon/logo paths), token features, and
top-level networks list. /envs.js exposes the chain list and active
brand to the SPA at runtime, so a deploy can rebrand by remounting
chains.yaml + assets — no image rebuild.
Dockerfile clones luxfi/explore + luxfi/indexer + luxfi/graph as
siblings inside the build, populates ./static from the SPA build, and
produces one binary. CI checks out the three repos as siblings so the
local replace in go.mod works without tagged releases.
4 repos, 4 images:
indexer → chain data (blocks, txs, tokens, contracts)
graph → subgraph events (swaps, pools, DeFi, GraphQL)
explore → Next.js frontend
explorer → all three combined (this)
Single image contains 3 binaries: indexer, graph, explorer.
Each can run standalone or together.
Three repos, three images:
luxfi/indexer → ghcr.io/luxfi/indexer (Go backend, API + indexing)
luxfi/explore → ghcr.io/luxfi/explore (Next.js frontend)
luxfi/explorer → ghcr.io/luxfi/explorer (this: combined single image)
Dockerfile pulls both repos at build time, embeds the Next.js static
export into the Go binary via go:embed, produces a single container.