3fb9ee75
test: make xos takes its three boxes by name
a73x 2026-09-04 12:50
Commit message
test/xos.sh
| Old | New | ||
|---|---|---|---|
| @@ -8,28 +8,29 @@ | |||
| 8 | # QUIC, and a cross-architecture `mux d upgrade` is refused in both | 8 | # QUIC, and a cross-architecture `mux d upgrade` is refused in both |
| 9 | # directions before a byte of the image moves. | 9 | # directions before a byte of the image moves. |
| 10 | # | 10 | # |
| 11 | # Three machines, and each one has a job: | 11 | # Four machines, taken by name (test/box_lib.sh), and each one has a job: |
| 12 | # | 12 | # |
| 13 | # THIS BOX Linux x86_64, the orchestrator and the Linux CLIENT. It | 13 | # THIS BOX Linux x86_64, the orchestrator and the Linux CLIENT. It |
| 14 | # drives the Linux→Mac legs with this tree's own `mux` and | 14 | # drives the Linux→Mac legs with this tree's own `mux` and |
| 15 | # `ptyclient`, and it builds the static-musl image the Linux | 15 | # `ptyclient`, and it builds the static-musl image the Linux |
| 16 | # daemon box installs. | 16 | # daemon box installs. |
| 17 | # $MUX_MAC an Apple-silicon Mac (default `squirtle`). It holds the | 17 | # $MAC_BUILDER a Mac with the toolchain and the shadow SDK. It BUILDS the |
| 18 | # toolchain and the shadow SDK, so it builds its own arm64 | 18 | # arm64 image and does nothing else: no mux is installed |
| 19 | # image; `make install` puts it at ~/.local/bin/mux, which is | 19 | # there and nothing is scrubbed there. |
| 20 | # where the appended PATH of an ssh handoff looks for it. It is | 20 | # $MAC_BOX an Apple-silicon macOS box with no toolchain on it, which |
| 21 | # a person's real machine, so this script kills nothing on it | 21 | # is where that image is RUN. Normally a bridged tart guest |
| 22 | # and removes nothing under ~ but the directory it made. | 22 | # from test/provision-mac.sh. |
| 23 | # $MUX_LAN a Linux VM the Mac can reach (default ubuntu@192.168.0.37). | 23 | # $LINUX_BOX an x86_64 Linux VM both this box and $MAC_BOX can reach. |
| 24 | # The e2e VM is on this box's private network and the Mac | 24 | # The e2e VM is on this box's private network and a Mac |
| 25 | # cannot see it, which is the whole reason a LAN box exists. | 25 | # cannot see it, which is the whole reason a second Linux |
| 26 | # This one IS a throwaway, so it gets test/vm.sh's | 26 | # box exists. |
| 27 | # scorched-earth reset. | ||
| 28 | # | 27 | # |
| 29 | # The fixture is a human action, never harness code (test/vm.sh's rule): | 28 | # MAC_BOX and LINUX_BOX are VMs the gate MAY scrub, and box_scrub is the |
| 30 | # the VM is made once with eitri `vm_create {name: mux-lan, host: onyx, | 29 | # whole of what that means; it runs at the start of the run and again on the |
| 31 | # network: lan}`, and the Mac is somebody's laptop. What the harness owns is | 30 | # way out. The boxes themselves are a human action, never harness code |
| 32 | # the reset, the install and the key. | 31 | # (test/vm.sh's rule): the Linux VM is made once with eitri, and the Mac |
| 32 | # guest by `eval "$(test/provision-mac.sh)"`. What the harness owns is the | ||
| 33 | # scrub, the pairing, the install and the key. | ||
| 33 | # | 34 | # |
| 34 | # Every claim about a box is asserted by a SEPARATE ssh reading that box | 35 | # Every claim about a box is asserted by a SEPARATE ssh reading that box |
| 35 | # through test/os_oracle.sh — never by trusting the connection under test, | 36 | # through test/os_oracle.sh — never by trusting the connection under test, |
| @@ -37,17 +38,25 @@ | |||
| 37 | # OS", CLAUDE.md). | 38 | # OS", CLAUDE.md). |
| 38 | set -eu | 39 | set -eu |
| 39 | 40 | ||
| 40 | MUSL_MUX="$1" # x86_64-linux-musl ReleaseSafe: the image installed on $MUX_LAN | 41 | BOX_GATE=xos |
| 42 | |||
| 43 | MUSL_MUX="$1" # x86_64-linux-musl ReleaseSafe: the image installed on $LINUX_BOX | ||
| 41 | MUX="$2" # this tree's own build: the Linux client for the Linux→Mac legs | 44 | MUX="$2" # this tree's own build: the Linux client for the Linux→Mac legs |
| 42 | PTYCLIENT="$3" # the real-pty fixture, driving a client the way hands do | 45 | PTYCLIENT="$3" # the real-pty fixture, driving a client the way hands do |
| 43 | 46 | ||
| 44 | MAC="${MUX_MAC:-squirtle}" | 47 | MAC_BOX=${MAC_BOX:-} |
| 45 | MAC_IP="${MUX_MAC_IP:-192.168.0.47}" | 48 | LINUX_BOX=${LINUX_BOX:-} |
| 46 | LAN="${MUX_LAN:-ubuntu@192.168.0.37}" | 49 | # The one Mac that is both is the common case, so the builder follows the |
| 47 | LAN_IP=${LAN#*@} | 50 | # box under test unless it is named. |
| 48 | # Relative to the Mac's $HOME, because that is how ssh's scp-like remote | 51 | MAC_BUILDER=${MAC_BUILDER:-$MAC_BOX} |
| 49 | # spelling (`squirtle:code/rad/mux`) resolves a path. | 52 | # The host halves are what the QUIC legs dial: QUIC has no ssh config to |
| 50 | REPO="${MUX_MAC_REPO:-code/rad/mux}" | 53 | # read, so a MAC_BOX or LINUX_BOX spelled with a name rather than an address |
| 54 | # must be a name this box resolves. | ||
| 55 | MAC_IP=${MAC_BOX#*@} | ||
| 56 | LAN_IP=${LINUX_BOX#*@} | ||
| 57 | # Relative to the BUILDER's $HOME, because that is how ssh's scp-like remote | ||
| 58 | # spelling (`HOST:code/rad/mux`) resolves a path. | ||
| 59 | REPO="${MAC_BUILDER_REPO:-code/rad/mux}" | ||
| 51 | BRANCH=mac-journey | 60 | BRANCH=mac-journey |
| 52 | QUIC_PORT=4433 | 61 | QUIC_PORT=4433 |
| 53 | # The port as /proc/net/udp writes a wildcard bind, which is the spelling | 62 | # The port as /proc/net/udp writes a wildcard bind, which is the spelling |
| @@ -56,18 +65,19 @@ QUIC_PORT=4433 | |||
| 56 | # out, because a hand-typed hex goes stale the moment somebody edits the port | 65 | # out, because a hand-typed hex goes stale the moment somebody edits the port |
| 57 | # above it; leg 7 spells the same derivation for the port a Mac announces. | 66 | # above it; leg 7 spells the same derivation for the port a Mac announces. |
| 58 | QUIC_HEX=$(printf '00000000:%04X' "$QUIC_PORT") | 67 | QUIC_HEX=$(printf '00000000:%04X' "$QUIC_PORT") |
| 68 | WANT_MAC_ARCH=aarch64 | ||
| 69 | WANT_LAN_ARCH=x86_64 | ||
| 59 | 70 | ||
| 60 | # BatchMode so a broken auth fails in seconds rather than prompting a | 71 | # now_ms for the leg clock, and sha256_of for the installed-image hashes. |
| 61 | # harness nobody sits in front of; accept-new so the first run records a | ||
| 62 | # box's host key where PLAIN ssh will find it, because mux's own entry dial | ||
| 63 | # spawns plain `ssh $HOST` and reads that same file. | ||
| 64 | SSH_OPTS="-o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new" | ||
| 65 | |||
| 66 | # now_ms for the leg clock, and sha256_of for the two installed-image hashes. | ||
| 67 | # The pid_*, file_* and udp_* names are sourced by the REMOTE legs from the | 72 | # The pid_*, file_* and udp_* names are sourced by the REMOTE legs from the |
| 68 | # copy beside the binary on each box, which is what keeps a leg from having to | 73 | # copy beside the binary on each box, which is what keeps a leg from having to |
| 69 | # know which OS is answering it. | 74 | # know which OS is answering it. |
| 70 | . "$(dirname "$0")/os_oracle.sh" | 75 | . "$(dirname "$0")/os_oracle.sh" |
| 76 | . "$(dirname "$0")/box_lib.sh" | ||
| 77 | |||
| 78 | # A provisioned Mac guest has a new host key every clone, so the harness's | ||
| 79 | # own ssh to it keeps none. mux's ssh is the exception handled below. | ||
| 80 | BOX_EPHEMERAL=$MAC_BOX | ||
| 71 | 81 | ||
| 72 | OK_COUNT=0 | 82 | OK_COUNT=0 |
| 73 | LEG_T0=0 | 83 | LEG_T0=0 |
| @@ -97,6 +107,10 @@ fail() { | |||
| 97 | # Ubuntu (/run/user/1000) and UNSET on macOS, where the daemon falls back to | 107 | # Ubuntu (/run/user/1000) and UNSET on macOS, where the daemon falls back to |
| 98 | # /tmp/mux-<uid>. That fallback is what the Linux→Mac legs exercise, and the | 108 | # /tmp/mux-<uid>. That fallback is what the Linux→Mac legs exercise, and the |
| 99 | # entry dial's own `mux d endpoint --start` will look nowhere else. | 109 | # entry dial's own `mux d endpoint --start` will look nowhere else. |
| 110 | # | ||
| 111 | # The builder gets the same prelude, where its only effect is a scratch | ||
| 112 | # directory the cleanup removes: the build legs cd into the repo themselves, | ||
| 113 | # and no mux ever runs there. | ||
| 100 | PRELUDE=' | 114 | PRELUDE=' |
| 101 | export XDG_STATE_HOME=$HOME/xos/state | 115 | export XDG_STATE_HOME=$HOME/xos/state |
| 102 | export XDG_CACHE_HOME=$HOME/xos/cache | 116 | export XDG_CACHE_HOME=$HOME/xos/cache |
| @@ -105,81 +119,59 @@ mkdir -p $HOME/xos | |||
| 105 | cd $HOME | 119 | cd $HOME |
| 106 | ' | 120 | ' |
| 107 | 121 | ||
| 108 | # mssh SECONDS — run the script on stdin on the Mac, under /bin/sh. | 122 | # ---- preflight: every box answers, before anything is written ---------- |
| 109 | # | 123 | box_preflight MAC_BOX "$MAC_BOX" "$WANT_MAC_ARCH" \ |
| 110 | # /bin/sh and not the Mac's login shell (zsh): `$VAR` holding several words | 124 | 'Provision one with: eval "$(test/provision-mac.sh)"' |
| 111 | # does not word-split in zsh, so an options string pasted into a command | 125 | box_preflight LINUX_BOX "$LINUX_BOX" "$WANT_LAN_ARCH" \ |
| 112 | # line there arrives as ONE argument and ssh answers "keyword batchmode | 126 | 'An x86_64 Linux VM this box and MAC_BOX can both ssh to.' |
| 113 | # extra arguments at end of line". | 127 | box_preflight MAC_BUILDER "$MAC_BUILDER" "$WANT_MAC_ARCH" \ |
| 114 | # | 128 | 'A Mac holding this repo with zig and deps/mac-sdk. Defaults to $MAC_BOX.' |
| 115 | # The rule for a script handed to this: anything INSIDE it that reads stdin | ||
| 116 | # gets it closed — `-n` for an ssh, `</dev/null` for the rest. The remote sh | ||
| 117 | # is reading its own script off stdin, and a command that drains stdin | ||
| 118 | # swallows the lines after it, silently and with a 0 exit, so they simply | ||
| 119 | # never run. `mux d endpoint` is one of those: it announces on stdout and | ||
| 120 | # then PROXIES, which is the whole point of the pipe that carried the | ||
| 121 | # announce being the QUIC fallback, and it ate the two assertions after it | ||
| 122 | # the first time this gate ran leg 7. | ||
| 123 | # | ||
| 124 | # The quoting rule is that there is NO quoting: the script rides this | ||
| 125 | # shell's stdin all the way to the remote `sh -s` and no shell in between | ||
| 126 | # parses it as a command line. The one thing a caller owes it is a heredoc; | ||
| 127 | # called with no redirect it would read the enclosing script's stdin. | ||
| 128 | mssh() { | ||
| 129 | _mt=$1 | ||
| 130 | # shellcheck disable=SC2086 # SSH_OPTS is words on purpose | ||
| 131 | { printf '%s\n' "$PRELUDE"; cat; } | timeout "$_mt" ssh $SSH_OPTS "$MAC" /bin/sh -s | ||
| 132 | } | ||
| 133 | 129 | ||
| 134 | # lssh SECONDS — the same, onto the Linux daemon box. | 130 | # The entry dial in leg 7 spawns a PLAIN `ssh $MAC_BOX` — mux's own, with |
| 135 | lssh() { | 131 | # mux's own options — and that one reads the real ~/.ssh/known_hosts. A |
| 136 | _lt=$1 | 132 | # freshly provisioned guest has a host key nobody has seen, at an address a |
| 137 | # shellcheck disable=SC2086 # SSH_OPTS is words on purpose | 133 | # previous guest may have held under a different one, so the stale entry is |
| 138 | { printf '%s\n' "$PRELUDE"; cat; } | timeout "$_lt" ssh $SSH_OPTS "$LAN" /bin/sh -s | 134 | # forgotten and the new key learned once, here, where a failure is a |
| 139 | } | 135 | # preflight and not a leg timing out with nothing to say. |
| 136 | ssh-keygen -R "$MAC_IP" >/dev/null 2>&1 || true | ||
| 137 | timeout 20 ssh -n -o BatchMode=yes -o ConnectTimeout=10 \ | ||
| 138 | -o StrictHostKeyChecking=accept-new "$MAC_BOX" true || | ||
| 139 | fail preflight "cannot record $MAC_BOX's host key for the plain ssh mux spawns" | ||
| 140 | |||
| 141 | # Both directions of this gate dial the other box from INSIDE a box: the | ||
| 142 | # macOS client's entry dial spawns ssh at the Linux daemon, and leg 9's | ||
| 143 | # `mux d upgrade` spawns ssh back the other way. | ||
| 144 | box_pair "$MAC_BOX" "$LINUX_BOX" | ||
| 145 | box_pair "$LINUX_BOX" "$MAC_BOX" | ||
| 140 | 146 | ||
| 141 | # ---- the local side is a hermetic laptop ------------------------------ | 147 | # ---- the local side is a hermetic laptop ------------------------------ |
| 142 | # Every XDG dir mux reads, isolated. Short root because a unix socket path | 148 | # Every XDG dir mux reads, isolated. Short root because a unix socket path |
| 143 | # caps at 107 bytes. | 149 | # caps at 107 bytes. |
| 144 | # Whether the run is responsible for ~/.config/mux on the Mac. Leg 7's entry | ||
| 145 | # dial makes the Mac run `mux d endpoint`, which CREATES the default key file | ||
| 146 | # when there is none — the product's own behaviour on any box a person dials, | ||
| 147 | # not something the harness can spell away, since mux writes that ssh line | ||
| 148 | # itself. Recorded in leg 3 and undone in the cleanup, so a Mac that had no | ||
| 149 | # mux key before this gate has none after it. | ||
| 150 | MAC_CFG_MADE=no | ||
| 151 | SCRATCH=$(mktemp -d /tmp/muxxos.XXXXXX) | 150 | SCRATCH=$(mktemp -d /tmp/muxxos.XXXXXX) |
| 152 | export XDG_STATE_HOME="$SCRATCH/st" XDG_RUNTIME_DIR="$SCRATCH/rt" | 151 | export XDG_STATE_HOME="$SCRATCH/st" XDG_RUNTIME_DIR="$SCRATCH/rt" |
| 153 | export XDG_CONFIG_HOME="$SCRATCH/cf" XDG_CACHE_HOME="$SCRATCH/ca" | 152 | export XDG_CONFIG_HOME="$SCRATCH/cf" XDG_CACHE_HOME="$SCRATCH/ca" |
| 154 | mkdir -p "$XDG_STATE_HOME" "$XDG_RUNTIME_DIR" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" | 153 | mkdir -p "$XDG_STATE_HOME" "$XDG_RUNTIME_DIR" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" |
| 155 | 154 | ||
| 156 | # Whatever this run started, stopped — on both boxes, on every exit. The | 155 | # Both boxes back to the mux-less state a new user has, on every exit. The |
| 157 | # Mac's ~/.local/bin/mux is deliberately NOT removed: `make install` owns | 156 | # builder is not scrubbed — it is not this gate's machine to reset — so its |
| 158 | # it, and a person may want the binary this run put there. | 157 | # scratch directory is all there is to take back there. |
| 159 | cleanup() { | 158 | cleanup() { |
| 160 | _rc=$? | 159 | _rc=$? |
| 161 | lssh 60 >/dev/null 2>&1 <<'L' || true | 160 | box_scrub "$LINUX_BOX" >/dev/null 2>&1 || true |
| 162 | ~/.local/bin/mux d stop 2>/dev/null || true | 161 | box_scrub "$MAC_BOX" >/dev/null 2>&1 || true |
| 163 | sleep 0.3 | 162 | box_ssh 60 "$MAC_BUILDER" >/dev/null 2>&1 <<'B' || true |
| 164 | pkill -x mux 2>/dev/null || true | 163 | rm -rf "$HOME/xos" |
| 165 | rm -rf ~/xos | 164 | B |
| 166 | L | ||
| 167 | mssh 60 >/dev/null 2>&1 <<M || true | ||
| 168 | \$HOME/.local/bin/mux d stop 2>/dev/null || true | ||
| 169 | rm -rf \$HOME/xos | ||
| 170 | [ "$MAC_CFG_MADE" = yes ] && rm -rf \$HOME/.config/mux | ||
| 171 | exit 0 | ||
| 172 | M | ||
| 173 | rm -rf "$SCRATCH" | 165 | rm -rf "$SCRATCH" |
| 174 | exit "$_rc" | 166 | exit "$_rc" |
| 175 | } | 167 | } |
| 176 | trap cleanup EXIT | 168 | trap cleanup EXIT |
| 177 | 169 | ||
| 178 | # ---- leg 1: push — the Mac builds THIS commit -------------------------- | 170 | # ---- leg 1: push — the builder builds THIS commit ----------------------- |
| 179 | leg | 171 | leg |
| 180 | HEAD_SHA=$(git rev-parse HEAD) | 172 | HEAD_SHA=$(git rev-parse HEAD) |
| 181 | PUSHED=no | 173 | PUSHED=no |
| 182 | HAVE=$(mssh 60 <<H || true | 174 | HAVE=$(box_ssh 60 "$MAC_BUILDER" <<H || true |
| 183 | cd \$HOME/$REPO 2>/dev/null || exit 0 | 175 | cd \$HOME/$REPO 2>/dev/null || exit 0 |
| 184 | git rev-parse -q --verify refs/heads/$BRANCH 2>/dev/null || true | 176 | git rev-parse -q --verify refs/heads/$BRANCH 2>/dev/null || true |
| 185 | H | 177 | H |
| @@ -188,7 +180,7 @@ if [ "$HAVE" != "$HEAD_SHA" ]; then | |||
| 188 | # A push cannot update the branch the remote has checked out, and the | 180 | # A push cannot update the branch the remote has checked out, and the |
| 189 | # run before this one left it checked out. Detaching first is what makes | 181 | # run before this one left it checked out. Detaching first is what makes |
| 190 | # a re-run work; it changes no commit and no file. | 182 | # a re-run work; it changes no commit and no file. |
| 191 | mssh 60 >/dev/null <<H || fail push "cannot detach $MAC:$REPO before the push" | 183 | box_ssh 60 "$MAC_BUILDER" >/dev/null <<H || fail push "cannot detach $MAC_BUILDER:$REPO before the push" |
| 192 | set -e | 184 | set -e |
| 193 | cd \$HOME/$REPO | 185 | cd \$HOME/$REPO |
| 194 | if [ "\$(git rev-parse --abbrev-ref HEAD)" = "$BRANCH" ]; then | 186 | if [ "\$(git rev-parse --abbrev-ref HEAD)" = "$BRANCH" ]; then |
| @@ -196,153 +188,106 @@ if [ "\$(git rev-parse --abbrev-ref HEAD)" = "$BRANCH" ]; then | |||
| 196 | fi | 188 | fi |
| 197 | H | 189 | H |
| 198 | # --force because this branch is a scratch mirror of whatever this tree | 190 | # --force because this branch is a scratch mirror of whatever this tree |
| 199 | # is and never shared history: a local amend or rebase leaves the Mac's | 191 | # is and never shared history: a local amend or rebase leaves the |
| 200 | # ref on a commit the new HEAD does not descend from, and a plain push | 192 | # builder's ref on a commit the new HEAD does not descend from, and a |
| 201 | # would be rejected as a non-fast-forward for the rest of the branch's | 193 | # plain push would be rejected as a non-fast-forward for the rest of the |
| 202 | # life. | 194 | # branch's life. |
| 203 | git push --force "$MAC:$REPO" "HEAD:refs/heads/$BRANCH" >/dev/null 2>&1 || | 195 | git push --force "$MAC_BUILDER:$REPO" "HEAD:refs/heads/$BRANCH" >/dev/null 2>&1 || |
| 204 | fail push "git push $MAC:$REPO HEAD:refs/heads/$BRANCH was refused" | 196 | fail push "git push $MAC_BUILDER:$REPO HEAD:refs/heads/$BRANCH was refused" |
| 205 | PUSHED=yes | 197 | PUSHED=yes |
| 206 | fi | 198 | fi |
| 207 | mssh 60 >/dev/null <<H || fail push "the Mac could not check out $BRANCH" | 199 | box_ssh 60 "$MAC_BUILDER" >/dev/null <<H || fail push "the builder could not check out $BRANCH" |
| 208 | set -e | 200 | set -e |
| 209 | cd \$HOME/$REPO | 201 | cd \$HOME/$REPO |
| 210 | git checkout -q $BRANCH | 202 | git checkout -q $BRANCH |
| 211 | git reset -q --hard $BRANCH | 203 | git reset -q --hard $BRANCH |
| 212 | H | 204 | H |
| 213 | MAC_SHA_GIT=$(mssh 60 <<H | 205 | BUILDER_SHA_GIT=$(box_ssh 60 "$MAC_BUILDER" <<H |
| 214 | cd \$HOME/$REPO && git rev-parse HEAD | 206 | cd \$HOME/$REPO && git rev-parse HEAD |
| 215 | H | 207 | H |
| 216 | ) | 208 | ) |
| 217 | [ "$MAC_SHA_GIT" = "$HEAD_SHA" ] || | 209 | [ "$BUILDER_SHA_GIT" = "$HEAD_SHA" ] || |
| 218 | fail push "the Mac is at $MAC_SHA_GIT, this tree is at $HEAD_SHA" | 210 | fail push "the builder is at $BUILDER_SHA_GIT, this tree is at $HEAD_SHA" |
| 219 | ok push "$MAC:$REPO on $BRANCH at $(git rev-parse --short HEAD), pushed=$PUSHED" | 211 | ok push "$MAC_BUILDER:$REPO on $BRANCH at $(git rev-parse --short HEAD), pushed=$PUSHED" |
| 220 | 212 | ||
| 221 | # ---- leg 2: build — one image per architecture ------------------------- | 213 | # ---- leg 2: build — one image per architecture ------------------------- |
| 222 | # The Mac builds its own arm64 binary and installs it where an ssh | 214 | # The builder builds its own arm64 binary and leg 3 puts it on the box that |
| 223 | # handoff's appended PATH finds it. The shim PATH is required for the link | 215 | # has no toolchain. The shim PATH is required for the link and Homebrew is |
| 224 | # and Homebrew is not on a non-interactive PATH, so both are spelled here. | 216 | # not on a non-interactive PATH, so both are spelled here. |
| 225 | leg | 217 | leg |
| 226 | # XDG_CACHE_HOME back to the Mac's own for these two: zig's GLOBAL package | 218 | # XDG_CACHE_HOME back to the builder's own for this one: zig's GLOBAL package |
| 227 | # cache lives under it, and building into a fresh one re-fetches ghostty and | 219 | # cache lives under it, and building into a fresh one re-fetches ghostty and |
| 228 | # the rest of the package set on every run — minutes, for isolation this leg | 220 | # the rest of the package set on every run — minutes, for isolation this leg |
| 229 | # does not need. What the prelude isolates is mux's own cache directory, and | 221 | # does not need. What the prelude isolates is mux's own cache directory, and |
| 230 | # no mux runs here. | 222 | # no mux runs here. |
| 231 | BOUT=$(mssh 2400 <<H | 223 | BOUT=$(box_ssh 2400 "$MAC_BUILDER" <<H |
| 232 | cd \$HOME/$REPO || { echo "RC_BUILD=90"; exit 0; } | 224 | cd \$HOME/$REPO || { echo "RC_BUILD=90"; exit 0; } |
| 233 | unset XDG_CACHE_HOME | 225 | unset XDG_CACHE_HOME |
| 234 | export PATH=\$HOME/$REPO/deps/mac-sdk/bin:/opt/homebrew/bin:\$PATH | 226 | export PATH=\$HOME/$REPO/deps/mac-sdk/bin:/opt/homebrew/bin:\$PATH |
| 235 | make build >/tmp/xos-build.log 2>&1; echo "RC_BUILD=\$?" | 227 | make build >/tmp/xos-build.log 2>&1; echo "RC_BUILD=\$?" |
| 236 | make install >/tmp/xos-install.log 2>&1; echo "RC_INSTALL=\$?" | ||
| 237 | H | 228 | H |
| 238 | ) | 229 | ) |
| 239 | for _step in BUILD INSTALL; do | 230 | BRC=$(printf '%s\n' "$BOUT" | sed -n 's/^RC_BUILD=//p') |
| 240 | _rc=$(printf '%s\n' "$BOUT" | sed -n "s/^RC_$_step=//p") | 231 | [ "$BRC" = "0" ] || { |
| 241 | [ "$_rc" = "0" ] && continue | ||
| 242 | _log=$(printf '%s' "$_step" | tr 'A-Z' 'a-z') | ||
| 243 | # The verdict first and the reading under it, test/vm.sh's shape: a | 232 | # The verdict first and the reading under it, test/vm.sh's shape: a |
| 244 | # reader scrolling back finds the sentence that says what broke, not the | 233 | # reader scrolling back finds the sentence that says what broke, not the |
| 245 | # tail of somebody else's build log. | 234 | # tail of somebody else's build log. |
| 246 | echo "xos FAIL: build: make $_log on $MAC exited ${_rc:-<no answer>}" | 235 | echo "xos FAIL: build: make build on $MAC_BUILDER exited ${BRC:-<no answer>}" |
| 247 | mssh 60 <<H | sed 's/^/ /' || true | 236 | box_ssh 60 "$MAC_BUILDER" <<'H' | sed 's/^/ /' || true |
| 248 | tail -30 /tmp/xos-$_log.log 2>/dev/null || true | 237 | tail -30 /tmp/xos-build.log 2>/dev/null || true |
| 249 | H | 238 | H |
| 250 | exit 1 | 239 | exit 1; } |
| 251 | done | 240 | BINFO=$(box_ssh 120 "$MAC_BUILDER" <<H |
| 252 | # The oracle goes beside the binary rather than being read out of the repo, | ||
| 253 | # so both remote boxes answer through the same path and a leg never has to | ||
| 254 | # know which one it is talking to. | ||
| 255 | MAC_INFO=$(mssh 120 <<H | ||
| 256 | set -e | 241 | set -e |
| 257 | mkdir -p \$HOME/xos | 242 | cd \$HOME/$REPO |
| 258 | cp \$HOME/$REPO/test/os_oracle.sh \$HOME/xos/os_oracle.sh | 243 | . ./test/os_oracle.sh |
| 259 | . \$HOME/xos/os_oracle.sh | 244 | echo "ver=\$(./zig-out/bin/mux --version)" |
| 260 | echo "ver=\$(\$HOME/.local/bin/mux --version)" | 245 | echo "sha=\$(sha256_of ./zig-out/bin/mux)" |
| 261 | echo "sha=\$(sha256_of \$HOME/.local/bin/mux)" | ||
| 262 | echo "exe=\$(real_path \$HOME/.local/bin/mux)" | ||
| 263 | echo "arch=\$(uname -m)" | ||
| 264 | H | 246 | H |
| 265 | ) | 247 | ) |
| 266 | MAC_VER=$(printf '%s\n' "$MAC_INFO" | sed -n 's/^ver=//p') | 248 | BUILT_VER=$(printf '%s\n' "$BINFO" | sed -n 's/^ver=//p') |
| 267 | MAC_BIN_SHA=$(printf '%s\n' "$MAC_INFO" | sed -n 's/^sha=//p') | 249 | MAC_BIN_SHA=$(printf '%s\n' "$BINFO" | sed -n 's/^sha=//p') |
| 268 | MAC_EXE=$(printf '%s\n' "$MAC_INFO" | sed -n 's/^exe=//p') | ||
| 269 | MAC_ARCH=$(printf '%s\n' "$MAC_INFO" | sed -n 's/^arch=//p') | ||
| 270 | WANT_VER=$("$MUX" --version) | 250 | WANT_VER=$("$MUX" --version) |
| 271 | [ -n "$MAC_VER" ] && [ "$MAC_VER" = "$WANT_VER" ] || | 251 | [ -n "$BUILT_VER" ] && [ "$BUILT_VER" = "$WANT_VER" ] || |
| 272 | fail build "$MAC answers '$MAC_VER', this tree built '$WANT_VER'" | 252 | fail build "$MAC_BUILDER built '$BUILT_VER', this tree built '$WANT_VER'" |
| 273 | [ "$MAC_ARCH" = "arm64" ] || | 253 | [ -n "$MAC_BIN_SHA" ] || fail build "$MAC_BUILDER would not hash the binary it built" |
| 274 | fail build "$MAC says it is '$MAC_ARCH'; this gate is about an arm64 Mac" | 254 | ok build "$MAC_BUILDER built an $WANT_MAC_ARCH '$BUILT_VER'" |
| 275 | [ -n "$MAC_BIN_SHA" ] || fail build "$MAC would not hash its installed mux" | 255 | |
| 276 | ok build "$MAC installed an $MAC_ARCH '$MAC_VER' at $MAC_EXE" | 256 | # ---- leg 3: reset — two mux-less boxes, each given one image ----------- |
| 277 | 257 | # The scrub is the hermeticity: each run starts from the mux-less box a new | |
| 278 | # ---- leg 3: reset — a mux-less Linux box, and a Mac left alone --------- | 258 | # user has, and box_scrub is the one place that says what that means. It |
| 259 | # writes no shell rc file on either box — the product carries its own PATH | ||
| 260 | # prefix on every remote word it sends — so what the legs below meet is a | ||
| 261 | # box this gate has configured in no way at all. | ||
| 279 | leg | 262 | leg |
| 280 | # The Mac first, and it is a REFUSAL and not a reset. This is a person's | 263 | box_scrub "$LINUX_BOX" |
| 281 | # machine: a stray mux here is theirs, and the Linux→Mac legs must meet the | 264 | box_scrub "$MAC_BOX" |
| 282 | # daemon THEY start, not one already running with unknown flags. | 265 | box_ssh 60 "$LINUX_BOX" >/dev/null <<'L' || fail reset "could not make the scratch directories on $LINUX_BOX" |
| 283 | MPRE=$(mssh 60 <<'M' | 266 | set -e |
| 284 | echo "uid=$(id -u)" | 267 | mkdir -p "$HOME/.local/bin" "$HOME/xos" |
| 285 | echo "muxes=$(pgrep -x mux 2>/dev/null | wc -l | tr -d ' ')" | ||
| 286 | [ -e /tmp/mux-$(id -u)/muxd.sock ] && echo "rt=present" || echo "rt=absent" | ||
| 287 | [ -e $HOME/.config/mux ] && echo "cfg=present" || echo "cfg=absent" | ||
| 288 | ifconfig 2>/dev/null | sed -n 's/^[[:space:]]*inet \([0-9.]*\) .*/ip=\1/p' | ||
| 289 | M | ||
| 290 | ) | ||
| 291 | MAC_UID=$(printf '%s\n' "$MPRE" | sed -n 's/^uid=//p') | ||
| 292 | [ -n "$MAC_UID" ] || fail reset "$MAC would not say its uid" | ||
| 293 | printf '%s\n' "$MPRE" | grep -qx 'muxes=0' || { | ||
| 294 | echo "xos FAIL: reset: $MAC already runs a mux, and this gate will not kill it." | ||
| 295 | echo " Stop it there (mux d stop) or run this against another Mac." | ||
| 296 | exit 1; } | ||
| 297 | # The SOCKET and not the directory. `pgrep` above already says no mux is | ||
| 298 | # running; what is left to rule out is a daemon serving at the path the | ||
| 299 | # Linux→Mac legs will dial. The directory itself is made by any mux at all, | ||
| 300 | # a plain client included, so on a Mac that has ever run one it is simply | ||
| 301 | # there — refusing on it would refuse every machine the gate is for. | ||
| 302 | printf '%s\n' "$MPRE" | grep -qx 'rt=absent' || | ||
| 303 | fail reset "$MAC already serves /tmp/mux-$MAC_UID/muxd.sock; the daemon this gate meets must be the one it starts" | ||
| 304 | # The address the QUIC legs dial, asserted against the Mac's own interface | ||
| 305 | # list rather than assumed from a variable: a stale MUX_MAC_IP would make | ||
| 306 | # leg 8 grade some other machine's silence. | ||
| 307 | printf '%s\n' "$MPRE" | grep -qx "ip=$MAC_IP" || | ||
| 308 | fail reset "$MAC does not hold $MAC_IP; set MUX_MAC_IP. It answered: $(printf '%s' "$MPRE" | tr '\n' ' ')" | ||
| 309 | MAC_SOCK="/tmp/mux-$MAC_UID/muxd.sock" | ||
| 310 | # Leg 7's dial will create this if it is not there; see MAC_CFG_MADE. | ||
| 311 | if printf '%s\n' "$MPRE" | grep -qx 'cfg=absent'; then MAC_CFG_MADE=yes; fi | ||
| 312 | |||
| 313 | # The Linux box IS a throwaway, so it gets test/vm.sh's scorched earth. The | ||
| 314 | # reset is the hermeticity: each run starts from the mux-less box a new user | ||
| 315 | # has. The two standing arrangements it (re)makes: | ||
| 316 | # linger — without it logind may tear down /run/user/1000 (the daemon's | ||
| 317 | # socket dir) between ssh sessions and reap the daemon with it. | ||
| 318 | # PATH — an ssh handoff runs a non-interactive bash whose PATH lacks | ||
| 319 | # ~/.local/bin; the line goes ABOVE bashrc's interactivity | ||
| 320 | # return, where Debian's ssh-aware bash reads it. | ||
| 321 | # shellcheck disable=SC2086 # SSH_OPTS is words on purpose | ||
| 322 | LAN_ARCH=$(timeout 20 ssh -n $SSH_OPTS "$LAN" 'uname -m') || | ||
| 323 | fail reset "cannot ssh to $LAN — is the VM up, and has its DHCP lease moved?" | ||
| 324 | [ "$LAN_ARCH" = "x86_64" ] || | ||
| 325 | fail reset "$LAN is $LAN_ARCH, and this gate installs an x86_64-musl build there" | ||
| 326 | lssh 120 >/dev/null <<'L' || fail reset "the scorched-earth reset was refused on the Linux box" | ||
| 327 | set -eu | ||
| 328 | pkill -x mux 2>/dev/null || true | ||
| 329 | sleep 0.3 | ||
| 330 | pkill -9 -x mux 2>/dev/null || true | ||
| 331 | rm -rf ~/.local/bin/mux ~/.local/state/mux ~/.cache/mux ~/.config/mux ~/xos | ||
| 332 | rm -rf "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"/mux* | ||
| 333 | sudo -n loginctl enable-linger "$(id -un)" | ||
| 334 | grep -q '^export PATH="$HOME/.local/bin' ~/.bashrc 2>/dev/null || \ | ||
| 335 | sed -i '1i export PATH="$HOME/.local/bin:$PATH"' ~/.bashrc | ||
| 336 | mkdir -p ~/.local/bin ~/xos | ||
| 337 | L | 268 | L |
| 338 | # shellcheck disable=SC2086 # SSH_OPTS is words on purpose | 269 | box_ssh 60 "$MAC_BOX" >/dev/null <<'M' || fail reset "could not make the scratch directories on $MAC_BOX" |
| 339 | scp -q $SSH_OPTS "$MUSL_MUX" "$LAN":.local/bin/mux || | 270 | set -e |
| 340 | fail reset "could not copy the musl build to $LAN" | 271 | mkdir -p "$HOME/.local/bin" "$HOME/xos" |
| 341 | # shellcheck disable=SC2086 # SSH_OPTS is words on purpose | 272 | M |
| 342 | scp -q $SSH_OPTS "$(dirname "$0")/os_oracle.sh" "$LAN":xos/os_oracle.sh || | 273 | # The oracle goes beside the binary on each box rather than being read out of |
| 343 | fail reset "could not copy the oracle to $LAN" | 274 | # a repo, so both boxes answer through the same path and a leg never has to |
| 275 | # know which one it is talking to. | ||
| 276 | box_scp "$LINUX_BOX" "$MUSL_MUX" .local/bin/mux || | ||
| 277 | fail reset "could not copy the musl build to $LINUX_BOX" | ||
| 278 | box_scp "$LINUX_BOX" "$(dirname "$0")/os_oracle.sh" xos/os_oracle.sh || | ||
| 279 | fail reset "could not copy the oracle to $LINUX_BOX" | ||
| 280 | box_scp "$MAC_BOX" "$(dirname "$0")/os_oracle.sh" xos/os_oracle.sh || | ||
| 281 | fail reset "could not copy the oracle to $MAC_BOX" | ||
| 282 | # Streamed through this box rather than copied between the two Macs: neither | ||
| 283 | # needs a key of the other's for that, and only this box can reach both. | ||
| 284 | box_stream "$MAC_BUILDER" "\$HOME/$REPO/zig-out/bin/mux" "$MAC_BOX" "\$HOME/.local/bin/mux" || | ||
| 285 | fail reset "could not stream the arm64 mux from $MAC_BUILDER to $MAC_BOX" | ||
| 286 | box_stream "$MAC_BUILDER" "\$HOME/$REPO/zig-out/bin/ptyclient" "$MAC_BOX" "\$HOME/xos/ptyclient" || | ||
| 287 | fail reset "could not stream ptyclient from $MAC_BUILDER to $MAC_BOX" | ||
| 288 | |||
| 344 | LAN_BIN_SHA=$(sha256_of "$MUSL_MUX") | 289 | LAN_BIN_SHA=$(sha256_of "$MUSL_MUX") |
| 345 | LINFO=$(lssh 60 <<'L' | 290 | LINFO=$(box_ssh 60 "$LINUX_BOX" <<'L' |
| 346 | . ~/xos/os_oracle.sh | 291 | . ~/xos/os_oracle.sh |
| 347 | echo "ver=$(~/.local/bin/mux --version)" | 292 | echo "ver=$(~/.local/bin/mux --version)" |
| 348 | echo "sha=$(sha256_of ~/.local/bin/mux)" | 293 | echo "sha=$(sha256_of ~/.local/bin/mux)" |
| @@ -354,20 +299,59 @@ LAN_VER=$(printf '%s\n' "$LINFO" | sed -n 's/^ver=//p') | |||
| 354 | LAN_GOT_SHA=$(printf '%s\n' "$LINFO" | sed -n 's/^sha=//p') | 299 | LAN_GOT_SHA=$(printf '%s\n' "$LINFO" | sed -n 's/^sha=//p') |
| 355 | LAN_EXE=$(printf '%s\n' "$LINFO" | sed -n 's/^exe=//p') | 300 | LAN_EXE=$(printf '%s\n' "$LINFO" | sed -n 's/^exe=//p') |
| 356 | LAN_RTD=$(printf '%s\n' "$LINFO" | sed -n 's/^rtd=//p') | 301 | LAN_RTD=$(printf '%s\n' "$LINFO" | sed -n 's/^rtd=//p') |
| 357 | [ -n "$LAN_EXE" ] || fail reset "$LAN would not resolve the path of its installed mux" | 302 | [ -n "$LAN_EXE" ] || fail reset "$LINUX_BOX would not resolve the path of its installed mux" |
| 358 | [ "$LAN_VER" = "$WANT_VER" ] || | 303 | [ "$LAN_VER" = "$WANT_VER" ] || |
| 359 | fail reset "$LAN answers '$LAN_VER', this tree built '$WANT_VER'" | 304 | fail reset "$LINUX_BOX answers '$LAN_VER', this tree built '$WANT_VER'" |
| 360 | [ "$LAN_GOT_SHA" = "$LAN_BIN_SHA" ] || | 305 | [ "$LAN_GOT_SHA" = "$LAN_BIN_SHA" ] || |
| 361 | fail reset "$LAN holds a mux hashing $LAN_GOT_SHA, the pushed image is $LAN_BIN_SHA" | 306 | fail reset "$LINUX_BOX holds a mux hashing $LAN_GOT_SHA, the pushed image is $LAN_BIN_SHA" |
| 362 | LAN_SOCK="$LAN_RTD/muxd.sock" | 307 | LAN_SOCK="$LAN_RTD/muxd.sock" |
| 363 | ok reset "$LAN reset to a mux-less box and given '$LAN_VER'; $MAC left running nothing" | 308 | |
| 309 | MINFO=$(box_ssh 60 "$MAC_BOX" <<'M' | ||
| 310 | . $HOME/xos/os_oracle.sh | ||
| 311 | echo "uid=$(id -u)" | ||
| 312 | echo "ver=$($HOME/.local/bin/mux --version)" | ||
| 313 | echo "sha=$(sha256_of $HOME/.local/bin/mux)" | ||
| 314 | echo "exe=$(real_path $HOME/.local/bin/mux)" | ||
| 315 | M | ||
| 316 | ) | ||
| 317 | MAC_UID=$(printf '%s\n' "$MINFO" | sed -n 's/^uid=//p') | ||
| 318 | MAC_VER=$(printf '%s\n' "$MINFO" | sed -n 's/^ver=//p') | ||
| 319 | MAC_GOT_SHA=$(printf '%s\n' "$MINFO" | sed -n 's/^sha=//p') | ||
| 320 | MAC_EXE=$(printf '%s\n' "$MINFO" | sed -n 's/^exe=//p') | ||
| 321 | [ -n "$MAC_UID" ] || fail reset "$MAC_BOX would not say its uid" | ||
| 322 | [ -n "$MAC_EXE" ] || fail reset "$MAC_BOX would not resolve the path of its installed mux" | ||
| 323 | [ "$MAC_VER" = "$WANT_VER" ] || | ||
| 324 | fail reset "$MAC_BOX answers '$MAC_VER', this tree built '$WANT_VER'" | ||
| 325 | # The bytes that RUN are the bytes the builder produced: `cat` down one pipe | ||
| 326 | # and up another is a stream nothing else checks. | ||
| 327 | [ "$MAC_GOT_SHA" = "$MAC_BIN_SHA" ] || | ||
| 328 | fail reset "$MAC_BOX holds a mux hashing $MAC_GOT_SHA, the builder's is $MAC_BIN_SHA" | ||
| 329 | MAC_SOCK="/tmp/mux-$MAC_UID/muxd.sock" | ||
| 330 | |||
| 331 | # What an ssh handoff will find, asked in the handoff's own words. sshd runs | ||
| 332 | # a NON-LOGIN shell, so nothing on either box puts ~/.local/bin on that PATH | ||
| 333 | # and the product does not expect it to: every remote word mux sends is | ||
| 334 | # prefixed with `PATH="$PATH:$HOME/.local/bin"` — `handoff.local_bin_append`, | ||
| 335 | # carried by the entry dial, the upgrade preflight and the upgrade's push. | ||
| 336 | # The prefix below is that string character for character, so what this | ||
| 337 | # grades is the installed binary being findable the way mux finds it, and | ||
| 338 | # not a shell rc file the harness wrote. A bare `command -v mux` here would | ||
| 339 | # be asking a question the handoff never asks. | ||
| 340 | for _box in "$LINUX_BOX" "$MAC_BOX"; do | ||
| 341 | _opts=$(box_opts_for "$_box") | ||
| 342 | # shellcheck disable=SC2086,SC2016 # the options are words; $PATH is the REMOTE shell's | ||
| 343 | _which=$(timeout 20 ssh -n $_opts "$_box" 'PATH="$PATH:$HOME/.local/bin"; command -v mux' 2>/dev/null) || _which="" | ||
| 344 | case "$_which" in | ||
| 345 | */.local/bin/mux) ;; | ||
| 346 | *) fail reset "with the handoff's own PATH prefix, $_box finds mux at '${_which:-nothing}'; every remote dial would fail" ;; | ||
| 347 | esac | ||
| 348 | done | ||
| 349 | ok reset "$LINUX_BOX and $MAC_BOX scrubbed and each given '$WANT_VER' on its PATH" | ||
| 364 | 350 | ||
| 365 | # ---- leg 4: keys — one PSK for the run, never the user's --------------- | 351 | # ---- leg 4: keys — one PSK for the run, never the user's --------------- |
| 366 | # `mux d keygen` always writes the default path, so the isolated | 352 | # `mux d keygen` always writes the default path, so the isolated |
| 367 | # XDG_CONFIG_HOME above is what puts it in the scratch tree. It is copied to | 353 | # XDG_CONFIG_HOME above is what puts it in the scratch tree. It is copied to |
| 368 | # ~/xos/key on both boxes and named on every QUIC command line; the Mac's | 354 | # ~/xos/key on both boxes and named on every QUIC command line. |
| 369 | # own ~/.config/mux/key is never written by the HARNESS (leg 7's dial makes | ||
| 370 | # the product write it, which MAC_CFG_MADE undoes). | ||
| 371 | # | 355 | # |
| 372 | # On the Linux box the same bytes ALSO go to that box's default key path, | 356 | # On the Linux box the same bytes ALSO go to that box's default key path, |
| 373 | # and leg 5's daemon is started with no `--key` at all, which is how a | 357 | # and leg 5's daemon is started with no `--key` at all, which is how a |
| @@ -386,17 +370,15 @@ cp "$XDG_CONFIG_HOME/mux/key" "$SCRATCH/key" | |||
| 386 | chmod 600 "$SCRATCH/key" | 370 | chmod 600 "$SCRATCH/key" |
| 387 | [ "$(file_size "$SCRATCH/key")" = "32" ] || | 371 | [ "$(file_size "$SCRATCH/key")" = "32" ] || |
| 388 | fail keys "the generated key is $(file_size "$SCRATCH/key") bytes, want 32" | 372 | fail keys "the generated key is $(file_size "$SCRATCH/key") bytes, want 32" |
| 389 | # shellcheck disable=SC2086 # SSH_OPTS is words on purpose | 373 | box_scp "$LINUX_BOX" "$SCRATCH/key" xos/key || fail keys "could not install the key on $LINUX_BOX" |
| 390 | scp -q $SSH_OPTS "$SCRATCH/key" "$LAN":xos/key || fail keys "could not install the key on $LAN" | 374 | box_scp "$MAC_BOX" "$SCRATCH/key" xos/key || fail keys "could not install the key on $MAC_BOX" |
| 391 | # shellcheck disable=SC2086 # SSH_OPTS is words on purpose | ||
| 392 | scp -q $SSH_OPTS "$SCRATCH/key" "$MAC":xos/key || fail keys "could not install the key on $MAC" | ||
| 393 | # BOTH default key paths on the Linux box, because two different | 375 | # BOTH default key paths on the Linux box, because two different |
| 394 | # environments resolve "the default key" there and they must not disagree: | 376 | # environments resolve "the default key" there and they must not disagree: |
| 395 | # everything the harness runs sees the prelude's isolated XDG_CONFIG_HOME, | 377 | # everything the harness runs sees the prelude's isolated XDG_CONFIG_HOME, |
| 396 | # while the `mux d endpoint` at the far end of the MAC's entry dial is a | 378 | # while the `mux d endpoint` at the far end of MAC_BOX's entry dial is a |
| 397 | # fresh ssh with the box's own environment and resolves ~/.config/mux/key. | 379 | # fresh ssh with the box's own environment and resolves ~/.config/mux/key. |
| 398 | # Same bytes at both, so whichever one a process picks is this run's key. | 380 | # Same bytes at both, so whichever one a process picks is this run's key. |
| 399 | lssh 30 >/dev/null <<'L' || fail keys "could not install the key at the default paths on $LAN" | 381 | box_ssh 30 "$LINUX_BOX" >/dev/null <<'L' || fail keys "could not install the key at the default paths on $LINUX_BOX" |
| 400 | set -e | 382 | set -e |
| 401 | for d in "$HOME/.config/mux" "${XDG_CONFIG_HOME:-$HOME/.config}/mux"; do | 383 | for d in "$HOME/.config/mux" "${XDG_CONFIG_HOME:-$HOME/.config}/mux"; do |
| 402 | mkdir -p "$d" | 384 | mkdir -p "$d" |
| @@ -408,21 +390,21 @@ L | |||
| 408 | # 0600 on both, because quic.Key.load refuses a group- or other-readable key | 390 | # 0600 on both, because quic.Key.load refuses a group- or other-readable key |
| 409 | # exactly as ssh refuses a private key, and scp does not carry the mode. | 391 | # exactly as ssh refuses a private key, and scp does not carry the mode. |
| 410 | KMODES=$( | 392 | KMODES=$( |
| 411 | lssh 30 <<'L' | 393 | box_ssh 30 "$LINUX_BOX" <<'L' |
| 412 | . ~/xos/os_oracle.sh | 394 | . ~/xos/os_oracle.sh |
| 413 | chmod 600 ~/xos/key | 395 | chmod 600 ~/xos/key |
| 414 | echo "lan=$(file_mode ~/xos/key)" | 396 | echo "lan=$(file_mode ~/xos/key)" |
| 415 | L | 397 | L |
| 416 | mssh 30 <<'M' | 398 | box_ssh 30 "$MAC_BOX" <<'M' |
| 417 | . $HOME/xos/os_oracle.sh | 399 | . $HOME/xos/os_oracle.sh |
| 418 | chmod 600 $HOME/xos/key | 400 | chmod 600 $HOME/xos/key |
| 419 | echo "mac=$(file_mode $HOME/xos/key)" | 401 | echo "mac=$(file_mode $HOME/xos/key)" |
| 420 | M | 402 | M |
| 421 | ) | 403 | ) |
| 422 | printf '%s\n' "$KMODES" | grep -qx 'lan=600' || | 404 | printf '%s\n' "$KMODES" | grep -qx 'lan=600' || |
| 423 | fail keys "the key on $LAN is not mode 600: $KMODES" | 405 | fail keys "the key on $LINUX_BOX is not mode 600: $KMODES" |
| 424 | printf '%s\n' "$KMODES" | grep -qx 'mac=600' || | 406 | printf '%s\n' "$KMODES" | grep -qx 'mac=600' || |
| 425 | fail keys "the key on $MAC is not mode 600: $KMODES" | 407 | fail keys "the key on $MAC_BOX is not mode 600: $KMODES" |
| 426 | ok keys "one 32-byte PSK at ~/xos/key, mode 600 on both boxes" | 408 | ok keys "one 32-byte PSK at ~/xos/key, mode 600 on both boxes" |
| 427 | 409 | ||
| 428 | # ---- leg 5: mac-to-linux — a Mac client on a Linux daemon -------------- | 410 | # ---- leg 5: mac-to-linux — a Mac client on a Linux daemon -------------- |
| @@ -431,7 +413,7 @@ ok keys "one 32-byte PSK at ~/xos/key, mode 600 on both boxes" | |||
| 431 | # QUIC. MUX_SHELL_INTEGRATION=1 because leg 6 reads a real exit code off | 413 | # QUIC. MUX_SHELL_INTEGRATION=1 because leg 6 reads a real exit code off |
| 432 | # this same daemon and marks are opt-in. | 414 | # this same daemon and marks are opt-in. |
| 433 | leg | 415 | leg |
| 434 | lssh 90 >/dev/null <<L || fail mac-to-linux "mux d start -d --quic was refused on $LAN" | 416 | box_ssh 90 "$LINUX_BOX" >/dev/null <<L || fail mac-to-linux "mux d start -d --quic was refused on $LINUX_BOX" |
| 435 | MUX_SHELL_INTEGRATION=1 ~/.local/bin/mux d start -d --quic 0.0.0.0:$QUIC_PORT | 417 | MUX_SHELL_INTEGRATION=1 ~/.local/bin/mux d start -d --quic 0.0.0.0:$QUIC_PORT |
| 436 | L | 418 | L |
| 437 | # The listener's key and the key `mux d endpoint` announces, side by side. | 419 | # The listener's key and the key `mux d endpoint` announces, side by side. |
| @@ -446,35 +428,35 @@ L | |||
| 446 | # this box's own environment and so resolves ~/.config/mux/key; asking under | 428 | # this box's own environment and so resolves ~/.config/mux/key; asking under |
| 447 | # the prelude's isolated config dir would check a sibling file that happens to | 429 | # the prelude's isolated config dir would check a sibling file that happens to |
| 448 | # hold the same bytes and guard nothing. | 430 | # hold the same bytes and guard nothing. |
| 449 | ANN=$(lssh 30 <<'L' | 431 | ANN=$(box_ssh 30 "$LINUX_BOX" <<'L' |
| 450 | unset XDG_CONFIG_HOME | 432 | unset XDG_CONFIG_HOME |
| 451 | ~/.local/bin/mux d endpoint </dev/null | 433 | ~/.local/bin/mux d endpoint </dev/null |
| 452 | L | 434 | L |
| 453 | ) | 435 | ) |
| 454 | case "$ANN" in | 436 | case "$ANN" in |
| 455 | "endpoint $QUIC_PORT $(od -An -tx1 -v < "$SCRATCH/key" | tr -d ' \n')") ;; | 437 | "endpoint $QUIC_PORT $(od -An -tx1 -v < "$SCRATCH/key" | tr -d ' \n')") ;; |
| 456 | *) fail mac-to-linux "$LAN announces '$ANN', which is not this run's key at port $QUIC_PORT" ;; | 438 | *) fail mac-to-linux "$LINUX_BOX announces '$ANN', which is not this run's key at port $QUIC_PORT" ;; |
| 457 | esac | 439 | esac |
| 458 | LPID=$(lssh 30 <<'L' | 440 | LPID=$(box_ssh 30 "$LINUX_BOX" <<'L' |
| 459 | pgrep -x mux | head -1 | 441 | pgrep -x mux | head -1 |
| 460 | L | 442 | L |
| 461 | ) | 443 | ) |
| 462 | [ -n "$LPID" ] || fail mac-to-linux "no mux process on $LAN after mux d start -d" | 444 | [ -n "$LPID" ] || fail mac-to-linux "no mux process on $LINUX_BOX after mux d start -d" |
| 463 | # The image this daemon is running, recorded before anything is pushed at it. | 445 | # The image this daemon is running, recorded before anything is pushed at it. |
| 464 | # Leg 9 reads it again after the refused push: on Linux an upgrade keeps the | 446 | # Leg 9 reads it again after the refused push: on Linux an upgrade keeps the |
| 465 | # pid across the exec, so the pid alone cannot tell an untouched daemon from | 447 | # pid across the exec, so the pid alone cannot tell an untouched daemon from |
| 466 | # one that swallowed a new image, and the exe is what can. | 448 | # one that swallowed a new image, and the exe is what can. |
| 467 | LIS=$(lssh 30 <<L | 449 | LIS=$(box_ssh 30 "$LINUX_BOX" <<L |
| 468 | . ~/xos/os_oracle.sh | 450 | . ~/xos/os_oracle.sh |
| 469 | pid_exe $LPID | 451 | pid_exe $LPID |
| 470 | L | 452 | L |
| 471 | ) | 453 | ) |
| 472 | [ "$LIS" = "$LAN_EXE" ] || | 454 | [ "$LIS" = "$LAN_EXE" ] || |
| 473 | fail mac-to-linux "daemon $LPID on $LAN execs '$LIS', not the installed '$LAN_EXE'" | 455 | fail mac-to-linux "daemon $LPID on $LINUX_BOX execs '$LIS', not the installed '$LAN_EXE'" |
| 474 | # Bound BEFORE the dial, asked of /proc and not of the daemon: a leg that | 456 | # Bound BEFORE the dial, asked of /proc and not of the daemon: a leg that |
| 475 | # read "no answer" from the Mac could otherwise be blaming the network for a | 457 | # read "no answer" from the Mac could otherwise be blaming the network for a |
| 476 | # listener that was never up. | 458 | # listener that was never up. |
| 477 | lssh 60 >/dev/null <<L || fail mac-to-linux "daemon $LPID binds no UDP $QUIC_PORT on $LAN" | 459 | box_ssh 60 "$LINUX_BOX" >/dev/null <<L || fail mac-to-linux "daemon $LPID binds no UDP $QUIC_PORT on $LINUX_BOX" |
| 478 | . ~/xos/os_oracle.sh | 460 | . ~/xos/os_oracle.sh |
| 479 | _i=0 | 461 | _i=0 |
| 480 | while [ \$_i -lt 50 ]; do | 462 | while [ \$_i -lt 50 ]; do |
| @@ -488,11 +470,11 @@ L | |||
| 488 | # `XOS-M2L-x86_64-` painted on a Mac's screen is the cross-OS fact itself: | 470 | # `XOS-M2L-x86_64-` painted on a Mac's screen is the cross-OS fact itself: |
| 489 | # the bytes were produced by a shell only the Linux box's process table can | 471 | # the bytes were produced by a shell only the Linux box's process table can |
| 490 | # account for. | 472 | # account for. |
| 491 | M2L=$(mssh 240 <<M | 473 | M2L=$(box_ssh 240 "$MAC_BOX" <<M |
| 492 | cd \$HOME/xos | 474 | cd \$HOME/xos |
| 493 | \$HOME/$REPO/zig-out/bin/ptyclient --cols 80 --rows 24 \ | 475 | \$HOME/xos/ptyclient --cols 80 --rows 24 \ |
| 494 | --out \$HOME/xos/m2l.cap --err \$HOME/xos/m2l.err -- \ | 476 | --out \$HOME/xos/m2l.cap --err \$HOME/xos/m2l.err -- \ |
| 495 | \$HOME/.local/bin/mux $LAN <<'PC' | 477 | \$HOME/.local/bin/mux $LINUX_BOX <<'PC' |
| 496 | expect \\x1b[?1049h 30000 | 478 | expect \\x1b[?1049h 30000 |
| 497 | settle 900 20000 | 479 | settle 900 20000 |
| 498 | send echo XOS-M2L-\$(uname -m)-\$\$\n | 480 | send echo XOS-M2L-\$(uname -m)-\$\$\n |
| @@ -503,14 +485,14 @@ waitexit 15000 | |||
| 503 | PC | 485 | PC |
| 504 | echo "PTYRC=\$?" | 486 | echo "PTYRC=\$?" |
| 505 | M | 487 | M |
| 506 | ) || fail mac-to-linux "$MAC would not run ptyclient" | 488 | ) || fail mac-to-linux "$MAC_BOX would not run ptyclient" |
| 507 | printf '%s\n' "$M2L" | grep -qx 'PTYRC=0' || { | 489 | printf '%s\n' "$M2L" | grep -qx 'PTYRC=0' || { |
| 508 | echo "xos FAIL: mac-to-linux: the Mac's client never painted XOS-M2L-x86_64-: $M2L" | 490 | echo "xos FAIL: mac-to-linux: the Mac's client never painted XOS-M2L-x86_64-: $M2L" |
| 509 | mssh 60 <<'M' | sed 's/^/ /' || true | 491 | box_ssh 60 "$MAC_BOX" <<'M' | sed 's/^/ /' || true |
| 510 | cat $HOME/xos/m2l.err 2>/dev/null | 492 | cat $HOME/xos/m2l.err 2>/dev/null |
| 511 | M | 493 | M |
| 512 | exit 1; } | 494 | exit 1; } |
| 513 | M2L_READ=$(mssh 60 <<'M' | 495 | M2L_READ=$(box_ssh 60 "$MAC_BOX" <<'M' |
| 514 | echo "pid=$(sed -n 's/.*XOS-M2L-x86_64-\([0-9][0-9]*\).*/\1/p' $HOME/xos/m2l.cap | head -1)" | 496 | echo "pid=$(sed -n 's/.*XOS-M2L-x86_64-\([0-9][0-9]*\).*/\1/p' $HOME/xos/m2l.cap | head -1)" |
| 515 | grep -c 'attaching over ssh' $HOME/xos/m2l.err 2>/dev/null || true | 497 | grep -c 'attaching over ssh' $HOME/xos/m2l.err 2>/dev/null || true |
| 516 | M | 498 | M |
| @@ -522,26 +504,26 @@ M2L_PID=$(printf '%s\n' "$M2L_READ" | sed -n 's/^pid=//p') | |||
| 522 | # above says the arm existed to move onto. | 504 | # above says the arm existed to move onto. |
| 523 | printf '%s\n' "$M2L_READ" | grep -qx '0' || | 505 | printf '%s\n' "$M2L_READ" | grep -qx '0' || |
| 524 | fail mac-to-linux "the Mac's dial fell back to the ssh pipe; it said: $M2L_READ" | 506 | fail mac-to-linux "the Mac's dial fell back to the ssh pipe; it said: $M2L_READ" |
| 525 | lssh 60 >/dev/null <<L || fail mac-to-linux "shell $M2L_PID is not a child of daemon $LPID on $LAN" | 507 | box_ssh 60 "$LINUX_BOX" >/dev/null <<L || fail mac-to-linux "shell $M2L_PID is not a child of daemon $LPID on $LINUX_BOX" |
| 526 | . ~/xos/os_oracle.sh | 508 | . ~/xos/os_oracle.sh |
| 527 | pid_children $LPID | grep -qx $M2L_PID | 509 | pid_children $LPID | grep -qx $M2L_PID |
| 528 | L | 510 | L |
| 529 | # The detach was a goodbye and not a tear: what the Mac typed is still on | 511 | # The detach was a goodbye and not a tear: what the Mac typed is still on |
| 530 | # the Linux box's grid. | 512 | # the Linux box's grid. |
| 531 | lssh 60 >/dev/null <<'L' || fail mac-to-linux "the session did not survive the Mac's detach" | 513 | box_ssh 60 "$LINUX_BOX" >/dev/null <<'L' || fail mac-to-linux "the session did not survive the Mac's detach" |
| 532 | ~/.local/bin/mux d dump | grep -q XOS-M2L-x86_64- | 514 | ~/.local/bin/mux d dump | grep -q XOS-M2L-x86_64- |
| 533 | L | 515 | L |
| 534 | ok mac-to-linux "a macOS client typed into shell $M2L_PID under daemon $LPID on $LAN, over QUIC" | 516 | ok mac-to-linux "a macOS client typed into shell $M2L_PID under daemon $LPID on $LINUX_BOX, over QUIC" |
| 535 | 517 | ||
| 536 | # ---- leg 6: mac-to-linux-quic — no ssh anywhere in the path ------------ | 518 | # ---- leg 6: mac-to-linux-quic — no ssh anywhere in the path ------------ |
| 537 | # The Darwin wolfSSL build's PSK handshake against the musl build's, in one | 519 | # The Darwin wolfSSL build's PSK handshake against the musl build's, in one |
| 538 | # round trip, joining the session leg 5 made. `mux a` never creates. | 520 | # round trip, joining the session leg 5 made. `mux a` never creates. |
| 539 | leg | 521 | leg |
| 540 | AQ=$(mssh 120 <<M | 522 | AQ=$(box_ssh 120 "$MAC_BOX" <<M |
| 541 | MUX_KEY_FILE=\$HOME/xos/key \$HOME/.local/bin/mux a run --quic $LAN_IP:$QUIC_PORT \ | 523 | MUX_KEY_FILE=\$HOME/xos/key \$HOME/.local/bin/mux a run --quic $LAN_IP:$QUIC_PORT \ |
| 542 | --key \$HOME/xos/key --timeout 20000 'echo m2lq-\$\$' | 524 | --key \$HOME/xos/key --timeout 20000 'echo m2lq-\$\$' |
| 543 | M | 525 | M |
| 544 | ) || fail mac-to-linux-quic "mux a run --quic answered nothing from $MAC: $AQ" | 526 | ) || fail mac-to-linux-quic "mux a run --quic answered nothing from $MAC_BOX: $AQ" |
| 545 | case "$AQ" in | 527 | case "$AQ" in |
| 546 | *'"mechanism":"marks"'*) ;; | 528 | *'"mechanism":"marks"'*) ;; |
| 547 | *) fail mac-to-linux-quic "no marks mechanism in the reply: $AQ" ;; | 529 | *) fail mac-to-linux-quic "no marks mechanism in the reply: $AQ" ;; |
| @@ -552,14 +534,14 @@ case "$AQ" in | |||
| 552 | esac | 534 | esac |
| 553 | AQ_PID=$(printf '%s\n' "$AQ" | sed -n 's/.*m2lq-\([0-9][0-9]*\).*/\1/p') | 535 | AQ_PID=$(printf '%s\n' "$AQ" | sed -n 's/.*m2lq-\([0-9][0-9]*\).*/\1/p') |
| 554 | [ -n "$AQ_PID" ] || fail mac-to-linux-quic "the reply carries no shell pid: $AQ" | 536 | [ -n "$AQ_PID" ] || fail mac-to-linux-quic "the reply carries no shell pid: $AQ" |
| 555 | lssh 60 >/dev/null <<L || fail mac-to-linux-quic "shell $AQ_PID is not a child of daemon $LPID" | 537 | box_ssh 60 "$LINUX_BOX" >/dev/null <<L || fail mac-to-linux-quic "shell $AQ_PID is not a child of daemon $LPID" |
| 556 | . ~/xos/os_oracle.sh | 538 | . ~/xos/os_oracle.sh |
| 557 | pid_children $LPID | grep -qx $AQ_PID | 539 | pid_children $LPID | grep -qx $AQ_PID |
| 558 | L | 540 | L |
| 559 | ok mac-to-linux-quic "mux a over QUIC ran shell $AQ_PID under $LPID, marks, exit 0" | 541 | ok mac-to-linux-quic "mux a over QUIC ran shell $AQ_PID under $LPID, marks, exit 0" |
| 560 | 542 | ||
| 561 | # ---- leg 7: linux-to-mac — the entry dial starts the Mac's daemon ------ | 543 | # ---- leg 7: linux-to-mac — the entry dial starts the Mac's daemon ------ |
| 562 | # `mux $MAC` from here is the ONLY thing in this gate allowed to start a | 544 | # `mux $MAC_BOX` from here is the ONLY thing in this gate allowed to start a |
| 563 | # daemon on the Mac: `mux d endpoint --start` is the asked dial's alone. The | 545 | # daemon on the Mac: `mux d endpoint --start` is the asked dial's alone. The |
| 564 | # Mac's ssh login has no XDG_RUNTIME_DIR, so the daemon it starts binds the | 546 | # Mac's ssh login has no XDG_RUNTIME_DIR, so the daemon it starts binds the |
| 565 | # /tmp/mux-<uid> fallback — the Darwin path, exercised by a real `mux HOST` | 547 | # /tmp/mux-<uid> fallback — the Darwin path, exercised by a real `mux HOST` |
| @@ -567,7 +549,7 @@ ok mac-to-linux-quic "mux a over QUIC ran shell $AQ_PID under $LPID, marks, exit | |||
| 567 | leg | 549 | leg |
| 568 | set +e | 550 | set +e |
| 569 | timeout 120 "$PTYCLIENT" --cols 80 --rows 24 \ | 551 | timeout 120 "$PTYCLIENT" --cols 80 --rows 24 \ |
| 570 | --out "$SCRATCH/l2m.cap" --err "$SCRATCH/l2m.err" -- "$MUX" "$MAC" \ | 552 | --out "$SCRATCH/l2m.cap" --err "$SCRATCH/l2m.err" -- "$MUX" "$MAC_BOX" \ |
| 571 | > "$SCRATCH/l2m.log" 2>&1 <<'EOF' | 553 | > "$SCRATCH/l2m.log" 2>&1 <<'EOF' |
| 572 | expect \x1b[?1049h 30000 | 554 | expect \x1b[?1049h 30000 |
| 573 | settle 900 20000 | 555 | settle 900 20000 |
| @@ -587,23 +569,21 @@ set -e | |||
| 587 | L2M_PID=$(sed -n 's/.*XOS-L2M-arm64-\([0-9][0-9]*\).*/\1/p' "$SCRATCH/l2m.cap" | head -1) | 569 | L2M_PID=$(sed -n 's/.*XOS-L2M-arm64-\([0-9][0-9]*\).*/\1/p' "$SCRATCH/l2m.cap" | head -1) |
| 588 | [ -n "$L2M_PID" ] || fail linux-to-mac "the capture carries no shell pid" | 570 | [ -n "$L2M_PID" ] || fail linux-to-mac "the capture carries no shell pid" |
| 589 | 571 | ||
| 590 | MSTATE=$(mssh 90 <<M | 572 | MSTATE=$(box_ssh 90 "$MAC_BOX" <<M |
| 591 | . \$HOME/xos/os_oracle.sh | 573 | . \$HOME/xos/os_oracle.sh |
| 592 | echo "pid=\$(pgrep -x mux | head -1)" | 574 | echo "pid=\$(pgrep -x mux | head -1)" |
| 593 | echo "n=\$(pgrep -x mux | wc -l | tr -d ' ')" | 575 | echo "n=\$(pgrep -x mux | wc -l | tr -d ' ')" |
| 594 | echo "announce=\$(\$HOME/.local/bin/mux d endpoint </dev/null 2>&1 | head -1)" | 576 | echo "announce=\$(\$HOME/.local/bin/mux d endpoint </dev/null 2>&1 | head -1)" |
| 595 | echo "want=\$(real_path \$HOME/.local/bin/mux)" | ||
| 596 | udp_local_bound $QUIC_HEX && echo "udp=bound" || echo "udp=none" | 577 | udp_local_bound $QUIC_HEX && echo "udp=bound" || echo "udp=none" |
| 597 | M | 578 | M |
| 598 | ) | 579 | ) |
| 599 | MPID=$(printf '%s\n' "$MSTATE" | sed -n 's/^pid=//p') | 580 | MPID=$(printf '%s\n' "$MSTATE" | sed -n 's/^pid=//p') |
| 600 | MNPROC=$(printf '%s\n' "$MSTATE" | sed -n 's/^n=//p') | 581 | MNPROC=$(printf '%s\n' "$MSTATE" | sed -n 's/^n=//p') |
| 601 | MANN=$(printf '%s\n' "$MSTATE" | sed -n 's/^announce=//p') | 582 | MANN=$(printf '%s\n' "$MSTATE" | sed -n 's/^announce=//p') |
| 602 | MWANT=$(printf '%s\n' "$MSTATE" | sed -n 's/^want=//p') | 583 | [ -n "$MPID" ] || fail linux-to-mac "no mux on $MAC_BOX after the entry dial: $MSTATE" |
| 603 | [ -n "$MPID" ] || fail linux-to-mac "no mux on $MAC after the entry dial: $MSTATE" | ||
| 604 | [ "$MNPROC" = "1" ] || | 584 | [ "$MNPROC" = "1" ] || |
| 605 | fail linux-to-mac "$MAC runs $MNPROC mux processes after the detach, want 1 (the daemon): $MSTATE" | 585 | fail linux-to-mac "$MAC_BOX runs $MNPROC mux processes after the detach, want 1 (the daemon): $MSTATE" |
| 606 | mssh 60 >/dev/null <<M || fail linux-to-mac "daemon $MPID does not hold $MAC_SOCK" | 586 | box_ssh 60 "$MAC_BOX" >/dev/null <<M || fail linux-to-mac "daemon $MPID does not hold $MAC_SOCK" |
| 607 | . \$HOME/xos/os_oracle.sh | 587 | . \$HOME/xos/os_oracle.sh |
| 608 | pid_holds_unix_sock $MPID $MAC_SOCK | 588 | pid_holds_unix_sock $MPID $MAC_SOCK |
| 609 | M | 589 | M |
| @@ -611,14 +591,14 @@ M | |||
| 611 | # lsof reports, which resolves every symlink on the way, so a $HOME reached | 591 | # lsof reports, which resolves every symlink on the way, so a $HOME reached |
| 612 | # through one would fail a correct exec unless the expected path is resolved | 592 | # through one would fail a correct exec unless the expected path is resolved |
| 613 | # the same way. | 593 | # the same way. |
| 614 | MIS=$(mssh 60 <<M | 594 | MIS=$(box_ssh 60 "$MAC_BOX" <<M |
| 615 | . \$HOME/xos/os_oracle.sh | 595 | . \$HOME/xos/os_oracle.sh |
| 616 | pid_exe $MPID | 596 | pid_exe $MPID |
| 617 | M | 597 | M |
| 618 | ) | 598 | ) |
| 619 | [ -n "$MWANT" ] && [ "$MIS" = "$MWANT" ] || | 599 | [ -n "$MAC_EXE" ] && [ "$MIS" = "$MAC_EXE" ] || |
| 620 | fail linux-to-mac "daemon $MPID execs '$MIS', not the installed '$MWANT'" | 600 | fail linux-to-mac "daemon $MPID execs '$MIS', not the installed '$MAC_EXE'" |
| 621 | mssh 60 >/dev/null <<M || fail linux-to-mac "shell $L2M_PID is not a child of daemon $MPID" | 601 | box_ssh 60 "$MAC_BOX" >/dev/null <<M || fail linux-to-mac "shell $L2M_PID is not a child of daemon $MPID" |
| 622 | . \$HOME/xos/os_oracle.sh | 602 | . \$HOME/xos/os_oracle.sh |
| 623 | pid_children $MPID | grep -qx $L2M_PID | 603 | pid_children $MPID | grep -qx $L2M_PID |
| 624 | M | 604 | M |
| @@ -644,7 +624,7 @@ case "$MANN" in | |||
| 644 | # was tried and lost — and the OS fact beside it is that nothing is | 624 | # was tried and lost — and the OS fact beside it is that nothing is |
| 645 | # bound at the port this gate would otherwise expect. | 625 | # bound at the port this gate would otherwise expect. |
| 646 | printf '%s\n' "$MSTATE" | grep -qx 'udp=none' || | 626 | printf '%s\n' "$MSTATE" | grep -qx 'udp=none' || |
| 647 | fail linux-to-mac "$MAC announces no endpoint yet binds UDP $QUIC_PORT: $MSTATE" | 627 | fail linux-to-mac "$MAC_BOX announces no endpoint yet binds UDP $QUIC_PORT: $MSTATE" |
| 648 | if grep -q 'attaching over ssh' "$SCRATCH/l2m.err"; then | 628 | if grep -q 'attaching over ssh' "$SCRATCH/l2m.err"; then |
| 649 | fail linux-to-mac "the client fell back from a QUIC arm the daemon says it has not got" | 629 | fail linux-to-mac "the client fell back from a QUIC arm the daemon says it has not got" |
| 650 | fi | 630 | fi |
| @@ -653,22 +633,22 @@ case "$MANN" in | |||
| 653 | MPORT=${MANN#endpoint } | 633 | MPORT=${MANN#endpoint } |
| 654 | MPORT=${MPORT%% *} | 634 | MPORT=${MPORT%% *} |
| 655 | case "$MPORT" in | 635 | case "$MPORT" in |
| 656 | ''|*[!0-9]*) fail linux-to-mac "$MAC announces no readable port: '$MANN'" ;; | 636 | ''|*[!0-9]*) fail linux-to-mac "$MAC_BOX announces no readable port: '$MANN'" ;; |
| 657 | esac | 637 | esac |
| 658 | # The announced port, as /proc/net/udp writes a wildcard bind — the | 638 | # The announced port, as /proc/net/udp writes a wildcard bind — the |
| 659 | # spelling both arms of the oracle take. Asked of lsof on the Mac, | 639 | # spelling both arms of the oracle take. Asked of lsof on the Mac, |
| 660 | # so the claim "the daemon is listening there" is the OS's and not | 640 | # so the claim "the daemon is listening there" is the OS's and not |
| 661 | # the daemon's own. | 641 | # the daemon's own. |
| 662 | mssh 60 >/dev/null <<M || fail linux-to-mac "$MAC announces '$MANN' but binds no UDP $MPORT" | 642 | box_ssh 60 "$MAC_BOX" >/dev/null <<M || fail linux-to-mac "$MAC_BOX announces '$MANN' but binds no UDP $MPORT" |
| 663 | . \$HOME/xos/os_oracle.sh | 643 | . \$HOME/xos/os_oracle.sh |
| 664 | udp_local_bound $(printf '00000000:%04X' "$MPORT") | 644 | udp_local_bound $(printf '00000000:%04X' "$MPORT") |
| 665 | M | 645 | M |
| 666 | if grep -q 'attaching over ssh' "$SCRATCH/l2m.err"; then | 646 | if grep -q 'attaching over ssh' "$SCRATCH/l2m.err"; then |
| 667 | fail linux-to-mac "$MAC offered QUIC on $MPORT and the dial fell back to ssh" | 647 | fail linux-to-mac "$MAC_BOX offered QUIC on $MPORT and the dial fell back to ssh" |
| 668 | fi | 648 | fi |
| 669 | L2M_WIRE="QUIC on the lazily-bound port $MPORT" ;; | 649 | L2M_WIRE="QUIC on the lazily-bound port $MPORT" ;; |
| 670 | *) | 650 | *) |
| 671 | fail linux-to-mac "$MAC's daemon answers no announce this leg can read: '$MANN'" ;; | 651 | fail linux-to-mac "$MAC_BOX's daemon answers no announce this leg can read: '$MANN'" ;; |
| 672 | esac | 652 | esac |
| 673 | ok linux-to-mac "shell $L2M_PID under daemon $MPID on $MAC_SOCK, over $L2M_WIRE" | 653 | ok linux-to-mac "shell $L2M_PID under daemon $MPID on $MAC_SOCK, over $L2M_WIRE" |
| 674 | 654 | ||
| @@ -679,10 +659,10 @@ ok linux-to-mac "shell $L2M_PID under daemon $MPID on $MAC_SOCK, over $L2M_WIRE" | |||
| 679 | # fails with the client's own words — which is a fact about the platform, | 659 | # fails with the client's own words — which is a fact about the platform, |
| 680 | # not a harness problem to loosen away. | 660 | # not a harness problem to loosen away. |
| 681 | leg | 661 | leg |
| 682 | mssh 60 >/dev/null <<'M' || fail linux-to-mac-quic "mux d stop was refused on $MAC" | 662 | box_ssh 60 "$MAC_BOX" >/dev/null <<'M' || fail linux-to-mac-quic "mux d stop was refused on $MAC_BOX" |
| 683 | $HOME/.local/bin/mux d stop | 663 | $HOME/.local/bin/mux d stop |
| 684 | M | 664 | M |
| 685 | mssh 60 >/dev/null <<M || fail linux-to-mac-quic "the bare daemon $MPID survives mux d stop by 3 s" | 665 | box_ssh 60 "$MAC_BOX" >/dev/null <<M || fail linux-to-mac-quic "the bare daemon $MPID survives mux d stop by 3 s" |
| 686 | . \$HOME/xos/os_oracle.sh | 666 | . \$HOME/xos/os_oracle.sh |
| 687 | _i=0 | 667 | _i=0 |
| 688 | while [ \$_i -lt 30 ]; do | 668 | while [ \$_i -lt 30 ]; do |
| @@ -691,17 +671,17 @@ while [ \$_i -lt 30 ]; do | |||
| 691 | done | 671 | done |
| 692 | exit 1 | 672 | exit 1 |
| 693 | M | 673 | M |
| 694 | mssh 90 >/dev/null <<M || fail linux-to-mac-quic "mux d start -d --quic was refused on $MAC" | 674 | box_ssh 90 "$MAC_BOX" >/dev/null <<M || fail linux-to-mac-quic "mux d start -d --quic was refused on $MAC_BOX" |
| 695 | MUX_SHELL_INTEGRATION=1 \$HOME/.local/bin/mux d start -d --quic 0.0.0.0:$QUIC_PORT --key \$HOME/xos/key | 675 | MUX_SHELL_INTEGRATION=1 \$HOME/.local/bin/mux d start -d --quic 0.0.0.0:$QUIC_PORT --key \$HOME/xos/key |
| 696 | M | 676 | M |
| 697 | MPID2=$(mssh 30 <<'M' | 677 | MPID2=$(box_ssh 30 "$MAC_BOX" <<'M' |
| 698 | pgrep -x mux | head -1 | 678 | pgrep -x mux | head -1 |
| 699 | M | 679 | M |
| 700 | ) | 680 | ) |
| 701 | [ -n "$MPID2" ] || fail linux-to-mac-quic "no mux on $MAC after the QUIC start" | 681 | [ -n "$MPID2" ] || fail linux-to-mac-quic "no mux on $MAC_BOX after the QUIC start" |
| 702 | [ "$MPID2" != "$MPID" ] || | 682 | [ "$MPID2" != "$MPID" ] || |
| 703 | fail linux-to-mac-quic "$MAC still runs the pre-stop daemon $MPID; the restart did not happen" | 683 | fail linux-to-mac-quic "$MAC_BOX still runs the pre-stop daemon $MPID; the restart did not happen" |
| 704 | mssh 60 >/dev/null <<M || fail linux-to-mac-quic "daemon $MPID2 binds no UDP $QUIC_PORT on $MAC" | 684 | box_ssh 60 "$MAC_BOX" >/dev/null <<M || fail linux-to-mac-quic "daemon $MPID2 binds no UDP $QUIC_PORT on $MAC_BOX" |
| 705 | . \$HOME/xos/os_oracle.sh | 685 | . \$HOME/xos/os_oracle.sh |
| 706 | _i=0 | 686 | _i=0 |
| 707 | while [ \$_i -lt 50 ]; do | 687 | while [ \$_i -lt 50 ]; do |
| @@ -737,7 +717,7 @@ set -e | |||
| 737 | exit 1; } | 717 | exit 1; } |
| 738 | L2MQ_PID=$(sed -n 's/.*XOS-L2MQ-arm64-\([0-9][0-9]*\).*/\1/p' "$SCRATCH/l2mq.cap" | head -1) | 718 | L2MQ_PID=$(sed -n 's/.*XOS-L2MQ-arm64-\([0-9][0-9]*\).*/\1/p' "$SCRATCH/l2mq.cap" | head -1) |
| 739 | [ -n "$L2MQ_PID" ] || fail linux-to-mac-quic "the QUIC capture carries no shell pid" | 719 | [ -n "$L2MQ_PID" ] || fail linux-to-mac-quic "the QUIC capture carries no shell pid" |
| 740 | mssh 60 >/dev/null <<M || fail linux-to-mac-quic "shell $L2MQ_PID is not a child of daemon $MPID2" | 720 | box_ssh 60 "$MAC_BOX" >/dev/null <<M || fail linux-to-mac-quic "shell $L2MQ_PID is not a child of daemon $MPID2" |
| 741 | . \$HOME/xos/os_oracle.sh | 721 | . \$HOME/xos/os_oracle.sh |
| 742 | pid_children $MPID2 | grep -qx $L2MQ_PID | 722 | pid_children $MPID2 | grep -qx $L2MQ_PID |
| 743 | M | 723 | M |
| @@ -745,15 +725,15 @@ AQ2=$(set +e; timeout 60 "$MUX" a run --quic "$MAC_IP:$QUIC_PORT" --key "$SCRATC | |||
| 745 | --timeout 20000 'echo l2mq-$$'; echo "RC=$?") | 725 | --timeout 20000 'echo l2mq-$$'; echo "RC=$?") |
| 746 | case "$AQ2" in | 726 | case "$AQ2" in |
| 747 | *RC=0*) ;; | 727 | *RC=0*) ;; |
| 748 | *) fail linux-to-mac-quic "mux a run --quic to $MAC did not answer: $AQ2" ;; | 728 | *) fail linux-to-mac-quic "mux a run --quic to $MAC_BOX did not answer: $AQ2" ;; |
| 749 | esac | 729 | esac |
| 750 | case "$AQ2" in | 730 | case "$AQ2" in |
| 751 | *'"mechanism":"marks"'*) ;; | 731 | *'"mechanism":"marks"'*) ;; |
| 752 | *) fail linux-to-mac-quic "no marks mechanism in the reply from $MAC: $AQ2" ;; | 732 | *) fail linux-to-mac-quic "no marks mechanism in the reply from $MAC_BOX: $AQ2" ;; |
| 753 | esac | 733 | esac |
| 754 | AQ2_PID=$(printf '%s\n' "$AQ2" | sed -n 's/.*l2mq-\([0-9][0-9]*\).*/\1/p') | 734 | AQ2_PID=$(printf '%s\n' "$AQ2" | sed -n 's/.*l2mq-\([0-9][0-9]*\).*/\1/p') |
| 755 | [ -n "$AQ2_PID" ] || fail linux-to-mac-quic "the reply carries no shell pid: $AQ2" | 735 | [ -n "$AQ2_PID" ] || fail linux-to-mac-quic "the reply carries no shell pid: $AQ2" |
| 756 | mssh 60 >/dev/null <<M || fail linux-to-mac-quic "shell $AQ2_PID is not a child of daemon $MPID2" | 736 | box_ssh 60 "$MAC_BOX" >/dev/null <<M || fail linux-to-mac-quic "shell $AQ2_PID is not a child of daemon $MPID2" |
| 757 | . \$HOME/xos/os_oracle.sh | 737 | . \$HOME/xos/os_oracle.sh |
| 758 | pid_children $MPID2 | grep -qx $AQ2_PID | 738 | pid_children $MPID2 | grep -qx $AQ2_PID |
| 759 | M | 739 | M |
| @@ -764,14 +744,14 @@ ok linux-to-mac-quic "macOS delivered inbound UDP $QUIC_PORT: shells $L2MQ_PID a | |||
| 764 | # `uname -m` is the FIRST line of the preflight and the whole gate: the | 744 | # `uname -m` is the FIRST line of the preflight and the whole gate: the |
| 765 | # image is never opened and nothing is streamed. The pin is therefore both | 745 | # image is never opened and nothing is streamed. The pin is therefore both |
| 766 | # the refusal's own words and the installed binary's hash on the far side, | 746 | # the refusal's own words and the installed binary's hash on the far side, |
| 767 | # unchanged from what legs 2 and 3 put there. | 747 | # unchanged from what leg 3 put there. |
| 768 | leg | 748 | leg |
| 769 | set +e | 749 | set +e |
| 770 | timeout 120 "$MUX" d upgrade "$MAC" --allow-same-version > "$SCRATCH/up_mac.out" 2>&1 | 750 | timeout 120 "$MUX" d upgrade "$MAC_BOX" --allow-same-version > "$SCRATCH/up_mac.out" 2>&1 |
| 771 | RC=$? | 751 | RC=$? |
| 772 | set -e | 752 | set -e |
| 773 | [ "$RC" -ne 0 ] || | 753 | [ "$RC" -ne 0 ] || |
| 774 | fail upgrade-refused "mux d upgrade $MAC exited 0; an x86_64 image must not reach an arm64 box" | 754 | fail upgrade-refused "mux d upgrade $MAC_BOX exited 0; an x86_64 image must not reach an arm64 box" |
| 775 | grep -q "is arm64 and this image is x86_64; refusing to push a binary that cannot run there" \ | 755 | grep -q "is arm64 and this image is x86_64; refusing to push a binary that cannot run there" \ |
| 776 | "$SCRATCH/up_mac.out" || { | 756 | "$SCRATCH/up_mac.out" || { |
| 777 | echo "xos FAIL: upgrade-refused: the Linux→Mac refusal is not the arch line; it said:" | 757 | echo "xos FAIL: upgrade-refused: the Linux→Mac refusal is not the arch line; it said:" |
| @@ -781,13 +761,13 @@ grep -q "is arm64 and this image is x86_64; refusing to push a binary that canno | |||
| 781 | # From the Mac, the mirror. The image there is aarch64 and the far side says | 761 | # From the Mac, the mirror. The image there is aarch64 and the far side says |
| 782 | # x86_64, so the two words differ in BOTH spelling and machine — which is | 762 | # x86_64, so the two words differ in BOTH spelling and machine — which is |
| 783 | # what makes this the mirror image and not the same test twice. | 763 | # what makes this the mirror image and not the same test twice. |
| 784 | UPL=$(mssh 180 <<M | 764 | UPL=$(box_ssh 180 "$MAC_BOX" <<M |
| 785 | \$HOME/.local/bin/mux d upgrade $LAN --allow-same-version 2>&1 | 765 | \$HOME/.local/bin/mux d upgrade $LINUX_BOX --allow-same-version 2>&1 |
| 786 | echo "RC=\$?" | 766 | echo "RC=\$?" |
| 787 | M | 767 | M |
| 788 | ) | 768 | ) |
| 789 | case "$UPL" in | 769 | case "$UPL" in |
| 790 | *RC=0*) fail upgrade-refused "mux d upgrade $LAN from $MAC exited 0; an aarch64 image must not reach an x86_64 box" ;; | 770 | *RC=0*) fail upgrade-refused "mux d upgrade $LINUX_BOX from $MAC_BOX exited 0; an aarch64 image must not reach an x86_64 box" ;; |
| 791 | esac | 771 | esac |
| 792 | case "$UPL" in | 772 | case "$UPL" in |
| 793 | *"is x86_64 and this image is aarch64; refusing to push a binary that cannot run there"*) ;; | 773 | *"is x86_64 and this image is aarch64; refusing to push a binary that cannot run there"*) ;; |
| @@ -795,13 +775,13 @@ case "$UPL" in | |||
| 795 | esac | 775 | esac |
| 796 | 776 | ||
| 797 | POST=$( | 777 | POST=$( |
| 798 | mssh 60 <<M | 778 | box_ssh 60 "$MAC_BOX" <<M |
| 799 | . \$HOME/xos/os_oracle.sh | 779 | . \$HOME/xos/os_oracle.sh |
| 800 | echo "macsha=\$(sha256_of \$HOME/.local/bin/mux)" | 780 | echo "macsha=\$(sha256_of \$HOME/.local/bin/mux)" |
| 801 | echo "macpid=\$(pgrep -x mux | head -1)" | 781 | echo "macpid=\$(pgrep -x mux | head -1)" |
| 802 | echo "macexe=\$(pid_exe \$(pgrep -x mux | head -1))" | 782 | echo "macexe=\$(pid_exe \$(pgrep -x mux | head -1))" |
| 803 | M | 783 | M |
| 804 | lssh 60 <<L | 784 | box_ssh 60 "$LINUX_BOX" <<L |
| 805 | . ~/xos/os_oracle.sh | 785 | . ~/xos/os_oracle.sh |
| 806 | echo "lansha=\$(sha256_of ~/.local/bin/mux)" | 786 | echo "lansha=\$(sha256_of ~/.local/bin/mux)" |
| 807 | echo "lanpid=\$(pgrep -x mux | head -1)" | 787 | echo "lanpid=\$(pgrep -x mux | head -1)" |
| @@ -809,28 +789,28 @@ echo "lanexe=\$(pid_exe $LPID)" | |||
| 809 | L | 789 | L |
| 810 | ) | 790 | ) |
| 811 | printf '%s\n' "$POST" | grep -qx "macsha=$MAC_BIN_SHA" || | 791 | printf '%s\n' "$POST" | grep -qx "macsha=$MAC_BIN_SHA" || |
| 812 | fail upgrade-refused "$MAC's installed mux changed across the refusal: $POST" | 792 | fail upgrade-refused "$MAC_BOX's installed mux changed across the refusal: $POST" |
| 813 | printf '%s\n' "$POST" | grep -qx "lansha=$LAN_BIN_SHA" || | 793 | printf '%s\n' "$POST" | grep -qx "lansha=$LAN_BIN_SHA" || |
| 814 | fail upgrade-refused "$LAN's installed mux changed across the refusal: $POST" | 794 | fail upgrade-refused "$LINUX_BOX's installed mux changed across the refusal: $POST" |
| 815 | printf '%s\n' "$POST" | grep -qx "macpid=$MPID2" || | 795 | printf '%s\n' "$POST" | grep -qx "macpid=$MPID2" || |
| 816 | fail upgrade-refused "$MAC's daemon is no longer $MPID2: $POST" | 796 | fail upgrade-refused "$MAC_BOX's daemon is no longer $MPID2: $POST" |
| 817 | printf '%s\n' "$POST" | grep -qx "lanpid=$LPID" || | 797 | printf '%s\n' "$POST" | grep -qx "lanpid=$LPID" || |
| 818 | fail upgrade-refused "$LAN's daemon is no longer $LPID: $POST" | 798 | fail upgrade-refused "$LINUX_BOX's daemon is no longer $LPID: $POST" |
| 819 | printf '%s\n' "$POST" | grep -qx "macexe=$MAC_EXE" || | 799 | printf '%s\n' "$POST" | grep -qx "macexe=$MAC_EXE" || |
| 820 | fail upgrade-refused "$MAC's daemon execs something else now: $POST" | 800 | fail upgrade-refused "$MAC_BOX's daemon execs something else now: $POST" |
| 821 | printf '%s\n' "$POST" | grep -qx "lanexe=$LAN_EXE" || | 801 | printf '%s\n' "$POST" | grep -qx "lanexe=$LAN_EXE" || |
| 822 | fail upgrade-refused "$LAN's daemon execs something else now: $POST" | 802 | fail upgrade-refused "$LINUX_BOX's daemon execs something else now: $POST" |
| 823 | ok upgrade-refused "both pushes refused on uname alone; both images and both daemons untouched" | 803 | ok upgrade-refused "both pushes refused on uname alone; both images and both daemons untouched" |
| 824 | 804 | ||
| 825 | # ---- leg 10: stop — and each box's socket goes with its daemon --------- | 805 | # ---- leg 10: stop — and each box's socket goes with its daemon --------- |
| 826 | leg | 806 | leg |
| 827 | mssh 60 >/dev/null <<'M' || fail stop "mux d stop was refused on $MAC" | 807 | box_ssh 60 "$MAC_BOX" >/dev/null <<'M' || fail stop "mux d stop was refused on $MAC_BOX" |
| 828 | $HOME/.local/bin/mux d stop | 808 | $HOME/.local/bin/mux d stop |
| 829 | M | 809 | M |
| 830 | lssh 60 >/dev/null <<'L' || fail stop "mux d stop was refused on $LAN" | 810 | box_ssh 60 "$LINUX_BOX" >/dev/null <<'L' || fail stop "mux d stop was refused on $LINUX_BOX" |
| 831 | ~/.local/bin/mux d stop | 811 | ~/.local/bin/mux d stop |
| 832 | L | 812 | L |
| 833 | MEND=$(mssh 60 <<M | 813 | MEND=$(box_ssh 60 "$MAC_BOX" <<M |
| 834 | . \$HOME/xos/os_oracle.sh | 814 | . \$HOME/xos/os_oracle.sh |
| 835 | _i=0 | 815 | _i=0 |
| 836 | while [ \$_i -lt 30 ]; do | 816 | while [ \$_i -lt 30 ]; do |
| @@ -842,10 +822,10 @@ pid_alive $MPID2 && echo "alive=yes" || echo "alive=no" | |||
| 842 | M | 822 | M |
| 843 | ) | 823 | ) |
| 844 | printf '%s\n' "$MEND" | grep -qx 'alive=no' || | 824 | printf '%s\n' "$MEND" | grep -qx 'alive=no' || |
| 845 | fail stop "$MAC's daemon $MPID2 survives mux d stop by 3 s: $MEND" | 825 | fail stop "$MAC_BOX's daemon $MPID2 survives mux d stop by 3 s: $MEND" |
| 846 | printf '%s\n' "$MEND" | grep -qx 'sock=gone' || | 826 | printf '%s\n' "$MEND" | grep -qx 'sock=gone' || |
| 847 | fail stop "$MAC_SOCK outlives the daemon that bound it: $MEND" | 827 | fail stop "$MAC_SOCK outlives the daemon that bound it: $MEND" |
| 848 | LEND=$(lssh 60 <<L | 828 | LEND=$(box_ssh 60 "$LINUX_BOX" <<L |
| 849 | . ~/xos/os_oracle.sh | 829 | . ~/xos/os_oracle.sh |
| 850 | _i=0 | 830 | _i=0 |
| 851 | while [ \$_i -lt 30 ]; do | 831 | while [ \$_i -lt 30 ]; do |
| @@ -857,7 +837,7 @@ pid_alive $LPID && echo "alive=yes" || echo "alive=no" | |||
| 857 | L | 837 | L |
| 858 | ) | 838 | ) |
| 859 | printf '%s\n' "$LEND" | grep -qx 'alive=no' || | 839 | printf '%s\n' "$LEND" | grep -qx 'alive=no' || |
| 860 | fail stop "$LAN's daemon $LPID survives mux d stop by 3 s: $LEND" | 840 | fail stop "$LINUX_BOX's daemon $LPID survives mux d stop by 3 s: $LEND" |
| 861 | printf '%s\n' "$LEND" | grep -qx 'sock=gone' || | 841 | printf '%s\n' "$LEND" | grep -qx 'sock=gone' || |
| 862 | fail stop "$LAN_SOCK outlives the daemon that bound it: $LEND" | 842 | fail stop "$LAN_SOCK outlives the daemon that bound it: $LEND" |
| 863 | ok stop "both daemons gone within 3 s, each taking its own socket with it" | 843 | ok stop "both daemons gone within 3 s, each taking its own socket with it" |
| @@ -871,4 +851,4 @@ ok stop "both daemons gone within 3 s, each taking its own socket with it" | |||
| 871 | echo " a leg was added (update the pin) or silently lost" | 851 | echo " a leg was added (update the pin) or silently lost" |
| 872 | exit 1 | 852 | exit 1 |
| 873 | } | 853 | } |
| 874 | echo "xos OK ($OK_COUNT legs: $MAC ($MAC_ARCH) and $LAN ($LAN_ARCH), each the other's client)" | 854 | echo "xos OK ($OK_COUNT legs: $MAC_BOX ($WANT_MAC_ARCH) and $LINUX_BOX ($WANT_LAN_ARCH), each the other's client)" |