Files
archy/tests/apps/build-terminal-handler-runtime.sh

106 lines
6.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Fixture image only: bounded public package acquisition, no host apt/service use.
# Requires a separate manager admission; this script is not a runtime test pass.
set -euo pipefail
[[ $# == 3 ]] || { echo 'usage: build-terminal-handler-runtime.sh EMPTY_OUTPUT 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)
# The image commit is a host control-plane operation, not a container RUN.
# Refuse an unbounded parent rather than relying on container limits for it.
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, 'Bounded parent memory required'
assert int((root / 'memory.swap.max').read_text()) == 0, 'Parent swap forbidden'
assert 0 < int((root / 'pids.max').read_text()) <= 256, 'Bounded parent process count required'
quota, period = (root / 'cpu.max').read_text().split()
assert 0 < int(quota) <= int(period), 'Parent CPU limit required'
PY
cmp "$root/neode-ui/package.json" "$deps/package.json"
cmp "$root/neode-ui/package-lock.json" "$deps/package-lock.json"
# These exact inputs must be committed; unrelated dirty upload fixtures are not copied.
git -C "$root" diff --exit-code HEAD -- neode-ui tests/apps/terminal-browser tests/apps/terminal-handler tests/apps/build-terminal-handler-runtime.sh >/dev/null
[[ -z $(git -C "$root" ls-files --others --exclude-standard -- tests/apps/terminal-handler tests/apps/build-terminal-handler-runtime.sh) ]] || exit 2
git -C "$root" rev-parse HEAD > "$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
trap - EXIT INT TERM
set +e
for file in "$out/ui.cid" "$out/packages.cid"; do
[[ -s "$file" ]] || continue
cid=$(<"$file")
if [[ "$cid" =~ ^[a-f0-9]{64}$ ]]; then
timeout --kill-after=2s 10s podman rm -f "$cid" >> "$out/cleanup.log" 2>&1 || status=1
podman container exists "$cid"; exists=$?
[[ "$exists" == 1 ]] || status=1
else status=1; fi
done
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/input/src/components" "$out/input/src/stores" "$out/ui-output"
cp "$root/neode-ui/src/components/CLIPopup.vue" "$out/input/src/components/"
cp "$root/neode-ui/src/stores/cli.ts" "$out/input/src/stores/"
cp "$root/neode-ui/src/style.css" "$out/input/src/"
cp "$root/neode-ui/"{tailwind.config.js,postcss.config.js,package.json,package-lock.json} "$out/input/"
cp -R "$root/tests/apps/terminal-browser" "$out/input/fixture"
mkdir -p "$out/input/fixture/public/assets/fonts/Montserrat" "$out/input/fixture/public/assets/img"
cp "$root/neode-ui/public/assets/fonts/Montserrat/"{Montserrat-Bold,Montserrat-ExtraBold}.ttf "$out/input/fixture/public/assets/fonts/Montserrat/"
cp "$root/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-terminal-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 terminal-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/terminal-dist/index.html" && -z $(find "$out/ui-output" -type l -print -quit) ]] || exit 2
# No source, home, credentials, engine socket or service storage is mounted into
# the public Debian package acquisition container. Its writable root is private.
podman create --cidfile "$out/packages.cid" --name "archy-terminal-packages-$nonce" --pull=never \
--network=slirp4netns:allow_host_loopback=false --pid=private --ipc=private --http-proxy=false --no-hosts \
--security-opt=no-new-privileges --memory=1g --memory-swap=1g --cpus=1 --pids-limit=128 \
--timeout=180 --log-driver=none --entrypoint /bin/sh "$image" -ec '
apt-get -o Acquire::Retries=0 -o Acquire::http::Timeout=20 -o Acquire::https::Timeout=20 update
DEBIAN_FRONTEND=noninteractive apt-get -o Acquire::Retries=0 -o Acquire::http::Timeout=20 -o Acquire::https::Timeout=20 install -y --no-install-recommends tmux
test ! -e /etc/tmux.conf && test ! -e /root/.tmux.conf
mkdir -p /terminal
dpkg-query -W > /terminal/package-versions.txt
tmux -V > /terminal/tmux-version.txt
sha256sum /usr/bin/tmux > /terminal/tmux.sha256
' > "$out/packages-create.log" 2>&1
timeout --kill-after=10s 190s podman start --attach "$(<"$out/packages.cid")" > "$out/packages.log" 2>&1
cid=$(<"$out/packages.cid")
podman cp "$out/ui-output/terminal-dist" "$cid:/terminal/dist"
podman cp "$root/tests/apps/terminal-handler/check.cjs" "$cid:/terminal/check.cjs"
# Keep the resulting fixture image; do not delete retained artifacts/caches.
# The admitted parent must bound this control-plane commit to1GiB/1CPU/60s.
timeout --kill-after=10s 60s podman commit --pause=false \
--change 'ENTRYPOINT ["/bin/false"]' --change 'CMD []' \
--change 'LABEL archy.fixture=terminal-handler-only' "$cid" > "$out/runtime-image"
podman cp "$cid:/terminal/package-versions.txt" "$out/package-versions.txt"
podman cp "$cid:/terminal/tmux-version.txt" "$out/tmux-version.txt"
podman cp "$cid:/terminal/tmux.sha256" "$out/tmux.sha256"
sha256sum "$root/tests/apps/terminal-handler/check.cjs" > "$out/driver.sha256"
(cd "$out/ui-output/terminal-dist" && find . -type f -print0 | sort -z | xargs -0 sha256sum) > "$out/assets.sha256"