run.sh RawHistoryFolder
1#!/usr/bin/env bash
2# Disposable navigation acceptance with the real client, daemons and UI driver.
3set -euo pipefail
4case "${1:-}" in
5 -h|--help) printf 'Usage: scripts/remote-picker-harness/run.sh\nRequires Docker, Python 3 and ssh-keygen.\nSet VEV_ACCEPTANCE_ARTIFACTS_DIR to keep logs elsewhere than /tmp.\nSet VEV_ACCEPTANCE_SCENARIOS to a space-separated subset, e.g.\n VEV_ACCEPTANCE_SCENARIOS="warm-reuse@quic warm-reuse@stdio"\nNames: navigation-repro local-palette-cycle direct-remote-named@quic|@stdio\n warm-reuse@quic|@stdio hybrid-exact-return@quic|@stdio\n client-picker-navigate-local@quic client-picker-navigate-direct@quic|@stdio\n'; exit 0 ;;
6 '') ;;
7 *) exit 2 ;;
8esac
9repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
10for tool in docker python3 ssh-keygen; do command -v "$tool" >/dev/null; done
11docker info >/dev/null
12run_id="$(date +%s)-$$"
13image="vev-acceptance:${run_id}"
14network="vev-acceptance-${run_id}"
15local_container="${network}-local"
16remote_container="${network}-remote"
17context_dir="$(mktemp -d)"
18# Sanitized, per-run evidence outside the worktree. Container state logs carry
19# structured vev events only: no host state, credentials, sockets or keys.
20artifacts_dir="${VEV_ACCEPTANCE_ARTIFACTS_DIR:-$(mktemp -d "/tmp/vev-acceptance-artifacts-${run_id}.XXXXXX")}"
21export VEV_ACCEPTANCE_ARTIFACTS_DIR="$artifacts_dir"
22
23collect_artifacts() {
24 mkdir -p "$artifacts_dir"
25 for role in local remote; do
26 container="${network}-${role}"
27 for file in vev-daemon.log vev-client.log vev-stdio.log \
28 vev-daemon-crash.log vev-client-crash.log vev-stdio-crash.log; do
29 docker cp "$container:/home/demo/.local/state/vev/$file" \
30 "$artifacts_dir/${role}-${file}" >/dev/null 2>&1 || true
31 done
32 docker cp "$container:/home/demo/.local/state/vev/broker/log/vev-daemon.log" \
33 "$artifacts_dir/${role}-vev-broker.log" >/dev/null 2>&1 || true
34 done
35}
36
37cleanup() {
38 status=$?
39 trap - EXIT INT TERM
40 set +e
41 collect_artifacts
42 if [ "$status" -ne 0 ]; then
43 for file in "$artifacts_dir"/*.log; do
44 [ -f "$file" ] || continue
45 printf '===== %s =====\n' "$file" >&2
46 tail -n 60 "$file" >&2
47 done
48 fi
49 docker rm -f "$local_container" "$remote_container" >/dev/null 2>&1
50 docker network rm "$network" >/dev/null 2>&1
51 docker image rm "$image" >/dev/null 2>&1
52 rm -rf "$context_dir"
53 printf 'acceptance artifacts: %s\n' "$artifacts_dir"
54 exit "$status"
55}
56trap cleanup EXIT
57trap 'exit 130' INT
58trap 'exit 143' TERM
59
60docker build -f "$repo_root/scripts/demo/Dockerfile" -t "$image" "$repo_root" >/dev/null
61docker network create "$network" >/dev/null
62# Client and daemon debug logging is enabled container-wide; docker exec and
63# the daemon child both inherit it. Remote proxy children receive it through
64# ssh SendEnv/AcceptEnv below.
65for role in local remote; do
66 docker run -d --name "${network}-${role}" --network "$network" \
67 --network-alias "$role" -e VEV_LOG=debug \
68 --entrypoint sleep "$image" infinity >/dev/null
69done
70
71# Only generated fixture credentials enter these containers; no host HOME or
72# sockets are mounted. Pin the fresh remote key instead of trusting keyscan.
73ssh-keygen -q -t ed25519 -N '' -C acceptance-client -f "$context_dir/client"
74ssh-keygen -q -t ed25519 -N '' -C acceptance-host -f "$context_dir/host"
75docker exec --user root "$remote_container" sh -c 'rm -f /etc/ssh/ssh_host_* /home/demo/.ssh/id_ed25519*; mkdir -p /run/sshd'
76docker cp "$context_dir/host" "$remote_container:/etc/ssh/ssh_host_ed25519_key"
77docker cp "$context_dir/host.pub" "$remote_container:/etc/ssh/ssh_host_ed25519_key.pub"
78docker cp "$context_dir/client.pub" "$remote_container:/home/demo/.ssh/authorized_keys"
79# Accept the debug level the client forwards so remote _broker-mux-stdio/_broker-mux-quic-proxy and
80# the daemon they spawn log at debug too.
81docker exec --user root "$remote_container" sh -c 'printf "\nAcceptEnv VEV_LOG\n" >> /etc/ssh/sshd_config; chmod 600 /etc/ssh/ssh_host_ed25519_key /home/demo/.ssh/authorized_keys; chown demo:demo /home/demo/.ssh/authorized_keys; /usr/sbin/sshd'
82docker cp "$context_dir/client" "$local_container:/home/demo/.ssh/id_ed25519"
83docker cp "$context_dir/client.pub" "$local_container:/home/demo/.ssh/id_ed25519.pub"
84{ printf 'remote '; cut -d' ' -f1-2 "$context_dir/host.pub"; } > "$context_dir/known_hosts"
85docker cp "$context_dir/known_hosts" "$local_container:/home/demo/.ssh/known_hosts"
86docker exec --user root "$local_container" sh -c 'chown demo:demo /home/demo/.ssh/id_ed25519* /home/demo/.ssh/known_hosts; chmod 600 /home/demo/.ssh/id_ed25519 /home/demo/.ssh/known_hosts'
87# Forward VEV_LOG on every remote SSH child (global option before the first
88# Host block); the fixture ssh config itself is otherwise untouched.
89docker exec "$local_container" sh -c 'p="$HOME/.ssh/config"; { printf "SendEnv VEV_LOG\n"; cat "$p"; } > "$p.new" && mv "$p.new" "$p" && chmod 600 "$p"'
90docker exec "$local_container" ssh -o BatchMode=yes -o ConnectTimeout=5 remote true
91docker exec "$local_container" vev host add remote
92# Baseline matrix: local IPC, direct named-remote (QUIC + explicit stdio, no
93# local daemon), warm remote reuse (QUIC + stdio), and hybrid exact-return
94# (QUIC + stdio). Each scenario asserts committed outcomes, not sent keys.
95# navigation_repro.py keeps the original hybrid transport regression.
96acceptance_py="$repo_root/scripts/remote-picker-harness/acceptance.py"
97scenarios_env="${VEV_ACCEPTANCE_SCENARIOS:-}"
98known_scenarios="navigation-repro local-palette-cycle direct-remote-named@quic direct-remote-named@stdio warm-reuse@quic warm-reuse@stdio hybrid-exact-return@quic hybrid-exact-return@stdio client-picker-navigate-local@quic client-picker-navigate-direct@quic client-picker-navigate-direct@stdio"
99for requested in $scenarios_env; do
100 case " $known_scenarios " in
101 *" $requested "*) ;;
102 *) printf 'unknown acceptance scenario: %s\n' "$requested" >&2; exit 2 ;;
103 esac
104done
105if [ -n "$scenarios_env" ] && [ -z "${scenarios_env//[[:space:]]/}" ]; then
106 printf 'VEV_ACCEPTANCE_SCENARIOS selects no scenarios\n' >&2
107 exit 2
108fi
109wanted_scenario() {
110 [ -z "$scenarios_env" ] && return 0
111 for want in $scenarios_env; do [ "$want" = "$1" ] && return 0; done
112 return 1
113}
114run_scenario() { # name spec [topology]
115 wanted_scenario "$1" || return 0
116 if [ -n "${3:-}" ]; then
117 VEV_ACCEPTANCE_TOPOLOGY="$3" python3 "$acceptance_py" "$local_container" "$remote_container" "$2"
118 else
119 python3 "$acceptance_py" "$local_container" "$remote_container" "$2"
120 fi
121}
122if wanted_scenario navigation-repro; then
123 python3 "$repo_root/scripts/remote-picker-harness/navigation_repro.py" "$local_container" "$remote_container"
124fi
125run_scenario local-palette-cycle local-palette-cycle
126run_scenario direct-remote-named@quic direct-remote-named@quic
127run_scenario direct-remote-named@stdio direct-remote-named@stdio
128run_scenario warm-reuse@quic warm-reuse@quic
129run_scenario warm-reuse@stdio warm-reuse@stdio
130run_scenario hybrid-exact-return@quic hybrid-exact-return@quic
131run_scenario hybrid-exact-return@stdio hybrid-exact-return@stdio
132# Client-owned navigation picker where the serving attachment answers the
133# palette session-picker command itself: local IPC and direct remote (QUIC and
134# explicit stdio). A hybrid client advertises the home-picker capability, so
135# its session-picker command delegates to the home route overlay (an R1
136# behavior pinned by hybrid-exact-return) instead of this interaction.
137run_scenario client-picker-navigate-local@quic client-picker-navigate@quic local
138run_scenario client-picker-navigate-direct@quic client-picker-navigate@quic direct
139run_scenario client-picker-navigate-direct@stdio client-picker-navigate@stdio direct