Settings ▸ Remote gains a "Connect via" picker — LAN (default), Tailscale (tailnet), or Relay (disabled, coming soon). On Tailnet, both devices run an embedded tsnet node via TailscaleKit (tailscale/libtailscale) and sync frames flow over the user's tailnet, so the phone can connect from anywhere the tailnet reaches; Noise E2EE runs above the transport unchanged. - NucleicTailnet (new target, macOS + iOS): TailnetNode wraps TailscaleKit's node lifecycle (auth-key login, generation-fenced start/stop since up() is un-cancellable) and drops to the framework's public C API for the data path — tailscale_dial/listen/accept hand back full-duplex socketpair fds, wrapped by FDFrameChannel (DispatchIO) into the shared FrameChannel seam. The Swift wrapper's one-way connection actors can't carry a bidirectional stream. - Host: TailnetListener adopts SyncListener; startSyncServer is single-flight and honors toggle-off/picker changes at the commit point; pairing QRs carry transport + tailnet IP/port hints (PairingPayload additive optional fields, forward/backward compatible over CBOR). - iPhone: pair/reconnect dial over whichever transport the pairing recorded; Settings gains a Tailscale auth-key field (Keychain, committed on editing end); connectivity chip shows "Connected · Tailnet". - TailscaleKit has no SwiftPM distribution: scripts/build-tailscalekit.sh builds a pinned libtailscale commit into an untracked local xcframework; Package.swift links it only when present (everything builds without it, the picker then reports Tailscale support as not built in), and the script clears SwiftPM's content-keyed manifest cache so the toggle is picked up. - iOS floor 17.0 → 18.1 (TailscaleKit requires the iOS 18 Swift runtime); package-app.sh embeds the framework in the .app like Sparkle. 703-test suite: no new failures (the 7 fake-claude/fake-grok staging issues reproduce identically on an untouched checkout — pre-existing, tracked separately). New coverage: FDFrameChannel over socketpairs, pairing-payload version-skew both directions, transport-setting resolution. Co-Authored-By: Claude Fable 5 <[email protected]>
71 lines
3.4 KiB
Bash
Executable File
71 lines
3.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Build the vendored TailscaleKit.xcframework the Tailnet sync transport links against.
|
|
#
|
|
# scripts/build-tailscalekit.sh
|
|
#
|
|
# TailscaleKit (github.com/tailscale/libtailscale, swift/) has no SwiftPM package and no
|
|
# tagged releases — it's an Xcode framework project wrapping a Go c-archive (tsnet), so we
|
|
# build it from a pinned commit and stage the result as a local binary target:
|
|
#
|
|
# third_party/TailscaleKit/TailscaleKit.xcframework (macOS + iOS + iOS-simulator slices)
|
|
#
|
|
# Package.swift picks the xcframework up automatically when it exists (see the
|
|
# `tailscaleKitAvailable` conditional there); without it every target still builds — the
|
|
# Tailnet transport just reports "not built in" at runtime. Like Resources/vmlinux-arm64,
|
|
# the artifact is a large binary and is NOT tracked; re-run this script after a clean clone.
|
|
#
|
|
# Requirements: Xcode (16.1+) and a Go toolchain (go.mod wants 1.25; any Go >= 1.21 will
|
|
# auto-download the right toolchain via GOTOOLCHAIN=auto).
|
|
|
|
set -euo pipefail
|
|
|
|
# Pinned libtailscale commit (main @ 2026-02-28, tailscale.com v1.94.1). Bump deliberately:
|
|
# the Swift wrapper API is pre-1.0 and the umbrella header mirrors the C API by hand.
|
|
LIBTAILSCALE_COMMIT="5e89501def80a6579ca5d0f9a02f336be62b8f2e"
|
|
LIBTAILSCALE_URL="https://github.com/tailscale/libtailscale.git"
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
STAGE="$ROOT/third_party/TailscaleKit"
|
|
CHECKOUT="$STAGE/.build/libtailscale"
|
|
OUT="$STAGE/TailscaleKit.xcframework"
|
|
|
|
command -v go >/dev/null || { echo "error: Go toolchain required (brew install go)" >&2; exit 1; }
|
|
|
|
mkdir -p "$STAGE/.build"
|
|
if [[ ! -d "$CHECKOUT/.git" ]]; then
|
|
git clone "$LIBTAILSCALE_URL" "$CHECKOUT"
|
|
fi
|
|
git -C "$CHECKOUT" fetch --quiet origin "$LIBTAILSCALE_COMMIT" 2>/dev/null || git -C "$CHECKOUT" fetch --quiet origin
|
|
git -C "$CHECKOUT" checkout --quiet "$LIBTAILSCALE_COMMIT"
|
|
|
|
# Their swift/Makefile drives everything: the Go c-archive per platform (via the root
|
|
# Makefile), then xcodebuild for each framework slice. Unsigned by design; the app build
|
|
# signs on embed. Their Makefile pipes xcodebuild through xcpretty/cat (masking failures),
|
|
# so start from a clean slate and verify every slice actually exists afterwards — never
|
|
# package stale products from an earlier run.
|
|
rm -rf "$CHECKOUT/swift/build"
|
|
make -C "$CHECKOUT/swift" macos ios ios-sim
|
|
|
|
PRODUCTS="$CHECKOUT/swift/build/Build/Products"
|
|
for slice in Release Release-iphoneos Release-iphonesimulator; do
|
|
[[ -e "$PRODUCTS/$slice/TailscaleKit.framework/TailscaleKit" ]] || {
|
|
echo "error: missing $slice slice — xcodebuild failed inside libtailscale's Makefile (see output above)" >&2
|
|
exit 1
|
|
}
|
|
done
|
|
rm -rf "$OUT"
|
|
xcodebuild -create-xcframework \
|
|
-framework "$PRODUCTS/Release/TailscaleKit.framework" \
|
|
-framework "$PRODUCTS/Release-iphoneos/TailscaleKit.framework" \
|
|
-framework "$PRODUCTS/Release-iphonesimulator/TailscaleKit.framework" \
|
|
-output "$OUT"
|
|
|
|
# SwiftPM caches evaluated manifests by CONTENT, so an unchanged Package.swift keeps the
|
|
# pre-artifact "TailscaleKit unavailable" evaluation forever — the next build would silently
|
|
# skip the framework. Drop the manifest cache so the availability check re-runs.
|
|
rm -rf ~/Library/Caches/org.swift.swiftpm/manifests
|
|
|
|
echo "✓ $OUT ($(du -sh "$OUT" | cut -f1), libtailscale @ ${LIBTAILSCALE_COMMIT:0:12})"
|
|
echo " (if Xcode has the iOS project open, File ▸ Packages ▸ Reset Package Caches once)"
|