feature/ci (#84)

Co-authored-by: openhands <openhands@all-hands.dev>
Reviewed-on: https://codeberg.org/johba/harb/pulls/84
This commit is contained in:
johba 2026-02-02 19:24:57 +01:00
parent beefe22f90
commit 4277f19b68
41 changed files with 3149 additions and 298 deletions

View file

@ -0,0 +1,50 @@
# 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"]

View file

@ -0,0 +1,57 @@
# 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"]

40
docker/Dockerfile.node-ci Normal file
View file

@ -0,0 +1,40 @@
# syntax=docker/dockerfile:1.6
FROM node:20-bookworm
LABEL org.opencontainers.image.source="https://codeberg.org/johba/harb-ci"
LABEL org.opencontainers.image.description="Node.js toolchain for Harb Stack CI jobs"
ENV DEBIAN_FRONTEND=noninteractive \
PNPM_HOME=/root/.local/share/pnpm \
PATH=/root/.local/share/pnpm:/root/.local/bin:/root/.foundry/bin:$PATH
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -y --no-install-recommends \
git \
ca-certificates \
build-essential \
pkg-config \
libssl-dev \
python3 \
python3-pip \
bc \
jq \
curl && \
rm -rf /var/lib/apt/lists/*
# Enable corepack-managed package managers and pin the versions we expect in CI.
RUN corepack enable && \
corepack prepare pnpm@8.15.4 --activate && \
corepack prepare yarn@1.22.19 --activate
# Install Foundry once so downstream jobs skip the bootstrap step.
RUN curl -L https://foundry.paradigm.xyz | bash && \
~/.foundry/bin/foundryup --version && \
~/.foundry/bin/foundryup
WORKDIR /workspace
CMD ["bash"]

View file

@ -0,0 +1,28 @@
# syntax=docker/dockerfile:1.6
FROM mcr.microsoft.com/playwright:v1.56.0-jammy
LABEL org.opencontainers.image.source="https://codeberg.org/johba/harb-ci"
LABEL org.opencontainers.image.description="Playwright + Docker image for Harb Stack end-to-end CI"
ENV DEBIAN_FRONTEND=noninteractive \
PNPM_HOME=/root/.local/share/pnpm \
PATH=/root/.local/share/pnpm:/root/.local/bin:$PATH
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -y --no-install-recommends \
git \
ca-certificates \
jq \
curl && \
rm -rf /var/lib/apt/lists/*
RUN corepack enable && \
corepack prepare pnpm@8.15.4 --activate && \
corepack prepare yarn@1.22.19 --activate
WORKDIR /workspace
CMD ["bash"]

View file

@ -0,0 +1,67 @@
# 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"]

View file

@ -0,0 +1,64 @@
# 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"]

View file

@ -0,0 +1,78 @@
# 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"]

View file

@ -0,0 +1,32 @@
#!/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

View file

@ -0,0 +1,35 @@
#!/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

View file

@ -0,0 +1,33 @@
#!/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/

View file

@ -0,0 +1,42 @@
#!/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