#!/bin/bash # Compose stack, Caddy snippet and readiness polling for the dice solver. # # Sourced by setup_kcd2_dice_solver.sh; split out to keep it under the 250-line cap. # Sourced rather than run, so it inherits the caller's strict mode and # the helper functions and variables defined above the source line. # --- Phase 3: the static-serve stack ---------------------------------------- write_serve_stack() { ensure_dir "$DICE_DATA_DIR" cat >"$DICE_COMPOSE" <"$DICE_INNER_CADDY" <HTTPS redirect listener on :80. Under # host networking that binds *:80 next to the edge, and SO_REUSEPORT then splits # inbound HTTP between the two, which would intermittently break ACME # challenges for every domain on this host. # # The site address is port-only and the loopback restriction is done with # "bind". These are two different things and using the wrong one breaks in a way # that looks like it works: # # A site address of "http://127.0.0.1:${DICE_PORT}" sets a host MATCHER of # 127.0.0.1. ${CADDY_CONTAINER} forwards the original "Host: # ${DICE_DOMAIN}" header, which then matches no route, and Caddy answers 200 # with an empty body. A local health check passes (curl sends Host: # 127.0.0.1) while every real request serves a blank page. # # ":${DICE_PORT}" matches any Host, and "bind 127.0.0.1" restricts the # listener itself — which the address never did. Without bind, host # networking would expose this unfronted server to the whole LAN. { admin off auto_https off } :${DICE_PORT} { bind 127.0.0.1 root * /srv # Single-page bundle: unknown paths should serve the app, not 404. try_files {path} /index.html file_server encode gzip } EOF log_ok "Wrote ${DICE_COMPOSE} and ${DICE_INNER_CADDY}." } # --- Phase 4: front it on the public domain --------------------------------- write_snippet() { cat >"$DICE_SNIPPET" </dev/null || echo 0)" if [[ ${size:-0} -gt 0 ]]; then return 0 fi sleep 1 done return 1 }