#!/bin/bash # Runs INSIDE a throwaway Ubuntu container (see run.sh). Exercises the real installer scripts as root, # with stand-ins for systemd and for the Ollama / LM Studio programs, and checks the outcome. set -u SERVER="$1"; TOKEN="$2" fail=0 ok() { echo " PASS $*"; } bad() { echo " FAIL $*"; fail=1; } check() { if eval "$2"; then ok "$1"; else bad "$1 [$2]"; fi; } echo "== prepare the container ($(. /etc/os-release; echo "$PRETTY_NAME"), $(uname -m))" export DEBIAN_FRONTEND=noninteractive if command -v apt-get >/dev/null; then apt-get update -qq >/dev/null && apt-get install -y -qq curl python3 sudo ca-certificates >/dev/null 2>&1 elif command -v dnf >/dev/null; then dnf install -y -q --allowerasing curl python3 sudo ca-certificates shadow-utils util-linux procps-ng >/dev/null 2>&1 elif command -v zypper >/dev/null; then zypper --non-interactive -q install curl python3 sudo ca-certificates shadow util-linux tar gzip >/dev/null 2>&1 elif command -v pacman >/dev/null; then pacman -Sy --noconfirm --needed curl python sudo ca-certificates >/dev/null 2>&1 fi useradd -m -s /bin/bash tester useradd -r -s /usr/sbin/nologin ollama 2>/dev/null || useradd -r -s /sbin/nologin ollama # the real Ollama installer creates this user mkdir -p /etc/sudoers.d echo 'tester ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/tester mkdir -p /run/systemd/system # the installer checks for a running systemd; the stand-in systemctl below does the rest # count how often a NON-root user invokes sudo: the installer must need exactly one elevation touch /var/log/sudo-by-user.log && chmod 666 /var/log/sudo-by-user.log SUDO="$(command -v sudo)" mv "$SUDO" /usr/bin/sudo.real cat > "$SUDO" <<'EOF' #!/bin/sh [ "$(id -u)" != 0 ] && echo "$*" >> /var/log/sudo-by-user.log exec /usr/bin/sudo.real "$@" EOF chmod 755 "$SUDO" # stand-ins: systemctl/journalctl/ldconfig (no systemd in a container), the ollama and lms programs mkdir -p /usr/local/bin /home/tester/.lmstudio/bin /run/stub cat > /usr/local/bin/systemctl <<'EOF' #!/bin/sh echo "systemctl $*" >> /var/log/systemctl.log exit 0 EOF cat > /usr/local/bin/journalctl <<'EOF' #!/bin/sh echo "connected to ws://stub" EOF printf '#!/bin/sh\nexit 0\n' > /usr/local/bin/ollama printf '#!/bin/sh\nexit 0\n' > /home/tester/.lmstudio/bin/lms chmod 755 /usr/local/bin/systemctl /usr/local/bin/journalctl /usr/local/bin/ollama /home/tester/.lmstudio/bin/lms chown -R tester /home/tester/.lmstudio # ldconfig -p must list libatomic so the preflight does not try apt cat > /usr/local/bin/ldconfig <<'EOF' #!/bin/sh echo " libatomic.so.1 (libc6,x86-64) => /usr/lib/libatomic.so.1" EOF chmod 755 /usr/local/bin/ldconfig cat > /usr/local/bin/systemd-run <<'EOF' #!/bin/sh echo "systemd-run $*" >> /var/log/systemd-run.log while [ $# -gt 0 ]; do case "$1" in --*) shift ;; *) break ;; esac; done # a real transient unit starts with an empty environment exec env -i PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin "$@" EOF chmod 755 /usr/local/bin/systemd-run for f in systemctl systemd-run journalctl ldconfig ollama; do ln -sf /usr/local/bin/$f /usr/bin/$f; done python3 /t/mock_runtimes.py & sleep 1 run_get() { su - tester -c "curl -fsSL $SERVER/get | WXCH_TOKEN=$TOKEN WXCH_SKIP=vllm sh -s -- $*" ; } echo; echo "== 1. fresh install through the one-liner (vLLM is covered by real-hardware tests)" run_get > /tmp/install1.log 2>&1; rc=$? tail -25 /tmp/install1.log check "install exits 0" "[ $rc = 0 ]" check "the installer elevated exactly once" "[ \$(wc -l < /var/log/sudo-by-user.log) = 1 ]" check "agent installed and owned by the agent user" "[ -x /opt/wxch-agent/wxch-agent ] && [ \$(stat -c %U /opt/wxch-agent/wxch-agent) = tester ]" check "agent token stored privately" "[ \$(stat -c %a /etc/wxch-agent.env) = 600 ] && grep -q $TOKEN /etc/wxch-agent.env" check "agent unit runs as the agent user" "grep -q '^User=tester' /etc/systemd/system/wxch-agent.service" check "agent unit points at this server" "grep -q 'ws://host.docker.internal' /etc/systemd/system/wxch-agent.service || grep -q 'ws://' /etc/systemd/system/wxch-agent.service" check "node.json holds folders and server" "python3 -c \"import json;d=json.load(open('/etc/wxch/node.json'));assert d['models_dir'].endswith('/ollama') and d['server'].startswith('ws')\"" check "model folders and marker exist" "[ -d /mnt/models/ollama ] && [ -d /mnt/models/gguf ] && [ -d /mnt/models/lmstudio ] && [ -e /mnt/models/.wxch-models ]" check "ollama drop-in written" "grep -q OLLAMA_MODELS=/mnt/models/ollama /etc/systemd/system/ollama.service.d/wxch.conf" check "lmstudio unit starts through the retrying helper" "grep -q '^ExecStart=/usr/local/bin/wxch-lms-start ' /etc/systemd/system/lmstudio.service && [ -x /usr/local/bin/wxch-lms-start ]" printf '#!/bin/sh c=/tmp/lms-fail-count; n=$(cat $c 2>/dev/null || echo 0); n=$((n+1)); echo $n > $c [ "$n" -ge 3 ] || { echo "Text file busy" >&2; exit 126; } ' > /tmp/flaky-lms && chmod 755 /tmp/flaky-lms && rm -f /tmp/lms-fail-count check "the helper retries a start that fails with Text file busy" "/usr/local/bin/wxch-lms-start /tmp/flaky-lms >/dev/null 2>&1 && [ \$(cat /tmp/lms-fail-count) = 3 ]" check "starter model on Ollama" "curl -s http://127.0.0.1:11434/api/tags | grep -q qwen2.5:0.5b" check "starter model on LM Studio" "curl -s http://127.0.0.1:1234/api/v1/models | grep -q qwen2.5-0.5b-instruct" check "self-test passed" "grep -q 'all runtimes passed the self-test' /tmp/install1.log" check "summary lists every step" "grep -q 'ok selftest' /tmp/install1.log && grep -q 'skipped vllm' /tmp/install1.log" echo; echo "== 2. running it again changes nothing important (idempotent)" run_get > /tmp/install2.log 2>&1; rc=$? check "second run exits 0" "[ $rc = 0 ]" check "second run needed another single elevation" "[ \$(wc -l < /var/log/sudo-by-user.log) = 2 ]" check "still exactly one model per runtime" "[ \$(curl -s http://127.0.0.1:11434/api/tags | grep -o '\"name\"' | wc -l) = 1 ]" echo; echo "== 3. --dry-run changes nothing and needs no sudo" before=$(wc -l < /var/log/sudo-by-user.log) su - tester -c "curl -fsSL $SERVER/get | WXCH_TOKEN=$TOKEN sh -s -- --dry-run" > /tmp/dry.log 2>&1; rc=$? check "dry run exits 0" "[ $rc = 0 ]" check "dry run used no sudo" "[ \$(wc -l < /var/log/sudo-by-user.log) = $before ]" check "dry run printed the plan" "grep -q 'plan:' /tmp/dry.log && grep -q 'selftest' /tmp/dry.log" echo; echo "== 3b. a machine without Docker/GPU skips vLLM instead of failing" su - tester -c "curl -fsSL $SERVER/get | WXCH_TOKEN=$TOKEN sh" > /tmp/novllm.log 2>&1; rc=$? tail -12 /tmp/novllm.log check "install without WXCH_SKIP exits 0" "[ $rc = 0 ]" check "vLLM was skipped with the reason" "grep -q 'skipping vLLM: no NVIDIA GPU found' /tmp/novllm.log && grep -q 'skipped vllm' /tmp/novllm.log" su - tester -c "curl -fsSL $SERVER/get | WXCH_TOKEN=$TOKEN WXCH_VLLM=required sh" > /tmp/reqvllm.log 2>&1; rc=$? check "WXCH_VLLM=required makes a missing GPU an error" "[ $rc != 0 ] && grep -q 'vLLM cannot be installed' /tmp/reqvllm.log" echo; echo "== 3c. started from inside the agent's own session, the installer detaches itself" su - tester -c "curl -fsSL $SERVER/get | WXCH_DETACH=1 WXCH_TOKEN=$TOKEN WXCH_SKIP=vllm sh" > /tmp/detach.log 2>&1; rc=$? tail -5 /tmp/detach.log check "detached install exits 0" "[ $rc = 0 ]" check "it ran through systemd-run as one unit" "grep -q 'systemd-run --unit=wxch-setup-.* --collect --wait --pipe' /var/log/systemd-run.log" check "the agent is owned by the invoking user even in the empty environment" "grep -q '^User=tester' /etc/systemd/system/wxch-agent.service" check "the user was told" "grep -q 'runs as a separate service' /tmp/detach.log" check "a normal run does not detach" "[ \$(wc -l < /var/log/systemd-run.log) = 1 ]" echo; echo "== 4. uninstall keeps runtimes and models by default" su - tester -c "curl -fsSL $SERVER/get | sh -s -- uninstall -y" > /tmp/un1.log 2>&1; rc=$? tail -6 /tmp/un1.log check "uninstall exits 0" "[ $rc = 0 ]" check "agent and its files are gone" "[ ! -e /usr/local/bin/wxch-lms-start ] && [ ! -e /opt/wxch-agent ] && [ ! -e /etc/wxch-agent.env ] && [ ! -e /etc/wxch/node.json ]" check "units removed" "[ ! -e /etc/systemd/system/wxch-agent.service ] && [ ! -e /etc/systemd/system/lmstudio.service ]" check "models were kept" "[ -d /mnt/models/ollama ] && [ -e /mnt/models/.wxch-models ]" echo; echo "== 5. --reinstall = uninstall (keeping models) + install again" su - tester -c "curl -fsSL $SERVER/get | WXCH_TOKEN=$TOKEN WXCH_SKIP=vllm sh -s -- --reinstall" > /tmp/re.log 2>&1; rc=$? tail -8 /tmp/re.log check "reinstall exits 0" "[ $rc = 0 ]" check "agent is back" "[ -x /opt/wxch-agent/wxch-agent ]" echo; echo "== 6. uninstall --all clears models, but only inside a folder the installer created" su - tester -c "curl -fsSL $SERVER/get | sh -s -- uninstall --all -y" > /tmp/un2.log 2>&1; rc=$? check "uninstall --all exits 0" "[ $rc = 0 ]" check "model folders are empty" "for d in ollama gguf lmstudio; do [ -z \"\$(ls -A /mnt/models/\$d)\" ] || exit 1; done" check "the model folder itself is still there" "[ -d /mnt/models ]" echo; echo "== 7. safety: a folder without the marker is never emptied" mkdir -p /srv/precious/ollama && echo data > /srv/precious/ollama/keep.txt su - tester -c "curl -fsSL $SERVER/get | WXCH_MODELS_DIR=/srv/precious sh -s -- uninstall --models -y" > /tmp/un3.log 2>&1 check "unmarked folder left alone" "[ -f /srv/precious/ollama/keep.txt ]" check "the refusal was explained" "grep -q 'no .wxch-models marker' /tmp/un3.log" echo if [ "$fail" = 0 ]; then echo "ALL INSTALLER CHECKS PASSED"; else echo "SOME INSTALLER CHECKS FAILED"; fi exit $fail