feat(demo): add VEV_ENV sandbox with two remote containers
- commit
- 1f5bcd2be970e5dc83954456ec952a1c9cb153b8
- author
- brice · · 2026-09-22 20:09 UTC
- parent
- 0125c1b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +423 -0
M .gitignore +1 -0
| @@ -9,3 +9,4 @@ docs/superpowers/ | |||
| 9 | 9 | .worktrees/ | |
| 10 | 10 | scripts/demo/out/ | |
| 11 | 11 | /.worktrees/ | |
| 12 | + | /build/ | |
| @@ -9,3 +9,4 @@ docs/superpowers/ | |||||
| 9 | .worktrees/ | 9 | .worktrees/ | ||
| 10 | scripts/demo/out/ | 10 | scripts/demo/out/ | ||
| 11 | /.worktrees/ | 11 | /.worktrees/ | ||
| 12 | + | /build/ | |||
A scripts/demo/README.md +80 -0
| @@ -0,0 +1,80 @@ | |||
| 1 | + | # Demo and sandbox scripts | |
| 2 | + | ||
| 3 | + | - `run.sh`, `demo.tape`, `smoke.tape`: VHS demo recording. | |
| 4 | + | - `sandbox-run.sh`: automated acceptance. Client and two remotes all run in | |
| 5 | + | containers on an internal network. | |
| 6 | + | - `test-remotes.sh`: two containerised remotes for manual testing from your own | |
| 7 | + | terminal. | |
| 8 | + | ||
| 9 | + | All of them build `build/vev` and bake it into the `scripts/demo/Dockerfile` | |
| 10 | + | image, so local and remote vev are the same binary. Docker must be rootless. | |
| 11 | + | ||
| 12 | + | ## Manual remote testing | |
| 13 | + | ||
| 14 | + | ```sh | |
| 15 | + | scripts/demo/test-remotes.sh up # build, start remote-a/remote-b, register both hosts | |
| 16 | + | scripts/demo/test-remotes.sh shell # print the client command | |
| 17 | + | scripts/demo/test-remotes.sh status # containers, ports, vev host list, vev ls --all | |
| 18 | + | scripts/demo/test-remotes.sh broker-restart # stop only the test broker; the next vev call respawns it | |
| 19 | + | scripts/demo/test-remotes.sh down # stop the test broker/daemon, remove containers and keys | |
| 20 | + | ``` | |
| 21 | + | ||
| 22 | + | Run the printed command from a real terminal, or `scripts/demo/test-remotes.sh | |
| 23 | + | shell --exec [vev args]`. It sets: | |
| 24 | + | ||
| 25 | + | - `VEV_ENV=test` and `VEV_ENV_ROOT=<checkout>/.dev`: all vev state lives in | |
| 26 | + | `.dev/test/`, apart from your installed vev. | |
| 27 | + | - `PATH=.dev/test/bin:$PATH`: vev runs `ssh` from `PATH` and has no ssh config | |
| 28 | + | option, so `.dev/test/bin/ssh` wraps the real client with | |
| 29 | + | `-F .dev/test/ssh/config`. The key, `known_hosts` and config are generated in | |
| 30 | + | `.dev/test/ssh/`. `~/.ssh` and your agent are never used. | |
| 31 | + | - `VEV_REMOTE_TRANSPORT=stdio`: hosts are registered with the SSH-only carriage. | |
| 32 | + | QUIC cannot work here. The remote proxy listens on a random UDP port, and | |
| 33 | + | the client dials that port on the SSH target name (`remote-a`), which does | |
| 34 | + | not resolve to the loopback port mapping. | |
| 35 | + | - `-u VEV`: a client started from a pane of your real vev is not treated as | |
| 36 | + | nested. | |
| 37 | + | ||
| 38 | + | sshd is published on `127.0.0.1:2222` (remote-a) and `127.0.0.1:2223` | |
| 39 | + | (remote-b). Override the ports with `VEV_TEST_SSH_PORT_A` and | |
| 40 | + | `VEV_TEST_SSH_PORT_B`, and the environment name with `VEV_TEST_ENV`. Plain | |
| 41 | + | shell access: `.dev/test/bin/ssh remote-a`. | |
| 42 | + | ||
| 43 | + | `up` is idempotent: it recreates the containers, reinstalls the key and | |
| 44 | + | skips hosts that are already registered. A `VEV_ENV=test` broker started by an | |
| 45 | + | older build keeps running until it is stopped, and `up` warns when it finds | |
| 46 | + | one. Run `down` then `up` after a rebuild. `down` stops **every** | |
| 47 | + | `VEV_ENV=test` daemon of this checkout, including sessions you opened | |
| 48 | + | there by hand. | |
| 49 | + | ||
| 50 | + | Remote sessions live in the containers and disappear on `down`. | |
| 51 | + | ||
| 52 | + | ### Checklist | |
| 53 | + | ||
| 54 | + | Open a few sessions first, with at least two tabs in one of them, local and on | |
| 55 | + | each remote: `vev new work`, `vev new ra remote-a`, `vev new rb remote-b` | |
| 56 | + | (each run with the environment above). | |
| 57 | + | ||
| 58 | + | - [ ] Picker over a session (`Alt+Space`, `SSP`): opens over the live | |
| 59 | + | session; `Esc` returns to the same session, tab and pane. | |
| 60 | + | - [ ] Move pane (`MFP`) and move tab (`MAT`): the destination picker lists | |
| 61 | + | local and remote sessions and tabs; the move lands where you chose. | |
| 62 | + | - [ ] `x` in the picker kills the selected session (local and remote); the | |
| 63 | + | row disappears and `vev ls --all` agrees. | |
| 64 | + | - [ ] Tab rows: expand a session and see one row per tab, with the right names | |
| 65 | + | and active marker. Selecting a tab row lands on that tab. | |
| 66 | + | - [ ] MRU order: switch A → B → C, reopen the picker, and check the order is | |
| 67 | + | C, B, A. | |
| 68 | + | - [ ] Bell icons: in a background tab or session run `sleep 2; printf '\a'`, | |
| 69 | + | switch away, and check that the bell icon shows on that session and tab | |
| 70 | + | row. It clears once you visit it. Try it on a remote session too. | |
| 71 | + | - [ ] Live preview: moving the cursor over a local session, then over a remote | |
| 72 | + | one, shows its current screen. Run `ticker` there to check that the | |
| 73 | + | preview updates live. | |
| 74 | + | - [ ] Remote attach and switch: `vev attach demo@remote-a:<session>` from | |
| 75 | + | the shell (this also adds the learned target `demo@remote-a`), then | |
| 76 | + | switch between local, remote-a and remote-b sessions from the picker | |
| 77 | + | without dropping input or output. | |
| 78 | + | - [ ] Broker restart: `scripts/demo/test-remotes.sh broker-restart` stops only | |
| 79 | + | the test broker. Reopen the picker from a running client and check that | |
| 80 | + | hosts and sessions come back. | |
| @@ -0,0 +1,80 @@ | |||||
| 1 | + | # Demo and sandbox scripts | |||
| 2 | + | ||||
| 3 | + | - `run.sh`, `demo.tape`, `smoke.tape`: VHS demo recording. | |||
| 4 | + | - `sandbox-run.sh`: automated acceptance. Client and two remotes all run in | |||
| 5 | + | containers on an internal network. | |||
| 6 | + | - `test-remotes.sh`: two containerised remotes for manual testing from your own | |||
| 7 | + | terminal. | |||
| 8 | + | ||||
| 9 | + | All of them build `build/vev` and bake it into the `scripts/demo/Dockerfile` | |||
| 10 | + | image, so local and remote vev are the same binary. Docker must be rootless. | |||
| 11 | + | ||||
| 12 | + | ## Manual remote testing | |||
| 13 | + | ||||
| 14 | + | ```sh | |||
| 15 | + | scripts/demo/test-remotes.sh up # build, start remote-a/remote-b, register both hosts | |||
| 16 | + | scripts/demo/test-remotes.sh shell # print the client command | |||
| 17 | + | scripts/demo/test-remotes.sh status # containers, ports, vev host list, vev ls --all | |||
| 18 | + | scripts/demo/test-remotes.sh broker-restart # stop only the test broker; the next vev call respawns it | |||
| 19 | + | scripts/demo/test-remotes.sh down # stop the test broker/daemon, remove containers and keys | |||
| 20 | + | ``` | |||
| 21 | + | ||||
| 22 | + | Run the printed command from a real terminal, or `scripts/demo/test-remotes.sh | |||
| 23 | + | shell --exec [vev args]`. It sets: | |||
| 24 | + | ||||
| 25 | + | - `VEV_ENV=test` and `VEV_ENV_ROOT=<checkout>/.dev`: all vev state lives in | |||
| 26 | + | `.dev/test/`, apart from your installed vev. | |||
| 27 | + | - `PATH=.dev/test/bin:$PATH`: vev runs `ssh` from `PATH` and has no ssh config | |||
| 28 | + | option, so `.dev/test/bin/ssh` wraps the real client with | |||
| 29 | + | `-F .dev/test/ssh/config`. The key, `known_hosts` and config are generated in | |||
| 30 | + | `.dev/test/ssh/`. `~/.ssh` and your agent are never used. | |||
| 31 | + | - `VEV_REMOTE_TRANSPORT=stdio`: hosts are registered with the SSH-only carriage. | |||
| 32 | + | QUIC cannot work here. The remote proxy listens on a random UDP port, and | |||
| 33 | + | the client dials that port on the SSH target name (`remote-a`), which does | |||
| 34 | + | not resolve to the loopback port mapping. | |||
| 35 | + | - `-u VEV`: a client started from a pane of your real vev is not treated as | |||
| 36 | + | nested. | |||
| 37 | + | ||||
| 38 | + | sshd is published on `127.0.0.1:2222` (remote-a) and `127.0.0.1:2223` | |||
| 39 | + | (remote-b). Override the ports with `VEV_TEST_SSH_PORT_A` and | |||
| 40 | + | `VEV_TEST_SSH_PORT_B`, and the environment name with `VEV_TEST_ENV`. Plain | |||
| 41 | + | shell access: `.dev/test/bin/ssh remote-a`. | |||
| 42 | + | ||||
| 43 | + | `up` is idempotent: it recreates the containers, reinstalls the key and | |||
| 44 | + | skips hosts that are already registered. A `VEV_ENV=test` broker started by an | |||
| 45 | + | older build keeps running until it is stopped, and `up` warns when it finds | |||
| 46 | + | one. Run `down` then `up` after a rebuild. `down` stops **every** | |||
| 47 | + | `VEV_ENV=test` daemon of this checkout, including sessions you opened | |||
| 48 | + | there by hand. | |||
| 49 | + | ||||
| 50 | + | Remote sessions live in the containers and disappear on `down`. | |||
| 51 | + | ||||
| 52 | + | ### Checklist | |||
| 53 | + | ||||
| 54 | + | Open a few sessions first, with at least two tabs in one of them, local and on | |||
| 55 | + | each remote: `vev new work`, `vev new ra remote-a`, `vev new rb remote-b` | |||
| 56 | + | (each run with the environment above). | |||
| 57 | + | ||||
| 58 | + | - [ ] Picker over a session (`Alt+Space`, `SSP`): opens over the live | |||
| 59 | + | session; `Esc` returns to the same session, tab and pane. | |||
| 60 | + | - [ ] Move pane (`MFP`) and move tab (`MAT`): the destination picker lists | |||
| 61 | + | local and remote sessions and tabs; the move lands where you chose. | |||
| 62 | + | - [ ] `x` in the picker kills the selected session (local and remote); the | |||
| 63 | + | row disappears and `vev ls --all` agrees. | |||
| 64 | + | - [ ] Tab rows: expand a session and see one row per tab, with the right names | |||
| 65 | + | and active marker. Selecting a tab row lands on that tab. | |||
| 66 | + | - [ ] MRU order: switch A → B → C, reopen the picker, and check the order is | |||
| 67 | + | C, B, A. | |||
| 68 | + | - [ ] Bell icons: in a background tab or session run `sleep 2; printf '\a'`, | |||
| 69 | + | switch away, and check that the bell icon shows on that session and tab | |||
| 70 | + | row. It clears once you visit it. Try it on a remote session too. | |||
| 71 | + | - [ ] Live preview: moving the cursor over a local session, then over a remote | |||
| 72 | + | one, shows its current screen. Run `ticker` there to check that the | |||
| 73 | + | preview updates live. | |||
| 74 | + | - [ ] Remote attach and switch: `vev attach demo@remote-a:<session>` from | |||
| 75 | + | the shell (this also adds the learned target `demo@remote-a`), then | |||
| 76 | + | switch between local, remote-a and remote-b sessions from the picker | |||
| 77 | + | without dropping input or output. | |||
| 78 | + | - [ ] Broker restart: `scripts/demo/test-remotes.sh broker-restart` stops only | |||
| 79 | + | the test broker. Reopen the picker from a running client and check that | |||
| 80 | + | hosts and sessions come back. | |||
A scripts/demo/compose.test.yaml +38 -0
| @@ -0,0 +1,38 @@ | |||
| 1 | + | # Two SSH remotes for manual testing from the host terminal. | |
| 2 | + | # Driven by scripts/demo/test-remotes.sh; not used by sandbox-run.sh. | |
| 3 | + | # | |
| 4 | + | # sshd is published on loopback only. QUIC is not published: the remote QUIC | |
| 5 | + | # proxy listens on a random UDP port per bootstrap and the client dials the | |
| 6 | + | # SSH target name, so hosts are registered with VEV_REMOTE_TRANSPORT=stdio. | |
| 7 | + | # The project name is derived from VEV_ENV/VEV_TEST_ENV so distinct sandbox | |
| 8 | + | # environments (see scripts/demo/test-remotes.sh) never share one compose | |
| 9 | + | # project's containers or network. | |
| 10 | + | name: vev-remotes-${VEV_TEST_ENV:-test} | |
| 11 | + | ||
| 12 | + | services: | |
| 13 | + | remote-a: | |
| 14 | + | image: vev-sandbox:${VEV_TEST_TAG:-test} | |
| 15 | + | hostname: remote-a | |
| 16 | + | user: root | |
| 17 | + | entrypoint: ["/usr/bin/sshd", "-D", "-e"] | |
| 18 | + | networks: [remotes] | |
| 19 | + | ports: | |
| 20 | + | - "127.0.0.1:${VEV_TEST_SSH_PORT_A:-2222}:22/tcp" | |
| 21 | + | tmpfs: | |
| 22 | + | - /run | |
| 23 | + | - /tmp | |
| 24 | + | ||
| 25 | + | remote-b: | |
| 26 | + | image: vev-sandbox:${VEV_TEST_TAG:-test} | |
| 27 | + | hostname: remote-b | |
| 28 | + | user: root | |
| 29 | + | entrypoint: ["/usr/bin/sshd", "-D", "-e"] | |
| 30 | + | networks: [remotes] | |
| 31 | + | ports: | |
| 32 | + | - "127.0.0.1:${VEV_TEST_SSH_PORT_B:-2223}:22/tcp" | |
| 33 | + | tmpfs: | |
| 34 | + | - /run | |
| 35 | + | - /tmp | |
| 36 | + | ||
| 37 | + | networks: | |
| 38 | + | remotes: {} | |
| @@ -0,0 +1,38 @@ | |||||
| 1 | + | # Two SSH remotes for manual testing from the host terminal. | |||
| 2 | + | # Driven by scripts/demo/test-remotes.sh; not used by sandbox-run.sh. | |||
| 3 | + | # | |||
| 4 | + | # sshd is published on loopback only. QUIC is not published: the remote QUIC | |||
| 5 | + | # proxy listens on a random UDP port per bootstrap and the client dials the | |||
| 6 | + | # SSH target name, so hosts are registered with VEV_REMOTE_TRANSPORT=stdio. | |||
| 7 | + | # The project name is derived from VEV_ENV/VEV_TEST_ENV so distinct sandbox | |||
| 8 | + | # environments (see scripts/demo/test-remotes.sh) never share one compose | |||
| 9 | + | # project's containers or network. | |||
| 10 | + | name: vev-remotes-${VEV_TEST_ENV:-test} | |||
| 11 | + | ||||
| 12 | + | services: | |||
| 13 | + | remote-a: | |||
| 14 | + | image: vev-sandbox:${VEV_TEST_TAG:-test} | |||
| 15 | + | hostname: remote-a | |||
| 16 | + | user: root | |||
| 17 | + | entrypoint: ["/usr/bin/sshd", "-D", "-e"] | |||
| 18 | + | networks: [remotes] | |||
| 19 | + | ports: | |||
| 20 | + | - "127.0.0.1:${VEV_TEST_SSH_PORT_A:-2222}:22/tcp" | |||
| 21 | + | tmpfs: | |||
| 22 | + | - /run | |||
| 23 | + | - /tmp | |||
| 24 | + | ||||
| 25 | + | remote-b: | |||
| 26 | + | image: vev-sandbox:${VEV_TEST_TAG:-test} | |||
| 27 | + | hostname: remote-b | |||
| 28 | + | user: root | |||
| 29 | + | entrypoint: ["/usr/bin/sshd", "-D", "-e"] | |||
| 30 | + | networks: [remotes] | |||
| 31 | + | ports: | |||
| 32 | + | - "127.0.0.1:${VEV_TEST_SSH_PORT_B:-2223}:22/tcp" | |||
| 33 | + | tmpfs: | |||
| 34 | + | - /run | |||
| 35 | + | - /tmp | |||
| 36 | + | ||||
| 37 | + | networks: | |||
| 38 | + | remotes: {} | |||
A scripts/demo/test-remotes.sh +304 -0
| @@ -0,0 +1,304 @@ | |||
| 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 | |
| 16 | + | set -euo pipefail | |
| 17 | + | ||
| 18 | + | cd "$(dirname "$0")/../.." | |
| 19 | + | root=$(pwd -P) | |
| 20 | + | ||
| 21 | + | export VEV_TEST_TAG=${VEV_TEST_TAG:-test} | |
| 22 | + | env_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. | |
| 27 | + | export VEV_TEST_ENV="$env_name" | |
| 28 | + | export VEV_TEST_SSH_PORT_A=${VEV_TEST_SSH_PORT_A:-2222} | |
| 29 | + | export VEV_TEST_SSH_PORT_B=${VEV_TEST_SSH_PORT_B:-2223} | |
| 30 | + | ||
| 31 | + | image="vev-sandbox:${VEV_TEST_TAG}" | |
| 32 | + | compose=(docker compose -f scripts/demo/compose.test.yaml) | |
| 33 | + | hosts=(remote-a remote-b) | |
| 34 | + | env_root="$root/.dev" | |
| 35 | + | test_dir="$env_root/$env_name" | |
| 36 | + | ssh_dir="$test_dir/ssh" | |
| 37 | + | bin_dir="$test_dir/bin" | |
| 38 | + | vev_bin="$root/build/vev" | |
| 39 | + | ||
| 40 | + | die() { echo "test-remotes: error: $*" >&2; exit 1; } | |
| 41 | + | log() { echo "test-remotes: $*" >&2; } | |
| 42 | + | ||
| 43 | + | port_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. | |
| 53 | + | vev_env() { | |
| 54 | + | env -u VEV -u VEV_ENV_ROOT \ | |
| 55 | + | VEV_ENV="$env_name" VEV_ENV_ROOT="$env_root" VEV_REMOTE_TRANSPORT=stdio \ | |
| 56 | + | PATH="$bin_dir:$PATH" "$@" | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | need_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 | + | ||
| 64 | + | real_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 | + | ||
| 71 | + | write_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 | + | ||
| 85 | + | Host $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 | |
| 99 | + | EOF | |
| 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. | |
| 106 | + | exec $(real_ssh) -F "$ssh_dir/config" "\$@" | |
| 107 | + | EOF | |
| 108 | + | chmod 700 "$bin_dir/ssh" | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | container_for() { | |
| 112 | + | "${compose[@]}" ps -q "$1" | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | install_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 | + | ||
| 130 | + | wait_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 | + | ||
| 147 | + | register_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 "$host" || die "vev host add $host failed" | |
| 156 | + | log "host registered: $host" | |
| 157 | + | done | |
| 158 | + | } | |
| 159 | + | ||
| 160 | + | cmd_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 | + | ||
| 188 | + | cmd_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. | |
| 207 | + | vev_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 | + | ||
| 222 | + | cmd_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" kill --daemon >/dev/null 2>&1 || true | |
| 231 | + | fi | |
| 232 | + | # vev has no broker stop command: 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. | |
| 259 | + | cmd_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 | + | ||
| 276 | + | cmd_shell_hint() { | |
| 277 | + | cat <<EOF | |
| 278 | + | Sandbox ready. Launch the client from any terminal with: | |
| 279 | + | ||
| 280 | + | cd $root && env -u VEV VEV_ENV=$env_name VEV_ENV_ROOT=$env_root VEV_REMOTE_TRANSPORT=stdio PATH=$bin_dir:\$PATH ./build/vev | |
| 281 | + | ||
| 282 | + | or: $0 shell --exec | |
| 283 | + | Plain ssh: $bin_dir/ssh remote-a | |
| 284 | + | EOF | |
| 285 | + | } | |
| 286 | + | ||
| 287 | + | cmd_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" VEV_REMOTE_TRANSPORT=stdio \ | |
| 292 | + | PATH="$bin_dir:$PATH" "$vev_bin" "$@" | |
| 293 | + | fi | |
| 294 | + | cmd_shell_hint | |
| 295 | + | } | |
| 296 | + | ||
| 297 | + | case "${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 ;; | |
| 304 | + | esac | |
| @@ -0,0 +1,304 @@ | |||||
| 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 | |||
| 16 | + | set -euo pipefail | |||
| 17 | + | ||||
| 18 | + | cd "$(dirname "$0")/../.." | |||
| 19 | + | root=$(pwd -P) | |||
| 20 | + | ||||
| 21 | + | export VEV_TEST_TAG=${VEV_TEST_TAG:-test} | |||
| 22 | + | env_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. | |||
| 27 | + | export VEV_TEST_ENV="$env_name" | |||
| 28 | + | export VEV_TEST_SSH_PORT_A=${VEV_TEST_SSH_PORT_A:-2222} | |||
| 29 | + | export VEV_TEST_SSH_PORT_B=${VEV_TEST_SSH_PORT_B:-2223} | |||
| 30 | + | ||||
| 31 | + | image="vev-sandbox:${VEV_TEST_TAG}" | |||
| 32 | + | compose=(docker compose -f scripts/demo/compose.test.yaml) | |||
| 33 | + | hosts=(remote-a remote-b) | |||
| 34 | + | env_root="$root/.dev" | |||
| 35 | + | test_dir="$env_root/$env_name" | |||
| 36 | + | ssh_dir="$test_dir/ssh" | |||
| 37 | + | bin_dir="$test_dir/bin" | |||
| 38 | + | vev_bin="$root/build/vev" | |||
| 39 | + | ||||
| 40 | + | die() { echo "test-remotes: error: $*" >&2; exit 1; } | |||
| 41 | + | log() { echo "test-remotes: $*" >&2; } | |||
| 42 | + | ||||
| 43 | + | port_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. | |||
| 53 | + | vev_env() { | |||
| 54 | + | env -u VEV -u VEV_ENV_ROOT \ | |||
| 55 | + | VEV_ENV="$env_name" VEV_ENV_ROOT="$env_root" VEV_REMOTE_TRANSPORT=stdio \ | |||
| 56 | + | PATH="$bin_dir:$PATH" "$@" | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | need_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 | + | ||||
| 64 | + | real_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 | + | ||||
| 71 | + | write_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 | + | ||||
| 85 | + | Host $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 | |||
| 99 | + | EOF | |||
| 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. | |||
| 106 | + | exec $(real_ssh) -F "$ssh_dir/config" "\$@" | |||
| 107 | + | EOF | |||
| 108 | + | chmod 700 "$bin_dir/ssh" | |||
| 109 | + | } | |||
| 110 | + | ||||
| 111 | + | container_for() { | |||
| 112 | + | "${compose[@]}" ps -q "$1" | |||
| 113 | + | } | |||
| 114 | + | ||||
| 115 | + | install_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 | + | ||||
| 130 | + | wait_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 | + | ||||
| 147 | + | register_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 "$host" || die "vev host add $host failed" | |||
| 156 | + | log "host registered: $host" | |||
| 157 | + | done | |||
| 158 | + | } | |||
| 159 | + | ||||
| 160 | + | cmd_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 | + | ||||
| 188 | + | cmd_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. | |||
| 207 | + | vev_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 | + | ||||
| 222 | + | cmd_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" kill --daemon >/dev/null 2>&1 || true | |||
| 231 | + | fi | |||
| 232 | + | # vev has no broker stop command: 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. | |||
| 259 | + | cmd_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 | + | ||||
| 276 | + | cmd_shell_hint() { | |||
| 277 | + | cat <<EOF | |||
| 278 | + | Sandbox ready. Launch the client from any terminal with: | |||
| 279 | + | ||||
| 280 | + | cd $root && env -u VEV VEV_ENV=$env_name VEV_ENV_ROOT=$env_root VEV_REMOTE_TRANSPORT=stdio PATH=$bin_dir:\$PATH ./build/vev | |||
| 281 | + | ||||
| 282 | + | or: $0 shell --exec | |||
| 283 | + | Plain ssh: $bin_dir/ssh remote-a | |||
| 284 | + | EOF | |||
| 285 | + | } | |||
| 286 | + | ||||
| 287 | + | cmd_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" VEV_REMOTE_TRANSPORT=stdio \ | |||
| 292 | + | PATH="$bin_dir:$PATH" "$vev_bin" "$@" | |||
| 293 | + | fi | |||
| 294 | + | cmd_shell_hint | |||
| 295 | + | } | |||
| 296 | + | ||||
| 297 | + | case "${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 ;; | |||
| 304 | + | esac | |||