diff --git a/scripts/check-phase14.3.sh b/scripts/check-phase14.3.sh index c2d1eef..01f9c65 100755 --- a/scripts/check-phase14.3.sh +++ b/scripts/check-phase14.3.sh @@ -7,6 +7,22 @@ # its own compose.yaml and serves the starter route with no edits. It ends # with "Phase 14.3 generator check passed". # +# --acceptance proves D-20 before the release tag exists: in a bare +# node:24-bookworm container it installs jq, the Docker CLI and compose +# plugin from Docker's signed apt repository and Go 1.27.0 from go.dev +# (sha256 checked), pipes the committed modules/sunrise/new.sh into +# `bash -s -- demo` with SUMMERCMS_REPO= and SUMMERCMS_REF=HEAD, +# then runs `docker compose up -d --wait && summer build && summer migrate && +# summer serve` in sm-demo-app and requires the starter route. The container +# drives the host Docker daemon through its socket, so it runs with +# --network host under a unique compose project that is removed afterwards. +# It ends with "Phase 14.3 acceptance passed". +# +# --acceptance-production runs the same chain with +# `curl -fsSL https://summercms.io/new | bash -s -- demo` and no SUMMERCMS_ +# overrides, after the release is tagged and the site redeployed. It ends +# with "Phase 14.3 production acceptance passed". +# # Needs Docker, the postgres:16 image (pulled when missing) and free host # ports 54320 and 8080. Every failure exits non-zero. # @@ -140,10 +156,152 @@ stage_generator() { echo "Phase 14.3 generator check passed" } +# acceptance_inner runs inside node:24-bookworm as root. ACC_MODE is local +# or production; ACC_REF is the framework commit for local runs. +read -r -d '' acceptance_inner <<'INNER' || true +export DEBIAN_FRONTEND=noninteractive +run() { + echo "+ $*" + "$@" +} + +echo "== apt: ca-certificates curl gnupg jq" +apt-get update -qq +apt-get install -y -qq ca-certificates curl gnupg jq >/dev/null + +echo "== Docker CLI and compose plugin from Docker's apt repository" +install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc +chmod a+r /etc/apt/keyrings/docker.asc +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" >/etc/apt/sources.list.d/docker.list +apt-get update -qq +apt-get install -y -qq docker-ce-cli docker-compose-plugin >/dev/null +docker --version +docker compose version + +echo "== Go 1.27.0 from go.dev, sha256 checked" +tarball=go1.27.0.linux-amd64.tar.gz +sha="$(curl -fsSL 'https://go.dev/dl/?mode=json&include=all' | + jq -r --arg f "$tarball" '.[] | select(.version == "go1.27.0") | .files[] | select(.filename == $f) | .sha256')" +[[ $sha =~ ^[0-9a-f]{64}$ ]] || { echo "no sha256 for $tarball on go.dev" >&2; exit 1; } +curl -fsSL -o "/tmp/$tarball" "https://go.dev/dl/$tarball" +(cd /tmp && echo "$sha $tarball" | sha256sum -c -) +tar -C /usr/local -xzf "/tmp/$tarball" +export PATH="/usr/local/go/bin:$PATH" +go version + +# Throwaway root container cloning a host-owned checkout. +git config --global --add safe.directory '*' + +mkdir /work +cd /work +server_pid="" +cleanup() { + local rc=$? + if [ -n "$server_pid" ]; then + kill "$server_pid" 2>/dev/null || true + wait "$server_pid" 2>/dev/null || true + fi + if [ -f /work/sm-demo-app/compose.yaml ]; then + (cd /work/sm-demo-app && docker compose down -v --remove-orphans >/dev/null 2>&1) || true + fi + exit "$rc" +} +trap cleanup EXIT + +if [ "$ACC_MODE" = production ]; then + echo "+ curl -fsSL https://summercms.io/new | bash -s -- demo" + curl -fsSL https://summercms.io/new | bash -s -- demo 2>&1 | tee /tmp/new.out +else + echo "+ curl -fsSL file:///fw/modules/sunrise/new.sh | SUMMERCMS_REPO=/fw SUMMERCMS_REF=$ACC_REF bash -s -- demo" + curl -fsSL file:///fw/modules/sunrise/new.sh | SUMMERCMS_REPO=/fw SUMMERCMS_REF="$ACC_REF" bash -s -- demo 2>&1 | tee /tmp/new.out +fi +grep -qx ' cd /work/sm-demo-app' /tmp/new.out || { echo "the script did not print cd /work/sm-demo-app" >&2; exit 1; } +grep -qF 'export PATH="/root/go/bin:$PATH"' /tmp/new.out || { echo "the script did not print the PATH hint" >&2; exit 1; } +export PATH="$(go env GOPATH)/bin:$PATH" + +echo "== D-20 chain" +cd sm-demo-app +run docker compose up -d --wait +run summer build +run summer migrate +echo "+ summer serve &" +summer serve >/tmp/serve.log 2>&1 & +server_pid=$! +body="" +for ((i = 0; i < 60; i++)); do + if body="$(curl -fsS http://127.0.0.1:8080/ 2>/dev/null)"; then + break + fi + kill -0 "$server_pid" 2>/dev/null || { cat /tmp/serve.log >&2; echo "summer serve exited" >&2; exit 1; } + sleep 1 +done +[ -n "$body" ] || { cat /tmp/serve.log >&2; echo "GET / did not answer within 60s" >&2; exit 1; } +echo "GET http://127.0.0.1:8080/ -> $body" +case "$body" in +*'"plugin":"golem15.demo"'*) ;; +*) echo "GET / body lacks \"plugin\":\"golem15.demo\"" >&2; exit 1 ;; +esac +INNER + +# stage_acceptance runs acceptance_inner in node:24-bookworm. $1 is local +# (committed script and repository at HEAD) or production. +stage_acceptance() { + local mode="$1" + command -v docker >/dev/null || fail "docker is not installed" + docker info >/dev/null 2>&1 || fail "the Docker daemon is not reachable (docker info failed)" + port_free 54320 || fail "127.0.0.1:54320 is in use; the generated compose.yaml publishes PostgreSQL there" + port_free 8080 || fail "127.0.0.1:8080 is in use; summer serve listens there" + + local ref="" mounts=() + if [ "$mode" = local ]; then + git -C "$ROOT" ls-files --error-unmatch modules/sunrise/new.sh >/dev/null 2>&1 || + fail "modules/sunrise/new.sh is not committed; the acceptance run clones HEAD" + git -C "$ROOT" diff --quiet HEAD -- modules/sunrise/new.sh || + fail "modules/sunrise/new.sh differs from HEAD; commit it first (what is tested must be committed)" + ref="$(git -C "$ROOT" rev-parse HEAD)" + mounts=(-v "$ROOT:/fw:ro") + fi + + echo "== docker pull node:24-bookworm" + docker pull -q node:24-bookworm >/dev/null + + # Globals: the EXIT trap runs after this function returns. + acc_project="phase143acc-$$-$(date +%s)" + acc_runner="$acc_project-runner" + acc_cleanup() { + local rc=$? + docker rm -f "$acc_runner" >/dev/null 2>&1 || true + local ids + ids="$(docker ps -aq --filter "label=com.docker.compose.project=$acc_project")" + [ -z "$ids" ] || docker rm -f $ids >/dev/null 2>&1 || true + ids="$(docker volume ls -q --filter "label=com.docker.compose.project=$acc_project")" + [ -z "$ids" ] || docker volume rm -f $ids >/dev/null 2>&1 || true + ids="$(docker network ls -q --filter "label=com.docker.compose.project=$acc_project")" + [ -z "$ids" ] || docker network rm $ids >/dev/null 2>&1 || true + exit "$rc" + } + trap acc_cleanup EXIT + + echo "== node:24-bookworm ($mode, compose project $acc_project)" + docker run --rm --name "$acc_runner" --network host \ + -v /var/run/docker.sock:/var/run/docker.sock "${mounts[@]}" \ + -e COMPOSE_PROJECT_NAME="$acc_project" -e ACC_MODE="$mode" -e ACC_REF="$ref" \ + node:24-bookworm bash -euo pipefail -c "$acceptance_inner" + + if [ "$mode" = production ]; then + echo "Phase 14.3 production acceptance passed" + else + echo "Phase 14.3 acceptance passed" + fi +} + case "${1:-}" in --generator) stage_generator ;; +--acceptance) stage_acceptance local ;; +--acceptance-production) stage_acceptance production ;; *) - echo "usage: $0 --generator" >&2 + echo "usage: $0 --generator | --acceptance | --acceptance-production" >&2 exit 2 ;; esac