-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathservices.sh
More file actions
executable file
·459 lines (414 loc) · 18 KB
/
Copy pathservices.sh
File metadata and controls
executable file
·459 lines (414 loc) · 18 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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
#!/bin/bash
# Manage docker-compose services (hardhat, IPFS, indexer, platform API, etc.)
#
# Usage:
# ./scripts/services.sh --start # Start services (preserves existing data)
# ./scripts/services.sh --stop # Stop services (preserves existing data)
# ./scripts/services.sh --status # Show whether services are running
# ./scripts/services.sh --url # Print the stable local UI URLs for all domains
#
# Note: This script isn't much more than a thin wrapper around
# docker-compose; it's fine to just use docker-compose directly
# if you're comfortable with that. We might be better off just
# documenting the docker-compose commands instead of having this
# script at all.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/timing.sh
. "$SCRIPT_DIR/lib/timing.sh"
DATA_DIR="${COMMONALITY_DATA_DIR:-./data}"
UI_IPFS_ARTIFACT_DIR="./data/ui-ipfs"
cd "$SCRIPT_DIR/.."
# Export UID/GID so docker-compose can run containers as the current user.
# UID is a bash built-in and isn't exported by default; GID has no built-in at all.
export UID
export GID=$(id -g)
docker_compose() {
if command -v docker-compose >/dev/null 2>&1; then
docker-compose "$@"
else
docker compose "$@"
fi
}
show_usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --start Start services (preserves existing data)"
echo " --stop Stop services (preserves existing data)"
echo " --status Show whether services are running"
echo " --url Print the stable local UI URLs for all domains"
echo " --check Fail-fast: env / on-chain / SPA config sync (see check-local-config-sync.sh)"
echo " --help Show this help message"
echo ""
echo "Data is stored in $DATA_DIR/. Use scripts/data.sh to manage it."
echo ""
echo "CauseStarter is a core domain (IPFS gateway + dedicated SPA on :8090)."
echo " Gateway: http://causestarter.localhost:8088/#/"
echo " App: http://localhost:8090/ (cause-assist on :3002)"
echo " Rebuild: ./scripts/deploy-causestarter.sh"
echo ""
echo "LOCAL_UI_DOMAINS (temporary): default is causestarter only."
echo " Restore every IPFS SPA: LOCAL_UI_DOMAINS=all $0 --start"
echo " See workflow/local-development.md"
}
# Which UI IPFS publishers to run on --start. Default: CauseStarter only.
# LOCAL_UI_DOMAINS=all restores the eight legacy domains + CauseStarter.
# Comma/space list also works, e.g. LOCAL_UI_DOMAINS=causestarter,tally
load_local_ui_domains_from_env_file() {
if [ -n "${LOCAL_UI_DOMAINS:-}" ] || [ ! -f .env ]; then
return 0
fi
local line value
line="$(grep -E '^[[:space:]]*LOCAL_UI_DOMAINS=' .env | tail -n 1 || true)"
[ -n "$line" ] || return 0
value="${line#*=}"
value="${value%\"}"
value="${value#\"}"
value="${value%\'}"
value="${value#\'}"
export LOCAL_UI_DOMAINS="$value"
}
local_publish_domains() {
load_local_ui_domains_from_env_file
node "$SCRIPT_DIR/ui-domains.mjs" list-local-publish
}
resolve_path_allow_missing() {
local path="$1"
local dir
local base
dir="$(dirname "$path")"
base="$(basename "$path")"
echo "$(cd "$dir" && pwd)/$base"
}
ponder_data_exists() {
[ -d "$DATA_DIR/ponder" ] && [ -n "$(find "$DATA_DIR/ponder" -mindepth 1 -print -quit 2>/dev/null)" ]
}
clear_stale_ponder_for_fresh_chain() {
if [ ! -f "$DATA_DIR/hardhat/state.json" ] && ponder_data_exists; then
echo "Warning: found existing Ponder indexer data but no saved local chain state."
echo "This usually means the chain was reset without clearing the indexer DB; clearing $DATA_DIR/ponder to avoid a blank/stale UI."
rm -rf "$DATA_DIR/ponder"
fi
}
check_existing_containers() {
local abs_data_dir
abs_data_dir="$(resolve_path_allow_missing "$DATA_DIR")"
# If any managed containers are unhealthy, stop and tell the user rather
# than letting compose fail with a cryptic dependency error.
local unhealthy
unhealthy=$(docker_compose ps --format '{{.Name}} {{.Health}}' 2>/dev/null \
| awk '$2 == "unhealthy" {print $1}' || true)
if [ -n "$unhealthy" ]; then
echo "Error: the following containers are running but unhealthy:"
echo "$unhealthy" | sed 's/^/ /'
echo ""
echo "This usually means a previous run left containers in a bad state"
echo "(e.g. the data directory was wiped while containers were still running)."
echo "Run './scripts/services.sh --stop' first, then try again."
exit 1
fi
# If the IPFS container is running but its repo is missing, docker compose
# will not restart it (it's already 'running'), so ipfs init never fires
# and the health check keeps failing.
if docker inspect commonality-ipfs &>/dev/null 2>&1; then
local ipfs_state
ipfs_state=$(docker inspect commonality-ipfs --format '{{.State.Status}}' 2>/dev/null || true)
if [ "$ipfs_state" = "running" ] && [ ! -f "$abs_data_dir/ipfs/config" ]; then
echo "Error: the IPFS container is running but $abs_data_dir/ipfs/config does not exist."
echo ""
echo "The data directory may have been wiped while the container was still running."
echo "Run './scripts/services.sh --stop' first, then try again."
exit 1
fi
fi
}
print_spa_urls() {
local found=false
local domain
for domain in $(local_publish_domains); do
local stable_file="$UI_IPFS_ARTIFACT_DIR/$domain/stable-url.txt"
local spa_file="$UI_IPFS_ARTIFACT_DIR/$domain/spa-url.txt"
if [ -f "$stable_file" ]; then
printf " %-22s %s\n" "$domain" "$(cat "$stable_file")"
found=true
elif [ -f "$spa_file" ]; then
printf " %-22s %s\n" "$domain" "$(cat "$spa_file")"
found=true
fi
done
if ! $found; then
echo "Error: no SPA URL artifacts found in $UI_IPFS_ARTIFACT_DIR/." >&2
echo "Run './scripts/services.sh --start' first." >&2
return 1
fi
}
wait_for_spa_gateway() {
echo "Waiting for the local IPFS gateway to serve all domain SPAs..."
local max_attempts=30
for domain in $(local_publish_domains); do
local spa_file="$UI_IPFS_ARTIFACT_DIR/$domain/spa-url.txt"
[ -f "$spa_file" ] || continue
local spa_url entrypoint_url attempt
spa_url=$(cat "$spa_file")
entrypoint_url="${spa_url%#*}index.html"
attempt=1
while [ "$attempt" -le "$max_attempts" ]; do
if curl --silent --show-error --fail "$entrypoint_url" >/dev/null; then
break
fi
sleep 1
attempt=$((attempt + 1))
done
if [ "$attempt" -gt "$max_attempts" ]; then
echo "Error: $domain SPA was published but never became reachable at $entrypoint_url" >&2
return 1
fi
done
}
wait_for_ui_ipfs_publisher() {
local domain="$1"
local service_name="ui-ipfs-publisher-${domain}"
local container_name="commonality-${service_name}"
while true; do
local status
status=$(docker inspect "$container_name" --format '{{.State.Status}}' 2>/dev/null || true)
case "$status" in
created|running|restarting|"")
sleep 2
;;
exited)
local exit_code
exit_code=$(docker inspect "$container_name" --format '{{.State.ExitCode}}')
if [ "$exit_code" -ne 0 ]; then
echo "Error: UI IPFS publish failed for domain: $domain"
echo "Showing recent logs from $container_name:"
docker_compose logs --tail=200 "$service_name" || true
exit 1
fi
echo " $domain: published"
return 0
;;
*)
echo "Warning: unexpected $container_name state: $status"
sleep 2
;;
esac
done
}
publish_ui_domains_to_ipfs() {
echo "Publishing domain UI builds to IPFS one at a time..."
echo " LOCAL_UI_DOMAINS=$(local_publish_domains | tr '\n' ' ')"
# Building all domain SPAs concurrently can exhaust Docker Desktop memory and
# kill Vite with exit code 137. Build/publish sequentially for reliability.
local domain
for domain in $(local_publish_domains); do
echo " $domain: building and publishing..."
docker_compose up -d --no-deps --force-recreate "ui-ipfs-publisher-${domain}"
wait_for_ui_ipfs_publisher "$domain"
done
wait_for_spa_gateway
echo ""
echo "All domains published to IPFS. Stable local UI URLs:"
print_spa_urls
}
wait_for_local_ui_gateway() {
echo "Waiting for the stable local UI gateway..."
local max_attempts=30
local attempt=1
while [ "$attempt" -le "$max_attempts" ]; do
if curl --silent --show-error --fail "http://localhost:8088/health" >/dev/null; then
return 0
fi
sleep 1
attempt=$((attempt + 1))
done
echo "Error: stable local UI gateway did not become reachable at http://localhost:8088/health" >&2
return 1
}
load_env_file_if_present() {
local file="$1"
if [ ! -f "$file" ]; then
return 0
fi
# shellcheck disable=SC1090
set -a
# shellcheck disable=SC1091
. "$file"
set +a
}
map_causestarter_contract_env() {
# Map root .env / hardhat deploy names onto VITE_* keys for CauseStarter runtime config.
export VITE_BELIEFS_CONTRACT_ADDRESS="${VITE_BELIEFS_CONTRACT_ADDRESS:-${BELIEFS_CONTRACT_ADDRESS:-}}"
export VITE_IMPLICATIONS_CONTRACT_ADDRESS="${VITE_IMPLICATIONS_CONTRACT_ADDRESS:-${IMPLICATIONS_CONTRACT_ADDRESS:-}}"
export VITE_MUTABLE_REF_UPDATER_CONTRACT_ADDRESS="${VITE_MUTABLE_REF_UPDATER_CONTRACT_ADDRESS:-${MUTABLE_REF_UPDATER_CONTRACT_ADDRESS:-${MUTABLE_REF_UPDATER_ADDRESS:-}}}"
export VITE_DELEGATABLE_NOTES_CONTRACT_ADDRESS="${VITE_DELEGATABLE_NOTES_CONTRACT_ADDRESS:-${DELEGATABLE_NOTES_CONTRACT_ADDRESS:-${DELEGATABLE_NOTES_ADDRESS:-}}}"
export VITE_NOTE_INTENT_CONTRACT_ADDRESS="${VITE_NOTE_INTENT_CONTRACT_ADDRESS:-${NOTE_INTENT_ADDRESS:-}}"
export VITE_ASSURANCE_CONTRACT_FACTORY_ADDRESS="${VITE_ASSURANCE_CONTRACT_FACTORY_ADDRESS:-${ASSURANCE_CONTRACT_FACTORY_ADDRESS:-}}"
export VITE_ERC1155_FACTORY_ADDRESS="${VITE_ERC1155_FACTORY_ADDRESS:-${ERC1155_FACTORY_ADDRESS:-}}"
export VITE_ALIGNMENT_ATTESTATIONS_CONTRACT_ADDRESS="${VITE_ALIGNMENT_ATTESTATIONS_CONTRACT_ADDRESS:-${ALIGNMENT_ATTESTATIONS_CONTRACT_ADDRESS:-${ALIGNMENT_ATTESTATIONS_ADDRESS:-}}}"
export VITE_TRUST_REGISTRY_CONTRACT_ADDRESS="${VITE_TRUST_REGISTRY_CONTRACT_ADDRESS:-${TRUST_REGISTRY_ADDRESS:-}}"
export VITE_DEFAULT_ALIGNMENT_TRUST_ROOT="${VITE_DEFAULT_ALIGNMENT_TRUST_ROOT:-0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f}"
export VITE_NUDGE_PUBLICATIONS_CONTRACT_ADDRESS="${VITE_NUDGE_PUBLICATIONS_CONTRACT_ADDRESS:-${NUDGE_PUBLICATIONS_CONTRACT_ADDRESS:-}}"
export VITE_PUBLISHED_DATA_CONTRACT_ADDRESS="${VITE_PUBLISHED_DATA_CONTRACT_ADDRESS:-${PUBLISHED_DATA_CONTRACT_ADDRESS:-}}"
export VITE_PROJECT_FACTORY_CONTRACT_ADDRESS="${VITE_PROJECT_FACTORY_CONTRACT_ADDRESS:-${PROJECT_FACTORY_ADDRESS:-}}"
export VITE_PAYMENT_TOKEN_ADDRESS="${VITE_PAYMENT_TOKEN_ADDRESS:-${PAYMENT_TOKEN_ADDRESS:-}}"
export VITE_CONTENT_REGISTRY_ADDRESS="${VITE_CONTENT_REGISTRY_ADDRESS:-${CONTENT_REGISTRY_ADDRESS:-}}"
export VITE_CHANNEL_REGISTRY_ADDRESS="${VITE_CHANNEL_REGISTRY_ADDRESS:-${CHANNEL_REGISTRY_ADDRESS:-}}"
export VITE_CHANNEL_ESCROW_ADDRESS="${VITE_CHANNEL_ESCROW_ADDRESS:-${CHANNEL_ESCROW_ADDRESS:-}}"
export VITE_CREATOR_CONTRACT_FACTORY_ADDRESS="${VITE_CREATOR_CONTRACT_FACTORY_ADDRESS:-${CREATOR_CONTRACT_FACTORY_ADDRESS:-}}"
export VITE_CHAIN_ID="${VITE_CHAIN_ID:-${CHAIN_ID:-31337}}"
export VITE_ETH_RPC_URL="${VITE_ETH_RPC_URL:-${ETH_RPC_URL:-http://127.0.0.1:8545}}"
export VITE_PLATFORM_API_URL="${VITE_PLATFORM_API_URL:-http://localhost:3001}"
export COMMONALITY_ENVIRONMENT="${COMMONALITY_ENVIRONMENT:-local}"
export VITE_EVENT_CACHE_URL="${VITE_EVENT_CACHE_URL:-}"
}
start_services() {
local -a core_services=(
hardhat-node
hardhat-deploy
ipfs
published-data-ipfs-mirror
indexer
platform-api-service
)
local -a buildable_services=(
hardhat-deploy
published-data-ipfs-mirror
indexer
platform-api-service
cause-assist
alignment-trust-bootstrap
causestarter
christian-bridge-creator
service-host-attesters
)
local domain
for domain in $(local_publish_domains); do
buildable_services+=("ui-ipfs-publisher-${domain}")
done
local -a services_to_build=()
timing_begin
"$SCRIPT_DIR/check-prerequisites.sh"
check_existing_containers
clear_stale_ponder_for_fresh_chain
echo "[$(date +%T)] Starting services with data directory: $DATA_DIR"
# Pre-create data directories owned by the current user so containers
# don't create them as root.
mkdir -p "$DATA_DIR/hardhat" "$DATA_DIR/ipfs" "$DATA_DIR/published-data-ipfs-mirror" "$DATA_DIR/ponder" \
"$DATA_DIR/alignment-trust-bootstrap"
for domain in $(local_publish_domains); do
mkdir -p "$UI_IPFS_ARTIFACT_DIR/$domain"
done
# The UI publisher bind-mounts these files so it reads contract addresses
# written by hardhat-deploy at runtime instead of stale values baked into
# the Docker image. Ensure clean checkouts have files to mount.
mkdir -p ui causestarter
# Create empty env files only when missing. `touch` on existing files updates
# mtime and forces Vite (npm run causestarter:dev) to full-reload the SPA.
[ -f .env ] || touch .env
[ -f ui/.env ] || touch ui/.env
[ -f causestarter/.env ] || touch causestarter/.env
services_to_build=()
while IFS= read -r line; do
services_to_build+=("$line")
done < <(node "$SCRIPT_DIR/docker-build-plan.mjs" list "${buildable_services[@]}")
if [ "${#services_to_build[@]}" -gt 0 ]; then
echo "[$(date +%T)] Rebuilding Docker images whose declared inputs changed:"
printf ' %s\n' "${services_to_build[@]}"
docker_compose build "${services_to_build[@]}"
node "$SCRIPT_DIR/docker-build-plan.mjs" record "${services_to_build[@]}"
echo "[$(date +%T)] Docker image rebuild finished."
else
echo "[$(date +%T)] Reusing existing Docker images; no declared build inputs changed."
fi
timing_mark docker_images
echo "[$(date +%T)] Starting core services (hardhat, ipfs, indexer, api)..."
docker_compose up -d --remove-orphans "${core_services[@]}"
timing_mark core_services
echo "[$(date +%T)] Publishing UI domains to IPFS..."
publish_ui_domains_to_ipfs
docker_compose up -d --no-deps --force-recreate ui-local-gateway
wait_for_local_ui_gateway
timing_mark ui_ipfs
# CauseStarter SPA + cause-assist (core founder surface on :8090).
# localhost.env matches hardhat-deploy --network localhost; live .env files win.
load_env_file_if_present deployments/localhost.env
load_env_file_if_present .env
load_env_file_if_present ui/.env
load_env_file_if_present causestarter/.env
map_causestarter_contract_env
# service-host-attesters must start after the env files above are sourced:
# it needs IMPLICATIONS_CONTRACT_ADDRESS from deployments/localhost.env, and
# compose reads that from this shell. The bridge-cluster editor's "submit
# pairs to attester" step talks to it on :3006.
echo "[$(date +%T)] Starting CauseStarter SPA, cause-assist, attesters, workers..."
docker_compose up -d --force-recreate \
cause-assist alignment-trust-bootstrap causestarter christian-bridge-creator \
service-host-attesters
timing_mark causestarter
# Compose auto-loads the root .env, so once generate-wallets.mjs has run the
# services sign with generated keys that hold no ETH on a fresh local chain.
# Without this they boot "degraded" and every on-chain write fails.
echo "Funding local service signer wallets..."
if ! node "$SCRIPT_DIR/fund-local-service-wallets.mjs"; then
echo "Warning: could not fund service signer wallets. Attesters may report"
echo "'degraded' and fail on-chain writes until you run:"
echo " node scripts/fund-local-service-wallets.mjs"
fi
echo "Recording local Hardhat-account trust (CauseStarter starter network)..."
if ! node "$SCRIPT_DIR/seed-local-alignment-trust.mjs"; then
echo "Warning: could not seed local alignment trust. CauseStarter project lists may stay gated until you run:"
echo " node scripts/seed-local-alignment-trust.mjs"
fi
timing_mark alignment_trust
echo ""
echo "Services started. Use 'docker compose logs -f' to view logs."
echo "Platform API service health: http://localhost:3001/health"
echo "Attesters (implication + content) health: http://localhost:3006/health"
echo "CauseStarter: http://localhost:${CAUSESTARTER_PORT:-8090}/ (gateway: http://causestarter.localhost:8088/#/)"
# Fail fast on env / on-chain / SPA config drift (PublishedData missing, stale ProjectFactory ABI, …).
echo ""
if ! "$SCRIPT_DIR/check-local-config-sync.sh"; then
echo ""
echo "Error: local config sync check failed after start."
echo "Services are up, but contract addresses or ABIs are inconsistent — fix before using the stack."
exit 1
fi
timing_mark config_sync
timing_summary
}
stop_services() {
echo "Stopping services..."
docker_compose down --remove-orphans
echo "Services stopped (data preserved in $DATA_DIR)."
}
show_status() {
docker_compose ps
}
case "${1:-}" in
--start)
start_services
;;
--stop)
stop_services
;;
--status)
show_status
;;
--url)
print_spa_urls
;;
--check)
exec "$SCRIPT_DIR/check-local-config-sync.sh"
;;
--help|-h|"")
show_usage
;;
*)
echo "Error: Unknown option: $1"
echo ""
show_usage
exit 1
;;
esac