#!/usr/bin/env bash # Offline fixture assets only. Separate bounded manager admission is required. set -euo pipefail [[ $# == 3 ]] || { echo 'usage: build-browser-runtime-offline.sh EMPTY_OUTPUT PINNED_BROWSER_IMAGE LOCKED_FRONTEND_DIRECTORY' >&2; exit 2; } out=$(realpath -e "$1"); image=$2; deps=$(realpath -e "$3") [[ "$image" =~ ^sha256:[0-9a-f]{64}$ && -d "$deps/node_modules" ]] || exit 2 [[ -d "$out" && -z $(find "$out" -mindepth 1 -maxdepth 1 -print -quit) ]] || exit 2 root=$(cd "$(dirname "$0")/../.." && pwd) PYTHONOPTIMIZE=0 python3 - <<'PY' from pathlib import Path group = next(line.split(':', 2)[2] for line in Path('/proc/self/cgroup').read_text().splitlines() if line.startswith('0::')) root = Path('/sys/fs/cgroup') / group.lstrip('/') assert 0 < int((root / 'memory.max').read_text()) <= 1073741824 assert int((root / 'memory.swap.max').read_text()) == 0 assert 0 < int((root / 'pids.max').read_text()) <= 256 quota, period = (root / 'cpu.max').read_text().split() assert 0 < int(quota) <= int(period) PY cmp "$root/neode-ui/package.json" "$deps/package.json" cmp "$root/neode-ui/package-lock.json" "$deps/package-lock.json" inputs=(neode-ui tests/cloud/browser/index.html tests/cloud/browser/main.js tests/cloud/browser/vite.config.mjs tests/cloud/browser/drive.cjs tests/cloud/browser/pagination.cjs tests/cloud/browser/pagination-seed.test.cjs tests/cloud/build-browser-runtime-offline.sh core/archipelago/src/api/handler/cloud_browser_tests.rs) git -C "$root" diff --exit-code HEAD -- "${inputs[@]}" >/dev/null [[ -z $(git -C "$root" ls-files --others --exclude-standard -- "${inputs[@]}") ]] || exit 2 commit=$(git -C "$root" rev-parse HEAD) printf '%s\n' "$commit" > "$out/source.commit" printf '%s\n' "$image" > "$out/base-image" podman ps -aq --no-trunc | sort > "$out/containers.before" cleanup() { local status=$? file cid exists # A parent stop may arrive during cleanup; finish only these bounded removals. trap '' INT TERM trap - EXIT set +e for file in "$out/ui.cid" "$out/image.cid"; do [[ -s "$file" ]] || continue cid=$(<"$file") if [[ "$cid" =~ ^[a-f0-9]{64}$ ]]; then timeout --kill-after=1s 6s podman rm -f "$cid" >> "$out/cleanup.log" 2>&1 || status=1 timeout 2s podman container exists "$cid"; exists=$? [[ "$exists" == 1 ]] || status=1 else status=1; fi done timeout 3s podman ps -aq --no-trunc | sort > "$out/containers.after" || status=1 cmp "$out/containers.before" "$out/containers.after" || status=1 printf '%s\n' "$status" > "$out/outer.exit" exit "$status" } trap cleanup EXIT trap 'exit 143' TERM trap 'exit 130' INT mkdir -p "$out/source" "$out/input/fixture" "$out/ui-output" # Export tracked bytes from the pinned commit, never copy a dirty source tree. git -C "$root" archive "$commit" neode-ui/src \ neode-ui/{tailwind.config.js,postcss.config.js,package.json,package-lock.json} \ neode-ui/public/assets/fonts/Montserrat/{Montserrat-Bold,Montserrat-ExtraBold}.ttf \ neode-ui/public/assets/img/bg.jpg tests/cloud/browser/{index.html,main.js,vite.config.mjs,drive.cjs,pagination.cjs} \ | tar --no-same-owner --no-same-permissions -xf - -C "$out/source" [[ -z $(find "$out/source" ! -type d ! -type f -print -quit) ]] || exit 2 snapshot="$out/source" cp -R "$snapshot/neode-ui/src" "$out/input/" cp "$snapshot/neode-ui/"{tailwind.config.js,postcss.config.js,package.json,package-lock.json} "$out/input/" cp "$snapshot/tests/cloud/browser/"{index.html,main.js,vite.config.mjs} "$out/input/fixture/" mkdir -p "$out/input/fixture/public/assets/fonts/Montserrat" "$out/input/fixture/public/assets/img" cp "$snapshot/neode-ui/public/assets/fonts/Montserrat/"{Montserrat-Bold,Montserrat-ExtraBold}.ttf "$out/input/fixture/public/assets/fonts/Montserrat/" cp "$snapshot/neode-ui/public/assets/img/bg.jpg" "$out/input/fixture/public/assets/img/" (cd "$out/input" && find . -type f -print0 | sort -z | xargs -0 sha256sum) > "$out/input.sha256" nonce="$(date +%s)-$$" podman create --cidfile "$out/ui.cid" --name "archy-cloud-assets-$nonce" --pull=never \ --network=none --pid=private --ipc=private --http-proxy=false --no-hosts \ --read-only --cap-drop=all --security-opt=no-new-privileges --memory=1g --memory-swap=1g \ --cpus=1 --pids-limit=256 --timeout=90 --log-driver=none \ --tmpfs /tmp:rw,size=256m --tmpfs /work:rw,size=128m \ --env HOME=/tmp --env XDG_CONFIG_HOME=/tmp/config --env NODE_PATH=/browser/node_modules \ --volume "$out/input:/input:ro" --volume "$out/ui-output:/out:rw" --volume "$deps/node_modules:/deps/node_modules:ro" \ --workdir /work --entrypoint /bin/sh "$image" -ec ' cp -R /input/. /work/ mkdir node_modules for dep in /deps/node_modules/*; do ln -s "$dep" node_modules/; done node /deps/node_modules/vite/bin/vite.js build --config fixture/vite.config.mjs cp -R dist /out/ ' > "$out/ui-create.log" 2>&1 timeout --kill-after=10s 100s podman start --attach "$(<"$out/ui.cid")" > "$out/ui-build.log" 2>&1 [[ -f "$out/ui-output/dist/index.html" && -z $(find "$out/ui-output" ! -type d ! -type f -print -quit) ]] || exit 2 # Never start the image-assembly container or mutate the retained base image. # Only its new private layer is writable for the host control-plane copy/commit. podman create --cidfile "$out/image.cid" --name "archy-cloud-image-$nonce" --pull=never \ --network=none --pid=private --ipc=private --http-proxy=false --no-hosts \ --cap-drop=all --security-opt=no-new-privileges --memory=1g --memory-swap=1g \ --cpus=1 --pids-limit=64 --timeout=10 --entrypoint /bin/false "$image" > "$out/image-create.log" 2>&1 cid=$(<"$out/image.cid") timeout --kill-after=2s 15s podman cp "$out/ui-output" "$cid:/cloud-pagination" timeout --kill-after=2s 15s podman cp "$cid:/cloud-pagination/dist" "$out/copied-dist" (cd "$out/ui-output/dist" && find . -type f -print0 | sort -z | xargs -0 sha256sum) > "$out/assets.sha256" (cd "$out/copied-dist" && find . -type f -print0 | sort -z | xargs -0 sha256sum) > "$out/copied-assets.sha256" cmp "$out/assets.sha256" "$out/copied-assets.sha256" timeout --kill-after=10s 60s podman commit --pause=false \ --change 'ENTRYPOINT ["/bin/false"]' --change 'CMD []' \ --change 'LABEL archy.fixture=cloud-pagination-only' "$cid" > "$out/runtime-image" sha256sum "$snapshot/tests/cloud/browser/"{drive.cjs,pagination.cjs} > "$out/drivers.sha256"