Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,69 @@ jobs:
otool -L "$BIN" | tee /tmp/o.txt
if otool -L "$BIN" | tail -n +2 | grep -iq tailscale; then
echo "::error::updater links a tailscale product (self-update circularity)"; exit 1; fi

build-legacy106:
name: Cross-build + legacy106 gate (macos-26)
runs-on: macos-26
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Install mavericks-shipyard
uses: ModernMavericks/shipyard/.github/actions/install@v1

- name: Install patched Go (mavericks-golang cross toolchain)
run: |
ver="$(cat components/golang/version)"
base="https://github.com/ModernMavericks/golang/releases/download/${ver}"
curl -fL --retry 3 -o SHA256SUMS "$base/SHA256SUMS"
gover="${ver%%-mavericks.*}"; rev="${ver#*-}"; pkg=""; sum=""
for pref in golang go126; do
cand="${pref}-${gover}-cross-${rev}.pkg"
s=$(awk -v f="$cand" '$2==f' SHA256SUMS || true)
if [ -n "$s" ]; then pkg="$cand"; sum="$s"; break; fi
done
[ -n "$pkg" ] || { echo "::error::no cross pkg for $ver in golang SHA256SUMS"; exit 1; }
curl -fL --retry 3 -o "$pkg" "$base/$pkg"
printf '%s\n' "$sum" | shasum -a 256 -c -
sudo installer -pkg "$pkg" -target /
echo "/usr/local/go126-cross/bin" >> "$GITHUB_PATH"

- name: Confirm the patched cross toolchain is on PATH
run: |
test "$(command -v go)" = /usr/local/go126-cross/bin/go
go version

- name: Cache SDKs (distinct key from the 10.9 job's cache)
uses: actions/cache@v6
with:
path: |
${{ github.workspace }}/.sdk-cache
${{ github.workspace }}/.sdk106-cache
key: macosx-sdk106-v1

- name: Derive the upstream version from the pin
run: sh build/derive-upstream-version.sh

- name: Build + gate the 10.6-floor binaries (x86_64 / min-10.6 / Core 2 safe)
env:
MAVERICKS_SDK_CACHE: ${{ github.workspace }}/.sdk-cache
MAVERICKS_SDK106_CACHE: ${{ github.workspace }}/.sdk106-cache
run: |
cmake --preset cross-legacy
cmake --build --preset cross-legacy
ctest --preset cross-legacy

- name: Build the 10.6 updater (must be min-10.6, x86_64)
env:
MAVERICKS_SDK_CACHE: ${{ github.workspace }}/.sdk-cache
MAVERICKS_SDK106_CACHE: ${{ github.workspace }}/.sdk106-cache
run: |
VER="$(cat UPSTREAM_VERSION 2>/dev/null || echo 1.0)"
sh cmake/build_updater106.sh build/updater106 "$VER"
BIN=build/updater106/TailscaleUpdater.app/Contents/MacOS/TailscaleUpdater
ARCHS=$(lipo -info "$BIN" | sed -n 's/.*: //p')
[ "$ARCHS" = "x86_64" ] || { echo "::error::updater archs: $ARCHS (expected x86_64)"; exit 1; }
MIN=$(otool -l "$BIN" | awk '/LC_VERSION_MIN_MACOSX/{f=1} f && /version/{print $2; exit}')
[ "$MIN" = "10.6" ] || { echo "::error::updater min-version is $MIN, expected 10.6"; exit 1; }
47 changes: 44 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,13 @@ jobs:
test "$(command -v go)" = /usr/local/go126-cross/bin/go
go version

- name: Cache the MacOSX10.9 SDK
- name: Cache the MacOSX10.9 SDK + assembled SDK106
uses: actions/cache@v6
with:
path: ${{ github.workspace }}/.sdk-cache
key: macosx10.9-sdk-v1
path: |
${{ github.workspace }}/.sdk-cache
${{ github.workspace }}/.sdk106-cache
key: macosx10.9-sdk-v2

- name: Build + gate the three Go binaries (x86_64 / min-10.9 / no post-10.9 imports)
env:
Expand All @@ -180,6 +182,22 @@ jobs:
if otool -L "$BIN" | tail -n +2 | grep -iq tailscale; then
echo "::error::updater links a tailscale product (self-update circularity)"; exit 1; fi

- name: Build + gate the 10.6-floor binaries
env:
MAVERICKS_SDK_CACHE: ${{ github.workspace }}/.sdk-cache
MAVERICKS_SDK106_CACHE: ${{ github.workspace }}/.sdk106-cache
run: |
cmake --preset cross-legacy
cmake --build --preset cross-legacy
ctest --preset cross-legacy

- name: Build the 10.6 updater
env:
MAVERICKS_SDK_CACHE: ${{ github.workspace }}/.sdk-cache
MAVERICKS_SDK106_CACHE: ${{ github.workspace }}/.sdk106-cache
VER: ${{ steps.ver.outputs.version }}
run: sh cmake/build_updater106.sh build/updater106 "$VER"

# Bundle the packaging inputs into ONE tarball so executable bits (the binaries, the updater .app's
# Mach-Os) survive the artifact round-trip -- artifacts otherwise drop file modes.
- name: Stage + upload packaging inputs
Expand All @@ -190,6 +208,10 @@ jobs:
cp build-cross/gobin/tailscale stage/tailscale
cp build-cross/gobin/tailscale-systray stage/tailscale-systray
cp -R build/updater/TailscaleUpdater.app stage/TailscaleUpdater.app
cp build-cross-legacy/gobin/tailscaled stage/tailscaled-106
cp build-cross-legacy/gobin/tailscale stage/tailscale-106
cp build-cross-legacy/gobin/tailscale-systray stage/tailscale-systray-106
cp -R build/updater106/TailscaleUpdater.app stage/TailscaleUpdater106.app
find stage -name '._*' -delete 2>/dev/null || true
tar -C stage -czf packaging-inputs.tgz .
- uses: actions/upload-artifact@v7
Expand Down Expand Up @@ -268,6 +290,24 @@ jobs:
--version "$VER" --pkg-url "$URL" --min-os 10.9.5 \
--notes-file dist/RELEASE_NOTES.md --pkg "$PKG" > dist/appcast.xml
echo "=== appcast.xml ==="; cat dist/appcast.xml

# 10.6-floor pkg: same version, different binaries + install floor.
PKG106="dist/tailscale-$VER-macos10.6.pkg"
sh cmake/make_app.sh in/tailscale-systray-106 "in/Mavericks Tailscale 106.app" "$VER" "$GITHUB_WORKSPACE/Tailscale.icns" 10.6
SHIPYARD_SCRIPTS="$SH" sh cmake/package_pkg_legacy106.sh --out "$PKG106" --version "$VER" \
--tailscaled in/tailscaled-106 --tailscale in/tailscale-106 \
--systray-app "in/Mavericks Tailscale 106.app" \
--updater-app in/TailscaleUpdater106.app \
--daemon-plist "$GITHUB_WORKSPACE/dist/com.tailscale.tailscaled.plist" \
--systray-agent "$GITHUB_WORKSPACE/dist/com.tailscale.systray.plist" \
--dist "$GITHUB_WORKSPACE/dist"

# Serve a SEPARATE 10.6 appcast (not mixed into the Sparkle feed:
# unsigned items confuse 10.9 Sparkle clients). The 10.6 updater
# reads this file; the 10.9 Sparkle reads the original appcast.
PKG106_URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/$VER/$(basename "$PKG106")"
printf '<?xml version="1.0" encoding="utf-8"?>\n<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">\n <channel>\n <title>Tailscale for Snow Leopard</title>\n <item>\n <title>Version %s</title>\n <sparkle:version>%s</sparkle:version>\n <sparkle:minimumSystemVersion>10.6</sparkle:minimumSystemVersion>\n <enclosure url="%s" type="application/octet-stream" />\n </item>\n </channel>\n</rss>\n' \
"$VER" "$VER" "$PKG106_URL" > dist/appcast-10.6.xml
# No SHA256SUMS here: publish-release.yml regenerates it over everything it attaches, so the
# checksums cover the appcast too and no repo hand-rolls that step.
# Conformance: the .pkg, the appcast and the version we are about to publish must agree with each
Expand All @@ -291,6 +331,7 @@ jobs:
path: |
dist/*.pkg
dist/appcast.xml
dist/appcast-10.6.xml
dist/RELEASE_NOTES.md

# Before anything is published: no log of this run, and no file about to be released, carries a
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ build/updater/
# Finder, and git prints "non-monotonic index" for every ._pack-*.idx it finds.
._*
.DS_Store
build-cross-legacy/
66 changes: 56 additions & 10 deletions CMakePresets.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,72 @@
{
"version": 6,
"cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 },
"cmakeMinimumRequired": {
"major": 3,
"minor": 25,
"patch": 0
},
"configurePresets": [
{
"name": "native",
"displayName": "native build on Mavericks 10.9 (mavericks-golang go126 .pkg)",
"displayName": "native \u2014 build on Mavericks 10.9 (mavericks-golang go126 .pkg)",
"binaryDir": "${sourceDir}/build-native",
"cacheVariables": { "MAVERICKS_TAILSCALE_EXPECTED_MODE": "native" }
"cacheVariables": {
"MAVERICKS_TAILSCALE_EXPECTED_MODE": "native"
}
},
{
"name": "cross",
"displayName": "cross build for Mavericks 10.9 on a modern host (go126-cross)",
"displayName": "cross \u2014 build for Mavericks 10.9 on a modern host (go126-cross)",
"binaryDir": "${sourceDir}/build-cross",
"cacheVariables": { "MAVERICKS_TAILSCALE_EXPECTED_MODE": "cross" }
"cacheVariables": {
"MAVERICKS_TAILSCALE_EXPECTED_MODE": "cross"
}
},
{
"name": "cross-legacy",
"displayName": "cross-legacy \u2014 build for Snow Leopard 10.6 on a modern host (go126-cross + legacy106 floor)",
"binaryDir": "${sourceDir}/build-cross-legacy",
"cacheVariables": {
"MAVERICKS_TAILSCALE_EXPECTED_MODE": "cross",
"MAVERICKS_TAILSCALE_FLOOR": "10.6"
}
}
],
"buildPresets": [
{ "name": "native", "configurePreset": "native" },
{ "name": "cross", "configurePreset": "cross" }
{
"name": "native",
"configurePreset": "native"
},
{
"name": "cross",
"configurePreset": "cross"
},
{
"name": "cross-legacy",
"configurePreset": "cross-legacy"
}
],
"testPresets": [
{ "name": "native", "configurePreset": "native", "output": { "outputOnFailure": true } },
{ "name": "cross", "configurePreset": "cross", "output": { "outputOnFailure": true } }
{
"name": "native",
"configurePreset": "native",
"output": {
"outputOnFailure": true
}
},
{
"name": "cross",
"configurePreset": "cross",
"output": {
"outputOnFailure": true
}
},
{
"name": "cross-legacy",
"configurePreset": "cross-legacy",
"output": {
"outputOnFailure": true
}
}
]
}
}
51 changes: 50 additions & 1 deletion cmake/build_tailscale.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,27 @@
# $3 GO go binary (the MM go126 .pkg)
# $4 ROOT repo root (for patches/ + overlays/)
# $5 VER product version (longStamp)
# $6 FLOOR macOS deployment floor: "10.9" (default) or "10.6" (Snow Leopard).
# 10.6 swaps the CC wrapper for cmake/legacy106/mavericks-cross-clang-106
# (min-10.6 + the 10.6 symbol archive) and builds with GOAMD64=v1 —
# SL Macs are Core 2 class and SIGILL on POPCNT/SSE4.2 (the Go >=1.26
# default baseline emits them).
set -eu
SRC=$1; OUT=$2; GO=$3; ROOT=$4; VER=$5
SRC=$1; OUT=$2; GO=$3; ROOT=$4; VER=$5; FLOOR=${6:-10.9}
case "$FLOOR" in
10.9|10.6) ;;
*) echo "build_tailscale: FLOOR must be 10.9 or 10.6, got '$FLOOR'" >&2; exit 2 ;;
esac
# Build on LOCAL disk. The repo (and thus a repo-relative OUT/SRC) is on NFS: slow, and it leaks
# ._ AppleDouble sidecars into vendor/ (which then break the build / contaminate archives). wrksrc +
# the Go build/module caches live under WORK ($HOME/.cache, local); override with MAVERICKS_TAILSCALE_WORK.
WORK="${MAVERICKS_TAILSCALE_WORK:-$HOME/.cache/mavericks-tailscale/work}"
# Normalize to absolute: the script cd's into wrksrc below, so any relative
# WORK would silently re-anchor to the wrong directory.
case "$WORK" in
/*) ;;
*) WORK="$(cd "$WORK" 2>/dev/null && pwd || echo "$PWD/$WORK")" ;;
esac
WRK="$WORK/wrksrc"
export GOCACHE="$WORK/gocache" GOMODCACHE="$WORK/gomodcache" GOPATH="$WORK/gopath"
export COPYFILE_DISABLE=1 # no ._ sidecars when copying off the NFS source
Expand All @@ -37,6 +52,15 @@ for p in "$ROOT"/patches/*.patch; do echo ">> patch $(basename "$p")"; patch -p0

# 2. Vendor the module graph, then overlay the third-party 10.9-SDK shims into vendor/ (these modules
# call Security/Cocoa APIs newer than the 10.9 SDK and won't compile without a version-gated reimpl).

# 2b. The 10.6 symbol-shim package (blank-imported from ipnauth). Copied for
# every floor BEFORE vendoring -- main-module packages build from source
# under -mod=vendor, but `go mod vendor` must see the import target exist.
# Inert where its symbols are unreferenced, load-bearing on 10.6.
echo ">> overlay legacy106 package"
mkdir -p legacy106
cp "$ROOT/overlays/legacy106/legacy106.go" "$ROOT/overlays/legacy106/legacy106_off.go" legacy106/

unset CC
echo ">> go mod vendor"; "$GO" mod vendor
echo ">> overlay certstore shim"
Expand All @@ -45,9 +69,34 @@ if [ -d vendor/fyne.io/systray ]; then
echo ">> overlay systray shim"; cp "$ROOT/overlays/systray_darwin.m" vendor/fyne.io/systray/systray_darwin.m
fi


# 3. Build each binary. -linkmode=external routes even pure-Go binaries through go.env's min-10.9 CC
# wrapper (Go 1.26 internal-links them to a 12.0 floor otherwise -- see mavericks-golang).
export CGO_ENABLED=1 GOARCH=amd64 GOFLAGS=-mod=vendor
if [ "$FLOOR" = 10.6 ]; then
# Snow Leopard floor: min-10.6 CC wrapper (with our crt1.10.6.o — 10.6 dyld
# needs a classic _start) + GOAMD64=v1 + the 10.6 symbol package. The symbol
# implementations must be cgo C code (a relocatable object on the link line):
# Go references them only via dynamic-bind entries, which an archive member
# cannot win. CC env overrides go.env's default wrapper.
GO_ABS="$(command -v "$GO")"
export MAVERICKS_GO_PREFIX="$(cd "$(dirname "$GO_ABS")/.." && pwd)"
export MAVERICKS_LEGACY106_ROOT="$ROOT/cmake/legacy106"
LEGACY106_A="$WORK/liblegacy106.a"
export MAVERICKS_LEGACY106_A="$LEGACY106_A"
export CC="$ROOT/cmake/legacy106/mavericks-cross-clang-106"
# stubs.c touches DIR internals (__dd_fd): compile it against the SAME
# prepared 10.6-floor sysroot every other object uses, not the host SDK.
SDK106=$(sh "$MAVERICKS_LEGACY106_ROOT/prepare_sdk106.sh" "$MAVERICKS_GO_PREFIX" "$ROOT")
/usr/bin/clang -arch x86_64 -isysroot "$SDK106" -mmacosx-version-min=10.6 \
-c "$ROOT/overlays/legacy106/stubs.c" -o "$WORK/stubs106.o"
rm -f "$LEGACY106_A"
ar rcs "$LEGACY106_A" "$WORK/stubs106.o"
# darwin_10_6 gates the legacy106 package definitions to this floor only
# (see the tag comment in overlays/legacy106/legacy106.go).
export GOFLAGS="-mod=vendor -tags=darwin_10_6"
export GOAMD64=v1
fi
# Stamp BOTH version strings to the clean upstream semver (e.g. 1.98.8). Without a stamp, tailscale
# derives the version from module VCS info -- stripped in our build -- and prints "<ver>-ERR-BuildInfo".
# We deliberately report the plain upstream version to the control server (Hostinfo.IPNVersion is
Expand Down
55 changes: 55 additions & 0 deletions cmake/build_updater106.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/sh
# build_updater106.sh — build the 10.6-native TailscaleUpdater.app.
# Pure ObjC, no external framework, no Go runtime — compiled directly
# with /usr/bin/clang against the prepared 10.6 SDK.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OUT="$1"; VER="${2:-1.0}"
# Only probe PATH when the override is unset (command -v fails hard
# under set -e when go isn't found, making the override unreachable).
if [ -n "${MAVERICKS_GO_PREFIX:-}" ]; then
PREFIX="$MAVERICKS_GO_PREFIX"
else
GO_BIN="$(command -v go 2>/dev/null)" || {
echo 'build_updater106: go not on PATH and MAVERICKS_GO_PREFIX not set' >&2; exit 1; }
PREFIX="$(cd "$(dirname "$GO_BIN")/.." && pwd)"
fi

SDK=$(sh "$ROOT/cmake/legacy106/prepare_sdk106.sh" "$PREFIX" "$ROOT")
APP="$OUT/TailscaleUpdater.app"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"

# The feed URL is build-configurable so forks get the right repo.
# Default: derive from the git remote, fall back to the upstream repo.
REPO="$(git -C "$ROOT" remote get-url origin 2>/dev/null \
| sed 's|.*github.com[:/]||;s|\.git$||' || true)"
[ -n "$REPO" ] || REPO="startergo/tailscale-legacy"
FEED_URL="${UPDATER_FEED_URL:-https://github.com/${REPO}/releases/latest/download/appcast-10.6.xml}"

/usr/bin/clang -arch x86_64 -isysroot "$SDK" -mmacosx-version-min=10.6 \
-x objective-c -fno-objc-arc \
-Wno-deprecated-declarations -Wno-format-security \
-DUPDATER_FEED_URL="\"$FEED_URL\"" \
-framework Cocoa \
-o "$APP/Contents/MacOS/TailscaleUpdater" \
"$ROOT/updater/updater106.m"

cat > "$APP/Contents/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key> <string>TailscaleUpdater</string>
<key>CFBundleIdentifier</key> <string>dev.modernmavericks.TailscaleUpdater</string>
<key>CFBundleVersion</key> <string>$VER</string>
<key>CFBundlePackageType</key> <string>APPL</string>
<key>CFBundleExecutable</key> <string>TailscaleUpdater</string>
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
<key>NSPrincipalClass</key> <string>NSApplication</string>
<key>LSUIElement</key> <true/>
<key>LSMinimumSystemVersion</key> <string>10.6</string>
</dict>
</plist>
PLIST

echo "OK: $APP"
Loading