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:
Hanzo AI
2026-06-03 11:12:00 -07:00
parent 09ca9d2781
commit f518621f63
61 changed files with 624 additions and 2364 deletions
-8
View File
@@ -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
+11 -10
View File
@@ -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="$(
+9 -10
View File
@@ -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="$(
-67
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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: |
+6 -20
View File
@@ -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" {} +
+5
View File
@@ -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
View File
-16
View File
@@ -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"]
+149 -181
View File
@@ -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.*
+47 -15
View File
@@ -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}'
-3
View File
@@ -1,3 +0,0 @@
module github.com/zoolabs/universe/auth-gateway
go 1.26.3
-302
View File
@@ -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
}
-83
View File
@@ -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
}
}
}
}
}
-90
View File
@@ -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
View File
@@ -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
-46
View File
@@ -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
+35 -31
View File
@@ -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
View File
@@ -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`.
-33
View File
@@ -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
-36
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+6 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+3 -4
View File
@@ -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:
+24
View File
@@ -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
+3 -4
View File
@@ -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
-98
View File
@@ -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
View File
@@ -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:
-103
View File
@@ -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
-25
View File
@@ -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
-83
View File
@@ -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
-9
View File
@@ -1,9 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: zoo-auth
resources:
- namespace.yaml
- auth-gateway.yaml
- validators.yaml
-7
View File
@@ -1,7 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: zoo-auth
labels:
app: zoo-auth
subnet: auth
-87
View File
@@ -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
+4 -2
View File
@@ -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:
-41
View File
@@ -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
+6 -3
View File
@@ -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
+9 -5
View File
@@ -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
+3 -3
View File
@@ -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
---
+4 -2
View File
@@ -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:
-133
View File
@@ -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: ""
-128
View File
@@ -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: ""
+93
View File
@@ -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: ""
-128
View File
@@ -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: ""
+2 -2
View File
@@ -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
View File
@@ -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",
-60
View File
@@ -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
View File
@@ -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
+16 -24
View File
@@ -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)
-46
View File
@@ -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 ""
-81
View File
@@ -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 ""
-46
View File
@@ -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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
-47
View File
@@ -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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
-36
View File
@@ -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
View File
@@ -1 +0,0 @@
zoo.id
-56
View File
@@ -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>