Skip to content

Releases: indiagrams/tunnelless

TailscaleKit.xcframework — tailscale.com v1.102.3

Choose a tag to compare

@github-actions github-actions released this 31 Aug 16:24
e617edf

Prebuilt TailscaleKit.xcframework, built from tailscale.com v1.102.3.

Upstream libtailscale ships no prebuilt binary, so this is the artifact that
makes adoption a drag-and-drop rather than a Go toolchain setup.

Unsigned by design — your app signs it on embed. Set it to Embed & Sign.

tailscale.com v1.102.3
slices ios-arm64, ios-arm64_x86_64-simulator, macos-arm64

Validated before publishing: privacy manifests present in both iOS slices
(ITMS-91053), no symlinks in iOS slices, and no vendor team ID left in the
signature (tailscale#15802).

gh release download tailscalekit-v1.102.3+3 -p 'TailscaleKit.xcframework.zip' -D /tmp
unzip -q /tmp/TailscaleKit.xcframework.zip -d vendor/

0.1.0

Choose a tag to compare

@prakashrj prakashrj released this 30 Aug 17:53
3baa03b

Added

  • tailscale/verify-floor-runtime.sh — proves a declared iOS floor by loading
    it.
    ci/check-platform-floors.sh compares declared floors against the
    minos a slice reports, but never loads anything, and every machine that
    builds this repo runs an OS above every floor it declares — so the failure a
    floor exists to prevent was invisible to every green check.

    The script builds the app for the simulator and launches it twice: once
    carrying the pre-lowering framework from an earlier tailscalekit-* release
    (the negative control, which dyld must refuse) and once carrying the current
    one. If the control loads, the run reports INCONCLUSIVE and discards the
    subject's pass rather than reporting it.

    It refuses to run on a simulator at or above the old floor, because such a
    runtime clears the old and new floors alike and cannot tell a lowered floor
    from an unlowered one. Needs a runtime in [declared floor, previous floor);
    not wired into CI, as GitHub's macOS images carry no old runtimes.

    First run on iOS 17.5 confirms the lowered floor: the app maps TailscaleKit
    and runs, while the same app carrying the tailscalekit-v1.102.3 framework
    dies with built for iOS-sim 18.1 which is newer than running OS. The macOS
    14.0 floor remains structural only
    — there is no macOS simulator, so it
    needs a machine or VM on macOS 14–15.5.

Changed

  • Deployment floors are now iOS 17.0 / macOS 14.0, down from 18.1 / 15.6.
    A new xcframework was published as tailscalekit-v1.102.3+2 — same tsnet
    version, rebuilt with patch 0003 — and verified from the downloaded asset:
    minos 17.0 (ios-arm64, and the simulator slice) and 14.0 (macos-arm64).
    Package.swift's URL, checksum and platform floors moved together;
    app/project.yml and app/Project.swift follow.

    Reaches users with the next app build. The declarations changing does not
    alter the 0.1.0 binaries currently in App Review.

Fixed

  • App Review rejection of macOS 0.1.0 build 5 (2026-08-30), both findings. Guideline 2.1(a) — "the Sign in button was unresponsive". Not reproducible here: on a cold, signed build (Debug and Release, macOS 26.5.2) tailscale_start returns res=0, control: AuthURL is … does reach the app's log pipe, ASWebAuthenticationSession.start() returns true and the sheet presents. What the investigation did find is a latent bug that produces exactly the reported symptom: start() returns Bool and the result was discarded. On failure nothing opens, the completion handler never fires, and the continuation was never resumed — so presentLogin() never returned, its isPresentingLogin latch stayed set, and the guard !isPresentingLogin at the top swallowed every later tap. The sign-in button goes permanently dead with no error shown anywhere. present(url:dismissWhen:) now returns a three-case Outcome (completed / cancelled / failedToPresent(reason:)) instead of a Bool that could not represent "the sheet never opened", resumes the continuation on every path, and the caller always clears the latch and shows the reason. WindowPresenter now prefers a key window, then any visible window, before the detached NSWindow() last resort — a window that was never shown cannot host a sheet, which is one way start() fails. presentLoginWhenURLAppears no longer falls through silently after ~20s leaving the UI on "waiting for login…"; it says no sign-in link arrived and points at Demo mode. Guideline 1.5 — the Support URL was https://github.com/indiagrams/tunnelless/issues, which Apple does not accept as a support site; now https://indiagram.com/support.html (verified HTTP 200). Note fastlane/Fastfile's metadata_dir is platform-agnostic, so this URL is shared: the queued iOS build 5 carried the same rejected URL and would have failed 1.5 for the same reason.

  • make ship recorded almost nothing about the release it had just run. A real ship produced a 13-line log — every line printed by bin/ship.rb itself, none from the multi-minute fastlane release those lines described. Redirecting changed nothing: the output never left the Ruby process. Bootstrap::Sh.run wraps Open3.capture3, which buffers until exit and discards stderr, and ship.rb then dropped the captured string on success and printed stdout without stderr on failure — so a ship that FAILED was exactly as undiagnosable as one that succeeded, which is backwards. Diagnosing the v0.1.0+6 ship meant reconstructing events from fastlane's report.xml and the ASC API.

    Fixed upstream in apple-shipkit (#276) rather than here, because bin/ is template-owned and bin/ship.rb was byte-identical to the template — a fork edit would conflict on every sync for a bug with nothing project-specific about it. Cherry-picked in as 5bb986f so this repo gets it without the full 271-commit upstream merge, which would also touch fastlane/metadata/ and could clobber the live App Review notes while both 0.1.0 submissions are queued with Apple.

    Adds Bootstrap::Sh.stream (tees a child's merged stdout+stderr as it arrives via popen2e; passes chdir: as a spawn option instead of mutating the process-global cwd for a whole release) and test/sh_stream_test.rb — 10 stdlib-only assertions wired as the stream-regression CI job, two of them controls asserting the old path fails what the new one passes. Mutation-verified: restoring the capture3 body fails 5 of the 10.

Removed

  • Module-cache Patch 1 (darwin os.Executable fallback) is gone. It merged
    upstream as tailscale#19052 and shipped in v1.98.0; the pin has been
    v1.102.3 since the last bump, so the block did nothing but print "already
    applied" on every build — confirmed by reading case "ios", "darwin": in
    tsnet.go at the tag, not by trusting the release date. First item of v0.2's
    "carry less" to actually land.

    It was kept in case the dependency were ever pinned below v1.98. That scenario
    is now guarded explicitly: build-tailscalekit.sh hard-fails on such a pin
    with the reason and a git log -S pointer to recover the patch, instead of
    letting TailscaleNode.init() fail at runtime with "tsnet: cannot find
    executable path". Boundary verified — v1.98.0 accepted, v1.96.4 rejected.

Changed

  • Deployment floors lowered to iOS 17.0 / macOS 14.0 (from 18.1 / 15.6) for
    the next xcframework build. tailscale/patches/0003-gate-listener-api.patch
    gates TailscaleKit's two listener actors behind @available, which confines
    their iOS 18 / macOS 15 requirement to themselves instead of the whole
    framework — this app is a pure client and never calls them. Every number was
    measured by building at it: unpatched the real floor is 18.0/15.0, and
    upstream's shipped 18.1/15.6 is above even that. Sent upstream as
    libtailscale#60;
    when it lands, delete the patch — do not rebase it.

    The floors in app/ and Package.swift deliberately stay at 18.1/15.6 until a
    new xcframework is published: they must describe the binary consumers actually
    resolve, not the one we can now build. ci/check-platform-floors.sh enforces
    that ordering on its own.

Added

  • ci/check-app-icon.sh — the build now refuses a placeholder icon. Build 1
    shipped apple-shipkit's flat blue template icon unchanged and came back as a
    Guideline 2.3.8 rejection. Nothing objected on the way out: the asset was a
    valid 1024x1024 PNG, all six CI cells were green, and App Store Connect's
    upload validation accepted it. The only thing that noticed was a human
    reviewer, a full cycle later.

    Placeholders are detected structurally, with no knowledge of the app's brand:
    quantise to 8 colours, take the largest distance between clusters covering

    =3% of the image. Artwork puts distant clusters on the canvas; a flat fill or
    a bare gradient does not. Measured — flat blue 0, shipkit's template 0,
    the real icon 282; the threshold is 40. Also checks 1024x1024 and rejects
    an alpha channel, which Apple refuses outright.

    Deliberately no Pillow: icons inside a built .ipa are CgBI PNGs, which
    Pillow refuses with "broken data stream" — so a Pillow implementation would
    silently skip the artifact that matters most. Pixels come via sips and a
    stdlib BMP parse. ALLOW_PLACEHOLDER_ICON=true overrides.

Changed

  • README's Upstream section brought current, and it now separates merged
    from shippedtailscale#20985 was listed as "Open, approved" when it had
    merged on 2026-08-26, and merging is not what retires the carried patch.
    Added libtailscale#59 (universal macOS slice, and why it is not what macOS
    support needs) and tailscale#21005 (down() calls tailscale_up()), both
    of which existed only in TAILSCALE.md. Every state re-verified against GitHub
    rather than carried over.

Added

  • tailscale-upstream-watch.yml now decides patch retirement instead of
    delegating it.
    It reads tsnet/tsnet.go at the pinned and latest tags and
    reports whether each contains tailscale#20985, replacing a weekly issue that
    said "verify by grepping the tagged source" and left the grepping to a human.
    Four outcomes: actionable now, a bump would retire it, merged-but-unreleased
    (nothing to do), and a fetch failure — which reports UNKNOWN rather than
    "not contained", because treating a failed fetch as absence is how a patch
    step quietly outlives its purpose. Since today's real state is
    merged-but-unreleased, the tracking issue now closes itself rather than
    reopening every Monday.

  • App Review notes now answer Guideline 5.1.1(v) (account deletion). Apple
    raised it against macOS build 4 and no build can answer it: the app creates no
    account, so there is nothing to add to the binary. The notes ...

Read more

TailscaleKit.xcframework — tailscale.com v1.102.3

Choose a tag to compare

@github-actions github-actions released this 30 Aug 04:37
c65a4a3

Prebuilt TailscaleKit.xcframework, built from tailscale.com v1.102.3.

Upstream libtailscale ships no prebuilt binary, so this is the artifact that
makes adoption a drag-and-drop rather than a Go toolchain setup.

Unsigned by design — your app signs it on embed. Set it to Embed & Sign.

tailscale.com v1.102.3
slices ios-arm64, ios-arm64_x86_64-simulator, macos-arm64

Validated before publishing: privacy manifests present in both iOS slices
(ITMS-91053), no symlinks in iOS slices, and no vendor team ID left in the
signature (tailscale#15802).

gh release download tailscalekit-v1.102.3+2 -p 'TailscaleKit.xcframework.zip' -D /tmp
unzip -q /tmp/TailscaleKit.xcframework.zip -d vendor/

TailscaleKit.xcframework — tailscale.com v1.102.3

Choose a tag to compare

@prakashrj prakashrj released this 26 Aug 05:40

Prebuilt TailscaleKit.xcframework, built from tailscale.com v1.102.3.

Upstream libtailscale ships no prebuilt binary — every consumer builds it themselves with a Go toolchain and Xcode. This release removes that step.

Use it

  1. Download TailscaleKit.xcframework.zip and unzip
  2. Drag TailscaleKit.xcframework into your Xcode project
  3. Set it to Embed & Sign
  4. Copy TailscaleNodeManager.swift and WebAuthLogin.swift
let manager = TailscaleNodeManager(hostName: "my-app")
try await manager.startForBrowserLogin()
let lb = await manager.cachedLoopback   // SOCKS5 on loopback

No NEPacketTunnelProvider, no NetworkExtension entitlement, no VPN permission prompt.

Contents

Slice
ios-arm64 device
ios-arm64_x86_64-simulator Simulator
macos-arm64 Apple Silicon Mac

Passes all six checks in validate-xcframework.sh:

  • PrivacyInfo.xcprivacy in both iOS slices — Apple rejects with ITMS-91053 without it
  • no symlinks in iOS slices — App Store rejects those
  • no vendor team ID in the signature (tailscale#15802)
  • simulator slice present, root Info.plist present

Unsigned by design. Your app signs it at embed time — that's why "Embed & Sign" matters.

Caveats

  • Pinned to v1.102.3. For a different version, build from source: bash tailscale/build-tailscalekit.sh
  • macOS slice is arm64 only (no Intel), and requires macOS 15.0+
  • Reference implementation, not a supported library — no compatibility guarantees between releases

Licence

TailscaleKit and libtailscale are BSD-3-Clause, © Tailscale & AUTHORS. The licence is included in the archive as LICENSE-tailscale-BSD-3-Clause.txt.

SHA-256: 0b73d55c44174951693cf02abcdcb75f854931d95c61a1628bda550a7ab91bba