refactor: consolidate CI and local dev orchestration (#108)
## Summary - Extract shared bootstrap functions into `scripts/bootstrap-common.sh` (eliminates ~120 lines of duplicated forge/cast commands from e2e.yml) - Create reusable `scripts/wait-for-service.sh` for health checks (replaces 60-line inline wait-for-stack) - Merge dev and CI entrypoints into unified scripts branching on `CI` env var (delete `docker/ci-entrypoints/`) - Replace 4 per-service CI Dockerfiles with parameterized `docker/Dockerfile.service-ci` - Add `sync-tax-rates.mjs` to CI image builder stage - Fix: CI now grants txnBot recenter access (was missing) - Fix: txnBot funding parameterized (CI=10eth, local=1eth) - Delete 5 obsolete migration docs and 4 DinD integration files Net: -1540 lines removed Closes #107 ## Test plan - [ ] E2E pipeline passes (bootstrap sources shared script, services use old images with commands override) - [ ] build-ci-images pipeline builds all 4 services with unified Dockerfile - [ ] Local dev stack boots via `./scripts/dev.sh start` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: openhands <openhands@all-hands.dev> Reviewed-on: https://codeberg.org/johba/harb/pulls/108
This commit is contained in:
parent
4277f19b68
commit
e5e1308e72
45 changed files with 882 additions and 2627 deletions
|
|
@ -13,14 +13,18 @@ when:
|
||||||
path:
|
path:
|
||||||
include:
|
include:
|
||||||
- .woodpecker/build-ci-images.yml
|
- .woodpecker/build-ci-images.yml
|
||||||
- docker/Dockerfile.*-ci
|
- docker/Dockerfile.service-ci
|
||||||
- docker/ci-entrypoints/**
|
- docker/Dockerfile.node-ci
|
||||||
|
- containers/*-entrypoint.sh
|
||||||
|
- containers/entrypoint-common.sh
|
||||||
- kraiken-lib/**
|
- kraiken-lib/**
|
||||||
- onchain/**
|
- onchain/**
|
||||||
- services/ponder/**
|
- services/ponder/**
|
||||||
- services/txnBot/**
|
- services/txnBot/**
|
||||||
- web-app/**
|
- web-app/**
|
||||||
- landing/**
|
- landing/**
|
||||||
|
- scripts/sync-tax-rates.mjs
|
||||||
|
- scripts/bootstrap-common.sh
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# Compile Solidity contracts to generate ABI files needed by Dockerfiles
|
# Compile Solidity contracts to generate ABI files needed by Dockerfiles
|
||||||
|
|
@ -28,7 +32,7 @@ steps:
|
||||||
image: registry.niovi.voyage/harb/node-ci:latest
|
image: registry.niovi.voyage/harb/node-ci:latest
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# Initialize git submodules (required for forge dependencies)
|
# Initialize git submodules (required for forge dependencies)
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
|
@ -56,54 +60,85 @@ steps:
|
||||||
# Login to registry
|
# Login to registry
|
||||||
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin
|
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin
|
||||||
|
|
||||||
|
SHA="${CI_COMMIT_SHA:0:7}"
|
||||||
|
|
||||||
# Build and push node-ci (base image with Foundry pre-installed)
|
# Build and push node-ci (base image with Foundry pre-installed)
|
||||||
echo "=== Building node-ci ==="
|
echo "=== Building node-ci ==="
|
||||||
docker build \
|
docker build \
|
||||||
-f docker/Dockerfile.node-ci \
|
-f docker/Dockerfile.node-ci \
|
||||||
-t "$REGISTRY/harb/node-ci:${CI_COMMIT_SHA:0:7}" \
|
-t "$REGISTRY/harb/node-ci:$SHA" \
|
||||||
-t "$REGISTRY/harb/node-ci:latest" \
|
-t "$REGISTRY/harb/node-ci:latest" \
|
||||||
.
|
.
|
||||||
docker push "$REGISTRY/harb/node-ci:${CI_COMMIT_SHA:0:7}"
|
docker push "$REGISTRY/harb/node-ci:$SHA"
|
||||||
docker push "$REGISTRY/harb/node-ci:latest"
|
docker push "$REGISTRY/harb/node-ci:latest"
|
||||||
|
|
||||||
# Build and push ponder-ci
|
# Build and push ponder-ci (unified Dockerfile)
|
||||||
echo "=== Building ponder-ci ==="
|
echo "=== Building ponder-ci ==="
|
||||||
docker build \
|
docker build \
|
||||||
-f docker/Dockerfile.ponder-ci \
|
-f docker/Dockerfile.service-ci \
|
||||||
-t "$REGISTRY/harb/ponder-ci:${CI_COMMIT_SHA:0:7}" \
|
--build-arg SERVICE_DIR=services/ponder \
|
||||||
|
--build-arg SERVICE_PORT=42069 \
|
||||||
|
--build-arg ENTRYPOINT_SCRIPT=containers/ponder-entrypoint.sh \
|
||||||
|
--build-arg HEALTHCHECK_RETRIES=12 \
|
||||||
|
--build-arg HEALTHCHECK_START=20s \
|
||||||
|
--build-arg NEEDS_SYMLINKS=false \
|
||||||
|
-t "$REGISTRY/harb/ponder-ci:$SHA" \
|
||||||
-t "$REGISTRY/harb/ponder-ci:latest" \
|
-t "$REGISTRY/harb/ponder-ci:latest" \
|
||||||
.
|
.
|
||||||
docker push "$REGISTRY/harb/ponder-ci:${CI_COMMIT_SHA:0:7}"
|
docker push "$REGISTRY/harb/ponder-ci:$SHA"
|
||||||
docker push "$REGISTRY/harb/ponder-ci:latest"
|
docker push "$REGISTRY/harb/ponder-ci:latest"
|
||||||
|
|
||||||
# Build and push webapp-ci
|
# Build and push webapp-ci (unified Dockerfile)
|
||||||
echo "=== Building webapp-ci ==="
|
echo "=== Building webapp-ci ==="
|
||||||
docker build \
|
docker build \
|
||||||
-f docker/Dockerfile.webapp-ci \
|
-f docker/Dockerfile.service-ci \
|
||||||
-t "$REGISTRY/harb/webapp-ci:${CI_COMMIT_SHA:0:7}" \
|
--build-arg SERVICE_DIR=web-app \
|
||||||
|
--build-arg SERVICE_PORT=5173 \
|
||||||
|
--build-arg HEALTHCHECK_PATH=/app/ \
|
||||||
|
--build-arg HEALTHCHECK_RETRIES=84 \
|
||||||
|
--build-arg HEALTHCHECK_START=15s \
|
||||||
|
--build-arg ENTRYPOINT_SCRIPT=containers/webapp-entrypoint.sh \
|
||||||
|
--build-arg NODE_ENV=development \
|
||||||
|
--build-arg NEEDS_SYMLINKS=true \
|
||||||
|
-t "$REGISTRY/harb/webapp-ci:$SHA" \
|
||||||
-t "$REGISTRY/harb/webapp-ci:latest" \
|
-t "$REGISTRY/harb/webapp-ci:latest" \
|
||||||
.
|
.
|
||||||
docker push "$REGISTRY/harb/webapp-ci:${CI_COMMIT_SHA:0:7}"
|
docker push "$REGISTRY/harb/webapp-ci:$SHA"
|
||||||
docker push "$REGISTRY/harb/webapp-ci:latest"
|
docker push "$REGISTRY/harb/webapp-ci:latest"
|
||||||
|
|
||||||
# Build and push landing-ci
|
# Build and push landing-ci (unified Dockerfile)
|
||||||
echo "=== Building landing-ci ==="
|
echo "=== Building landing-ci ==="
|
||||||
docker build \
|
docker build \
|
||||||
-f docker/Dockerfile.landing-ci \
|
-f docker/Dockerfile.service-ci \
|
||||||
-t "$REGISTRY/harb/landing-ci:${CI_COMMIT_SHA:0:7}" \
|
--build-arg SERVICE_DIR=landing \
|
||||||
|
--build-arg SERVICE_PORT=5174 \
|
||||||
|
--build-arg ENTRYPOINT_SCRIPT=containers/landing-ci-entrypoint.sh \
|
||||||
|
--build-arg NODE_ENV=development \
|
||||||
|
--build-arg HEALTHCHECK_RETRIES=6 \
|
||||||
|
--build-arg HEALTHCHECK_START=10s \
|
||||||
|
--build-arg NEEDS_SYMLINKS=false \
|
||||||
|
-t "$REGISTRY/harb/landing-ci:$SHA" \
|
||||||
-t "$REGISTRY/harb/landing-ci:latest" \
|
-t "$REGISTRY/harb/landing-ci:latest" \
|
||||||
.
|
.
|
||||||
docker push "$REGISTRY/harb/landing-ci:${CI_COMMIT_SHA:0:7}"
|
docker push "$REGISTRY/harb/landing-ci:$SHA"
|
||||||
docker push "$REGISTRY/harb/landing-ci:latest"
|
docker push "$REGISTRY/harb/landing-ci:latest"
|
||||||
|
|
||||||
# Build and push txnbot-ci
|
# Build and push txnbot-ci (unified Dockerfile)
|
||||||
echo "=== Building txnbot-ci ==="
|
echo "=== Building txnbot-ci ==="
|
||||||
docker build \
|
docker build \
|
||||||
-f docker/Dockerfile.txnbot-ci \
|
-f docker/Dockerfile.service-ci \
|
||||||
-t "$REGISTRY/harb/txnbot-ci:${CI_COMMIT_SHA:0:7}" \
|
--build-arg SERVICE_DIR=services/txnBot \
|
||||||
|
--build-arg SERVICE_PORT=43069 \
|
||||||
|
--build-arg HEALTHCHECK_PATH=/status \
|
||||||
|
--build-arg HEALTHCHECK_RETRIES=4 \
|
||||||
|
--build-arg HEALTHCHECK_START=10s \
|
||||||
|
--build-arg ENTRYPOINT_SCRIPT=containers/txnbot-entrypoint.sh \
|
||||||
|
--build-arg NPM_INSTALL_CMD=install \
|
||||||
|
--build-arg NEEDS_SYMLINKS=false \
|
||||||
|
-t "$REGISTRY/harb/txnbot-ci:$SHA" \
|
||||||
-t "$REGISTRY/harb/txnbot-ci:latest" \
|
-t "$REGISTRY/harb/txnbot-ci:latest" \
|
||||||
.
|
.
|
||||||
docker push "$REGISTRY/harb/txnbot-ci:${CI_COMMIT_SHA:0:7}"
|
docker push "$REGISTRY/harb/txnbot-ci:$SHA"
|
||||||
docker push "$REGISTRY/harb/txnbot-ci:latest"
|
docker push "$REGISTRY/harb/txnbot-ci:latest"
|
||||||
|
|
||||||
echo "=== All CI images built and pushed ==="
|
echo "=== All CI images built and pushed ==="
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ steps:
|
||||||
image: registry.niovi.voyage/harb/node-ci:latest
|
image: registry.niovi.voyage/harb/node-ci:latest
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile
|
yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile
|
||||||
|
|
@ -20,7 +20,7 @@ steps:
|
||||||
image: registry.niovi.voyage/harb/node-ci:latest
|
image: registry.niovi.voyage/harb/node-ci:latest
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd onchain
|
cd onchain
|
||||||
export PATH=/root/.foundry/bin:$PATH
|
export PATH=/root/.foundry/bin:$PATH
|
||||||
|
|
@ -36,7 +36,7 @@ steps:
|
||||||
CI: "true"
|
CI: "true"
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
npm config set fund false
|
npm config set fund false
|
||||||
npm config set audit false
|
npm config set audit false
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ steps:
|
||||||
image: registry.niovi.voyage/harb/node-ci:latest
|
image: registry.niovi.voyage/harb/node-ci:latest
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile
|
yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile
|
||||||
|
|
@ -22,7 +22,7 @@ steps:
|
||||||
HARB_ENV: BASE_SEPOLIA_LOCAL_FORK
|
HARB_ENV: BASE_SEPOLIA_LOCAL_FORK
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd onchain
|
cd onchain
|
||||||
export PATH=/root/.foundry/bin:$PATH
|
export PATH=/root/.foundry/bin:$PATH
|
||||||
|
|
@ -45,7 +45,7 @@ steps:
|
||||||
image: registry.niovi.voyage/harb/node-ci:latest
|
image: registry.niovi.voyage/harb/node-ci:latest
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile
|
yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile
|
||||||
|
|
@ -59,7 +59,7 @@ steps:
|
||||||
from_secret: base_sepolia_rpc
|
from_secret: base_sepolia_rpc
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd onchain
|
cd onchain
|
||||||
export BASE_SEPOLIA_RPC="$BASE_SEPOLIA_RPC"
|
export BASE_SEPOLIA_RPC="$BASE_SEPOLIA_RPC"
|
||||||
|
|
|
||||||
|
|
@ -203,127 +203,18 @@ steps:
|
||||||
- install-deps
|
- install-deps
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
set -eu
|
# Create a bootstrap wrapper that runs under bash
|
||||||
|
# (Woodpecker uses /bin/sh which lacks 'source' and bash-isms)
|
||||||
# Foundry is pre-installed in node-ci image
|
export ANVIL_RPC=http://anvil:8545
|
||||||
echo "=== Foundry version ==="
|
export CONTRACT_ENV=/woodpecker/src/contracts.env
|
||||||
forge --version
|
export LOG_FILE=/dev/null
|
||||||
cast --version
|
export ONCHAIN_DIR="$PWD/onchain"
|
||||||
|
export TXNBOT_FUND_VALUE=10ether
|
||||||
echo "=== Waiting for Anvil ==="
|
export TXNBOT_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
|
||||||
for i in $(seq 1 60); do
|
export TXNBOT_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
|
||||||
if cast chain-id --rpc-url http://anvil:8545 2>/dev/null; then
|
exec bash scripts/ci-bootstrap.sh
|
||||||
echo "Anvil is ready"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "Waiting for Anvil... ($i/60)"
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "=== Deploying contracts ==="
|
|
||||||
cd onchain
|
|
||||||
|
|
||||||
# Deploy contracts using forge script
|
|
||||||
forge script script/DeployLocal.sol:DeployLocal \
|
|
||||||
--rpc-url http://anvil:8545 \
|
|
||||||
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
|
|
||||||
--broadcast
|
|
||||||
|
|
||||||
# Extract deployed addresses using Node.js (available in this image)
|
|
||||||
node -e "
|
|
||||||
const data = require('./broadcast/DeployLocal.sol/31337/run-latest.json');
|
|
||||||
const txns = data.transactions;
|
|
||||||
const kraiken = txns.find(t => t.contractName === 'Kraiken').contractAddress;
|
|
||||||
const stake = txns.find(t => t.contractName === 'Stake').contractAddress;
|
|
||||||
const lm = txns.find(t => t.contractName === 'LiquidityManager').contractAddress;
|
|
||||||
console.log('KRAIKEN=' + kraiken);
|
|
||||||
console.log('STAKE=' + stake);
|
|
||||||
console.log('LIQUIDITY_MANAGER=' + lm);
|
|
||||||
" > ../addresses.txt
|
|
||||||
. ../addresses.txt
|
|
||||||
|
|
||||||
# Get current block number as start block
|
|
||||||
START_BLOCK=$(cast block-number --rpc-url http://anvil:8545)
|
|
||||||
|
|
||||||
echo "=== Contract Deployment Complete ==="
|
|
||||||
echo "KRAIKEN: $KRAIKEN"
|
|
||||||
echo "STAKE: $STAKE"
|
|
||||||
echo "LIQUIDITY_MANAGER: $LIQUIDITY_MANAGER"
|
|
||||||
echo "START_BLOCK: $START_BLOCK"
|
|
||||||
|
|
||||||
# Build kraiken-lib BEFORE writing contracts.env
|
|
||||||
# (services wait for contracts.env, so kraiken-lib must be ready first)
|
|
||||||
echo "=== Building kraiken-lib (shared dependency) ==="
|
|
||||||
cd ../kraiken-lib
|
|
||||||
npm ci --ignore-scripts
|
|
||||||
./node_modules/.bin/tsc
|
|
||||||
cd ../onchain
|
|
||||||
|
|
||||||
# Write environment file for other services (absolute path for detached services)
|
|
||||||
{
|
|
||||||
echo "KRAIKEN=$KRAIKEN"
|
|
||||||
echo "STAKE=$STAKE"
|
|
||||||
echo "LIQUIDITY_MANAGER=$LIQUIDITY_MANAGER"
|
|
||||||
echo "START_BLOCK=$START_BLOCK"
|
|
||||||
echo "PONDER_RPC_URL_1=http://anvil:8545"
|
|
||||||
echo "DATABASE_URL=postgres://ponder:ponder_local@postgres:5432/ponder_local"
|
|
||||||
echo "RPC_URL=http://anvil:8545"
|
|
||||||
} > /woodpecker/src/contracts.env
|
|
||||||
|
|
||||||
# Write deployments-local.json for E2E tests
|
|
||||||
printf '{\n "contracts": {\n "Kraiken": "%s",\n "Stake": "%s",\n "LiquidityManager": "%s"\n }\n}\n' \
|
|
||||||
"$KRAIKEN" "$STAKE" "$LIQUIDITY_MANAGER" > deployments-local.json
|
|
||||||
echo "=== deployments-local.json written ==="
|
|
||||||
cat deployments-local.json
|
|
||||||
|
|
||||||
# Deployer and fee destination addresses
|
|
||||||
DEPLOYER_PK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
|
||||||
DEPLOYER_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
|
|
||||||
FEE_DEST=0xf6a3eef9088A255c32b6aD2025f83E57291D9011
|
|
||||||
WETH=0x4200000000000000000000000000000000000006
|
|
||||||
SWAP_ROUTER=0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4
|
|
||||||
MAX_UINT=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
|
|
||||||
echo "=== Funding LiquidityManager ==="
|
|
||||||
cast send --rpc-url http://anvil:8545 \
|
|
||||||
--private-key $DEPLOYER_PK \
|
|
||||||
"$LIQUIDITY_MANAGER" --value 0.1ether
|
|
||||||
|
|
||||||
echo "=== Granting recenter access ==="
|
|
||||||
cast rpc --rpc-url http://anvil:8545 anvil_impersonateAccount $FEE_DEST
|
|
||||||
cast send --rpc-url http://anvil:8545 --from $FEE_DEST --unlocked \
|
|
||||||
"$LIQUIDITY_MANAGER" "setRecenterAccess(address)" $DEPLOYER_ADDR
|
|
||||||
cast rpc --rpc-url http://anvil:8545 anvil_stopImpersonatingAccount $FEE_DEST
|
|
||||||
|
|
||||||
echo "=== Calling recenter() to seed liquidity ==="
|
|
||||||
cast send --rpc-url http://anvil:8545 --private-key $DEPLOYER_PK \
|
|
||||||
"$LIQUIDITY_MANAGER" "recenter()"
|
|
||||||
|
|
||||||
echo "=== Seeding application state (initial swap) ==="
|
|
||||||
# Wrap ETH to WETH
|
|
||||||
cast send --rpc-url http://anvil:8545 --private-key $DEPLOYER_PK \
|
|
||||||
$WETH "deposit()" --value 0.02ether
|
|
||||||
# Approve router
|
|
||||||
cast send --rpc-url http://anvil:8545 --private-key $DEPLOYER_PK \
|
|
||||||
$WETH "approve(address,uint256)" $SWAP_ROUTER $MAX_UINT
|
|
||||||
# Execute initial KRK swap
|
|
||||||
cast send --legacy --gas-limit 300000 --rpc-url http://anvil:8545 --private-key $DEPLOYER_PK \
|
|
||||||
$SWAP_ROUTER "exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))" \
|
|
||||||
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDR,10000000000000000,0,0)"
|
|
||||||
|
|
||||||
# Fund txnBot wallet
|
|
||||||
TXNBOT_ADDR=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
|
|
||||||
cast send --rpc-url http://anvil:8545 \
|
|
||||||
--private-key $DEPLOYER_PK \
|
|
||||||
--value 10ether \
|
|
||||||
$TXNBOT_ADDR
|
|
||||||
|
|
||||||
echo "TXNBOT_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" >> /woodpecker/src/contracts.env
|
|
||||||
|
|
||||||
echo "=== Bootstrap complete ==="
|
|
||||||
|
|
||||||
# Step 2: Wait for stack to be healthy (services run in background)
|
# Step 2: Wait for stack to be healthy (services run in background)
|
||||||
# Max 3 minutes - fail fast if services don't come up
|
|
||||||
- name: wait-for-stack
|
- name: wait-for-stack
|
||||||
image: alpine:3.20
|
image: alpine:3.20
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
@ -331,62 +222,14 @@ steps:
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
set -eu
|
set -eu
|
||||||
apk add --no-cache curl
|
apk add --no-cache curl bash
|
||||||
|
|
||||||
echo "=== Waiting for stack to be healthy (max 7 min) ==="
|
echo "=== Waiting for stack to be healthy (max 7 min) ==="
|
||||||
|
bash scripts/wait-for-service.sh http://ponder:42069/ 420 ponder
|
||||||
MAX_ATTEMPTS=84 # 84 * 5s = 420s = 7 minutes
|
bash scripts/wait-for-service.sh http://webapp:5173/app/ 420 webapp
|
||||||
ATTEMPT=0
|
bash scripts/wait-for-service.sh http://landing:5174/ 420 landing
|
||||||
|
bash scripts/wait-for-service.sh http://caddy:8081/app/ 420 caddy
|
||||||
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
|
||||||
ATTEMPT=$((ATTEMPT + 1))
|
|
||||||
PONDER_OK=0
|
|
||||||
WEBAPP_OK=0
|
|
||||||
LANDING_OK=0
|
|
||||||
CADDY_OK=0
|
|
||||||
|
|
||||||
# Check each service with verbose output on failure
|
|
||||||
# Ponder dev mode serves at root (/) - matches Dockerfile healthcheck
|
|
||||||
if curl -sf --max-time 3 http://ponder:42069/ > /dev/null 2>&1; then
|
|
||||||
PONDER_OK=1
|
|
||||||
fi
|
|
||||||
# Webapp configured with --base /app/
|
|
||||||
if curl -sf --max-time 3 http://webapp:5173/app/ > /dev/null 2>&1; then
|
|
||||||
WEBAPP_OK=1
|
|
||||||
fi
|
|
||||||
if curl -sf --max-time 3 http://landing:5174/ > /dev/null 2>&1; then
|
|
||||||
LANDING_OK=1
|
|
||||||
fi
|
|
||||||
# Caddy check: verify proxy is working by checking webapp through Caddy
|
|
||||||
# Use /app/ since it's a reliable known-good route (landing fallback can return 403 if not ready)
|
|
||||||
if curl -sf --max-time 3 http://caddy:8081/app/ > /dev/null 2>&1; then
|
|
||||||
CADDY_OK=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[$(date +%T)] ($ATTEMPT/$MAX_ATTEMPTS) ponder=$PONDER_OK webapp=$WEBAPP_OK landing=$LANDING_OK caddy=$CADDY_OK"
|
|
||||||
|
|
||||||
if [ "$PONDER_OK" = "1" ] && [ "$WEBAPP_OK" = "1" ] && [ "$LANDING_OK" = "1" ] && [ "$CADDY_OK" = "1" ]; then
|
|
||||||
echo "All services healthy!"
|
|
||||||
echo "=== Stack is healthy ==="
|
echo "=== Stack is healthy ==="
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "ERROR: Services did not become healthy within 7 minutes"
|
|
||||||
echo "Final status: ponder=$PONDER_OK webapp=$WEBAPP_OK landing=$LANDING_OK caddy=$CADDY_OK"
|
|
||||||
# Show more diagnostic info
|
|
||||||
echo "=== Diagnostic: checking individual endpoints ==="
|
|
||||||
echo "--- Ponder root (/) ---"
|
|
||||||
curl -v --max-time 5 http://ponder:42069/ 2>&1 | head -20 || true
|
|
||||||
echo "--- Webapp /app/ ---"
|
|
||||||
curl -v --max-time 5 http://webapp:5173/app/ 2>&1 | head -20 || true
|
|
||||||
echo "--- Landing / ---"
|
|
||||||
curl -v --max-time 5 http://landing:5174/ 2>&1 | head -20 || true
|
|
||||||
echo "--- Caddy / (landing via proxy) ---"
|
|
||||||
curl -v --max-time 5 http://caddy:8081/ 2>&1 | head -20 || true
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
# Step 3: Run E2E tests
|
# Step 3: Run E2E tests
|
||||||
- name: run-e2e-tests
|
- name: run-e2e-tests
|
||||||
|
|
@ -428,12 +271,12 @@ steps:
|
||||||
|
|
||||||
if [ -d playwright-report ]; then
|
if [ -d playwright-report ]; then
|
||||||
tar -czf artifacts/playwright-report.tgz playwright-report
|
tar -czf artifacts/playwright-report.tgz playwright-report
|
||||||
echo "✓ Playwright report archived"
|
echo "Playwright report archived"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -d test-results ]; then
|
if [ -d test-results ]; then
|
||||||
tar -czf artifacts/test-results.tgz test-results
|
tar -czf artifacts/test-results.tgz test-results
|
||||||
echo "✓ Test results archived"
|
echo "Test results archived"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ls -lh artifacts/ 2>/dev/null || echo "No artifacts"
|
ls -lh artifacts/ 2>/dev/null || echo "No artifacts"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ steps:
|
||||||
image: registry.niovi.voyage/harb/node-ci:latest
|
image: registry.niovi.voyage/harb/node-ci:latest
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile
|
yarn --cwd onchain/lib/uni-v3-lib install --frozen-lockfile
|
||||||
|
|
@ -20,7 +20,7 @@ steps:
|
||||||
image: registry.niovi.voyage/harb/node-ci:latest
|
image: registry.niovi.voyage/harb/node-ci:latest
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
if ! command -v bc >/dev/null 2>&1; then
|
if ! command -v bc >/dev/null 2>&1; then
|
||||||
apt-get update
|
apt-get update
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ steps:
|
||||||
event: tag
|
event: tag
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
corepack enable
|
corepack enable
|
||||||
|
|
@ -75,7 +75,7 @@ steps:
|
||||||
event: tag
|
event: tag
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
npm config set fund false
|
npm config set fund false
|
||||||
npm config set audit false
|
npm config set audit false
|
||||||
|
|
@ -125,7 +125,7 @@ steps:
|
||||||
from_secret: registry_password
|
from_secret: registry_password
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
bash -lc '
|
bash -c '
|
||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
if [ -z "${CI_COMMIT_TAG:-}" ]; then
|
if [ -z "${CI_COMMIT_TAG:-}" ]; then
|
||||||
echo "CI_COMMIT_TAG not set" >&2
|
echo "CI_COMMIT_TAG not set" >&2
|
||||||
|
|
|
||||||
81
AGENTS.md
81
AGENTS.md
|
|
@ -11,13 +11,33 @@
|
||||||
3. **Compete** - Snatch undervalued positions to optimise returns.
|
3. **Compete** - Snatch undervalued positions to optimise returns.
|
||||||
|
|
||||||
## Operating the Stack
|
## Operating the Stack
|
||||||
- Start everything with `nohup ./scripts/dev.sh start &` and stop via `./scripts/dev.sh stop`. Do not launch services individually.
|
|
||||||
- **Restart modes** for faster iteration:
|
### Quick Start
|
||||||
- `./scripts/dev.sh restart --light` - Fast restart (~10-20s): only webapp + txnbot, preserves Anvil/Ponder state. Use for frontend changes.
|
```bash
|
||||||
- `./scripts/dev.sh restart --full` - Full restart (~3-4min): redeploys contracts, fresh state. Use for contract changes.
|
nohup ./scripts/dev.sh start & # start (takes ~3-6 min first time)
|
||||||
- Supported environments: `BASE_SEPOLIA_LOCAL_FORK` (default Anvil fork), `BASE_SEPOLIA`, and `BASE`. Match contract addresses and RPCs accordingly.
|
tail -f nohup.out # watch progress
|
||||||
- The stack uses Docker containers orchestrated via docker-compose. The script boots Anvil, deploys contracts, seeds liquidity, starts Ponder, launches the landing site, and runs the txnBot. Wait for logs to settle before manual testing.
|
./scripts/dev.sh health # verify all services healthy
|
||||||
- **Prerequisites**: Docker Engine (Linux) or Colima (Mac). See installation instructions below.
|
./scripts/dev.sh stop # stop and clean up
|
||||||
|
```
|
||||||
|
Do not launch services individually — `dev.sh` enforces phased startup with health gates.
|
||||||
|
|
||||||
|
### Restart Modes
|
||||||
|
- `./scripts/dev.sh restart --light` — Fast (~10-20s): only webapp + txnbot, preserves Anvil/Ponder state. Use for frontend changes.
|
||||||
|
- `./scripts/dev.sh restart --full` — Full (~3-6min): redeploys contracts, fresh state. Use for contract changes.
|
||||||
|
|
||||||
|
### Common Pitfalls
|
||||||
|
- **Docker disk full**: `dev.sh start` refuses to run if Docker disk usage exceeds 20GB. Fix: `./scripts/dev.sh stop` (auto-prunes) or `docker system prune -af --volumes`.
|
||||||
|
- **Stale Ponder state**: If Ponder fails with schema errors after contract changes, delete its state: `rm -rf services/ponder/.ponder/` then `./scripts/dev.sh restart --full`.
|
||||||
|
- **kraiken-lib out of date**: If services fail with import errors or missing exports, rebuild: `./scripts/build-kraiken-lib.sh`. The dev script does this automatically on `start`, but manual rebuilds are needed if you change kraiken-lib while the stack is already running.
|
||||||
|
- **Container not found errors**: `dev.sh` expects Docker Compose v2 container names (`harb-anvil-1`, hyphens not underscores). Verify with `docker compose version`.
|
||||||
|
- **Port conflicts**: The stack uses ports 8545 (Anvil), 5173 (webapp), 5174 (landing), 42069 (Ponder), 43069 (txnBot), 8081 (Caddy). Check with `lsof -i :<port>` if startup fails.
|
||||||
|
- **npm ci failures in containers**: Named Docker volumes cache `node_modules/`. If dependencies change and installs fail, remove the volume: `docker volume rm harb_webapp_node_modules` (or similar), then restart.
|
||||||
|
|
||||||
|
### Environments
|
||||||
|
Supported: `BASE_SEPOLIA_LOCAL_FORK` (default Anvil fork), `BASE_SEPOLIA`, and `BASE`. Match contract addresses and RPCs accordingly.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
Docker Engine (Linux) or Colima (Mac). See `docs/docker.md` for installation.
|
||||||
|
|
||||||
## Component Guides
|
## Component Guides
|
||||||
- `onchain/` - Solidity + Foundry contracts, deploy scripts, and fuzzing helpers ([details](onchain/AGENTS.md)).
|
- `onchain/` - Solidity + Foundry contracts, deploy scripts, and fuzzing helpers ([details](onchain/AGENTS.md)).
|
||||||
|
|
@ -30,13 +50,13 @@
|
||||||
- Contracts: run `forge build`, `forge test`, and `forge snapshot` inside `onchain/`.
|
- Contracts: run `forge build`, `forge test`, and `forge snapshot` inside `onchain/`.
|
||||||
- Fuzzing: scripts under `onchain/analysis/` (e.g., `./analysis/run-fuzzing.sh [optimizer] debugCSV`) generate replayable scenarios.
|
- Fuzzing: scripts under `onchain/analysis/` (e.g., `./analysis/run-fuzzing.sh [optimizer] debugCSV`) generate replayable scenarios.
|
||||||
- Integration: after the stack boots, inspect Anvil logs, hit `http://localhost:8081/api/graphql` for Ponder, and poll `http://localhost:8081/api/txn/status` for txnBot health.
|
- Integration: after the stack boots, inspect Anvil logs, hit `http://localhost:8081/api/graphql` for Ponder, and poll `http://localhost:8081/api/txn/status` for txnBot health.
|
||||||
- **E2E Tests**: Playwright-based full-stack tests in `tests/e2e/` verify complete user journeys (mint ETH → swap KRK → stake). Run with `npm run test:e2e` from repo root. Tests use mocked wallet provider with Anvil accounts and automatically start/stop the stack. See `INTEGRATION_TEST_STATUS.md` and `SWAP_VERIFICATION.md` for details.
|
- **E2E Tests**: Playwright-based full-stack tests in `tests/e2e/` verify complete user journeys (mint ETH → swap KRK → stake). Run with `npm run test:e2e` from repo root. Tests use mocked wallet provider with Anvil accounts. In CI, the Woodpecker e2e pipeline runs these against pre-built service images.
|
||||||
|
|
||||||
## Version Validation System
|
## Version Validation System
|
||||||
- **Contract VERSION**: `Kraiken.sol` exposes a `VERSION` constant (currently v1) that must be incremented for breaking changes to TAX_RATES, events, or core data structures.
|
- **Contract VERSION**: `Kraiken.sol` exposes a `VERSION` constant (currently v1) that must be incremented for breaking changes to TAX_RATES, events, or core data structures.
|
||||||
- **Ponder Validation**: On startup, Ponder reads the contract VERSION and validates against `COMPATIBLE_CONTRACT_VERSIONS` in `kraiken-lib/src/version.ts`. Fails hard (exit 1) on mismatch to prevent indexing wrong data.
|
- **Ponder Validation**: On startup, Ponder reads the contract VERSION and validates against `COMPATIBLE_CONTRACT_VERSIONS` in `kraiken-lib/src/version.ts`. Fails hard (exit 1) on mismatch to prevent indexing wrong data.
|
||||||
- **Frontend Check**: Web-app validates `KRAIKEN_LIB_VERSION` at runtime (currently placeholder; future: query Ponder GraphQL for full 3-way validation).
|
- **Frontend Check**: Web-app validates `KRAIKEN_LIB_VERSION` at runtime (currently placeholder; future: query Ponder GraphQL for full 3-way validation).
|
||||||
- **CI Enforcement**: GitHub workflow validates that contract VERSION is in `COMPATIBLE_CONTRACT_VERSIONS` before merging PRs.
|
- **CI Enforcement**: Woodpecker `release.yml` pipeline validates that contract VERSION matches `COMPATIBLE_CONTRACT_VERSIONS` before release.
|
||||||
- See `VERSION_VALIDATION.md` for complete architecture, workflows, and troubleshooting.
|
- See `VERSION_VALIDATION.md` for complete architecture, workflows, and troubleshooting.
|
||||||
|
|
||||||
## Docker Installation & Setup
|
## Docker Installation & Setup
|
||||||
|
|
@ -48,7 +68,8 @@
|
||||||
docker ps # verify installation
|
docker ps # verify installation
|
||||||
```
|
```
|
||||||
- **Container Orchestration**: `docker-compose.yml` has NO `depends_on` declarations. All service ordering is handled in `scripts/dev.sh` via phased startup with explicit health checks.
|
- **Container Orchestration**: `docker-compose.yml` has NO `depends_on` declarations. All service ordering is handled in `scripts/dev.sh` via phased startup with explicit health checks.
|
||||||
- **Startup Phases**: (1) Create all containers, (2) Start anvil+postgres and wait for healthy, (3) Start bootstrap and wait for completion, (4) Start ponder and wait for healthy, (5) Start webapp/landing/txn-bot, (6) Start caddy.
|
- **Startup Phases**: (1) Start anvil+postgres and wait for healthy, (2) Start bootstrap and wait for exit, (3) Start ponder and wait for healthy, (4) Start webapp/landing/txn-bot, (5) Start caddy, (6) Smoke test via `scripts/wait-for-service.sh`.
|
||||||
|
- **Shared Bootstrap**: Contract deployment, seeding, and funding logic lives in `scripts/bootstrap-common.sh`, sourced by both `containers/bootstrap.sh` (local dev) and `scripts/ci-bootstrap.sh` (CI). Constants (FEE_DEST, WETH, SWAP_ROUTER, default keys) are defined once there.
|
||||||
- **Logging Configuration**: All services have log rotation configured (max 10MB per file, 3 files max = 30MB per container) to prevent disk bloat. Logs are automatically rotated by Docker.
|
- **Logging Configuration**: All services have log rotation configured (max 10MB per file, 3 files max = 30MB per container) to prevent disk bloat. Logs are automatically rotated by Docker.
|
||||||
- **Disk Management** (Portable, No Per-Machine Setup Required):
|
- **Disk Management** (Portable, No Per-Machine Setup Required):
|
||||||
- **20GB Hard Limit**: The stack enforces a 20GB total Docker disk usage limit (images + containers + volumes + build cache).
|
- **20GB Hard Limit**: The stack enforces a 20GB total Docker disk usage limit (images + containers + volumes + build cache).
|
||||||
|
|
@ -98,8 +119,8 @@
|
||||||
- **Logs**: `journalctl -u woodpecker-server -f` (NOT `docker logs`)
|
- **Logs**: `journalctl -u woodpecker-server -f` (NOT `docker logs`)
|
||||||
|
|
||||||
### Pipeline Configs
|
### Pipeline Configs
|
||||||
- `.woodpecker/build-ci-images.yml` — Builds Docker CI images. Triggers on **push** to `master` or `feature/ci` when files in `docker/`, `.woodpecker/`, `kraiken-lib/`, `onchain/out/`, or `web-app/` change.
|
- `.woodpecker/build-ci-images.yml` — Builds Docker CI images using unified `docker/Dockerfile.service-ci`. Triggers on **push** to `master` or `feature/ci` when files in `docker/`, `.woodpecker/`, `containers/`, `kraiken-lib/`, `onchain/`, `services/`, `web-app/`, or `landing/` change.
|
||||||
- `.woodpecker/e2e.yml` — Runs Playwright E2E tests. Triggers on **pull_request** to `master`.
|
- `.woodpecker/e2e.yml` — Runs Playwright E2E tests. Bootstrap step sources `scripts/bootstrap-common.sh` for shared deploy/seed logic. Health checks use `scripts/wait-for-service.sh`. Triggers on **pull_request** to `master`.
|
||||||
- Pipeline numbering: even = build-ci-images (push events), odd = E2E (pull_request events). This is not guaranteed but was the observed pattern.
|
- Pipeline numbering: even = build-ci-images (push events), odd = E2E (pull_request events). This is not guaranteed but was the observed pattern.
|
||||||
|
|
||||||
### Monitoring Pipelines via DB
|
### Monitoring Pipelines via DB
|
||||||
|
|
@ -133,17 +154,47 @@ PGPASSWORD='<db_password>' psql -h 127.0.0.1 -U woodpecker -d woodpecker -c \
|
||||||
- **API auth limitation**: The server caches user token hashes in memory. Inserting a token directly into the DB does not work without restarting the server (`sudo systemctl restart woodpecker-server`).
|
- **API auth limitation**: The server caches user token hashes in memory. Inserting a token directly into the DB does not work without restarting the server (`sudo systemctl restart woodpecker-server`).
|
||||||
|
|
||||||
### CI Docker Images
|
### CI Docker Images
|
||||||
- `docker/Dockerfile.webapp-ci` — Webapp CI image with Vite dev server.
|
- `docker/Dockerfile.service-ci` — Unified parameterized Dockerfile for all service CI images (ponder, webapp, landing, txnBot). Uses `--build-arg` for service-specific configuration (SERVICE_DIR, SERVICE_PORT, ENTRYPOINT_SCRIPT, NEEDS_SYMLINKS, etc.).
|
||||||
- **Symlinks fix** (lines 57-59): Creates `/web-app`, `/kraiken-lib`, `/onchain` symlinks to work around Vite's `removeBase()` stripping `/app/` prefix from filesystem paths.
|
- **sync-tax-rates**: Builder stage runs `scripts/sync-tax-rates.mjs` to sync tax rates from `Stake.sol` into kraiken-lib before TypeScript compilation.
|
||||||
|
- **Symlinks fix** (webapp only, `NEEDS_SYMLINKS=true`): Creates `/web-app`, `/kraiken-lib`, `/onchain` symlinks to work around Vite's `removeBase()` stripping `/app/` prefix from filesystem paths.
|
||||||
- **CI env detection** (`CI=true`): Disables Vue DevTools plugin in `vite.config.ts` to prevent 500 errors caused by path resolution issues with `/app/` base path.
|
- **CI env detection** (`CI=true`): Disables Vue DevTools plugin in `vite.config.ts` to prevent 500 errors caused by path resolution issues with `/app/` base path.
|
||||||
- **HEALTHCHECK**: `--retries=84 --interval=5s` = 420s (7 min) total wait, aligned with `wait-for-stack` step timeout.
|
- **HEALTHCHECK**: Configurable via build args; webapp uses `--retries=84 --interval=5s` = 420s (7 min), aligned with `wait-for-stack` step timeout.
|
||||||
|
- **Shared entrypoints**: Each service uses a unified entrypoint script (`containers/<service>-entrypoint.sh`) that branches on `CI=true` env var for CI vs local dev paths. Common helpers in `containers/entrypoint-common.sh`.
|
||||||
|
- **Shared bootstrap**: `scripts/bootstrap-common.sh` contains shared contract deployment, seeding, and funding functions used by both `containers/bootstrap.sh` (local dev) and `.woodpecker/e2e.yml` (CI).
|
||||||
- CI images are tagged with git SHA and `latest`, pushed to a local registry.
|
- CI images are tagged with git SHA and `latest`, pushed to a local registry.
|
||||||
|
|
||||||
|
### CI Agent & Registry Auth
|
||||||
|
- **Agent**: Runs as user `ci` (uid 1001) on `harb-staging`, same host as the dev environment. Binary at `/usr/local/bin/woodpecker-agent`.
|
||||||
|
- **Registry credentials**: The `ci` user must have Docker auth configured at `/home/ci/.docker/config.json` to pull private images from `registry.niovi.voyage`. If images fail to pull with "no basic auth credentials", fix with:
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /home/ci/.docker
|
||||||
|
sudo cp /home/debian/.docker/config.json /home/ci/.docker/config.json
|
||||||
|
sudo chown -R ci:ci /home/ci/.docker
|
||||||
|
sudo chmod 600 /home/ci/.docker/config.json
|
||||||
|
```
|
||||||
|
- **Shared Docker daemon**: The `ci` and `debian` users share the same Docker daemon. Running `docker system prune` as `debian` removes images cached for CI pipelines. If CI image pulls fail after a prune, either fix registry auth (above) or pre-pull images as `debian`: `docker pull registry.niovi.voyage/harb/ponder-ci:latest` etc.
|
||||||
|
|
||||||
### CI Debugging Tips
|
### CI Debugging Tips
|
||||||
- If pipelines aren't being created after a push, check Codeberg webhook delivery logs first.
|
- If pipelines aren't being created after a push, check Codeberg webhook delivery logs first.
|
||||||
- The Woodpecker server needs `sudo` to restart. Without it, you cannot: refresh API tokens, clear cached state, or recover from webhook auth issues.
|
- The Woodpecker server needs `sudo` to restart. Without it, you cannot: refresh API tokens, clear cached state, or recover from webhook auth issues.
|
||||||
- E2E pipeline failures often come from `wait-for-stack` timing out. Check the webapp HEALTHCHECK alignment and Ponder indexing time.
|
- E2E pipeline failures often come from `wait-for-stack` timing out. Check the webapp HEALTHCHECK alignment and Ponder indexing time.
|
||||||
- The `web-app/vite.config.ts` `allowedHosts` array must include container hostnames (`webapp`, `caddy`) for health checks to succeed inside Docker networks.
|
- The `web-app/vite.config.ts` `allowedHosts` array must include container hostnames (`webapp`, `caddy`) for health checks to succeed inside Docker networks.
|
||||||
|
- **Never use `bash -lc`** in Woodpecker pipeline commands — login shell resets PATH via `/etc/profile`, losing Foundry and other tools set by Docker ENV. Use `bash -c` instead.
|
||||||
|
|
||||||
|
## Codeberg API Access
|
||||||
|
- **Auth**: Codeberg API tokens are stored in `~/.netrc` (standard `curl --netrc` format, `chmod 600`):
|
||||||
|
```
|
||||||
|
machine codeberg.org
|
||||||
|
login johba
|
||||||
|
password <api-token>
|
||||||
|
```
|
||||||
|
The `password` field holds the API token — this is standard `.netrc` convention, not an actual password.
|
||||||
|
- **Generate tokens** at `https://codeberg.org/user/settings/applications`.
|
||||||
|
- **Usage**: Pass `--netrc` to curl for authenticated Codeberg API calls:
|
||||||
|
```bash
|
||||||
|
curl --netrc -s https://codeberg.org/api/v1/repos/johba/harb/issues | jq '.[0].title'
|
||||||
|
```
|
||||||
|
- **Note**: The repo uses SSH for git push/pull (`ssh://git@codeberg.org`), so `.netrc` is only used for REST API interactions (issues, PRs, releases).
|
||||||
|
|
||||||
## References
|
## References
|
||||||
- Deployment history: `onchain/deployments-local.json`, `onchain/broadcast/`.
|
- Deployment history: `onchain/deployments-local.json`, `onchain/broadcast/`.
|
||||||
|
|
|
||||||
|
|
@ -1,264 +0,0 @@
|
||||||
# Changelog: Version Validation System & Tax Rate Index Refactoring
|
|
||||||
|
|
||||||
## Date: 2025-10-07
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
This release implements a comprehensive version validation system to ensure contract-indexer-frontend compatibility and completes the tax rate index refactoring to eliminate fragile decimal lookups.
|
|
||||||
|
|
||||||
## Major Features
|
|
||||||
|
|
||||||
### 1. Version Validation System
|
|
||||||
|
|
||||||
**Contract Changes:**
|
|
||||||
- `onchain/src/Kraiken.sol`: Added `VERSION = 1` constant (line 28)
|
|
||||||
- Public constant for runtime validation
|
|
||||||
- Must be incremented for breaking changes to TAX_RATES, events, or data structures
|
|
||||||
|
|
||||||
**kraiken-lib:**
|
|
||||||
- `kraiken-lib/src/version.ts` (NEW): Central version tracking
|
|
||||||
- `KRAIKEN_LIB_VERSION = 1`
|
|
||||||
- `COMPATIBLE_CONTRACT_VERSIONS = [1]`
|
|
||||||
- `isCompatibleVersion()` validation function
|
|
||||||
- `getVersionMismatchError()` for detailed error reporting
|
|
||||||
- `kraiken-lib/package.json`: Added `./version` export
|
|
||||||
|
|
||||||
**Ponder Indexer:**
|
|
||||||
- `services/ponder/src/helpers/version.ts` (NEW): Contract version validation
|
|
||||||
- Reads `VERSION` from deployed contract at startup
|
|
||||||
- Validates against `COMPATIBLE_CONTRACT_VERSIONS`
|
|
||||||
- **Fails hard (exit 1)** on mismatch to prevent indexing wrong data
|
|
||||||
- `services/ponder/src/kraiken.ts`: Integrated version check on first Transfer event
|
|
||||||
- `services/ponder/ponder-env.d.ts`: Fixed permissions (chmod 666)
|
|
||||||
|
|
||||||
**Frontend:**
|
|
||||||
- `web-app/src/composables/useVersionCheck.ts` (NEW): Version validation composable
|
|
||||||
- Validates `KRAIKEN_LIB_VERSION` loads correctly
|
|
||||||
- Placeholder for future GraphQL-based 3-way validation
|
|
||||||
- Warns (doesn't fail) on mismatch
|
|
||||||
|
|
||||||
**CI/CD:**
|
|
||||||
- `.github/workflows/validate-version.yml` (NEW): Automated version validation
|
|
||||||
- Validates contract VERSION is in COMPATIBLE_CONTRACT_VERSIONS
|
|
||||||
- Runs on PRs and pushes to master/main
|
|
||||||
- Prevents merging incompatible versions
|
|
||||||
|
|
||||||
**Documentation:**
|
|
||||||
- `VERSION_VALIDATION.md` (NEW): Complete architecture and workflows
|
|
||||||
- System architecture diagram
|
|
||||||
- Version bump workflow
|
|
||||||
- Troubleshooting guide
|
|
||||||
- Maintenance guidelines
|
|
||||||
|
|
||||||
### 2. Container Orchestration Fix
|
|
||||||
|
|
||||||
**Problem:** Container dependency graph validator can fail with "container not found in input list" errors when containers have `depends_on` metadata.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
- `docker-compose.yml`: Removed ALL `depends_on` declarations from:
|
|
||||||
- bootstrap
|
|
||||||
- ponder
|
|
||||||
- webapp
|
|
||||||
- landing
|
|
||||||
- txn-bot
|
|
||||||
- caddy
|
|
||||||
|
|
||||||
- `scripts/dev.sh`: Implemented phased startup with explicit health checks:
|
|
||||||
1. Create all containers (`docker-compose up --no-start`)
|
|
||||||
2. Start anvil & postgres, wait for healthy
|
|
||||||
3. Start bootstrap, wait for completion
|
|
||||||
4. Start ponder, wait for healthy
|
|
||||||
5. Start webapp/landing/txn-bot
|
|
||||||
6. Start caddy
|
|
||||||
|
|
||||||
**Result:** Stack starts reliably without dependency graph errors.
|
|
||||||
|
|
||||||
### 3. Tax Rate Index Refactoring (Completion)
|
|
||||||
|
|
||||||
**Web App:**
|
|
||||||
- `web-app/src/composables/useSnatchSelection.ts`:
|
|
||||||
- Replaced `position.taxRate >= maxTaxRateDecimal` with `posIndex >= selectedTaxRateIndex`
|
|
||||||
- Fixed test data to match index-based logic
|
|
||||||
|
|
||||||
- `web-app/src/composables/usePositions.ts`:
|
|
||||||
- Replaced decimal-based sorting with index-based sorting
|
|
||||||
- Changed threshold calculation from average percentage to average index
|
|
||||||
|
|
||||||
- `web-app/src/components/collapse/CollapseActive.vue`:
|
|
||||||
- Changed low tax detection from decimal to index comparison
|
|
||||||
|
|
||||||
- `web-app/src/views/GraphView.vue`: **DELETED** (dead code, 63 lines)
|
|
||||||
|
|
||||||
**Ponder:**
|
|
||||||
- `services/ponder/ponder.schema.ts`:
|
|
||||||
- **CRITICAL FIX**: Import `TAX_RATE_OPTIONS` from kraiken-lib instead of hardcoded array
|
|
||||||
- Added `taxRateIndex` column to positions table
|
|
||||||
- Added index on `taxRateIndex` column
|
|
||||||
|
|
||||||
- `services/ponder/src/stake.ts`:
|
|
||||||
- Extract and store `taxRateIndex` from contract events
|
|
||||||
|
|
||||||
**Tests:**
|
|
||||||
- `kraiken-lib/src/tests/taxRates.test.ts`: Fixed Jest ES module compatibility
|
|
||||||
- `kraiken-lib/jest.config.js` → `kraiken-lib/jest.config.cjs`: Renamed for CommonJS
|
|
||||||
- `web-app/src/composables/__tests__/useSnatchSelection.spec.ts`: Fixed test data inconsistencies
|
|
||||||
|
|
||||||
## File Changes
|
|
||||||
|
|
||||||
### Added Files (7)
|
|
||||||
1. `.github/workflows/validate-version.yml` - CI/CD validation
|
|
||||||
2. `VERSION_VALIDATION.md` - Documentation
|
|
||||||
3. `kraiken-lib/src/version.ts` - Version tracking
|
|
||||||
4. `kraiken-lib/jest.config.cjs` - Jest config
|
|
||||||
5. `services/ponder/src/helpers/version.ts` - Ponder validation
|
|
||||||
6. `web-app/src/composables/useVersionCheck.ts` - Frontend validation
|
|
||||||
7. `scripts/sync-tax-rates.mjs` - Tax rate sync script
|
|
||||||
|
|
||||||
### Deleted Files (2)
|
|
||||||
1. `web-app/src/views/GraphView.vue` - Dead code
|
|
||||||
2. `kraiken-lib/jest.config.js` - Replaced with .cjs
|
|
||||||
|
|
||||||
### Modified Files (29)
|
|
||||||
1. `.gitignore` - Added test artifacts, logs, ponder state
|
|
||||||
2. `CLAUDE.md` - Added Version Validation and Podman Orchestration sections
|
|
||||||
3. `kraiken-lib/AGENTS.md` - Added version.ts to Key Modules
|
|
||||||
4. `kraiken-lib/package.json` - Added ./version export
|
|
||||||
5. `kraiken-lib/src/index.ts` - Export version validation functions
|
|
||||||
6. `kraiken-lib/src/taxRates.ts` - Generated tax rates with checksums
|
|
||||||
7. `kraiken-lib/src/tests/taxRates.test.ts` - Fixed Jest compatibility
|
|
||||||
8. `onchain/src/Kraiken.sol` - Added VERSION constant
|
|
||||||
9. `docker-compose.yml` - Removed all depends_on declarations
|
|
||||||
10. `scripts/build-kraiken-lib.sh` - Updated build process
|
|
||||||
11. `scripts/dev.sh` - Implemented phased startup
|
|
||||||
12. `services/ponder/AGENTS.md` - Updated documentation
|
|
||||||
13. `services/ponder/ponder-env.d.ts` - Fixed permissions
|
|
||||||
14. `services/ponder/ponder.schema.ts` - Import from kraiken-lib, add taxRateIndex
|
|
||||||
15. `services/ponder/src/kraiken.ts` - Added version validation
|
|
||||||
16. `services/ponder/src/stake.ts` - Store taxRateIndex
|
|
||||||
17. `tests/e2e/01-acquire-and-stake.spec.ts` - Test updates
|
|
||||||
18. `web-app/README.md` - Documentation updates
|
|
||||||
19. `web-app/env.d.ts` - Type updates
|
|
||||||
20. `web-app/src/components/StakeHolder.vue` - Index-based logic
|
|
||||||
21. `web-app/src/components/collapse/CollapseActive.vue` - Index comparison
|
|
||||||
22. `web-app/src/components/fcomponents/FSelect.vue` - Index handling
|
|
||||||
23. `web-app/src/composables/__tests__/useSnatchSelection.spec.ts` - Fixed tests
|
|
||||||
24. `web-app/src/composables/useAdjustTaxRates.ts` - Index-based adjustments
|
|
||||||
25. `web-app/src/composables/usePositions.ts` - Index-based sorting and threshold
|
|
||||||
26. `web-app/src/composables/useSnatchSelection.ts` - Index-based filtering
|
|
||||||
27. `web-app/src/composables/useStake.ts` - Index handling
|
|
||||||
28-29. Various documentation and configuration updates
|
|
||||||
|
|
||||||
## Breaking Changes
|
|
||||||
|
|
||||||
### For Contract Deployments
|
|
||||||
- **New VERSION constant must be present** in Kraiken.sol
|
|
||||||
- Ponder will fail to start if VERSION is missing or incompatible
|
|
||||||
|
|
||||||
### For Ponder
|
|
||||||
- **Schema migration required**: Add `taxRateIndex` column to positions table
|
|
||||||
- **Database reset recommended**: Delete `.ponder/` directory before starting
|
|
||||||
- **New import required**: Import TAX_RATE_OPTIONS from kraiken-lib
|
|
||||||
|
|
||||||
### For kraiken-lib Consumers
|
|
||||||
- **New export**: `kraiken-lib/version` must be built
|
|
||||||
- Run `./scripts/build-kraiken-lib.sh` to regenerate dist/
|
|
||||||
|
|
||||||
## Migration Guide
|
|
||||||
|
|
||||||
### Updating to This Version
|
|
||||||
|
|
||||||
1. **Stop the stack:**
|
|
||||||
```bash
|
|
||||||
./scripts/dev.sh stop
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Clean Ponder state:**
|
|
||||||
```bash
|
|
||||||
rm -rf services/ponder/.ponder/
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Rebuild kraiken-lib:**
|
|
||||||
```bash
|
|
||||||
./scripts/build-kraiken-lib.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Rebuild contracts (if needed):**
|
|
||||||
```bash
|
|
||||||
cd onchain && forge build
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Start the stack:**
|
|
||||||
```bash
|
|
||||||
./scripts/dev.sh start
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **Verify version validation:**
|
|
||||||
```bash
|
|
||||||
docker logs harb_ponder_1 | grep "version validated"
|
|
||||||
```
|
|
||||||
Should output: `✓ Contract version validated: v1 (kraiken-lib v1)`
|
|
||||||
|
|
||||||
### Future Version Bumps
|
|
||||||
|
|
||||||
When making breaking changes to TAX_RATES, events, or data structures:
|
|
||||||
|
|
||||||
1. **Increment VERSION in Kraiken.sol:**
|
|
||||||
```solidity
|
|
||||||
uint256 public constant VERSION = 2;
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Update COMPATIBLE_CONTRACT_VERSIONS in kraiken-lib/src/version.ts:**
|
|
||||||
```typescript
|
|
||||||
export const KRAIKEN_LIB_VERSION = 2;
|
|
||||||
export const COMPATIBLE_CONTRACT_VERSIONS = [2]; // Or [1, 2] for backward compat
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Rebuild and redeploy:**
|
|
||||||
```bash
|
|
||||||
./scripts/build-kraiken-lib.sh
|
|
||||||
rm -rf services/ponder/.ponder/
|
|
||||||
cd onchain && forge script script/Deploy.s.sol
|
|
||||||
```
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
### Unit Tests
|
|
||||||
- ✅ kraiken-lib tests pass
|
|
||||||
- ✅ web-app tests pass
|
|
||||||
- ✅ Ponder codegen succeeds
|
|
||||||
- ✅ onchain tests pass
|
|
||||||
|
|
||||||
### Integration Tests
|
|
||||||
- ✅ Stack starts without dependency errors
|
|
||||||
- ✅ Ponder validates contract version successfully
|
|
||||||
- ✅ Ponder indexes events with taxRateIndex
|
|
||||||
- ✅ GraphQL endpoint responds
|
|
||||||
- ✅ Version validation logs appear in Ponder output
|
|
||||||
|
|
||||||
### Manual Verification
|
|
||||||
```bash
|
|
||||||
# Check Ponder logs for version validation
|
|
||||||
docker logs harb_ponder_1 | grep "version validated"
|
|
||||||
# Output: ✓ Contract version validated: v1 (kraiken-lib v1)
|
|
||||||
|
|
||||||
# Check contract VERSION
|
|
||||||
cast call $KRAIKEN_ADDRESS "VERSION()" --rpc-url http://localhost:8545
|
|
||||||
# Output: 1
|
|
||||||
|
|
||||||
# Query positions with taxRateIndex
|
|
||||||
curl -X POST http://localhost:42069/graphql \
|
|
||||||
-d '{"query":"{ positions { id taxRateIndex taxRate } }"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Known Issues
|
|
||||||
|
|
||||||
None. All blocking issues resolved.
|
|
||||||
|
|
||||||
## Contributors
|
|
||||||
|
|
||||||
- Claude Code (Anthropic)
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- Full architecture: `VERSION_VALIDATION.md`
|
|
||||||
- Podman orchestration: `CLAUDE.md` § Podman Orchestration
|
|
||||||
- Tax rate system: `kraiken-lib/src/taxRates.ts`
|
|
||||||
249
CI_MIGRATION.md
249
CI_MIGRATION.md
|
|
@ -1,249 +0,0 @@
|
||||||
# CI Migration: Composite Integration Service (Option A)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The E2E pipeline has been refactored to use a **composite integration service** that bundles the entire Harb stack into a single Docker image. This eliminates Docker-in-Docker complexity and significantly speeds up CI runs.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Before (Docker-in-Docker)
|
|
||||||
```
|
|
||||||
Woodpecker Pipeline
|
|
||||||
├─ Service: docker:dind (privileged)
|
|
||||||
└─ Step: run-e2e
|
|
||||||
├─ Install docker CLI + docker-compose
|
|
||||||
├─ Run ./scripts/dev.sh start (nested containers)
|
|
||||||
│ ├─ anvil
|
|
||||||
│ ├─ postgres
|
|
||||||
│ ├─ bootstrap
|
|
||||||
│ ├─ ponder
|
|
||||||
│ ├─ webapp
|
|
||||||
│ ├─ landing
|
|
||||||
│ ├─ txn-bot
|
|
||||||
│ └─ caddy
|
|
||||||
└─ Run Playwright tests
|
|
||||||
```
|
|
||||||
|
|
||||||
**Issues**:
|
|
||||||
- ~3-5 minutes stack startup overhead per run
|
|
||||||
- Complex nested container management
|
|
||||||
- Docker-in-Docker reliability issues
|
|
||||||
- Dependency reinstallation in every step
|
|
||||||
|
|
||||||
### After (Composite Service)
|
|
||||||
```
|
|
||||||
Woodpecker Pipeline
|
|
||||||
├─ Service: harb/integration (contains full stack)
|
|
||||||
│ └─ Manages internal docker-compose lifecycle
|
|
||||||
├─ Step: wait-for-stack (30-60s)
|
|
||||||
└─ Step: run-e2e-tests (Playwright only)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits**:
|
|
||||||
- ✅ **3-5 minutes faster** - Stack starts in parallel with pipeline setup
|
|
||||||
- ✅ **Simpler** - No DinD complexity, standard service pattern
|
|
||||||
- ✅ **Reliable** - Single health check, clearer failure modes
|
|
||||||
- ✅ **Reusable** - Same image for local testing and CI
|
|
||||||
|
|
||||||
## Components
|
|
||||||
|
|
||||||
### 1. Integration Image (`docker/Dockerfile.integration`)
|
|
||||||
- Base: `docker:27-dind`
|
|
||||||
- Bundles: Full project + docker-compose
|
|
||||||
- Entrypoint: Starts dockerd + Harb stack automatically
|
|
||||||
- Healthcheck: Validates GraphQL endpoint is responsive
|
|
||||||
|
|
||||||
### 2. CI Compose File (`docker-compose.ci.yml`)
|
|
||||||
- Simplified interface for local testing
|
|
||||||
- Exposes port 8081 for stack access
|
|
||||||
- Persists Docker state in named volume
|
|
||||||
|
|
||||||
### 3. New E2E Pipeline (`.woodpecker/e2e-new.yml`)
|
|
||||||
- Service: `harb/integration` (stack)
|
|
||||||
- Step 1: Wait for stack health
|
|
||||||
- Step 2: Run Playwright tests
|
|
||||||
- Step 3: Collect artifacts
|
|
||||||
|
|
||||||
### 4. Build Script (`scripts/build-integration-image.sh`)
|
|
||||||
- Builds integration image
|
|
||||||
- Pushes to registry
|
|
||||||
- Includes local testing instructions
|
|
||||||
|
|
||||||
## Migration Steps
|
|
||||||
|
|
||||||
### 1. Build the Integration Image
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build locally
|
|
||||||
./scripts/build-integration-image.sh
|
|
||||||
|
|
||||||
# Or with custom registry
|
|
||||||
REGISTRY=localhost:5000 ./scripts/build-integration-image.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Push to Registry
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Login to registry (if using sovraigns.network registry)
|
|
||||||
docker login registry.sovraigns.network -u ciuser
|
|
||||||
|
|
||||||
# Push
|
|
||||||
docker push registry.sovraigns.network/harb/integration:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Activate New Pipeline
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backup old E2E pipeline
|
|
||||||
mv .woodpecker/e2e.yml .woodpecker/e2e-old.yml
|
|
||||||
|
|
||||||
# Activate new pipeline
|
|
||||||
mv .woodpecker/e2e-new.yml .woodpecker/e2e.yml
|
|
||||||
|
|
||||||
# Commit changes
|
|
||||||
git add .woodpecker/e2e.yml docker/ scripts/build-integration-image.sh
|
|
||||||
git commit -m "ci: migrate E2E to composite integration service"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Update CI Image Build Workflow
|
|
||||||
|
|
||||||
Add to release pipeline or create dedicated workflow:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# .woodpecker/build-ci-images.yml
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: build-integration-image
|
|
||||||
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- tag
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
- master
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build-and-push
|
|
||||||
image: docker:27-dind
|
|
||||||
privileged: true
|
|
||||||
environment:
|
|
||||||
DOCKER_HOST: tcp://docker:2375
|
|
||||||
REGISTRY_USER:
|
|
||||||
from_secret: registry_user
|
|
||||||
REGISTRY_PASSWORD:
|
|
||||||
from_secret: registry_password
|
|
||||||
commands:
|
|
||||||
- docker login registry.sovraigns.network -u $REGISTRY_USER -p $REGISTRY_PASSWORD
|
|
||||||
- ./scripts/build-integration-image.sh
|
|
||||||
- docker push registry.sovraigns.network/harb/integration:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Local Testing
|
|
||||||
|
|
||||||
### Test Integration Image Directly
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start the stack container
|
|
||||||
docker run --rm --privileged -p 8081:8081 \
|
|
||||||
registry.sovraigns.network/harb/integration:latest
|
|
||||||
|
|
||||||
# Wait for health (in another terminal)
|
|
||||||
curl http://localhost:8081/api/graphql
|
|
||||||
|
|
||||||
# Run E2E tests against it
|
|
||||||
npm run test:e2e
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test via docker-compose.ci.yml
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start stack
|
|
||||||
docker-compose -f docker-compose.ci.yml up -d
|
|
||||||
|
|
||||||
# Wait for healthy
|
|
||||||
docker-compose -f docker-compose.ci.yml ps
|
|
||||||
|
|
||||||
# Run tests
|
|
||||||
npm run test:e2e
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
docker-compose -f docker-compose.ci.yml down -v
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rollback Plan
|
|
||||||
|
|
||||||
If issues arise, revert to old pipeline:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Restore old pipeline
|
|
||||||
mv .woodpecker/e2e-old.yml .woodpecker/e2e.yml
|
|
||||||
|
|
||||||
# Commit
|
|
||||||
git add .woodpecker/e2e.yml
|
|
||||||
git commit -m "ci: rollback to DinD E2E pipeline"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Comparison
|
|
||||||
|
|
||||||
| Metric | Before (DinD) | After (Composite) | Improvement |
|
|
||||||
|--------|---------------|-------------------|-------------|
|
|
||||||
| Stack startup | ~180-240s | ~60-90s | **~2-3 min faster** |
|
|
||||||
| Total E2E time | ~8-10 min | ~5-6 min | **~40% faster** |
|
|
||||||
| Complexity | High (nested) | Low (standard) | Simpler |
|
|
||||||
| Reliability | Medium | High | More stable |
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Image build fails
|
|
||||||
```bash
|
|
||||||
# Check kraiken-lib builds successfully
|
|
||||||
./scripts/build-kraiken-lib.sh
|
|
||||||
|
|
||||||
# Build with verbose output
|
|
||||||
docker build -f docker/Dockerfile.integration --progress=plain .
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stack doesn't start in CI
|
|
||||||
```bash
|
|
||||||
# Check service logs in Woodpecker
|
|
||||||
# Services run detached, logs available via Woodpecker UI
|
|
||||||
|
|
||||||
# Test locally first
|
|
||||||
docker run --rm --privileged -p 8081:8081 \
|
|
||||||
registry.sovraigns.network/harb/integration:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Healthcheck times out
|
|
||||||
- Default timeout: 120s start period + 30 retries × 5s = ~270s max
|
|
||||||
- First run is slower (pulling images, building)
|
|
||||||
- Subsequent runs use cached layers (~60-90s)
|
|
||||||
|
|
||||||
## Future Improvements
|
|
||||||
|
|
||||||
1. **Multi-stage build** - Separate build and runtime images
|
|
||||||
2. **Layer caching** - Optimize Dockerfile for faster rebuilds
|
|
||||||
3. **Parallel services** - Start independent services concurrently
|
|
||||||
4. **Resource limits** - Add memory/CPU constraints for CI
|
|
||||||
5. **Image variants** - Separate images for different test suites
|
|
||||||
|
|
||||||
## Podman to Docker Migration
|
|
||||||
|
|
||||||
As part of this work, the Woodpecker agent was migrated from Podman to Docker:
|
|
||||||
|
|
||||||
**Changes made**:
|
|
||||||
- Updated `/etc/woodpecker/agent.env`:
|
|
||||||
- `WOODPECKER_BACKEND_DOCKER_HOST=unix:///var/run/docker.sock`
|
|
||||||
- Added `ci` user to `docker` group
|
|
||||||
- Restarted `woodpecker-agent` service
|
|
||||||
|
|
||||||
**Agent label update** (optional, cosmetic):
|
|
||||||
```bash
|
|
||||||
# /etc/woodpecker/agent.env
|
|
||||||
WOODPECKER_AGENT_LABELS=docker=true # (was podman=true)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Questions?
|
|
||||||
|
|
||||||
See `CLAUDE.md` for overall stack architecture and `INTEGRATION_TEST_STATUS.md` for E2E test details.
|
|
||||||
|
|
@ -1,260 +0,0 @@
|
||||||
# ✅ CI Migration Complete
|
|
||||||
|
|
||||||
**Date**: 2025-11-20
|
|
||||||
**Branch**: feature/ci
|
|
||||||
**Commit**: 8c6b6c4
|
|
||||||
**Status**: **READY FOR TESTING**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## All Steps Completed ✅
|
|
||||||
|
|
||||||
### 1. Podman → Docker Migration ✅
|
|
||||||
- ✅ Updated `/etc/woodpecker/agent.env` to use Docker socket
|
|
||||||
- ✅ Added `ci` user to `docker` group
|
|
||||||
- ✅ Restarted Woodpecker agent
|
|
||||||
- ✅ Verified agent running with Docker backend
|
|
||||||
|
|
||||||
### 2. Composite Integration Service Created ✅
|
|
||||||
- ✅ `docker/Dockerfile.integration` - Self-contained stack image
|
|
||||||
- ✅ `docker/integration-entrypoint.sh` - Orchestration script
|
|
||||||
- ✅ `docker-compose.ci.yml` - Local testing interface
|
|
||||||
- ✅ `scripts/build-integration-image.sh` - Build automation
|
|
||||||
- ✅ `.woodpecker/e2e.yml` - Refactored E2E pipeline
|
|
||||||
|
|
||||||
### 3. Documentation Complete ✅
|
|
||||||
- ✅ `CI_MIGRATION.md` - Technical documentation
|
|
||||||
- ✅ `MIGRATION_SUMMARY.md` - Executive summary
|
|
||||||
- ✅ `QUICKSTART_MIGRATION.md` - Testing guide
|
|
||||||
- ✅ `MIGRATION_STATUS.md` - Status report
|
|
||||||
- ✅ `MIGRATION_COMPLETE.md` - This file
|
|
||||||
|
|
||||||
### 4. Integration Image Built ✅
|
|
||||||
```
|
|
||||||
Image: registry.sovraigns.network/harb/integration:latest
|
|
||||||
Digest: sha256:0543d2466680f4860e77789d5f3d16e7fb02527221b2ec6e3461381d7b207a2c
|
|
||||||
Size: 515MB (491MB compressed)
|
|
||||||
Status: Built and pushed to registry
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Image Pushed to Registry ✅
|
|
||||||
- ✅ Logged in to `registry.sovraigns.network`
|
|
||||||
- ✅ Pushed `harb/integration:latest`
|
|
||||||
- ✅ Verified image in registry catalog
|
|
||||||
|
|
||||||
### 6. Pipeline Activated ✅
|
|
||||||
- ✅ Backed up old pipeline to `.woodpecker/e2e-old.yml`
|
|
||||||
- ✅ Activated new pipeline in `.woodpecker/e2e.yml`
|
|
||||||
- ✅ All changes committed to git (commit 8c6b6c4)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What Changed
|
|
||||||
|
|
||||||
### Files Modified/Created (10 files, +1067/-97 lines)
|
|
||||||
```
|
|
||||||
M .dockerignore (updated excludes)
|
|
||||||
A .woodpecker/e2e-old.yml (backup of old DinD pipeline)
|
|
||||||
M .woodpecker/e2e.yml (new composite service pipeline)
|
|
||||||
A CI_MIGRATION.md (technical docs)
|
|
||||||
A MIGRATION_SUMMARY.md (executive summary)
|
|
||||||
A QUICKSTART_MIGRATION.md (testing guide)
|
|
||||||
A MIGRATION_STATUS.md (status report)
|
|
||||||
A docker-compose.ci.yml (local testing)
|
|
||||||
A docker/Dockerfile.integration (integration image)
|
|
||||||
A docker/integration-entrypoint.sh (entrypoint script)
|
|
||||||
A scripts/build-integration-image.sh (build script)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Architecture Changes
|
|
||||||
|
|
||||||
**Before (Docker-in-Docker)**:
|
|
||||||
```
|
|
||||||
Woodpecker Pipeline
|
|
||||||
└─ Service: docker:dind
|
|
||||||
└─ Step: run-e2e
|
|
||||||
├─ Install docker CLI + docker-compose
|
|
||||||
├─ ./scripts/dev.sh start (8 nested containers)
|
|
||||||
└─ npx playwright test
|
|
||||||
|
|
||||||
Time: ~8-10 minutes
|
|
||||||
Complexity: High (nested containers)
|
|
||||||
```
|
|
||||||
|
|
||||||
**After (Composite Service)**:
|
|
||||||
```
|
|
||||||
Woodpecker Pipeline
|
|
||||||
├─ Service: harb/integration (full stack)
|
|
||||||
└─ Steps:
|
|
||||||
├─ wait-for-stack (~60-90s)
|
|
||||||
└─ run-e2e-tests
|
|
||||||
|
|
||||||
Time: ~5-6 minutes
|
|
||||||
Complexity: Low (single service)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
### 1. Push Branch (if not already done)
|
|
||||||
```bash
|
|
||||||
git push origin feature/ci
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Test E2E Pipeline
|
|
||||||
|
|
||||||
The new E2E pipeline will automatically trigger on pull requests. To test:
|
|
||||||
|
|
||||||
**Option A: Create PR**
|
|
||||||
```bash
|
|
||||||
# Create PR from feature/ci to master
|
|
||||||
# Woodpecker will automatically run the new E2E pipeline
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option B: Manual trigger**
|
|
||||||
- Go to Woodpecker UI: https://ci.sovraigns.network
|
|
||||||
- Navigate to `johba/harb`
|
|
||||||
- Manually trigger pipeline for `feature/ci` branch
|
|
||||||
|
|
||||||
### 3. Monitor First Run
|
|
||||||
|
|
||||||
Watch the pipeline execution:
|
|
||||||
- **Service start**: `stack` service should become healthy in ~60-90s
|
|
||||||
- **Step 1**: `wait-for-stack` should succeed
|
|
||||||
- **Step 2**: `run-e2e-tests` should run Playwright tests
|
|
||||||
- **Step 3**: `collect-artifacts` should gather results
|
|
||||||
|
|
||||||
**Expected total time**: ~5-6 minutes (vs. old ~8-10 minutes)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Improvements
|
|
||||||
|
|
||||||
| Metric | Before | After | Improvement |
|
|
||||||
|--------|--------|-------|-------------|
|
|
||||||
| Stack startup | 180-240s | 60-90s | **~2-3 min faster** |
|
|
||||||
| Total E2E time | 8-10 min | 5-6 min | **~40% faster** |
|
|
||||||
| Complexity | High (DinD + 8 nested) | Low (1 service) | **Much simpler** |
|
|
||||||
| Code duplication | 100% | 0% | **Eliminated** |
|
|
||||||
| Reliability | Medium | High | **More stable** |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Verification Checklist
|
|
||||||
|
|
||||||
- [x] Podman → Docker migration complete
|
|
||||||
- [x] Agent running with Docker backend
|
|
||||||
- [x] Integration Dockerfile created
|
|
||||||
- [x] docker-compose.ci.yml created
|
|
||||||
- [x] Build script created
|
|
||||||
- [x] New E2E pipeline created
|
|
||||||
- [x] Documentation complete
|
|
||||||
- [x] Integration image built successfully
|
|
||||||
- [x] Image pushed to registry
|
|
||||||
- [x] Old pipeline backed up
|
|
||||||
- [x] New pipeline activated
|
|
||||||
- [x] All changes committed
|
|
||||||
- [ ] **Branch pushed to remote** ← Do this next
|
|
||||||
- [ ] **E2E pipeline tested in CI** ← Final validation
|
|
||||||
- [ ] **Performance improvement verified** ← Measure results
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Rollback Instructions
|
|
||||||
|
|
||||||
If issues arise, rollback is simple:
|
|
||||||
|
|
||||||
### Rollback Pipeline Only
|
|
||||||
```bash
|
|
||||||
# Restore old E2E pipeline
|
|
||||||
git checkout HEAD~1 .woodpecker/e2e.yml
|
|
||||||
git commit -m "ci: rollback to DinD E2E pipeline"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
### Full Rollback (including Podman)
|
|
||||||
```bash
|
|
||||||
# Restore old pipeline
|
|
||||||
git checkout HEAD~1 .woodpecker/e2e.yml
|
|
||||||
git commit -m "ci: rollback migration"
|
|
||||||
git push
|
|
||||||
|
|
||||||
# Restore Podman backend (requires sudo)
|
|
||||||
sudo nano /etc/woodpecker/agent.env
|
|
||||||
# Change: WOODPECKER_BACKEND_DOCKER_HOST=unix:///run/user/1001/podman/podman.sock
|
|
||||||
sudo systemctl restart woodpecker-agent
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Metrics to Validate
|
|
||||||
|
|
||||||
After the first successful E2E run:
|
|
||||||
|
|
||||||
1. **Performance**: E2E pipeline completes in ~5-6 minutes (vs. old ~8-10 min)
|
|
||||||
2. **Reliability**: No DinD-related errors in logs
|
|
||||||
3. **Simplicity**: Single service instead of multiple nested containers
|
|
||||||
4. **Test results**: All Playwright tests pass
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Integration Image Details
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
Image: registry.sovraigns.network/harb/integration:latest
|
|
||||||
Digest: sha256:0543d2466680f4860e77789d5f3d16e7fb02527221b2ec6e3461381d7b207a2c
|
|
||||||
Size: 515MB (compressed: 491MB)
|
|
||||||
Base: docker:27-dind
|
|
||||||
Layers: 23
|
|
||||||
Registry: Local (registry.sovraigns.network:5000)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Image Contents**:
|
|
||||||
- Docker daemon (DinD)
|
|
||||||
- docker-compose
|
|
||||||
- Full Harb project source
|
|
||||||
- All entrypoint scripts
|
|
||||||
- Automatic stack startup on container launch
|
|
||||||
|
|
||||||
**Healthcheck**:
|
|
||||||
- URL: `http://localhost:8081/api/graphql`
|
|
||||||
- Interval: 5s
|
|
||||||
- Start period: 120s
|
|
||||||
- Retries: 30
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Known Issues / Notes
|
|
||||||
|
|
||||||
1. **First Run**: May be slightly slower due to image pull, but all subsequent runs will be fast
|
|
||||||
2. **Logs**: Stack logs are inside the service container (view via Woodpecker UI)
|
|
||||||
3. **Registry**: Uses basic auth (ciuser / some-strong-password)
|
|
||||||
4. **Agent Label**: Still shows `podman=true` (cosmetic, can be updated later)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Future Optimizations
|
|
||||||
|
|
||||||
Once stable, consider:
|
|
||||||
|
|
||||||
1. **Multi-stage build**: Separate build and runtime images
|
|
||||||
2. **Layer caching**: Optimize Dockerfile for faster rebuilds
|
|
||||||
3. **Image variants**: Separate images for different test suites
|
|
||||||
4. **Parallel services**: Start independent services concurrently
|
|
||||||
5. **Consolidate CI images**: Merge `Dockerfile.node-ci` + `Dockerfile.playwright-ci`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
For questions or issues:
|
|
||||||
- See `CI_MIGRATION.md` for technical details
|
|
||||||
- See `QUICKSTART_MIGRATION.md` for testing instructions
|
|
||||||
- See `MIGRATION_SUMMARY.md` for executive summary
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status**: ✅ **COMPLETE - Ready for CI Testing**
|
|
||||||
|
|
||||||
All code written, tested, committed, and deployed. The new CI infrastructure is ready for validation.
|
|
||||||
|
|
@ -1,240 +0,0 @@
|
||||||
# Migration Status Report
|
|
||||||
|
|
||||||
**Date**: 2025-11-20
|
|
||||||
**Branch**: feature/ci
|
|
||||||
**Commit**: 8c6b6c4
|
|
||||||
|
|
||||||
## ✅ Completed Steps
|
|
||||||
|
|
||||||
### 1. Podman → Docker Migration ✅
|
|
||||||
- Updated `/etc/woodpecker/agent.env` to use Docker socket
|
|
||||||
- Added `ci` user to `docker` group
|
|
||||||
- Restarted Woodpecker agent
|
|
||||||
- **Verified**: Agent running successfully with Docker backend
|
|
||||||
|
|
||||||
### 2. Composite Integration Service Created ✅
|
|
||||||
- Created `docker/Dockerfile.integration` (self-contained stack image)
|
|
||||||
- Created `docker/integration-entrypoint.sh` (orchestration script)
|
|
||||||
- Created `docker-compose.ci.yml` (local testing interface)
|
|
||||||
- Created `scripts/build-integration-image.sh` (build automation)
|
|
||||||
- Created refactored `.woodpecker/e2e.yml` pipeline
|
|
||||||
|
|
||||||
### 3. Integration Image Built ✅
|
|
||||||
- **Image**: `registry.sovraigns.network/harb/integration:latest`
|
|
||||||
- **Size**: 515MB (491MB compressed)
|
|
||||||
- **Status**: Built locally, ready for push
|
|
||||||
- **Build time**: ~45 seconds
|
|
||||||
|
|
||||||
### 4. Pipeline Activated ✅
|
|
||||||
- Backed up old E2E pipeline to `.woodpecker/e2e-old.yml`
|
|
||||||
- Activated new pipeline in `.woodpecker/e2e.yml`
|
|
||||||
- All changes committed to git
|
|
||||||
|
|
||||||
### 5. Documentation Created ✅
|
|
||||||
- `CI_MIGRATION.md` - Complete technical documentation
|
|
||||||
- `MIGRATION_SUMMARY.md` - Executive summary
|
|
||||||
- `QUICKSTART_MIGRATION.md` - Step-by-step testing guide
|
|
||||||
- `MIGRATION_STATUS.md` - This file
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠️ Remaining Actions
|
|
||||||
|
|
||||||
### Action 1: Push Integration Image to Registry
|
|
||||||
|
|
||||||
**Status**: Blocked - requires registry authentication
|
|
||||||
|
|
||||||
**What to do**:
|
|
||||||
```bash
|
|
||||||
# Option A: Login with credentials (requires password)
|
|
||||||
docker login registry.sovraigns.network -u ciuser
|
|
||||||
# Password: <ask admin>
|
|
||||||
|
|
||||||
# Option B: Build image in CI (recommended)
|
|
||||||
# The E2E pipeline can build the image on first run
|
|
||||||
# Add a build step before the service in e2e.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
**Recommendation**: For now, let the CI build the image on first run. This tests the full build process in CI and doesn't require manual registry access.
|
|
||||||
|
|
||||||
### Action 2: Test New E2E Pipeline
|
|
||||||
|
|
||||||
**Options**:
|
|
||||||
|
|
||||||
**A. Let CI build image (recommended)**
|
|
||||||
1. Add build step to `.woodpecker/e2e.yml`:
|
|
||||||
```yaml
|
|
||||||
steps:
|
|
||||||
- name: build-integration-image
|
|
||||||
image: docker:27-dind
|
|
||||||
privileged: true
|
|
||||||
environment:
|
|
||||||
DOCKER_HOST: tcp://docker:2375
|
|
||||||
commands:
|
|
||||||
- ./scripts/build-integration-image.sh
|
|
||||||
- docker tag registry.sovraigns.network/harb/integration:latest harb-integration:local
|
|
||||||
|
|
||||||
services:
|
|
||||||
- name: stack
|
|
||||||
image: harb-integration:local # Use locally built image
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
**B. Push image manually (requires sudo/password)**
|
|
||||||
```bash
|
|
||||||
# Get registry password from admin or check htpasswd
|
|
||||||
docker login registry.sovraigns.network -u ciuser
|
|
||||||
docker push registry.sovraigns.network/harb/integration:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
**C. Test locally first**
|
|
||||||
```bash
|
|
||||||
# Start the integration container
|
|
||||||
docker run --rm --privileged -p 8081:8081 \
|
|
||||||
registry.sovraigns.network/harb/integration:latest
|
|
||||||
|
|
||||||
# In another terminal, wait for healthy
|
|
||||||
timeout 300 sh -c 'until curl -sf http://localhost:8081/api/graphql; do sleep 5; done'
|
|
||||||
|
|
||||||
# Run E2E tests
|
|
||||||
npm run test:e2e
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Current State
|
|
||||||
|
|
||||||
### Files Changed (10 files, +1067/-97 lines)
|
|
||||||
```
|
|
||||||
M .dockerignore (updated to exclude more build artifacts)
|
|
||||||
A .woodpecker/e2e-old.yml (backup of old DinD pipeline)
|
|
||||||
M .woodpecker/e2e.yml (new composite service pipeline)
|
|
||||||
A CI_MIGRATION.md (technical documentation)
|
|
||||||
A MIGRATION_SUMMARY.md (executive summary)
|
|
||||||
A QUICKSTART_MIGRATION.md (testing guide)
|
|
||||||
A docker-compose.ci.yml (local testing interface)
|
|
||||||
A docker/Dockerfile.integration (integration image)
|
|
||||||
A docker/integration-entrypoint.sh (entrypoint script)
|
|
||||||
A scripts/build-integration-image.sh (build automation)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Commit Hash
|
|
||||||
```
|
|
||||||
8c6b6c4 - ci: migrate to composite integration service + Docker backend
|
|
||||||
```
|
|
||||||
|
|
||||||
### Branch
|
|
||||||
```
|
|
||||||
feature/ci
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps (Choose One)
|
|
||||||
|
|
||||||
### Option A: Build in CI (Recommended)
|
|
||||||
1. Modify `.woodpecker/e2e.yml` to add build step (see above)
|
|
||||||
2. Commit change
|
|
||||||
3. Push to remote
|
|
||||||
4. Watch CI build and test
|
|
||||||
|
|
||||||
**Pros**: Tests full CI build process, no registry credentials needed
|
|
||||||
**Cons**: First run will be slower (~5-10 min extra)
|
|
||||||
|
|
||||||
### Option B: Push Image Manually
|
|
||||||
1. Get registry password from admin
|
|
||||||
2. `docker login registry.sovraigns.network -u ciuser`
|
|
||||||
3. `docker push registry.sovraigns.network/harb/integration:latest`
|
|
||||||
4. Push branch to remote
|
|
||||||
5. Watch CI test
|
|
||||||
|
|
||||||
**Pros**: Faster first CI run
|
|
||||||
**Cons**: Requires registry credentials
|
|
||||||
|
|
||||||
### Option C: Local Test First
|
|
||||||
1. Run integration container locally (see commands above)
|
|
||||||
2. Run E2E tests against it
|
|
||||||
3. Verify everything works
|
|
||||||
4. Then proceed with Option A or B
|
|
||||||
|
|
||||||
**Pros**: Catch issues before CI
|
|
||||||
**Cons**: Takes more time upfront
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Expectations
|
|
||||||
|
|
||||||
### Old Pipeline (DinD)
|
|
||||||
- Stack startup: ~180-240s
|
|
||||||
- Total E2E: ~8-10 minutes
|
|
||||||
- Complexity: High (nested containers)
|
|
||||||
|
|
||||||
### New Pipeline (Composite)
|
|
||||||
- Stack startup: ~60-90s (if image pre-built) OR ~5-10 min (first build)
|
|
||||||
- Total E2E: ~5-6 minutes (after first build)
|
|
||||||
- Complexity: Low (single service)
|
|
||||||
|
|
||||||
### After First CI Run
|
|
||||||
- **Image cached**: Subsequent runs will be fast (~5-6 min total)
|
|
||||||
- **Improvement**: ~3-5 minutes faster per run
|
|
||||||
- **Simplification**: 1 service instead of DinD + 8 nested containers
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Rollback Instructions
|
|
||||||
|
|
||||||
If something goes wrong:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Restore old E2E pipeline
|
|
||||||
git checkout HEAD~1 .woodpecker/e2e.yml
|
|
||||||
|
|
||||||
# Or manually
|
|
||||||
mv .woodpecker/e2e-old.yml .woodpecker/e2e.yml
|
|
||||||
|
|
||||||
# Commit and push
|
|
||||||
git add .woodpecker/e2e.yml
|
|
||||||
git commit -m "ci: rollback to DinD E2E pipeline"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
To rollback Podman migration (requires sudo):
|
|
||||||
```bash
|
|
||||||
# Edit agent config
|
|
||||||
sudo nano /etc/woodpecker/agent.env
|
|
||||||
# Change: WOODPECKER_BACKEND_DOCKER_HOST=unix:///run/user/1001/podman/podman.sock
|
|
||||||
|
|
||||||
# Restart agent
|
|
||||||
sudo systemctl restart woodpecker-agent
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
|
|
||||||
- [x] Podman → Docker migration complete
|
|
||||||
- [x] Integration Dockerfile created
|
|
||||||
- [x] docker-compose.ci.yml created
|
|
||||||
- [x] Build script created
|
|
||||||
- [x] New E2E pipeline created
|
|
||||||
- [x] Documentation complete
|
|
||||||
- [x] Integration image builds successfully
|
|
||||||
- [ ] Image pushed to registry OR build-in-CI implemented
|
|
||||||
- [ ] CI E2E pipeline tested and passing
|
|
||||||
- [ ] Performance improvement verified (~3-5 min faster)
|
|
||||||
|
|
||||||
**Current Status**: 8/10 complete - Ready for final testing
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recommendation
|
|
||||||
|
|
||||||
I recommend **Option A (Build in CI)** because:
|
|
||||||
1. No registry credentials needed
|
|
||||||
2. Tests the full build process in CI environment
|
|
||||||
3. Image will be cached for subsequent runs
|
|
||||||
4. First run will validate everything works end-to-end
|
|
||||||
|
|
||||||
The only downside is the first run will take longer (~5-10 min extra for image build), but all subsequent runs will be much faster.
|
|
||||||
|
|
||||||
Would you like me to modify the E2E pipeline to build the image in CI?
|
|
||||||
|
|
@ -1,267 +0,0 @@
|
||||||
# CI Infrastructure Migration Summary
|
|
||||||
|
|
||||||
**Date**: 2025-11-20
|
|
||||||
**Branch**: feature/ci
|
|
||||||
**Status**: ✅ Ready for Testing
|
|
||||||
|
|
||||||
## Changes Implemented
|
|
||||||
|
|
||||||
### 1. Podman → Docker Migration ✅
|
|
||||||
|
|
||||||
**Agent Configuration** (`/etc/woodpecker/agent.env`):
|
|
||||||
```diff
|
|
||||||
- WOODPECKER_BACKEND_DOCKER_HOST=unix:///run/user/1001/podman/podman.sock
|
|
||||||
+ WOODPECKER_BACKEND_DOCKER_HOST=unix:///var/run/docker.sock
|
|
||||||
```
|
|
||||||
|
|
||||||
**User Permissions**:
|
|
||||||
- Added `ci` user to `docker` group
|
|
||||||
- Agent now uses native Docker instead of rootless Podman
|
|
||||||
|
|
||||||
**Benefits**:
|
|
||||||
- Simpler configuration
|
|
||||||
- Better Docker Compose support
|
|
||||||
- Native DinD compatibility
|
|
||||||
- Consistency with dev environment
|
|
||||||
|
|
||||||
**Status**: ✅ Complete - Agent running successfully with Docker backend
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Composite Integration Service (Option A) ✅
|
|
||||||
|
|
||||||
Eliminated Docker-in-Docker complexity by creating a self-contained integration image.
|
|
||||||
|
|
||||||
**New Files Created**:
|
|
||||||
|
|
||||||
1. **`docker/Dockerfile.integration`** - Composite image bundling full stack
|
|
||||||
- Base: `docker:27-dind`
|
|
||||||
- Includes: Full project + docker-compose + all dependencies
|
|
||||||
- Entrypoint: Auto-starts dockerd + Harb stack
|
|
||||||
- Health: GraphQL endpoint validation
|
|
||||||
|
|
||||||
2. **`docker/integration-entrypoint.sh`** - Startup orchestration script
|
|
||||||
- Starts Docker daemon
|
|
||||||
- Builds kraiken-lib
|
|
||||||
- Launches stack via `dev.sh`
|
|
||||||
- Keeps container alive with graceful shutdown
|
|
||||||
|
|
||||||
3. **`docker-compose.ci.yml`** - Simplified CI interface
|
|
||||||
- Single service: `harb-stack`
|
|
||||||
- Privileged mode for DinD
|
|
||||||
- Port 8081 exposed for testing
|
|
||||||
- Volume for Docker state persistence
|
|
||||||
|
|
||||||
4. **`scripts/build-integration-image.sh`** - Image build automation
|
|
||||||
- Builds kraiken-lib first
|
|
||||||
- Builds Docker image
|
|
||||||
- Provides testing + push instructions
|
|
||||||
|
|
||||||
5. **`.woodpecker/e2e-new.yml`** - Refactored E2E pipeline
|
|
||||||
- **Service**: `harb/integration` (full stack)
|
|
||||||
- **Step 1**: Wait for stack health (~60-90s)
|
|
||||||
- **Step 2**: Run Playwright tests
|
|
||||||
- **Step 3**: Collect artifacts
|
|
||||||
- **Removed**: DinD service, docker CLI installation, nested container management
|
|
||||||
|
|
||||||
6. **`CI_MIGRATION.md`** - Complete migration documentation
|
|
||||||
- Architecture comparison (before/after)
|
|
||||||
- Migration steps
|
|
||||||
- Local testing guide
|
|
||||||
- Troubleshooting
|
|
||||||
- Performance metrics
|
|
||||||
|
|
||||||
**Performance Improvements**:
|
|
||||||
| Metric | Before | After | Improvement |
|
|
||||||
|--------|--------|-------|-------------|
|
|
||||||
| Stack startup | 180-240s | 60-90s | ~2-3 min faster |
|
|
||||||
| Total E2E | 8-10 min | 5-6 min | ~40% faster |
|
|
||||||
| Complexity | High | Low | Simpler |
|
|
||||||
|
|
||||||
**Status**: ✅ Complete - Files created, ready for build + test
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture Changes
|
|
||||||
|
|
||||||
### Before: Docker-in-Docker Pattern
|
|
||||||
```
|
|
||||||
Woodpecker Pipeline
|
|
||||||
└─ Service: docker:dind
|
|
||||||
└─ Step: run-e2e (node-ci image)
|
|
||||||
├─ apt-get install docker-cli docker-compose
|
|
||||||
├─ DOCKER_HOST=tcp://docker:2375
|
|
||||||
├─ ./scripts/dev.sh start (creates 8 nested containers)
|
|
||||||
│ ├─ anvil
|
|
||||||
│ ├─ postgres
|
|
||||||
│ ├─ bootstrap
|
|
||||||
│ ├─ ponder
|
|
||||||
│ ├─ webapp
|
|
||||||
│ ├─ landing
|
|
||||||
│ ├─ txn-bot
|
|
||||||
│ └─ caddy
|
|
||||||
└─ npx playwright test
|
|
||||||
```
|
|
||||||
|
|
||||||
### After: Composite Service Pattern
|
|
||||||
```
|
|
||||||
Woodpecker Pipeline
|
|
||||||
├─ Service: harb/integration (self-contained stack)
|
|
||||||
│ └─ Internal: dockerd + docker-compose managing 8 services
|
|
||||||
│
|
|
||||||
└─ Steps:
|
|
||||||
├─ wait-for-stack (curl healthcheck)
|
|
||||||
└─ run-e2e-tests (playwright only)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
### 1. Build Integration Image
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/debian/harb-ci
|
|
||||||
./scripts/build-integration-image.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected time**: 5-10 minutes (first build)
|
|
||||||
|
|
||||||
### 2. Test Locally (Optional)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start stack container
|
|
||||||
docker run --rm --privileged -p 8081:8081 \
|
|
||||||
registry.sovraigns.network/harb/integration:latest
|
|
||||||
|
|
||||||
# In another terminal, verify health
|
|
||||||
curl http://localhost:8081/api/graphql
|
|
||||||
|
|
||||||
# Run E2E tests
|
|
||||||
npm run test:e2e
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Push to Registry
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Login (if needed)
|
|
||||||
docker login registry.sovraigns.network -u ciuser
|
|
||||||
|
|
||||||
# Push
|
|
||||||
docker push registry.sovraigns.network/harb/integration:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Activate New Pipeline
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backup old pipeline
|
|
||||||
mv .woodpecker/e2e.yml .woodpecker/e2e-old.yml
|
|
||||||
|
|
||||||
# Activate new pipeline
|
|
||||||
mv .woodpecker/e2e-new.yml .woodpecker/e2e.yml
|
|
||||||
|
|
||||||
# Commit
|
|
||||||
git add -A
|
|
||||||
git commit -m "ci: migrate to composite integration service + Docker backend"
|
|
||||||
git push origin feature/ci
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Test in CI
|
|
||||||
|
|
||||||
Create a PR or manually trigger the E2E pipeline in Woodpecker UI.
|
|
||||||
|
|
||||||
**Expected behavior**:
|
|
||||||
- `harb/integration` service starts
|
|
||||||
- Stack becomes healthy in ~60-90s
|
|
||||||
- Playwright tests run against `http://stack:8081`
|
|
||||||
- Artifacts collected
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Rollback Plan
|
|
||||||
|
|
||||||
If issues occur, revert is simple:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Restore old E2E pipeline
|
|
||||||
mv .woodpecker/e2e-old.yml .woodpecker/e2e.yml
|
|
||||||
|
|
||||||
# Revert Podman backend (requires sudo)
|
|
||||||
sudo vi /etc/woodpecker/agent.env
|
|
||||||
# Change: WOODPECKER_BACKEND_DOCKER_HOST=unix:///run/user/1001/podman/podman.sock
|
|
||||||
sudo systemctl restart woodpecker-agent
|
|
||||||
|
|
||||||
# Commit
|
|
||||||
git add .woodpecker/e2e.yml
|
|
||||||
git commit -m "ci: rollback migration"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified/Created
|
|
||||||
|
|
||||||
### Created
|
|
||||||
- `docker/Dockerfile.integration`
|
|
||||||
- `docker/integration-entrypoint.sh`
|
|
||||||
- `docker-compose.ci.yml`
|
|
||||||
- `scripts/build-integration-image.sh`
|
|
||||||
- `.woodpecker/e2e-new.yml`
|
|
||||||
- `CI_MIGRATION.md`
|
|
||||||
- `MIGRATION_SUMMARY.md` (this file)
|
|
||||||
|
|
||||||
### Modified
|
|
||||||
- `/etc/woodpecker/agent.env` (via sudo)
|
|
||||||
- User `ci` groups (via sudo)
|
|
||||||
|
|
||||||
### To Be Renamed (on activation)
|
|
||||||
- `.woodpecker/e2e.yml` → `.woodpecker/e2e-old.yml` (backup)
|
|
||||||
- `.woodpecker/e2e-new.yml` → `.woodpecker/e2e.yml` (activate)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cleanup Opportunities (Future)
|
|
||||||
|
|
||||||
Once migration is stable:
|
|
||||||
|
|
||||||
1. **Remove old E2E pipeline**: Delete `.woodpecker/e2e-old.yml`
|
|
||||||
2. **Stop Podman service**: `sudo systemctl disable podman-api-ci`
|
|
||||||
3. **Update agent label**: Change `podman=true` → `docker=true` in agent.env
|
|
||||||
4. **Consolidate CI images**: Merge `Dockerfile.node-ci` + `Dockerfile.playwright-ci`
|
|
||||||
5. **Remove DinD references**: Clean up old documentation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Questions & Issues
|
|
||||||
|
|
||||||
### Image build fails?
|
|
||||||
- Check `./scripts/build-kraiken-lib.sh` runs successfully
|
|
||||||
- Ensure Docker daemon is running
|
|
||||||
- Check disk space: `df -h` and `docker system df`
|
|
||||||
|
|
||||||
### Stack doesn't become healthy in CI?
|
|
||||||
- Check Woodpecker service logs
|
|
||||||
- Increase healthcheck `start_period` or `retries` in e2e-new.yml
|
|
||||||
- Test image locally first
|
|
||||||
|
|
||||||
### E2E tests fail?
|
|
||||||
- Verify stack URLs are correct (`http://stack:8081` for service-to-service)
|
|
||||||
- Check if stack actually started (service logs)
|
|
||||||
- Ensure Playwright image has network access to stack service
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
|
|
||||||
- [x] Podman → Docker migration complete
|
|
||||||
- [x] Integration Dockerfile created
|
|
||||||
- [x] docker-compose.ci.yml created
|
|
||||||
- [x] Build script created
|
|
||||||
- [x] New E2E pipeline created
|
|
||||||
- [x] Documentation written
|
|
||||||
- [ ] Integration image builds successfully
|
|
||||||
- [ ] Local test passes
|
|
||||||
- [ ] Image pushed to registry
|
|
||||||
- [ ] CI E2E pipeline passes
|
|
||||||
|
|
||||||
**Current Status**: Ready for testing phase
|
|
||||||
|
|
@ -1,196 +0,0 @@
|
||||||
# Quick Start: CI Migration Testing
|
|
||||||
|
|
||||||
## Status: ✅ Ready to Build & Test
|
|
||||||
|
|
||||||
All code is written. Follow these steps to activate the new CI infrastructure.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 1: Build the Integration Image (~5-10 min)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/debian/harb-ci
|
|
||||||
./scripts/build-integration-image.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**What it does**: Builds a Docker image containing the full Harb stack
|
|
||||||
**Expected output**: `✓ Image built successfully: registry.sovraigns.network/harb/integration:latest`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 2: Test Locally (Optional, ~5 min)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Terminal 1: Start the stack
|
|
||||||
docker run --rm --privileged -p 8081:8081 \
|
|
||||||
registry.sovraigns.network/harb/integration:latest
|
|
||||||
|
|
||||||
# Terminal 2: Wait for healthy (~60-90s)
|
|
||||||
timeout 300 sh -c 'until curl -sf http://localhost:8081/api/graphql; do sleep 5; done'
|
|
||||||
echo "Stack is healthy!"
|
|
||||||
|
|
||||||
# Terminal 3: Run E2E tests
|
|
||||||
cd /home/debian/harb-ci
|
|
||||||
npm run test:e2e
|
|
||||||
|
|
||||||
# Cleanup: Ctrl+C in Terminal 1
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 3: Push to Registry
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Login to registry
|
|
||||||
docker login registry.sovraigns.network -u ciuser
|
|
||||||
# Password: (ask admin or check /etc/docker/registry/htpasswd)
|
|
||||||
|
|
||||||
# Push image
|
|
||||||
docker push registry.sovraigns.network/harb/integration:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 4: Activate New Pipeline
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/debian/harb-ci
|
|
||||||
|
|
||||||
# Backup old E2E pipeline
|
|
||||||
mv .woodpecker/e2e.yml .woodpecker/e2e-old.yml
|
|
||||||
|
|
||||||
# Activate new pipeline
|
|
||||||
mv .woodpecker/e2e-new.yml .woodpecker/e2e.yml
|
|
||||||
|
|
||||||
# Stage all changes
|
|
||||||
git add -A
|
|
||||||
|
|
||||||
# Commit
|
|
||||||
git commit -m "ci: migrate to composite integration service
|
|
||||||
|
|
||||||
- Migrate agent from Podman to Docker
|
|
||||||
- Create composite harb/integration image
|
|
||||||
- Refactor E2E pipeline to use service pattern
|
|
||||||
- Eliminate Docker-in-Docker complexity
|
|
||||||
- Expected improvement: ~3-5 min faster E2E runs"
|
|
||||||
|
|
||||||
# Push to trigger CI
|
|
||||||
git push origin feature/ci
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 5: Monitor CI Run
|
|
||||||
|
|
||||||
1. Open Woodpecker UI: https://ci.sovraigns.network
|
|
||||||
2. Navigate to `johba/harb` repository
|
|
||||||
3. Find the pipeline for your latest push
|
|
||||||
4. Watch the `e2e` pipeline:
|
|
||||||
- **Service**: `stack` should start and become healthy (~60-90s)
|
|
||||||
- **Step 1**: `wait-for-stack` should succeed
|
|
||||||
- **Step 2**: `run-e2e-tests` should pass
|
|
||||||
- **Step 3**: `collect-artifacts` should gather results
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Build fails: "kraiken-lib build failed"
|
|
||||||
```bash
|
|
||||||
# Test kraiken-lib build separately
|
|
||||||
./scripts/build-kraiken-lib.sh
|
|
||||||
|
|
||||||
# Check for errors, fix, then rebuild
|
|
||||||
./scripts/build-integration-image.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Local test: Stack doesn't start
|
|
||||||
```bash
|
|
||||||
# Check Docker daemon is running
|
|
||||||
docker info
|
|
||||||
|
|
||||||
# Check disk space (need ~10GB)
|
|
||||||
df -h
|
|
||||||
docker system df
|
|
||||||
|
|
||||||
# View container logs
|
|
||||||
docker logs <container-id>
|
|
||||||
```
|
|
||||||
|
|
||||||
### CI: Healthcheck timeout
|
|
||||||
- **Cause**: First run pulls images, takes longer (~2-3 min)
|
|
||||||
- **Fix**: Increase `start_period` in `.woodpecker/e2e-new.yml` line 18:
|
|
||||||
```yaml
|
|
||||||
start_period: 180s # was 120s
|
|
||||||
```
|
|
||||||
|
|
||||||
### CI: "Image not found"
|
|
||||||
- **Cause**: Forgot to push to registry
|
|
||||||
- **Fix**: Run Step 3 (push to registry)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Rollback (if needed)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Restore old pipeline
|
|
||||||
mv .woodpecker/e2e-old.yml .woodpecker/e2e.yml
|
|
||||||
|
|
||||||
git add .woodpecker/e2e.yml
|
|
||||||
git commit -m "ci: rollback to DinD E2E pipeline"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Checklist
|
|
||||||
|
|
||||||
All files created and ready:
|
|
||||||
|
|
||||||
- [x] `docker/Dockerfile.integration` - Integration image definition
|
|
||||||
- [x] `docker/integration-entrypoint.sh` - Startup script
|
|
||||||
- [x] `docker-compose.ci.yml` - CI compose file
|
|
||||||
- [x] `scripts/build-integration-image.sh` - Build automation
|
|
||||||
- [x] `.woodpecker/e2e-new.yml` - New E2E pipeline
|
|
||||||
- [x] `CI_MIGRATION.md` - Full documentation
|
|
||||||
- [x] `MIGRATION_SUMMARY.md` - Change summary
|
|
||||||
- [x] `QUICKSTART_MIGRATION.md` - This file
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Expected Timeline
|
|
||||||
|
|
||||||
| Step | Time | Can Skip? |
|
|
||||||
|------|------|-----------|
|
|
||||||
| 1. Build image | 5-10 min | No |
|
|
||||||
| 2. Local test | 5 min | Yes (recommended though) |
|
|
||||||
| 3. Push to registry | 1 min | No |
|
|
||||||
| 4. Activate pipeline | 1 min | No |
|
|
||||||
| 5. Monitor CI | 5-6 min | No |
|
|
||||||
| **Total** | **17-23 min** | - |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Indicators
|
|
||||||
|
|
||||||
✅ **Build succeeds**: Image tagged as `registry.sovraigns.network/harb/integration:latest`
|
|
||||||
✅ **Local test passes**: GraphQL endpoint responds, Playwright tests pass
|
|
||||||
✅ **Registry push succeeds**: Image visible in registry
|
|
||||||
✅ **CI pipeline passes**: All steps green in Woodpecker UI
|
|
||||||
✅ **Performance improved**: E2E run completes in ~5-6 min (was 8-10 min)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Actions
|
|
||||||
|
|
||||||
After successful CI run:
|
|
||||||
|
|
||||||
1. **Monitor stability** - Run a few more PRs to ensure consistency
|
|
||||||
2. **Update documentation** - Add new CI architecture to `CLAUDE.md`
|
|
||||||
3. **Clean up** - Remove `.woodpecker/e2e-old.yml` after 1 week
|
|
||||||
4. **Optimize** - Consider multi-stage builds for faster rebuilds
|
|
||||||
5. **Consolidate** - Merge CI images (`Dockerfile.node-ci` + `Dockerfile.playwright-ci`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Questions?** See `CI_MIGRATION.md` for detailed documentation.
|
|
||||||
|
|
@ -124,28 +124,20 @@ export function useVersionCheck() {
|
||||||
|
|
||||||
### 5. CI/CD Validation
|
### 5. CI/CD Validation
|
||||||
|
|
||||||
**File:** `.github/workflows/validate-version.yml`
|
**File:** `.woodpecker/release.yml` (version-check step)
|
||||||
|
|
||||||
```yaml
|
The Woodpecker release pipeline validates version consistency on tagged releases. The `version-check` step:
|
||||||
- name: Extract versions and validate
|
1. Builds kraiken-lib (including `sync-tax-rates.mjs`)
|
||||||
run: |
|
2. Runs an inline Node.js script that:
|
||||||
CONTRACT_VERSION=$(grep -oP 'VERSION\s*=\s*\K\d+' onchain/src/Kraiken.sol)
|
- Extracts `VERSION` from `Kraiken.sol`
|
||||||
LIB_VERSION=$(grep -oP 'KRAIKEN_LIB_VERSION\s*=\s*\K\d+' kraiken-lib/src/version.ts)
|
- Extracts `KRAIKEN_LIB_VERSION` and `COMPATIBLE_CONTRACT_VERSIONS` from `kraiken-lib/src/version.ts`
|
||||||
COMPATIBLE=$(grep -oP 'COMPATIBLE_CONTRACT_VERSIONS\s*=\s*\[\K[^\]]+' kraiken-lib/src/version.ts)
|
- Fails if contract VERSION differs from lib VERSION
|
||||||
|
- Fails if contract VERSION is not in COMPATIBLE_CONTRACT_VERSIONS
|
||||||
|
|
||||||
if echo ",$COMPATIBLE," | grep -q ",$CONTRACT_VERSION,"; then
|
**Triggered on:** tag events (releases)
|
||||||
echo "✓ Version sync validated"
|
|
||||||
else
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
**Triggered on:**
|
|
||||||
- PRs touching `Kraiken.sol` or `version.ts`
|
|
||||||
- Pushes to `master`/`main`
|
|
||||||
|
|
||||||
**Prevents:**
|
**Prevents:**
|
||||||
- Merging incompatible versions
|
- Releasing with incompatible versions
|
||||||
- Deploying with stale kraiken-lib
|
- Deploying with stale kraiken-lib
|
||||||
|
|
||||||
## Workflows
|
## Workflows
|
||||||
|
|
|
||||||
|
|
@ -26,48 +26,30 @@ if [[ -n "$GIT_BRANCH" ]]; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
STATE_DIR=$ROOT_DIR/tmp/containers
|
STATE_DIR=$ROOT_DIR/tmp/containers
|
||||||
LOG_DIR=$STATE_DIR/logs
|
LOG_DIR=$STATE_DIR/logs
|
||||||
SETUP_LOG=$LOG_DIR/setup.log
|
SETUP_LOG=$LOG_DIR/setup.log
|
||||||
CONTRACT_ENV=$STATE_DIR/contracts.env
|
|
||||||
TXNBOT_ENV=$STATE_DIR/txnBot.env
|
|
||||||
MNEMONIC_FILE=$ROOT_DIR/onchain/.secret.local
|
MNEMONIC_FILE=$ROOT_DIR/onchain/.secret.local
|
||||||
|
|
||||||
mkdir -p "$LOG_DIR"
|
mkdir -p "$LOG_DIR"
|
||||||
: >"$SETUP_LOG"
|
: >"$SETUP_LOG"
|
||||||
|
|
||||||
|
# ── Configure shared bootstrap variables ──
|
||||||
ANVIL_RPC=${ANVIL_RPC:-"http://anvil:8545"}
|
ANVIL_RPC=${ANVIL_RPC:-"http://anvil:8545"}
|
||||||
FEE_DEST=0xf6a3eef9088A255c32b6aD2025f83E57291D9011
|
CONTRACT_ENV=$STATE_DIR/contracts.env
|
||||||
WETH=0x4200000000000000000000000000000000000006
|
LOG_FILE=$SETUP_LOG
|
||||||
SWAP_ROUTER=0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4
|
ONCHAIN_DIR=$ROOT_DIR/onchain
|
||||||
MAX_UINT=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
|
|
||||||
DEFAULT_DEPLOYER_PK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
|
||||||
DEFAULT_DEPLOYER_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
|
|
||||||
DEPLOYER_PK=${DEPLOYER_PK:-$DEFAULT_DEPLOYER_PK}
|
|
||||||
DEPLOYER_ADDR=${DEPLOYER_ADDR:-$DEFAULT_DEPLOYER_ADDR}
|
|
||||||
|
|
||||||
TXNBOT_FUND_VALUE=${TXNBOT_FUND_VALUE:-1ether}
|
TXNBOT_FUND_VALUE=${TXNBOT_FUND_VALUE:-1ether}
|
||||||
|
|
||||||
log() {
|
# Source shared bootstrap functions
|
||||||
echo "[bootstrap] $*"
|
# shellcheck source=../scripts/bootstrap-common.sh
|
||||||
}
|
source "$ROOT_DIR/scripts/bootstrap-common.sh"
|
||||||
|
|
||||||
BOOTSTRAP_START=$(date +%s%3N)
|
# ── Local-only helpers ─────────────────────────────────────────────────
|
||||||
|
|
||||||
wait_for_rpc() {
|
|
||||||
for _ in {1..120}; do
|
|
||||||
if cast chain-id --rpc-url "$ANVIL_RPC" >/dev/null 2>&1; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
log "Timed out waiting for Anvil at $ANVIL_RPC"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
maybe_set_deployer_from_mnemonic() {
|
maybe_set_deployer_from_mnemonic() {
|
||||||
if [[ -n "$DEPLOYER_PK" && -n "$DEPLOYER_ADDR" ]]; then
|
if [[ -n "$DEPLOYER_PK" && "$DEPLOYER_PK" != "$DEFAULT_DEPLOYER_PK" ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if [[ -f "$MNEMONIC_FILE" ]]; then
|
if [[ -f "$MNEMONIC_FILE" ]]; then
|
||||||
|
|
@ -76,12 +58,10 @@ maybe_set_deployer_from_mnemonic() {
|
||||||
if [[ -n "$mnemonic" ]]; then
|
if [[ -n "$mnemonic" ]]; then
|
||||||
pk="$(cast wallet private-key --mnemonic "$mnemonic" --mnemonic-derivation-path "m/44'/60'/0'/0/0")"
|
pk="$(cast wallet private-key --mnemonic "$mnemonic" --mnemonic-derivation-path "m/44'/60'/0'/0/0")"
|
||||||
addr="$(cast wallet address --private-key "$pk")"
|
addr="$(cast wallet address --private-key "$pk")"
|
||||||
DEPLOYER_PK=${DEPLOYER_PK:-$pk}
|
DEPLOYER_PK=${pk}
|
||||||
DEPLOYER_ADDR=${DEPLOYER_ADDR:-$addr}
|
DEPLOYER_ADDR=${addr}
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
DEPLOYER_PK=${DEPLOYER_PK:-$DEFAULT_DEPLOYER_PK}
|
|
||||||
DEPLOYER_ADDR=${DEPLOYER_ADDR:-$DEFAULT_DEPLOYER_ADDR}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
derive_txnbot_wallet() {
|
derive_txnbot_wallet() {
|
||||||
|
|
@ -91,115 +71,14 @@ derive_txnbot_wallet() {
|
||||||
if [[ -n "$mnemonic" ]]; then
|
if [[ -n "$mnemonic" ]]; then
|
||||||
TXNBOT_PRIVATE_KEY="$(cast wallet private-key --mnemonic "$mnemonic" --mnemonic-index 2)"
|
TXNBOT_PRIVATE_KEY="$(cast wallet private-key --mnemonic "$mnemonic" --mnemonic-index 2)"
|
||||||
TXNBOT_ADDRESS="$(cast wallet address --private-key "$TXNBOT_PRIVATE_KEY")"
|
TXNBOT_ADDRESS="$(cast wallet address --private-key "$TXNBOT_PRIVATE_KEY")"
|
||||||
log "Derived txnBot wallet: $TXNBOT_ADDRESS (account index 2)"
|
bootstrap_log "Derived txnBot wallet: $TXNBOT_ADDRESS (account index 2)"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# Fallback to hardcoded Anvil account 1
|
# Fallback to hardcoded Anvil account 1
|
||||||
TXNBOT_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
|
TXNBOT_PRIVATE_KEY=$DEFAULT_TXNBOT_PK
|
||||||
TXNBOT_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
|
TXNBOT_ADDRESS=$DEFAULT_TXNBOT_ADDR
|
||||||
log "Using default txnBot wallet: $TXNBOT_ADDRESS"
|
bootstrap_log "Using default txnBot wallet: $TXNBOT_ADDRESS"
|
||||||
}
|
|
||||||
|
|
||||||
run_forge_script() {
|
|
||||||
log "Deploying contracts to fork"
|
|
||||||
pushd "$ROOT_DIR/onchain" >/dev/null
|
|
||||||
forge script script/DeployLocal.sol --fork-url "$ANVIL_RPC" --broadcast >>"$SETUP_LOG" 2>&1
|
|
||||||
popd >/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
extract_addresses() {
|
|
||||||
local run_file
|
|
||||||
run_file="$(ls -t "$ROOT_DIR/onchain/broadcast/DeployLocal.sol"/*/run-latest.json 2>/dev/null | head -n1)"
|
|
||||||
if [[ -z "$run_file" ]]; then
|
|
||||||
log "Deployment artifact not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
log "Using artifact ${run_file#$ROOT_DIR/}"
|
|
||||||
LIQUIDITY_MANAGER="$(jq -r '.transactions[] | select(.contractName=="LiquidityManager") | .contractAddress' "$run_file" | head -n1)"
|
|
||||||
KRAIKEN="$(jq -r '.transactions[] | select(.contractName=="Kraiken") | .contractAddress' "$run_file" | head -n1)"
|
|
||||||
STAKE="$(jq -r '.transactions[] | select(.contractName=="Stake") | .contractAddress' "$run_file" | head -n1)"
|
|
||||||
DEPLOY_BLOCK="$(jq -r '.receipts[0].blockNumber' "$run_file" | xargs printf "%d")"
|
|
||||||
if [[ -z "$LIQUIDITY_MANAGER" || "$LIQUIDITY_MANAGER" == "null" ]]; then
|
|
||||||
log "LiquidityManager address missing"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
cat >"$CONTRACT_ENV" <<EOCONTRACTS
|
|
||||||
LIQUIDITY_MANAGER=$LIQUIDITY_MANAGER
|
|
||||||
KRAIKEN=$KRAIKEN
|
|
||||||
STAKE=$STAKE
|
|
||||||
EOCONTRACTS
|
|
||||||
}
|
|
||||||
|
|
||||||
fund_liquidity_manager() {
|
|
||||||
log "Funding LiquidityManager"
|
|
||||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
||||||
"$LIQUIDITY_MANAGER" --value 0.1ether >>"$SETUP_LOG" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
grant_recenter_access() {
|
|
||||||
log "Granting recenter access"
|
|
||||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_impersonateAccount "$FEE_DEST" >>"$SETUP_LOG" 2>&1
|
|
||||||
cast send --rpc-url "$ANVIL_RPC" --from "$FEE_DEST" --unlocked \
|
|
||||||
"$LIQUIDITY_MANAGER" "setRecenterAccess(address)" "$DEPLOYER_ADDR" >>"$SETUP_LOG" 2>&1
|
|
||||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_stopImpersonatingAccount "$FEE_DEST" >>"$SETUP_LOG" 2>&1
|
|
||||||
if [[ -n "$TXNBOT_ADDRESS" ]]; then
|
|
||||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_impersonateAccount "$FEE_DEST" >>"$SETUP_LOG" 2>&1
|
|
||||||
cast send --rpc-url "$ANVIL_RPC" --from "$FEE_DEST" --unlocked \
|
|
||||||
"$LIQUIDITY_MANAGER" "setRecenterAccess(address)" "$TXNBOT_ADDRESS" >>"$SETUP_LOG" 2>&1
|
|
||||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_stopImpersonatingAccount "$FEE_DEST" >>"$SETUP_LOG" 2>&1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
call_recenter() {
|
|
||||||
local recenter_pk="$DEPLOYER_PK"
|
|
||||||
local recenter_addr="$DEPLOYER_ADDR"
|
|
||||||
if [[ -n "$TXNBOT_ADDRESS" ]]; then
|
|
||||||
recenter_pk="$TXNBOT_PRIVATE_KEY"
|
|
||||||
recenter_addr="$TXNBOT_ADDRESS"
|
|
||||||
fi
|
|
||||||
log "Calling recenter() via $recenter_addr"
|
|
||||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$recenter_pk" \
|
|
||||||
"$LIQUIDITY_MANAGER" "recenter()" >>"$SETUP_LOG" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
seed_application_state() {
|
|
||||||
log "Wrapping ETH to WETH"
|
|
||||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
||||||
"$WETH" "deposit()" --value 0.02ether >>"$SETUP_LOG" 2>&1
|
|
||||||
log "Approving router"
|
|
||||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
||||||
"$WETH" "approve(address,uint256)" "$SWAP_ROUTER" "$MAX_UINT" >>"$SETUP_LOG" 2>&1
|
|
||||||
log "Executing initial KRK swap"
|
|
||||||
cast send --legacy --gas-limit 300000 --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
||||||
"$SWAP_ROUTER" "exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))" \
|
|
||||||
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDR,10000000000000000,0,0)" >>"$SETUP_LOG" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
prime_chain() {
|
|
||||||
log "Pre-mining 5 blocks (minimal warmup for fast Ponder sync)..."
|
|
||||||
# Mine just 5 blocks - enough for Ponder to have some history but keeps sync fast
|
|
||||||
if cast rpc --rpc-url "$ANVIL_RPC" anvil_mine "0x5" "0x1" >/dev/null 2>&1; then
|
|
||||||
log "Mined 5 blocks"
|
|
||||||
else
|
|
||||||
log "Batch mining failed, using individual evm_mine calls"
|
|
||||||
for i in {1..5}; do
|
|
||||||
cast rpc --rpc-url "$ANVIL_RPC" evm_mine >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
log "Pre-mining complete"
|
|
||||||
}
|
|
||||||
|
|
||||||
write_deployments_json() {
|
|
||||||
cat >"$ROOT_DIR/onchain/deployments-local.json" <<EODEPLOYMENTS
|
|
||||||
{
|
|
||||||
"contracts": {
|
|
||||||
"Kraiken": "$KRAIKEN",
|
|
||||||
"Stake": "$STAKE",
|
|
||||||
"LiquidityManager": "$LIQUIDITY_MANAGER"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EODEPLOYMENTS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
write_ponder_env() {
|
write_ponder_env() {
|
||||||
|
|
@ -215,9 +94,10 @@ EOPONDER
|
||||||
}
|
}
|
||||||
|
|
||||||
write_txn_bot_env() {
|
write_txn_bot_env() {
|
||||||
|
local txnbot_env=$STATE_DIR/txnBot.env
|
||||||
local provider_url=${TXNBOT_PROVIDER_URL:-$ANVIL_RPC}
|
local provider_url=${TXNBOT_PROVIDER_URL:-$ANVIL_RPC}
|
||||||
local graphql_endpoint=${TXNBOT_GRAPHQL_ENDPOINT:-http://ponder:42069/graphql}
|
local graphql_endpoint=${TXNBOT_GRAPHQL_ENDPOINT:-http://ponder:42069/graphql}
|
||||||
cat >"$TXNBOT_ENV" <<EOTXNBOT
|
cat >"$txnbot_env" <<EOTXNBOT
|
||||||
ENVIRONMENT=BASE_SEPOLIA_LOCAL_FORK
|
ENVIRONMENT=BASE_SEPOLIA_LOCAL_FORK
|
||||||
PROVIDER_URL=$provider_url
|
PROVIDER_URL=$provider_url
|
||||||
PRIVATE_KEY=$TXNBOT_PRIVATE_KEY
|
PRIVATE_KEY=$TXNBOT_PRIVATE_KEY
|
||||||
|
|
@ -229,26 +109,32 @@ PORT=43069
|
||||||
EOTXNBOT
|
EOTXNBOT
|
||||||
}
|
}
|
||||||
|
|
||||||
fund_txn_bot_wallet() {
|
prime_chain() {
|
||||||
if [[ -z "$TXNBOT_ADDRESS" ]]; then
|
bootstrap_log "Pre-mining 5 blocks (minimal warmup for fast Ponder sync)..."
|
||||||
return
|
if cast rpc --rpc-url "$ANVIL_RPC" anvil_mine "0x5" "0x1" >/dev/null 2>&1; then
|
||||||
|
bootstrap_log "Mined 5 blocks"
|
||||||
|
else
|
||||||
|
bootstrap_log "Batch mining failed, using individual evm_mine calls"
|
||||||
|
for i in {1..5}; do
|
||||||
|
cast rpc --rpc-url "$ANVIL_RPC" evm_mine >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
log "Funding txnBot wallet $TXNBOT_ADDRESS"
|
bootstrap_log "Pre-mining complete"
|
||||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
||||||
"$TXNBOT_ADDRESS" --value "$TXNBOT_FUND_VALUE" >>"$SETUP_LOG" 2>&1 || true
|
|
||||||
local wei hex
|
|
||||||
wei="$(cast --to-unit "$TXNBOT_FUND_VALUE" wei)"
|
|
||||||
hex="$(cast --to-hex "$wei")"
|
|
||||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_setBalance "$TXNBOT_ADDRESS" "$hex" >>"$SETUP_LOG" 2>&1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Main ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
log "Waiting for Anvil"
|
local start_time
|
||||||
|
start_time=$(date +%s%3N)
|
||||||
|
|
||||||
|
bootstrap_log "Waiting for Anvil"
|
||||||
wait_for_rpc
|
wait_for_rpc
|
||||||
maybe_set_deployer_from_mnemonic
|
maybe_set_deployer_from_mnemonic
|
||||||
derive_txnbot_wallet
|
derive_txnbot_wallet
|
||||||
run_forge_script
|
run_forge_script
|
||||||
extract_addresses
|
extract_addresses
|
||||||
|
write_contracts_env
|
||||||
fund_liquidity_manager
|
fund_liquidity_manager
|
||||||
grant_recenter_access
|
grant_recenter_access
|
||||||
call_recenter
|
call_recenter
|
||||||
|
|
@ -260,14 +146,17 @@ main() {
|
||||||
prime_chain &
|
prime_chain &
|
||||||
local prime_pid=$!
|
local prime_pid=$!
|
||||||
wait "$prime_pid"
|
wait "$prime_pid"
|
||||||
BOOTSTRAP_END=$(date +%s%3N)
|
|
||||||
elapsed_ms=$((BOOTSTRAP_END - BOOTSTRAP_START))
|
local end_time
|
||||||
|
end_time=$(date +%s%3N)
|
||||||
|
local elapsed_ms=$((end_time - start_time))
|
||||||
|
local elapsed_sec
|
||||||
elapsed_sec=$(awk -v ms="$elapsed_ms" 'BEGIN { printf "%.3f", ms/1000 }')
|
elapsed_sec=$(awk -v ms="$elapsed_ms" 'BEGIN { printf "%.3f", ms/1000 }')
|
||||||
log "Bootstrap complete in ${elapsed_sec}s"
|
bootstrap_log "Bootstrap complete in ${elapsed_sec}s"
|
||||||
log "Kraiken: $KRAIKEN"
|
bootstrap_log "Kraiken: $KRAIKEN"
|
||||||
log "Stake: $STAKE"
|
bootstrap_log "Stake: $STAKE"
|
||||||
log "LiquidityManager: $LIQUIDITY_MANAGER"
|
bootstrap_log "LiquidityManager: $LIQUIDITY_MANAGER"
|
||||||
log "txnBot: $TXNBOT_ADDRESS"
|
bootstrap_log "txnBot: $TXNBOT_ADDRESS"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|
|
||||||
59
containers/entrypoint-common.sh
Executable file
59
containers/entrypoint-common.sh
Executable file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Shared helpers for service entrypoints (local dev mode).
|
||||||
|
# Source this file in each entrypoint script.
|
||||||
|
|
||||||
|
# Checkout a git branch if GIT_BRANCH is set.
|
||||||
|
# Args: $1 = root directory, $2 = log prefix
|
||||||
|
entrypoint_checkout_branch() {
|
||||||
|
local root_dir="$1"
|
||||||
|
local prefix="$2"
|
||||||
|
local git_branch="${GIT_BRANCH:-}"
|
||||||
|
|
||||||
|
if [[ -z "$git_branch" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$root_dir"
|
||||||
|
git config --global --add safe.directory "$root_dir" 2>/dev/null || true
|
||||||
|
local current
|
||||||
|
current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
|
if [[ "$current" != "$git_branch" ]]; then
|
||||||
|
echo "[$prefix] Switching to branch: $git_branch"
|
||||||
|
if git rev-parse --verify "$git_branch" >/dev/null 2>&1; then
|
||||||
|
git checkout "$git_branch" 2>/dev/null || echo "[$prefix] WARNING: Could not checkout $git_branch"
|
||||||
|
else
|
||||||
|
git fetch origin "$git_branch" 2>/dev/null || true
|
||||||
|
git checkout "$git_branch" 2>/dev/null || echo "[$prefix] WARNING: Could not checkout $git_branch"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate kraiken-lib dist exists.
|
||||||
|
# Args: $1 = root directory, $2 = log prefix
|
||||||
|
entrypoint_require_kraiken_lib() {
|
||||||
|
local root_dir="$1"
|
||||||
|
local prefix="$2"
|
||||||
|
local required_dist="$root_dir/kraiken-lib/dist/index.js"
|
||||||
|
|
||||||
|
if [[ ! -f "$required_dist" ]]; then
|
||||||
|
echo "[$prefix] ERROR: Run ./scripts/build-kraiken-lib.sh before starting containers" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install node_modules if needed (named volume may be empty).
|
||||||
|
# Args: $1 = log prefix
|
||||||
|
entrypoint_install_deps() {
|
||||||
|
local prefix="$1"
|
||||||
|
|
||||||
|
if [[ ! -d node_modules/.bin ]]; then
|
||||||
|
echo "[$prefix] Installing dependencies..."
|
||||||
|
npm ci --loglevel error && npm cache clean --force 2>&1 || {
|
||||||
|
echo "[$prefix] npm ci failed, trying npm install"
|
||||||
|
npm install --no-save --loglevel error && npm cache clean --force
|
||||||
|
}
|
||||||
|
else
|
||||||
|
echo "[$prefix] Using cached node_modules from volume"
|
||||||
|
fi
|
||||||
|
}
|
||||||
5
containers/landing-ci-entrypoint.sh
Executable file
5
containers/landing-ci-entrypoint.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Minimal CI entrypoint for landing — just starts the dev server.
|
||||||
|
set -euo pipefail
|
||||||
|
cd /app/landing
|
||||||
|
exec npm run dev -- --host 0.0.0.0 --port 5174
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR=/workspace
|
|
||||||
GIT_BRANCH="${GIT_BRANCH:-}"
|
|
||||||
|
|
||||||
# Checkout branch if specified
|
|
||||||
if [[ -n "$GIT_BRANCH" ]]; then
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
git config --global --add safe.directory "$ROOT_DIR" 2>/dev/null || true
|
|
||||||
CURRENT=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
||||||
|
|
||||||
if [[ "$CURRENT" != "$GIT_BRANCH" ]]; then
|
|
||||||
echo "[landing-entrypoint] Switching to branch: $GIT_BRANCH"
|
|
||||||
# Try local branch first, then remote
|
|
||||||
if git rev-parse --verify "$GIT_BRANCH" >/dev/null 2>&1; then
|
|
||||||
git checkout "$GIT_BRANCH" 2>/dev/null || echo "[landing-entrypoint] WARNING: Could not checkout $GIT_BRANCH"
|
|
||||||
else
|
|
||||||
git fetch origin "$GIT_BRANCH" 2>/dev/null || true
|
|
||||||
git checkout "$GIT_BRANCH" 2>/dev/null || echo "[landing-entrypoint] WARNING: Could not checkout $GIT_BRANCH"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
LANDING_DIR=$ROOT_DIR/landing
|
|
||||||
REQUIRED_DIST="$ROOT_DIR/kraiken-lib/dist/index.js"
|
|
||||||
|
|
||||||
if [[ ! -f "$REQUIRED_DIST" ]]; then
|
|
||||||
echo "[landing-entrypoint] ERROR: Run ./scripts/build-kraiken-lib.sh before starting containers" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$LANDING_DIR"
|
|
||||||
|
|
||||||
# Check if node_modules is populated (named volume may be empty on first run)
|
|
||||||
if [[ ! -d node_modules/.bin ]]; then
|
|
||||||
echo "[landing-entrypoint] Installing dependencies..."
|
|
||||||
npm ci --loglevel error && npm cache clean --force 2>&1 || {
|
|
||||||
echo "[landing-entrypoint] npm ci failed, trying npm install"
|
|
||||||
npm install --no-save --loglevel error && npm cache clean --force
|
|
||||||
}
|
|
||||||
else
|
|
||||||
echo "[landing-entrypoint] Using cached node_modules from volume"
|
|
||||||
fi
|
|
||||||
|
|
||||||
export CHOKIDAR_USEPOLLING=${CHOKIDAR_USEPOLLING:-1}
|
|
||||||
export HOST=0.0.0.0
|
|
||||||
export PORT=${PORT:-5174}
|
|
||||||
|
|
||||||
exec npm run dev -- --host 0.0.0.0 --port 5174
|
|
||||||
20
containers/landing-entrypoint.sh
Executable file
20
containers/landing-entrypoint.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR=/workspace
|
||||||
|
|
||||||
|
# shellcheck source=entrypoint-common.sh
|
||||||
|
source "$ROOT_DIR/containers/entrypoint-common.sh"
|
||||||
|
|
||||||
|
entrypoint_checkout_branch "$ROOT_DIR" "landing-entrypoint"
|
||||||
|
entrypoint_require_kraiken_lib "$ROOT_DIR" "landing-entrypoint"
|
||||||
|
|
||||||
|
cd "$ROOT_DIR/landing"
|
||||||
|
|
||||||
|
entrypoint_install_deps "landing-entrypoint"
|
||||||
|
|
||||||
|
export CHOKIDAR_USEPOLLING=${CHOKIDAR_USEPOLLING:-1}
|
||||||
|
export HOST=0.0.0.0
|
||||||
|
export PORT=${PORT:-5174}
|
||||||
|
|
||||||
|
exec npm run dev -- --host 0.0.0.0 --port 5174
|
||||||
|
|
@ -1,27 +1,42 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT_DIR=/workspace
|
if [[ "${CI:-}" == "true" ]]; then
|
||||||
GIT_BRANCH="${GIT_BRANCH:-}"
|
# ── CI path ────────────────────────────────────────────────────────
|
||||||
|
cd /app/services/ponder
|
||||||
|
|
||||||
# Checkout branch if specified
|
echo "[ponder-ci] Starting Ponder indexer..."
|
||||||
if [[ -n "$GIT_BRANCH" ]]; then
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
git config --global --add safe.directory "$ROOT_DIR" 2>/dev/null || true
|
|
||||||
CURRENT=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
||||||
|
|
||||||
if [[ "$CURRENT" != "$GIT_BRANCH" ]]; then
|
: "${DATABASE_URL:?DATABASE_URL is required}"
|
||||||
echo "[ponder-entrypoint] Switching to branch: $GIT_BRANCH"
|
: "${PONDER_RPC_URL_1:?PONDER_RPC_URL_1 is required}"
|
||||||
# Try local branch first, then remote
|
|
||||||
if git rev-parse --verify "$GIT_BRANCH" >/dev/null 2>&1; then
|
export PONDER_RPC_TIMEOUT=${PONDER_RPC_TIMEOUT:-20000}
|
||||||
git checkout "$GIT_BRANCH" 2>/dev/null || echo "[ponder-entrypoint] WARNING: Could not checkout $GIT_BRANCH"
|
export HOST=${HOST:-0.0.0.0}
|
||||||
else
|
export PORT=${PORT:-42069}
|
||||||
git fetch origin "$GIT_BRANCH" 2>/dev/null || true
|
|
||||||
git checkout "$GIT_BRANCH" 2>/dev/null || echo "[ponder-entrypoint] WARNING: Could not checkout $GIT_BRANCH"
|
cat > .env.local <<EOF
|
||||||
fi
|
DATABASE_URL=${DATABASE_URL}
|
||||||
fi
|
PONDER_RPC_URL_1=${PONDER_RPC_URL_1}
|
||||||
|
DATABASE_SCHEMA=${DATABASE_SCHEMA:-ponder_ci}
|
||||||
|
START_BLOCK=${START_BLOCK:-0}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "[ponder-ci] Environment configured:"
|
||||||
|
echo " DATABASE_URL: ${DATABASE_URL}"
|
||||||
|
echo " PONDER_RPC_URL_1: ${PONDER_RPC_URL_1}"
|
||||||
|
echo " START_BLOCK: ${START_BLOCK:-0}"
|
||||||
|
|
||||||
|
exec npm run dev
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Local dev path ─────────────────────────────────────────────────
|
||||||
|
ROOT_DIR=/workspace
|
||||||
|
|
||||||
|
# shellcheck source=entrypoint-common.sh
|
||||||
|
source "$ROOT_DIR/containers/entrypoint-common.sh"
|
||||||
|
|
||||||
|
entrypoint_checkout_branch "$ROOT_DIR" "ponder-entrypoint"
|
||||||
|
|
||||||
CONTRACT_ENV=$ROOT_DIR/tmp/containers/contracts.env
|
CONTRACT_ENV=$ROOT_DIR/tmp/containers/contracts.env
|
||||||
PONDER_WORKDIR=$ROOT_DIR/services/ponder
|
PONDER_WORKDIR=$ROOT_DIR/services/ponder
|
||||||
|
|
||||||
|
|
@ -59,22 +74,8 @@ if [[ -n "$START_BLOCK" ]]; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
REQUIRED_DIST="$ROOT_DIR/kraiken-lib/dist/index.js"
|
entrypoint_require_kraiken_lib "$ROOT_DIR" "ponder-entrypoint"
|
||||||
if [[ ! -f "$REQUIRED_DIST" ]]; then
|
entrypoint_install_deps "ponder-entrypoint"
|
||||||
echo "[ponder-entrypoint] ERROR: Run ./scripts/build-kraiken-lib.sh before starting containers" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if node_modules is populated (named volume may be empty on first run)
|
|
||||||
if [[ ! -d node_modules/.bin ]]; then
|
|
||||||
echo "[ponder-entrypoint] Installing dependencies..."
|
|
||||||
npm ci --loglevel error && npm cache clean --force 2>&1 || {
|
|
||||||
echo "[ponder-entrypoint] npm ci failed, trying npm install"
|
|
||||||
npm install --no-save --loglevel error && npm cache clean --force
|
|
||||||
}
|
|
||||||
else
|
|
||||||
echo "[ponder-entrypoint] Using cached node_modules from volume"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Load and export all environment variables from .env.local
|
# Load and export all environment variables from .env.local
|
||||||
if [[ -f .env.local ]]; then
|
if [[ -f .env.local ]]; then
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR=/workspace
|
|
||||||
GIT_BRANCH="${GIT_BRANCH:-}"
|
|
||||||
|
|
||||||
# Checkout branch if specified
|
|
||||||
if [[ -n "$GIT_BRANCH" ]]; then
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
git config --global --add safe.directory "$ROOT_DIR" 2>/dev/null || true
|
|
||||||
CURRENT=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
||||||
|
|
||||||
if [[ "$CURRENT" != "$GIT_BRANCH" ]]; then
|
|
||||||
echo "[txn-bot-entrypoint] Switching to branch: $GIT_BRANCH"
|
|
||||||
# Try local branch first, then remote
|
|
||||||
if git rev-parse --verify "$GIT_BRANCH" >/dev/null 2>&1; then
|
|
||||||
git checkout "$GIT_BRANCH" 2>/dev/null || echo "[txn-bot-entrypoint] WARNING: Could not checkout $GIT_BRANCH"
|
|
||||||
else
|
|
||||||
git fetch origin "$GIT_BRANCH" 2>/dev/null || true
|
|
||||||
git checkout "$GIT_BRANCH" 2>/dev/null || echo "[txn-bot-entrypoint] WARNING: Could not checkout $GIT_BRANCH"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
TXNBOT_ENV_FILE=$ROOT_DIR/tmp/containers/txnBot.env
|
|
||||||
BOT_DIR=$ROOT_DIR/services/txnBot
|
|
||||||
REQUIRED_DIST=$ROOT_DIR/kraiken-lib/dist/index.js
|
|
||||||
|
|
||||||
while [[ ! -f "$TXNBOT_ENV_FILE" ]]; do
|
|
||||||
echo "[txn-bot-entrypoint] waiting for env file"
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ ! -f "$REQUIRED_DIST" ]]; then
|
|
||||||
echo "[txn-bot-entrypoint] ERROR: Run ./scripts/build-kraiken-lib.sh before starting containers" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$BOT_DIR"
|
|
||||||
|
|
||||||
# Check if node_modules is populated (named volume may be empty on first run)
|
|
||||||
if [[ ! -d node_modules/.bin ]]; then
|
|
||||||
echo "[txn-bot-entrypoint] Installing txn-bot dependencies..."
|
|
||||||
npm ci --loglevel error && npm cache clean --force 2>&1 || {
|
|
||||||
echo "[txn-bot-entrypoint] npm ci failed, trying npm install"
|
|
||||||
npm install --no-save --loglevel error && npm cache clean --force
|
|
||||||
}
|
|
||||||
else
|
|
||||||
echo "[txn-bot-entrypoint] Using cached node_modules from volume"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[txn-bot-entrypoint] Building TypeScript..."
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
export TXN_BOT_ENV_FILE="$TXNBOT_ENV_FILE"
|
|
||||||
exec npm run start
|
|
||||||
62
containers/txnbot-entrypoint.sh
Executable file
62
containers/txnbot-entrypoint.sh
Executable file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${CI:-}" == "true" ]]; then
|
||||||
|
# ── CI path ────────────────────────────────────────────────────────
|
||||||
|
echo "[txnbot-ci] Starting Transaction Bot..."
|
||||||
|
|
||||||
|
: "${TXNBOT_PRIVATE_KEY:?TXNBOT_PRIVATE_KEY is required}"
|
||||||
|
: "${RPC_URL:?RPC_URL is required}"
|
||||||
|
: "${KRAIKEN_ADDRESS:?KRAIKEN_ADDRESS is required}"
|
||||||
|
: "${STAKE_ADDRESS:?STAKE_ADDRESS is required}"
|
||||||
|
: "${LIQUIDITY_MANAGER_ADDRESS:?LIQUIDITY_MANAGER_ADDRESS is required}"
|
||||||
|
|
||||||
|
cat > /tmp/txnBot.env <<EOF
|
||||||
|
TXNBOT_PRIVATE_KEY=${TXNBOT_PRIVATE_KEY}
|
||||||
|
RPC_URL=${RPC_URL}
|
||||||
|
KRAIKEN_ADDRESS=${KRAIKEN_ADDRESS}
|
||||||
|
STAKE_ADDRESS=${STAKE_ADDRESS}
|
||||||
|
LIQUIDITY_MANAGER_ADDRESS=${LIQUIDITY_MANAGER_ADDRESS}
|
||||||
|
POOL_ADDRESS=${POOL_ADDRESS:-}
|
||||||
|
WETH_ADDRESS=${WETH_ADDRESS:-0x4200000000000000000000000000000000000006}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
export TXN_BOT_ENV_FILE=/tmp/txnBot.env
|
||||||
|
|
||||||
|
echo "[txnbot-ci] Environment configured:"
|
||||||
|
echo " RPC_URL: ${RPC_URL}"
|
||||||
|
echo " KRAIKEN_ADDRESS: ${KRAIKEN_ADDRESS}"
|
||||||
|
|
||||||
|
echo "[txnbot-ci] Building TypeScript..."
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
exec npm run start
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Local dev path ─────────────────────────────────────────────────
|
||||||
|
ROOT_DIR=/workspace
|
||||||
|
|
||||||
|
# shellcheck source=entrypoint-common.sh
|
||||||
|
source "$ROOT_DIR/containers/entrypoint-common.sh"
|
||||||
|
|
||||||
|
entrypoint_checkout_branch "$ROOT_DIR" "txnbot-entrypoint"
|
||||||
|
|
||||||
|
TXNBOT_ENV_FILE=$ROOT_DIR/tmp/containers/txnBot.env
|
||||||
|
BOT_DIR=$ROOT_DIR/services/txnBot
|
||||||
|
|
||||||
|
while [[ ! -f "$TXNBOT_ENV_FILE" ]]; do
|
||||||
|
echo "[txnbot-entrypoint] waiting for env file"
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
entrypoint_require_kraiken_lib "$ROOT_DIR" "txnbot-entrypoint"
|
||||||
|
|
||||||
|
cd "$BOT_DIR"
|
||||||
|
|
||||||
|
entrypoint_install_deps "txnbot-entrypoint"
|
||||||
|
|
||||||
|
echo "[txnbot-entrypoint] Building TypeScript..."
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
export TXN_BOT_ENV_FILE="$TXNBOT_ENV_FILE"
|
||||||
|
exec npm run start
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR=/workspace
|
|
||||||
GIT_BRANCH="${GIT_BRANCH:-}"
|
|
||||||
|
|
||||||
# Checkout branch if specified
|
|
||||||
if [[ -n "$GIT_BRANCH" ]]; then
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
git config --global --add safe.directory "$ROOT_DIR" 2>/dev/null || true
|
|
||||||
CURRENT=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
||||||
|
|
||||||
if [[ "$CURRENT" != "$GIT_BRANCH" ]]; then
|
|
||||||
echo "[webapp-entrypoint] Switching to branch: $GIT_BRANCH"
|
|
||||||
# Try local branch first, then remote
|
|
||||||
if git rev-parse --verify "$GIT_BRANCH" >/dev/null 2>&1; then
|
|
||||||
git checkout "$GIT_BRANCH" 2>/dev/null || echo "[webapp-entrypoint] WARNING: Could not checkout $GIT_BRANCH"
|
|
||||||
else
|
|
||||||
git fetch origin "$GIT_BRANCH" 2>/dev/null || true
|
|
||||||
git checkout "$GIT_BRANCH" 2>/dev/null || echo "[webapp-entrypoint] WARNING: Could not checkout $GIT_BRANCH"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
CONTRACT_ENV=$ROOT_DIR/tmp/containers/contracts.env
|
|
||||||
APP_DIR=$ROOT_DIR/web-app
|
|
||||||
SWAP_ROUTER=0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4
|
|
||||||
|
|
||||||
while [[ ! -f "$CONTRACT_ENV" ]]; do
|
|
||||||
echo "[frontend-entrypoint] waiting for contracts env"
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
REQUIRED_DIST="$ROOT_DIR/kraiken-lib/dist/index.js"
|
|
||||||
if [[ ! -f "$REQUIRED_DIST" ]]; then
|
|
||||||
echo "[frontend-entrypoint] ERROR: Run ./scripts/build-kraiken-lib.sh before starting containers" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
source "$CONTRACT_ENV"
|
|
||||||
|
|
||||||
cd "$APP_DIR"
|
|
||||||
|
|
||||||
# Check if node_modules is populated (named volume may be empty on first run)
|
|
||||||
if [[ ! -d node_modules/.bin ]]; then
|
|
||||||
echo "[frontend-entrypoint] Installing dependencies..."
|
|
||||||
npm ci --loglevel error && npm cache clean --force 2>&1 || {
|
|
||||||
echo "[frontend-entrypoint] npm ci failed, trying npm install"
|
|
||||||
npm install --no-save --loglevel error && npm cache clean --force
|
|
||||||
}
|
|
||||||
else
|
|
||||||
echo "[frontend-entrypoint] Using cached node_modules from volume"
|
|
||||||
fi
|
|
||||||
|
|
||||||
export VITE_DEFAULT_CHAIN_ID=${VITE_DEFAULT_CHAIN_ID:-31337}
|
|
||||||
export VITE_LOCAL_RPC_URL=${VITE_LOCAL_RPC_URL:-/api/rpc}
|
|
||||||
export VITE_LOCAL_RPC_PROXY_TARGET=${VITE_LOCAL_RPC_PROXY_TARGET:-http://anvil:8545}
|
|
||||||
export VITE_LOCAL_GRAPHQL_PROXY_TARGET=${VITE_LOCAL_GRAPHQL_PROXY_TARGET:-http://ponder:42069}
|
|
||||||
export VITE_LOCAL_TXN_PROXY_TARGET=${VITE_LOCAL_TXN_PROXY_TARGET:-http://txn-bot:43069}
|
|
||||||
export VITE_KRAIKEN_ADDRESS=$KRAIKEN
|
|
||||||
export VITE_STAKE_ADDRESS=$STAKE
|
|
||||||
export VITE_SWAP_ROUTER=$SWAP_ROUTER
|
|
||||||
export VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK=${VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK:-/api/graphql}
|
|
||||||
export VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK=${VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK:-/api/txn}
|
|
||||||
export CHOKIDAR_USEPOLLING=${CHOKIDAR_USEPOLLING:-1}
|
|
||||||
|
|
||||||
exec npm run dev -- --host 0.0.0.0 --port 5173 --base /app/
|
|
||||||
70
containers/webapp-entrypoint.sh
Executable file
70
containers/webapp-entrypoint.sh
Executable file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${CI:-}" == "true" ]]; then
|
||||||
|
# ── CI path ────────────────────────────────────────────────────────
|
||||||
|
cd /app/web-app
|
||||||
|
|
||||||
|
echo "[webapp-ci] Starting Web App..."
|
||||||
|
|
||||||
|
: "${VITE_KRAIKEN_ADDRESS:?VITE_KRAIKEN_ADDRESS is required}"
|
||||||
|
: "${VITE_STAKE_ADDRESS:?VITE_STAKE_ADDRESS is required}"
|
||||||
|
|
||||||
|
# Disable Vue DevTools in CI to avoid path resolution issues
|
||||||
|
export CI=true
|
||||||
|
export VITE_DEFAULT_CHAIN_ID=${VITE_DEFAULT_CHAIN_ID:-31337}
|
||||||
|
export VITE_LOCAL_RPC_URL=${VITE_LOCAL_RPC_URL:-/api/rpc}
|
||||||
|
export VITE_LOCAL_RPC_PROXY_TARGET=${VITE_LOCAL_RPC_PROXY_TARGET:-http://anvil:8545}
|
||||||
|
export VITE_LOCAL_GRAPHQL_PROXY_TARGET=${VITE_LOCAL_GRAPHQL_PROXY_TARGET:-http://ponder:42069}
|
||||||
|
export VITE_LOCAL_TXN_PROXY_TARGET=${VITE_LOCAL_TXN_PROXY_TARGET:-http://txn-bot:43069}
|
||||||
|
export VITE_SWAP_ROUTER=${VITE_SWAP_ROUTER:-0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4}
|
||||||
|
export VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK=${VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK:-/api/graphql}
|
||||||
|
export VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK=${VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK:-/api/txn}
|
||||||
|
|
||||||
|
echo "[webapp-ci] Environment configured:"
|
||||||
|
echo " VITE_KRAIKEN_ADDRESS: ${VITE_KRAIKEN_ADDRESS}"
|
||||||
|
echo " VITE_STAKE_ADDRESS: ${VITE_STAKE_ADDRESS}"
|
||||||
|
echo " VITE_DEFAULT_CHAIN_ID: ${VITE_DEFAULT_CHAIN_ID}"
|
||||||
|
|
||||||
|
exec npm run dev -- --host 0.0.0.0 --port 5173 --base /app/
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Local dev path ─────────────────────────────────────────────────
|
||||||
|
ROOT_DIR=/workspace
|
||||||
|
SWAP_ROUTER=0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4
|
||||||
|
|
||||||
|
# shellcheck source=entrypoint-common.sh
|
||||||
|
source "$ROOT_DIR/containers/entrypoint-common.sh"
|
||||||
|
|
||||||
|
entrypoint_checkout_branch "$ROOT_DIR" "webapp-entrypoint"
|
||||||
|
|
||||||
|
CONTRACT_ENV=$ROOT_DIR/tmp/containers/contracts.env
|
||||||
|
APP_DIR=$ROOT_DIR/web-app
|
||||||
|
|
||||||
|
while [[ ! -f "$CONTRACT_ENV" ]]; do
|
||||||
|
echo "[webapp-entrypoint] waiting for contracts env"
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
entrypoint_require_kraiken_lib "$ROOT_DIR" "webapp-entrypoint"
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$CONTRACT_ENV"
|
||||||
|
|
||||||
|
cd "$APP_DIR"
|
||||||
|
|
||||||
|
entrypoint_install_deps "webapp-entrypoint"
|
||||||
|
|
||||||
|
export VITE_DEFAULT_CHAIN_ID=${VITE_DEFAULT_CHAIN_ID:-31337}
|
||||||
|
export VITE_LOCAL_RPC_URL=${VITE_LOCAL_RPC_URL:-/api/rpc}
|
||||||
|
export VITE_LOCAL_RPC_PROXY_TARGET=${VITE_LOCAL_RPC_PROXY_TARGET:-http://anvil:8545}
|
||||||
|
export VITE_LOCAL_GRAPHQL_PROXY_TARGET=${VITE_LOCAL_GRAPHQL_PROXY_TARGET:-http://ponder:42069}
|
||||||
|
export VITE_LOCAL_TXN_PROXY_TARGET=${VITE_LOCAL_TXN_PROXY_TARGET:-http://txn-bot:43069}
|
||||||
|
export VITE_KRAIKEN_ADDRESS=$KRAIKEN
|
||||||
|
export VITE_STAKE_ADDRESS=$STAKE
|
||||||
|
export VITE_SWAP_ROUTER=$SWAP_ROUTER
|
||||||
|
export VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK=${VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK:-/api/graphql}
|
||||||
|
export VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK=${VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK:-/api/txn}
|
||||||
|
export CHOKIDAR_USEPOLLING=${CHOKIDAR_USEPOLLING:-1}
|
||||||
|
|
||||||
|
exec npm run dev -- --host 0.0.0.0 --port 5173 --base /app/
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
# CI-specific docker-compose file
|
|
||||||
# This provides a simplified interface for running the integration stack in Woodpecker CI
|
|
||||||
# Usage: docker-compose -f docker-compose.ci.yml up -d
|
|
||||||
|
|
||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
|
||||||
harb-stack:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: docker/Dockerfile.integration
|
|
||||||
privileged: true # Required for Docker-in-Docker
|
|
||||||
environment:
|
|
||||||
- HARB_ENV=BASE_SEPOLIA_LOCAL_FORK
|
|
||||||
- SKIP_WATCH=1
|
|
||||||
- COMPOSE_PROJECT_NAME=harb-ci
|
|
||||||
ports:
|
|
||||||
- "8081:8081" # Caddy (main API gateway)
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8081/api/graphql"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 30
|
|
||||||
start_period: 120s
|
|
||||||
volumes:
|
|
||||||
# Mount the workspace so changes are reflected (for local testing)
|
|
||||||
- .:/workspace:cached
|
|
||||||
# Persist Docker state within the container
|
|
||||||
- harb-ci-docker:/var/lib/docker
|
|
||||||
networks:
|
|
||||||
- harb-ci-network
|
|
||||||
|
|
||||||
networks:
|
|
||||||
harb-ci-network:
|
|
||||||
driver: bridge
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
harb-ci-docker:
|
|
||||||
|
|
@ -88,7 +88,7 @@ services:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: containers/node-dev.Containerfile
|
dockerfile: containers/node-dev.Containerfile
|
||||||
entrypoint: ["/workspace/containers/ponder-dev-entrypoint.sh"]
|
entrypoint: ["/workspace/containers/ponder-entrypoint.sh"]
|
||||||
user: "0:0"
|
user: "0:0"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/workspace:z
|
- .:/workspace:z
|
||||||
|
|
@ -119,7 +119,7 @@ services:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: containers/node-dev.Containerfile
|
dockerfile: containers/node-dev.Containerfile
|
||||||
entrypoint: ["/workspace/containers/webapp-dev-entrypoint.sh"]
|
entrypoint: ["/workspace/containers/webapp-entrypoint.sh"]
|
||||||
user: "0:0"
|
user: "0:0"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/workspace:z
|
- .:/workspace:z
|
||||||
|
|
@ -141,14 +141,14 @@ services:
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:5173/"]
|
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:5173/"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
retries: 6
|
retries: 24
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
|
|
||||||
landing:
|
landing:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: containers/node-dev.Containerfile
|
dockerfile: containers/node-dev.Containerfile
|
||||||
entrypoint: ["/workspace/containers/landing-dev-entrypoint.sh"]
|
entrypoint: ["/workspace/containers/landing-entrypoint.sh"]
|
||||||
user: "0:0"
|
user: "0:0"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/workspace:z
|
- .:/workspace:z
|
||||||
|
|
@ -175,7 +175,7 @@ services:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: containers/node-dev.Containerfile
|
dockerfile: containers/node-dev.Containerfile
|
||||||
entrypoint: ["/workspace/containers/txn-bot-entrypoint.sh"]
|
entrypoint: ["/workspace/containers/txnbot-entrypoint.sh"]
|
||||||
user: "0:0"
|
user: "0:0"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/workspace:z
|
- .:/workspace:z
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
# syntax=docker/dockerfile:1.6
|
|
||||||
# Composite integration image that bundles the entire Harb stack for E2E testing
|
|
||||||
# This image runs docker-compose internally to orchestrate all services
|
|
||||||
|
|
||||||
FROM docker:27-dind
|
|
||||||
|
|
||||||
LABEL org.opencontainers.image.source="https://codeberg.org/johba/harb-ci"
|
|
||||||
LABEL org.opencontainers.image.description="Harb Stack integration container for E2E CI tests"
|
|
||||||
|
|
||||||
ENV DOCKER_TLS_CERTDIR="" \
|
|
||||||
COMPOSE_PROJECT_NAME=harb-ci \
|
|
||||||
HARB_ENV=BASE_SEPOLIA_LOCAL_FORK \
|
|
||||||
SKIP_WATCH=1
|
|
||||||
|
|
||||||
# Install docker-compose, bash, curl, and other essentials
|
|
||||||
RUN apk add --no-cache \
|
|
||||||
bash \
|
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
docker-cli-compose \
|
|
||||||
shadow \
|
|
||||||
su-exec
|
|
||||||
|
|
||||||
# Create a non-root user for running the stack
|
|
||||||
RUN addgroup -g 1000 harb && \
|
|
||||||
adduser -D -u 1000 -G harb harb
|
|
||||||
|
|
||||||
WORKDIR /workspace
|
|
||||||
|
|
||||||
# Copy the entire project (will be mounted at runtime in CI, but needed for standalone usage)
|
|
||||||
COPY --chown=harb:harb . /workspace/
|
|
||||||
|
|
||||||
# Pre-build kraiken-lib to speed up startup
|
|
||||||
RUN cd /workspace && \
|
|
||||||
if [ -f scripts/build-kraiken-lib.sh ]; then \
|
|
||||||
./scripts/build-kraiken-lib.sh || echo "kraiken-lib build skipped"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Healthcheck: verify the stack is responding via Caddy
|
|
||||||
HEALTHCHECK --interval=5s --timeout=3s --retries=30 --start-period=120s \
|
|
||||||
CMD curl -f http://localhost:8081/api/graphql || exit 1
|
|
||||||
|
|
||||||
# Entrypoint script to start Docker daemon and the stack
|
|
||||||
COPY docker/integration-entrypoint.sh /entrypoint.sh
|
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
|
|
||||||
EXPOSE 8081
|
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
||||||
CMD ["bash"]
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
# Production image for Landing page (Vite + Vue)
|
|
||||||
# Used in CI for E2E testing - contains all code baked in
|
|
||||||
|
|
||||||
FROM node:20-alpine AS builder
|
|
||||||
|
|
||||||
RUN apk add --no-cache git bash
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package files first for better caching
|
|
||||||
COPY package.json package-lock.json ./
|
|
||||||
COPY kraiken-lib/package.json kraiken-lib/package-lock.json ./kraiken-lib/
|
|
||||||
COPY landing/package.json landing/package-lock.json ./landing/
|
|
||||||
|
|
||||||
# Copy ABI files needed by kraiken-lib
|
|
||||||
COPY onchain/out/Kraiken.sol/Kraiken.json ./onchain/out/Kraiken.sol/
|
|
||||||
COPY onchain/out/Stake.sol/Stake.json ./onchain/out/Stake.sol/
|
|
||||||
|
|
||||||
# Install kraiken-lib dependencies and build
|
|
||||||
WORKDIR /app/kraiken-lib
|
|
||||||
RUN npm ci --ignore-scripts
|
|
||||||
COPY kraiken-lib/ ./
|
|
||||||
RUN ./node_modules/.bin/tsc
|
|
||||||
|
|
||||||
# Install landing dependencies
|
|
||||||
WORKDIR /app/landing
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Copy landing source
|
|
||||||
COPY landing/ ./
|
|
||||||
|
|
||||||
# Production image
|
|
||||||
FROM node:20-alpine
|
|
||||||
|
|
||||||
RUN apk add --no-cache dumb-init wget bash
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy kraiken-lib (src for vite alias, dist for runtime)
|
|
||||||
COPY --from=builder /app/kraiken-lib/src ./kraiken-lib/src
|
|
||||||
COPY --from=builder /app/kraiken-lib/dist ./kraiken-lib/dist
|
|
||||||
COPY --from=builder /app/kraiken-lib/package.json ./kraiken-lib/
|
|
||||||
COPY --from=builder /app/landing ./landing
|
|
||||||
|
|
||||||
WORKDIR /app/landing
|
|
||||||
|
|
||||||
ENV NODE_ENV=development
|
|
||||||
ENV HOST=0.0.0.0
|
|
||||||
ENV PORT=5174
|
|
||||||
|
|
||||||
EXPOSE 5174
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=3s --retries=6 --start-period=10s \
|
|
||||||
CMD wget --spider -q http://127.0.0.1:5174/ || exit 1
|
|
||||||
|
|
||||||
# Landing doesn't need contract addresses - just serve static content
|
|
||||||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5174"]
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
# Production image for Ponder indexer service
|
|
||||||
# Used in CI for E2E testing - contains all code baked in
|
|
||||||
|
|
||||||
FROM node:20-alpine AS builder
|
|
||||||
|
|
||||||
RUN apk add --no-cache git bash
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package files first for better caching
|
|
||||||
COPY package.json package-lock.json ./
|
|
||||||
COPY kraiken-lib/package.json kraiken-lib/package-lock.json ./kraiken-lib/
|
|
||||||
COPY services/ponder/package.json services/ponder/package-lock.json ./services/ponder/
|
|
||||||
|
|
||||||
# Copy ABI files needed by kraiken-lib
|
|
||||||
COPY onchain/out/Kraiken.sol/Kraiken.json ./onchain/out/Kraiken.sol/
|
|
||||||
COPY onchain/out/Stake.sol/Stake.json ./onchain/out/Stake.sol/
|
|
||||||
|
|
||||||
# Install kraiken-lib dependencies and build
|
|
||||||
WORKDIR /app/kraiken-lib
|
|
||||||
RUN npm ci --ignore-scripts
|
|
||||||
COPY kraiken-lib/ ./
|
|
||||||
RUN ./node_modules/.bin/tsc
|
|
||||||
|
|
||||||
# Install ponder dependencies
|
|
||||||
WORKDIR /app/services/ponder
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Copy ponder source
|
|
||||||
COPY services/ponder/ ./
|
|
||||||
|
|
||||||
# Copy shared config files needed by ponder
|
|
||||||
WORKDIR /app
|
|
||||||
COPY onchain/deployments*.json ./onchain/
|
|
||||||
|
|
||||||
# Production image
|
|
||||||
FROM node:20-alpine
|
|
||||||
|
|
||||||
RUN apk add --no-cache dumb-init wget postgresql-client bash
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy kraiken-lib with full structure (needed for node_modules symlink resolution)
|
|
||||||
COPY --from=builder /app/kraiken-lib ./kraiken-lib
|
|
||||||
|
|
||||||
# Copy ponder with all node_modules
|
|
||||||
COPY --from=builder /app/services/ponder ./services/ponder
|
|
||||||
|
|
||||||
# Copy onchain artifacts
|
|
||||||
COPY --from=builder /app/onchain ./onchain
|
|
||||||
|
|
||||||
# Copy entrypoint
|
|
||||||
COPY docker/ci-entrypoints/ponder-ci-entrypoint.sh /entrypoint.sh
|
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
|
|
||||||
WORKDIR /app/services/ponder
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
ENV HOST=0.0.0.0
|
|
||||||
ENV PORT=42069
|
|
||||||
|
|
||||||
EXPOSE 42069
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=3s --retries=12 --start-period=20s \
|
|
||||||
CMD wget --spider -q http://127.0.0.1:42069/ || exit 1
|
|
||||||
|
|
||||||
ENTRYPOINT ["dumb-init", "--", "/entrypoint.sh"]
|
|
||||||
113
docker/Dockerfile.service-ci
Normal file
113
docker/Dockerfile.service-ci
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# Unified CI image for Harb services (ponder, webapp, landing, txnBot).
|
||||||
|
# Parameterized via build args — eliminates per-service Dockerfile duplication.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# docker build -f docker/Dockerfile.service-ci \
|
||||||
|
# --build-arg SERVICE_DIR=services/ponder \
|
||||||
|
# --build-arg SERVICE_PORT=42069 \
|
||||||
|
# --build-arg ENTRYPOINT_SCRIPT=containers/ponder-entrypoint.sh \
|
||||||
|
# -t ponder-ci .
|
||||||
|
|
||||||
|
# ── Build args (declared early for builder stage) ──────────────────
|
||||||
|
ARG SERVICE_DIR
|
||||||
|
ARG NPM_INSTALL_CMD=ci
|
||||||
|
|
||||||
|
# ── Builder stage ──────────────────────────────────────────────────
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache git bash
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy root package files
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
|
||||||
|
# Copy kraiken-lib package files
|
||||||
|
COPY kraiken-lib/package.json kraiken-lib/package-lock.json ./kraiken-lib/
|
||||||
|
|
||||||
|
# Copy ABI files needed by kraiken-lib
|
||||||
|
COPY onchain/out/Kraiken.sol/Kraiken.json ./onchain/out/Kraiken.sol/
|
||||||
|
COPY onchain/out/Stake.sol/Stake.json ./onchain/out/Stake.sol/
|
||||||
|
|
||||||
|
# Copy Stake.sol for sync-tax-rates + the script itself
|
||||||
|
COPY onchain/src/Stake.sol ./onchain/src/
|
||||||
|
COPY scripts/sync-tax-rates.mjs ./scripts/
|
||||||
|
|
||||||
|
# Install kraiken-lib dependencies, run sync-tax-rates, and build
|
||||||
|
WORKDIR /app/kraiken-lib
|
||||||
|
RUN npm ci --ignore-scripts
|
||||||
|
COPY kraiken-lib/ ./
|
||||||
|
RUN node ../scripts/sync-tax-rates.mjs && ./node_modules/.bin/tsc
|
||||||
|
|
||||||
|
# Install service dependencies
|
||||||
|
ARG SERVICE_DIR
|
||||||
|
ARG NPM_INSTALL_CMD
|
||||||
|
WORKDIR /app/${SERVICE_DIR}
|
||||||
|
COPY ${SERVICE_DIR}/package.json ./
|
||||||
|
# Use glob pattern to optionally copy package-lock.json (txnBot has none)
|
||||||
|
COPY ${SERVICE_DIR}/package-lock.jso[n] ./
|
||||||
|
RUN if [ "$NPM_INSTALL_CMD" = "install" ]; then npm install; else npm ci; fi
|
||||||
|
|
||||||
|
# Copy service source
|
||||||
|
COPY ${SERVICE_DIR}/ ./
|
||||||
|
|
||||||
|
# Copy onchain deployment artifacts (glob handles missing files)
|
||||||
|
WORKDIR /app
|
||||||
|
COPY onchain/deployments*.jso[n] ./onchain/
|
||||||
|
|
||||||
|
# ── Runtime stage ──────────────────────────────────────────────────
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
RUN apk add --no-cache dumb-init wget bash
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy kraiken-lib (src for Vite alias, dist for runtime, package.json for resolution)
|
||||||
|
COPY --from=builder /app/kraiken-lib/src ./kraiken-lib/src
|
||||||
|
COPY --from=builder /app/kraiken-lib/dist ./kraiken-lib/dist
|
||||||
|
COPY --from=builder /app/kraiken-lib/package.json ./kraiken-lib/
|
||||||
|
|
||||||
|
# Copy service with all node_modules
|
||||||
|
ARG SERVICE_DIR
|
||||||
|
COPY --from=builder /app/${SERVICE_DIR} ./${SERVICE_DIR}
|
||||||
|
|
||||||
|
# Copy onchain artifacts
|
||||||
|
COPY --from=builder /app/onchain ./onchain
|
||||||
|
|
||||||
|
# Create placeholder deployments-local.json if not present
|
||||||
|
RUN test -f /app/onchain/deployments-local.json || \
|
||||||
|
(mkdir -p /app/onchain && echo '{"contracts":{}}' > /app/onchain/deployments-local.json)
|
||||||
|
|
||||||
|
# Conditionally create symlinks for Vite path resolution (webapp only)
|
||||||
|
ARG NEEDS_SYMLINKS=false
|
||||||
|
RUN if [ "$NEEDS_SYMLINKS" = "true" ]; then \
|
||||||
|
ln -sf /app/web-app /web-app && \
|
||||||
|
ln -sf /app/kraiken-lib /kraiken-lib && \
|
||||||
|
ln -sf /app/onchain /onchain; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy entrypoint script
|
||||||
|
# For services with entrypoints (ponder, webapp, txnbot): pass the actual entrypoint
|
||||||
|
# For landing (no entrypoint): defaults to entrypoint-common.sh which is just helpers
|
||||||
|
ARG ENTRYPOINT_SCRIPT=containers/entrypoint-common.sh
|
||||||
|
COPY ${ENTRYPOINT_SCRIPT} /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
# Set working directory to service
|
||||||
|
WORKDIR /app/${SERVICE_DIR}
|
||||||
|
|
||||||
|
ARG NODE_ENV=production
|
||||||
|
ENV NODE_ENV=${NODE_ENV}
|
||||||
|
ENV HOST=0.0.0.0
|
||||||
|
|
||||||
|
ARG SERVICE_PORT=8080
|
||||||
|
ENV PORT=${SERVICE_PORT}
|
||||||
|
EXPOSE ${SERVICE_PORT}
|
||||||
|
|
||||||
|
ARG HEALTHCHECK_PATH=/
|
||||||
|
ARG HEALTHCHECK_RETRIES=12
|
||||||
|
ARG HEALTHCHECK_START=20s
|
||||||
|
HEALTHCHECK --interval=5s --timeout=3s --retries=${HEALTHCHECK_RETRIES} --start-period=${HEALTHCHECK_START} \
|
||||||
|
CMD wget --spider -q http://127.0.0.1:${PORT}${HEALTHCHECK_PATH} || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["dumb-init", "--", "/entrypoint.sh"]
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
# Production image for Transaction Bot service
|
|
||||||
# Used in CI for E2E testing - contains all code baked in
|
|
||||||
|
|
||||||
FROM node:20-alpine AS builder
|
|
||||||
|
|
||||||
RUN apk add --no-cache git bash
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package files first for better caching
|
|
||||||
COPY package.json package-lock.json ./
|
|
||||||
COPY kraiken-lib/package.json kraiken-lib/package-lock.json ./kraiken-lib/
|
|
||||||
COPY services/txnBot/package.json ./services/txnBot/
|
|
||||||
|
|
||||||
# Copy ABI files needed by kraiken-lib
|
|
||||||
COPY onchain/out/Kraiken.sol/Kraiken.json ./onchain/out/Kraiken.sol/
|
|
||||||
COPY onchain/out/Stake.sol/Stake.json ./onchain/out/Stake.sol/
|
|
||||||
|
|
||||||
# Install kraiken-lib dependencies and build
|
|
||||||
WORKDIR /app/kraiken-lib
|
|
||||||
RUN npm ci --ignore-scripts
|
|
||||||
COPY kraiken-lib/ ./
|
|
||||||
RUN ./node_modules/.bin/tsc
|
|
||||||
|
|
||||||
# Install txnBot dependencies (no lock file for txnBot)
|
|
||||||
WORKDIR /app/services/txnBot
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# Copy txnBot source
|
|
||||||
COPY services/txnBot/ ./
|
|
||||||
|
|
||||||
# Copy shared config files
|
|
||||||
WORKDIR /app
|
|
||||||
COPY onchain/deployments*.json ./onchain/
|
|
||||||
|
|
||||||
# Production image
|
|
||||||
FROM node:20-alpine
|
|
||||||
|
|
||||||
RUN apk add --no-cache dumb-init wget bash
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy built artifacts
|
|
||||||
COPY --from=builder /app/kraiken-lib/dist ./kraiken-lib/dist
|
|
||||||
COPY --from=builder /app/kraiken-lib/package.json ./kraiken-lib/
|
|
||||||
COPY --from=builder /app/services/txnBot ./services/txnBot
|
|
||||||
COPY --from=builder /app/onchain ./onchain
|
|
||||||
|
|
||||||
# Copy entrypoint
|
|
||||||
COPY docker/ci-entrypoints/txnbot-ci-entrypoint.sh /entrypoint.sh
|
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
|
|
||||||
WORKDIR /app/services/txnBot
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
ENV HOST=0.0.0.0
|
|
||||||
ENV PORT=43069
|
|
||||||
|
|
||||||
EXPOSE 43069
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=3s --retries=4 --start-period=10s \
|
|
||||||
CMD wget --spider -q http://127.0.0.1:43069/status || exit 1
|
|
||||||
|
|
||||||
ENTRYPOINT ["dumb-init", "--", "/entrypoint.sh"]
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
# Production image for Web App (Vite + Vue)
|
|
||||||
# Used in CI for E2E testing - contains all code baked in
|
|
||||||
# Includes filesystem symlinks for Vite path resolution in Docker
|
|
||||||
|
|
||||||
FROM node:20-alpine AS builder
|
|
||||||
|
|
||||||
RUN apk add --no-cache git bash
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package files first for better caching
|
|
||||||
COPY package.json package-lock.json ./
|
|
||||||
COPY kraiken-lib/package.json kraiken-lib/package-lock.json ./kraiken-lib/
|
|
||||||
COPY web-app/package.json web-app/package-lock.json ./web-app/
|
|
||||||
|
|
||||||
# Copy ABI files needed by kraiken-lib
|
|
||||||
COPY onchain/out/Kraiken.sol/Kraiken.json ./onchain/out/Kraiken.sol/
|
|
||||||
COPY onchain/out/Stake.sol/Stake.json ./onchain/out/Stake.sol/
|
|
||||||
|
|
||||||
# Install kraiken-lib dependencies and build
|
|
||||||
WORKDIR /app/kraiken-lib
|
|
||||||
RUN npm ci --ignore-scripts
|
|
||||||
COPY kraiken-lib/ ./
|
|
||||||
RUN ./node_modules/.bin/tsc
|
|
||||||
|
|
||||||
# Install webapp dependencies
|
|
||||||
WORKDIR /app/web-app
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Copy webapp source
|
|
||||||
COPY web-app/ ./
|
|
||||||
|
|
||||||
# Production image
|
|
||||||
FROM node:20-alpine
|
|
||||||
|
|
||||||
RUN apk add --no-cache dumb-init wget bash
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy kraiken-lib (src for vite alias, dist for runtime)
|
|
||||||
COPY --from=builder /app/kraiken-lib/src ./kraiken-lib/src
|
|
||||||
COPY --from=builder /app/kraiken-lib/dist ./kraiken-lib/dist
|
|
||||||
COPY --from=builder /app/kraiken-lib/package.json ./kraiken-lib/
|
|
||||||
COPY --from=builder /app/web-app ./web-app
|
|
||||||
|
|
||||||
# Copy ABI files needed by kraiken-lib at compile time
|
|
||||||
COPY --from=builder /app/onchain/out ./onchain/out
|
|
||||||
|
|
||||||
# Create placeholder deployments-local.json for Vite compilation
|
|
||||||
# Actual contract addresses are provided via VITE_* environment variables at runtime
|
|
||||||
RUN mkdir -p /app/onchain && \
|
|
||||||
echo '{"contracts":{}}' > /app/onchain/deployments-local.json
|
|
||||||
|
|
||||||
# Create symlinks so Vite's path resolution works when base (/app/) is a prefix of root (/app/web-app)
|
|
||||||
# Vite's internal removeBase() can strip the /app/ prefix from filesystem paths, producing
|
|
||||||
# /web-app/src/... instead of /app/web-app/src/... — symlinks make both paths valid
|
|
||||||
RUN ln -s /app/web-app /web-app && \
|
|
||||||
ln -s /app/kraiken-lib /kraiken-lib && \
|
|
||||||
ln -s /app/onchain /onchain
|
|
||||||
|
|
||||||
# Copy entrypoint
|
|
||||||
COPY docker/ci-entrypoints/webapp-ci-entrypoint.sh /entrypoint.sh
|
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
|
|
||||||
WORKDIR /app/web-app
|
|
||||||
|
|
||||||
ENV NODE_ENV=development
|
|
||||||
ENV HOST=0.0.0.0
|
|
||||||
ENV PORT=5173
|
|
||||||
# Disable Vue DevTools in CI builds - vite.config.ts checks for CI=true
|
|
||||||
ENV CI=true
|
|
||||||
|
|
||||||
EXPOSE 5173
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=3s --retries=84 --start-period=15s \
|
|
||||||
CMD wget --spider -q http://127.0.0.1:5173/app/ || exit 1
|
|
||||||
|
|
||||||
ENTRYPOINT ["dumb-init", "--", "/entrypoint.sh"]
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Change to the ponder directory (Woodpecker runs from /woodpecker/src/)
|
|
||||||
cd /app/services/ponder
|
|
||||||
|
|
||||||
echo "[ponder-ci] Starting Ponder indexer..."
|
|
||||||
|
|
||||||
# Required environment variables (set by Woodpecker from bootstrap step)
|
|
||||||
: "${DATABASE_URL:?DATABASE_URL is required}"
|
|
||||||
: "${PONDER_RPC_URL_1:?PONDER_RPC_URL_1 is required}"
|
|
||||||
|
|
||||||
# Optional with defaults
|
|
||||||
export PONDER_RPC_TIMEOUT=${PONDER_RPC_TIMEOUT:-20000}
|
|
||||||
export HOST=${HOST:-0.0.0.0}
|
|
||||||
export PORT=${PORT:-42069}
|
|
||||||
|
|
||||||
# Create .env.local from environment
|
|
||||||
cat > .env.local <<EOF
|
|
||||||
DATABASE_URL=${DATABASE_URL}
|
|
||||||
PONDER_RPC_URL_1=${PONDER_RPC_URL_1}
|
|
||||||
DATABASE_SCHEMA=${DATABASE_SCHEMA:-ponder_ci}
|
|
||||||
START_BLOCK=${START_BLOCK:-0}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "[ponder-ci] Environment configured:"
|
|
||||||
echo " DATABASE_URL: ${DATABASE_URL}"
|
|
||||||
echo " PONDER_RPC_URL_1: ${PONDER_RPC_URL_1}"
|
|
||||||
echo " START_BLOCK: ${START_BLOCK:-0}"
|
|
||||||
|
|
||||||
# Run ponder in dev mode (indexes and serves GraphQL)
|
|
||||||
exec npm run dev
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "[txnbot-ci] Starting Transaction Bot..."
|
|
||||||
|
|
||||||
# Required environment variables (set by Woodpecker from bootstrap step)
|
|
||||||
: "${TXNBOT_PRIVATE_KEY:?TXNBOT_PRIVATE_KEY is required}"
|
|
||||||
: "${RPC_URL:?RPC_URL is required}"
|
|
||||||
: "${KRAIKEN_ADDRESS:?KRAIKEN_ADDRESS is required}"
|
|
||||||
: "${STAKE_ADDRESS:?STAKE_ADDRESS is required}"
|
|
||||||
: "${LIQUIDITY_MANAGER_ADDRESS:?LIQUIDITY_MANAGER_ADDRESS is required}"
|
|
||||||
|
|
||||||
# Create txnBot.env file from environment
|
|
||||||
cat > /tmp/txnBot.env <<EOF
|
|
||||||
TXNBOT_PRIVATE_KEY=${TXNBOT_PRIVATE_KEY}
|
|
||||||
RPC_URL=${RPC_URL}
|
|
||||||
KRAIKEN_ADDRESS=${KRAIKEN_ADDRESS}
|
|
||||||
STAKE_ADDRESS=${STAKE_ADDRESS}
|
|
||||||
LIQUIDITY_MANAGER_ADDRESS=${LIQUIDITY_MANAGER_ADDRESS}
|
|
||||||
POOL_ADDRESS=${POOL_ADDRESS:-}
|
|
||||||
WETH_ADDRESS=${WETH_ADDRESS:-0x4200000000000000000000000000000000000006}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
export TXN_BOT_ENV_FILE=/tmp/txnBot.env
|
|
||||||
|
|
||||||
echo "[txnbot-ci] Environment configured:"
|
|
||||||
echo " RPC_URL: ${RPC_URL}"
|
|
||||||
echo " KRAIKEN_ADDRESS: ${KRAIKEN_ADDRESS}"
|
|
||||||
|
|
||||||
# Build TypeScript
|
|
||||||
echo "[txnbot-ci] Building TypeScript..."
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Run the bot
|
|
||||||
exec npm run start
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Change to the webapp directory (Woodpecker runs from /woodpecker/src/)
|
|
||||||
cd /app/web-app
|
|
||||||
|
|
||||||
echo "[webapp-ci] Starting Web App..."
|
|
||||||
|
|
||||||
# Required environment variables (set by Woodpecker from bootstrap step)
|
|
||||||
: "${VITE_KRAIKEN_ADDRESS:?VITE_KRAIKEN_ADDRESS is required}"
|
|
||||||
: "${VITE_STAKE_ADDRESS:?VITE_STAKE_ADDRESS is required}"
|
|
||||||
|
|
||||||
# Disable Vue DevTools in CI to avoid path resolution issues
|
|
||||||
# vite.config.ts checks for CI=true to skip vite-plugin-vue-devtools
|
|
||||||
export CI=true
|
|
||||||
|
|
||||||
# Defaults for CI environment
|
|
||||||
export VITE_DEFAULT_CHAIN_ID=${VITE_DEFAULT_CHAIN_ID:-31337}
|
|
||||||
export VITE_LOCAL_RPC_URL=${VITE_LOCAL_RPC_URL:-/api/rpc}
|
|
||||||
export VITE_LOCAL_RPC_PROXY_TARGET=${VITE_LOCAL_RPC_PROXY_TARGET:-http://anvil:8545}
|
|
||||||
export VITE_LOCAL_GRAPHQL_PROXY_TARGET=${VITE_LOCAL_GRAPHQL_PROXY_TARGET:-http://ponder:42069}
|
|
||||||
export VITE_LOCAL_TXN_PROXY_TARGET=${VITE_LOCAL_TXN_PROXY_TARGET:-http://txn-bot:43069}
|
|
||||||
export VITE_SWAP_ROUTER=${VITE_SWAP_ROUTER:-0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4}
|
|
||||||
export VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK=${VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK:-/api/graphql}
|
|
||||||
export VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK=${VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK:-/api/txn}
|
|
||||||
|
|
||||||
echo "[webapp-ci] Environment configured:"
|
|
||||||
echo " VITE_KRAIKEN_ADDRESS: ${VITE_KRAIKEN_ADDRESS}"
|
|
||||||
echo " VITE_STAKE_ADDRESS: ${VITE_STAKE_ADDRESS}"
|
|
||||||
echo " VITE_DEFAULT_CHAIN_ID: ${VITE_DEFAULT_CHAIN_ID}"
|
|
||||||
|
|
||||||
# Run Vite dev server
|
|
||||||
exec npm run dev -- --host 0.0.0.0 --port 5173 --base /app/
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "[integration] Starting Docker daemon..."
|
|
||||||
|
|
||||||
# Start Docker daemon in the background
|
|
||||||
dockerd-entrypoint.sh dockerd &
|
|
||||||
DOCKERD_PID=$!
|
|
||||||
|
|
||||||
# Wait for Docker daemon to be ready
|
|
||||||
echo "[integration] Waiting for Docker daemon..."
|
|
||||||
timeout 30 sh -c 'until docker info >/dev/null 2>&1; do sleep 1; done'
|
|
||||||
|
|
||||||
echo "[integration] Docker daemon ready"
|
|
||||||
echo "[integration] Starting Harb stack..."
|
|
||||||
|
|
||||||
cd /workspace
|
|
||||||
|
|
||||||
# Build kraiken-lib if not already built
|
|
||||||
if [ ! -d "kraiken-lib/dist" ] || [ -z "$(ls -A kraiken-lib/dist 2>/dev/null)" ]; then
|
|
||||||
echo "[integration] Building kraiken-lib..."
|
|
||||||
./scripts/build-kraiken-lib.sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start the stack using dev.sh
|
|
||||||
echo "[integration] Launching stack via dev.sh..."
|
|
||||||
./scripts/dev.sh start
|
|
||||||
|
|
||||||
echo "[integration] Stack started successfully"
|
|
||||||
echo "[integration] Health endpoint: http://localhost:8081/api/graphql"
|
|
||||||
echo "[integration] Keeping container alive..."
|
|
||||||
|
|
||||||
# Keep the container running and forward signals to dockerd
|
|
||||||
trap "echo '[integration] Shutting down...'; ./scripts/dev.sh stop; kill $DOCKERD_PID; exit 0" SIGTERM SIGINT
|
|
||||||
|
|
||||||
# Wait for dockerd or run custom command if provided
|
|
||||||
if [ $# -gt 0 ]; then
|
|
||||||
echo "[integration] Executing: $*"
|
|
||||||
exec "$@"
|
|
||||||
else
|
|
||||||
wait $DOCKERD_PID
|
|
||||||
fi
|
|
||||||
|
|
@ -4,11 +4,13 @@ The Docker stack powers `scripts/dev.sh` using containerized services. Every boo
|
||||||
|
|
||||||
## Service Topology
|
## Service Topology
|
||||||
- `anvil` – Base Sepolia fork with optional mnemonic from `onchain/.secret.local`
|
- `anvil` – Base Sepolia fork with optional mnemonic from `onchain/.secret.local`
|
||||||
- `bootstrap` – one-shot job running `DeployLocal.sol`, seeding liquidity, priming blocks, and writing shared env files
|
- `bootstrap` – one-shot job running `DeployLocal.sol`, seeding liquidity, priming blocks, and writing shared env files (uses `scripts/bootstrap-common.sh`)
|
||||||
- `ponder` – `npm run dev` for the indexer (port 42069 inside the pod)
|
- `postgres` – PostgreSQL 16 database for Ponder indexer state
|
||||||
- `frontend` – Vite dev server for `web-app` (port 5173 inside the pod)
|
- `ponder` – `npm run dev` for the indexer (port 42069)
|
||||||
- `txn-bot` – automation loop plus Express status API (port 43069 inside the pod)
|
- `webapp` – Vite dev server for `web-app` (port 5173)
|
||||||
- `caddy` – front door at `http://<host>:80`, routing `/api/graphql`, `/health`, `/api/rpc`, and `/api/txn` to the internal services
|
- `landing` – Vite dev server for landing page (port 5174)
|
||||||
|
- `txn-bot` – automation loop plus Express status API (port 43069)
|
||||||
|
- `caddy` – reverse proxy at `http://localhost:8081`, routing `/app/` → webapp, `/api/graphql` → ponder, `/api/rpc` → anvil, `/` → landing
|
||||||
|
|
||||||
All containers mount the repository so code edits hot-reload exactly as the local script. Named volumes keep `node_modules` caches between restarts.
|
All containers mount the repository so code edits hot-reload exactly as the local script. Named volumes keep `node_modules` caches between restarts.
|
||||||
|
|
||||||
|
|
|
||||||
157
scripts/bootstrap-common.sh
Executable file
157
scripts/bootstrap-common.sh
Executable file
|
|
@ -0,0 +1,157 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Shared bootstrap functions for local dev and CI.
|
||||||
|
# Source this file after setting these variables:
|
||||||
|
# ANVIL_RPC - Anvil JSON-RPC URL (required)
|
||||||
|
# DEPLOYER_PK - Deployer private key (defaults to Anvil account 0)
|
||||||
|
# DEPLOYER_ADDR - Deployer address (defaults to Anvil account 0)
|
||||||
|
# TXNBOT_ADDRESS - TxnBot wallet address (optional)
|
||||||
|
# TXNBOT_PRIVATE_KEY- TxnBot private key (optional)
|
||||||
|
# TXNBOT_FUND_VALUE - Amount to fund txnBot (default: 1ether)
|
||||||
|
# CONTRACT_ENV - Path to write contracts.env (required)
|
||||||
|
# LOG_FILE - Log file for cast/forge output (default: /dev/null)
|
||||||
|
# ONCHAIN_DIR - Path to onchain/ directory (required)
|
||||||
|
# KRAIKEN_LIB_DIR - Path to kraiken-lib/ directory (optional, for CI build)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── Constants ──────────────────────────────────────────────────────────
|
||||||
|
FEE_DEST=0xf6a3eef9088A255c32b6aD2025f83E57291D9011
|
||||||
|
WETH=0x4200000000000000000000000000000000000006
|
||||||
|
SWAP_ROUTER=0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4
|
||||||
|
MAX_UINT=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
|
||||||
|
DEFAULT_DEPLOYER_PK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
||||||
|
DEFAULT_DEPLOYER_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
|
||||||
|
|
||||||
|
DEFAULT_TXNBOT_PK=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
|
||||||
|
DEFAULT_TXNBOT_ADDR=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
|
||||||
|
|
||||||
|
# ── Defaults ───────────────────────────────────────────────────────────
|
||||||
|
DEPLOYER_PK=${DEPLOYER_PK:-$DEFAULT_DEPLOYER_PK}
|
||||||
|
DEPLOYER_ADDR=${DEPLOYER_ADDR:-$DEFAULT_DEPLOYER_ADDR}
|
||||||
|
TXNBOT_FUND_VALUE=${TXNBOT_FUND_VALUE:-1ether}
|
||||||
|
LOG_FILE=${LOG_FILE:-/dev/null}
|
||||||
|
|
||||||
|
# ── Helpers ────────────────────────────────────────────────────────────
|
||||||
|
bootstrap_log() {
|
||||||
|
echo "[bootstrap] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Functions ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
wait_for_rpc() {
|
||||||
|
for _ in {1..120}; do
|
||||||
|
if cast chain-id --rpc-url "$ANVIL_RPC" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
bootstrap_log "Timed out waiting for Anvil at $ANVIL_RPC"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run_forge_script() {
|
||||||
|
bootstrap_log "Deploying contracts to fork"
|
||||||
|
pushd "$ONCHAIN_DIR" >/dev/null
|
||||||
|
forge script script/DeployLocal.sol --fork-url "$ANVIL_RPC" --broadcast >>"$LOG_FILE" 2>&1
|
||||||
|
popd >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_addresses() {
|
||||||
|
local run_file
|
||||||
|
run_file="$(ls -t "$ONCHAIN_DIR/broadcast/DeployLocal.sol"/*/run-latest.json 2>/dev/null | head -n1)"
|
||||||
|
if [[ -z "$run_file" ]]; then
|
||||||
|
bootstrap_log "Deployment artifact not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
bootstrap_log "Using artifact $run_file"
|
||||||
|
LIQUIDITY_MANAGER="$(jq -r '.transactions[] | select(.contractName=="LiquidityManager") | .contractAddress' "$run_file" | head -n1)"
|
||||||
|
KRAIKEN="$(jq -r '.transactions[] | select(.contractName=="Kraiken") | .contractAddress' "$run_file" | head -n1)"
|
||||||
|
STAKE="$(jq -r '.transactions[] | select(.contractName=="Stake") | .contractAddress' "$run_file" | head -n1)"
|
||||||
|
DEPLOY_BLOCK="$(jq -r '.receipts[0].blockNumber' "$run_file" | xargs printf "%d")"
|
||||||
|
if [[ -z "$LIQUIDITY_MANAGER" || "$LIQUIDITY_MANAGER" == "null" ]]; then
|
||||||
|
bootstrap_log "LiquidityManager address missing"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
write_contracts_env() {
|
||||||
|
cat >"$CONTRACT_ENV" <<EOCONTRACTS
|
||||||
|
LIQUIDITY_MANAGER=$LIQUIDITY_MANAGER
|
||||||
|
KRAIKEN=$KRAIKEN
|
||||||
|
STAKE=$STAKE
|
||||||
|
EOCONTRACTS
|
||||||
|
}
|
||||||
|
|
||||||
|
fund_liquidity_manager() {
|
||||||
|
bootstrap_log "Funding LiquidityManager"
|
||||||
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||||
|
"$LIQUIDITY_MANAGER" --value 0.1ether >>"$LOG_FILE" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
grant_recenter_access() {
|
||||||
|
bootstrap_log "Granting recenter access to deployer"
|
||||||
|
cast rpc --rpc-url "$ANVIL_RPC" anvil_impersonateAccount "$FEE_DEST" >>"$LOG_FILE" 2>&1
|
||||||
|
cast send --rpc-url "$ANVIL_RPC" --from "$FEE_DEST" --unlocked \
|
||||||
|
"$LIQUIDITY_MANAGER" "setRecenterAccess(address)" "$DEPLOYER_ADDR" >>"$LOG_FILE" 2>&1
|
||||||
|
cast rpc --rpc-url "$ANVIL_RPC" anvil_stopImpersonatingAccount "$FEE_DEST" >>"$LOG_FILE" 2>&1
|
||||||
|
|
||||||
|
if [[ -n "${TXNBOT_ADDRESS:-}" ]]; then
|
||||||
|
bootstrap_log "Granting recenter access to txnBot ($TXNBOT_ADDRESS)"
|
||||||
|
cast rpc --rpc-url "$ANVIL_RPC" anvil_impersonateAccount "$FEE_DEST" >>"$LOG_FILE" 2>&1
|
||||||
|
cast send --rpc-url "$ANVIL_RPC" --from "$FEE_DEST" --unlocked \
|
||||||
|
"$LIQUIDITY_MANAGER" "setRecenterAccess(address)" "$TXNBOT_ADDRESS" >>"$LOG_FILE" 2>&1
|
||||||
|
cast rpc --rpc-url "$ANVIL_RPC" anvil_stopImpersonatingAccount "$FEE_DEST" >>"$LOG_FILE" 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
call_recenter() {
|
||||||
|
local recenter_pk="$DEPLOYER_PK"
|
||||||
|
local recenter_addr="$DEPLOYER_ADDR"
|
||||||
|
if [[ -n "${TXNBOT_ADDRESS:-}" ]]; then
|
||||||
|
recenter_pk="$TXNBOT_PRIVATE_KEY"
|
||||||
|
recenter_addr="$TXNBOT_ADDRESS"
|
||||||
|
fi
|
||||||
|
bootstrap_log "Calling recenter() via $recenter_addr"
|
||||||
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$recenter_pk" \
|
||||||
|
"$LIQUIDITY_MANAGER" "recenter()" >>"$LOG_FILE" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
seed_application_state() {
|
||||||
|
bootstrap_log "Wrapping ETH to WETH"
|
||||||
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||||
|
"$WETH" "deposit()" --value 0.02ether >>"$LOG_FILE" 2>&1
|
||||||
|
bootstrap_log "Approving router"
|
||||||
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||||
|
"$WETH" "approve(address,uint256)" "$SWAP_ROUTER" "$MAX_UINT" >>"$LOG_FILE" 2>&1
|
||||||
|
bootstrap_log "Executing initial KRK swap"
|
||||||
|
cast send --legacy --gas-limit 300000 --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||||
|
"$SWAP_ROUTER" "exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))" \
|
||||||
|
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDR,10000000000000000,0,0)" >>"$LOG_FILE" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
fund_txn_bot_wallet() {
|
||||||
|
if [[ -z "${TXNBOT_ADDRESS:-}" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
bootstrap_log "Funding txnBot wallet $TXNBOT_ADDRESS with $TXNBOT_FUND_VALUE"
|
||||||
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||||
|
"$TXNBOT_ADDRESS" --value "$TXNBOT_FUND_VALUE" >>"$LOG_FILE" 2>&1 || true
|
||||||
|
local wei hex
|
||||||
|
wei="$(cast --to-unit "$TXNBOT_FUND_VALUE" wei)"
|
||||||
|
hex="$(cast --to-hex "$wei")"
|
||||||
|
cast rpc --rpc-url "$ANVIL_RPC" anvil_setBalance "$TXNBOT_ADDRESS" "$hex" >>"$LOG_FILE" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
write_deployments_json() {
|
||||||
|
local target="${1:-$ONCHAIN_DIR/deployments-local.json}"
|
||||||
|
cat >"$target" <<EODEPLOYMENTS
|
||||||
|
{
|
||||||
|
"contracts": {
|
||||||
|
"Kraiken": "$KRAIKEN",
|
||||||
|
"Stake": "$STAKE",
|
||||||
|
"LiquidityManager": "$LIQUIDITY_MANAGER"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EODEPLOYMENTS
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Build and push CI images for E2E testing
|
# Build CI images locally using the unified Dockerfile.service-ci
|
||||||
|
# For CI pipeline builds, see .woodpecker/build-ci-images.yml
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
@ -15,7 +16,12 @@ echo "Tag: $TAG"
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Building ponder-ci ==="
|
echo "=== Building ponder-ci ==="
|
||||||
docker build \
|
docker build \
|
||||||
-f docker/Dockerfile.ponder-ci \
|
-f docker/Dockerfile.service-ci \
|
||||||
|
--build-arg SERVICE_DIR=services/ponder \
|
||||||
|
--build-arg SERVICE_PORT=42069 \
|
||||||
|
--build-arg ENTRYPOINT_SCRIPT=containers/ponder-entrypoint.sh \
|
||||||
|
--build-arg HEALTHCHECK_RETRIES=12 \
|
||||||
|
--build-arg HEALTHCHECK_START=20s \
|
||||||
-t "$REGISTRY/harb/ponder-ci:$TAG" \
|
-t "$REGISTRY/harb/ponder-ci:$TAG" \
|
||||||
.
|
.
|
||||||
|
|
||||||
|
|
@ -23,7 +29,15 @@ docker build \
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Building webapp-ci ==="
|
echo "=== Building webapp-ci ==="
|
||||||
docker build \
|
docker build \
|
||||||
-f docker/Dockerfile.webapp-ci \
|
-f docker/Dockerfile.service-ci \
|
||||||
|
--build-arg SERVICE_DIR=web-app \
|
||||||
|
--build-arg SERVICE_PORT=5173 \
|
||||||
|
--build-arg HEALTHCHECK_PATH=/app/ \
|
||||||
|
--build-arg HEALTHCHECK_RETRIES=84 \
|
||||||
|
--build-arg HEALTHCHECK_START=15s \
|
||||||
|
--build-arg ENTRYPOINT_SCRIPT=containers/webapp-entrypoint.sh \
|
||||||
|
--build-arg NODE_ENV=development \
|
||||||
|
--build-arg NEEDS_SYMLINKS=true \
|
||||||
-t "$REGISTRY/harb/webapp-ci:$TAG" \
|
-t "$REGISTRY/harb/webapp-ci:$TAG" \
|
||||||
.
|
.
|
||||||
|
|
||||||
|
|
@ -31,7 +45,13 @@ docker build \
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Building landing-ci ==="
|
echo "=== Building landing-ci ==="
|
||||||
docker build \
|
docker build \
|
||||||
-f docker/Dockerfile.landing-ci \
|
-f docker/Dockerfile.service-ci \
|
||||||
|
--build-arg SERVICE_DIR=landing \
|
||||||
|
--build-arg SERVICE_PORT=5174 \
|
||||||
|
--build-arg ENTRYPOINT_SCRIPT=containers/landing-ci-entrypoint.sh \
|
||||||
|
--build-arg NODE_ENV=development \
|
||||||
|
--build-arg HEALTHCHECK_RETRIES=6 \
|
||||||
|
--build-arg HEALTHCHECK_START=10s \
|
||||||
-t "$REGISTRY/harb/landing-ci:$TAG" \
|
-t "$REGISTRY/harb/landing-ci:$TAG" \
|
||||||
.
|
.
|
||||||
|
|
||||||
|
|
@ -39,7 +59,14 @@ docker build \
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Building txnbot-ci ==="
|
echo "=== Building txnbot-ci ==="
|
||||||
docker build \
|
docker build \
|
||||||
-f docker/Dockerfile.txnbot-ci \
|
-f docker/Dockerfile.service-ci \
|
||||||
|
--build-arg SERVICE_DIR=services/txnBot \
|
||||||
|
--build-arg SERVICE_PORT=43069 \
|
||||||
|
--build-arg HEALTHCHECK_PATH=/status \
|
||||||
|
--build-arg HEALTHCHECK_RETRIES=4 \
|
||||||
|
--build-arg HEALTHCHECK_START=10s \
|
||||||
|
--build-arg ENTRYPOINT_SCRIPT=containers/txnbot-entrypoint.sh \
|
||||||
|
--build-arg NPM_INSTALL_CMD=install \
|
||||||
-t "$REGISTRY/harb/txnbot-ci:$TAG" \
|
-t "$REGISTRY/harb/txnbot-ci:$TAG" \
|
||||||
.
|
.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
REGISTRY="${REGISTRY:-registry.niovi.voyage}"
|
|
||||||
IMAGE_NAME="${IMAGE_NAME:-harb/integration}"
|
|
||||||
TAG="${TAG:-latest}"
|
|
||||||
FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}"
|
|
||||||
|
|
||||||
echo "Building integration image: ${FULL_IMAGE}"
|
|
||||||
echo "This may take 5-10 minutes on first build..."
|
|
||||||
|
|
||||||
# Build kraiken-lib first (required by the image)
|
|
||||||
echo "=== Building kraiken-lib ==="
|
|
||||||
./scripts/build-kraiken-lib.sh
|
|
||||||
|
|
||||||
# Build the integration image
|
|
||||||
echo "=== Building Docker image ==="
|
|
||||||
docker build \
|
|
||||||
-f docker/Dockerfile.integration \
|
|
||||||
-t "${FULL_IMAGE}" \
|
|
||||||
--progress=plain \
|
|
||||||
.
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "✓ Image built successfully: ${FULL_IMAGE}"
|
|
||||||
echo ""
|
|
||||||
echo "To test locally:"
|
|
||||||
echo " docker run --rm --privileged -p 8081:8081 ${FULL_IMAGE}"
|
|
||||||
echo ""
|
|
||||||
echo "To push to registry:"
|
|
||||||
echo " docker push ${FULL_IMAGE}"
|
|
||||||
74
scripts/ci-bootstrap.sh
Executable file
74
scripts/ci-bootstrap.sh
Executable file
|
|
@ -0,0 +1,74 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# CI bootstrap script — runs under bash to support 'source' and bash-isms.
|
||||||
|
# Env vars ANVIL_RPC, CONTRACT_ENV, LOG_FILE, ONCHAIN_DIR, TXNBOT_FUND_VALUE,
|
||||||
|
# TXNBOT_ADDRESS, TXNBOT_PRIVATE_KEY must be set before calling this script.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "=== Foundry version ==="
|
||||||
|
forge --version
|
||||||
|
cast --version
|
||||||
|
|
||||||
|
# Source shared bootstrap functions
|
||||||
|
source scripts/bootstrap-common.sh
|
||||||
|
|
||||||
|
echo "=== Waiting for Anvil ==="
|
||||||
|
wait_for_rpc
|
||||||
|
|
||||||
|
echo "=== Deploying contracts ==="
|
||||||
|
run_forge_script
|
||||||
|
extract_addresses
|
||||||
|
|
||||||
|
echo "=== Contract Deployment Complete ==="
|
||||||
|
echo "KRAIKEN: $KRAIKEN"
|
||||||
|
echo "STAKE: $STAKE"
|
||||||
|
echo "LIQUIDITY_MANAGER: $LIQUIDITY_MANAGER"
|
||||||
|
echo "DEPLOY_BLOCK: $DEPLOY_BLOCK"
|
||||||
|
|
||||||
|
# Build kraiken-lib BEFORE writing contracts.env
|
||||||
|
# (services wait for contracts.env, so kraiken-lib must be ready first)
|
||||||
|
echo "=== Building kraiken-lib (shared dependency) ==="
|
||||||
|
cd kraiken-lib
|
||||||
|
npm ci --ignore-scripts
|
||||||
|
if [[ -f ../scripts/sync-tax-rates.mjs ]]; then
|
||||||
|
node ../scripts/sync-tax-rates.mjs
|
||||||
|
fi
|
||||||
|
./node_modules/.bin/tsc
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Get current block number as start block
|
||||||
|
START_BLOCK=$(cast block-number --rpc-url "$ANVIL_RPC")
|
||||||
|
|
||||||
|
# Write contracts.env with CI-specific extra vars
|
||||||
|
{
|
||||||
|
echo "KRAIKEN=$KRAIKEN"
|
||||||
|
echo "STAKE=$STAKE"
|
||||||
|
echo "LIQUIDITY_MANAGER=$LIQUIDITY_MANAGER"
|
||||||
|
echo "START_BLOCK=$START_BLOCK"
|
||||||
|
echo "PONDER_RPC_URL_1=http://anvil:8545"
|
||||||
|
echo "DATABASE_URL=postgres://ponder:ponder_local@postgres:5432/ponder_local"
|
||||||
|
echo "RPC_URL=http://anvil:8545"
|
||||||
|
} > "$CONTRACT_ENV"
|
||||||
|
|
||||||
|
# Write deployments-local.json for E2E tests
|
||||||
|
write_deployments_json "$ONCHAIN_DIR/deployments-local.json"
|
||||||
|
echo "=== deployments-local.json written ==="
|
||||||
|
cat "$ONCHAIN_DIR/deployments-local.json"
|
||||||
|
|
||||||
|
echo "=== Funding LiquidityManager ==="
|
||||||
|
fund_liquidity_manager
|
||||||
|
|
||||||
|
echo "=== Granting recenter access ==="
|
||||||
|
grant_recenter_access
|
||||||
|
|
||||||
|
echo "=== Calling recenter() to seed liquidity ==="
|
||||||
|
call_recenter
|
||||||
|
|
||||||
|
echo "=== Seeding application state (initial swap) ==="
|
||||||
|
seed_application_state
|
||||||
|
|
||||||
|
echo "=== Funding txnBot ==="
|
||||||
|
fund_txn_bot_wallet
|
||||||
|
|
||||||
|
echo "TXNBOT_PRIVATE_KEY=$TXNBOT_PRIVATE_KEY" >> "$CONTRACT_ENV"
|
||||||
|
|
||||||
|
echo "=== Bootstrap complete ==="
|
||||||
|
|
@ -34,7 +34,8 @@ fi
|
||||||
|
|
||||||
container_name() {
|
container_name() {
|
||||||
local service="$1"
|
local service="$1"
|
||||||
echo "${PROJECT_NAME}_${service}_1"
|
# docker compose v2 uses hyphens; v1 used underscores
|
||||||
|
echo "${PROJECT_NAME}-${service}-1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check Docker disk usage and warn if approaching limits
|
# Check Docker disk usage and warn if approaching limits
|
||||||
|
|
@ -201,6 +202,13 @@ start_stack() {
|
||||||
|
|
||||||
wait_for_healthy "$(container_name caddy)" "$CADDY_TIMEOUT" || exit 1
|
wait_for_healthy "$(container_name caddy)" "$CADDY_TIMEOUT" || exit 1
|
||||||
|
|
||||||
|
# Smoke test: verify end-to-end connectivity through Caddy
|
||||||
|
echo " Running smoke test..."
|
||||||
|
./scripts/wait-for-service.sh http://localhost:8081/app/ 30 "caddy-proxy" || {
|
||||||
|
echo " [!!] Smoke test failed — Caddy proxy not serving /app/"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
if [[ -z "${SKIP_WATCH:-}" ]]; then
|
if [[ -z "${SKIP_WATCH:-}" ]]; then
|
||||||
echo "Watching for kraiken-lib changes..."
|
echo "Watching for kraiken-lib changes..."
|
||||||
./scripts/watch-kraiken-lib.sh &
|
./scripts/watch-kraiken-lib.sh &
|
||||||
|
|
|
||||||
26
scripts/wait-for-service.sh
Executable file
26
scripts/wait-for-service.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Wait for an HTTP service to respond with 2xx.
|
||||||
|
# Usage: wait-for-service.sh <url> [timeout_seconds] [label]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
URL="${1:?Usage: wait-for-service.sh <url> [timeout_seconds] [label]}"
|
||||||
|
TIMEOUT="${2:-120}"
|
||||||
|
LABEL="${3:-$URL}"
|
||||||
|
INTERVAL=5
|
||||||
|
|
||||||
|
ATTEMPTS=$((TIMEOUT / INTERVAL))
|
||||||
|
if (( ATTEMPTS < 1 )); then ATTEMPTS=1; fi
|
||||||
|
|
||||||
|
for i in $(seq 1 "$ATTEMPTS"); do
|
||||||
|
if curl -sf --max-time 3 "$URL" > /dev/null 2>&1; then
|
||||||
|
echo "[wait] $LABEL healthy after $((i * INTERVAL))s"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "[wait] ($i/$ATTEMPTS) $LABEL not ready..."
|
||||||
|
sleep "$INTERVAL"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[wait] ERROR: $LABEL not healthy after ${TIMEOUT}s"
|
||||||
|
echo "--- Diagnostic: $URL ---"
|
||||||
|
curl -v --max-time 5 "$URL" 2>&1 | head -20 || true
|
||||||
|
exit 1
|
||||||
|
|
@ -227,7 +227,7 @@ Use `./scripts/watch-kraiken-lib.sh` to rebuild `kraiken-lib` on file changes an
|
||||||
**Location**: `tests/e2e/` (repo root)
|
**Location**: `tests/e2e/` (repo root)
|
||||||
**Framework**: Playwright
|
**Framework**: Playwright
|
||||||
**Coverage**: Complete user journeys (mint ETH → swap KRK → stake)
|
**Coverage**: Complete user journeys (mint ETH → swap KRK → stake)
|
||||||
**References**: See `INTEGRATION_TEST_STATUS.md` and `SWAP_VERIFICATION.md`
|
**CI**: Woodpecker e2e pipeline runs these against pre-built service images
|
||||||
|
|
||||||
**Test Strategy**:
|
**Test Strategy**:
|
||||||
- Use mocked wallet provider with Anvil accounts
|
- Use mocked wallet provider with Anvil accounts
|
||||||
|
|
|
||||||
|
|
@ -46,4 +46,4 @@ Tests should rely on these roles and labels instead of private helpers.
|
||||||
|
|
||||||
### E2E Tests
|
### E2E Tests
|
||||||
|
|
||||||
See `INTEGRATION_TEST_STATUS.md` in the repository root for complete testing documentation.
|
Run `npm run test:e2e` from the repo root. Tests use Playwright against the full Docker stack. In CI, the Woodpecker e2e pipeline handles this automatically.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue