mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 08:02:24 +00:00
* fix(control): bump the dialect to v6 so an out-of-date server says so The control dialect has been renamed, extended and cut since it was last numbered, all of it against CONTROL_VERSION 5: the machine tree replaced WorkspaceList/Get/Put/Delete with WorkspaceTree, MachineGet and the tab/pane verbs, GitStream arrived with its chunk and end events, and ReplyOk::Attached and FileMeta went away. A peer left behind by any of that still answers the hello, because the number it answers with still matches. It is also still sitting at the path the installer looks for, tty7-server-c5p5, so a client decides it already has the server it needs. Then the first call reaches a variant the peer has never heard of, the frame fails to decode, and the read loop takes the whole link down with it. What the user sees is a remote workspace that opens with no tabs and a git detail pane that never fills, with nothing anywhere saying why. Moving the number puts all three guards back: the hello is refused with the message that names the old build, the remote binary is looked for at c6p5 and installed rather than trusted, and a stale local daemon gets the restart prompt it should have been getting all along. Document the rule next to the constant while it is fresh: move it when a variant is added or removed. The feature strings only cover what a peer can safely ignore, and a request it cannot decode is not that. * feat(remote): publish a tty7-server for macOS hosts A remote workspace has been Linux-only for no reason anyone chose: the installer derives the asset name from `uname -sm`, and the only names it knew were the two musl builds. A Mac on the other end of an SSH profile got "a remote tty7 workspace needs a Linux host" and stopped there. Publish the two Apple slices alongside them and teach the installer to ask for them. `Darwin arm64` and `Darwin x86_64` now map to tty7-server-macos-aarch64 and tty7-server-macos-x86_64; everything past that point already worked, because nothing under it was ever Linux- specific — the install path is POSIX, the upload is SFTP, and the dialect probe runs the binary before trusting it. The machine names are matched per system rather than by architecture alone. Linux says aarch64 on one distribution and arm64 on the next, while a Mac only ever says arm64, so honouring Linux's spellings under Darwin would be guessing at output no Mac produces. Static linking is not the instrument on macOS — Apple ships no static libSystem — so assert-macho.sh stands in for assert-static.sh with the guarantee that actually matters: every dependency resolves under /usr/lib or /System/Library, so nothing the destination Mac lacks can be picked up from a build runner, and the binary carries the signature arm64 refuses to run without. Not signed or notarized beyond that, deliberately. The binary is never downloaded by the Mac that runs it: the client fetches it, verifies it against checksums.txt and writes it over SFTP, which sets no quarantine attribute, so Gatekeeper is not in the path. ASSET_X86_64 and ASSET_AARCH64 become ASSET_LINUX_*, which is what they always meant and could not keep meaning next to a macOS pair. * fix(ci): sign the x86_64 macOS server, and stop the guard flaking on it Two faults the first green run hid from each other. The linker ad-hoc signs the arm64 slice because Apple Silicon will not execute anything unsigned, and leaves x86_64 bare. That is fine on an Intel Mac, but the x86_64 server is also what an Apple Silicon box gets when it asks through a Rosetta shell, and handing that machine an unsigned binary is a guess about Rosetta nobody needs to make. Sign both slices ad-hoc in the workflow — no identity, no secrets, nothing to do with the notarized signing the GUI bundles get. The guard that caught it was itself unreliable: `codesign -dv | grep -q` under `pipefail` reports failure whenever grep wins the race, because -q exits on the first match and the writer takes SIGPIPE. Small output means the writer usually finishes first, which is why the arm64 job passed and x86_64 failed on the same signed-or-not question. Capture into a variable and match afterwards, the way the release workflow already does it. --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
81 lines
3.2 KiB
Bash
Executable File
81 lines
3.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# Usage: assert-macho.sh <path-to-mach-o> <expected-arch>
|
|
# Fail unless the binary is a Mach-O executable for <expected-arch> that depends
|
|
# on nothing but the libraries every macOS already has, and carries a code
|
|
# signature.
|
|
#
|
|
# The macOS counterpart of assert-static.sh, and the same decision (D10) behind
|
|
# it: one `tty7-server` binary is pushed to an arbitrary remote Mac and has to
|
|
# run there with nothing installed alongside it. Static linking is not the
|
|
# instrument on macOS — Apple does not ship a static libSystem and linking one
|
|
# is unsupported — so the equivalent guarantee is "links only what the OS
|
|
# guarantees is present". A stray Homebrew dependency picked up from the runner
|
|
# would still compile, still pass a build-only job, and then fail on the first
|
|
# Mac that does not have /opt/homebrew — far from the change that caused it.
|
|
set -euo pipefail
|
|
|
|
BIN="$1"
|
|
WANT_ARCH="$2"
|
|
|
|
if [ ! -f "$BIN" ]; then
|
|
echo "::error::assert-macho.sh: $BIN does not exist"
|
|
exit 1
|
|
fi
|
|
|
|
echo "--- file ---"
|
|
file "$BIN"
|
|
echo "--- otool -L ---"
|
|
otool -L "$BIN"
|
|
echo "--- otool -l (build version) ---"
|
|
otool -l "$BIN" | grep -A 4 -E 'LC_BUILD_VERSION|LC_VERSION_MIN_MACOSX' || true
|
|
|
|
fail=0
|
|
|
|
# Each probe is captured into a variable and matched afterwards, never piped
|
|
# into `grep -q`. Under `pipefail` that pipeline is a coin toss: -q exits on the
|
|
# first match, the writer takes SIGPIPE, and the pipeline reports failure — so a
|
|
# binary that passes would be reported as failing, on the runs where grep
|
|
# happened to win the race.
|
|
FILE_SAYS=$(file "$BIN")
|
|
if [[ "$FILE_SAYS" != *"Mach-O 64-bit executable ${WANT_ARCH}"* ]]; then
|
|
echo "::error::$BIN is not a 64-bit Mach-O executable for ${WANT_ARCH}"
|
|
fail=1
|
|
fi
|
|
|
|
# Every dependency must live somewhere the OS owns. /usr/lib and
|
|
# /System/Library are the two prefixes shipped with macOS itself; anything else
|
|
# — /opt/homebrew, /usr/local, @rpath into a bundle we are not shipping — is a
|
|
# library the destination Mac has no reason to have.
|
|
#
|
|
# `tail -n +2` drops otool's first line, which is the binary's own path and
|
|
# would otherwise be judged as if it were a dependency.
|
|
STRAY=$(otool -L "$BIN" | tail -n +2 | awk '{print $1}' \
|
|
| grep -Ev '^(/usr/lib/|/System/Library/)' || true)
|
|
if [ -n "$STRAY" ]; then
|
|
echo "::error::$BIN links libraries that are not part of macOS:"
|
|
echo "$STRAY"
|
|
fail=1
|
|
fi
|
|
|
|
# arm64 refuses to execute an unsigned binary outright, so an unsigned build
|
|
# would not fail here but on the user's Mac, as "killed: 9" with no explanation.
|
|
#
|
|
# Asserted for both slices, not just arm64. The linker ad-hoc signs arm64 on its
|
|
# own and leaves x86_64 bare — which would be fine on an Intel Mac, but the
|
|
# x86_64 server is also what an Apple Silicon box gets when it asks through a
|
|
# Rosetta shell (`uname -sm` = "Darwin x86_64"), and that is not a machine to
|
|
# hand an unsigned binary to on a guess. The workflow signs it; this catches the
|
|
# day it stops.
|
|
SIGNING=$(codesign -dv "$BIN" 2>&1 || true)
|
|
if [[ "$SIGNING" != *"Signature="* ]]; then
|
|
echo "::error::$BIN carries no code signature — arm64 macOS will refuse to run it"
|
|
echo "$SIGNING"
|
|
fail=1
|
|
fi
|
|
|
|
if [ "$fail" -ne 0 ]; then
|
|
exit 1
|
|
fi
|
|
|
|
echo "✅ $BIN is a self-contained ${WANT_ARCH} Mach-O ($(du -h "$BIN" | cut -f1))"
|