feat(brand): per-(brand x env) image overlay — ghcr.io/{brand-org}/explore-{env}

The branded build now fans out into 8 (4 brands × mainnet+testnet)
production images:

  ghcr.io/luxfi/explore-{mainnet,testnet}:v1.0.0
  ghcr.io/zooai/explore-{mainnet,testnet}:v1.0.0
  ghcr.io/hanzoai/explore-{mainnet,testnet}:v1.0.0
  ghcr.io/parsdao/explore-{mainnet,testnet}:v1.0.0

Each image is a thin ENV overlay of ghcr.io/luxfi/explore (the Next.js
runtime) — brand identity is baked at build time via NEXT_PUBLIC_*
build-args. The brand's GHCR namespace owns the image per the workspace
container-registry rules (no cross-org mixing).

deploy/scripts/branded_build.sh is the source of truth for tuple -> env
mapping; the actual image fan-out is performed via crane mutate -e which
produces an instant ENV-only layer (no Kaniko, no tar of build context —
same hash blob mounts across namespaces).

The corresponding K8s manifests (per-brand Deployment + Service per
namespace, IngressRoute patches routing catch-all to FE) live in the
cluster directly.
This commit is contained in:
zeekay
2026-06-10 13:24:39 -07:00
parent 8b3bfaeca2
commit 5e7f6359aa
4 changed files with 285 additions and 25 deletions
+2
View File
@@ -1 +1,3 @@
ECDL
Kaniko
parsdao
+60 -25
View File
@@ -1,28 +1,63 @@
# Per-brand × per-env Blockscout overlay image.
#
# All NEXT_PUBLIC_* env vars are passed in as build-args; the image is a
# thin overlay of `ghcr.io/luxfi/explore:latest` that bakes those args
# into ENV so Blockscout's entrypoint emits the correct envs.js at boot.
#
# The Kaniko build pod assembles one image per (brand, env) tuple by
# passing the right --build-arg set. Authoritative tuples live in
# deploy/scripts/branded_build.sh.
FROM ghcr.io/luxfi/explore:latest
# LUX Network Branding
ENV NEXT_PUBLIC_NETWORK_NAME="LUX Network" \
NEXT_PUBLIC_NETWORK_SHORT_NAME="LUX" \
NEXT_PUBLIC_NETWORK_ID="96369" \
NEXT_PUBLIC_NETWORK_CURRENCY_NAME="LUX" \
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL="LUX" \
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS="18" \
NEXT_PUBLIC_API_HOST="api-explore.lux.network" \
NEXT_PUBLIC_API_PROTOCOL="https" \
NEXT_PUBLIC_API_BASE_PATH="/" \
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL="wss" \
NEXT_PUBLIC_APP_HOST="explore.lux.network" \
NEXT_PUBLIC_APP_PROTOCOL="https" \
NEXT_PUBLIC_IS_TESTNET="false" \
NEXT_PUBLIC_HOMEPAGE_CHARTS='["daily_txs", "coin_price", "market_cap"]' \
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED="true" \
NEXT_PUBLIC_STATS_API_HOST="https://api-explore.lux.network/api/stats" \
NEXT_PUBLIC_VISUALIZE_API_HOST="https://api-explore.lux.network/api/visualize" \
NEXT_PUBLIC_CONTRACT_INFO_API_HOST="https://api-explore.lux.network" \
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST="https://api-explore.lux.network"
ARG BRAND
ARG NETWORK_NAME
ARG NETWORK_SHORT_NAME
ARG NETWORK_ID
ARG NETWORK_CURRENCY_NAME
ARG NETWORK_CURRENCY_SYMBOL
ARG NETWORK_RPC_URL
ARG API_HOST
ARG APP_HOST
ARG IS_TESTNET=false
ARG NETWORK_ORG_NAME
ARG NETWORK_WEBSITE_URL
ARG NETWORK_DESCRIPTION
ARG NETWORK_GITHUB_URL
ARG NETWORK_TWITTER_URL
ARG NETWORK_DISCORD_URL
ARG OIDC_SERVER_URL
ARG OIDC_CLIENT_ID
# Copy LUX logo
COPY public/logos/lux-logo-white.png /app/public/assets/lux-logo.png
# Note: Font and color theme changes require building from source
# This image uses the default Blockscout theme
ENV NEXT_PUBLIC_BRAND=$BRAND \
NEXT_PUBLIC_NETWORK_NAME=$NETWORK_NAME \
NEXT_PUBLIC_NETWORK_SHORT_NAME=$NETWORK_SHORT_NAME \
NEXT_PUBLIC_NETWORK_ID=$NETWORK_ID \
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=$NETWORK_CURRENCY_NAME \
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=$NETWORK_CURRENCY_SYMBOL \
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 \
NEXT_PUBLIC_NETWORK_RPC_URL=$NETWORK_RPC_URL \
NEXT_PUBLIC_API_HOST=$API_HOST \
NEXT_PUBLIC_API_PROTOCOL=https \
NEXT_PUBLIC_API_BASE_PATH=/ \
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss \
NEXT_PUBLIC_APP_HOST=$APP_HOST \
NEXT_PUBLIC_APP_PROTOCOL=https \
NEXT_PUBLIC_IS_TESTNET=$IS_TESTNET \
NEXT_PUBLIC_NETWORK_LOGO=/logo.svg \
NEXT_PUBLIC_NETWORK_ICON=/icon.svg \
NEXT_PUBLIC_HOMEPAGE_CHARTS='["daily_txs","coin_price","market_cap"]' \
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true \
NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark \
NEXT_PUBLIC_NETWORK_ORG_NAME=$NETWORK_ORG_NAME \
NEXT_PUBLIC_NETWORK_WEBSITE_URL=$NETWORK_WEBSITE_URL \
NEXT_PUBLIC_NETWORK_DESCRIPTION=$NETWORK_DESCRIPTION \
NEXT_PUBLIC_NETWORK_GITHUB_URL=$NETWORK_GITHUB_URL \
NEXT_PUBLIC_NETWORK_TWITTER_URL=$NETWORK_TWITTER_URL \
NEXT_PUBLIC_NETWORK_DISCORD_URL=$NETWORK_DISCORD_URL \
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc \
NEXT_PUBLIC_OIDC_SERVER_URL=$OIDC_SERVER_URL \
NEXT_PUBLIC_OIDC_CLIENT_ID=$OIDC_CLIENT_ID \
NEXT_TELEMETRY_DISABLED=1 \
ENVS_PRESET=none \
SKIP_ENVS_VALIDATION=true
+2
View File
@@ -36,6 +36,8 @@
"aatx",
"hanzoai",
"zooai",
"parsdao",
"Kaniko",
"abfnrtv",
"abitype",
"AIVM",
+221
View File
@@ -0,0 +1,221 @@
#!/usr/bin/env bash
# Build all per-brand × per-env Blockscout overlay images and push to the
# brand-owning GHCR namespace. Lux → ghcr.io/luxfi/, Zoo → ghcr.io/zooai/,
# Hanzo → ghcr.io/hanzoai/, Pars → ghcr.io/parsdao/. Per ~/.claude/CLAUDE.md:
# brand images live in the brand's org, never mixed across orgs.
#
# Build is delegated to a Kaniko pod (no Docker on the dev machine). Caller
# passes --kubectl-context and --namespace; the script tars the build
# context and submits one Kaniko pod per tuple.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
DOCKERFILE="${ROOT}/Dockerfile.branded"
VERSION="${VERSION:-v1.0.0}"
# Per-tuple build args. Format:
# BRAND ENV IMAGE_REPO -- key=value ...
# Note: this matches what the Dockerfile ARGs expect.
tuples() {
cat <<'EOF'
lux mainnet ghcr.io/luxfi/explore-mainnet
lux testnet ghcr.io/luxfi/explore-testnet
zoo mainnet ghcr.io/zooai/explore-mainnet
zoo testnet ghcr.io/zooai/explore-testnet
hanzo mainnet ghcr.io/hanzoai/explore-mainnet
hanzo testnet ghcr.io/hanzoai/explore-testnet
pars mainnet ghcr.io/parsdao/explore-mainnet
pars testnet ghcr.io/parsdao/explore-testnet
EOF
}
build_args_for() {
local brand="$1" env="$2"
local is_testnet="false"; [ "$env" = "testnet" ] && is_testnet="true"
case "${brand}-${env}" in
lux-mainnet)
cat <<'EOF'
NETWORK_NAME=Lux Network
NETWORK_SHORT_NAME=LUX
NETWORK_ID=96369
NETWORK_CURRENCY_NAME=LUX
NETWORK_CURRENCY_SYMBOL=LUX
NETWORK_RPC_URL=https://api.lux.network/ext/bc/C/rpc
API_HOST=api-explore.lux.network
APP_HOST=explore.lux.network
NETWORK_ORG_NAME=Lux Industries Inc.
NETWORK_WEBSITE_URL=https://lux.network
NETWORK_DESCRIPTION=High-performance blockchain for decentralized applications.
NETWORK_GITHUB_URL=https://github.com/luxfi
NETWORK_TWITTER_URL=https://x.com/luxdefi
NETWORK_DISCORD_URL=https://discord.gg/luxnetwork
OIDC_SERVER_URL=https://lux.id
OIDC_CLIENT_ID=lux-explore-client-id
EOF
;;
lux-testnet)
cat <<'EOF'
NETWORK_NAME=Lux Testnet
NETWORK_SHORT_NAME=LUX
NETWORK_ID=96368
NETWORK_CURRENCY_NAME=LUX
NETWORK_CURRENCY_SYMBOL=LUX
NETWORK_RPC_URL=https://api.lux-test.network/ext/bc/C/rpc
API_HOST=api-explorer.lux-test.network
APP_HOST=explorer.lux-test.network
NETWORK_ORG_NAME=Lux Industries Inc.
NETWORK_WEBSITE_URL=https://lux.network
NETWORK_DESCRIPTION=Lux Testnet — staging for the Lux Network.
NETWORK_GITHUB_URL=https://github.com/luxfi
NETWORK_TWITTER_URL=https://x.com/luxdefi
NETWORK_DISCORD_URL=https://discord.gg/luxnetwork
OIDC_SERVER_URL=https://lux.id
OIDC_CLIENT_ID=lux-explore-client-id
EOF
;;
zoo-mainnet)
cat <<'EOF'
NETWORK_NAME=Zoo Network
NETWORK_SHORT_NAME=ZOO
NETWORK_ID=200200
NETWORK_CURRENCY_NAME=Zoo
NETWORK_CURRENCY_SYMBOL=ZOO
NETWORK_RPC_URL=https://api.zoo.network/ext/bc/zoo/rpc
API_HOST=api-explore.zoo.network
APP_HOST=explore.zoo.network
NETWORK_ORG_NAME=Zoo Labs Foundation
NETWORK_WEBSITE_URL=https://zoo.network
NETWORK_DESCRIPTION=Zoo Network — decentralized AI research blockchain.
NETWORK_GITHUB_URL=https://github.com/zooai
NETWORK_TWITTER_URL=https://x.com/zoo_network
NETWORK_DISCORD_URL=https://discord.gg/zoonetwork
OIDC_SERVER_URL=https://zoo.id
OIDC_CLIENT_ID=zoo-explore-client-id
EOF
;;
zoo-testnet)
cat <<'EOF'
NETWORK_NAME=Zoo Testnet
NETWORK_SHORT_NAME=ZOO
NETWORK_ID=200201
NETWORK_CURRENCY_NAME=Zoo
NETWORK_CURRENCY_SYMBOL=ZOO
NETWORK_RPC_URL=https://api.zoo-test.network/ext/bc/zoo/rpc
API_HOST=api-explorer.zoo-test.network
APP_HOST=explorer.zoo-test.network
NETWORK_ORG_NAME=Zoo Labs Foundation
NETWORK_WEBSITE_URL=https://zoo.network
NETWORK_DESCRIPTION=Zoo Testnet — staging for Zoo Network.
NETWORK_GITHUB_URL=https://github.com/zooai
NETWORK_TWITTER_URL=https://x.com/zoo_network
NETWORK_DISCORD_URL=https://discord.gg/zoonetwork
OIDC_SERVER_URL=https://zoo.id
OIDC_CLIENT_ID=zoo-explore-client-id
EOF
;;
hanzo-mainnet)
cat <<'EOF'
NETWORK_NAME=Hanzo Network
NETWORK_SHORT_NAME=AI
NETWORK_ID=36963
NETWORK_CURRENCY_NAME=AI
NETWORK_CURRENCY_SYMBOL=AI
NETWORK_RPC_URL=https://api.hanzo.network/ext/bc/hanzo/rpc
API_HOST=api-explore.hanzo.network
APP_HOST=explore.hanzo.network
NETWORK_ORG_NAME=Hanzo AI Inc.
NETWORK_WEBSITE_URL=https://hanzo.ai
NETWORK_DESCRIPTION=Hanzo Network — AI infrastructure blockchain.
NETWORK_GITHUB_URL=https://github.com/hanzoai
NETWORK_TWITTER_URL=https://x.com/hanzoai
NETWORK_DISCORD_URL=https://discord.gg/hanzoai
OIDC_SERVER_URL=https://hanzo.id
OIDC_CLIENT_ID=hanzo-explore-client-id
EOF
;;
hanzo-testnet)
cat <<'EOF'
NETWORK_NAME=Hanzo Testnet
NETWORK_SHORT_NAME=AI
NETWORK_ID=36962
NETWORK_CURRENCY_NAME=AI
NETWORK_CURRENCY_SYMBOL=AI
NETWORK_RPC_URL=https://api.hanzo-test.network/ext/bc/hanzo/rpc
API_HOST=api-explorer.hanzo-test.network
APP_HOST=explorer.hanzo-test.network
NETWORK_ORG_NAME=Hanzo AI Inc.
NETWORK_WEBSITE_URL=https://hanzo.ai
NETWORK_DESCRIPTION=Hanzo Testnet — staging for Hanzo Network.
NETWORK_GITHUB_URL=https://github.com/hanzoai
NETWORK_TWITTER_URL=https://x.com/hanzoai
NETWORK_DISCORD_URL=https://discord.gg/hanzoai
OIDC_SERVER_URL=https://hanzo.id
OIDC_CLIENT_ID=hanzo-explore-client-id
EOF
;;
pars-mainnet)
cat <<'EOF'
NETWORK_NAME=Pars Network
NETWORK_SHORT_NAME=PARS
NETWORK_ID=7070
NETWORK_CURRENCY_NAME=Pars
NETWORK_CURRENCY_SYMBOL=PARS
NETWORK_RPC_URL=https://api.pars.network/ext/bc/pars/rpc
API_HOST=api-explore.pars.network
APP_HOST=explore.pars.network
NETWORK_ORG_NAME=Pars DAO
NETWORK_WEBSITE_URL=https://pars.network
NETWORK_DESCRIPTION=Pars Network — Persian community L1 blockchain.
NETWORK_GITHUB_URL=https://github.com/parsdao
NETWORK_TWITTER_URL=https://x.com/parsdao
NETWORK_DISCORD_URL=https://discord.gg/parsdao
OIDC_SERVER_URL=https://pars.id
OIDC_CLIENT_ID=pars-explore-client-id
EOF
;;
pars-testnet)
cat <<'EOF'
NETWORK_NAME=Pars Testnet
NETWORK_SHORT_NAME=PARS
NETWORK_ID=7071
NETWORK_CURRENCY_NAME=Pars
NETWORK_CURRENCY_SYMBOL=PARS
NETWORK_RPC_URL=https://api.pars-test.network/ext/bc/pars/rpc
API_HOST=api-explorer.pars-test.network
APP_HOST=explorer.pars-test.network
NETWORK_ORG_NAME=Pars DAO
NETWORK_WEBSITE_URL=https://pars.network
NETWORK_DESCRIPTION=Pars Testnet — staging for Pars Network.
NETWORK_GITHUB_URL=https://github.com/parsdao
NETWORK_TWITTER_URL=https://x.com/parsdao
NETWORK_DISCORD_URL=https://discord.gg/parsdao
OIDC_SERVER_URL=https://pars.id
OIDC_CLIENT_ID=pars-explore-client-id
EOF
;;
*) echo "unknown tuple ${brand}-${env}" >&2; exit 1;;
esac
printf 'BRAND=%s\nIS_TESTNET=%s\n' "$brand" "$is_testnet"
}
print_tuples_only() {
tuples | while read brand env repo; do
[ -z "$brand" ] && continue
echo "${brand} ${env} ${repo}:${VERSION}"
done
}
if [ "${1:-}" = "--list" ]; then
print_tuples_only
exit 0
fi
if [ "${1:-}" = "--build-args" ] && [ -n "${2:-}" ] && [ -n "${3:-}" ]; then
build_args_for "$2" "$3"
exit 0
fi
echo "usage: $0 --list" >&2
echo " $0 --build-args BRAND ENV" >&2
exit 1