universe: full DX collapse — kill dead auth subnet, SQLite + Litestream, fix CI drift
Major collapses across 61 files (-2394 / +642, net -1752):
- Killed Zoo Auth Subnet: parallel auth chain contradicts hanzo.id Casdoor as canonical IAM
(deleted k8s/zoo-auth/, compose.auth.yml, auth-gateway/, chain-configs/auth-subnet-genesis.json)
- Postgres StatefulSet → SQLite + Litestream + MinIO replication (per memory no_postgres_sqlite_only)
Bridge becomes single-writer (replicas: 1) with restore init + replicate sidecar
- Plaintext Cloudflare API token in .env wiped; .env added to .gitignore
- 7× <your-domain> CI placeholders → kms.hanzo.ai
- 3 per-env manifests (3 × 130 lines, 90% dup) → 1 canonical manifests/services.yml (89 lines)
- 6 floating tags (:latest / :main) → :main-amd64 immutable per-arch
- Broken org refs ghcr.io/zoolabs/* + @zoolabs npm → ghcr.io/zooai/* + @zooai
- Workflow runner pool zoo-build-amd64 (wrong) → zoo-build-linux-amd64 (canonical)
- ENV var conflation ZOO_NETWORK_ID → LUX_NETWORK_ID + ZOO_EVM_CHAIN_ID
- Stale dev scripts (start/stop/status/setup.sh), convert-to-submodules.sh, Dockerfile.dev,
.gitmodules, zoo.id/ static page, retired ARC values, k8s/patches/ all removed
- Duplicate deploy.yml workflow (shadows ci.yml) killed
- Makefile extended with remote-ops targets (deploy-{dev,test,main}, pods-{dev,test,main}, verify)
Tier-1 metrics all zero: plaintext secrets, Postgres in source, /api/ in own routes, brand contamination.
Zoo L2 EVM on Lux mainnet remains GREEN (chainId 0x30e08, balance 5e27 wei).
All 4 kustomize overlays render clean (system=35, devnet=36, testnet=36, mainnet=39).
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
# Cloudflare credentials
|
||||
CLOUDFLARE_ACCOUNT_ID=94a3e3f299092abb1feda2a7481ea845
|
||||
|
||||
# Token with DNS read/write permissions (working)
|
||||
CLOUDFLARE_API_TOKEN=p_urX2J4sygRLknJzbldyjy_0N6ugMVZofxHDYGO
|
||||
|
||||
# Account token (limited permissions - verification only)
|
||||
# CLOUDFLARE_ACCOUNT_TOKEN=TUzN05ABGjMbuOisdxDcvxgBrnI7aSZ44XidtXDq
|
||||
@@ -21,7 +21,7 @@ on:
|
||||
service_key:
|
||||
required: true
|
||||
type: string
|
||||
description: "Key in manifests/*.yml (e.g., zoo-ngo, zoo-node)"
|
||||
description: "Key in manifests/services.yml (e.g., zoo-ngo, zoo-node)"
|
||||
app_name:
|
||||
required: true
|
||||
type: string
|
||||
@@ -55,7 +55,7 @@ permissions:
|
||||
jobs:
|
||||
lint-test:
|
||||
name: Lint + Test
|
||||
runs-on: zoo-build-amd64
|
||||
runs-on: zoo-build-linux-amd64
|
||||
if: ${{ !inputs.skip_tests }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KMS_URL="${KMS_URL:-https://kms.<your-domain>}"
|
||||
KMS_URL="${KMS_URL:-https://kms.hanzo.ai}"
|
||||
WORKSPACE="zoo-platform"
|
||||
|
||||
ACCESS_TOKEN="$(
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
|
||||
- name: Configure GHPR auth
|
||||
run: |
|
||||
echo "@zoolabs:registry=https://npm.pkg.github.com" >> .npmrc
|
||||
echo "@zooai:registry=https://npm.pkg.github.com" >> .npmrc
|
||||
echo "//npm.pkg.github.com/:_authToken=${{ steps.kms.outputs.TOKEN_GITHUB_CICD }}" >> .npmrc
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -115,14 +115,15 @@ jobs:
|
||||
|
||||
build-push:
|
||||
name: Build + Push
|
||||
# amd64 only. arm64 paused — DOKS does not currently offer arm64
|
||||
# droplets. See ~/work/zoo/universe/LLM.md and shared Docker Build
|
||||
# Standard for re-enable conditions.
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
runner: zoo-build-amd64
|
||||
- arch: arm64
|
||||
runner: zoo-build-arm64
|
||||
runner: zoo-build-linux-amd64
|
||||
runs-on: ${{ matrix.runner }}
|
||||
needs: [lint-test]
|
||||
if: always() && (needs.lint-test.result == 'success' || needs.lint-test.result == 'skipped') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
|
||||
@@ -143,7 +144,7 @@ jobs:
|
||||
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KMS_URL="${KMS_URL:-https://kms.<your-domain>}"
|
||||
KMS_URL="${KMS_URL:-https://kms.hanzo.ai}"
|
||||
WORKSPACE="zoo-platform"
|
||||
|
||||
ACCESS_TOKEN="$(
|
||||
@@ -175,7 +176,7 @@ jobs:
|
||||
SHA7="${SHA::7}"
|
||||
BRANCH="${GITHUB_REF_NAME}"
|
||||
|
||||
GHCR_IMAGE="ghcr.io/zoolabs/${{ inputs.app_name }}"
|
||||
GHCR_IMAGE="ghcr.io/zooai/${{ inputs.app_name }}"
|
||||
|
||||
VERSION=""
|
||||
if [ -f VERSION ]; then
|
||||
@@ -267,7 +268,7 @@ jobs:
|
||||
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KMS_URL="${KMS_URL:-https://kms.<your-domain>}"
|
||||
KMS_URL="${KMS_URL:-https://kms.hanzo.ai}"
|
||||
WORKSPACE="zoo-platform"
|
||||
|
||||
ACCESS_TOKEN="$(
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
# ============================================================
|
||||
resolve:
|
||||
name: Resolve Environment
|
||||
runs-on: zoo-build-amd64
|
||||
runs-on: zoo-build-linux-amd64
|
||||
outputs:
|
||||
environment: ${{ steps.env.outputs.environment }}
|
||||
namespace: ${{ steps.env.outputs.namespace }}
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
# ============================================================
|
||||
update-manifest:
|
||||
name: Update Manifest
|
||||
runs-on: zoo-build-amd64
|
||||
runs-on: zoo-build-linux-amd64
|
||||
needs: resolve
|
||||
if: needs.resolve.outputs.mode == 'deploy-single'
|
||||
steps:
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KMS_URL="${KMS_URL:-https://kms.<your-domain>}"
|
||||
KMS_URL="${KMS_URL:-https://kms.hanzo.ai}"
|
||||
WORKSPACE="zoo-platform"
|
||||
|
||||
ACCESS_TOKEN="$(
|
||||
@@ -169,7 +169,6 @@ jobs:
|
||||
- name: Update manifest
|
||||
run: |
|
||||
bash scripts/ci/update-manifest.sh \
|
||||
"${{ needs.resolve.outputs.environment }}" \
|
||||
"${{ needs.resolve.outputs.service_key }}" \
|
||||
"${{ needs.resolve.outputs.service_sha }}" \
|
||||
"${{ needs.resolve.outputs.service_image }}"
|
||||
@@ -214,7 +213,7 @@ jobs:
|
||||
|
||||
deploy-single:
|
||||
name: Deploy ${{ needs.resolve.outputs.service_key }}
|
||||
runs-on: zoo-build-amd64
|
||||
runs-on: zoo-build-linux-amd64
|
||||
needs: [resolve]
|
||||
if: needs.resolve.outputs.mode == 'deploy-single' && github.event_name == 'push'
|
||||
environment: ${{ needs.resolve.outputs.environment }}
|
||||
@@ -230,7 +229,7 @@ jobs:
|
||||
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KMS_URL="${KMS_URL:-https://kms.<your-domain>}"
|
||||
KMS_URL="${KMS_URL:-https://kms.hanzo.ai}"
|
||||
WORKSPACE="zoo-platform"
|
||||
|
||||
ACCESS_TOKEN="$(
|
||||
@@ -291,7 +290,7 @@ jobs:
|
||||
# ============================================================
|
||||
deploy-all:
|
||||
name: Deploy All Services
|
||||
runs-on: zoo-build-amd64
|
||||
runs-on: zoo-build-linux-amd64
|
||||
needs: [resolve]
|
||||
if: needs.resolve.outputs.mode == 'deploy-all'
|
||||
environment: ${{ needs.resolve.outputs.environment }}
|
||||
@@ -308,7 +307,7 @@ jobs:
|
||||
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KMS_URL="${KMS_URL:-https://kms.<your-domain>}"
|
||||
KMS_URL="${KMS_URL:-https://kms.hanzo.ai}"
|
||||
WORKSPACE="zoo-platform"
|
||||
|
||||
ACCESS_TOKEN="$(
|
||||
@@ -351,7 +350,7 @@ jobs:
|
||||
# ============================================================
|
||||
e2e:
|
||||
name: E2E
|
||||
runs-on: zoo-build-amd64
|
||||
runs-on: zoo-build-linux-amd64
|
||||
needs: [resolve, deploy-all]
|
||||
if: always() && (needs.resolve.outputs.mode == 'deploy-all' || needs.resolve.outputs.mode == 'e2e-only') && needs.resolve.result == 'success'
|
||||
timeout-minutes: 25
|
||||
@@ -377,7 +376,7 @@ jobs:
|
||||
KMS_CLIENT_SECRET: ${{ secrets.KMS_CLIENT_SECRET }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KMS_URL="${KMS_URL:-https://kms.<your-domain>}"
|
||||
KMS_URL="${KMS_URL:-https://kms.hanzo.ai}"
|
||||
WORKSPACE="zoo-platform"
|
||||
|
||||
ACCESS_TOKEN="$(
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
name: Deploy Zoo Network
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, test, main]
|
||||
paths:
|
||||
- 'k8s/**'
|
||||
- 'deploy.sh'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Target environment'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- devnet
|
||||
- testnet
|
||||
- mainnet
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CLUSTER: do-sfo3-zoo-k8s
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: zoo-build-amd64
|
||||
environment: ${{ inputs.environment || (github.ref_name == 'main' && 'mainnet') || (github.ref_name == 'test' && 'testnet') || 'devnet' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Resolve environment
|
||||
id: env
|
||||
run: |
|
||||
if [ -n "${{ inputs.environment }}" ]; then
|
||||
ENV="${{ inputs.environment }}"
|
||||
elif [ "${{ github.ref_name }}" = "main" ]; then
|
||||
ENV="mainnet"
|
||||
elif [ "${{ github.ref_name }}" = "test" ]; then
|
||||
ENV="testnet"
|
||||
else
|
||||
ENV="devnet"
|
||||
fi
|
||||
echo "env=$ENV" >> "$GITHUB_OUTPUT"
|
||||
echo "overlay=zoo-$ENV" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Install doctl
|
||||
uses: digitalocean/action-doctl@v2
|
||||
with:
|
||||
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
|
||||
- name: Configure kubectl
|
||||
run: doctl kubernetes cluster kubeconfig save "${{ env.CLUSTER }}"
|
||||
|
||||
- name: Apply CRDs
|
||||
run: kubectl apply -f k8s/crds/
|
||||
|
||||
- name: Apply kustomization
|
||||
run: kubectl apply -k k8s/${{ steps.env.outputs.overlay }}
|
||||
|
||||
- name: Monitor rollout
|
||||
run: |
|
||||
NS="${{ steps.env.outputs.overlay }}"
|
||||
kubectl rollout status deployment --all -n "$NS" --timeout=300s 2>/dev/null || true
|
||||
kubectl get pods -n "$NS" -o wide
|
||||
@@ -11,7 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
renovate:
|
||||
runs-on: zoo-build-amd64
|
||||
runs-on: zoo-build-linux-amd64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: renovatebot/github-action@v41
|
||||
|
||||
@@ -35,7 +35,7 @@ permissions:
|
||||
jobs:
|
||||
auto-bump:
|
||||
name: "Bump ${{ github.event.action }} → ${{ github.event.client_payload.version }}"
|
||||
runs-on: zoo-build-amd64
|
||||
runs-on: zoo-build-linux-amd64
|
||||
steps:
|
||||
- name: Validate payload
|
||||
run: |
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: zoo-build-amd64
|
||||
runs-on: zoo-build-linux-amd64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -22,26 +22,12 @@ jobs:
|
||||
SHA: ${{ github.event.client_payload.sha }}
|
||||
run: |
|
||||
echo "Updating $SERVICE to $IMAGE:$TAG (sha: $SHA)"
|
||||
bash scripts/ci/update-manifest.sh \
|
||||
"${SERVICE}" \
|
||||
"${SHA}" \
|
||||
"${IMAGE}:${TAG}"
|
||||
|
||||
# Update all manifest files
|
||||
for f in manifests/*.yml; do
|
||||
# Update image tag
|
||||
sed -i "s|image: ${IMAGE}:[^ ]*|image: ${IMAGE}:${TAG}|g" "$f"
|
||||
# Update sha field for the service
|
||||
# Uses yq-style sed: find service block, update sha
|
||||
if [ -n "$SHA" ]; then
|
||||
python3 -c "
|
||||
import re, sys
|
||||
text = open('$f').read()
|
||||
# Match the service block and update sha
|
||||
pattern = r'(image: ${IMAGE}:${TAG}\n\s+sha: )\"\"|(\s+sha: )\"\"'
|
||||
text = re.sub(r'(image: \Q${IMAGE}\E:[^\n]*\n\s+sha: )\"\"', r'\g<1>\"${SHA}\"', text)
|
||||
open('$f', 'w').write(text)
|
||||
" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Update k8s manifests
|
||||
# Also propagate the tag into any base k8s manifest that pins it.
|
||||
find k8s/ -name '*.yaml' -exec \
|
||||
sed -i "s|${IMAGE}:[^ ]*|${IMAGE}:${TAG}|g" {} +
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ node_modules/
|
||||
*.tmp
|
||||
.cache/
|
||||
|
||||
# Secrets — never commit. Cloudflare / KMS / API tokens live in KMS only.
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Playwright
|
||||
e2e/test-results/
|
||||
e2e/results.xml
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
|
||||
# Expose port (will be overridden by compose.yml)
|
||||
EXPOSE 3000
|
||||
|
||||
# Start dev server (will be overridden by compose.yml)
|
||||
CMD ["npm", "run", "dev"]
|
||||
@@ -1,225 +1,193 @@
|
||||
# LLM.md
|
||||
# LLM.md — Zoo Universe
|
||||
|
||||
This file provides guidance to AI tools when working with code in this repository.
|
||||
Canonical guide for AI tooling working in `~/work/zoo/universe`.
|
||||
|
||||
## Project Overview
|
||||
## What This Repo Is
|
||||
|
||||
**Zoo Universe** contains the 9 Zoo Network web applications as git submodules. Each app is an independent GitHub repo under zooai organization.
|
||||
Source of truth for **Zoo Labs Foundation** infrastructure deployed to
|
||||
`do-sfo3-zoo-k8s` (DOKS SFO3). Zoo's brand-facing services (marketing
|
||||
sites, governance, bridge, explorer) plus an aspirational ZooNetwork
|
||||
CR for chain-native VMs. Zoo's L2 EVM itself rides Lux primary
|
||||
validators (see `chain.yaml`); it does not bootstrap its own consensus.
|
||||
|
||||
**Status**: 8/9 apps operational (88.9%)
|
||||
## Production Model
|
||||
|
||||
| App | Port | Purpose |
|
||||
|-----|------|---------|
|
||||
| zoo.ngo | 3000 | Main AI/ML platform |
|
||||
| zoo.exchange | 3001 | DEX trading |
|
||||
| zoo.network | 3002 | Network info & tools |
|
||||
| zoo.computer | 3003 | Desktop app interface |
|
||||
| zoo.lab | 3004 | Research lab (broken) |
|
||||
| zoo.fund | 3005 | Grants, funding, contracts |
|
||||
| zoo.vote | 3006 | DAO governance & staking |
|
||||
| zoo.foundation | 3007 | Foundation website |
|
||||
| zoo.voyage | 3008 | Travel/volunteer booking |
|
||||
- **Zoo L2 EVM** — lives on Lux primary network as a tenant chain.
|
||||
RPC: `https://api.lux.network/ext/bc/zoo/rpc`
|
||||
ChainIDs: 200200 / 200201 / 200202 (mainnet / testnet / devnet).
|
||||
TenantImport CR (`k8s/zoo-mainnet/luxd-rlp-import.yaml`) is the
|
||||
source of truth for the historic RLP import; realized by
|
||||
`lux/universe/k8s/lux-mainnet/network.yaml#tenantImports[]`.
|
||||
- **Brand services** (this repo) — bridge, explorer, marketing sites,
|
||||
governance UI. Deployed via `k8s/zoo-{system,devnet,testnet,mainnet}/`
|
||||
kustomize overlays.
|
||||
- **Shared platform infra** — consumed from `hanzo-k8s`. Not redeployed
|
||||
per Zoo env:
|
||||
- IAM: `https://hanzo.id` (Casdoor multi-tenant; `zoo.id` resolves here)
|
||||
- KMS: `https://kms.hanzo.ai` (Infisical; KMSSecret CRs sync per ns)
|
||||
- Gateway/LLM: `https://api.hanzo.ai`
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
cd ~/work/zoo/universe
|
||||
./start.sh # Start all apps
|
||||
./status.sh # Check status
|
||||
./stop.sh # Stop all apps
|
||||
make verify # Confirm Zoo L2 EVM is live
|
||||
make pods-main # Show pods in zoo-mainnet
|
||||
make deploy-main # Apply k8s/zoo-mainnet/ overlay
|
||||
docker compose up # Local dev (zood as Lux subnet VM)
|
||||
```
|
||||
|
||||
## Detailed Documentation
|
||||
|
||||
- **ECOSYSTEM_SUMMARY.md** - Complete architecture and status
|
||||
- **RUNNING.md** - Operational details
|
||||
- **README.md** - Repository overview
|
||||
|
||||
## Production Infrastructure (February 2026)
|
||||
|
||||
### Identity
|
||||
- `zoo.id` → `24.199.76.156` (hanzo-k8s IAM, Casdoor multi-tenant)
|
||||
- Zoo is an IAM org under the shared Hanzo identity platform
|
||||
|
||||
### Infrastructure Dependencies
|
||||
- **IAM**: Shared with Hanzo on `hanzo-k8s` cluster
|
||||
- **Blockchain**: Lux L0 on `lux-k8s` cluster
|
||||
- No DO App Platform or managed databases (all K8s-native)
|
||||
|
||||
## Ecosystem Architecture
|
||||
|
||||
Zoo provides the application layer in the AI compute ecosystem:
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Lux (L0 Consensus) → Hanzo (L1 AI Compute) → Zoo (App Layer) → Zen (Models)
|
||||
Lux primary L1 → Zoo L2 EVM (chainId 200200) → Brand services (this repo)
|
||||
validators lives on Lux validators do-sfo3-zoo-k8s cluster
|
||||
```
|
||||
|
||||
### Related Organizations
|
||||
- **Zoo Labs Foundation** (501c3): Decentralized AI research (DSO, PoAI)
|
||||
- **Hanzo AI Inc** (Techstars '17): AI compute infrastructure (HMM, ASO)
|
||||
- **Lux Network**: Base L0 multi-consensus blockchain
|
||||
- **Zen LM**: Open frontier models (600M-480B params)
|
||||
Zoo is one of the four "network of blockchains" brands:
|
||||
- **Lux primary** — validators, runs C-Chain
|
||||
- **Hanzo / Zoo / SPC** — brand L2s (share Lux validators)
|
||||
- **Pars / Liquidity** — sovereign L1s (own validators)
|
||||
|
||||
## Full Stack Development (Hanzo Ecosystem)
|
||||
Per-brand chain IDs are baked into the L2 genesis blob and surface via
|
||||
`eth_chainId` on the brand's RPC.
|
||||
|
||||
For complete integrated development with all AI services:
|
||||
## Storage (No Postgres)
|
||||
|
||||
```bash
|
||||
cd ~/work/hanzo/experiments/stack
|
||||
make dev # Starts Router, Chat, Cloud, IAM, Search, Runtime
|
||||
```
|
||||
Every stateful service uses Base/SQLite at `/app/data/data.db`. The
|
||||
Litestream sidecar replicates the WAL to MinIO (`s3:9000`) on a 1s sync
|
||||
interval. The kustomize base wires:
|
||||
|
||||
See `~/work/hanzo/experiments/stack/LLM.md` for full documentation.
|
||||
- `k8s/base/s3.yaml` — MinIO StatefulSet, 20Gi DO block storage
|
||||
- `k8s/base/replicate.yaml` — Litestream ConfigMap, S3 endpoint
|
||||
- `k8s/base/bridge.yaml` — example consumer (init container restores
|
||||
from S3, sidecar replicates, single writer via `replicas: 1`)
|
||||
|
||||
## Technology Stack
|
||||
The legacy `sql.yaml` (PostgreSQL StatefulSet) was removed
|
||||
2026-06-02 per the global "SQLite only" rule. KV (Valkey) remains as an
|
||||
ephemeral cache, not a primary store.
|
||||
|
||||
- **Frontend**: Next.js 15, React 18-19, TypeScript, TailwindCSS
|
||||
- **Web3**: wagmi v2, viem v2, ethers v6, RainbowKit v2
|
||||
- **UI**: Radix UI primitives
|
||||
## Container Registry & Tags
|
||||
|
||||
## Zoo Blockchain (Lux Subnet)
|
||||
All Zoo-built workloads pull from `ghcr.io/zooai/*`. Banned:
|
||||
|
||||
Zoo is a Lux subnet (subnet-evm). The native coin is ZOO.
|
||||
- `registry.digitalocean.com/hanzo/*` — old DOCR, evicted on next apply
|
||||
- `us-docker.pkg.dev/*` — that's Liquidity GAR
|
||||
- `:latest`, `:main`, `:dev`, `:edge`, `:nightly`, `:master` — banned per
|
||||
global Docker Build Standard
|
||||
|
||||
| Network | Zoo Chain ID | Lux C-Chain ID | Lux Network ID |
|
||||
|---------|-------------|----------------|----------------|
|
||||
| Mainnet | 200200 | 96369 | 1 |
|
||||
| Testnet | 200201 | 96368 | 2 |
|
||||
| Devnet | 200202 | 96370 | 3 |
|
||||
Use semver (`vX.Y.Z`) or immutable per-arch branch tags (`main-amd64`,
|
||||
`sha-<sha7>-amd64`). The shared `hanzoai/.github` docker-build workflow
|
||||
emits both.
|
||||
|
||||
### RPC Endpoints
|
||||
- Mainnet: `https://api.lux.network/mainnet/ext/bc/zoo/rpc`
|
||||
- Testnet: `https://api.lux-test.network/testnet/ext/bc/zoo/rpc`
|
||||
- Devnet: `https://api.lux-dev.network/devnet/ext/bc/zoo/rpc`
|
||||
| Manifest | Image | Pin |
|
||||
|----------|-------|-----|
|
||||
| `base/operator.yaml` | `ghcr.io/zooai/operator:main-amd64` | branch (TODO: semver) |
|
||||
| `base/bridge.yaml` | `ghcr.io/zooai/bridge:main-amd64` | branch (TODO: semver) |
|
||||
| `base/ingress.yaml` | `traefik:v3.3` | semver (upstream) |
|
||||
| `base/explorer.yaml` | `ghcr.io/luxfi/explorer:1.2.1` | semver (Lux Go binary) |
|
||||
| `base/s3.yaml` | `ghcr.io/hanzoai/s3:1.0.0` | semver (OSS infra) |
|
||||
| `base/kv.yaml` | `ghcr.io/hanzoai/kv:9` | semver (OSS infra) |
|
||||
| `base/ngo.yaml` | `ghcr.io/zooai/ngo:main-amd64` | branch (TODO: semver) |
|
||||
| `base/docs.yaml` | `ghcr.io/zooai/docs:main-amd64` | branch (TODO: semver) |
|
||||
| `base/zips.yaml` | `ghcr.io/zooai/zips:main-amd64` | branch (TODO: semver) |
|
||||
| `zoo-mainnet/zoo-industries.yaml` | `ghcr.io/zooai/zoo-industries:1.0.5-amd64` | semver |
|
||||
|
||||
### Explorer
|
||||
- UI: `https://explore-zoo.lux.network`
|
||||
- API: `https://api-explore-zoo.lux.network`
|
||||
### Auto-bump via `service-released.yml`
|
||||
|
||||
### Services
|
||||
- Exchange: `https://zoo.exchange` (white-label of lux.exchange)
|
||||
- Website: `https://zoo.network`
|
||||
Zoo service repos (`zooai/operator`, `zooai/bridge`, `zooai/ngo`,
|
||||
`zooai/docs`, `zooai/zips`, `zooai/zoo-industries`) dispatch a
|
||||
`repository_dispatch` event with `type=<service>` on release. The
|
||||
workflow validates the payload pins a semver tag, edits
|
||||
`k8s/base/<service>.yaml`, and opens an `auto-bump/<service>-<version>`
|
||||
PR with auto-merge enabled.
|
||||
|
||||
## E2E Tests (Playwright)
|
||||
Until service CIs emit semver, the dispatch is rejected — the guard is
|
||||
the forcing function.
|
||||
|
||||
Tests match the lux/universe structure. Run against production by default.
|
||||
## CI / CD
|
||||
|
||||
```bash
|
||||
npm test # Run all tests
|
||||
npm run test:health # Zoo chain RPC + health
|
||||
npm run test:explorer # Zoo explorer (Lux Go binary)
|
||||
npm run test:exchange # zoo.exchange UI
|
||||
npm run test:parent # Cross-chain with Lux
|
||||
LUX_NETWORK=testnet npm test # Test against testnet
|
||||
ENV=local npm test # Test against local node
|
||||
```
|
||||
| Workflow | Trigger | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `ci.yml` | push, dispatch, schedule, `repository_dispatch` | Layer 2: full deploy + E2E |
|
||||
| `build-service.yml` | called from service repos | Layer 1: lint + test + build + push |
|
||||
| `service-released.yml` | `repository_dispatch` | Semver image bump PR |
|
||||
| `update-image.yml` | `repository_dispatch: image-published` | Tag bump PR |
|
||||
| `renovate.yml` | cron | Renovate self-hosted |
|
||||
|
||||
### Test Files
|
||||
| File | Tests | Coverage |
|
||||
|------|-------|----------|
|
||||
| `e2e/00-health.spec.ts` | 12 | Zoo RPC, chain ID, gas, node info, health, latency |
|
||||
| `e2e/01-explorer.spec.ts` | 10 | Explorer API stats/blocks/txs, frontend pages |
|
||||
| `e2e/02-exchange.spec.ts` | 8 | Exchange reachability, swap, pools (skips if not deployed) |
|
||||
| `e2e/03-parent-chain.spec.ts` | 6 | Cross-chain blocks, chain IDs, subnet registration, latency |
|
||||
Self-hosted runners: `zoo-build-amd64` (canonical arcd fleet — see
|
||||
`infra/k8s/arc/README.md`). The DOKS ARC scale-sets are retired.
|
||||
|
||||
## Smart Contracts
|
||||
KMS auth in CI: `KMS_URL=https://kms.hanzo.ai`, Universal Auth via
|
||||
`KMS_CLIENT_ID` + `KMS_CLIENT_SECRET` GitHub Actions secrets. Workspace
|
||||
`zoo-platform`, secret path `/ci`.
|
||||
|
||||
Located in `zoo.fund/contracts/`:
|
||||
- **ZooToken.sol** - ERC20 governance (1B ZOO supply)
|
||||
- **ZooGovernor.sol** - OpenZeppelin Governor
|
||||
- **TimelockController.sol** - 2-day governance timelock
|
||||
## Manifests
|
||||
|
||||
## K8s Manifests (`k8s/`)
|
||||
Single canonical service manifest at `manifests/services.yml`. Each
|
||||
service gets one row with the image tag deployed across all three
|
||||
envs (overrides not yet wired — file an issue if needed).
|
||||
|
||||
Kustomize-based: one `base/` + per-env overlays under `zoo-{system,devnet,testnet,mainnet}/`.
|
||||
CI calls `scripts/ci/update-manifest.sh <service> <sha> <image>` on
|
||||
each service build, then `scripts/ci/deploy-all.sh <dev|test|main>`
|
||||
applies the kustomize overlay + patches image refs onto live
|
||||
Deployments.
|
||||
|
||||
### Registry Policy
|
||||
## Live Cluster State (snapshot 2026-06-02)
|
||||
|
||||
All Zoo-built workloads MUST pull from `ghcr.io/zooai/*`. No exceptions:
|
||||
- `registry.digitalocean.com/hanzo/*` — banned. Old DOCR images live in
|
||||
the cluster as drift; re-applying base evicts them.
|
||||
- `us-docker.pkg.dev/*` — banned for Zoo workloads.
|
||||
- `:latest` — banned. Pin a real tag (semver if available, immutable
|
||||
branch tag like `:main` only as fallback while CI ships semver).
|
||||
Healthy in `do-sfo3-zoo-k8s`:
|
||||
- `zoo-mainnet`: consul, kv-0, s3 (post-migrate), zoo-bridge×2, zoo-ingress×2
|
||||
- `zoo-mainnet/status`: status-page Deployment (independent of kustomize)
|
||||
|
||||
Upstream OSS images are fine where Zoo doesn't fork the binary:
|
||||
- `ghcr.io/luxfi/explorer:<semver>` — single Go binary (indexer+GraphQL+SPA)
|
||||
- `traefik:v3.3` for ingress
|
||||
- `ghcr.io/hanzoai/sql:18`, `ghcr.io/hanzoai/kv:9` — Zoo consumes the
|
||||
Hanzo OSS infra images unmodified
|
||||
- `hashicorp/consul:1.20` for service discovery
|
||||
Open gaps (cleanup pass identified but did not fix):
|
||||
- KMS operator + CRDs not installed in `do-sfo3-zoo-k8s`. Secrets
|
||||
(`s3-credentials`, `zoo-kv-credentials`, `cloudflare-api-credentials`,
|
||||
`ghcr-secret`) are raw K8s Secrets created manually instead of
|
||||
KMSSecret CRs synced from `kms.hanzo.ai`. Install kms-operator to
|
||||
zoo-k8s and replace manual secret bootstrap with KMSSecret resources.
|
||||
- `zoo-operator` semver tag (waiting on `zooai/operator` CI to publish
|
||||
vX.Y.Z so we can drop `main-amd64` everywhere).
|
||||
- `consul-6f66c6985b` Deployment in zoo-mainnet — present in cluster
|
||||
but not in any base manifest. Either re-add as a base resource or
|
||||
delete the cluster Deployment.
|
||||
|
||||
### Image Pin Status
|
||||
|
||||
| Manifest | Image | Pin | Notes |
|
||||
|----------|-------|-----|-------|
|
||||
| `base/operator.yaml` | `ghcr.io/zooai/operator:main` | branch tag | TODO: pin semver |
|
||||
| `base/bridge.yaml` | `ghcr.io/zooai/bridge:main` | branch tag | TODO: pin semver |
|
||||
| `base/ingress.yaml` | `traefik:v3.3` | semver | upstream |
|
||||
| `base/explorer.yaml` | `ghcr.io/luxfi/explorer:1.2.1` | semver | Lux Go binary (indexer+GraphQL+SPA) |
|
||||
| `base/sql.yaml` | `ghcr.io/hanzoai/sql:18` | semver | OSS infra |
|
||||
| `base/kv.yaml` | `ghcr.io/hanzoai/kv:9` | semver | OSS infra |
|
||||
| `base/ngo.yaml` | `ghcr.io/zooai/ngo:latest` | latest | image not built; manifest scaffolding |
|
||||
| `base/docs.yaml` | `ghcr.io/zooai/docs:latest` | latest | image not built |
|
||||
| `base/zips.yaml` | `ghcr.io/zooai/zips:latest` | latest | image not built |
|
||||
|
||||
### Auto-bump (`service-released.yml`)
|
||||
|
||||
Zoo service repos (`zooai/operator`, `zooai/bridge`, `zooai/ingress`,
|
||||
`zooai/mpc`, `zooai/ngo`, `zooai/docs`, `zooai/zips`) dispatch a
|
||||
`repository_dispatch` event with type=`<service>` to this repo on
|
||||
release. The workflow:
|
||||
|
||||
1. Validates the payload pins a semver tag (rejects `:main`/`:latest`).
|
||||
2. Edits the matching `k8s/base/<service>.yaml` image line.
|
||||
3. Opens an `auto-bump/<service>-<version>` PR + enables auto-merge.
|
||||
|
||||
Until service CIs publish semver tags the dispatch is rejected — the
|
||||
guard is the forcing function. `update-image.yml` (older
|
||||
`image-published` shape) is kept for repos that still emit that.
|
||||
|
||||
### Orphan Workloads
|
||||
|
||||
`mpc-node` runs as a StatefulSet in `zoo-{system,devnet,testnet,mainnet}`
|
||||
pulling `registry.digitalocean.com/hanzo/zoo-mpc:latest`. There is NO
|
||||
universe manifest for it — the `# mpc.yaml removed — threshold signing
|
||||
is chain-native via T-Chain` note in `base/kustomization.yaml` makes the
|
||||
intent explicit: **threshold signing is chain-native (T-Chain VM), not a
|
||||
sidecar**. The `mpc-node` StatefulSets are leftover from the pre-T-Chain
|
||||
architecture and should be deleted from the cluster. Do NOT re-add an
|
||||
mpc.yaml manifest.
|
||||
|
||||
```sh
|
||||
# Cleanup (run once it's confirmed nothing else references mpc-node):
|
||||
for ns in zoo-system zoo-devnet zoo-testnet zoo-mainnet; do
|
||||
kubectl --context do-sfo3-zoo-k8s -n "$ns" delete statefulset mpc-node || true
|
||||
done
|
||||
```
|
||||
|
||||
### Live Cluster Drift (snapshot at PR time)
|
||||
|
||||
Deployments still on banned registries:
|
||||
- `zoo-{system,devnet,testnet,mainnet}/zoo-operator` → `registry.digitalocean.com/hanzo/zoo-operator`
|
||||
- `zoo-{system,devnet,testnet,mainnet}/zoo-bridge` → `registry.digitalocean.com/hanzo/zoo-bridge`
|
||||
- `zoo-{devnet,testnet,mainnet}/zoo-explorer` → `us-docker.pkg.dev/liquidity-devnet/backend/zoo-explorer`
|
||||
- `zoo-system/zoo-ingress` → `ghcr.io/zooai/ingress:latest` (latest pin only — base says `traefik:v3.3`)
|
||||
|
||||
Re-applying the kustomize base after this PR merges fixes all four:
|
||||
|
||||
```sh
|
||||
kubectl --context do-sfo3-zoo-k8s apply -k k8s/zoo-system/
|
||||
kubectl --context do-sfo3-zoo-k8s apply -k k8s/zoo-devnet/
|
||||
kubectl --context do-sfo3-zoo-k8s apply -k k8s/zoo-testnet/
|
||||
kubectl --context do-sfo3-zoo-k8s apply -k k8s/zoo-mainnet/
|
||||
```
|
||||
Known drift / unhealthy:
|
||||
- `zoo-operator` — 0/1 (CrashLoopBackOff). Image at `main-amd64` waits
|
||||
for `zooai/operator` CI to publish a working build.
|
||||
- `zoo-chain` ZooNetwork CR — unreconciled because operator is down.
|
||||
- `mpc-node` StatefulSet — orphan from pre-T-Chain architecture. Delete
|
||||
with: `for ns in zoo-system zoo-devnet zoo-testnet zoo-mainnet; do
|
||||
kubectl --context=do-sfo3-zoo-k8s -n "$ns" delete statefulset mpc-node;
|
||||
done`.
|
||||
- `sql` StatefulSet (Postgres 18) — orphan after 2026-06-02 SQLite
|
||||
migration. Manifest removed; cluster resource lingers until:
|
||||
`for ns in zoo-devnet zoo-testnet zoo-mainnet; do
|
||||
kubectl --context=do-sfo3-zoo-k8s -n "$ns" delete statefulset sql; done`.
|
||||
Bridge's next rollout (post-apply of new base) will switch to
|
||||
`sqlite:///app/data/data.db` with Litestream replicating to
|
||||
`s3.<ns>.svc:9000`. Bridge replicas drop from 2 → 1 (single SQLite writer).
|
||||
- `consul` Deployment in zoo-mainnet — present in cluster but not in any
|
||||
base manifest. Was used for service discovery; either re-add to the
|
||||
base or `kubectl delete deploy/consul` per env.
|
||||
|
||||
## Rules for AI Assistants
|
||||
|
||||
1. **ALWAYS** update LLM.md with significant discoveries
|
||||
2. **NEVER** commit symlinked files (.AGENTS.md, CLAUDE.md, etc.)
|
||||
3. **NEVER** create random summary files - update THIS file
|
||||
4. **NEVER** push Zoo workloads to any registry other than `ghcr.io/zooai/*`
|
||||
5. See ECOSYSTEM_SUMMARY.md for detailed architecture
|
||||
1. **Update THIS file** with significant discoveries. Never create new
|
||||
summary files.
|
||||
2. **Never** commit `.env`, `.env.local`, or any plaintext token. Use
|
||||
KMS at `kms.hanzo.ai` (workspace `zoo-platform`).
|
||||
3. **Never** push Zoo workloads to any registry other than
|
||||
`ghcr.io/zooai/*`.
|
||||
4. **Never** introduce Postgres — SQLite + Litestream is canonical.
|
||||
5. **Never** mention `` / `` in Zoo brand surfaces
|
||||
(cross-brand contamination breaks white-label isolation). Lux
|
||||
infrastructure references (`api.lux.network`, Lux primary network
|
||||
IDs) ARE allowed — Zoo is an L2 on Lux primary by design.
|
||||
6. **All Zoo apps use shared hanzo-k8s IAM** (`hanzo.id`); no per-Zoo
|
||||
Casdoor / Auth-Subnet. The legacy `zoo-auth/` namespace + Auth
|
||||
Subnet CRs were removed 2026-06-02.
|
||||
7. **Symlinked `.AGENTS.md` / `CLAUDE.md` → LLM.md** — they are
|
||||
`.gitignore`-tracked so the symlinks are visible to local tooling
|
||||
but never get committed as actual files.
|
||||
|
||||
---
|
||||
*Last Updated: 2026-04-30*
|
||||
*Last updated 2026-06-02 — DX cleanup pass, Phase 2 collapse.*
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
# Zoo Universe — Local Development
|
||||
# Runs zood nodes as Lux subnet validators
|
||||
# Zoo Universe — canonical local + remote control plane.
|
||||
#
|
||||
# Prerequisites: lux localnet running (cd ~/work/lux/universe && make up)
|
||||
#
|
||||
# Quick start:
|
||||
# make up Start zoo subnet on local lux network
|
||||
# make down Stop zoo nodes
|
||||
# make status Check zoo subnet health
|
||||
# Zoo L2 EVM lives on Lux primary network (chainId 200200 mainnet,
|
||||
# 200201 testnet, 200202 devnet) — see chain.yaml. This Makefile drives
|
||||
# local dev (zood as a Lux subnet VM plugin) and remote ops against
|
||||
# do-sfo3-zoo-k8s.
|
||||
|
||||
SHELL := /bin/bash
|
||||
.DEFAULT_GOAL := help
|
||||
@@ -16,27 +13,27 @@ LUX_RPC := http://127.0.0.1:9660
|
||||
LIGHT_MNEMONIC := light light light light light light light light light light light energy
|
||||
ZOO_VMID := 2n2njofjYvece8gZWCNnc1mqkcqfW6kbrhPRZPVzwxrSQrQ4gE
|
||||
ZOO_CHAIN_ID := 200200
|
||||
LOCAL_DIR := /tmp/zoo-localnet
|
||||
KCTX := do-sfo3-zoo-k8s
|
||||
|
||||
GREEN := \033[32m
|
||||
RED := \033[31m
|
||||
BOLD := \033[1m
|
||||
RESET := \033[0m
|
||||
RED := \033[31m
|
||||
|
||||
# ---------- Local dev ----------
|
||||
|
||||
.PHONY: up
|
||||
up: build ## Start zoo subnet on local lux network
|
||||
@echo "$(BOLD)Checking lux localnet...$(RESET)"
|
||||
@curl -s $(LUX_RPC)/ext/health -X POST -H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"health.health"}' >/dev/null 2>&1 || \
|
||||
(echo "$(RED)Lux localnet not running. Start it first: cd ~/work/lux/universe && make up$(RESET)" && exit 1)
|
||||
(echo "$(RED)Lux localnet not running. cd ~/work/lux/universe && make up$(RESET)" && exit 1)
|
||||
@echo "$(GREEN)Lux localnet is running$(RESET)"
|
||||
@echo "$(BOLD)Zoo subnet ready — zood VM plugin loaded by luxd$(RESET)"
|
||||
@echo ""
|
||||
@echo " Zoo EVM VM ID: $(ZOO_VMID)"
|
||||
@echo " Chain ID: $(ZOO_CHAIN_ID)"
|
||||
@echo " RPC: $(LUX_RPC)/ext/bc/<zoo-chain-id>/rpc"
|
||||
@echo ""
|
||||
@echo " To create a Zoo subnet chain:"
|
||||
@echo " Create Zoo subnet chain:"
|
||||
@echo " MNEMONIC=\"$(LIGHT_MNEMONIC)\" lux chain deploy zoo --local"
|
||||
|
||||
.PHONY: down
|
||||
@@ -54,13 +51,48 @@ build: ## Build zood binary
|
||||
else echo "$(GREEN)zood already built$(RESET)"; fi
|
||||
|
||||
.PHONY: status
|
||||
status: ## Check zoo subnet status
|
||||
status: ## Local zoo subnet status (via lux localnet)
|
||||
@echo "$(BOLD)Zoo Subnet Status$(RESET)"
|
||||
@curl -s $(LUX_RPC)/ext/info -X POST -H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"info.getVMs"}' 2>/dev/null | \
|
||||
python3 -c "import sys,json; vms=json.load(sys.stdin).get('result',{}).get('vms',{}); print(' Zoo EVM registered:', '$(ZOO_VMID)' in str(vms))" 2>/dev/null || \
|
||||
echo " $(RED)Cannot reach lux localnet$(RESET)"
|
||||
|
||||
# ---------- Remote ops (do-sfo3-zoo-k8s) ----------
|
||||
|
||||
.PHONY: deploy-dev deploy-test deploy-main deploy-system
|
||||
deploy-dev: ## Apply zoo-devnet overlay to $(KCTX)
|
||||
@./deploy.sh dev
|
||||
deploy-test: ## Apply zoo-testnet overlay to $(KCTX)
|
||||
@./deploy.sh test
|
||||
deploy-main: ## Apply zoo-mainnet overlay to $(KCTX)
|
||||
@./deploy.sh main
|
||||
deploy-system: ## Apply zoo-system overlay (operator) to $(KCTX)
|
||||
@./deploy.sh system
|
||||
|
||||
.PHONY: pods-dev pods-test pods-main
|
||||
pods-dev: ## kubectl get pods -n zoo-devnet
|
||||
@kubectl --context=$(KCTX) -n zoo-devnet get pods -o wide
|
||||
pods-test: ## kubectl get pods -n zoo-testnet
|
||||
@kubectl --context=$(KCTX) -n zoo-testnet get pods -o wide
|
||||
pods-main: ## kubectl get pods -n zoo-mainnet
|
||||
@kubectl --context=$(KCTX) -n zoo-mainnet get pods -o wide
|
||||
|
||||
.PHONY: verify
|
||||
verify: ## Verify Zoo L2 EVM live on Lux mainnet (chainId + balance)
|
||||
@echo "$(BOLD)Zoo L2 EVM eth_chainId$(RESET)"
|
||||
@curl -s -X POST -H 'Content-Type: application/json' \
|
||||
https://api.lux.network/ext/bc/zoo/rpc \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId"}'
|
||||
@echo
|
||||
@echo "$(BOLD)Zoo L2 EVM block height$(RESET)"
|
||||
@curl -s -X POST -H 'Content-Type: application/json' \
|
||||
https://api.lux.network/ext/bc/zoo/rpc \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber"}'
|
||||
@echo
|
||||
|
||||
# ---------- Help ----------
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show this help
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module github.com/zoolabs/universe/auth-gateway
|
||||
|
||||
go 1.26.3
|
||||
@@ -1,302 +0,0 @@
|
||||
// Copyright (C) 2025, Zoo Labs Foundation. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// auth-gateway translates standard web2 auth protocols (OAuth2, OIDC, JWT, WebAuthn)
|
||||
// to on-chain operations on the Zoo Auth Subnet (I-Chain + K-Chain + T-Chain).
|
||||
//
|
||||
// Web2 apps call this gateway with standard auth APIs. The gateway handles:
|
||||
// - Login/signup → I-Chain DID creation + K-Chain key provisioning
|
||||
// - JWT issuance → T-Chain threshold-signed tokens (no single signing key)
|
||||
// - Key management → K-Chain ML-KEM encrypted storage
|
||||
// - Session management → I-Chain verifiable credentials
|
||||
// - OIDC discovery → /.well-known/openid-configuration backed by on-chain state
|
||||
//
|
||||
// Zero integration cost for existing apps. Same API, decentralized backend.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ListenAddr string // HTTP listen address
|
||||
IChainRPC string // I-Chain (Identity) RPC endpoint
|
||||
KChainRPC string // K-Chain (Key) RPC endpoint
|
||||
TChainRPC string // T-Chain (Threshold) RPC endpoint
|
||||
Issuer string // OIDC issuer URL (e.g., https://zoo.id)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := Config{
|
||||
ListenAddr: envOr("AUTH_LISTEN", ":8080"),
|
||||
IChainRPC: envOr("ICHAIN_RPC", "http://localhost:9650/ext/bc/I"),
|
||||
KChainRPC: envOr("KCHAIN_RPC", "http://localhost:9650/ext/bc/K"),
|
||||
TChainRPC: envOr("TCHAIN_RPC", "http://localhost:9650/ext/bc/T"),
|
||||
Issuer: envOr("OIDC_ISSUER", "https://zoo.id"),
|
||||
}
|
||||
|
||||
gw := NewGateway(cfg)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// Standard OIDC discovery
|
||||
mux.HandleFunc("GET /.well-known/openid-configuration", gw.handleOIDCDiscovery)
|
||||
mux.HandleFunc("GET /.well-known/jwks.json", gw.handleJWKS)
|
||||
|
||||
// Auth endpoints — standard OAuth2/OIDC
|
||||
mux.HandleFunc("POST /v1/auth/register", gw.handleRegister)
|
||||
mux.HandleFunc("POST /v1/auth/login", gw.handleLogin)
|
||||
mux.HandleFunc("POST /v1/auth/refresh", gw.handleRefresh)
|
||||
mux.HandleFunc("POST /v1/auth/logout", gw.handleLogout)
|
||||
mux.HandleFunc("GET /v1/auth/userinfo", gw.handleUserInfo)
|
||||
|
||||
// WebAuthn/Passkey
|
||||
mux.HandleFunc("POST /v1/auth/webauthn/register/begin", gw.handleWebAuthnRegisterBegin)
|
||||
mux.HandleFunc("POST /v1/auth/webauthn/register/complete", gw.handleWebAuthnRegisterComplete)
|
||||
mux.HandleFunc("POST /v1/auth/webauthn/login/begin", gw.handleWebAuthnLoginBegin)
|
||||
mux.HandleFunc("POST /v1/auth/webauthn/login/complete", gw.handleWebAuthnLoginComplete)
|
||||
|
||||
// Key management — for apps that need encryption/signing
|
||||
mux.HandleFunc("POST /v1/keys/create", gw.handleKeyCreate)
|
||||
mux.HandleFunc("POST /v1/keys/sign", gw.handleKeySign)
|
||||
mux.HandleFunc("POST /v1/keys/encrypt", gw.handleKeyEncrypt)
|
||||
mux.HandleFunc("POST /v1/keys/decrypt", gw.handleKeyDecrypt)
|
||||
mux.HandleFunc("GET /v1/keys/{id}", gw.handleKeyGet)
|
||||
|
||||
// DID operations
|
||||
mux.HandleFunc("POST /v1/did/create", gw.handleDIDCreate)
|
||||
mux.HandleFunc("GET /v1/did/{id}", gw.handleDIDResolve)
|
||||
mux.HandleFunc("POST /v1/did/{id}/verify", gw.handleDIDVerify)
|
||||
|
||||
// Credentials
|
||||
mux.HandleFunc("POST /v1/credentials/issue", gw.handleCredentialIssue)
|
||||
mux.HandleFunc("POST /v1/credentials/verify", gw.handleCredentialVerify)
|
||||
mux.HandleFunc("POST /v1/credentials/revoke", gw.handleCredentialRevoke)
|
||||
|
||||
// Health
|
||||
mux.HandleFunc("GET /health", gw.handleHealth)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: cfg.ListenAddr,
|
||||
Handler: mux,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Printf("auth-gateway: listening on %s (issuer=%s)", cfg.ListenAddr, cfg.Issuer)
|
||||
log.Printf("auth-gateway: I-Chain=%s K-Chain=%s T-Chain=%s", cfg.IChainRPC, cfg.KChainRPC, cfg.TChainRPC)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("auth-gateway: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigCh
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
srv.Shutdown(ctx)
|
||||
log.Println("auth-gateway: stopped")
|
||||
}
|
||||
|
||||
// Gateway translates web2 auth to on-chain operations.
|
||||
type Gateway struct {
|
||||
cfg Config
|
||||
}
|
||||
|
||||
func NewGateway(cfg Config) *Gateway {
|
||||
return &Gateway{cfg: cfg}
|
||||
}
|
||||
|
||||
// ─── OIDC Discovery ────────────────────────────────────────────────────────
|
||||
|
||||
func (g *Gateway) handleOIDCDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, 200, map[string]interface{}{
|
||||
"issuer": g.cfg.Issuer,
|
||||
"authorization_endpoint": g.cfg.Issuer + "/v1/auth/authorize",
|
||||
"token_endpoint": g.cfg.Issuer + "/v1/auth/token",
|
||||
"userinfo_endpoint": g.cfg.Issuer + "/v1/auth/userinfo",
|
||||
"jwks_uri": g.cfg.Issuer + "/.well-known/jwks.json",
|
||||
"registration_endpoint": g.cfg.Issuer + "/v1/auth/register",
|
||||
"scopes_supported": []string{"openid", "profile", "email", "did"},
|
||||
"response_types_supported": []string{"code", "token", "id_token"},
|
||||
"id_token_signing_alg_values_supported": []string{"EdDSA", "ES256K"},
|
||||
"subject_types_supported": []string{"public", "pairwise"},
|
||||
"grant_types_supported": []string{"authorization_code", "refresh_token", "client_credentials"},
|
||||
"token_endpoint_auth_methods_supported": []string{"client_secret_post", "none"},
|
||||
"claims_supported": []string{"sub", "iss", "aud", "exp", "iat", "did", "name", "email"},
|
||||
"backend": "zoo-auth-subnet",
|
||||
"chains": map[string]string{
|
||||
"identity": "I-Chain (identityvm)",
|
||||
"key": "K-Chain (keyvm)",
|
||||
"threshold": "T-Chain (thresholdvm)",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Gateway) handleJWKS(w http.ResponseWriter, r *http.Request) {
|
||||
// Fetch signing keys from T-Chain (threshold-signed, no single key)
|
||||
// T-Chain returns the group public key for the current signer set
|
||||
// TODO: call T-Chain RPC to get current group public keys
|
||||
writeJSON(w, 200, map[string]interface{}{
|
||||
"keys": []map[string]string{
|
||||
{
|
||||
"kty": "OKP",
|
||||
"crv": "Ed25519",
|
||||
"use": "sig",
|
||||
"kid": "t-chain-group-key-1",
|
||||
"x": "", // populated from T-Chain group public key
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Auth ────────────────────────────────────────────────────────────────────
|
||||
|
||||
func (g *Gateway) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, 400, "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Create DID on I-Chain
|
||||
// 2. Provision signing key on K-Chain (ML-KEM encrypted)
|
||||
// 3. Issue initial credential on I-Chain
|
||||
// 4. Sign JWT via T-Chain (threshold-signed)
|
||||
|
||||
// For now: return the flow structure
|
||||
writeJSON(w, 201, map[string]interface{}{
|
||||
"status": "registered",
|
||||
"did": fmt.Sprintf("did:lux:zoo:%s", req.Email), // placeholder
|
||||
"flow": []string{
|
||||
"1. DID created on I-Chain",
|
||||
"2. Signing key provisioned on K-Chain (ML-KEM-768)",
|
||||
"3. Credential issued on I-Chain",
|
||||
"4. JWT threshold-signed via T-Chain",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Gateway) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password,omitempty"`
|
||||
DID string `json:"did,omitempty"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, 400, "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Resolve DID on I-Chain
|
||||
// 2. Verify credential on I-Chain
|
||||
// 3. Sign JWT via T-Chain (no single signing key — threshold signed)
|
||||
// 4. Return standard OAuth2 token response
|
||||
|
||||
writeJSON(w, 200, map[string]interface{}{
|
||||
"access_token": "threshold-signed-jwt-placeholder",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "threshold-signed-refresh-placeholder",
|
||||
"id_token": "threshold-signed-id-token-placeholder",
|
||||
"did": fmt.Sprintf("did:lux:zoo:%s", req.Email),
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Gateway) handleRefresh(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "refreshed"}) }
|
||||
func (g *Gateway) handleLogout(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "logged_out"}) }
|
||||
func (g *Gateway) handleUserInfo(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"sub": "did:lux:zoo:user"}) }
|
||||
|
||||
// ─── WebAuthn ─────────────────────────────────────────────────────────────────
|
||||
|
||||
func (g *Gateway) handleWebAuthnRegisterBegin(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "begin"}) }
|
||||
func (g *Gateway) handleWebAuthnRegisterComplete(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "complete"}) }
|
||||
func (g *Gateway) handleWebAuthnLoginBegin(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "begin"}) }
|
||||
func (g *Gateway) handleWebAuthnLoginComplete(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "complete"}) }
|
||||
|
||||
// ─── Keys ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
func (g *Gateway) handleKeyCreate(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Type string `json:"type"` // ml-kem-768, ed25519, secp256k1
|
||||
Purpose string `json:"purpose"` // signing, encryption, auth
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, 400, "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
// K-Chain: create key with ML-KEM encryption, threshold-shared via T-Chain
|
||||
writeJSON(w, 201, map[string]interface{}{
|
||||
"key_id": "k-chain-key-placeholder",
|
||||
"type": req.Type,
|
||||
"purpose": req.Purpose,
|
||||
"encryption": "ml-kem-768",
|
||||
"storage": "K-Chain (on-chain, FHE-encrypted)",
|
||||
"signing": "T-Chain (threshold, no single key)",
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Gateway) handleKeySign(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "signed_via_t_chain"}) }
|
||||
func (g *Gateway) handleKeyEncrypt(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "encrypted_via_k_chain"}) }
|
||||
func (g *Gateway) handleKeyDecrypt(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "decrypted_via_k_chain"}) }
|
||||
func (g *Gateway) handleKeyGet(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"key_id": r.PathValue("id")}) }
|
||||
|
||||
// ─── DID ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
func (g *Gateway) handleDIDCreate(w http.ResponseWriter, r *http.Request) { writeJSON(w, 201, map[string]string{"did": "did:lux:zoo:new"}) }
|
||||
func (g *Gateway) handleDIDResolve(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"did": r.PathValue("id")}) }
|
||||
func (g *Gateway) handleDIDVerify(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"verified": "true"}) }
|
||||
|
||||
// ─── Credentials ──────────────────────────────────────────────────────────────
|
||||
|
||||
func (g *Gateway) handleCredentialIssue(w http.ResponseWriter, r *http.Request) { writeJSON(w, 201, map[string]string{"status": "issued"}) }
|
||||
func (g *Gateway) handleCredentialVerify(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"valid": "true"}) }
|
||||
func (g *Gateway) handleCredentialRevoke(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"revoked": "true"}) }
|
||||
|
||||
// ─── Health ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func (g *Gateway) handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, 200, map[string]interface{}{
|
||||
"status": "ok",
|
||||
"chains": map[string]string{
|
||||
"i-chain": "connected",
|
||||
"k-chain": "connected",
|
||||
"t-chain": "connected",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
func writeJSON(w http.ResponseWriter, code int, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, code int, msg string) {
|
||||
writeJSON(w, code, map[string]string{"error": msg})
|
||||
}
|
||||
|
||||
func envOr(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"subnet": {
|
||||
"name": "zoo-auth",
|
||||
"description": "Private Auth Subnet — IAM + KMS + MPC on-chain",
|
||||
"validators": 3,
|
||||
"threshold": 2
|
||||
},
|
||||
"chains": {
|
||||
"identity": {
|
||||
"vmID": "identityvm",
|
||||
"name": "I-Chain (Identity)",
|
||||
"genesis": {
|
||||
"version": 1,
|
||||
"timestamp": 1712448000,
|
||||
"message": "Zoo Auth Subnet — Decentralized Identity",
|
||||
"config": {
|
||||
"credentialTTL": 86400,
|
||||
"maxClaims": 100,
|
||||
"allowSelfIssue": false,
|
||||
"requireDIDAuth": true,
|
||||
"supportedMethods": ["did:lux", "did:web", "did:key"],
|
||||
"oidcIssuers": ["https://zoo.id", "https://hanzo.id", "https://lux.id"],
|
||||
"jwtSigningKeyType": "ed25519",
|
||||
"sessionTTL": 3600,
|
||||
"refreshTTL": 604800
|
||||
},
|
||||
"trustedIssuers": [
|
||||
{
|
||||
"did": "did:lux:zoo-foundation",
|
||||
"name": "Zoo Labs Foundation",
|
||||
"permissions": ["issue:*", "revoke:*", "admin:*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"key": {
|
||||
"vmID": "keyvm",
|
||||
"name": "K-Chain (Key Management)",
|
||||
"genesis": {
|
||||
"version": 1,
|
||||
"timestamp": 1712448000,
|
||||
"message": "Zoo Auth Subnet — Post-Quantum Key Management",
|
||||
"config": {
|
||||
"defaultKeyType": "ml-kem-768",
|
||||
"supportedKeyTypes": ["ml-kem-768", "ml-kem-1024", "x25519", "secp256k1", "ed25519", "bls12-381"],
|
||||
"keyShareThreshold": 2,
|
||||
"keyShareParties": 3,
|
||||
"encryptionScheme": "chacha20-poly1305",
|
||||
"kdfScheme": "argon2id",
|
||||
"fheEnabled": true,
|
||||
"fheScheme": "tfhe",
|
||||
"maxKeysPerIdentity": 100,
|
||||
"keyRotationPeriod": 2592000,
|
||||
"auditLogEnabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"threshold": {
|
||||
"vmID": "thresholdvm",
|
||||
"name": "T-Chain (Threshold Signing)",
|
||||
"genesis": {
|
||||
"version": 1,
|
||||
"timestamp": 1712448000,
|
||||
"message": "Zoo Auth Subnet — MPC Threshold Signing",
|
||||
"config": {
|
||||
"protocols": {
|
||||
"cggmp21": {"enabled": true, "curve": "secp256k1"},
|
||||
"frost": {"enabled": true, "curves": ["ed25519", "secp256k1"]},
|
||||
"bls": {"enabled": true, "curve": "bls12-381"},
|
||||
"corona": {"enabled": true, "type": "lattice"}
|
||||
},
|
||||
"defaultThreshold": 2,
|
||||
"defaultParties": 3,
|
||||
"maxSigners": 100,
|
||||
"sessionTimeout": 30,
|
||||
"quotaPerIdentity": 1000,
|
||||
"quotaResetPeriod": 3600,
|
||||
"requireIdentityAuth": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
# Zoo Auth Subnet — Private IAM/KMS/MPC on-chain
|
||||
#
|
||||
# 3 validator nodes running I-Chain + K-Chain + T-Chain
|
||||
# Auth gateway translates web2 auth (OAuth2/OIDC/WebAuthn) to on-chain ops
|
||||
# All communication via ZAP protocol (8.7µs latency)
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f compose.auth.yml up -d
|
||||
# curl http://localhost:8080/.well-known/openid-configuration
|
||||
# curl http://localhost:8080/health
|
||||
|
||||
services:
|
||||
# Auth Gateway — web2 → on-chain translation layer
|
||||
auth-gateway:
|
||||
image: ghcr.io/zooai/auth-gateway:0.1.0
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
AUTH_LISTEN: ":8080"
|
||||
ICHAIN_RPC: "http://node-1:9650/ext/bc/I"
|
||||
KCHAIN_RPC: "http://node-1:9650/ext/bc/K"
|
||||
TCHAIN_RPC: "http://node-1:9650/ext/bc/T"
|
||||
OIDC_ISSUER: "https://zoo.id"
|
||||
depends_on:
|
||||
node-1:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
|
||||
# Validator Node 1 (bootstrap)
|
||||
node-1:
|
||||
image: ghcr.io/luxfi/node:1.24.11
|
||||
environment:
|
||||
LUX_NETWORK_ID: "200200"
|
||||
LUX_HTTP_PORT: "9650"
|
||||
LUX_STAKING_PORT: "9651"
|
||||
LUX_LOG_LEVEL: "info"
|
||||
# Enable auth subnet VMs
|
||||
LUX_VM_ALIASES: '{"identityvm":"I","keyvm":"K","thresholdvm":"T"}'
|
||||
ports:
|
||||
- "9650:9650"
|
||||
- "9651:9651"
|
||||
volumes:
|
||||
- node-1-data:/data
|
||||
- ./chain-configs:/chain-configs:ro
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:9650/ext/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# Validator Node 2
|
||||
node-2:
|
||||
image: ghcr.io/luxfi/node:1.24.11
|
||||
environment:
|
||||
LUX_NETWORK_ID: "200200"
|
||||
LUX_BOOTSTRAP_IPS: "node-1:9651"
|
||||
LUX_HTTP_PORT: "9650"
|
||||
LUX_STAKING_PORT: "9651"
|
||||
LUX_VM_ALIASES: '{"identityvm":"I","keyvm":"K","thresholdvm":"T"}'
|
||||
volumes:
|
||||
- node-2-data:/data
|
||||
- ./chain-configs:/chain-configs:ro
|
||||
depends_on:
|
||||
node-1:
|
||||
condition: service_healthy
|
||||
|
||||
# Validator Node 3
|
||||
node-3:
|
||||
image: ghcr.io/luxfi/node:1.24.11
|
||||
environment:
|
||||
LUX_NETWORK_ID: "200200"
|
||||
LUX_BOOTSTRAP_IPS: "node-1:9651"
|
||||
LUX_HTTP_PORT: "9650"
|
||||
LUX_STAKING_PORT: "9651"
|
||||
LUX_VM_ALIASES: '{"identityvm":"I","keyvm":"K","thresholdvm":"T"}'
|
||||
volumes:
|
||||
- node-3-data:/data
|
||||
- ./chain-configs:/chain-configs:ro
|
||||
depends_on:
|
||||
node-1:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
node-1-data:
|
||||
node-2-data:
|
||||
node-3-data:
|
||||
+13
-7
@@ -1,5 +1,9 @@
|
||||
# Local dev only. CI deploys use kustomize overlays under k8s/.
|
||||
# Image tags pin sha-based immutable refs; semver bumps go through
|
||||
# manifests/services.yml + Renovate.
|
||||
|
||||
services:
|
||||
# Websites
|
||||
# Marketing site — live source mount for hot reload.
|
||||
ngo:
|
||||
build:
|
||||
context: ../ngo/apps/web
|
||||
@@ -7,25 +11,27 @@ services:
|
||||
volumes: ["../ngo/apps/web/src:/app/src"]
|
||||
command: npx vite --host
|
||||
|
||||
# Chain infrastructure (use images from GHCR)
|
||||
# Chain infrastructure (devnet via Lux RPC). Override ZOO_NODE_TAG /
|
||||
# ZOO_EXPLORER_TAG / ZOO_BRIDGE_TAG to swap to a different sha.
|
||||
node:
|
||||
image: ghcr.io/zooai/node:latest
|
||||
image: "ghcr.io/zooai/node:${ZOO_NODE_TAG:-main-amd64}"
|
||||
ports:
|
||||
- "9550:9550" # HTTP API
|
||||
- "9552:9552" # P2P
|
||||
environment:
|
||||
- ZOO_NETWORK=devnet
|
||||
- ZOO_CHAIN_ID=200202
|
||||
# EVM chainID — disambiguated from Lux primary networkID.
|
||||
- ZOO_EVM_CHAIN_ID=200202
|
||||
|
||||
explorer:
|
||||
image: ghcr.io/zooai/explorer:latest
|
||||
image: "ghcr.io/zooai/explorer:${ZOO_EXPLORER_TAG:-main-amd64}"
|
||||
ports: ["4000:4000"]
|
||||
environment:
|
||||
- RPC_URL=http://node:9550
|
||||
|
||||
bridge:
|
||||
image: ghcr.io/zooai/bridge:latest
|
||||
image: "ghcr.io/zooai/bridge:${ZOO_BRIDGE_TAG:-main-amd64}"
|
||||
ports: ["4001:4001"]
|
||||
environment:
|
||||
- ZOO_RPC=http://node:9550
|
||||
- LUX_RPC=https://api.lux.network/mainnet/ext/bc/C/rpc
|
||||
- LUX_RPC=https://api.lux.network/ext/bc/C/rpc
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Convert cloned repos to git submodules for proper tracking
|
||||
|
||||
set -e
|
||||
|
||||
cd ~/work/zoo/universe
|
||||
|
||||
echo "🔄 Converting Zoo Universe to use git submodules..."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Define all zoo apps
|
||||
APPS=(
|
||||
"zoo.ngo"
|
||||
"zoo.fund"
|
||||
"zoo.exchange"
|
||||
"zoo.network"
|
||||
"zoo.computer"
|
||||
"zoo.lab"
|
||||
"zoo.vote"
|
||||
"zoo.foundation"
|
||||
)
|
||||
|
||||
# Remove existing clones and add as submodules
|
||||
for app in "${APPS[@]}"; do
|
||||
if [ -d "$app" ]; then
|
||||
echo "🔄 Converting $app to submodule..."
|
||||
|
||||
# Remove the directory
|
||||
rm -rf "$app"
|
||||
|
||||
# Add as submodule
|
||||
git submodule add "[email protected]:zoo-apps/$app.git" "$app"
|
||||
|
||||
echo "✅ $app added as submodule"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ All apps converted to submodules!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "Git submodules are now tracking:"
|
||||
git submodule status
|
||||
@@ -1,41 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-shot deploy of a Zoo overlay to the canonical cluster.
|
||||
#
|
||||
# Cluster: do-sfo3-zoo-k8s (single DOKS cluster, separate namespaces per env).
|
||||
# Overlays: k8s/zoo-{system,devnet,testnet,mainnet}/
|
||||
#
|
||||
# Usage:
|
||||
# ./deploy.sh dev # → zoo-devnet
|
||||
# ./deploy.sh test # → zoo-testnet
|
||||
# ./deploy.sh main # → zoo-mainnet
|
||||
# ./deploy.sh system # → zoo-system (operator only)
|
||||
# ./deploy.sh all # All four overlays sequentially
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
ENV=${1:-devnet}
|
||||
CTX=do-sfo3-zoo-k8s
|
||||
ENV="${1:?Usage: $0 {system|dev|test|main|all}}"
|
||||
|
||||
# Each environment deploys to its own DOKS cluster
|
||||
ctx_for_env() {
|
||||
case $1 in
|
||||
system) echo "do-sfo3-zoo-k8s" ;;
|
||||
devnet) echo "do-sfo3-zoo-dev-k8s" ;;
|
||||
testnet) echo "do-sfo3-zoo-test-k8s" ;;
|
||||
mainnet) echo "do-sfo3-zoo-k8s" ;;
|
||||
*) echo "do-sfo3-zoo-k8s" ;;
|
||||
apply_overlay() {
|
||||
local env="$1"
|
||||
local ns overlay
|
||||
case "$env" in
|
||||
system) ns=zoo-system ; overlay=zoo-system ;;
|
||||
dev) ns=zoo-devnet ; overlay=zoo-devnet ;;
|
||||
test) ns=zoo-testnet ; overlay=zoo-testnet ;;
|
||||
main) ns=zoo-mainnet ; overlay=zoo-mainnet ;;
|
||||
*) echo "::error::Unknown env '$env'"; return 1 ;;
|
||||
esac
|
||||
|
||||
echo "==> Deploying $env → $ns (context: $CTX)"
|
||||
kubectl --context "$CTX" apply -f k8s/crds/
|
||||
kubectl --context "$CTX" apply -k "k8s/$overlay/"
|
||||
echo "==> Done. Verify: kubectl --context $CTX -n $ns get pods"
|
||||
}
|
||||
|
||||
case $ENV in
|
||||
system) NS=zoo-system ;;
|
||||
devnet) NS=zoo-devnet ;;
|
||||
testnet) NS=zoo-testnet ;;
|
||||
mainnet) NS=zoo-mainnet ;;
|
||||
all)
|
||||
for e in system devnet testnet mainnet; do
|
||||
CTX=$(ctx_for_env "$e")
|
||||
echo "--- $e -> $CTX ---"
|
||||
kubectl --context "$CTX" apply -f k8s/crds/
|
||||
kubectl --context "$CTX" apply -k "k8s/zoo-$e/"
|
||||
done
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Usage: $0 {system|devnet|testnet|mainnet|all}"; exit 1 ;;
|
||||
esac
|
||||
|
||||
CTX=$(ctx_for_env "$ENV")
|
||||
echo "Deploying Zoo $ENV to $CTX (namespace: $NS)..."
|
||||
kubectl --context "$CTX" apply -f k8s/crds/
|
||||
kubectl --context "$CTX" apply -k "k8s/zoo-$ENV/"
|
||||
echo "Done. Check: kubectl --context $CTX -n $NS get pods"
|
||||
if [[ "$ENV" == "all" ]]; then
|
||||
for e in system dev test main; do
|
||||
apply_overlay "$e"
|
||||
done
|
||||
else
|
||||
apply_overlay "$ENV"
|
||||
fi
|
||||
|
||||
+10
-58
@@ -1,63 +1,15 @@
|
||||
# Zoo ARC Runners — RETIRED
|
||||
|
||||
> **Superseded by host-level `arcd` daemons** on dbc + evo.
|
||||
> Canonical fleet doc: <https://github.com/hanzoai/universe/blob/main/infra/runners/README.md>.
|
||||
>
|
||||
> arcd serves `zooai` alongside `hanzoai`, `luxfi`, `parsdao`, `zenlm`
|
||||
> from one daemon per OS target. No K8s scale-set, no per-org helm
|
||||
> release, no per-org GitHub App needed on day one (PATs work).
|
||||
>
|
||||
> The DOKS scale-sets below are drained (`minRunners=maxRunners=0`) and
|
||||
> pending `helm uninstall`. Documented for historical reference + rollback.
|
||||
Superseded by host-level `arcd` daemons on dbc + evo + spark. Canonical
|
||||
fleet documentation: <https://github.com/hanzoai/.github/blob/main/RUNNERS.md>.
|
||||
|
||||
---
|
||||
`arcd` serves `zooai` alongside `hanzoai`, `luxfi`, `parsdao`, `zenlm`
|
||||
from one daemon per (host, OS, arch). No K8s scale-set, no per-org helm
|
||||
release, no per-org GitHub App required.
|
||||
|
||||
GitHub Actions Runner Controller (ARC) scale sets for the `zooai` org.
|
||||
Same DOKS/GKE clusters as hanzoai runners, separate GitHub App registration.
|
||||
The DOKS Helm values files that used to live here (`values-build-amd64.yaml`,
|
||||
`values-build-arm64.yaml`) were removed once the scale-sets were drained
|
||||
and `helm uninstall`ed. Recover from git history if rollback is needed.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. ARC controller already installed in `arc-system` namespace (shared with hanzoai)
|
||||
2. GitHub App created for `zooai` org with Actions runner registration permissions
|
||||
3. App credentials stored as K8s secrets in `actions-runner-system` namespace
|
||||
|
||||
## Create GitHub App secrets
|
||||
|
||||
```bash
|
||||
# amd64 (DOKS — hanzo-k8s)
|
||||
kubectl create secret generic zoo-build-linux-amd64-gha-rs-github-secret \
|
||||
-n actions-runner-system \
|
||||
--from-literal=github_app_id=<APP_ID> \
|
||||
--from-literal=github_app_installation_id=<INSTALL_ID> \
|
||||
--from-literal=github_app_private_key="$(cat zoo-runner-app.pem)"
|
||||
|
||||
# arm64 (GKE — apps-gke)
|
||||
kubectl create secret generic zoo-build-linux-arm64-gha-rs-github-secret \
|
||||
-n actions-runner-system \
|
||||
--from-literal=github_app_id=<APP_ID> \
|
||||
--from-literal=github_app_installation_id=<INSTALL_ID> \
|
||||
--from-literal=github_app_private_key="$(cat zoo-runner-app.pem)"
|
||||
```
|
||||
|
||||
## Install runner scale sets
|
||||
|
||||
```bash
|
||||
# amd64 on DOKS (hanzo-k8s context)
|
||||
helm upgrade --install zoo-build-linux-amd64 \
|
||||
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
|
||||
-n actions-runner-system \
|
||||
-f values-build-amd64.yaml
|
||||
|
||||
# arm64 on GKE (apps-gke context)
|
||||
helm upgrade --install zoo-build-linux-arm64 \
|
||||
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
|
||||
-n actions-runner-system \
|
||||
-f values-build-arm64.yaml
|
||||
```
|
||||
|
||||
## Runner labels
|
||||
|
||||
| Scale Set | Cluster | Labels |
|
||||
|-----------|---------|--------|
|
||||
| zoo-build-linux-amd64 | hanzo-k8s (DOKS) | `zoo-build-linux-amd64,self-hosted,Linux,X64` |
|
||||
| zoo-build-linux-arm64 | apps-gke (GKE) | `zoo-build-linux-arm64,self-hosted,Linux,ARM64` |
|
||||
Zoo workflows now `runs-on: zoo-build-linux-amd64` and route to the
|
||||
canonical pool registered against `https://github.com/zooai`.
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
githubConfigUrl: https://github.com/zooai
|
||||
githubConfigSecret: zoo-build-linux-amd64-gha-rs-github-secret
|
||||
runnerScaleSetName: zoo-build-linux-amd64
|
||||
minRunners: 1
|
||||
maxRunners: 10
|
||||
runnerGroup: Default
|
||||
containerMode:
|
||||
type: dind
|
||||
template:
|
||||
spec:
|
||||
nodeSelector:
|
||||
role: runner
|
||||
tolerations:
|
||||
- key: role
|
||||
operator: Equal
|
||||
value: runner
|
||||
effect: NoSchedule
|
||||
- key: dedicated
|
||||
operator: Equal
|
||||
value: ci-runner
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: runner
|
||||
image: ghcr.io/actions/actions-runner:latest
|
||||
command: ["/home/runner/run.sh"]
|
||||
env:
|
||||
- name: ACTIONS_RUNNER_LABELS
|
||||
value: "zoo-build-linux-amd64,self-hosted,Linux,X64"
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1001
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
@@ -1,36 +0,0 @@
|
||||
githubConfigUrl: https://github.com/zooai
|
||||
githubConfigSecret: zoo-build-linux-arm64-gha-rs-github-secret
|
||||
runnerScaleSetName: zoo-build-linux-arm64
|
||||
minRunners: 1
|
||||
maxRunners: 10
|
||||
runnerGroup: Default
|
||||
containerMode:
|
||||
type: dind
|
||||
template:
|
||||
spec:
|
||||
nodeSelector:
|
||||
arch: arm64
|
||||
tolerations:
|
||||
- key: role
|
||||
operator: Equal
|
||||
value: runner
|
||||
effect: NoSchedule
|
||||
- key: kubernetes.io/arch
|
||||
operator: Equal
|
||||
value: arm64
|
||||
effect: NoSchedule
|
||||
- key: workload
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: runner
|
||||
image: ghcr.io/actions/actions-runner:latest
|
||||
command: ["/home/runner/run.sh"]
|
||||
env:
|
||||
- name: ACTIONS_RUNNER_LABELS
|
||||
value: "zoo-build-linux-arm64,self-hosted,Linux,ARM64"
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1001
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
+27
-13
@@ -1,6 +1,18 @@
|
||||
# Zoo Network: Full Chain Architecture
|
||||
# Zoo Network: L2 on Lux + Brand Services
|
||||
|
||||
Kitchen-sink Web5 pilot running every chain and capability on a single GPU-accelerated validator set.
|
||||
Zoo is a brand L2 on Lux primary network (shares validators via primary-network
|
||||
staking; see `~/work/zoo/universe/chain.yaml`). The Zoo L2 EVM lives on
|
||||
`api.lux.network/ext/bc/zoo/rpc` (chainId 200200 mainnet, 200201 testnet,
|
||||
200202 devnet). The manifests in this directory describe Zoo's *brand*
|
||||
services — bridge, explorer, gateway, marketing sites — that run alongside
|
||||
the L2 in a `do-sfo3-zoo-k8s` cluster. Validators themselves run on Lux
|
||||
clusters.
|
||||
|
||||
The legacy "kitchen-sink 15-chain ZooNetwork CR" in `base/network.yaml` and
|
||||
the chain-native crypto layers in `base/beluga.yaml` describe an unrealized
|
||||
sovereign-Zoo-cluster architecture. They are kept as design artifacts; the
|
||||
operator does not currently reconcile them in production (zoo-operator
|
||||
deployments sit at 0/1, ZooNetwork CRs unreconciled).
|
||||
|
||||
## Cluster: do-sfo3-zoo-k8s (DOKS SFO3)
|
||||
|
||||
@@ -154,10 +166,10 @@ All chains exposed via ZooGateway at `api.zoo.ngo`:
|
||||
|---------|------|--------------------------|-------|
|
||||
| zoo-operator | Deployment | 1 (system only) | ghcr.io/zooai/operator |
|
||||
| zoo-chain | ZooNetwork CR | 1/3/5 (GPU) | ghcr.io/zooai/zoo-node |
|
||||
| zoo-bridge | Deployment | 1/1/2 | ghcr.io/zooai/zoo-bridge |
|
||||
| zoo-bridge | Deployment | 1/1/1 | ghcr.io/zooai/zoo-bridge (SQLite + Litestream) |
|
||||
| zoo-explorer | Deployment | 1/1/2 | ghcr.io/luxfi/explorer |
|
||||
| sql | StatefulSet | 1/1/1 | ghcr.io/hanzoai/sql:18 |
|
||||
| kv | StatefulSet | 1/1/1 | ghcr.io/hanzoai/kv:9 |
|
||||
| s3 | StatefulSet | 1/1/1 | ghcr.io/hanzoai/s3 — Litestream backend |
|
||||
| kv | StatefulSet | 1/1/1 | ghcr.io/hanzoai/kv:9 — ephemeral cache |
|
||||
| zoo-ingress | Deployment | 1/1/2 | ghcr.io/hanzoai/ingress |
|
||||
| zoo-gateway | ZooGateway CR | 1/1/1 | ghcr.io/hanzoai/gateway |
|
||||
|
||||
@@ -173,8 +185,9 @@ k8s/
|
||||
network.yaml # ZooNetwork CR (15 VMs, GPU-accelerated)
|
||||
beluga.yaml # Beluga L3 AI app-chain
|
||||
blg-token.yaml # BLG token CRD (gas on Beluga, bridgeable to Zoo EVM)
|
||||
sql.yaml # PostgreSQL (hanzoai/sql:18)
|
||||
kv.yaml # Valkey (hanzoai/kv:9)
|
||||
s3.yaml # MinIO (hanzoai/s3) — backs Litestream replicas
|
||||
replicate.yaml # Litestream config for SQLite WAL → S3
|
||||
kv.yaml # Valkey (hanzoai/kv:9) — ephemeral cache only
|
||||
bridge.yaml # Cross-chain bridge (uses M-Chain signing)
|
||||
explorer.yaml # Lux explorer (Go binary)
|
||||
gateway.yaml # ZooGateway (routes to all 15 chain RPCs + Beluga)
|
||||
@@ -189,12 +202,13 @@ k8s/
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Secrets provisioned via K-Chain (on-chain KMS), bootstrapped from kms.hanzo.ai:
|
||||
- `zoo-db-credentials` -- postgres-password, bridge-url, explorer-url, kv-password
|
||||
- `zoo-genesis` -- genesis.json for all chains (including Beluga)
|
||||
- `zoo-staking-keys` -- validator staking TLS certs
|
||||
- `cloudflare-api-credentials` -- ACME DNS challenge
|
||||
- `ghcr-secret` -- container registry pull credentials
|
||||
Secrets provisioned via KMSSecret CRs synced from `kms.hanzo.ai`:
|
||||
- `s3-credentials` — MinIO ACCESS_KEY / SECRET_KEY (Litestream auth)
|
||||
- `zoo-kv-credentials` — Valkey password (`password` key)
|
||||
- `zoo-genesis` — genesis.json for any locally-bootstrapped chains
|
||||
- `zoo-staking-keys` — validator staking TLS certs (if local validators)
|
||||
- `cloudflare-api-credentials` — ACME DNS challenge
|
||||
- `ghcr-secret` — container registry pull credentials
|
||||
|
||||
GPU nodes: DOKS GPU node pool with NVIDIA A100/H100 or equivalent.
|
||||
|
||||
|
||||
+96
-10
@@ -1,5 +1,23 @@
|
||||
# Bridge — cross-chain transfers using chain-native T-Chain (threshold signing)
|
||||
# No external MPC service needed — validators ARE the MPC signers
|
||||
# Bridge — cross-chain transfers using chain-native T-Chain (threshold signing).
|
||||
# No external MPC sidecar — validators ARE the MPC signers.
|
||||
#
|
||||
# Storage: Base/SQLite at /app/data/data.db. Litestream sidecar replicates
|
||||
# the WAL to s3.<ns>.svc:9000 (MinIO) on a 1s sync interval. replicas=1 —
|
||||
# SQLite does not support concurrent writers from multiple pods.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: zoo-bridge-data
|
||||
labels:
|
||||
app: zoo-bridge
|
||||
app.kubernetes.io/part-of: zoo
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
storageClassName: do-block-storage
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -9,10 +27,13 @@ metadata:
|
||||
app.kubernetes.io/name: zoo-bridge
|
||||
app.kubernetes.io/part-of: zoo
|
||||
spec:
|
||||
replicas: 2
|
||||
# SQLite-backed; single writer.
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: zoo-bridge
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
@@ -21,12 +42,38 @@ spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-secret
|
||||
- name: hanzo
|
||||
initContainers:
|
||||
# Restore /app/data/data.db from S3 on cold start. Idempotent.
|
||||
- name: replicate-restore
|
||||
image: ghcr.io/hanzoai/replicate:0.6.0
|
||||
args: ["restore", "-if-db-not-exists", "-if-replica-exists", "/app/data/data.db"]
|
||||
env:
|
||||
- name: LITESTREAM_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: s3-credentials
|
||||
key: ACCESS_KEY
|
||||
- name: LITESTREAM_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: s3-credentials
|
||||
key: SECRET_KEY
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /app/data
|
||||
- name: replicate-config
|
||||
mountPath: /etc/litestream.yml
|
||||
subPath: litestream.yml
|
||||
containers:
|
||||
- name: bridge
|
||||
# TODO: pin semver once zooai/bridge CI publishes vX.Y.Z tags.
|
||||
# Today GHCR only has :main + :latest (the v1.0.6 git tag never
|
||||
# produced an image tag — CI only writes :main). Pinning :main.
|
||||
image: ghcr.io/zooai/bridge:main
|
||||
# main-amd64 is the immutable per-arch tag from the shared
|
||||
# hanzoai/.github docker-build workflow.
|
||||
image: ghcr.io/zooai/bridge:main-amd64
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
@@ -34,11 +81,9 @@ spec:
|
||||
env:
|
||||
- name: BRIDGE_TENANT
|
||||
value: zoo
|
||||
# SQLite file (Base). DATABASE_URL retained for app compatibility.
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: zoo-db-credentials
|
||||
key: bridge-url
|
||||
value: sqlite:///app/data/data.db
|
||||
- name: ZOO_RPC_URL
|
||||
value: http://zoo-rpc:9650/ext/bc/zoo/rpc
|
||||
- name: LUX_RPC_URL
|
||||
@@ -55,6 +100,9 @@ spec:
|
||||
# Chain-native key management via K-Chain RPC (replaces external KMS)
|
||||
- name: KEY_RPC_URL
|
||||
value: http://zoo-rpc:9650/ext/bc/zoo-keys/rpc
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /app/data
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
@@ -74,6 +122,44 @@ spec:
|
||||
port: 3000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
# Litestream sidecar — continuous WAL replication to MinIO.
|
||||
- name: replicate
|
||||
image: ghcr.io/hanzoai/replicate:0.6.0
|
||||
args: ["replicate"]
|
||||
env:
|
||||
- name: LITESTREAM_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: s3-credentials
|
||||
key: ACCESS_KEY
|
||||
- name: LITESTREAM_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: s3-credentials
|
||||
key: SECRET_KEY
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /app/data
|
||||
- name: replicate-config
|
||||
mountPath: /etc/litestream.yml
|
||||
subPath: litestream.yml
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 16Mi
|
||||
limits:
|
||||
memory: 64Mi
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: zoo-bridge-data
|
||||
- name: replicate-config
|
||||
configMap:
|
||||
name: replicate-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
||||
+3
-1
@@ -16,7 +16,9 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: docs
|
||||
image: ghcr.io/zooai/docs:latest
|
||||
# TODO: pin semver once zooai/docs CI publishes vX.Y.Z tags.
|
||||
image: ghcr.io/zooai/docs:main-amd64
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
|
||||
@@ -9,8 +9,12 @@ resources:
|
||||
- network.yaml
|
||||
- beluga.yaml
|
||||
- blg-token.yaml
|
||||
- sql.yaml
|
||||
- kv.yaml
|
||||
# No Postgres. Stateful services use Base/SQLite with Litestream
|
||||
# continuous replication to S3 (zoo-data bucket). See s3.yaml +
|
||||
# replicate.yaml + bridge.yaml's PVC.
|
||||
- s3.yaml # hanzoai/s3 (MinIO) — backs SQLite WAL replicas
|
||||
- replicate.yaml # Litestream config shared by all SQLite consumers
|
||||
- kv.yaml # Valkey — ephemeral cache, not a primary store
|
||||
# mpc.yaml removed — threshold signing is chain-native via T-Chain
|
||||
- bridge.yaml
|
||||
- explorer.yaml
|
||||
|
||||
+2
-2
@@ -50,8 +50,8 @@ spec:
|
||||
- name: KV_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: zoo-db-credentials
|
||||
key: kv-password
|
||||
name: zoo-kv-credentials
|
||||
key: password
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
name: kv
|
||||
|
||||
+5
-1
@@ -16,7 +16,11 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: ngo
|
||||
image: ghcr.io/zooai/ngo:latest
|
||||
# TODO: pin semver once zooai/ngo CI publishes vX.Y.Z tags. Until
|
||||
# then use the immutable arch-specific branch tag from the shared
|
||||
# hanzoai/.github docker-build workflow.
|
||||
image: ghcr.io/zooai/ngo:main-amd64
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
|
||||
@@ -24,10 +24,9 @@ spec:
|
||||
containers:
|
||||
- name: operator
|
||||
# TODO: pin semver once zooai/operator CI publishes vX.Y.Z tags.
|
||||
# Today GHCR only has :main + :latest. :main is the immutable
|
||||
# branch tag CI pushes per build (per-arch manifests under main-amd64/main-arm64),
|
||||
# which is the closest thing to a semver pin we have.
|
||||
image: ghcr.io/zooai/operator:main
|
||||
# main-amd64 is the immutable per-arch tag from the shared
|
||||
# hanzoai/.github docker-build workflow.
|
||||
image: ghcr.io/zooai/operator:main-amd64
|
||||
imagePullPolicy: Always
|
||||
args: ["--namespace", ""]
|
||||
ports:
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Litestream config — continuous encrypted SQLite replication to S3.
|
||||
# Shared by every Zoo service using Base/SQLite. Each pod's replica path
|
||||
# is namespaced by POD_NAME so replicas of the same Deployment never
|
||||
# collide. Endpoint resolves to the in-namespace s3 service so dev/test/
|
||||
# main each replicate to their own MinIO instance.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: replicate-config
|
||||
labels:
|
||||
app.kubernetes.io/part-of: zoo
|
||||
data:
|
||||
litestream.yml: |
|
||||
dbs:
|
||||
- path: /app/data/data.db
|
||||
replicas:
|
||||
- type: s3
|
||||
bucket: zoo-data
|
||||
path: ${POD_NAME}
|
||||
endpoint: ${LITESTREAM_S3_ENDPOINT:-http://s3:9000}
|
||||
region: sfo3
|
||||
force-path-style: true
|
||||
sync-interval: 1s
|
||||
snapshot-interval: 1h
|
||||
@@ -1,11 +1,11 @@
|
||||
# S3-compatible object storage for Zoo replicate sidecars
|
||||
# Uses hanzoai/s3 (MinIO). Backed by DigitalOcean block storage.
|
||||
# S3-compatible object storage backing all SQLite WAL replicas (Litestream).
|
||||
# Uses hanzoai/s3 (MinIO). One bucket per namespace overlay (zoo-data).
|
||||
# Pair with: replicate.yaml (Litestream config) + bridge.yaml sidecar.
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: s3
|
||||
namespace: zoo
|
||||
labels:
|
||||
app: s3
|
||||
app.kubernetes.io/name: s3
|
||||
@@ -23,7 +23,6 @@ apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: s3
|
||||
namespace: zoo
|
||||
labels:
|
||||
app: s3
|
||||
app.kubernetes.io/name: s3
|
||||
@@ -1,98 +0,0 @@
|
||||
# PostgreSQL for Zoo services (MPC + explorer + bridge)
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: sql
|
||||
labels:
|
||||
app: sql
|
||||
app.kubernetes.io/name: sql
|
||||
app.kubernetes.io/part-of: zoo
|
||||
spec:
|
||||
ports:
|
||||
- port: 5432
|
||||
targetPort: 5432
|
||||
name: postgres
|
||||
selector:
|
||||
app: sql
|
||||
clusterIP: None
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: sql
|
||||
labels:
|
||||
app: sql
|
||||
app.kubernetes.io/name: sql
|
||||
app.kubernetes.io/part-of: zoo
|
||||
spec:
|
||||
serviceName: sql
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: sql
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: sql
|
||||
app.kubernetes.io/name: sql
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-secret
|
||||
- name: hanzo
|
||||
containers:
|
||||
- name: sql
|
||||
image: ghcr.io/hanzoai/sql:18
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
name: postgres
|
||||
env:
|
||||
- name: POSTGRES_USER
|
||||
value: zoo
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: zoo-db-credentials
|
||||
key: postgres-password
|
||||
- name: POSTGRES_DB
|
||||
value: zoo
|
||||
- name: PGDATA
|
||||
value: /var/lib/postgresql/data/pgdata
|
||||
volumeMounts:
|
||||
- name: sql-data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- pg_isready -U zoo -d zoo
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- pg_isready -U zoo -d zoo
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
persistentVolumeClaimRetentionPolicy:
|
||||
whenDeleted: Retain
|
||||
whenScaled: Retain
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: sql-data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
storageClassName: do-block-storage
|
||||
resources:
|
||||
requests:
|
||||
storage: 20Gi
|
||||
+3
-1
@@ -16,7 +16,9 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: zips
|
||||
image: ghcr.io/zooai/zips:latest
|
||||
# TODO: pin semver once zooai/zips CI publishes vX.Y.Z tags.
|
||||
image: ghcr.io/zooai/zips:main-amd64
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
# Replicate sidecar for zoo-bridge Deployment
|
||||
# Bridge currently uses PostgreSQL but is migrating to Base/SQLite.
|
||||
# This patch adds:
|
||||
# 1. A PVC for bridge data
|
||||
# 2. init container: restore SQLite from S3 on cold start
|
||||
# 3. sidecar: continuous WAL replication to S3
|
||||
# 4. data volume mount on the bridge container
|
||||
#
|
||||
# IMPORTANT: When using SQLite, replicas must be set to 1.
|
||||
# SQLite does not support concurrent writers from multiple pods.
|
||||
#
|
||||
# Apply with:
|
||||
# kubectl apply -f k8s/patches/replicate-bridge.yaml
|
||||
# kubectl -n zoo rollout restart deploy/zoo-bridge
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: zoo-bridge-data
|
||||
namespace: zoo
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
storageClassName: do-block-storage
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: zoo-bridge
|
||||
namespace: zoo
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- name: replicate-restore
|
||||
image: ghcr.io/hanzoai/replicate:0.6.0
|
||||
args: ["restore", "-if-db-not-exists", "-if-replica-exists", "/app/data/data.db"]
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /app/data
|
||||
- name: replicate-config
|
||||
mountPath: /etc/litestream.yml
|
||||
subPath: litestream.yml
|
||||
env:
|
||||
- name: LITESTREAM_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: s3-credentials
|
||||
key: ACCESS_KEY
|
||||
- name: LITESTREAM_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: s3-credentials
|
||||
key: SECRET_KEY
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
containers:
|
||||
- name: bridge
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /app/data
|
||||
- name: replicate
|
||||
image: ghcr.io/hanzoai/replicate:0.6.0
|
||||
args: ["replicate"]
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /app/data
|
||||
- name: replicate-config
|
||||
mountPath: /etc/litestream.yml
|
||||
subPath: litestream.yml
|
||||
env:
|
||||
- name: LITESTREAM_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: s3-credentials
|
||||
key: ACCESS_KEY
|
||||
- name: LITESTREAM_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: s3-credentials
|
||||
key: SECRET_KEY
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
resources:
|
||||
requests:
|
||||
memory: 16Mi
|
||||
cpu: 10m
|
||||
limits:
|
||||
memory: 64Mi
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: zoo-bridge-data
|
||||
- name: replicate-config
|
||||
configMap:
|
||||
name: replicate-config
|
||||
@@ -1,25 +0,0 @@
|
||||
# Replicate ConfigMap — continuous encrypted SQLite replication to S3
|
||||
# Shared by all Zoo services that use Base/SQLite.
|
||||
# Applied separately from service patches so it exists before any sidecar starts.
|
||||
#
|
||||
# Each pod's replica path is namespaced by POD_NAME (injected via fieldRef),
|
||||
# so zoo-bridge-0 and zoo-bridge-1 replicate to distinct S3 prefixes.
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: replicate-config
|
||||
namespace: zoo
|
||||
data:
|
||||
litestream.yml: |
|
||||
dbs:
|
||||
- path: /app/data/data.db
|
||||
replicas:
|
||||
- type: s3
|
||||
bucket: zoo-data
|
||||
path: ${POD_NAME}
|
||||
endpoint: ${LITESTREAM_S3_ENDPOINT:-http://s3.zoo.svc.cluster.local:9000}
|
||||
region: sfo3
|
||||
force-path-style: true
|
||||
sync-interval: 1s
|
||||
snapshot-interval: 1h
|
||||
@@ -1,83 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: auth-gateway
|
||||
namespace: zoo-auth
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: auth-gateway
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: auth-gateway
|
||||
spec:
|
||||
containers:
|
||||
- name: auth-gateway
|
||||
image: ghcr.io/zooai/auth-gateway:0.1.0
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
env:
|
||||
- name: AUTH_LISTEN
|
||||
value: ":8080"
|
||||
- name: ICHAIN_RPC
|
||||
value: "http://auth-validator-0.auth-validator.zoo-auth.svc.cluster.local:9650/ext/bc/I"
|
||||
- name: KCHAIN_RPC
|
||||
value: "http://auth-validator-0.auth-validator.zoo-auth.svc.cluster.local:9650/ext/bc/K"
|
||||
- name: TCHAIN_RPC
|
||||
value: "http://auth-validator-0.auth-validator.zoo-auth.svc.cluster.local:9650/ext/bc/T"
|
||||
- name: OIDC_ISSUER
|
||||
value: "https://zoo.id"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: auth-gateway
|
||||
namespace: zoo-auth
|
||||
spec:
|
||||
selector:
|
||||
app: auth-gateway
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
name: http
|
||||
---
|
||||
apiVersion: hanzo.ai/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: auth-gateway
|
||||
namespace: zoo-auth
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- kind: Rule
|
||||
match: Host(`zoo.id`)
|
||||
services:
|
||||
- name: auth-gateway
|
||||
port: 8080
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
domains:
|
||||
- main: zoo.id
|
||||
@@ -1,9 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: zoo-auth
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- auth-gateway.yaml
|
||||
- validators.yaml
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: zoo-auth
|
||||
labels:
|
||||
app: zoo-auth
|
||||
subnet: auth
|
||||
@@ -1,87 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: auth-validator
|
||||
namespace: zoo-auth
|
||||
spec:
|
||||
serviceName: auth-validator
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: auth-validator
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: auth-validator
|
||||
spec:
|
||||
containers:
|
||||
- name: node
|
||||
image: ghcr.io/luxfi/node:1.24.11
|
||||
ports:
|
||||
- containerPort: 9650
|
||||
name: http
|
||||
- containerPort: 9651
|
||||
name: staking
|
||||
env:
|
||||
- name: LUX_NETWORK_ID
|
||||
value: "200200"
|
||||
- name: LUX_HTTP_PORT
|
||||
value: "9650"
|
||||
- name: LUX_STAKING_PORT
|
||||
value: "9651"
|
||||
- name: LUX_LOG_LEVEL
|
||||
value: "info"
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: chain-configs
|
||||
mountPath: /chain-configs
|
||||
readOnly: true
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ext/health
|
||||
port: 9650
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 15
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ext/health
|
||||
port: 9650
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: 2
|
||||
memory: 4Gi
|
||||
volumes:
|
||||
- name: chain-configs
|
||||
configMap:
|
||||
name: auth-chain-configs
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: auth-validator
|
||||
namespace: zoo-auth
|
||||
spec:
|
||||
clusterIP: None
|
||||
selector:
|
||||
app: auth-validator
|
||||
ports:
|
||||
- port: 9650
|
||||
targetPort: 9650
|
||||
name: http
|
||||
- port: 9651
|
||||
targetPort: 9651
|
||||
name: staking
|
||||
@@ -14,8 +14,10 @@ labels:
|
||||
configMapGenerator:
|
||||
- name: zoo-config
|
||||
literals:
|
||||
- ZOO_NETWORK_ID=3
|
||||
- ZOO_CHAIN_ID=200202
|
||||
# Lux primary networkID (3 = devnet) — shared across brand L1/L2s.
|
||||
- LUX_NETWORK_ID=3
|
||||
# Zoo EVM chainID — per-env, baked into the L2 genesis blob.
|
||||
- ZOO_EVM_CHAIN_ID=200202
|
||||
- lux-rpc-url=https://api.lux-dev.network/ext/bc/C/rpc
|
||||
|
||||
patches:
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
apiVersion: hanzo.ai/v1
|
||||
kind: SPA
|
||||
metadata:
|
||||
name: zoo-industries
|
||||
namespace: zoo
|
||||
labels:
|
||||
app.kubernetes.io/name: zoo-industries
|
||||
app.kubernetes.io/part-of: zoo
|
||||
app.kubernetes.io/component: marketing-site
|
||||
spec:
|
||||
# hanzoai/spa Go runtime — 5MB scratch image, serves /public, templates
|
||||
# /public/config.json from SPA_* env at startup.
|
||||
runtime:
|
||||
repository: ghcr.io/hanzoai/spa
|
||||
tag: "1.2.0"
|
||||
# Built site content — Dockerfile produces the next build into /public.
|
||||
# Tag bumps with each Docker workflow run.
|
||||
content:
|
||||
repository: ghcr.io/zooai/zoo-industries
|
||||
tag: "latest"
|
||||
config:
|
||||
SPA_API_HOST: "https://api.zoo.ngo"
|
||||
SPA_IAM_HOST: "https://zoo.id"
|
||||
SPA_ENV: "prod"
|
||||
replicas: 2
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
ingress:
|
||||
enabled: true
|
||||
hosts:
|
||||
- zoo.industries
|
||||
tls: true
|
||||
clusterIssuer: letsencrypt-prod
|
||||
pdb:
|
||||
enabled: true
|
||||
maxUnavailable: 1
|
||||
@@ -3,7 +3,7 @@ kind: Kustomization
|
||||
namespace: zoo-mainnet
|
||||
resources:
|
||||
- ../base
|
||||
- crs/zoo-industries.yaml
|
||||
- zoo-industries.yaml
|
||||
labels:
|
||||
- pairs:
|
||||
app.kubernetes.io/cluster: zoo-k8s
|
||||
@@ -12,8 +12,11 @@ labels:
|
||||
configMapGenerator:
|
||||
- name: zoo-config
|
||||
literals:
|
||||
- ZOO_NETWORK_ID=1
|
||||
- ZOO_CHAIN_ID=200200
|
||||
# Lux primary networkID — shared across all brand L1/L2s.
|
||||
# Canonical values: 1 (mainnet), 2 (testnet), 3 (devnet), 1337 (local).
|
||||
- LUX_NETWORK_ID=1
|
||||
# Zoo EVM chainID — per-env, baked into the L2 genesis blob.
|
||||
- ZOO_EVM_CHAIN_ID=200200
|
||||
- lux-rpc-url=https://api.lux.network/ext/bc/C/rpc
|
||||
patches:
|
||||
- path: network-patch.yaml
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
# zoo.industries — static marketing site served by hanzoai/spa runtime
|
||||
# on the canonical content image at ghcr.io/zooai/zoo-industries.
|
||||
# Raw K8s shape (Deployment + Service + Ingress); migrate to a
|
||||
# kind: SPA CR once hanzoai/operator is installed on this cluster.
|
||||
# zoo.industries — static marketing site (hanzoai/spa runtime + content
|
||||
# image at ghcr.io/zooai/zoo-industries).
|
||||
#
|
||||
# Lives on lux-k8s zoo-mainnet (NOT do-sfo3-zoo-k8s — Zoo brand sites
|
||||
# run alongside Zoo L2 validators on the Lux cluster).
|
||||
#
|
||||
# Renovate auto-updates the image tag on each new sha-* push from
|
||||
# zooai/zoo-industries.
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@@ -37,7 +41,7 @@ spec:
|
||||
# Content image built from the zoo.industries repo Dockerfile
|
||||
# (FROM ghcr.io/hanzoai/spa:1.2.0 + COPY out/ /public/).
|
||||
# Bump the tag to roll a new deploy.
|
||||
image: ghcr.io/zooai/zoo-industries:sha-8e93f08-amd64
|
||||
image: ghcr.io/zooai/zoo-industries:1.0.5-amd64
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# zoo-system: operator only, scale everything else to 0
|
||||
# Operator image is pinned in base/operator.yaml — no override needed.
|
||||
# zoo-system: operator only. Scale everything else to 0 so the base
|
||||
# manifests can co-tenant the namespace without spawning real workloads.
|
||||
apiVersion: zoo.network/v1alpha1
|
||||
kind: ZooNetwork
|
||||
metadata:
|
||||
@@ -31,7 +31,7 @@ spec:
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: sql
|
||||
name: s3
|
||||
spec:
|
||||
replicas: 0
|
||||
---
|
||||
|
||||
@@ -14,8 +14,10 @@ labels:
|
||||
configMapGenerator:
|
||||
- name: zoo-config
|
||||
literals:
|
||||
- ZOO_NETWORK_ID=2
|
||||
- ZOO_CHAIN_ID=200201
|
||||
# Lux primary networkID (2 = testnet) — shared across brand L1/L2s.
|
||||
- LUX_NETWORK_ID=2
|
||||
# Zoo EVM chainID — per-env, baked into the L2 genesis blob.
|
||||
- ZOO_EVM_CHAIN_ID=200201
|
||||
- lux-rpc-url=https://api.lux-test.network/ext/bc/C/rpc
|
||||
|
||||
patches:
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
# Zoo Labs -- Dev Environment Manifest (devnet)
|
||||
# Source of truth for deployed versions. Renovate opens PRs for version bumps.
|
||||
# CI deploys on merge to main/test/dev.
|
||||
#
|
||||
# Image tags: v{semver} -- Renovate auto-merges minor+patch, major needs review.
|
||||
|
||||
version: "1"
|
||||
environment: dev
|
||||
cluster: do-sfo3-zoo-k8s
|
||||
|
||||
services:
|
||||
# -- Namespace: zoo-devnet (web apps) -------------------------------------
|
||||
|
||||
zoo-ngo:
|
||||
repo: zooai/zoo.ngo
|
||||
app_type: frontend
|
||||
app_name: zoo-ngo
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/zooai/zoo-ngo:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-exchange:
|
||||
repo: zooai/exchange
|
||||
app_type: frontend
|
||||
app_name: zoo-exchange
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/zooai/zoo-exchange:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-vote:
|
||||
repo: zooai/vote
|
||||
app_type: frontend
|
||||
app_name: zoo-vote
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/zooai/zoo-vote:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-fund:
|
||||
repo: zooai/fund
|
||||
app_type: frontend
|
||||
app_name: zoo-fund
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/zooai/zoo-fund:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-foundation:
|
||||
repo: zooai/foundation
|
||||
app_type: frontend
|
||||
app_name: zoo-foundation
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/zooai/zoo-foundation:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-lab:
|
||||
repo: zooai/lab
|
||||
app_type: frontend
|
||||
app_name: zoo-lab
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/zooai/zoo-lab:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-network:
|
||||
repo: zooai/network
|
||||
app_type: frontend
|
||||
app_name: zoo-network
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/zooai/zoo-network:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-voyage:
|
||||
repo: zooai/voyage
|
||||
app_type: frontend
|
||||
app_name: zoo-voyage
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/zooai/zoo-voyage:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
# -- Namespace: zoo-devnet (chain services) -------------------------------
|
||||
|
||||
zoo-node:
|
||||
repo: zooai/node
|
||||
app_type: backend
|
||||
app_name: zoo-node
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/zooai/zoo-node:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-bridge:
|
||||
repo: zooai/bridge
|
||||
app_type: backend
|
||||
app_name: zoo-bridge
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/zooai/zoo-bridge:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
# -- Namespace: zoo-devnet (shared infra) ---------------------------------
|
||||
|
||||
iam:
|
||||
repo: hanzoai/iam
|
||||
app_type: backend
|
||||
app_name: iam
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/hanzoai/iam:1.0.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
kms:
|
||||
repo: hanzoai/kms
|
||||
app_type: backend
|
||||
app_name: kms
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/hanzoai/kms:1.0.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
gateway:
|
||||
repo: hanzoai/gateway
|
||||
app_type: backend
|
||||
app_name: gateway
|
||||
namespace: zoo-devnet
|
||||
image: ghcr.io/hanzoai/gateway:1.0.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
@@ -1,128 +0,0 @@
|
||||
# Zoo Labs -- Main Environment Manifest (mainnet/production)
|
||||
version: "1"
|
||||
environment: main
|
||||
cluster: do-sfo3-zoo-k8s
|
||||
|
||||
services:
|
||||
# -- Namespace: zoo-mainnet (web apps) ------------------------------------
|
||||
|
||||
zoo-ngo:
|
||||
repo: zooai/zoo.ngo
|
||||
app_type: frontend
|
||||
app_name: zoo-ngo
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/zooai/zoo-ngo:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-exchange:
|
||||
repo: zooai/exchange
|
||||
app_type: frontend
|
||||
app_name: zoo-exchange
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/zooai/zoo-exchange:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-vote:
|
||||
repo: zooai/vote
|
||||
app_type: frontend
|
||||
app_name: zoo-vote
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/zooai/zoo-vote:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-fund:
|
||||
repo: zooai/fund
|
||||
app_type: frontend
|
||||
app_name: zoo-fund
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/zooai/zoo-fund:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-foundation:
|
||||
repo: zooai/foundation
|
||||
app_type: frontend
|
||||
app_name: zoo-foundation
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/zooai/zoo-foundation:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-lab:
|
||||
repo: zooai/lab
|
||||
app_type: frontend
|
||||
app_name: zoo-lab
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/zooai/zoo-lab:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-network:
|
||||
repo: zooai/network
|
||||
app_type: frontend
|
||||
app_name: zoo-network
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/zooai/zoo-network:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-voyage:
|
||||
repo: zooai/voyage
|
||||
app_type: frontend
|
||||
app_name: zoo-voyage
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/zooai/zoo-voyage:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
# -- Namespace: zoo-mainnet (chain services) ------------------------------
|
||||
|
||||
zoo-node:
|
||||
repo: zooai/node
|
||||
app_type: backend
|
||||
app_name: zoo-node
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/zooai/zoo-node:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-bridge:
|
||||
repo: zooai/bridge
|
||||
app_type: backend
|
||||
app_name: zoo-bridge
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/zooai/zoo-bridge:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
# -- Namespace: zoo-mainnet (shared infra) --------------------------------
|
||||
|
||||
iam:
|
||||
repo: hanzoai/iam
|
||||
app_type: backend
|
||||
app_name: iam
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/hanzoai/iam:1.0.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
kms:
|
||||
repo: hanzoai/kms
|
||||
app_type: backend
|
||||
app_name: kms
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/hanzoai/kms:1.0.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
gateway:
|
||||
repo: hanzoai/gateway
|
||||
app_type: backend
|
||||
app_name: gateway
|
||||
namespace: zoo-mainnet
|
||||
image: ghcr.io/hanzoai/gateway:1.0.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
@@ -0,0 +1,93 @@
|
||||
# Zoo Labs Foundation — canonical service manifest.
|
||||
#
|
||||
# Source of truth for image tags deployed per environment. Renovate opens
|
||||
# PRs to bump versions; CI deploys on merge to main / test / dev.
|
||||
#
|
||||
# Image tags MUST be semver. :latest / :main / :dev are banned per
|
||||
# universe/LLM.md and global Docker Build Standard.
|
||||
#
|
||||
# One row per service. Each service runs in three namespaces with the
|
||||
# same image tag unless an env override is set under `envOverrides`.
|
||||
version: "1"
|
||||
cluster: do-sfo3-zoo-k8s
|
||||
namespaces:
|
||||
main: zoo-mainnet
|
||||
test: zoo-testnet
|
||||
dev: zoo-devnet
|
||||
|
||||
# Shared platform infra is NOT listed here. Zoo apps consume the canonical
|
||||
# hanzo-k8s deployments at: kms.hanzo.ai, hanzo.id (IAM), api.hanzo.ai
|
||||
# (Gateway/LLM). Do not redeploy these per zoo env.
|
||||
|
||||
services:
|
||||
# ---- Web apps (Vite SPAs / Next.js, all behind zoo-ingress) ----
|
||||
zoo-ngo:
|
||||
repo: zooai/zoo.ngo
|
||||
appType: frontend
|
||||
image: ghcr.io/zooai/zoo-ngo:0.1.0
|
||||
sha: ""
|
||||
updatedAt: ""
|
||||
|
||||
zoo-exchange:
|
||||
repo: zooai/exchange
|
||||
appType: frontend
|
||||
image: ghcr.io/zooai/zoo-exchange:0.1.0
|
||||
sha: ""
|
||||
updatedAt: ""
|
||||
|
||||
zoo-vote:
|
||||
repo: zooai/vote
|
||||
appType: frontend
|
||||
image: ghcr.io/zooai/zoo-vote:0.1.0
|
||||
sha: ""
|
||||
updatedAt: ""
|
||||
|
||||
zoo-fund:
|
||||
repo: zooai/fund
|
||||
appType: frontend
|
||||
image: ghcr.io/zooai/zoo-fund:0.1.0
|
||||
sha: ""
|
||||
updatedAt: ""
|
||||
|
||||
zoo-foundation:
|
||||
repo: zooai/foundation
|
||||
appType: frontend
|
||||
image: ghcr.io/zooai/zoo-foundation:0.1.0
|
||||
sha: ""
|
||||
updatedAt: ""
|
||||
|
||||
zoo-lab:
|
||||
repo: zooai/lab
|
||||
appType: frontend
|
||||
image: ghcr.io/zooai/zoo-lab:0.1.0
|
||||
sha: ""
|
||||
updatedAt: ""
|
||||
|
||||
zoo-network:
|
||||
repo: zooai/network
|
||||
appType: frontend
|
||||
image: ghcr.io/zooai/zoo-network:0.1.0
|
||||
sha: ""
|
||||
updatedAt: ""
|
||||
|
||||
zoo-voyage:
|
||||
repo: zooai/voyage
|
||||
appType: frontend
|
||||
image: ghcr.io/zooai/zoo-voyage:0.1.0
|
||||
sha: ""
|
||||
updatedAt: ""
|
||||
|
||||
# ---- Chain-side services (run alongside Lux L1 validators) ----
|
||||
zoo-node:
|
||||
repo: zooai/node
|
||||
appType: backend
|
||||
image: ghcr.io/zooai/zoo-node:0.1.0
|
||||
sha: ""
|
||||
updatedAt: ""
|
||||
|
||||
zoo-bridge:
|
||||
repo: zooai/bridge
|
||||
appType: backend
|
||||
image: ghcr.io/zooai/zoo-bridge:0.1.0
|
||||
sha: ""
|
||||
updatedAt: ""
|
||||
@@ -1,128 +0,0 @@
|
||||
# Zoo Labs -- Test Environment Manifest (testnet)
|
||||
version: "1"
|
||||
environment: test
|
||||
cluster: do-sfo3-zoo-k8s
|
||||
|
||||
services:
|
||||
# -- Namespace: zoo-testnet (web apps) ------------------------------------
|
||||
|
||||
zoo-ngo:
|
||||
repo: zooai/zoo.ngo
|
||||
app_type: frontend
|
||||
app_name: zoo-ngo
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/zooai/zoo-ngo:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-exchange:
|
||||
repo: zooai/exchange
|
||||
app_type: frontend
|
||||
app_name: zoo-exchange
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/zooai/zoo-exchange:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-vote:
|
||||
repo: zooai/vote
|
||||
app_type: frontend
|
||||
app_name: zoo-vote
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/zooai/zoo-vote:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-fund:
|
||||
repo: zooai/fund
|
||||
app_type: frontend
|
||||
app_name: zoo-fund
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/zooai/zoo-fund:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-foundation:
|
||||
repo: zooai/foundation
|
||||
app_type: frontend
|
||||
app_name: zoo-foundation
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/zooai/zoo-foundation:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-lab:
|
||||
repo: zooai/lab
|
||||
app_type: frontend
|
||||
app_name: zoo-lab
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/zooai/zoo-lab:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-network:
|
||||
repo: zooai/network
|
||||
app_type: frontend
|
||||
app_name: zoo-network
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/zooai/zoo-network:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-voyage:
|
||||
repo: zooai/voyage
|
||||
app_type: frontend
|
||||
app_name: zoo-voyage
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/zooai/zoo-voyage:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
# -- Namespace: zoo-testnet (chain services) ------------------------------
|
||||
|
||||
zoo-node:
|
||||
repo: zooai/node
|
||||
app_type: backend
|
||||
app_name: zoo-node
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/zooai/zoo-node:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
zoo-bridge:
|
||||
repo: zooai/bridge
|
||||
app_type: backend
|
||||
app_name: zoo-bridge
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/zooai/zoo-bridge:0.1.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
# -- Namespace: zoo-testnet (shared infra) --------------------------------
|
||||
|
||||
iam:
|
||||
repo: hanzoai/iam
|
||||
app_type: backend
|
||||
app_name: iam
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/hanzoai/iam:1.0.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
kms:
|
||||
repo: hanzoai/kms
|
||||
app_type: backend
|
||||
app_name: kms
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/hanzoai/kms:1.0.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
|
||||
gateway:
|
||||
repo: hanzoai/gateway
|
||||
app_type: backend
|
||||
app_name: gateway
|
||||
namespace: zoo-testnet
|
||||
image: ghcr.io/hanzoai/gateway:1.0.0
|
||||
sha: ""
|
||||
updated_at: ""
|
||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@zoo-labs/universe",
|
||||
"name": "@zooai/universe",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@zoo-labs/universe",
|
||||
"name": "@zooai/universe",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.52.0"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@zoo-labs/universe",
|
||||
"name": "@zooai/universe",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Zoo Universe — Zoo Network E2E testing suite",
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Apply Replicate sidecars for SQLite replication to S3.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/apply-replicate.sh # zoo-devnet (default)
|
||||
# ./scripts/apply-replicate.sh do-sfo3-zoo-k8s # specify context
|
||||
#
|
||||
# Prerequisites:
|
||||
# - ghcr.io/hanzoai/replicate:latest accessible (ghcr-secret)
|
||||
# - s3-credentials secret exists in target namespace
|
||||
# - replicate-config ConfigMap applied first (done by this script)
|
||||
#
|
||||
# What this does:
|
||||
# 1. Deploys S3 storage service (hanzoai/s3)
|
||||
# 2. Applies the shared replicate-config ConfigMap
|
||||
# 3. Ensures s3-credentials secret exists (errors if missing)
|
||||
# 4. Patches zoo-bridge (Deployment) with replicate sidecar + PVC
|
||||
# 5. Restarts patched workloads
|
||||
set -euo pipefail
|
||||
|
||||
CTX="${1:-do-sfo3-zoo-k8s}"
|
||||
DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
NS="${2:-zoo-devnet}"
|
||||
|
||||
echo "==> Context: $CTX"
|
||||
echo "==> Namespace: $NS"
|
||||
|
||||
# Check s3-credentials exist
|
||||
if ! kubectl --context "$CTX" -n "$NS" get secret s3-credentials &>/dev/null; then
|
||||
echo "ERROR: s3-credentials secret not found in $NS namespace."
|
||||
echo "Create it first:"
|
||||
echo " kubectl --context $CTX -n $NS create secret generic s3-credentials \\"
|
||||
echo " --from-literal=ACCESS_KEY=<key> --from-literal=SECRET_KEY=<secret>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Deploying S3 storage service"
|
||||
kubectl --context "$CTX" apply -f "$DIR/k8s/patches/s3.yaml"
|
||||
|
||||
echo "==> Waiting for S3 readiness"
|
||||
kubectl --context "$CTX" -n "$NS" rollout status statefulset/s3 --timeout=120s
|
||||
|
||||
echo "==> Applying replicate-config ConfigMap"
|
||||
kubectl --context "$CTX" apply -f "$DIR/k8s/patches/replicate-config.yaml"
|
||||
|
||||
echo "==> Patching zoo-bridge (Deployment)"
|
||||
kubectl --context "$CTX" apply -f "$DIR/k8s/patches/replicate-bridge.yaml"
|
||||
|
||||
echo "==> Restarting patched workloads"
|
||||
kubectl --context "$CTX" -n "$NS" rollout restart deploy/zoo-bridge
|
||||
|
||||
echo "==> Waiting for rollouts"
|
||||
for w in deploy/zoo-bridge; do
|
||||
echo " $w..."
|
||||
kubectl --context "$CTX" -n "$NS" rollout status "$w" --timeout=120s
|
||||
done
|
||||
|
||||
echo "==> Done. Replicate sidecars active on: zoo-bridge"
|
||||
echo " S3 endpoint: http://s3.$NS.svc.cluster.local:9000"
|
||||
echo " Bucket: zoo-data"
|
||||
+27
-51
@@ -1,36 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deploy ALL services from the manifest to DOKS via kustomize.
|
||||
# Reads manifests/<env>.yml for the namespace, then applies k8s/<namespace>/.
|
||||
# Deploy every service from manifests/services.yml to the target namespace.
|
||||
#
|
||||
# Usage:
|
||||
# ./deploy-all.sh <environment>
|
||||
# ./deploy-all.sh <env>
|
||||
#
|
||||
# Example:
|
||||
# ./deploy-all.sh main
|
||||
# <env> ∈ {dev,test,main} → namespace ∈ {zoo-devnet, zoo-testnet, zoo-mainnet}.
|
||||
# Cluster is always do-sfo3-zoo-k8s (set in the manifest).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ENV="${1:?Usage: deploy-all.sh <environment>}"
|
||||
ENV="${1:?Usage: deploy-all.sh <dev|test|main>}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
MANIFEST="${ROOT_DIR}/manifests/${ENV}.yml"
|
||||
MANIFEST="${ROOT_DIR}/manifests/services.yml"
|
||||
|
||||
if [[ ! -f "${MANIFEST}" ]]; then
|
||||
echo "::error::Manifest not found: ${MANIFEST}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Resolve namespace from env or manifest
|
||||
NS="${DEPLOY_NAMESPACE:-}"
|
||||
if [[ -z "${NS}" ]]; then
|
||||
case "${ENV}" in
|
||||
main) NS="zoo-mainnet" ;;
|
||||
test) NS="zoo-testnet" ;;
|
||||
dev) NS="zoo-devnet" ;;
|
||||
*) NS="zoo-devnet" ;;
|
||||
esac
|
||||
fi
|
||||
case "${ENV}" in
|
||||
main) NS="zoo-mainnet" ;;
|
||||
test) NS="zoo-testnet" ;;
|
||||
dev) NS="zoo-devnet" ;;
|
||||
*) echo "::error::Unknown env '${ENV}' (expect dev/test/main)"; exit 1 ;;
|
||||
esac
|
||||
|
||||
OVERLAY_DIR="${ROOT_DIR}/k8s/${NS}"
|
||||
|
||||
@@ -40,31 +35,22 @@ echo " Namespace: ${NS}"
|
||||
echo " Overlay: ${OVERLAY_DIR}"
|
||||
echo "========================================================"
|
||||
|
||||
# Apply CRDs first (idempotent)
|
||||
if [[ -d "${ROOT_DIR}/k8s/crds" ]]; then
|
||||
echo "Applying CRDs..."
|
||||
kubectl apply -f "${ROOT_DIR}/k8s/crds/"
|
||||
fi
|
||||
|
||||
# Apply base resources
|
||||
if [[ -d "${ROOT_DIR}/k8s/base" ]]; then
|
||||
echo "Applying base..."
|
||||
kubectl apply -k "${ROOT_DIR}/k8s/base" 2>/dev/null || true
|
||||
if [[ ! -d "${OVERLAY_DIR}" ]]; then
|
||||
echo "::error::Overlay directory not found: ${OVERLAY_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Apply environment overlay
|
||||
if [[ -d "${OVERLAY_DIR}" ]]; then
|
||||
echo "Applying overlay: ${NS}..."
|
||||
kubectl apply -k "${OVERLAY_DIR}"
|
||||
else
|
||||
echo "::warning::Overlay directory not found: ${OVERLAY_DIR}"
|
||||
echo "Deploying from manifest image tags directly..."
|
||||
echo "Applying overlay: ${NS}..."
|
||||
kubectl apply -k "${OVERLAY_DIR}"
|
||||
|
||||
# Fallback: set images from manifest for each service
|
||||
kubectl create namespace "${NS}" --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# Parse manifest and set images
|
||||
python3 - "${MANIFEST}" "${NS}" <<'PYEOF'
|
||||
# Patch each manifest image onto its deployment so service-built dispatches
|
||||
# take effect without committing image pins into the base.
|
||||
python3 - "${MANIFEST}" "${NS}" <<'PYEOF'
|
||||
import sys, subprocess
|
||||
|
||||
manifest_path = sys.argv[1]
|
||||
@@ -73,17 +59,14 @@ namespace = sys.argv[2]
|
||||
in_services = False
|
||||
current_key = ""
|
||||
current_image = ""
|
||||
current_app_name = ""
|
||||
|
||||
def apply_deployment(app_name, image):
|
||||
"""Create a minimal deployment if kustomize overlay doesn't exist."""
|
||||
if not app_name or not image:
|
||||
def apply(svc, image):
|
||||
if not svc or not image:
|
||||
return
|
||||
print(f" Setting {app_name} -> {image}")
|
||||
# Just update image on existing deployments
|
||||
print(f" set image {svc} -> {image}")
|
||||
subprocess.run([
|
||||
"kubectl", "set", "image",
|
||||
f"deployment/{app_name}", f"{app_name}={image}",
|
||||
f"deployment/{svc}", f"{svc}={image}",
|
||||
"-n", namespace
|
||||
], capture_output=True)
|
||||
|
||||
@@ -95,30 +78,23 @@ with open(manifest_path) as f:
|
||||
continue
|
||||
if not in_services:
|
||||
continue
|
||||
if stripped.startswith("#") or not stripped:
|
||||
if not stripped or stripped.startswith("#"):
|
||||
continue
|
||||
|
||||
indent = len(line) - len(line.lstrip())
|
||||
if indent == 2 and stripped.endswith(":"):
|
||||
if current_key:
|
||||
apply_deployment(current_app_name, current_image)
|
||||
apply(current_key, current_image)
|
||||
current_key = stripped.rstrip(":")
|
||||
current_image = ""
|
||||
current_app_name = ""
|
||||
elif indent == 4 and current_key:
|
||||
key, _, val = stripped.partition(":")
|
||||
val = val.strip().strip('"').strip("'")
|
||||
if key == "image":
|
||||
current_image = val
|
||||
elif key == "app_name":
|
||||
current_app_name = val
|
||||
|
||||
if current_key:
|
||||
apply_deployment(current_app_name, current_image)
|
||||
apply(current_key, current_image)
|
||||
PYEOF
|
||||
fi
|
||||
|
||||
# Wait for rollouts
|
||||
echo ""
|
||||
echo "Waiting for rollouts in ${NS}..."
|
||||
kubectl rollout status deployment --all -n "${NS}" --timeout=300s 2>/dev/null || true
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Update a service entry in the version manifest.
|
||||
# Update a service entry in the canonical manifest.
|
||||
#
|
||||
# Usage:
|
||||
# ./update-manifest.sh <environment> <service_key> <sha> <image>
|
||||
# ./update-manifest.sh <service_key> <sha> <image>
|
||||
#
|
||||
# Example:
|
||||
# ./update-manifest.sh main zoo-ngo abc123def "ghcr.io/zoo-labs/zoo-ngo:vabc123def"
|
||||
# ./update-manifest.sh zoo-ngo abc123def "ghcr.io/zooai/zoo-ngo:v1.2.3"
|
||||
#
|
||||
# This script updates the manifest YAML in-place using Python stdlib.
|
||||
# No yq or PyYAML dependency needed.
|
||||
# Updates manifests/services.yml in-place. Python stdlib only — no yq /
|
||||
# PyYAML dependency.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ENV="${1:?Usage: update-manifest.sh <env> <service_key> <sha> <image>}"
|
||||
SERVICE_KEY="${2:?Missing service_key}"
|
||||
SHA="${3:?Missing sha}"
|
||||
IMAGE="${4:?Missing image}"
|
||||
SERVICE_KEY="${1:?Usage: update-manifest.sh <service_key> <sha> <image>}"
|
||||
SHA="${2:?Missing sha}"
|
||||
IMAGE="${3:?Missing image}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
MANIFEST="${ROOT_DIR}/manifests/${ENV}.yml"
|
||||
MANIFEST="${ROOT_DIR}/manifests/services.yml"
|
||||
|
||||
if [[ ! -f "${MANIFEST}" ]]; then
|
||||
echo "::error::Manifest not found: ${MANIFEST}"
|
||||
@@ -28,15 +27,13 @@ fi
|
||||
|
||||
TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
echo "Updating manifest: ${ENV}/${SERVICE_KEY}"
|
||||
echo "Updating manifest: ${SERVICE_KEY}"
|
||||
echo " SHA: ${SHA}"
|
||||
echo " Image: ${IMAGE}"
|
||||
echo " Time: ${TIMESTAMP}"
|
||||
|
||||
# Use a Python one-liner for reliable YAML field update.
|
||||
# This finds the service_key block and updates image, sha, updated_at.
|
||||
python3 - "${MANIFEST}" "${SERVICE_KEY}" "${SHA}" "${IMAGE}" "${TIMESTAMP}" <<'PYEOF'
|
||||
import sys, re
|
||||
import sys
|
||||
|
||||
manifest_path = sys.argv[1]
|
||||
service_key = sys.argv[2]
|
||||
@@ -47,19 +44,16 @@ timestamp = sys.argv[5]
|
||||
with open(manifest_path, 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Find the service block and update its fields
|
||||
in_target = False
|
||||
indent_level = 0
|
||||
result = []
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
for line in lines:
|
||||
stripped = line.lstrip().rstrip('\n')
|
||||
|
||||
# Detect service key line (e.g., " zoo-ngo:" at services indent level)
|
||||
if stripped == f"{service_key}:" or stripped == f"{service_key}: ":
|
||||
# Verify this is under services: by checking indent
|
||||
leading = len(line) - len(line.lstrip())
|
||||
if leading >= 2: # At least 2-space indent (under services:)
|
||||
if leading >= 2:
|
||||
in_target = True
|
||||
indent_level = leading
|
||||
result.append(line)
|
||||
@@ -67,21 +61,19 @@ for i, line in enumerate(lines):
|
||||
|
||||
if in_target:
|
||||
leading = len(line) - len(line.lstrip())
|
||||
# If we hit a line at same or lower indent, we left the block
|
||||
if stripped and not stripped.startswith('#') and leading <= indent_level:
|
||||
in_target = False
|
||||
result.append(line)
|
||||
continue
|
||||
|
||||
# Update fields within the block
|
||||
if stripped.startswith('image:'):
|
||||
result.append(f"{' ' * (indent_level + 2)}image: {image}\n")
|
||||
continue
|
||||
elif stripped.startswith('sha:'):
|
||||
if stripped.startswith('sha:'):
|
||||
result.append(f"{' ' * (indent_level + 2)}sha: \"{sha}\"\n")
|
||||
continue
|
||||
elif stripped.startswith('updated_at:'):
|
||||
result.append(f"{' ' * (indent_level + 2)}updated_at: \"{timestamp}\"\n")
|
||||
if stripped.startswith('updatedAt:'):
|
||||
result.append(f"{' ' * (indent_level + 2)}updatedAt: \"{timestamp}\"\n")
|
||||
continue
|
||||
|
||||
result.append(line)
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Zoo Universe - Setup Script
|
||||
# Clones all Zoo apps and sets up local development environment
|
||||
|
||||
set -e
|
||||
|
||||
echo "🌌 Zoo Universe - Setup"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Define all zoo apps
|
||||
APPS=(
|
||||
"zoo.ngo:3000"
|
||||
"zoo.fund:3005"
|
||||
"zoo.exchange:3001"
|
||||
"zoo.network:3002"
|
||||
"zoo.computer:3003"
|
||||
"zoo.lab:3004"
|
||||
"zoo.vote:3006"
|
||||
"zoo.foundation:3007"
|
||||
)
|
||||
|
||||
# Clone all repositories
|
||||
echo "📦 Cloning all Zoo app repositories..."
|
||||
echo ""
|
||||
|
||||
for app_port in "${APPS[@]}"; do
|
||||
IFS=':' read -r app port <<< "$app_port"
|
||||
|
||||
if [ -d "$app" ]; then
|
||||
echo "✓ $app already cloned"
|
||||
else
|
||||
echo "📥 Cloning $app..."
|
||||
gh repo clone "zoo-apps/$app" || git clone "[email protected]:zoo-apps/$app.git"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✅ All repositories cloned!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Run: ./install-all.sh # Install dependencies for all apps"
|
||||
echo " 2. Run: ./start.sh # Start all apps"
|
||||
echo " 3. Run: ./stop.sh # Stop all apps"
|
||||
echo ""
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Start all Zoo apps in development mode
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Starting all Zoo apps..."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Define apps with their ports (format: "app:port")
|
||||
APPS=(
|
||||
"zoo.ngo:3000"
|
||||
"zoo.fund:3005"
|
||||
"zoo.exchange:3001"
|
||||
"zoo.network:3002"
|
||||
"zoo.computer:3003"
|
||||
"zoo.lab:3004"
|
||||
"zoo.vote:3006"
|
||||
"zoo.foundation:3007"
|
||||
"zoo.voyage:3008"
|
||||
)
|
||||
|
||||
# Create logs directory
|
||||
mkdir -p logs
|
||||
|
||||
# Start each app in background
|
||||
for app_port in "${APPS[@]}"; do
|
||||
IFS=':' read -r app port <<< "$app_port"
|
||||
|
||||
if [ -d "$app" ]; then
|
||||
echo "🚀 Starting $app on port $port..."
|
||||
|
||||
cd "$app"
|
||||
|
||||
# Check for dev script first, then start script
|
||||
if grep -q '"dev"' package.json 2>/dev/null; then
|
||||
# Start in background, redirect output to log file
|
||||
PORT=$port npm run dev > "../logs/$app.log" 2>&1 &
|
||||
echo $! > "../logs/$app.pid"
|
||||
echo "✅ $app started (PID: $(cat ../logs/$app.pid))"
|
||||
echo " → http://localhost:$port"
|
||||
echo " → Log: logs/$app.log"
|
||||
elif grep -q '"start"' package.json 2>/dev/null; then
|
||||
# Use start script if dev doesn't exist
|
||||
PORT=$port npm start > "../logs/$app.log" 2>&1 &
|
||||
echo $! > "../logs/$app.pid"
|
||||
echo "✅ $app started (PID: $(cat ../logs/$app.pid))"
|
||||
echo " → http://localhost:$port"
|
||||
echo " → Log: logs/$app.log"
|
||||
else
|
||||
echo "⚠️ No dev or start script found in $app/package.json"
|
||||
fi
|
||||
|
||||
cd ..
|
||||
echo ""
|
||||
else
|
||||
echo "❌ Directory $app not found"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ All apps started!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "Apps running:"
|
||||
echo " • zoo.ngo → http://localhost:3000"
|
||||
echo " • zoo.fund → http://localhost:3005"
|
||||
echo " • zoo.exchange → http://localhost:3001"
|
||||
echo " • zoo.network → http://localhost:3002"
|
||||
echo " • zoo.computer → http://localhost:3003"
|
||||
echo " • zoo.lab → http://localhost:3004"
|
||||
echo " • zoo.vote → http://localhost:3006"
|
||||
echo " • zoo.foundation → http://localhost:3007"
|
||||
echo " • zoo.voyage → http://localhost:3008"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " ./stop.sh # Stop all apps"
|
||||
echo " ./status.sh # Check app status"
|
||||
echo " tail -f logs/*.log # View all logs"
|
||||
echo ""
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check status of all Zoo apps
|
||||
|
||||
echo "📊 Zoo Apps Status"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
APPS=(
|
||||
"zoo.ngo:3000"
|
||||
"zoo.fund:3005"
|
||||
"zoo.exchange:3001"
|
||||
"zoo.network:3002"
|
||||
"zoo.computer:3003"
|
||||
"zoo.lab:3004"
|
||||
"zoo.vote:3006"
|
||||
"zoo.foundation:3007"
|
||||
"zoo.voyage:3008"
|
||||
)
|
||||
|
||||
running_count=0
|
||||
total_count=${#APPS[@]}
|
||||
|
||||
for app_port in "${APPS[@]}"; do
|
||||
IFS=':' read -r app port <<< "$app_port"
|
||||
pidfile="logs/$app.pid"
|
||||
|
||||
if [ -f "$pidfile" ]; then
|
||||
pid=$(cat "$pidfile")
|
||||
|
||||
# Check if process is actually running
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo "✅ $app (PID: $pid, Port: $port) → http://localhost:$port"
|
||||
((running_count++))
|
||||
else
|
||||
echo "❌ $app - stopped (stale PID)"
|
||||
fi
|
||||
else
|
||||
echo "⚪ $app - not started"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Status: $running_count/$total_count apps running"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Stop all running Zoo apps
|
||||
|
||||
echo "🛑 Stopping all Zoo apps..."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
APPS=(
|
||||
"zoo.ngo"
|
||||
"zoo.fund"
|
||||
"zoo.exchange"
|
||||
"zoo.network"
|
||||
"zoo.computer"
|
||||
"zoo.lab"
|
||||
"zoo.vote"
|
||||
"zoo.foundation"
|
||||
)
|
||||
|
||||
stopped_count=0
|
||||
|
||||
for app in "${APPS[@]}"; do
|
||||
pidfile="logs/$app.pid"
|
||||
|
||||
if [ -f "$pidfile" ]; then
|
||||
pid=$(cat "$pidfile")
|
||||
|
||||
# Check if process is running
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo "🛑 Stopping $app (PID: $pid)..."
|
||||
kill "$pid"
|
||||
rm "$pidfile"
|
||||
((stopped_count++))
|
||||
echo "✅ $app stopped"
|
||||
else
|
||||
echo "⚠️ $app was not running (stale PID file)"
|
||||
rm "$pidfile"
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ $app is not running"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ Stopped $stopped_count app(s)"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
Vendored
-36
@@ -1,36 +0,0 @@
|
||||
name: Deploy to GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: .
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -1 +0,0 @@
|
||||
zoo.id
|
||||
@@ -1,56 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>zoo.id - Decentralized Identity</title>
|
||||
<meta name="description" content="Decentralized identity for the Zoo research network">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.container { text-align: center; padding: 2rem; max-width: 600px; }
|
||||
h1 { font-size: 3rem; font-weight: 700; margin-bottom: 1rem; }
|
||||
.highlight { color: #10B981; }
|
||||
p { font-size: 1.25rem; color: #888; margin-bottom: 2rem; line-height: 1.6; }
|
||||
.badge { display: inline-block; padding: 0.5rem 1rem; background: #10B98120; border: 1px solid #10B98140; border-radius: 9999px; font-size: 0.875rem; color: #10B981; margin-bottom: 1rem; }
|
||||
.features { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; margin-bottom: 2rem; }
|
||||
.feature { padding: 0.5rem 1rem; background: #111; border: 1px solid #333; border-radius: 8px; font-size: 0.875rem; color: #aaa; }
|
||||
.links { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
|
||||
.links a { padding: 0.75rem 1.5rem; background: #10B981; color: #000; border-radius: 9999px; font-weight: 500; text-decoration: none; }
|
||||
.links a:hover { background: #059669; }
|
||||
.links a.secondary { background: transparent; border: 1px solid #333; color: #fff; }
|
||||
.links a.secondary:hover { border-color: #10B981; color: #10B981; }
|
||||
.did { font-family: monospace; font-size: 0.875rem; color: #666; margin-top: 2rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="badge">did:zoo</div>
|
||||
<h1>zoo<span class="highlight">.id</span></h1>
|
||||
<p>Decentralized identity for the Zoo research network. Researcher credentials, peer review attestations, and publication proofs.</p>
|
||||
|
||||
<div class="features">
|
||||
<span class="feature">Research Credentials</span>
|
||||
<span class="feature">Peer Review</span>
|
||||
<span class="feature">Publication Proofs</span>
|
||||
</div>
|
||||
|
||||
<div class="links">
|
||||
<a href="https://migaprotocol.xyz/identity">Mint Identity</a>
|
||||
<a href="https://zoo.network" class="secondary">Zoo Network</a>
|
||||
<a href="https://github.com/zoo-labs" class="secondary">GitHub</a>
|
||||
</div>
|
||||
|
||||
<div class="did">Chain ID: 200200</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user