-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompose.fork.yml
More file actions
205 lines (194 loc) · 7.11 KB
/
Copy pathcompose.fork.yml
File metadata and controls
205 lines (194 loc) · 7.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# The fork rehearsal, as containers.
#
# A full run of the paid loop against a forked Base mainnet: a real 402, a real
# EIP-3009 signature, a real USDC settlement, and attestations written against
# the schema that is actually registered on Base. Nothing costs anything and
# nothing reaches mainnet. See scripts/fork/README.md.
#
# Its own postgres, deliberately. The demo stack's database holds a corpus that
# took hours to crawl, and attestations written here have UIDs that exist only
# on the fork; keeping them in separate containers means neither can be written
# into the other by a mistyped variable.
#
# Its own project name, and not a detail. Compose derives the project from the
# directory, so without this the fork's `postgres` and the demo stack's are the
# same container: bringing this up remounts wuzzy-postgres-1 onto another volume
# and the 4555-page corpus silently goes missing while its data sits untouched
# on a volume nothing is using.
name: wuzzy-fork
# Engine-agnostic: podman compose and docker compose alike.
services:
# Anything after block 51018240 has the attestation schema, so the fork
# inherits it rather than registering a local invention.
chain:
image: ghcr.io/foundry-rs/foundry:latest
command: ["anvil --fork-url ${FORK_RPC_URL:-https://mainnet.base.org} --host 0.0.0.0"]
ports:
- "127.0.0.1:8545:8545"
# Forking pulls state from upstream before it answers, which takes long
# enough that anything dialling it on startup loses the race and exits.
healthcheck:
test: ["CMD-SHELL", "cast block-number --rpc-url http://127.0.0.1:8545 > /dev/null"]
interval: 5s
timeout: 10s
retries: 30
start_period: 20s
postgres:
image: docker.io/pgvector/pgvector:pg16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
# The hnsw index cannot be built inside the 64MB default, and the failure is
# silent: the restore "succeeds" without the index and search is slow forever.
shm_size: 1gb
ports:
- "127.0.0.1:5435:5432"
volumes:
- fork-pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 10
redis:
image: docker.io/library/redis:7-alpine
command: ["redis-server", "--save", "", "--appendonly", "no"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
# Deterministic vectors, no API key. Retrieval runs lexical here, but the
# worker still embeds what it crawls, because nothing else writes chunks.
embeddings:
image: docker.io/oven/bun:1-alpine
working_dir: /demo
volumes:
- ./scripts/demo:/demo:z,ro
command: ["bun", "/demo/stub-embeddings.ts"]
expose:
- "39500"
# NOT the demo stack's mock, which approves everything. This one verifies the
# signature against real USDC, checks the payer's balance and whether the
# authorization is spent, and settles by submitting transferWithAuthorization.
# It runs from the backend image because it needs ethers.
facilitator:
build:
context: .
dockerfile: apps/backend/Dockerfile
volumes:
- ./scripts/fork:/app/scripts/fork:z,ro
environment:
FORK_RPC_URL: http://chain:8545
command: ["bun", "scripts/fork/facilitator.ts"]
# It runs from the backend image but is not the backend: the inherited
# check polls /healthz on a port this does not serve, and would report a
# working facilitator as unhealthy forever.
healthcheck:
disable: true
expose:
- "39601"
depends_on:
chain:
condition: service_healthy
api:
build:
context: .
dockerfile: apps/backend/Dockerfile
env_file: scripts/fork/local/fork.env
environment:
DB_MIGRATIONS_RUN: "true"
PORT: "3000"
POSTGRES_HOST: postgres
REDIS_HOST: redis
EMBEDDING_BASE_URL: http://embeddings:39500
X402_FACILITATOR_URL: http://facilitator:39601
BASE_RPC_URL: http://chain:8545
ports:
- "127.0.0.1:3002:3000"
depends_on:
chain:
condition: service_healthy
postgres:
condition: service_healthy
redis:
condition: service_healthy
# Crawls what the API was paid for, then embeds it, so an index is searchable
# when its crawl finishes. `--scale worker=3` is how a slow crawl goes faster.
worker:
build:
context: .
dockerfile: apps/backend/Dockerfile
env_file: scripts/fork/local/fork.env
environment:
POSTGRES_HOST: postgres
REDIS_HOST: redis
EMBEDDING_BASE_URL: http://embeddings:39500
BASE_RPC_URL: http://chain:8545
command: ["bun", "apps/backend/src/worker.ts"]
# The image healthchecks an HTTP port the worker does not listen on, so
# without this it reports unhealthy for its whole life while crawling
# perfectly well.
healthcheck:
disable: true
depends_on:
# The API owns the migration, and the worker's first query is against a
# table it creates. Waiting on postgres alone is a race that only shows
# itself on a fresh volume, as an undefined-table crash on boot.
api:
condition: service_healthy
redis:
condition: service_healthy
# Writes the onchain receipts, and the only process holding a funded key.
#
# Exactly one, never scaled: every batch is a transaction from a single
# account, so a second attester would build against the same nonce and throw
# away a transaction it had already paid for. More throughput is a bigger
# multiAttest batch.
attester:
build:
context: .
dockerfile: apps/backend/Dockerfile
command: ["bun", "apps/backend/src/attester.ts"]
env_file: scripts/fork/local/fork.env
environment:
POSTGRES_HOST: postgres
REDIS_HOST: redis
BASE_RPC_URL: http://chain:8545
# Anvil's first account: published, well known, funded only on a fork.
ATTESTER_PRIVATE_KEY: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
healthcheck:
disable: true
depends_on:
chain:
condition: service_healthy
api:
condition: service_healthy
redis:
condition: service_healthy
# This fork's own admin. Bound to loopback, and on a different port from the
# demo stack's, so the two corpora cannot be mistaken for each other.
admin:
build:
context: .
dockerfile: apps/admin/Dockerfile
environment:
BACKEND_ORIGIN: http://api:3000
ports:
- "127.0.0.1:8082:80"
depends_on:
- api
# The docs the briefing tells the client to read, from the sibling wuzzy-docs
# checkout. Build them first: they must be the built output, because the dev
# server renders on the client and an agent fetching a page over HTTP would
# get an empty shell and conclude there is no documentation.
docs:
image: docker.io/library/nginx:alpine
volumes:
- ${WUZZY_DOCS_DIR:-../wuzzy-docs/doc_build}:/usr/share/nginx/html:z,ro
- ./scripts/fork/docs-nginx.conf:/etc/nginx/conf.d/default.conf:z,ro
ports:
- "127.0.0.1:4000:80"
volumes:
fork-pgdata: