v0.7.0 4c16dd8

test-remotes.sh RawHistoryFolder

1#!/usr/bin/env bash
2# Two containerised SSH remotes for manual vev testing from the host terminal.
3#
4#   scripts/demo/test-remotes.sh up|status|down|broker-restart|shell
5#
6# Everything vev-side runs with VEV_ENV=test, so config, state, runtime and
7# hosts live in .dev/test/ of this checkout. SSH material lives in
8# .dev/test/ssh/; ~/.ssh is never read or written. vev runs plain `ssh` from
9# PATH, so .dev/test/bin/ssh wraps the real ssh with `-F .dev/test/ssh/config`.
10#
11# Environment:
12#   VEV_TEST_SSH_PORT_A / VEV_TEST_SSH_PORT_B  loopback sshd ports (2222/2223)
13#   VEV_TEST_TAG                               image tag (test)
14#   VEV_TEST_ENV                               VEV_ENV name (test)
15#   DOCKER_HOST                                honoured as-is
16set -euo pipefail
17
18cd "$(dirname "$0")/../.."
19root=$(pwd -P)
20
21export VEV_TEST_TAG=${VEV_TEST_TAG:-test}
22env_name=${VEV_TEST_ENV:-test}
23# Exported so compose.test.yaml's `name:` interpolates the same resolved
24# value: a distinct VEV_ENV name must get a distinct compose project, or two
25# sandboxes running side by side under different VEV_TEST_ENV values collide
26# on the same containers/network.
27export VEV_TEST_ENV="$env_name"
28export VEV_TEST_SSH_PORT_A=${VEV_TEST_SSH_PORT_A:-2222}
29export VEV_TEST_SSH_PORT_B=${VEV_TEST_SSH_PORT_B:-2223}
30
31image="vev-sandbox:${VEV_TEST_TAG}"
32compose=(docker compose -f scripts/demo/compose.test.yaml)
33hosts=(remote-a remote-b)
34env_root="$root/.dev"
35test_dir="$env_root/$env_name"
36ssh_dir="$test_dir/ssh"
37bin_dir="$test_dir/bin"
38vev_bin="$root/build/vev"
39
40die() { echo "test-remotes: error: $*" >&2; exit 1; }
41log() { echo "test-remotes: $*" >&2; }
42
43port_for() {
44  case "$1" in
45    remote-a) echo "$VEV_TEST_SSH_PORT_A" ;;
46    remote-b) echo "$VEV_TEST_SSH_PORT_B" ;;
47    *) die "unknown host $1" ;;
48  esac
49}
50
51# vev_env runs a command with the sandbox vev environment. VEV is unset so a
52# command launched from inside a real vev pane does not look nested.
53vev_env() {
54  env -u VEV -u VEV_ENV_ROOT \
55    VEV_ENV="$env_name" VEV_ENV_ROOT="$env_root" \
56    PATH="$bin_dir:$PATH" "$@"
57}
58
59need_docker() {
60  command -v docker >/dev/null || die "docker not found"
61  docker info >/dev/null 2>&1 || die "docker daemon unreachable (DOCKER_HOST=${DOCKER_HOST:-unset})"
62}
63
64real_ssh() {
65  local path
66  path=$(PATH=$(printf '%s' "$PATH" | tr ':' '\n' | grep -vxF "$bin_dir" | paste -sd:) command -v ssh) \
67    || die "ssh client not found"
68  echo "$path"
69}
70
71write_ssh_material() {
72  local dir
73  for dir in "$env_root" "$test_dir" "$ssh_dir" "$bin_dir"; do
74    [[ -d $dir ]] || mkdir -m 700 "$dir"
75  done
76  if [[ ! -f "$ssh_dir/id_ed25519" ]]; then
77    ssh-keygen -q -t ed25519 -N '' -C vev-test-remotes -f "$ssh_dir/id_ed25519"
78  fi
79  {
80    echo "# Generated by scripts/demo/test-remotes.sh. Sandbox only."
81    local host
82    for host in "${hosts[@]}"; do
83      cat <<EOF
84
85Host $host
86    HostName 127.0.0.1
87    Port $(port_for "$host")
88    User demo
89    HostKeyAlias $host
90    IdentityFile $ssh_dir/id_ed25519
91    IdentitiesOnly yes
92    IdentityAgent none
93    UserKnownHostsFile $ssh_dir/known_hosts
94    GlobalKnownHostsFile /dev/null
95    StrictHostKeyChecking yes
96    ControlMaster no
97    ControlPath none
98    LogLevel ERROR
99EOF
100    done
101  } >"$ssh_dir/config"
102  chmod 600 "$ssh_dir/config"
103  cat >"$bin_dir/ssh" <<EOF
104#!/bin/sh
105# Sandbox ssh: vev runs \`ssh\` from PATH; force the sandbox config.
106exec $(real_ssh) -F "$ssh_dir/config" "\$@"
107EOF
108  chmod 700 "$bin_dir/ssh"
109}
110
111container_for() {
112  "${compose[@]}" ps -q "$1"
113}
114
115install_keys() {
116  local host cid pub
117  pub=$(cat "$ssh_dir/id_ed25519.pub")
118  : >"$ssh_dir/known_hosts.tmp"
119  for host in "${hosts[@]}"; do
120    cid=$(container_for "$host")
121    [[ -n $cid ]] || die "$host container is not running"
122    docker exec -i "$cid" sh -c 'umask 077; cat > /home/demo/.ssh/authorized_keys && chown demo:demo /home/demo/.ssh/authorized_keys' <<<"$pub"
123    printf '%s ' "$host" >>"$ssh_dir/known_hosts.tmp"
124    docker exec "$cid" cut -d' ' -f1-2 /etc/ssh/ssh_host_ed25519_key.pub >>"$ssh_dir/known_hosts.tmp"
125  done
126  mv "$ssh_dir/known_hosts.tmp" "$ssh_dir/known_hosts"
127  chmod 600 "$ssh_dir/known_hosts"
128}
129
130wait_ssh() {
131  local host
132  for host in "${hosts[@]}"; do
133    local ok=
134    for _ in $(seq 1 40); do
135      if timeout 5 "$bin_dir/ssh" -o ConnectTimeout=2 -o BatchMode=yes "$host" true >/dev/null 2>&1; then
136        ok=1; break
137      fi
138      sleep 0.25
139    done
140    [[ -n $ok ]] || die "SSH readiness timeout for $host (try: $bin_dir/ssh -v $host true)"
141    timeout 10 "$bin_dir/ssh" -o BatchMode=yes "$host" 'vev --version' >/dev/null \
142      || die "vev is not runnable on $host"
143    log "ssh ready: $host"
144  done
145}
146
147register_hosts() {
148  local host listed
149  listed=$(vev_env timeout 30 "$vev_bin" host list 2>&1) || die "vev host list failed: $listed"
150  for host in "${hosts[@]}"; do
151    if grep -qw -- "$host" <<<"$listed"; then
152      log "host already registered: $host"
153      continue
154    fi
155    vev_env timeout 30 "$vev_bin" host add --transport ssh "$host" || die "vev host add --transport ssh $host failed"
156    log "host registered: $host"
157  done
158}
159
160cmd_up() {
161  need_docker
162  log "building build/vev"
163  go build -o build/vev .
164  log "building $image"
165  docker build -q --label vev.sandbox.binary-sha256="$(sha256sum build/vev | cut -d' ' -f1)" \
166    -t "$image" -f scripts/demo/Dockerfile . >/dev/null
167  local stale
168  stale=$(vev_pids | while read -r pid; do
169    exe=$(readlink "/proc/$pid/exe" 2>/dev/null) || continue
170    [[ $exe == "$vev_bin" ]] || echo "$pid"
171  done | paste -sd' ')
172  if [[ -n $stale ]]; then
173    log "warning: VEV_ENV=$env_name broker/daemon from an older build is still running (pids $stale)."
174    log "warning: it keeps serving until stopped; run '$0 down' then '$0 up' to test the new build."
175  fi
176  write_ssh_material
177  # --force-recreate picks up a rebuilt image and restarts remote daemons.
178  local out
179  out=$("${compose[@]}" up -d --force-recreate --remove-orphans remote-a remote-b 2>&1) \
180    || die "compose up failed: $out"
181  install_keys
182  wait_ssh
183  register_hosts
184  echo
185  cmd_shell_hint
186}
187
188cmd_status() {
189  need_docker
190  "${compose[@]}" ps --format 'table {{.Service}}\t{{.State}}\t{{.Ports}}'
191  echo
192  if [[ ! -x $vev_bin ]]; then
193    echo "build/vev missing; run: $0 up"
194    return 0
195  fi
196  echo "== vev host list"
197  vev_env timeout 15 "$vev_bin" host list || echo "(host list failed: $?)"
198  echo "== vev ls --all"
199  vev_env timeout 30 "$vev_bin" ls --all || echo "(ls --all failed: $?)"
200}
201
202# vev_pids prints vev processes of this checkout's sandbox environment: the
203# executable lives inside this checkout, and the environment carries both
204# VEV_ENV=<name> and VEV_ENV_ROOT=<this checkout>/.dev. The owner's installed
205# vev and other VEV_ENV names never match. Pane shells are not signalled; they
206# end with their daemon.
207vev_pids() {
208  local environ pid exe vars
209  for environ in /proc/[0-9]*/environ; do
210    [[ -r $environ ]] || continue
211    pid=${environ#/proc/}; pid=${pid%/environ}
212    exe=$(readlink "/proc/$pid/exe" 2>/dev/null) || continue
213    [[ ${exe% (deleted)} == "$root"/* ]] || continue
214    vars=$(tr '\0' '\n' 2>/dev/null <"$environ") || continue
215    if grep -qxF "VEV_ENV=$env_name" <<<"$vars" && grep -qxF "VEV_ENV_ROOT=$env_root" <<<"$vars"; then
216      echo "$pid"
217    fi
218  done
219  return 0
220}
221
222cmd_down() {
223  if [[ -x $vev_bin && -d $test_dir ]]; then
224    local host
225    # Forget the registrations so a later plain VEV_ENV=test client does not
226    # keep dialing hosts that no longer exist; `up` re-adds them.
227    for host in "${hosts[@]}"; do
228      vev_env timeout 15 "$vev_bin" host rm "$host" >/dev/null 2>&1 || true
229    done
230    vev_env timeout 10 "$vev_bin" _daemon-stop >/dev/null 2>&1 || true
231  fi
232  # `kill --all` would stop every vev of this user: TERM the sandbox broker and any leftover
233  # sandbox roles, then KILL whatever ignores it.
234  local pids
235  pids=$(vev_pids | paste -sd' ')
236  if [[ -n $pids ]]; then
237    log "stopping VEV_ENV=$env_name vev processes: $pids"
238    # shellcheck disable=SC2086
239    kill -TERM $pids 2>/dev/null || true
240    for _ in $(seq 1 20); do
241      [[ -z $(vev_pids) ]] && break
242      sleep 0.1
243    done
244    pids=$(vev_pids | paste -sd' ')
245    # shellcheck disable=SC2086
246    [[ -z $pids ]] || kill -KILL $pids 2>/dev/null || true
247  fi
248  if command -v docker >/dev/null && docker info >/dev/null 2>&1; then
249    "${compose[@]}" down -v --remove-orphans --timeout 5 >/dev/null 2>&1 || true
250  else
251    log "docker unreachable; containers were not removed (DOCKER_HOST=${DOCKER_HOST:-unset})"
252  fi
253  rm -rf "$ssh_dir" "$bin_dir"
254  log "down"
255}
256
257# broker_restart stops only this sandbox's broker. The daemon and its sessions
258# stay up; the next vev invocation respawns the broker.
259cmd_broker_restart() {
260  local pid pids=()
261  for pid in $(vev_pids); do
262    if tr '\0' ' ' 2>/dev/null </proc/"$pid"/cmdline | grep -q '_broker-production-serve'; then
263      pids+=("$pid")
264    fi
265  done
266  [[ ${#pids[@]} -gt 0 ]] || die "no VEV_ENV=$env_name broker is running"
267  log "stopping broker: ${pids[*]}"
268  kill -TERM "${pids[@]}"
269  for _ in $(seq 1 30); do
270    kill -0 "${pids[@]}" 2>/dev/null || break
271    sleep 0.1
272  done
273  vev_env timeout 30 "$vev_bin" host list
274}
275
276cmd_shell_hint() {
277  cat <<EOF
278Sandbox ready. Launch the client from any terminal with:
279
280  cd $root && env -u VEV VEV_ENV=$env_name VEV_ENV_ROOT=$env_root PATH=$bin_dir:\$PATH ./build/vev
281
282or: $0 shell --exec
283Plain ssh:  $bin_dir/ssh remote-a
284EOF
285}
286
287cmd_shell() {
288  [[ -x $bin_dir/ssh ]] || die "sandbox is not up; run: $0 up"
289  if [[ ${1:-} == --exec ]]; then
290    shift
291    exec env -u VEV -u VEV_ENV_ROOT VEV_ENV="$env_name" VEV_ENV_ROOT="$env_root" \
292      PATH="$bin_dir:$PATH" "$vev_bin" "$@"
293  fi
294  cmd_shell_hint
295}
296
297case "${1:-}" in
298  up) cmd_up ;;
299  status) cmd_status ;;
300  down) cmd_down ;;
301  broker-restart) cmd_broker_restart ;;
302  shell) shift; cmd_shell "$@" ;;
303  *) echo "usage: $0 up|status|down|broker-restart|shell [--exec [vev args]]" >&2; exit 2 ;;
304esac

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close