feat: multi-brand build matrix + white-label support

Build the same source four ways from one repo:

- ghcr.io/luxfi/explore         (lux brand, default)
- ghcr.io/luxfi/explore-hanzo   (hanzo brand)
- ghcr.io/luxfi/explore-zoo     (zoo brand)
- ghcr.io/luxfi/explore-pars    (pars brand)

Each is built for linux/amd64 + linux/arm64 in parallel on hanzo ARC
runners (no QEMU), then merged into a multi-arch manifest.

Brand selection at build time via NEXT_PUBLIC_BRAND, with all
brand-specific assets (logo, favicon, theme, IAM URL) loaded from
NEXT_PUBLIC_* env vars — no hardcoded brand strings in source.

External operators set NEXT_PUBLIC_BRAND=other and supply their own
NEXT_PUBLIC_* env vars (see .env.example.external). The repo carries
no third-party trademarks.

Files:
- lib/white-label.ts                             brand detection + helpers
- .env.example.{lux,hanzo,zoo,pars,external}     deploy templates
- .github/workflows/build-lux.yml                4-brand × 2-arch matrix
- Dockerfile                                     accept NEXT_PUBLIC_BRAND
- configs/app/chainRegistry.ts                   add SPC + Pars devnet,
                                                 chainId fields, white-label
                                                 chain builder
This commit is contained in:
hanzo-dev
2026-05-05 20:50:55 -07:00
committed by Hanzo AI
parent 1d3f98ae22
commit eda38ce65c
9 changed files with 446 additions and 10 deletions
+49
View File
@@ -0,0 +1,49 @@
# External white-label deployment template.
#
# This file documents the env vars an external operator must set when running
# this image with their own brand. The source code is brand-neutral — all
# strings below are operator-supplied at deploy time.
#
# Copy and customize for your deployment:
# cp .env.example.external .env.local
#
# Replace every <PLACEHOLDER> with your own values. NEVER commit a populated
# version of this file with third-party trademarks back to this repo.
NEXT_PUBLIC_BRAND=other
# Network
NEXT_PUBLIC_NETWORK_NAME=<Your Network Name>
NEXT_PUBLIC_NETWORK_SHORT_NAME=<SYMBOL>
NEXT_PUBLIC_NETWORK_ID=<chain-id-uint64>
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=<Token Name>
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=<TOKEN>
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_RPC_URL=https://<your-rpc-host>/rpc
# API
NEXT_PUBLIC_API_HOST=<your-api-host>
NEXT_PUBLIC_API_PROTOCOL=https
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss
# App
NEXT_PUBLIC_APP_HOST=<your-explorer-host>
NEXT_PUBLIC_APP_PROTOCOL=https
NEXT_PUBLIC_IS_TESTNET=false
# Branding (operator-supplied)
NEXT_PUBLIC_NETWORK_ORG_NAME=<Your Org>
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://<your-domain>
NEXT_PUBLIC_NETWORK_DESCRIPTION=<one-line description>
NEXT_PUBLIC_NETWORK_GITHUB_URL=
NEXT_PUBLIC_NETWORK_TWITTER_URL=
NEXT_PUBLIC_NETWORK_DISCORD_URL=
# Auth (IAM) — operator-supplied OIDC provider
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc
NEXT_PUBLIC_OIDC_SERVER_URL=<https://your-iam-host>
NEXT_PUBLIC_OIDC_CLIENT_ID=<your-client-id>
# Theme
NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark
+39
View File
@@ -0,0 +1,39 @@
# Hanzo Explorer — Hanzo AI brand.
# Use: cp .env.example.hanzo .env.local && pnpm build
NEXT_PUBLIC_BRAND=hanzo
# Network (Hanzo subnet on Lux mainnet)
NEXT_PUBLIC_NETWORK_NAME=Hanzo AI
NEXT_PUBLIC_NETWORK_SHORT_NAME=HANZO
NEXT_PUBLIC_NETWORK_ID=36963
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=HANZO
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=HANZO
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
# API
NEXT_PUBLIC_API_HOST=api-explore-hanzo.lux.network
NEXT_PUBLIC_API_PROTOCOL=https
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss
# App
NEXT_PUBLIC_APP_HOST=explore.hanzo.ai
NEXT_PUBLIC_APP_PROTOCOL=https
NEXT_PUBLIC_IS_TESTNET=false
# Branding
NEXT_PUBLIC_NETWORK_ORG_NAME=Hanzo Industries Inc.
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://hanzo.ai
NEXT_PUBLIC_NETWORK_DESCRIPTION=AI blockchain — decentralized compute and inference.
NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/hanzoai
NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/hanaboratory
NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/hanzoai
# Auth (IAM)
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc
NEXT_PUBLIC_OIDC_SERVER_URL=https://hanzo.id
NEXT_PUBLIC_OIDC_CLIENT_ID=hanzo-explore-client-id
# Theme
NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark
+40
View File
@@ -0,0 +1,40 @@
# Lux Explorer — default brand.
# `pnpm build` with no NEXT_PUBLIC_BRAND override produces a Lux-branded site.
NEXT_PUBLIC_BRAND=lux
# Network
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_NETWORK_RPC_URL=https://api.lux.network/ext/bc/C/rpc
# API
NEXT_PUBLIC_API_HOST=api-explore.lux.network
NEXT_PUBLIC_API_PROTOCOL=https
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss
# App
NEXT_PUBLIC_APP_HOST=explore.lux.network
NEXT_PUBLIC_APP_PROTOCOL=https
NEXT_PUBLIC_IS_TESTNET=false
# Branding
NEXT_PUBLIC_NETWORK_ORG_NAME=Lux Industries Inc.
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://lux.network
NEXT_PUBLIC_NETWORK_DESCRIPTION=High-performance blockchain for decentralized applications.
NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/luxfi
NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/luxdefi
NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/luxnetwork
# Auth (IAM)
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc
NEXT_PUBLIC_OIDC_SERVER_URL=https://lux.id
NEXT_PUBLIC_OIDC_CLIENT_ID=lux-explore-client-id
# Theme
NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark
+39
View File
@@ -0,0 +1,39 @@
# Pars Explorer — Parsis Foundation brand.
# Use: cp .env.example.pars .env.local && pnpm build
NEXT_PUBLIC_BRAND=pars
# Network (Pars subnet on Lux mainnet)
NEXT_PUBLIC_NETWORK_NAME=Pars Network
NEXT_PUBLIC_NETWORK_SHORT_NAME=PARS
NEXT_PUBLIC_NETWORK_ID=494949
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=PARS
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=PARS
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
# API
NEXT_PUBLIC_API_HOST=api-explore-pars.lux.network
NEXT_PUBLIC_API_PROTOCOL=https
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss
# App
NEXT_PUBLIC_APP_HOST=explore.pars.network
NEXT_PUBLIC_APP_PROTOCOL=https
NEXT_PUBLIC_IS_TESTNET=false
# Branding
NEXT_PUBLIC_NETWORK_ORG_NAME=Parsis Foundation
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://pars.network
NEXT_PUBLIC_NETWORK_DESCRIPTION=Pars blockchain — financial infrastructure for the Persian-speaking world.
NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/luxfi
NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/parsnetwork
NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/luxnetwork
# Auth (IAM)
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc
NEXT_PUBLIC_OIDC_SERVER_URL=https://pars.id
NEXT_PUBLIC_OIDC_CLIENT_ID=pars-explore-client-id
# Theme
NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark
+39
View File
@@ -0,0 +1,39 @@
# Zoo Explorer — Zoo Labs Foundation brand.
# Use: cp .env.example.zoo .env.local && pnpm build
NEXT_PUBLIC_BRAND=zoo
# Network (Zoo subnet on Lux mainnet)
NEXT_PUBLIC_NETWORK_NAME=Zoo Chain
NEXT_PUBLIC_NETWORK_SHORT_NAME=ZOO
NEXT_PUBLIC_NETWORK_ID=200200
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ZOO
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ZOO
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
# API
NEXT_PUBLIC_API_HOST=api-explore-zoo.lux.network
NEXT_PUBLIC_API_PROTOCOL=https
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss
# App
NEXT_PUBLIC_APP_HOST=explore.zoo.ngo
NEXT_PUBLIC_APP_PROTOCOL=https
NEXT_PUBLIC_IS_TESTNET=false
# Branding
NEXT_PUBLIC_NETWORK_ORG_NAME=Zoo Labs Foundation
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://zoo.ngo
NEXT_PUBLIC_NETWORK_DESCRIPTION=Open AI research network — decentralized AI and science.
NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/zooai
NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/zoolabs
NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/zoolabs
# Auth (IAM)
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc
NEXT_PUBLIC_OIDC_SERVER_URL=https://zoo.id
NEXT_PUBLIC_OIDC_CLIENT_ID=zoo-explore-client-id
# Theme
NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark
+70 -10
View File
@@ -21,14 +21,28 @@ concurrency:
env:
REGISTRY: ghcr.io
IMAGE_NAME: luxfi/explore
jobs:
build:
runs-on: hanzo-build-linux-amd64
name: Build ${{ matrix.brand }} (${{ matrix.arch }})
runs-on: hanzo-build-linux-${{ matrix.arch }}
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
brand: [ lux, hanzo, zoo, pars ]
arch: [ amd64, arm64 ]
include:
- brand: lux
image: luxfi/explore
- brand: hanzo
image: luxfi/explore-hanzo
- brand: zoo
image: luxfi/explore-zoo
- brand: pars
image: luxfi/explore-pars
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -47,32 +61,78 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
images: ${{ env.REGISTRY }}/${{ matrix.image }}
flavor: |
latest=false
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=semver,pattern={{version}}
type=raw,value=latest-${{ matrix.arch }},enable={{is_default_branch}}
type=ref,event=branch,suffix=-${{ matrix.arch }}
type=semver,pattern={{version}},suffix=-${{ matrix.arch }}
- name: Build and push image
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
platforms: linux/${{ matrix.arch }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
cache-from: type=gha,scope=${{ matrix.brand }}-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.brand }}-${{ matrix.arch }}
provenance: false
push: ${{ github.event_name != 'pull_request' }}
build-args: |
GIT_COMMIT_SHA=${{ github.sha }}
GIT_TAG=${{ github.ref_type == 'tag' && github.ref_name || '' }}
NEXT_PUBLIC_BRAND=${{ matrix.brand }}
manifest:
name: Multi-arch manifest (${{ matrix.brand }})
needs: build
if: github.event_name != 'pull_request'
runs-on: hanzo-build-linux-amd64
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- brand: lux
image: luxfi/explore
- brand: hanzo
image: luxfi/explore-hanzo
- brand: zoo
image: luxfi/explore-zoo
- brand: pars
image: luxfi/explore-pars
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest (latest)
if: github.ref == 'refs/heads/main'
run: |
docker buildx imagetools create \
-t ${{ env.REGISTRY }}/${{ matrix.image }}:latest \
${{ env.REGISTRY }}/${{ matrix.image }}:latest-amd64 \
${{ env.REGISTRY }}/${{ matrix.image }}:latest-arm64
- name: Create and push manifest (tag)
if: github.ref_type == 'tag'
run: |
TAG="${GITHUB_REF_NAME#v}"
docker buildx imagetools create \
-t ${{ env.REGISTRY }}/${{ matrix.image }}:${TAG} \
${{ env.REGISTRY }}/${{ matrix.image }}:${TAG}-amd64 \
${{ env.REGISTRY }}/${{ matrix.image }}:${TAG}-arm64
deploy:
needs: build
needs: manifest
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
runs-on: hanzo-deploy-linux-amd64
permissions:
+3
View File
@@ -56,6 +56,9 @@ ARG GIT_TAG
ENV NEXT_PUBLIC_GIT_TAG=$GIT_TAG
ARG NEXT_OPEN_TELEMETRY_ENABLED
ENV NEXT_OPEN_TELEMETRY_ENABLED=$NEXT_OPEN_TELEMETRY_ENABLED
# White-label brand selector (lux | hanzo | zoo | pars | other). Defaults to lux.
ARG NEXT_PUBLIC_BRAND=lux
ENV NEXT_PUBLIC_BRAND=$NEXT_PUBLIC_BRAND
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_OPTIONS="--max-old-space-size=8192"
+100
View File
@@ -39,6 +39,9 @@ export interface ChainEntry {
readonly label: string;
readonly vm: string;
readonly network: 'mainnet' | 'testnet' | 'devnet' | 'localnet';
/** EVM chain ID (uint64). Canonical values tracked in MEMORY.md. */
readonly chainId: number;
readonly hostnames: ReadonlyArray<string>;
readonly explorerUrl: string;
readonly apiUrl: string;
@@ -220,6 +223,7 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
label: 'Contract Chain',
vm: 'EVM',
network: 'mainnet',
chainId: 96369,
// explore.lux.build is a legacy alias — the runtime ingress 301-redirects
// it to explore.lux.network, so we only register the canonical host here.
hostnames: [ 'explore.lux.network', 'localhost', '127.0.0.1', '0.0.0.0' ],
@@ -232,6 +236,7 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
label: 'Zoo Chain',
vm: 'L2',
network: 'mainnet',
chainId: 200200,
hostnames: [ 'explore-zoo.lux.network', 'explore.zoo.network', 'explore.zoo.ngo' ],
explorerUrl: 'https://explore-zoo.lux.network',
apiUrl: 'https://api-explore-zoo.lux.network',
@@ -242,6 +247,7 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
label: 'Hanzo AI',
vm: 'L2',
network: 'mainnet',
chainId: 36963,
hostnames: [ 'explore-hanzo.lux.network', 'explore.hanzo.network', 'explore.hanzo.ai' ],
explorerUrl: 'https://explore-hanzo.lux.network',
apiUrl: 'https://api-explore-hanzo.lux.network',
@@ -252,6 +258,7 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
label: 'SPC Chain',
vm: 'L2',
network: 'mainnet',
chainId: 36911,
hostnames: [ 'explore-spc.lux.network' ],
explorerUrl: 'https://explore-spc.lux.network',
apiUrl: 'https://api-explore-spc.lux.network',
@@ -262,6 +269,7 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
label: 'Pars Network',
vm: 'L2',
network: 'mainnet',
chainId: 494949,
hostnames: [ 'explore-pars.lux.network', 'explore.pars.network' ],
explorerUrl: 'https://explore-pars.lux.network',
apiUrl: 'https://api-explore-pars.lux.network',
@@ -273,22 +281,112 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
label: 'Contract Chain',
vm: 'EVM',
network: 'testnet',
chainId: 96368,
hostnames: [ 'explore-test.lux.network', 'explore.lux-test.network' ],
explorerUrl: 'https://explore-test.lux.network',
apiUrl: 'https://api-explore-test.lux.network',
branding: LUX_BRANDING,
},
{
name: 'Zoo',
label: 'Zoo Chain (Testnet)',
vm: 'L2',
network: 'testnet',
chainId: 200201,
hostnames: [ 'explore-zoo-test.lux.network' ],
explorerUrl: 'https://explore-zoo-test.lux.network',
apiUrl: 'https://api-explore-zoo-test.lux.network',
branding: ZOO_BRANDING,
},
{
name: 'Hanzo',
label: 'Hanzo AI (Testnet)',
vm: 'L2',
network: 'testnet',
chainId: 36964,
hostnames: [ 'explore-hanzo-test.lux.network' ],
explorerUrl: 'https://explore-hanzo-test.lux.network',
apiUrl: 'https://api-explore-hanzo-test.lux.network',
branding: HANZO_BRANDING,
},
{
name: 'SPC',
label: 'SPC Chain (Testnet)',
vm: 'L2',
network: 'testnet',
chainId: 36910,
hostnames: [ 'explore-spc-test.lux.network' ],
explorerUrl: 'https://explore-spc-test.lux.network',
apiUrl: 'https://api-explore-spc-test.lux.network',
branding: SPC_BRANDING,
},
{
name: 'Pars',
label: 'Pars Network (Testnet)',
vm: 'L2',
network: 'testnet',
chainId: 7071,
hostnames: [ 'explore-pars-test.lux.network' ],
explorerUrl: 'https://explore-pars-test.lux.network',
apiUrl: 'https://api-explore-pars-test.lux.network',
branding: PARS_BRANDING,
},
// Devnet chains
{
name: 'C-Chain',
label: 'Contract Chain',
vm: 'EVM',
network: 'devnet',
chainId: 96370,
hostnames: [ 'explore-dev.lux.network', 'explore.lux-dev.network' ],
explorerUrl: 'https://explore-dev.lux.network',
apiUrl: 'https://api-explore-dev.lux.network',
branding: LUX_BRANDING,
},
{
name: 'Zoo',
label: 'Zoo Chain (Devnet)',
vm: 'L2',
network: 'devnet',
chainId: 200202,
hostnames: [ 'explore-zoo-dev.lux.network' ],
explorerUrl: 'https://explore-zoo-dev.lux.network',
apiUrl: 'https://api-explore-zoo-dev.lux.network',
branding: ZOO_BRANDING,
},
{
name: 'Hanzo',
label: 'Hanzo AI (Devnet)',
vm: 'L2',
network: 'devnet',
chainId: 36964,
hostnames: [ 'explore-hanzo-dev.lux.network' ],
explorerUrl: 'https://explore-hanzo-dev.lux.network',
apiUrl: 'https://api-explore-hanzo-dev.lux.network',
branding: HANZO_BRANDING,
},
{
name: 'SPC',
label: 'SPC Chain (Devnet)',
vm: 'L2',
network: 'devnet',
chainId: 36912,
hostnames: [ 'explore-spc-dev.lux.network' ],
explorerUrl: 'https://explore-spc-dev.lux.network',
apiUrl: 'https://api-explore-spc-dev.lux.network',
branding: SPC_BRANDING,
},
{
name: 'Pars',
label: 'Pars Network (Devnet)',
vm: 'L2',
network: 'devnet',
chainId: 494951,
hostnames: [ 'explore-pars-dev.lux.network' ],
explorerUrl: 'https://explore-pars-dev.lux.network',
apiUrl: 'https://api-explore-pars-dev.lux.network',
branding: PARS_BRANDING,
},
// Localnet — chainId 1337. Listing localhost / 127.0.0.1 / 0.0.0.0 here
// keeps isWhiteLabelMode() false in local dev so the chain + network
@@ -298,6 +396,7 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
label: 'Contract Chain',
vm: 'EVM',
network: 'localnet',
chainId: 1337,
hostnames: [ 'explore.localnet', 'explore-local.lux.network' ],
explorerUrl: 'http://localhost:3000',
apiUrl: 'http://localhost:4000',
@@ -368,6 +467,7 @@ function buildWhiteLabelChain(hostname: string): ChainEntry {
label: branding.brandName,
vm: 'EVM',
network: (getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true' ? 'testnet' : 'mainnet') as 'mainnet' | 'testnet' | 'devnet' | 'localnet',
chainId: Number(getEnvValue('NEXT_PUBLIC_NETWORK_ID')) || 0,
hostnames: [ hostname ],
explorerUrl: `${ protocol }://${ appHost }`,
apiUrl: apiHost ? `${ apiProtocol }://${ apiHost }` : '',
+67
View File
@@ -0,0 +1,67 @@
// White-label brand detection.
//
// Source code in this repo is brand-neutral. Brand selection happens at
// deploy time via `NEXT_PUBLIC_BRAND` env var or by hostname inspection.
// All brand-specific assets (logo, title, favicon, color theme, IAM URL)
// load from `NEXT_PUBLIC_*` env vars — no hardcoded brand strings here.
//
// Supported brands:
// - lux (default)
// - hanzo
// - zoo
// - pars
// - other (any external white-label deploy: branding from env vars)
//
// External deployments select their brand by setting NEXT_PUBLIC_BRAND in
// the runtime environment. They do NOT add their brand here — keeping this
// repo free of any third-party trademarks.
import { getEnvValue } from 'configs/app/utils';
export type WhiteLabelBrand = 'lux' | 'hanzo' | 'zoo' | 'pars' | 'other';
const BRAND_HOSTNAME_SUFFIXES: ReadonlyArray<{ readonly suffix: string; readonly brand: WhiteLabelBrand }> = [
{ suffix: '.lux.network', brand: 'lux' },
{ suffix: '.lux.build', brand: 'lux' },
{ suffix: '.hanzo.ai', brand: 'hanzo' },
{ suffix: '.hanzo.network', brand: 'hanzo' },
{ suffix: '.zoo.ngo', brand: 'zoo' },
{ suffix: '.zoo.network', brand: 'zoo' },
{ suffix: '.pars.network', brand: 'pars' },
];
export function getWhiteLabelBrand(hostname?: string): WhiteLabelBrand {
// 1. Explicit env var wins (CI/CD, k8s ConfigMap)
const envBrand = getEnvValue('NEXT_PUBLIC_BRAND');
if (envBrand) {
const normalized = envBrand.toLowerCase();
if (
normalized === 'lux' ||
normalized === 'hanzo' ||
normalized === 'zoo' ||
normalized === 'pars' ||
normalized === 'other'
) {
return normalized;
}
// Any unknown brand string → 'other' (env-driven white-label)
return 'other';
}
// 2. Hostname-based detection
const host = (hostname || (typeof window !== 'undefined' ? window.location.hostname : '')).toLowerCase();
if (host) {
for (const entry of BRAND_HOSTNAME_SUFFIXES) {
if (host === entry.suffix.slice(1) || host.endsWith(entry.suffix)) {
return entry.brand;
}
}
}
// 3. Default
return 'lux';
}
export function isWhiteLabelExternal(brand: WhiteLabelBrand): boolean {
return brand === 'other';
}