This commit is contained in:
parent
fb2f6079cc
commit
934c94cb5c
50 changed files with 10464 additions and 758 deletions
204
tools/dev.sh
Executable file
204
tools/dev.sh
Executable file
|
|
@ -0,0 +1,204 @@
|
|||
#!/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" <<EOF
|
||||
:$PORT {
|
||||
root * $ABS_WEB
|
||||
encode zstd gzip
|
||||
|
||||
@isolated path /demos/* /catcrafts*.wasm /runtime.js /dom-env.js /dom-webgpu.js /catcrafts-head.js /files.json /variants.json /*.wgsl
|
||||
header @isolated {
|
||||
Cross-Origin-Opener-Policy "same-origin"
|
||||
Cross-Origin-Embedder-Policy "require-corp"
|
||||
Cross-Origin-Resource-Policy "same-origin"
|
||||
}
|
||||
|
||||
@static path /catcrafts*.wasm /runtime.js /dom-env.js /dom-webgpu.js /catcrafts-head.js /files.json /variants.json /styles.css /favicon.svg /robots.txt /*.wgsl /*.jpg /posts.json /rates.json
|
||||
handle @static {
|
||||
header Cache-Control "no-store"
|
||||
file_server
|
||||
}
|
||||
|
||||
handle_path /media/* {
|
||||
root * $PWD_MEDIA
|
||||
header Cache-Control "no-store"
|
||||
file_server
|
||||
}
|
||||
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:$BACKEND_PORT
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Rail selection for dev:
|
||||
# * a repo-root .env (gitignored, never committed) is sourced if present —
|
||||
# put MOLLIE_API_KEY=test_… there to point dev at Mollie's real test mode;
|
||||
# * DEV_RAIL=fake|mollie overrides the automatic choice;
|
||||
# * default with no key is the fake rail: full order lifecycle, no network.
|
||||
# "Pay" an order with: touch $WORK/orders.jsonl.fake-paid
|
||||
#
|
||||
# A live_ key is refused outright. Dev creates throwaway orders; pointing them
|
||||
# at real money collection is never what anyone meant.
|
||||
if [ -f .env ]; then
|
||||
set -a; . ./.env; set +a
|
||||
fi
|
||||
RAIL="${DEV_RAIL:-}"
|
||||
if [ -z "$RAIL" ]; then
|
||||
RAIL=fake
|
||||
[ -n "${MOLLIE_API_KEY:-}" ] && RAIL=mollie
|
||||
fi
|
||||
if [ "$RAIL" = mollie ]; then
|
||||
case "${MOLLIE_API_KEY:-}" in
|
||||
test_*) echo "dev: payments via Mollie TEST mode" ;;
|
||||
live_*) echo "dev: refusing to run dev against a LIVE Mollie key." >&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 <<EOF
|
||||
|
||||
catcrafts.net is running: http://localhost:$PORT
|
||||
|
||||
/ home
|
||||
/shop product list
|
||||
/shop/fp6-pmos product page + checkout (fake payment rail)
|
||||
/projects the Crafter suite
|
||||
/posts fediverse posts
|
||||
/demos demo list; /demos/raytracer loads the wasm
|
||||
/legal/privacy privacy notice
|
||||
/feed.xml Atom feed
|
||||
/media/* mirrored post media (run tools/fetch-media.sh to populate)
|
||||
|
||||
Orders from this session go to a temp file and are discarded on exit.
|
||||
Payment rail: $RAIL$([ "$RAIL" = fake ] && printf '%s' " — simulate a customer paying with:
|
||||
touch $WORK/orders.jsonl.fake-paid")
|
||||
Ctrl-C to stop.
|
||||
|
||||
EOF
|
||||
|
||||
# Surface backend output as it happens — this is where a render error shows up.
|
||||
tail -f "$WORK/server.log" &
|
||||
wait "$SRV_PID"
|
||||
Loading…
Reference in a new issue