#!/bin/sh # Run the whole site locally, in the same shape as production. # # tools/dev.sh build both products and serve on :8080 # tools/dev.sh --no-build use whatever is already in bin/ # # Why this exists: catcrafts-server serves PAGES only. Static assets — the wasm # module, styles.css, the JS bridges — are Caddy's job in production, so running # the server on its own gives you correct HTML with no stylesheet, which looks # broken and isn't. This starts both and puts Caddy in front, so what you see # locally is what the deployed site does, including the reverse-proxy split and # the scoped cross-origin headers. # # Ctrl-C stops both. set -eu BUILD=1 [ "${1:-}" = "--no-build" ] && BUILD=0 PORT="${DEV_PORT:-8080}" BACKEND_PORT="${DEV_BACKEND_PORT:-8081}" WORK="$(mktemp -d)" # Refuse to start if either port is taken. # # Without this, a leftover instance from an earlier run keeps serving: the new # backend fails to bind, the old Caddy carries on proxying to the OLD binary, and # the site looks like the build did not take effect. That has wasted real time # twice — the symptom (stale content) points at the build, not at a process. port_busy() { if command -v ss >/dev/null 2>&1; then ss -ltn 2>/dev/null | grep -qE "[:.]$1 " else curl -s -o /dev/null --max-time 1 "http://127.0.0.1:$1/" 2>/dev/null fi } for _p in "$PORT" "$BACKEND_PORT"; do if port_busy "$_p"; then echo "dev: port $_p is already in use — another instance is probably still running." >&2 echo "dev: find it with: ss -ltnp | grep -E ':(8080|8081) '" >&2 echo "dev: then kill those PIDs, or set DEV_PORT / DEV_BACKEND_PORT." >&2 exit 1 fi done cleanup() { [ -n "${SRV_PID:-}" ] && kill "$SRV_PID" 2>/dev/null || true [ -n "${CADDY_PID:-}" ] && kill "$CADDY_PID" 2>/dev/null || true rm -rf "$WORK" } trap cleanup EXIT INT TERM # Exactly one match or fail loudly. A variant directory name embeds a config # hash, so two matches means the tree holds artifacts from two different # configurations and picking either would be a coin flip — this has caused real # confusion (testing a stale binary and believing the result). onedir() { _m=$(find bin -maxdepth 1 -type d -name "$1" 2>/dev/null | sort) _n=$(printf '%s\n' "$_m" | grep -c . || true) if [ "$_n" -ne 1 ]; then echo "dev: expected exactly one $1 directory under bin/, found $_n" >&2 [ "$_n" -gt 1 ] && printf '%s\n' "$_m" >&2 echo "dev: run 'rm -rf bin' and try again" >&2 exit 1 fi printf '%s' "$_m" } if [ "$BUILD" = 1 ]; then echo "dev: building the server product..." crafter-build --local -- --product=server >"$WORK/build-server.log" 2>&1 \ || { echo "dev: server build failed:" >&2; tail -20 "$WORK/build-server.log" >&2; exit 1; } SRV=$(onedir 'Catcrafts.Server-*') # sitemap.xml and feed.xml are generated from the same route table and Post # model the pages use, and the wasm build copies them into the bundle — so # they have to exist before it runs. "$SRV/catcrafts-server" --sitemap > sitemap.xml "$SRV/catcrafts-server" --feed > feed.xml echo "dev: building the wasm bundle..." crafter-build --local >"$WORK/build-web.log" 2>&1 \ || { echo "dev: wasm build failed:" >&2; tail -20 "$WORK/build-web.log" >&2; exit 1; } fi SRV=$(onedir 'Catcrafts.Server-*') WEB=$(onedir 'Catcrafts.Net-*') # Makes the static shell survive being served at a deep URL. Run unconditionally, # not just after a build: --no-build may be pointing at a bundle someone produced # with a bare crafter-build, and the script is idempotent. ./tools/fix-bundle-depth.sh "$WEB" >/dev/null ABS_WEB="$(cd "$WEB" && pwd)" # Mirrored post media. Absent is fine — the pages render, the media 404s — so # this does not block running the site before fetch-media.sh has been run. mkdir -p media PWD_MEDIA="$(cd media && pwd)" # Mirrors deploy/Caddyfile.example: static assets from disk, everything else # proxied to the backend, cross-origin isolation only on the paths that boot the # wasm module. cat > "$WORK/Caddyfile" <&2 echo "dev: live keys belong in /etc/catcrafts/payments.env on the server." >&2 exit 1 ;; *) echo "dev: MOLLIE_API_KEY is not a test_ or live_ key" >&2; exit 1 ;; esac fi "$SRV/catcrafts-server" --serve "$BACKEND_PORT" \ --orders="$WORK/orders.jsonl" --rail="$RAIL" \ --redirect-base="http://localhost:$PORT" >"$WORK/server.log" 2>&1 & SRV_PID=$! caddy run --config "$WORK/Caddyfile" --adapter caddyfile >"$WORK/caddy.log" 2>&1 & CADDY_PID=$! # Wait for the front door rather than sleeping a fixed amount. i=0 while [ "$i" -lt 100 ]; do curl -s -o /dev/null "http://127.0.0.1:$PORT/api/healthz" 2>/dev/null && break i=$((i + 1)); sleep 0.1 done if [ "$i" -ge 100 ]; then echo "dev: did not come up. server log:" >&2; cat "$WORK/server.log" >&2 echo "dev: caddy log:" >&2; cat "$WORK/caddy.log" >&2 exit 1 fi cat <