a73x

3fb9ee75

test: make xos takes its three boxes by name

a73x   2026-09-04 12:50

Commit message
test: make xos takes its three boxes by name

The Mac side of every leg is MAC_BOX, a box with no toolchain, and not
the machine that built the image: MAC_BUILDER builds, the binary and the
pty fixture are streamed onto MAC_BOX, and the sha256 on the far side is
compared with the builder's. LINUX_BOX is the other end. The QUIC legs
dial each name's host half instead of a hardcoded address, so a stale IP
literal can no longer make a leg grade some other machine's silence.

Both boxes are scrubbed at the start and again on the way out, which
retires the "this is a person's machine" refusals and the MAC_CFG_MADE
bookkeeping that put back a ~/.config/mux the product had created.

Two things the scrub cannot cover are done in the preflight. mux's own
entry dial spawns a plain `ssh HOST` that reads the real known_hosts and
cannot be handed the loose options the harness uses for a guest whose
key changes every clone, so the stale entry is forgotten and the new key
learned once, here. And a plain `ssh BOX 'command -v mux'` must answer
~/.local/bin/mux on both boxes, because that is the question the handoff
actually asks — asserted now rather than assumed of a configured box.

Ten legs, the same ten assertions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SakwJEwD9dXBoRP5kWbemW

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)"