From 67f5244069787d90a8a58b91233595d44c2782ef Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sat, 20 Jun 2026 05:06:02 +0000 Subject: [PATCH 1/3] chore: trim repo to dvm-only, remove leftover monorepo build contexts Deletes 11 non-dvm Dockerfiles, all non-dvm src/ entrypoints and helpers, and all non-dvm build-context dirs (configs/, dev-fixtures/, akash-ator-probe/, townhouse-ator-sidecar/, supervisord.conf, docker-compose-oyster.yml). Edits esbuild.config.mjs to a single entryPoint (entrypoint-dvm.ts) and trims unneeded externals. Updates package.json to remove main/start scripts pointing at the deleted entrypoint-sdk and prune deps only used by deleted entrypoints. Fixes tsconfig.json to stand alone (removes the extends to the monorepo root tsconfig that no longer exists here); vitest was failing on this pre-existing breakage. Closes #2 Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 4 +- Dockerfile.agent-runtime-patched | 7 - Dockerfile.akash-anvil | 92 - Dockerfile.akash-solana | 51 - Dockerfile.akash-solana-explorer | 59 - Dockerfile.mill | 224 - Dockerfile.nix | 146 - Dockerfile.oyster | 148 - Dockerfile.sdk-e2e | 224 - Dockerfile.toon-client | 265 - Dockerfile.town | 176 - Dockerfile.townhouse-api | 192 - README.md | 2 +- akash-ator-probe/Dockerfile | 91 - akash-ator-probe/checksums.txt | 8 - akash-ator-probe/entrypoint.sh | 125 - configs/townhouse-dev-connector.yaml | 30 - configs/townhouse-hs-connector.yaml | 174 - dev-fixtures/README.md | 26 - dev-fixtures/mill-01.config.json | 39 - dev-fixtures/mill-02.config.json | 39 - dev-fixtures/townhouse-hs-mill.config.json | 51 - docker-compose-oyster.yml | 162 - esbuild.config.mjs | 29 +- package-lock.json | 14358 +++++++++++++++++++ package.json | 22 +- src/attestation-server.test.ts | 148 - src/attestation-server.ts | 327 - src/entrypoint-mill.test.ts | 457 - src/entrypoint-mill.ts | 594 - src/entrypoint-sdk.ts | 1211 -- src/entrypoint-toon-client-helpers.ts | 349 - src/entrypoint-toon-client.test.ts | 217 - src/entrypoint-toon-client.ts | 1208 -- src/entrypoint-town.ts | 113 - src/entrypoint-townhouse-api.ts | 160 - src/shared.test.ts | 733 - src/shared.ts | 411 - supervisord.conf | 39 - townhouse-ator-sidecar/Dockerfile | 127 - townhouse-ator-sidecar/checksums.txt | 8 - townhouse-ator-sidecar/entrypoint.sh | 119 - tsconfig.json | 8 +- 43 files changed, 14379 insertions(+), 8594 deletions(-) delete mode 100644 Dockerfile.agent-runtime-patched delete mode 100644 Dockerfile.akash-anvil delete mode 100644 Dockerfile.akash-solana delete mode 100644 Dockerfile.akash-solana-explorer delete mode 100644 Dockerfile.mill delete mode 100644 Dockerfile.nix delete mode 100644 Dockerfile.oyster delete mode 100644 Dockerfile.sdk-e2e delete mode 100644 Dockerfile.toon-client delete mode 100644 Dockerfile.town delete mode 100644 Dockerfile.townhouse-api delete mode 100644 akash-ator-probe/Dockerfile delete mode 100644 akash-ator-probe/checksums.txt delete mode 100755 akash-ator-probe/entrypoint.sh delete mode 100644 configs/townhouse-dev-connector.yaml delete mode 100644 configs/townhouse-hs-connector.yaml delete mode 100644 dev-fixtures/README.md delete mode 100644 dev-fixtures/mill-01.config.json delete mode 100644 dev-fixtures/mill-02.config.json delete mode 100644 dev-fixtures/townhouse-hs-mill.config.json delete mode 100644 docker-compose-oyster.yml create mode 100644 package-lock.json delete mode 100644 src/attestation-server.test.ts delete mode 100644 src/attestation-server.ts delete mode 100644 src/entrypoint-mill.test.ts delete mode 100644 src/entrypoint-mill.ts delete mode 100644 src/entrypoint-sdk.ts delete mode 100644 src/entrypoint-toon-client-helpers.ts delete mode 100644 src/entrypoint-toon-client.test.ts delete mode 100644 src/entrypoint-toon-client.ts delete mode 100644 src/entrypoint-town.ts delete mode 100644 src/entrypoint-townhouse-api.ts delete mode 100644 src/shared.test.ts delete mode 100644 src/shared.ts delete mode 100644 supervisord.conf delete mode 100644 townhouse-ator-sidecar/Dockerfile delete mode 100644 townhouse-ator-sidecar/checksums.txt delete mode 100755 townhouse-ator-sidecar/entrypoint.sh diff --git a/CLAUDE.md b/CLAUDE.md index 25b3791..558403b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ The TOON Protocol **Arweave DVM node** — NIP-90 **kind:5094** blob storage. Bu Part of the **TOON Protocol** — pay-to-write Nostr over Interledger (ILP), split into per-team repos. -> **Follow-ups:** this repo was carved from the monorepo `docker/` aggregator and still contains the other images' build contexts — trim to dvm-only (keep `Dockerfile.dvm` + `src/entrypoint-dvm.ts` + shared helpers). Add the image-publish workflow (carve the dvm job from `publish-townhouse-images.yml`). +> **Follow-ups:** Add the image-publish workflow (carve the dvm job from `publish-townhouse-images.yml`). Fix `Dockerfile.dvm` to build standalone (it still references monorepo paths that don't exist here). ## Build This builds a Docker image, not an npm package: @@ -23,5 +23,5 @@ Cross-cutting agent skills, docs, and the canonical project context live in **[t Canonical rules/decisions: `toon-meta` → `_bmad-output/project-context.md`. ## Cross-repo dependencies -- Consumes `@toon-protocol/{core,sdk,bls}` from **npm** (pinned semver) — the Arweave handler lives in `sdk`. +- Consumes `@toon-protocol/{core,sdk}` from **npm** (pinned semver) — the Arweave handler lives in `sdk`. - The ILP payment engine is the separate **[toon-protocol/connector](https://github.com/toon-protocol/connector)** repo. The DVM receives ILP packets from the connector via HTTP and trusts they were already validated; **claim validation lives ONLY in the connector.** diff --git a/Dockerfile.agent-runtime-patched b/Dockerfile.agent-runtime-patched deleted file mode 100644 index 338b7de..0000000 --- a/Dockerfile.agent-runtime-patched +++ /dev/null @@ -1,7 +0,0 @@ -# Patch agent-runtime image to add missing dependencies -FROM connector:latest - -WORKDIR /app - -# Install missing dependencies (use Express 4.x for compatibility) -RUN npm install express@4 diff --git a/Dockerfile.akash-anvil b/Dockerfile.akash-anvil deleted file mode 100644 index 5b4b98e..0000000 --- a/Dockerfile.akash-anvil +++ /dev/null @@ -1,92 +0,0 @@ -# Town EVM Devnet — Akash-hosted Anvil image -# -# Bakes contracts/evm + a deploy/fund entrypoint into the foundry image so it -# runs standalone on Akash without host volume mounts (Akash doesn't support -# bind mounts). -# -# Build: -# docker build -f docker/Dockerfile.akash-anvil -t ghcr.io/toon-protocol/akash-anvil:demo . -# Push: -# docker push ghcr.io/toon-protocol/akash-anvil:demo -# -# Mirrors the dev compose anvil command (docker-compose-townhouse-dev.yml:67-123) -# except it uses --state /data/state.json for snapshot persistence and reads -# CHAIN_ID + ANVIL_* from env so the SDL can override. - -FROM ghcr.io/foundry-rs/foundry:latest - -USER root - -# Bake contracts (forge script DeployLocal.s.sol uses these). -COPY contracts/evm /contracts - -COPY <<'EOF' /entrypoint.sh -#!/bin/sh -set -eu - -CHAIN_ID="${CHAIN_ID:-31338}" -ANVIL_HOST="${ANVIL_HOST:-0.0.0.0}" -ANVIL_PORT="${ANVIL_PORT:-8545}" -ANVIL_ACCOUNTS="${ANVIL_ACCOUNTS:-10}" -ANVIL_BALANCE="${ANVIL_BALANCE:-10000}" -ANVIL_BLOCK_TIME="${ANVIL_BLOCK_TIME:-2}" -STATE_FILE="${STATE_FILE:-/data/state.json}" - -mkdir -p "$(dirname "$STATE_FILE")" - -echo "Starting Anvil (chain-id=$CHAIN_ID, port=$ANVIL_PORT, state=$STATE_FILE)..." -anvil \ - --host "$ANVIL_HOST" \ - --port "$ANVIL_PORT" \ - --chain-id "$CHAIN_ID" \ - --accounts "$ANVIL_ACCOUNTS" \ - --balance "$ANVIL_BALANCE" \ - --block-time "$ANVIL_BLOCK_TIME" \ - --state "$STATE_FILE" \ - --state-interval 30 & -ANVIL_PID=$! - -trap 'kill -TERM "$ANVIL_PID" 2>/dev/null || true' TERM INT - -echo "Waiting for Anvil to be ready..." -until cast client --rpc-url "http://localhost:$ANVIL_PORT" 2>/dev/null | grep -q 'anvil'; do - sleep 1 -done - -# Deploy contracts only on first boot (state file absent). -if [ ! -s "$STATE_FILE" ]; then - echo "First boot — deploying contracts..." - cd /contracts - forge script script/DeployLocal.s.sol:DeployLocalScript \ - --rpc-url "http://localhost:$ANVIL_PORT" \ - --broadcast \ - --skip-simulation \ - || echo "Deployment failed (non-fatal)" - - echo "Funding test accounts with Mock USDC..." - DEPLOYER_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 - TOKEN=0x5FbDB2315678afecb367f032d93F642f64180aa3 - AMOUNT=10000000000000000000000 - for ADDR in \ - 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \ - 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 \ - 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc \ - 0x976EA74026E726554dB657fA54763abd0C3a0aa9 \ - 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955 \ - 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f \ - 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720; do - cast send --rpc-url "http://localhost:$ANVIL_PORT" --private-key "$DEPLOYER_KEY" \ - "$TOKEN" "transfer(address,uint256)" "$ADDR" "$AMOUNT" 2>/dev/null \ - && echo "Funded $ADDR" || echo "Fund $ADDR failed (non-fatal)" - done -else - echo "State file exists — skipping deployment (warm boot)." -fi - -echo "Anvil ready." -wait "$ANVIL_PID" -EOF - -RUN chmod +x /entrypoint.sh - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/Dockerfile.akash-solana b/Dockerfile.akash-solana deleted file mode 100644 index 0a6cc50..0000000 --- a/Dockerfile.akash-solana +++ /dev/null @@ -1,51 +0,0 @@ -# Town SOL Devnet — Akash-hosted Solana test-validator image -# -# Bakes infra/solana/entrypoint.sh + contracts/solana/*.so into the upstream -# solana-test-validator image so it runs standalone on Akash (no host bind -# mounts available there). -# -# Build: -# docker build -f docker/Dockerfile.akash-solana -t ghcr.io/toon-protocol/akash-solana:demo . -# Push: -# docker push ghcr.io/toon-protocol/akash-solana:demo -# -# Mirrors the dev compose solana service (docker-compose-townhouse-dev.yml:128-153) -# except --ledger points at /data/test-ledger for persistence (not tmpfs). - -FROM ghcr.io/beeman/solana-test-validator:latest - -USER root - -# Node 18 (Debian 12 default) + npm + curl. Node is for the post-startup -# bootstrap (Mock USDC mint + faucet treasury); curl is used by the entrypoint -# readiness probe. Upstream beeman/solana-test-validator:latest dropped curl, -# so we re-add it here. --no-install-recommends + cache clean keep size in -# check. -RUN apt-get update \ - && apt-get install -y --no-install-recommends nodejs npm ca-certificates curl \ - && rm -rf /var/lib/apt/lists/* - -# Bake the BPF programs that get loaded at genesis. -COPY contracts/solana /programs - -# Bake the existing entrypoint (loads .so files via --bpf-program at genesis, -# probes /health for readiness, then runs the USDC bootstrap). -COPY infra/solana/entrypoint.sh /entrypoint.sh - -# Bootstrap module: SPL primitives + Mock USDC mint creator + dev keypairs. -# Runs once at startup against http://localhost:8899 (idempotent on existing -# mints). See infra/solana/bootstrap-usdc.mjs. -COPY infra/solana/package.json /bootstrap/package.json -COPY infra/solana/spl-primitives.mjs /bootstrap/spl-primitives.mjs -COPY infra/solana/spl-primitives.d.mts /bootstrap/spl-primitives.d.mts -COPY infra/solana/bootstrap-usdc.mjs /bootstrap/bootstrap-usdc.mjs -COPY infra/solana/keys /bootstrap/keys -RUN cd /bootstrap && npm install --omit=dev --no-audit --no-fund \ - && npm cache clean --force - -# Patch the ledger path to /data so persistent volume survives restarts. -RUN sed -i 's|--limit-ledger-size 50000000|--ledger /data/test-ledger --limit-ledger-size 50000000|' /entrypoint.sh \ - && chmod +x /entrypoint.sh \ - && mkdir -p /data - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/Dockerfile.akash-solana-explorer b/Dockerfile.akash-solana-explorer deleted file mode 100644 index ce19810..0000000 --- a/Dockerfile.akash-solana-explorer +++ /dev/null @@ -1,59 +0,0 @@ -# Town SOL Explorer — Akash-hosted Solana Explorer image -# -# Builds the official solana-labs/explorer (the source behind -# explorer.solana.com) as a production Next.js server, pre-configured to -# default to our custom RPC cluster. -# -# Build: -# docker build -f docker/Dockerfile.akash-solana-explorer \ -# -t ghcr.io/toon-protocol/akash-solana-explorer:demo . -# Push: -# docker push ghcr.io/toon-protocol/akash-solana-explorer:demo -# -# Pinned to a known-good commit. Bump SOLANA_EXPLORER_REF to upgrade. - -# solana-labs/explorer has no tags; pin to a commit SHA on master. -# Bump deliberately when rolling forward. Last known-good: 2026-05-04. -ARG SOLANA_EXPLORER_REF=63fe184b6a1543f86c436c9e1cce1dcc7696a818 - -FROM node:20-alpine AS builder - -ARG SOLANA_EXPLORER_REF - -RUN apk add --no-cache git python3 make g++ - -WORKDIR /app -RUN git clone https://github.com/solana-labs/explorer.git . \ - && git checkout "${SOLANA_EXPLORER_REF}" - -# Repo uses pnpm; package manager pinned in package.json. Install + build. -RUN corepack enable \ - && corepack prepare --activate \ - && pnpm install --frozen-lockfile \ - && pnpm build - - -FROM node:20-alpine AS runtime - -WORKDIR /app - -# Copy only what's needed to run `next start`. Next.js 14 standalone output -# would be smaller, but the upstream config doesn't enable it; copy the full -# build instead. -COPY --from=builder /app /app - -ENV NODE_ENV=production -ENV PORT=3000 -ENV HOSTNAME=0.0.0.0 - -# Sane defaults; SDL env can override at deploy time. -ENV NEXT_PUBLIC_DEFAULT_CLUSTER=custom -ENV NEXT_PUBLIC_CLUSTER_NAME="Town SOL Devnet" - -EXPOSE 3000 - -# Invoke Next.js directly via the local binary. We deliberately avoid -# `pnpm start` here because the runtime stage (a fresh node:20-alpine) -# doesn't have corepack/pnpm activated; only the builder stage does. -# `node_modules/.bin/next` is always present after a successful build. -CMD ["node_modules/.bin/next", "start", "-p", "3000", "-H", "0.0.0.0"] diff --git a/Dockerfile.mill b/Dockerfile.mill deleted file mode 100644 index 7cdd297..0000000 --- a/Dockerfile.mill +++ /dev/null @@ -1,224 +0,0 @@ -# TOON Mill Node Container (esbuild-bundled Multi-Stage Build) -# -# Purpose: -# Production-grade Mill swap-node container for Townhouse orchestration. -# Runs a multi-chain payment-channel swap issuer with embedded connector. -# -# Consumers: -# - `docker-compose-townhouse.yml` — mill service references `image: toon:mill` -# - `packages/townhouse` DockerOrchestrator — builds and runs this image -# -# Build from repo root: -# docker build -f docker/Dockerfile.mill -t toon:mill . -# -# Pattern follows docker/Dockerfile.town (multi-stage, esbuild, native modules, non-root). -# -# NOTE: image-level LABELs are declared in the runtime stage (after the -# second FROM) — LABELs declared before any FROM are silently dropped by -# Docker and never appear on the produced image (`docker inspect` confirms). - -# -- Stage 1: Builder ------------------------------------------------------- -FROM node:20-alpine AS builder - -# Install build dependencies for native modules -RUN apk add --no-cache python3 py3-setuptools make g++ - -# Install pnpm (pinned for deterministic builds) -RUN corepack enable && corepack prepare pnpm@8.15.0 --activate - -WORKDIR /app - -# Copy dependency manifests for installation. -# Copy the package.json of EVERY in-context workspace project (not just bls/ -# core/sdk/mill/docker). With `--frozen-lockfile`, pnpm prunes any workspace -# project whose manifest is absent from the install graph, which drops that -# project's deps from `node_modules/.pnpm` and changes how shared deps' peer -# variants get hoisted/symlinked. The host always has the FULL workspace, so a -# partial manifest set produces a DIFFERENT, fragile node_modules layout here — -# e.g. the sdk's `@toon-protocol/connector` / `viem` symlink resolves to a peer -# variant whose .d.ts isn't walkable, breaking the tsup DTS pass with -# `Cannot find module '…'` while the ESM bundle still builds (#143, same class -# as #138/#141/#149). Copying all manifests makes the Docker install graph -# identical to the host's so DTS type resolution is deterministic. -# (packages/examples members are copied too; .dockerignore controls source.) -COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc ./ -COPY patches/ ./patches/ -COPY packages/bls/package.json ./packages/bls/ -COPY packages/client/package.json ./packages/client/ -COPY packages/core/package.json ./packages/core/ -COPY packages/faucet/package.json ./packages/faucet/ -COPY packages/memvid-node/package.json ./packages/memvid-node/ -COPY packages/mill/package.json ./packages/mill/ -COPY packages/mina-zkapp/package.json ./packages/mina-zkapp/ -COPY packages/pet-circuit/package.json ./packages/pet-circuit/ -COPY packages/pet-dvm/package.json ./packages/pet-dvm/ -COPY packages/relay/package.json ./packages/relay/ -COPY packages/rig/package.json ./packages/rig/ -COPY packages/sdk/package.json ./packages/sdk/ -COPY packages/town/package.json ./packages/town/ -COPY packages/townhouse/package.json ./packages/townhouse/ -COPY packages/townhouse-web/package.json ./packages/townhouse-web/ -COPY examples/client-example/package.json ./examples/client-example/ -COPY examples/sdk-example/package.json ./examples/sdk-example/ -COPY examples/town-example/package.json ./examples/town-example/ -COPY docker/package.json ./docker/ - -# Install all dependencies (including devDependencies needed for build). -# `--ignore-scripts` skips native postinstall builds (the Rust-backed -# memvid-node/pet-circuit, etc.) that the now-complete workspace graph would -# otherwise trigger — the builder only transpiles JS. The runtime stage installs -# its own native deps (@libsql/client, libsql) fresh. better-sqlite3's native -# `.node` IS cherry-picked from the pnpm store below (it backs the connector's -# local claim store), so we explicitly `pnpm rebuild` just that one package. -RUN pnpm install --frozen-lockfile --ignore-scripts && \ - pnpm rebuild -r better-sqlite3 - -# Copy source code (only packages needed for Mill) -COPY tsconfig.json ./ -COPY packages/bls/ ./packages/bls/ -COPY packages/core/ ./packages/core/ -COPY packages/sdk/ ./packages/sdk/ -COPY packages/mill/ ./packages/mill/ -COPY packages/mina-zkapp/ ./packages/mina-zkapp/ -COPY docker/ ./docker/ - -# Build workspace packages needed by Mill, then bundle the entrypoint. -# Only builds: core -> sdk -> bls -> mill (Mill's dependency chain). -# Invokes esbuild directly with entrypoint-mill.ts as the single entry point. -RUN pnpm -r \ - --filter '@toon-protocol/core' \ - --filter '@toon-protocol/sdk' \ - --filter '@toon-protocol/bls' \ - --filter '@toon-protocol/mina-zkapp' \ - --filter '@toon-protocol/mill' \ - build && \ - cd docker && \ - pnpm exec esbuild src/entrypoint-mill.ts \ - --bundle \ - --platform=node \ - --target=node20 \ - --format=esm \ - --minify \ - --outfile=dist/entrypoint-mill.js \ - --banner:js="import { createRequire } from 'module'; const require = createRequire(import.meta.url);" \ - --external:better-sqlite3 \ - --external:@libsql/client \ - --external:libsql \ - --external:ethers \ - --external:express \ - --external:mina-signer \ - --external:o1js \ - --external:@solana/kit \ - --external:@solana-program/token \ - --external:@toon-protocol/mina-zkapp - -# Pack the built mina-zkapp workspace package into a tarball so the runtime -# stage can `npm install` it (it is not published to npm). Required for the -# embedded connector's Mina payment-channel zkApp pre-compile — without it a -# mill configured with a Mina chainProvider logs `zkapp_compile_error: -# @toon-protocol/mina-zkapp is required ... but is not installed` (#240). -RUN mkdir -p /tarballs && \ - ( cd packages/mina-zkapp && pnpm pack --pack-destination /tarballs ) && \ - mv /tarballs/toon-protocol-mina-zkapp-*.tgz /tarballs/mina-zkapp.tgz - -# -- Assemble minimal runtime directory -------------------------------------- -RUN mkdir -p /runtime/node_modules - -# Copy esbuild bundle -RUN cp docker/dist/entrypoint-mill.js /runtime/ - -# ESM package.json (bundle uses import.meta.url via banner) -RUN echo '{"type":"module"}' > /runtime/package.json - -# Cherry-pick better-sqlite3 (native module) + its runtime deps (bindings, file-uri-to-path) -RUN SQLITE_DIR=$(ls -d node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3) && \ - BINDINGS_DIR=$(ls -d node_modules/.pnpm/bindings@*/node_modules/bindings) && \ - FUTP_DIR=$(ls -d node_modules/.pnpm/file-uri-to-path@*/node_modules/file-uri-to-path) && \ - mkdir -p /runtime/node_modules/better-sqlite3/build/Release && \ - cp -r "$SQLITE_DIR/lib" /runtime/node_modules/better-sqlite3/ && \ - cp "$SQLITE_DIR/build/Release/better_sqlite3.node" \ - /runtime/node_modules/better-sqlite3/build/Release/ || { echo "ERROR: better-sqlite3.node not found"; exit 1; } && \ - cp "$SQLITE_DIR/package.json" /runtime/node_modules/better-sqlite3/ && \ - cp -r "$BINDINGS_DIR" /runtime/node_modules/ && \ - cp -r "$FUTP_DIR" /runtime/node_modules/ - -# Copy all @solana/* v3 packages + @solana-program/token (connector Solana settlement SDK) -RUN mkdir -p /runtime/node_modules/@solana /runtime/node_modules/@solana-program && \ - for store_dir in $(ls -d node_modules/.pnpm/@solana+*@3.0.*/node_modules/@solana/*/ 2>/dev/null); do \ - pkg=$(basename "$store_dir"); \ - [ ! -d "/runtime/node_modules/@solana/$pkg" ] && cp -rL "$store_dir" "/runtime/node_modules/@solana/$pkg" || true; \ - done && \ - SOLANA_TOKEN_STORE=$(ls -d node_modules/.pnpm/@solana-program+token@0.6*/node_modules/@solana-program/token 2>/dev/null | head -1) && \ - [ -n "$SOLANA_TOKEN_STORE" ] && cp -rL "$SOLANA_TOKEN_STORE" /runtime/node_modules/@solana-program/token || true && \ - WS_STORE=$(ls -d node_modules/.pnpm/ws@8.*/node_modules/ws 2>/dev/null | tail -1) && \ - [ -n "$WS_STORE" ] && cp -rL "$WS_STORE" /runtime/node_modules/ws || true - -# Connector requires express at runtime for its health server even when -# adminApi.enabled=false. Install via npm — same pattern as Dockerfile.sdk-e2e. -# -# @libsql/client + libsql (toon-protocol/connector#79): connector 3.8.0 moved its -# local settlement/claim SQLite store off better-sqlite3 to libsql, lazy-required -# at runtime. Without them Mill's embedded connector silently disables the -# per-packet claim subsystem (incl. the relation-aware InboundClaimValidator from -# connector#78). libsql ships a prebuilt linux-x64-musl NAPI binding that loads -# on node:20-alpine (verified). -# mina-zkapp (packed tarball) + o1js + mina-signer back the Mina payment-channel -# leg (#240). All three are `--external` in the esbuild bundle above, so they -# MUST be present in node_modules at runtime. o1js/mina-signer pinned to the -# workspace-resolved versions so the runtime circuit matches what mill was built -# against. npm resolves o1js's transitive deps cleanly (flat), avoiding the -# pnpm-store symlink-cherry-pick fragility. -RUN cd /tmp && npm init -y && npm install --omit=dev ethers@6 express@4 socks-proxy-agent@8 @libsql/client@0.14.0 libsql@0.4.7 /tarballs/mina-zkapp.tgz o1js@2.14.0 mina-signer@3.1.0 && \ - cp -r /tmp/node_modules/* /runtime/node_modules/ && \ - find /runtime/node_modules -name '*.d.ts' -delete && \ - find /runtime/node_modules -name '*.d.ts.map' -delete && \ - find /runtime/node_modules -name '*.js.map' -delete && \ - rm -rf /tmp/node_modules /tmp/package.json /tmp/package-lock.json - -# -- Stage 2: Runtime -------------------------------------------------------- -FROM node:20-alpine - -# Image metadata (must be declared in the runtime stage so they appear on the -# produced image; LABELs before the first FROM are silently dropped). -LABEL maintainer="toon-protocol" -LABEL version="1.0.0" -LABEL description="TOON Mill Node - Multi-chain swap peer with embedded connector" - -# Install runtime deps for native modules ONLY. -RUN apk add --no-cache libstdc++ - -WORKDIR /app - -# Create non-root user for security BEFORE copying files. -# Symlink /app/data -> /data so the embedded connector's hardcoded -# `./data/claims-.db` path (relative to WORKDIR=/app) lands in -# the named volume mounted at /data instead of the container's -# ephemeral writable layer. -RUN addgroup -g 1001 toon && \ - adduser -D -u 1001 -G toon toon && \ - mkdir -p /data && \ - chown toon:toon /data && \ - ln -s /data /app/data - -# Copy minimal runtime from builder stage -COPY --from=builder --chown=toon:toon /runtime ./ - -# Environment variables (with defaults) -ENV NODE_ENV=production -ENV BLS_PORT=3200 - -# Expose ports: -# 3000 - BTP (embedded ConnectorNode, peer connections from standalone connector) -# 3200 - BLS HTTP API (health endpoint) -EXPOSE 3000 3200 - -# Volume for persistent data (channel state SQLite database) -VOLUME /data - -# Health check targets BLS port (3200) -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD wget -q --spider http://localhost:${BLS_PORT}/health || exit 1 - -# Run as non-root user -USER toon -CMD ["node", "/app/entrypoint-mill.js"] \ No newline at end of file diff --git a/Dockerfile.nix b/Dockerfile.nix deleted file mode 100644 index a256069..0000000 --- a/Dockerfile.nix +++ /dev/null @@ -1,146 +0,0 @@ -# TOON Oyster CVM -- Nix Reproducible Docker Image Expression -# -# This is a Nix expression (NOT a traditional Dockerfile) that produces a -# deterministic Docker image via `dockerTools.buildLayeredImage`. Every byte -# of the output is derived from content-addressed inputs in the Nix store, -# ensuring that two independent builds from the same source tree produce -# identical Docker image hashes and thus identical PCR (Platform Configuration -# Register) values for TEE attestation verification. -# -# Reproducibility Constraints (FR-TEE-5): -# - All package inputs come from a pinned nixpkgs commit (via flake.lock) -# - No mutable package managers (no apt-get, no npm install, no pip install) -# - No unpinned base images (no FROM, no :latest tags) -# - No git clones without pinned commits -# - No remote script execution (no curl | bash) -# - Timestamps are fixed to epoch 0 for layer reproducibility -# -# This file is consumed by the flake.nix at the project root: -# nix build .#docker-image -# -# The resulting image contains the same runtime components as Dockerfile.oyster: -# - Node.js 20 runtime -# - supervisord for multi-process orchestration -# - Non-root toon user (uid 1001, gid 1001) -# - Ports: 3100 (BLS HTTP), 7100 (Nostr Relay WS), 1300 (Attestation HTTP) -# - CMD: supervisord -c /etc/supervisord.conf -# -# Usage (from flake.nix): -# nix build .#docker-image -# docker load < result -# -{ pkgs, productionDeps }: - -let - # Non-root user for security (matching Dockerfile.oyster uid/gid 1001) - toon-user = pkgs.runCommand "toon-user" { } '' - mkdir -p $out/etc - echo "root:x:0:0:root:/root:/bin/sh" > $out/etc/passwd - echo "toon:x:1001:1001:toon:/app:/bin/sh" >> $out/etc/passwd - echo "root:x:0:" > $out/etc/group - echo "toon:x:1001:" >> $out/etc/group - echo "root:x:!:1::::::" > $out/etc/shadow - echo "toon:x:!:1::::::" >> $out/etc/shadow - ''; - - # Supervisord configuration (copied from docker/supervisord.conf) - supervisord-conf = pkgs.runCommand "supervisord-conf" { } '' - mkdir -p $out/etc - cat > $out/etc/supervisord.conf << 'SUPERVISOR_EOF' - [supervisord] - nodaemon=true - user=root - - [program:toon] - priority=10 - command=node /app/dist/entrypoint-sdk.js - user=toon - autorestart=true - stopwaitsecs=15 - stdout_logfile=/dev/stdout - stdout_logfile_maxbytes=0 - stderr_logfile=/dev/stderr - stderr_logfile_maxbytes=0 - - [program:attestation] - priority=20 - command=node /app/dist/attestation-server.js - user=toon - autorestart=true - startsecs=5 - stopwaitsecs=10 - stdout_logfile=/dev/stdout - stdout_logfile_maxbytes=0 - stderr_logfile=/dev/stderr - stderr_logfile_maxbytes=0 - SUPERVISOR_EOF - ''; - - # Application directory structure - app-dir = pkgs.runCommand "toon-app" { } '' - mkdir -p $out/app - cp -r ${productionDeps}/* $out/app/ - mkdir -p $out/data - ''; - -in -pkgs.dockerTools.buildLayeredImage { - name = "toon"; - tag = "nix"; - - # Fixed creation timestamp for reproducibility -- epoch 0 ensures no - # timestamp-based non-determinism in the image metadata - created = "1970-01-01T00:00:00Z"; - - contents = [ - # Runtime: Node.js 20 (pinned via nixpkgs commit in flake.lock) - pkgs.nodejs_20 - - # Process orchestration: supervisord for Oyster CVM multi-process management - pkgs.python3Packages.supervisor - - # Minimal runtime utilities (required by supervisord and health checks) - pkgs.coreutils - pkgs.bash - - # User definition and supervisord config - toon-user - supervisord-conf - - # Application code and production dependencies - app-dir - ]; - - config = { - # CMD matches Dockerfile.oyster: run supervisord for multi-process management - Cmd = [ "${pkgs.python3Packages.supervisor}/bin/supervisord" "-c" "/etc/supervisord.conf" ]; - - # Exposed ports match Dockerfile.oyster: - # 3100 - BLS HTTP API (enriched /health, /handle-packet) - # 7100 - Nostr Relay WebSocket - # 1300 - Attestation HTTP server (/attestation/raw, /health) - ExposedPorts = { - "3100/tcp" = { }; - "7100/tcp" = { }; - "1300/tcp" = { }; - }; - - # Environment variables - Env = [ - "NODE_ENV=production" - "BLS_PORT=3100" - "WS_PORT=7100" - "PATH=/bin:${pkgs.nodejs_20}/bin:${pkgs.coreutils}/bin:${pkgs.bash}/bin:${pkgs.python3Packages.supervisor}/bin" - ]; - - WorkingDir = "/app"; - - # Non-root user for container execution - User = "toon"; - - # Data volume for persistent storage - Volumes = { - "/data" = { }; - }; - }; -} diff --git a/Dockerfile.oyster b/Dockerfile.oyster deleted file mode 100644 index a23f714..0000000 --- a/Dockerfile.oyster +++ /dev/null @@ -1,148 +0,0 @@ -# TOON Oyster CVM Container (esbuild-bundled Multi-Stage Build) -# -# Extends the standard TOON Docker image with: -# - supervisord for multi-process orchestration -# - Attestation server sidecar process -# - Attestation port (1300) exposed -# -# Build from repo root: -# docker build -f docker/Dockerfile.oyster -t toon:oyster . -# -# This image is consumed by the Oyster CVM tooling: -# oyster-cvm build --docker-compose docker/docker-compose-oyster.yml -# -# Inside the Oyster CVM, supervisord manages two processes: -# 1. toon (priority=10): Embedded ConnectorNode + BLS HTTP + Nostr Relay WS + Bootstrap -# 2. attestation (priority=20): TEE attestation HTTP server -# -# The connector is EMBEDDED — ConnectorNode runs in-process via entrypoint-sdk.js. -# The enclave is fully self-contained; no external connector container is needed. -# Marlin's vsock proxy handles all inbound/outbound networking transparently. -# -# esbuild bundles all JS into self-contained files. Only native modules -# (better-sqlite3) and dynamically-required packages (ethers, express) are -# copied as separate node_modules. Target image: ~75-85MB (down from 740MB). - -# -- Stage 1: Builder -------------------------------------------------------- -FROM node:20-alpine AS builder - -# Install build dependencies for native modules -RUN apk add --no-cache python3 py3-setuptools make g++ - -# Install pnpm -# Pin pnpm version for deterministic builds (required for Oyster CVM / Nix reproducibility). -# Must match the version in CLAUDE.md and docker/Dockerfile. -RUN corepack enable && corepack prepare pnpm@8.15.0 --activate - -WORKDIR /app - -# Copy dependency manifests for installation -COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc ./ -COPY patches/ ./patches/ -COPY packages/bls/package.json ./packages/bls/ -COPY packages/client/package.json ./packages/client/ -COPY packages/core/package.json ./packages/core/ -COPY packages/relay/package.json ./packages/relay/ -COPY packages/sdk/package.json ./packages/sdk/ -COPY packages/town/package.json ./packages/town/ -COPY docker/package.json ./docker/ - -# Install all dependencies (including devDependencies needed for build) -RUN pnpm install --frozen-lockfile - -# Copy source code -COPY tsconfig.json ./ -COPY packages/bls/ ./packages/bls/ -COPY packages/client/ ./packages/client/ -COPY packages/core/ ./packages/core/ -COPY packages/relay/ ./packages/relay/ -COPY packages/sdk/ ./packages/sdk/ -COPY packages/town/ ./packages/town/ -COPY docker/ ./docker/ - -# Build workspace packages needed for Docker (skip client — browser-only, not used at runtime) -# Then run esbuild to produce self-contained bundles -RUN pnpm -r --filter '!@toon-protocol/client' build && cd docker && pnpm run build - -# -- Assemble minimal runtime directory -------------------------------------- -# Only bundles + native modules + dynamically-required packages -RUN mkdir -p /runtime/node_modules - -# Copy esbuild bundles -RUN cp docker/dist/entrypoint-sdk.js /runtime/ && \ - cp docker/dist/attestation-server.js /runtime/ - -# ESM package.json (bundles use import.meta.url via banner) -RUN echo '{"type":"module"}' > /runtime/package.json - -# Cherry-pick better-sqlite3 (native module) + its runtime deps (bindings, file-uri-to-path) -# Each package resolved from pnpm store individually (version-agnostic globs) -RUN SQLITE_DIR=$(ls -d node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3) && \ - BINDINGS_DIR=$(ls -d node_modules/.pnpm/bindings@*/node_modules/bindings) && \ - FUTP_DIR=$(ls -d node_modules/.pnpm/file-uri-to-path@*/node_modules/file-uri-to-path) && \ - mkdir -p /runtime/node_modules/better-sqlite3/build/Release && \ - cp -r "$SQLITE_DIR/lib" /runtime/node_modules/better-sqlite3/ && \ - cp "$SQLITE_DIR/build/Release/better_sqlite3.node" \ - /runtime/node_modules/better-sqlite3/build/Release/ && \ - cp "$SQLITE_DIR/package.json" /runtime/node_modules/better-sqlite3/ && \ - cp -r "$BINDINGS_DIR" /runtime/node_modules/ && \ - cp -r "$FUTP_DIR" /runtime/node_modules/ - -# Install ethers + express (dynamically required by connector) and @ardrive/turbo-sdk -# (lazy-imported by Arweave DVM handler) with all transitive deps. -# Copy entire npm flat node_modules — more robust than cherry-picking individual packages -RUN cd /tmp && npm init -y && npm install --omit=dev ethers@6 express@4 @ardrive/turbo-sdk && \ - cp -r /tmp/node_modules/* /runtime/node_modules/ && \ - find /runtime/node_modules -name '*.d.ts' -delete && \ - find /runtime/node_modules -name '*.d.ts.map' -delete && \ - find /runtime/node_modules -name '*.js.map' -delete && \ - find /runtime/node_modules -name '*.d.mts' -delete && \ - rm -rf /tmp/node_modules /tmp/package.json /tmp/package-lock.json - -# -- Stage 2: Runtime (Oyster CVM variant) ----------------------------------- -FROM node:20-alpine - -# Install runtime dependencies for native modules + supervisord -# supervisor: Alpine package for multi-process orchestration inside -# the single Oyster CVM container (not pip-installed Python supervisor) -RUN apk add --no-cache libstdc++ supervisor - -WORKDIR /app - -# Create non-root user for security BEFORE copying files -# supervisord runs as root (required to switch users via user= directive) -# but individual programs execute as the 'toon' user -RUN addgroup -g 1001 toon && \ - adduser -D -u 1001 -G toon toon && \ - mkdir -p /data && \ - chown toon:toon /data - -# Copy minimal runtime from builder stage (--chown avoids duplicate layer) -COPY --from=builder --chown=toon:toon /runtime ./ - -# Copy supervisord configuration for Oyster CVM multi-process management -COPY docker/supervisord.conf /etc/supervisord.conf - -# Environment variables (with defaults) -ENV NODE_ENV=production -ENV BLS_PORT=3100 -ENV WS_PORT=7100 - -# Expose ports: -# 3000 - BTP (embedded ConnectorNode, peer connections) -# 3100 - BLS HTTP API (enriched /health, /handle-packet) -# 7100 - Nostr Relay WebSocket -# 1300 - Attestation HTTP server (/attestation/raw, /health) -EXPOSE 3000 3100 7100 1300 - -# Volume for persistent data -VOLUME /data - -# Health check targets BLS port (3100) -- same as standard Dockerfile -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD wget -q --spider http://localhost:${BLS_PORT}/health || exit 1 - -# Run supervisord (manages toon + attestation processes) -# supervisord must run as root to switch to the toon user via -# the user= directive in supervisord.conf -CMD ["supervisord", "-c", "/etc/supervisord.conf"] diff --git a/Dockerfile.sdk-e2e b/Dockerfile.sdk-e2e deleted file mode 100644 index 4e37a9b..0000000 --- a/Dockerfile.sdk-e2e +++ /dev/null @@ -1,224 +0,0 @@ -# TOON SDK E2E Peer Container (esbuild-bundled Multi-Stage Build) -# -# Purpose: -# Local SDK E2E peer image — runs a ConnectorNode + BLS HTTP API + -# Nostr Relay WebSocket outside of any TEE enclave. Intentionally minimal. -# -# This is NOT a TEE production image. For the Marlin Oyster CVM (TEE -# attestation) production image, see `docker/Dockerfile.oyster`. -# -# Consumers: -# - `scripts/sdk-e2e-infra.sh` — builds and tags this image as `toon:sdk-e2e` -# - `docker-compose-sdk-e2e.yml` — peer1 and peer2 services reference -# `image: toon:sdk-e2e` -# -# Build from repo root: -# docker build -f docker/Dockerfile.sdk-e2e -t toon:sdk-e2e . -# -# Divergence from Dockerfile.oyster (intentional): -# - NO supervisord (single-process container; CMD runs node directly) -# - NO attestation-server bundle (SDK E2E never exercises /attestation/raw) -# - NO port 1300 exposed (attestation port) -# - NO supervisord.conf copy -# - Builder stage does NOT invoke `cd docker && pnpm run build` (which -# would also try to bundle attestation-server and has been observed to -# fail). Instead, we invoke esbuild directly with ONLY -# `src/entrypoint-sdk.ts` as the entry point, mirroring the flags in -# `docker/esbuild.config.mjs`. Rationale: a failing attestation-server -# bundle must NOT block local SDK E2E infra (AC-3 option (a) in story -# 12.11). -# -# Everything else (base image, pnpm pin, workspace builds, native module -# cherry-picking, runtime node_modules flat install, non-root user, -# healthcheck, exposed BTP/BLS/WS ports) mirrors Dockerfile.oyster exactly -# so the runtime surface consumed by packages/sdk/tests/e2e/docker-*.test.ts -# is unchanged. - -# -- Stage 1a: Rust builder (SKIPPED) ------------------------------------------ -# pet-dvm / pet-circuit / memvid-node are not needed for ator transport E2E. -# This stage is a no-op; the builder stage below excludes all three packages. -FROM node:20-alpine AS rust-builder -RUN echo "rust-builder stub (pet/memvid skipped)" - -# -- Stage 1b: Builder ------------------------------------------------------- -FROM node:20-alpine AS builder - -# Install build dependencies for native modules -RUN apk add --no-cache python3 py3-setuptools make g++ - -# Install pnpm -# Pin pnpm version for deterministic builds (matches Dockerfile.oyster + CLAUDE.md) -RUN corepack enable && corepack prepare pnpm@8.15.0 --activate - -WORKDIR /app - -# Copy dependency manifests for installation -COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc ./ -COPY patches/ ./patches/ -COPY packages/bls/package.json ./packages/bls/ -COPY packages/client/package.json ./packages/client/ -COPY packages/core/package.json ./packages/core/ -COPY packages/relay/package.json ./packages/relay/ -COPY packages/sdk/package.json ./packages/sdk/ -COPY packages/town/package.json ./packages/town/ -COPY packages/mill/package.json ./packages/mill/ -# NOTE: pet-dvm, pet-circuit, memvid-node package.json COPYs removed — -# not needed for ator transport E2E testing. -COPY docker/package.json ./docker/ - -# Install all dependencies (including devDependencies needed for build) -RUN pnpm install --frozen-lockfile - -# Copy source code -COPY tsconfig.json ./ -COPY packages/bls/ ./packages/bls/ -COPY packages/client/ ./packages/client/ -COPY packages/core/ ./packages/core/ -COPY packages/relay/ ./packages/relay/ -COPY packages/sdk/ ./packages/sdk/ -COPY packages/town/ ./packages/town/ -COPY packages/mill/ ./packages/mill/ -# NOTE: pet-dvm, pet-circuit, memvid-node source COPYs removed — -# not needed for ator transport E2E testing. Kept as esbuild externals. -COPY docker/ ./docker/ - -# Build workspace packages (skip client — browser-only, not used at runtime; -# skip memvid-node — napi-rs Rust addon, requires Rust toolchain not present -# in this image and its Linux .node is not produced here by design; skip -# pet-dvm and pet-circuit — they tsc-compile fine but their runtime code paths -# depend on memvid-node's native .node artifact, and esbuild keeps them -# external below so we don't need their dist/). -# Then invoke esbuild DIRECTLY with entrypoint-sdk as the single entry point. -# This intentionally skips docker/esbuild.config.mjs' second entry point -# (attestation-server) because SDK E2E does not run attestation. Mirrors the -# banner/bundle/minify/target/platform flags from docker/esbuild.config.mjs. -RUN pnpm -r \ - --filter '!@toon-protocol/client' \ - --filter '!@toon-protocol/memvid-node' \ - --filter '!@toon-protocol/pet-dvm' \ - --filter '!@toon-protocol/pet-circuit' \ - build && \ - cd docker && \ - pnpm exec esbuild src/entrypoint-sdk.ts \ - --bundle \ - --platform=node \ - --target=node20 \ - --format=esm \ - --minify \ - --outfile=dist/entrypoint-sdk.js \ - --banner:js="import { createRequire } from 'module'; const require = createRequire(import.meta.url);" \ - --external:better-sqlite3 \ - --external:ethers \ - --external:express \ - --external:@ardrive/turbo-sdk \ - --external:o1js \ - --external:@solana/kit \ - --external:@solana-program/token \ - --external:@toon-protocol/mina-zkapp \ - --external:@toon-protocol/memvid-node \ - --external:@toon-protocol/pet-dvm \ - --external:@toon-protocol/pet-circuit \ - --external:socks-proxy-agent - -# -- Assemble minimal runtime directory -------------------------------------- -# Only the entrypoint-sdk bundle + native modules + dynamically-required packages. -# NOTE: attestation-server.js is intentionally NOT copied (SDK E2E does not use it). -RUN mkdir -p /runtime/node_modules - -# Copy esbuild bundle (entrypoint-sdk only) -RUN cp docker/dist/entrypoint-sdk.js /runtime/ - -# ESM package.json (bundle uses import.meta.url via banner) -RUN echo '{"type":"module"}' > /runtime/package.json - -# Cherry-pick better-sqlite3 (native module) + its runtime deps (bindings, file-uri-to-path) -# Each package resolved from pnpm store individually (version-agnostic globs). -# Mirrors Dockerfile.oyster lines 80–89 exactly — non-negotiable per AC-6. -RUN SQLITE_DIR=$(ls -d node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3) && \ - BINDINGS_DIR=$(ls -d node_modules/.pnpm/bindings@*/node_modules/bindings) && \ - FUTP_DIR=$(ls -d node_modules/.pnpm/file-uri-to-path@*/node_modules/file-uri-to-path) && \ - mkdir -p /runtime/node_modules/better-sqlite3/build/Release && \ - cp -r "$SQLITE_DIR/lib" /runtime/node_modules/better-sqlite3/ && \ - cp "$SQLITE_DIR/build/Release/better_sqlite3.node" \ - /runtime/node_modules/better-sqlite3/build/Release/ && \ - cp "$SQLITE_DIR/package.json" /runtime/node_modules/better-sqlite3/ && \ - cp -r "$BINDINGS_DIR" /runtime/node_modules/ && \ - cp -r "$FUTP_DIR" /runtime/node_modules/ - -# Install ethers + express (dynamically required by connector) and @ardrive/turbo-sdk -# (lazy-imported by Arweave DVM handler) with all transitive deps. -# Copy entire npm flat node_modules — more robust than cherry-picking individual packages. -# -# @libsql/client + libsql (toon-protocol/connector#79): connector 3.8.0 moved its -# local settlement/claim SQLite store off better-sqlite3 to libsql, lazy-required -# at runtime. From connector 3.10.x this is HARD-required — without it the embedded -# connector aborts at startup ("libsql is required for per-packet claims persistence -# … Payment channel native dependency missing — connector startup aborted"), so the -# sdk-e2e peers never become healthy and every SDK E2E (EVM/Solana/Mina) fails. -# Pinned to the same versions as Dockerfile.mill. libsql ships a prebuilt -# linux-x64-musl NAPI binding that loads on node:20-alpine (verified). -RUN cd /tmp && npm init -y && npm install --omit=dev ethers@6 express@4 @ardrive/turbo-sdk socks-proxy-agent@8 @libsql/client@0.14.0 libsql@0.4.7 && \ - cp -r /tmp/node_modules/* /runtime/node_modules/ && \ - find /runtime/node_modules -name '*.d.ts' -delete && \ - find /runtime/node_modules -name '*.d.ts.map' -delete && \ - find /runtime/node_modules -name '*.js.map' -delete && \ - find /runtime/node_modules -name '*.d.mts' -delete && \ - rm -rf /tmp/node_modules /tmp/package.json /tmp/package-lock.json - -# Stage @toon-protocol/{core,sdk,mill} (tsup-built, needed by mill at runtime). -# pet-dvm / pet-circuit / memvid-node are excluded (not needed for ator E2E). -RUN mkdir -p /runtime/node_modules/@toon-protocol/core && \ - cp packages/core/package.json /runtime/node_modules/@toon-protocol/core/ && \ - cp -r packages/core/dist /runtime/node_modules/@toon-protocol/core/ && \ - mkdir -p /runtime/node_modules/@toon-protocol/sdk && \ - cp packages/sdk/package.json /runtime/node_modules/@toon-protocol/sdk/ && \ - cp -r packages/sdk/dist /runtime/node_modules/@toon-protocol/sdk/ && \ - mkdir -p /runtime/node_modules/@toon-protocol/mill && \ - cp packages/mill/package.json /runtime/node_modules/@toon-protocol/mill/ && \ - cp -r packages/mill/dist /runtime/node_modules/@toon-protocol/mill/ - - -# -- Stage 2: Runtime (SDK E2E variant — no supervisord, no attestation) ----- -FROM node:20-alpine - -# Install runtime deps for native modules ONLY. -# NO `supervisor` package — single-process container, runs node directly. -RUN apk add --no-cache libstdc++ - -WORKDIR /app - -# Create non-root user for security BEFORE copying files. -# No supervisord here, so `toon` can be the CMD user directly (no root bootstrap needed). -RUN addgroup -g 1001 toon && \ - adduser -D -u 1001 -G toon toon && \ - mkdir -p /data && \ - chown toon:toon /data - -# Copy minimal runtime from builder stage (--chown avoids duplicate layer) -COPY --from=builder --chown=toon:toon /runtime ./ - -# Ensure /app/data is writable by the toon user (connector stores SQLite + ledger here) -RUN mkdir -p /app/data && chown toon:toon /app/data - -# Environment variables (with defaults — match Dockerfile.oyster) -ENV NODE_ENV=production -ENV BLS_PORT=3100 -ENV WS_PORT=7100 - -# Expose ports (no 1300 attestation): -# 3000 - BTP (embedded ConnectorNode, peer connections) -# 3100 - BLS HTTP API (enriched /health, /handle-packet) -# 7100 - Nostr Relay WebSocket -EXPOSE 3000 3100 7100 - -# Volume for persistent data -VOLUME /data - -# Health check targets BLS port (3100) — identical to Dockerfile.oyster:142–143 -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD wget -q --spider http://localhost:${BLS_PORT}/health || exit 1 - -# Run the entrypoint-sdk bundle directly as the non-root `toon` user. -# Matches `command: ['node', '/app/entrypoint-sdk.js']` in docker-compose-sdk-e2e.yml. -USER toon -CMD ["node", "/app/entrypoint-sdk.js"] diff --git a/Dockerfile.toon-client b/Dockerfile.toon-client deleted file mode 100644 index 5f13a7b..0000000 --- a/Dockerfile.toon-client +++ /dev/null @@ -1,265 +0,0 @@ -# syntax=docker/dockerfile:1.7 -# toon-client pod image (Story 49.3) -# -# Purpose: -# Long-lived deployment that wraps 49.1's in-process publish flow behind a -# Fastify HTTP control plane. Runs a LOCAL `anon` daemon (ator-onion mode) -# for outbound SOCKS5 to the operator's .anyone hidden service — NO -# HiddenServiceDir (the pod is a CLIENT, not an HS host). -# -# REAL npm-consumer build: rather than esbuild-inlining the local client -# source, this image PACKS the local @toon-protocol workspace packages into -# npm tarballs (exactly what `npm publish` uploads) and `npm install`s them -# into a clean consumer project. So it exercises the client's published -# surface — package.json `files`, `exports`, declared `dependencies` / -# `optionalDependencies` — the same way `npm i @toon-protocol/client` would -# for a third-party developer. The entrypoint is transpiled with all package -# imports left external, so it resolves @toon-protocol/client (+ deps) from -# node_modules at runtime, not from a bundle. -# -# Consumers: -# - scripts/akash-deploy.sh cmd_build_toon_client -# - deploy/akash/toon-client.sdl.yaml -# - scripts/townhouse-e2e-local-hs.sh (local e2e, `up --local` rebuilds this) -# -# Build (from repo root): -# docker build -f docker/Dockerfile.toon-client \ -# -t ghcr.io/toon-protocol/toon-client:demo . - -# ── Stage 1: anon-base (Debian + ator-protocol .deb) ───────────────────────── -# Pinned to the same version that docker/townhouse-ator-sidecar uses, so the -# anon binary behaviour is consistent across the two integration paths. -FROM debian:bookworm-slim AS anon-base - -ARG TARGETARCH -ARG ANON_VERSION=0.4.10.0-beta -ARG DEB_REVISION=1.d12.bookworm+1 -ARG ANON_RELEASE_BASE=https://github.com/anyone-protocol/ator-protocol/releases/download/v${ANON_VERSION} - -RUN set -eux; \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - ; \ - rm -rf /var/lib/apt/lists/* - -# Checksums mirrored from docker/townhouse-ator-sidecar/checksums.txt — keep -# in sync. sha256sum -c compatible format (checksum, two spaces, filename). -COPY docker/townhouse-ator-sidecar/checksums.txt /tmp/checksums.txt - -RUN set -eux; \ - case "${TARGETARCH}" in \ - amd64) DEB_FILE="anon_${ANON_VERSION}-${DEB_REVISION}_amd64.deb" ;; \ - arm64) DEB_FILE="anon_${ANON_VERSION}-${DEB_REVISION}_arm64.deb" ;; \ - *) echo "ERROR: unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ - esac; \ - URL_FILE="$(echo "${DEB_FILE}" | sed 's/+/%2B/g')"; \ - curl -fsSL -o "/tmp/${DEB_FILE}" "${ANON_RELEASE_BASE}/${URL_FILE}"; \ - cd /tmp; \ - grep " ${DEB_FILE}\$" /tmp/checksums.txt > /tmp/checksum.active; \ - sha256sum -c /tmp/checksum.active; \ - apt-get update; \ - echo 'anon anon/terms boolean true' | debconf-set-selections; \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "/tmp/${DEB_FILE}"; \ - rm -f "/tmp/${DEB_FILE}" /tmp/checksums.txt /tmp/checksum.active; \ - apt-get clean; \ - rm -rf /var/lib/apt/lists/*; \ - anon --version | grep -q "${ANON_VERSION}" - -# ── Stage 2: Node.js builder (pack workspace → install as an npm consumer) ─── -FROM node:20-bookworm-slim AS builder - -# python3/make/g++: native build of better-sqlite3 (a @toon-protocol/relay dep) -# when the consumer project installs it from the registry. -RUN apt-get update && \ - apt-get install -y --no-install-recommends python3 make g++ && \ - rm -rf /var/lib/apt/lists/* -RUN corepack enable && corepack prepare pnpm@8.15.0 --activate - -WORKDIR /app - -COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc ./ -COPY patches/ ./patches/ -# Copy the package.json of EVERY workspace project (not just the handful the -# client transitively needs). With `--frozen-lockfile`, pnpm prunes any -# workspace project whose manifest is absent from the install graph, which in -# turn drops that project's deps from `node_modules/.pnpm` and changes how -# shared deps' peer variants are hoisted/symlinked. The host always has the -# full workspace, so a partial manifest set here produces a DIFFERENT, fragile -# node_modules layout — e.g. the client's `viem` symlink can resolve to a peer -# variant whose type declarations aren't walkable, breaking the tsup DTS pass -# with `Cannot find module 'viem'` while the ESM bundle still builds (#138). -# Copying all manifests makes the Docker install graph identical to the host's, -# so dependency type resolution in the DTS build is deterministic. -# mina-zkapp is an OPTIONAL dependency of the client (the on-chain Mina -# channel-open path imports it lazily) and is NOT published to npm, so its -# workspace package must also be present for `pnpm pack` to resolve the -# client's `workspace:*` ref and for the runtime `import()`. -COPY packages/bls/package.json ./packages/bls/ -COPY packages/client/package.json ./packages/client/ -COPY packages/core/package.json ./packages/core/ -COPY packages/faucet/package.json ./packages/faucet/ -COPY packages/memvid-node/package.json ./packages/memvid-node/ -COPY packages/mill/package.json ./packages/mill/ -COPY packages/mina-zkapp/package.json ./packages/mina-zkapp/ -COPY packages/pet-circuit/package.json ./packages/pet-circuit/ -COPY packages/pet-dvm/package.json ./packages/pet-dvm/ -COPY packages/relay/package.json ./packages/relay/ -COPY packages/rig/package.json ./packages/rig/ -COPY packages/sdk/package.json ./packages/sdk/ -COPY packages/town/package.json ./packages/town/ -COPY packages/townhouse/package.json ./packages/townhouse/ -COPY packages/townhouse-web/package.json ./packages/townhouse-web/ -COPY examples/client-example/package.json ./examples/client-example/ -COPY examples/sdk-example/package.json ./examples/sdk-example/ -COPY examples/town-example/package.json ./examples/town-example/ -COPY docker/package.json ./docker/ - -# `--ignore-scripts` skips native postinstall builds (better-sqlite3, the -# Rust-backed memvid-node/pet-circuit, etc.) that the now-complete workspace -# graph would otherwise trigger. None of those native artifacts are needed to -# build/pack the JS client packages; the runtime image installs its own deps -# (incl. native better-sqlite3) fresh in the /consumer stage. This keeps the -# fuller manifest set from slowing the build or failing on toolchain gaps while -# still giving pnpm the complete graph it needs for deterministic hoisting. -RUN pnpm install --frozen-lockfile --ignore-scripts - -COPY tsconfig.json ./ -COPY packages/bls/ ./packages/bls/ -COPY packages/client/ ./packages/client/ -COPY packages/core/ ./packages/core/ -COPY packages/relay/ ./packages/relay/ -COPY packages/mina-zkapp/ ./packages/mina-zkapp/ -COPY packages/townhouse/contracts/ ./packages/townhouse/contracts/ -COPY docker/ ./docker/ - -# Build the local packages, then PACK each into a version-stamped npm tarball. -# pnpm rewrites `workspace:*` deps → real versions on pack, exactly as it would -# on `npm publish`. The tarball IS the published artifact. -RUN pnpm -r \ - --filter '@toon-protocol/bls' \ - --filter '@toon-protocol/core' \ - --filter '@toon-protocol/relay' \ - --filter '@toon-protocol/mina-zkapp' \ - --filter '@toon-protocol/client' \ - build - -# `pnpm pack` is not a recursive command (no --filter), so pack from each -# package dir. `--pack-destination` collects the tarballs in one place. -# mina-zkapp is packed too so the client's optional `@toon-protocol/mina-zkapp` -# dep resolves to a sibling file: tarball in the consumer install (it is not on -# npm) and is present for the runtime on-chain Mina channel-open import. -RUN mkdir -p /tarballs && \ - ( cd packages/bls && pnpm pack --pack-destination /tarballs ) && \ - ( cd packages/core && pnpm pack --pack-destination /tarballs ) && \ - ( cd packages/relay && pnpm pack --pack-destination /tarballs ) && \ - ( cd packages/mina-zkapp && pnpm pack --pack-destination /tarballs ) && \ - ( cd packages/client && pnpm pack --pack-destination /tarballs ) && \ - ( cd /tarballs && \ - mv toon-protocol-bls-*.tgz bls.tgz && \ - mv toon-protocol-core-*.tgz core.tgz && \ - mv toon-protocol-relay-*.tgz relay.tgz && \ - mv toon-protocol-mina-zkapp-*.tgz mina-zkapp.tgz && \ - mv toon-protocol-client-*.tgz client.tgz ) - -# Clean consumer project: install the packed tarballs (file:) + the entrypoint's -# own registry deps, exactly like a third-party developer. This `npm install` -# is the PUBLISH-SURFACE GATE — it fails if the client's `files`/`exports`/ -# `dependencies` are wrong. npm satisfies the tarballs' inter-package version -# refs from the sibling file: deps, and pulls everything else (incl. the -# client's optionalDependencies + relay's native better-sqlite3) from the -# public registry. -WORKDIR /consumer -RUN cat > package.json <<'JSON' -{ - "name": "toon-client-consumer", - "version": "0.0.0", - "private": true, - "type": "module", - "dependencies": { - "@toon-protocol/bls": "file:/tarballs/bls.tgz", - "@toon-protocol/core": "file:/tarballs/core.tgz", - "@toon-protocol/relay": "file:/tarballs/relay.tgz", - "@toon-protocol/mina-zkapp": "file:/tarballs/mina-zkapp.tgz", - "@toon-protocol/client": "file:/tarballs/client.tgz", - "@noble/curves": "^2.0.0", - "ajv": "^8.18.0", - "ajv-formats": "^3.0.1", - "bs58": "^6.0.0", - "fastify": "^5.0.0", - "nostr-tools": "^2.10.4", - "o1js": "2.14.0", - "viem": "^2.47.0" - }, - "devDependencies": { - "esbuild": "^0.24.0" - } -} -JSON -RUN npm install --no-audit --no-fund - -# Transpile the entrypoint with ALL package imports left external, so the output -# imports the INSTALLED @toon-protocol/client (+ deps) from /consumer/node_modules -# at runtime rather than inlining local source. -RUN cp /app/docker/src/entrypoint-toon-client.ts ./entrypoint-toon-client.ts && \ - ./node_modules/.bin/esbuild entrypoint-toon-client.ts \ - --bundle \ - --packages=external \ - --platform=node \ - --target=node20 \ - --format=esm \ - --outfile=entrypoint-toon-client.js && \ - rm entrypoint-toon-client.ts - -RUN mkdir -p /consumer/contracts && \ - cp /app/packages/townhouse/contracts/foreign-publish.schema.json /consumer/contracts/ && \ - rm -rf /consumer/node_modules/.cache - -# ── Stage 3: Runtime (debian:bookworm-slim + Node.js + anon) ───────────────── -# We start from anon-base (which has the anon binary already installed via .deb) -# and layer Node.js 20 on top. This is cheaper than copying anon out of -# anon-base into a node:20-bookworm-slim base because the .deb installs system -# libs (libevent, openssl, etc) that anon needs at runtime. -FROM anon-base AS runtime - -# Node.js 20 from NodeSource (matches the major version used by the builder -# stage's node:20-bookworm-slim base). Keeps the apt-installed Node aligned -# with the bundled JS target=node20. -RUN set -eux; \ - apt-get update; \ - apt-get install -y --no-install-recommends ca-certificates curl gnupg; \ - mkdir -p /etc/apt/keyrings; \ - curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | \ - gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; \ - echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" \ - > /etc/apt/sources.list.d/nodesource.list; \ - apt-get update; \ - apt-get install -y --no-install-recommends nodejs; \ - apt-get purge -y --auto-remove curl gnupg; \ - apt-get clean; \ - rm -rf /var/lib/apt/lists/*; \ - node --version - -WORKDIR /consumer -COPY --from=builder /consumer /consumer - -# Anon data directory. The container runs as root (matches the townhouse -# ator-sidecar convention — Akash provider Pod Security Policies sometimes -# reject non-root images). anon's strict ownership check on DataDirectory -# then matches the running user. -RUN mkdir -p /var/lib/anon /etc/anon && chown -R root:root /var/lib/anon /etc/anon - -ENV NODE_ENV=production -ENV POD_PORT=8080 -ENV ANON_SOCKS_PORT=9050 -ENV FOREIGN_PUBLISH_SCHEMA_PATH=/consumer/contracts/foreign-publish.schema.json - -EXPOSE 8080 - -# Pod takes 30-120s to fully boot: anon bootstrap (30-90s) + faucet drip + balance poll. -# start-period gives anon enough headroom to build a circuit before health checks begin. -HEALTHCHECK --interval=30s --timeout=10s --start-period=180s --retries=3 \ - CMD node -e "fetch('http://127.0.0.1:'+process.env.POD_PORT+'/healthz').then(r=>r.ok?process.exit(0):process.exit(1)).catch(()=>process.exit(1))" || exit 1 - -CMD ["node", "/consumer/entrypoint-toon-client.js"] diff --git a/Dockerfile.town b/Dockerfile.town deleted file mode 100644 index c802425..0000000 --- a/Dockerfile.town +++ /dev/null @@ -1,176 +0,0 @@ -# TOON Town Node Container (esbuild-bundled Multi-Stage Build) -# -# Purpose: -# Production-grade Town relay container for Townhouse orchestration. -# Runs a Nostr relay with ILP write-fees, connecting to a standalone connector. -# -# Consumers: -# - `docker-compose-townhouse.yml` — town service references `image: toon:town` -# - `packages/townhouse` DockerOrchestrator — builds and runs this image -# -# Build from repo root: -# docker build -f docker/Dockerfile.town -t toon:town . -# -# Pattern follows docker/Dockerfile.sdk-e2e (multi-stage, esbuild, native modules, non-root). - -# -- Stage 1: Builder ------------------------------------------------------- -FROM node:20-alpine AS builder - -# Install build dependencies for native modules -RUN apk add --no-cache python3 py3-setuptools make g++ - -# Install pnpm (pinned for deterministic builds) -RUN corepack enable && corepack prepare pnpm@8.15.0 --activate - -WORKDIR /app - -# Copy dependency manifests for installation -COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc ./ -COPY patches/ ./patches/ -COPY packages/bls/package.json ./packages/bls/ -COPY packages/core/package.json ./packages/core/ -COPY packages/relay/package.json ./packages/relay/ -COPY packages/sdk/package.json ./packages/sdk/ -COPY packages/town/package.json ./packages/town/ -COPY docker/package.json ./docker/ - -# Install all dependencies (including devDependencies needed for build) -RUN pnpm install --frozen-lockfile - -# Copy source code (only packages needed for Town) -COPY tsconfig.json ./ -COPY packages/bls/ ./packages/bls/ -COPY packages/core/ ./packages/core/ -COPY packages/relay/ ./packages/relay/ -COPY packages/sdk/ ./packages/sdk/ -COPY packages/town/ ./packages/town/ -COPY docker/ ./docker/ - -# Build workspace packages needed by Town, then bundle the entrypoint. -# Only builds: core -> relay -> sdk -> bls -> town (Town's dependency chain). -# Invokes esbuild directly with entrypoint-town.ts as the single entry point. -RUN pnpm -r \ - --filter '@toon-protocol/core' \ - --filter '@toon-protocol/relay' \ - --filter '@toon-protocol/sdk' \ - --filter '@toon-protocol/bls' \ - --filter '@toon-protocol/town' \ - build && \ - cd docker && \ - pnpm exec esbuild src/entrypoint-town.ts \ - --bundle \ - --platform=node \ - --target=node20 \ - --format=esm \ - --minify \ - --outfile=dist/entrypoint-town.js \ - --banner:js="import { createRequire } from 'module'; const require = createRequire(import.meta.url);" \ - --external:better-sqlite3 \ - --external:@libsql/client \ - --external:libsql \ - --external:ethers \ - --external:express \ - --external:mina-signer \ - --external:o1js \ - --external:@solana/kit \ - --external:@solana-program/token \ - --external:@toon-protocol/mina-zkapp - -# -- Assemble minimal runtime directory -------------------------------------- -RUN mkdir -p /runtime/node_modules - -# Copy esbuild bundle -RUN cp docker/dist/entrypoint-town.js /runtime/ - -# ESM package.json (bundle uses import.meta.url via banner) -RUN echo '{"type":"module"}' > /runtime/package.json - -# Cherry-pick better-sqlite3 (native module) + its runtime deps (bindings, file-uri-to-path) -RUN SQLITE_DIR=$(ls -d node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3) && \ - BINDINGS_DIR=$(ls -d node_modules/.pnpm/bindings@*/node_modules/bindings) && \ - FUTP_DIR=$(ls -d node_modules/.pnpm/file-uri-to-path@*/node_modules/file-uri-to-path) && \ - mkdir -p /runtime/node_modules/better-sqlite3/build/Release && \ - cp -r "$SQLITE_DIR/lib" /runtime/node_modules/better-sqlite3/ && \ - cp "$SQLITE_DIR/build/Release/better_sqlite3.node" \ - /runtime/node_modules/better-sqlite3/build/Release/ && \ - cp "$SQLITE_DIR/package.json" /runtime/node_modules/better-sqlite3/ && \ - cp -r "$BINDINGS_DIR" /runtime/node_modules/ && \ - cp -r "$FUTP_DIR" /runtime/node_modules/ - -# Copy all @solana/* v3 packages + @solana-program/token (connector Solana settlement SDK) -# These are external in esbuild; must be present as real modules at runtime. -# Each @solana+name@3.x pnpm store entry holds the package at .../node_modules/@solana/name/. -# We copy them all so transitive requires between sub-packages resolve correctly. -RUN mkdir -p /runtime/node_modules/@solana /runtime/node_modules/@solana-program && \ - for store_dir in $(ls -d node_modules/.pnpm/@solana+*@3.0.*/node_modules/@solana/*/ 2>/dev/null); do \ - pkg=$(basename "$store_dir"); \ - [ ! -d "/runtime/node_modules/@solana/$pkg" ] && cp -rL "$store_dir" "/runtime/node_modules/@solana/$pkg" || true; \ - done && \ - SOLANA_TOKEN_STORE=$(ls -d node_modules/.pnpm/@solana-program+token@0.6*/node_modules/@solana-program/token 2>/dev/null | head -1) && \ - [ -n "$SOLANA_TOKEN_STORE" ] && cp -rL "$SOLANA_TOKEN_STORE" /runtime/node_modules/@solana-program/token || true && \ - WS_STORE=$(ls -d node_modules/.pnpm/ws@8.*/node_modules/ws 2>/dev/null | tail -1) && \ - [ -n "$WS_STORE" ] && cp -rL "$WS_STORE" /runtime/node_modules/ws || true - -# Connector requires express at runtime for its health server (port 8080 -# inside the container) even when we set adminApi.enabled=false. Install it -# (and ethers, which the connector lazy-loads for settlement) into the -# runtime via npm — same pattern as Dockerfile.sdk-e2e. -# -# @libsql/client + libsql (toon-protocol/connector#79): connector 3.8.0 migrated -# its local settlement/claim SQLite store off better-sqlite3 to libsql, which it -# lazy-`requireOptional`s at runtime. Without these the per-packet claim -# subsystem (incl. the relation-aware InboundClaimValidator that connector#78 -# adds) silently fails to initialize and value-bearing packets auto-fulfill -# instead of claim-gating. libsql ships a prebuilt linux-x64-musl NAPI binding -# that loads on this node:20-alpine runtime (verified). better-sqlite3 above is -# retained separately for the Nostr relay's own event store. -RUN cd /tmp && npm init -y && npm install --omit=dev ethers@6 express@4 socks-proxy-agent@8 @libsql/client@0.14.0 libsql@0.4.7 && \ - cp -r /tmp/node_modules/* /runtime/node_modules/ && \ - find /runtime/node_modules -name '*.d.ts' -delete && \ - find /runtime/node_modules -name '*.d.ts.map' -delete && \ - find /runtime/node_modules -name '*.js.map' -delete && \ - rm -rf /tmp/node_modules /tmp/package.json /tmp/package-lock.json - -# -- Stage 2: Runtime -------------------------------------------------------- -FROM node:20-alpine - -# Install runtime deps for native modules ONLY. -RUN apk add --no-cache libstdc++ - -WORKDIR /app - -# Create non-root user for security BEFORE copying files. -# Symlink /app/data -> /data so the embedded connector's hardcoded -# `./data/claims-.db` path (relative to WORKDIR=/app) lands in -# the named volume mounted at /data instead of the container's -# ephemeral writable layer. -RUN addgroup -g 1001 toon && \ - adduser -D -u 1001 -G toon toon && \ - mkdir -p /data && \ - chown toon:toon /data && \ - ln -s /data /app/data - -# Copy minimal runtime from builder stage -COPY --from=builder --chown=toon:toon /runtime ./ - -# Environment variables (with defaults) -ENV NODE_ENV=production -ENV BLS_PORT=3100 -ENV WS_PORT=7100 - -# Expose ports: -# 3000 - BTP (embedded ConnectorNode, peer connections from standalone connector) -# 3100 - BLS HTTP API (health endpoint) -# 7100 - Nostr Relay WebSocket -EXPOSE 3000 3100 7100 - -# Volume for persistent data (SQLite relay database) -VOLUME /data - -# Health check targets BLS port (3100) -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD wget -q --spider http://localhost:${BLS_PORT}/health || exit 1 - -# Run as non-root user -USER toon -CMD ["node", "/app/entrypoint-town.js"] diff --git a/Dockerfile.townhouse-api b/Dockerfile.townhouse-api deleted file mode 100644 index 9728006..0000000 --- a/Dockerfile.townhouse-api +++ /dev/null @@ -1,192 +0,0 @@ -# TOON Townhouse API Container (esbuild-bundled Multi-Stage Build) -# -# Purpose: -# Containerized Townhouse API server for HS-mode deployment (Story 45.4). -# Mounts /var/run/docker.sock at runtime to orchestrate sibling containers. -# -# Consumers: -# - docker-compose-townhouse-hs.yml (Story 45.2) — townhouse-api service -# - `npx @toon-protocol/townhouse hs up` (Story 45.4) — one-command apex start -# -# Build from repo root: -# docker build -f docker/Dockerfile.townhouse-api -t toon:townhouse-api . -# -# Pattern follows docker/Dockerfile.mill (multi-stage, esbuild, non-root). -# Key difference: no @solana/* or mina-signer cherry-picks — those transitive -# imports from the connector dep chain are kept as externals, installed via npm -# in the runtime stage (simpler than cherry-picking pnpm's hoist tree for the -# few paths the API server actually exercises at runtime). -# -# NOTE: image-level LABELs are declared in the runtime stage (after the -# second FROM) — LABELs declared before any FROM are silently dropped by -# Docker and never appear on the produced image. - -# -- Stage 1: Builder ------------------------------------------------------- -FROM node:20-alpine AS builder - -# Install build dependencies for native modules -RUN apk add --no-cache python3 py3-setuptools make g++ - -# Install pnpm (pinned for deterministic builds) -RUN corepack enable && corepack prepare pnpm@8.15.0 --activate - -WORKDIR /app - -# Copy dependency manifests for layer-cache efficiency. -# Copy the package.json of EVERY in-context workspace project (not just the -# handful townhouse transitively needs). With `--frozen-lockfile`, pnpm prunes -# any workspace project whose manifest is absent from the install graph, which -# drops that project's deps from `node_modules/.pnpm` and changes how shared -# deps' peer variants get hoisted/symlinked. The host always has the FULL -# workspace, so a partial manifest set produces a DIFFERENT, fragile -# node_modules layout here — e.g. the sdk's `@toon-protocol/connector` symlink -# resolves to a peer variant whose .d.ts isn't walkable, breaking the tsup DTS -# pass with `Cannot find module '@toon-protocol/connector'` while the ESM bundle -# still builds (#143, same class as #138/#141). Copying all manifests makes the -# Docker install graph identical to the host's so DTS type resolution is -# deterministic. (packages/examples is excluded by .dockerignore — the only -# member intentionally omitted, matching the host's tolerance of it.) -COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc ./ -COPY patches/ ./patches/ -COPY packages/bls/package.json ./packages/bls/ -COPY packages/client/package.json ./packages/client/ -COPY packages/core/package.json ./packages/core/ -COPY packages/faucet/package.json ./packages/faucet/ -COPY packages/memvid-node/package.json ./packages/memvid-node/ -COPY packages/mill/package.json ./packages/mill/ -COPY packages/mina-zkapp/package.json ./packages/mina-zkapp/ -COPY packages/pet-circuit/package.json ./packages/pet-circuit/ -COPY packages/pet-dvm/package.json ./packages/pet-dvm/ -COPY packages/relay/package.json ./packages/relay/ -COPY packages/rig/package.json ./packages/rig/ -COPY packages/sdk/package.json ./packages/sdk/ -COPY packages/town/package.json ./packages/town/ -COPY packages/townhouse/package.json ./packages/townhouse/ -COPY packages/townhouse-web/package.json ./packages/townhouse-web/ -COPY examples/client-example/package.json ./examples/client-example/ -COPY examples/sdk-example/package.json ./examples/sdk-example/ -COPY examples/town-example/package.json ./examples/town-example/ -COPY docker/package.json ./docker/ - -# Install all dependencies (including devDependencies needed for build). -# `--ignore-scripts` skips native postinstall builds (better-sqlite3, the -# Rust-backed memvid-node/pet-circuit) that the now-complete workspace graph -# would otherwise trigger — the builder only transpiles JS, and the runtime -# stage installs its own native deps fresh. -RUN pnpm install --frozen-lockfile --ignore-scripts - -# Copy source code (only packages in the townhouse dep chain) -COPY tsconfig.json ./ -COPY packages/bls/ ./packages/bls/ -COPY packages/core/ ./packages/core/ -COPY packages/sdk/ ./packages/sdk/ -COPY packages/mill/ ./packages/mill/ -COPY packages/townhouse/ ./packages/townhouse/ -COPY docker/ ./docker/ - -# Build workspace packages, then bundle the entrypoint. -# Dependency chain: core → sdk → bls → mill → townhouse -RUN pnpm -r \ - --filter '@toon-protocol/core' \ - --filter '@toon-protocol/sdk' \ - --filter '@toon-protocol/bls' \ - --filter '@toon-protocol/mill' \ - --filter '@toon-protocol/townhouse' \ - build && \ - cd docker && \ - pnpm exec esbuild src/entrypoint-townhouse-api.ts \ - --bundle \ - --platform=node \ - --target=node20 \ - --format=esm \ - --minify \ - --outfile=dist/entrypoint-townhouse-api.js \ - --banner:js="import { createRequire } from 'module'; const require = createRequire(import.meta.url);" \ - --external:dockerode \ - --external:ethers \ - --external:express \ - --external:mina-signer \ - --external:o1js \ - --external:@solana/kit \ - --external:@solana-program/token \ - --external:@toon-protocol/mina-zkapp - -# -- Assemble minimal runtime directory -------------------------------------- -RUN mkdir -p /runtime/node_modules - -# Copy esbuild bundle -RUN cp docker/dist/entrypoint-townhouse-api.js /runtime/ - -# ESM package.json (bundle uses import.meta.url via banner) -RUN echo '{"type":"module"}' > /runtime/package.json - -# Install external modules: dockerode (Docker socket client), ethers + express -# (connector dep chain), socks-proxy-agent (ATOR transport). -RUN cd /tmp && npm init -y && \ - npm install --omit=dev dockerode@4 ethers@6 express@4 socks-proxy-agent@8 && \ - cp -r /tmp/node_modules/* /runtime/node_modules/ && \ - find /runtime/node_modules -name '*.d.ts' -delete && \ - find /runtime/node_modules -name '*.d.ts.map' -delete && \ - find /runtime/node_modules -name '*.js.map' -delete && \ - rm -rf /tmp/node_modules /tmp/package.json /tmp/package-lock.json - -# Copy @solana/* v3 packages and @solana-program/token — transitive externals -# from the connector dep chain. The API server never exercises Solana settlement -# paths at runtime, but the bundle references them as external imports and Node -# will attempt to resolve them on startup. -RUN mkdir -p /runtime/node_modules/@solana /runtime/node_modules/@solana-program && \ - for store_dir in $(ls -d node_modules/.pnpm/@solana+*@3.0.*/node_modules/@solana/*/ 2>/dev/null); do \ - pkg=$(basename "$store_dir"); \ - [ ! -d "/runtime/node_modules/@solana/$pkg" ] && cp -rL "$store_dir" "/runtime/node_modules/@solana/$pkg" || true; \ - done && \ - SOLANA_TOKEN_STORE=$(ls -d node_modules/.pnpm/@solana-program+token@0.6*/node_modules/@solana-program/token 2>/dev/null | head -1) && \ - [ -n "$SOLANA_TOKEN_STORE" ] && cp -rL "$SOLANA_TOKEN_STORE" /runtime/node_modules/@solana-program/token || true && \ - WS_STORE=$(ls -d node_modules/.pnpm/ws@8.*/node_modules/ws 2>/dev/null | tail -1) && \ - [ -n "$WS_STORE" ] && cp -rL "$WS_STORE" /runtime/node_modules/ws || true - -# -- Stage 2: Runtime -------------------------------------------------------- -FROM node:20-alpine - -# Image metadata (must be in runtime stage — see header comment) -LABEL maintainer="toon-protocol" -LABEL version="0.1.0" -LABEL description="TOON Townhouse API - Fastify orchestration API for HS-mode deployment" - -# Install runtime deps: native modules + docker CLI/compose. -# The orchestrator (DockerOrchestrator.startNodeViaCompose) shells out to -# `docker compose -f ... up -d ` for each lazy peer-node provisioning -# request. Without docker-cli + docker-cli-compose installed in the image, -# every `node add` POST fails at step 4 (`start-container`) with ENOENT on -# the `docker` binary. Discovered by Story 46.4 live gate run (Finding E, -# 2026-05-11). -RUN apk add --no-cache libstdc++ docker-cli docker-cli-compose - -WORKDIR /app - -# Create non-root user (uid:gid 1001:1001, matches town/mill/dvm pattern). -# The container mounts /var/run/docker.sock at runtime — the toon user must be -# in the docker group (gid 999 by default on most Linux hosts). Operators who -# need socket access should add --group-add 999 to their docker run command, -# or run with --privileged in development. The Dockerfile does NOT bake in the -# docker group assumption; that would tie the image to a specific host gid. -RUN addgroup -g 1001 toon && \ - adduser -D -u 1001 -G toon toon - -# Copy minimal runtime from builder stage -COPY --from=builder --chown=toon:toon /runtime ./ - -# Environment variable defaults -ENV NODE_ENV=production -ENV TOWNHOUSE_API_PORT=28090 - -# Expose port: -# 28090 - Townhouse API (Fastify, operator maps to preferred host port) -EXPOSE 28090 - -# Health check against the API health endpoint -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD wget -q --spider http://localhost:${TOWNHOUSE_API_PORT}/api/health || exit 1 - -# Run as non-root user -USER toon -CMD ["node", "/app/entrypoint-townhouse-api.js"] diff --git a/README.md b/README.md index 4ec02aa..a0d2205 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ TOON Protocol **Arweave DVM node** — NIP-90 kind:5094 blob storage. The node accepts a paid claim (routed via the hub/connector), uploads the blob to Arweave via Turbo, and returns the tx id. Built from `Dockerfile.dvm` over `src/entrypoint-dvm.ts`, which wraps `@toon-protocol/sdk`'s `createArweaveDvmHandler`. ## Status / follow-ups -- This repo was carved from the monorepo's `docker/` aggregator and **still contains the other images' build contexts** (Dockerfile.town/mill/townhouse-api + their entrypoints). **Trim to dvm-only** (keep `Dockerfile.dvm` + `src/entrypoint-dvm.ts` + shared helpers). - Add the **image-publish workflow** (carve from the monorepo `publish-townhouse-images.yml`'s dvm job): multi-arch build → push to GHCR → signed digest. +- `Dockerfile.dvm` still references monorepo paths (`packages/*`, `pnpm-workspace.yaml`, etc.) that do not exist here. Fixing the Dockerfile to build standalone is a separate follow-up. - Publishes no npm package (it's a container); kept `private`. > Extracted from the TOON monorepo with full git history preserved. diff --git a/akash-ator-probe/Dockerfile b/akash-ator-probe/Dockerfile deleted file mode 100644 index 6a59210..0000000 --- a/akash-ator-probe/Dockerfile +++ /dev/null @@ -1,91 +0,0 @@ -# syntax=docker/dockerfile:1.7 -# ATOR-on-Akash probe image — pinned anon v0.4.10.0-beta, no-ingress design. -# -# Mission: validate that the @anyone-protocol `anon` binary boots, joins the -# public Anyone network, and publishes a hidden service descriptor when run -# inside an Akash provider's container runtime. Nothing in this container is -# externally reachable — the .anyone address (pre-generated locally and seeded -# via HS_SECRET_KEY_B64) IS the access mechanism. The round-trip test dials -# that address through a local anon SOCKS5 proxy, confirms PROBE-OK response. -# -# Build (amd64): docker build --platform linux/amd64 -t ator-probe:v0.4.10.0-beta -f docker/akash-ator-probe/Dockerfile docker/akash-ator-probe/ -# Build (arm64): docker build --platform linux/arm64 -t ator-probe:v0.4.10.0-beta -f docker/akash-ator-probe/Dockerfile docker/akash-ator-probe/ -# -# Build context is THIS directory (not repo root) — checksums.txt + entrypoint.sh -# are the only inputs; nothing from the workspace is copied in. -FROM debian:bookworm-slim - -ARG TARGETARCH -ARG ANON_VERSION=0.4.10.0-beta -ARG DEB_REVISION=1.d12.bookworm+1 -ARG ANON_RELEASE_BASE=https://github.com/anyone-protocol/ator-protocol/releases/download/v${ANON_VERSION} - -# Runtime deps. curl is purged after .deb install. socat is the in-container -# HS target (tiny TCP listener responding "PROBE-OK\n" so the round-trip test -# can verify the .anyone → hidden-service-port → 127.0.0.1:TARGET_PORT path is -# alive). No HTTP server — this container has no public ingress. -RUN set -eux; \ - apt-get update; \ - apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - netcat-openbsd \ - socat \ - ; \ - rm -rf /var/lib/apt/lists/* - -COPY checksums.txt /tmp/checksums.txt - -# Download + verify the .deb for TARGETARCH. Both amd64 and arm64 are pinned in -# checksums.txt; sha256sum -c fails the build on mismatch (no silent pass). -RUN set -eux; \ - case "${TARGETARCH}" in \ - amd64) DEB_FILE="anon_${ANON_VERSION}-${DEB_REVISION}_amd64.deb" ;; \ - arm64) DEB_FILE="anon_${ANON_VERSION}-${DEB_REVISION}_arm64.deb" ;; \ - *) echo "ERROR: unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ - esac; \ - URL_FILE="$(echo "${DEB_FILE}" | sed 's/+/%2B/g')"; \ - echo "Fetching ${ANON_RELEASE_BASE}/${URL_FILE}"; \ - curl -fsSL -o "/tmp/${DEB_FILE}" "${ANON_RELEASE_BASE}/${URL_FILE}"; \ - cd /tmp; \ - grep " ${DEB_FILE}\$" /tmp/checksums.txt > /tmp/checksum.active; \ - sha256sum -c /tmp/checksum.active; \ - apt-get update; \ - echo 'anon anon/terms boolean true' | debconf-set-selections; \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "/tmp/${DEB_FILE}"; \ - rm -f "/tmp/${DEB_FILE}" /tmp/checksums.txt /tmp/checksum.active; \ - apt-get purge -y --auto-remove curl; \ - apt-get clean; \ - rm -rf /var/lib/apt/lists/*; \ - anon --version | grep -q "${ANON_VERSION}" - -COPY entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod 0755 /usr/local/bin/entrypoint.sh - -# State directories used by the entrypoint. We run as root because some Akash -# providers' Kubernetes Pod Security Policies reject non-root images outright -# (matches the pattern in docker/Dockerfile.akash-anvil:18). For a derisking -# probe this is acceptable; the production townhouse SDL will reintroduce a -# non-root user once we identify which providers tolerate it. -# -# Anon's .deb postinst creates a `debian-anon` user (uid 100) and chowns -# /var/lib/anon to that user. When we seed an existing hs_ed25519_secret_key -# and run anon as root, anon sees the directory owner-mismatch, closes its -# Socks listener mid-init, and segfaults with "free(): invalid size". chown -# everything to root NOW so root-running anon is happy with the layout. -RUN mkdir -p /var/lib/anon /etc/anon && \ - chown -R root:root /var/lib/anon /etc/anon - -# Persistent volume — when this is mounted as Akash persistent storage the -# .anyone address survives redeploys (hs_ed25519_secret_key lives under -# /var/lib/anon/hs/). For the probe we use HS_SECRET_KEY_B64 env injection -# instead, which works without persistent storage. -VOLUME ["/var/lib/anon"] - -# Port 8080 is a minimal "I'm alive" HTTP responder (socat-backed in the -# entrypoint). It exists ONLY to satisfy Akash Console API's "at least one -# global service" manifest validator and the kubelet readiness probe — the -# operational access path is exclusively the .anyone hidden service. -EXPOSE 8080 - -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/akash-ator-probe/checksums.txt b/akash-ator-probe/checksums.txt deleted file mode 100644 index 66446c2..0000000 --- a/akash-ator-probe/checksums.txt +++ /dev/null @@ -1,8 +0,0 @@ -# ATOR Protocol (anon) v0.4.10.0-beta — Debian bookworm .deb packages -# Source URL pattern: -# https://github.com/anyone-protocol/ator-protocol/releases/download/v0.4.10.0-beta/ -# Mirrored from connector/docker/ator-public-sidecar/checksums.txt — keep in sync. -# -# Format: sha256sum -c compatible (checksum, two spaces, filename) -f75c139509fd353504d0de45356336bef7dcdf13c3d11a087b3a9733bc08790a anon_0.4.10.0-beta-1.d12.bookworm+1_amd64.deb -1f5f0971f9c359d42aa8c77442234dfdccc50654927c93b2f538410582563e5a anon_0.4.10.0-beta-1.d12.bookworm+1_arm64.deb diff --git a/akash-ator-probe/entrypoint.sh b/akash-ator-probe/entrypoint.sh deleted file mode 100755 index 26cdba2..0000000 --- a/akash-ator-probe/entrypoint.sh +++ /dev/null @@ -1,125 +0,0 @@ -#!/bin/sh -# ATOR-on-Akash probe entrypoint — no-ingress design. -# -# Architecture: this container runs anon as a hidden service ONLY. Nothing is -# externally reachable from the internet. The .anyone address IS the access -# mechanism — clients dial it through their own anon SOCKS5 proxy. -# -# Hostname discovery: we expect the operator to PRE-GENERATE the v3 keypair -# locally and inject the secret via HS_SECRET_KEY_B64. anon detects existing -# keys under /var/lib/anon/hs/ and reuses them, so the .anyone address is -# known before deploy. If HS_SECRET_KEY_B64 is unset, anon generates fresh -# keys (useful for one-off dev tests, useless for an Akash deploy where you -# can't see the resulting hostname without a wallet shell). -# -# Layout: -# - anon (foreground): joins the public Anyone network, publishes a hidden -# service on TARGET_PORT, forwards to 127.0.0.1:TARGET_PORT -# - socat (background): listens on 127.0.0.1:TARGET_PORT and responds with -# "PROBE-OK\n" — the round-trip target reached via .anyone -# -# Environment: -# NICKNAME (default: probe) — informational, used in logs -# TARGET_PORT (default: 9000) — HS forward target inside this container -# HS_SECRET_KEY_B64 (optional) — base64-encoded contents of -# hs_ed25519_secret_key. When -# set, seeded into the HS dir -# so anon reuses the keypair -# (deterministic .anyone address). -set -eu - -NICKNAME=${NICKNAME:-probe} -TARGET_PORT=${TARGET_PORT:-9000} - -mkdir -p /var/lib/anon/hs -chmod 0700 /var/lib/anon/hs - -# Seed the v3 hidden-service keypair if provided. anon detects existing files -# under HiddenServiceDir and reuses them — no regeneration. The secret key MUST -# be mode 0600 or anon refuses to load it. -if [ -n "${HS_SECRET_KEY_B64:-}" ]; then - echo "[probe ${NICKNAME}] seeding hs_ed25519_secret_key from HS_SECRET_KEY_B64" - printf '%s' "${HS_SECRET_KEY_B64}" | base64 -d > /var/lib/anon/hs/hs_ed25519_secret_key - chmod 0600 /var/lib/anon/hs/hs_ed25519_secret_key - # anon regenerates the public key + hostname from the secret on first boot - # if they're absent, so we don't need to seed those. -else - echo "[probe ${NICKNAME}] HS_SECRET_KEY_B64 unset — anon will generate fresh keys (hostname will be unknown without lease shell)" -fi - -# Generate torrc — public Anyone network, single hidden service. -RC=/etc/anon/torrc -cat > "$RC" <