From 0f42ada793c97715595fa74adcbc36dbc9561afa Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Sat, 14 Mar 2026 06:13:35 -0400 Subject: [PATCH] Prepare for pub.dev publishing with Gitea release pipeline - Add README.md and CHANGELOG.md (required by pub.dev) - Add .pubignore / ios/.pubignore to include xcframework in published package - Add Gitea Actions workflow: builds xcframework and publishes on release - Release tag must match pubspec version with no v prefix (e.g. "0.1.0") - Requires PUB_TOKEN secret in Gitea repo settings Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitea/workflows/publish.yml | 60 +++++++++++++++++++++++++++++++++++ .pubignore | 19 +++++++++++ CHANGELOG.md | 9 ++++++ README.md | 61 ++++++++++++++++++++++++++++++++++++ ios/.pubignore | 3 ++ 5 files changed, 152 insertions(+) create mode 100644 .gitea/workflows/publish.yml create mode 100644 .pubignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 ios/.pubignore diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml new file mode 100644 index 0000000..26b8b02 --- /dev/null +++ b/.gitea/workflows/publish.yml @@ -0,0 +1,60 @@ +name: Publish to pub.dev + +on: + release: + types: [published] + +jobs: + publish: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate release tag (no v prefix) + run: | + TAG="${{ gitea.event.release.tag_name }}" + if [[ "$TAG" == v* ]]; then + echo "Error: tag '$TAG' has a v prefix. Use '0.1.0' not 'v0.1.0'" + exit 1 + fi + echo "Publishing version: $TAG" + + - name: Verify version matches pubspec + run: | + TAG="${{ gitea.event.release.tag_name }}" + PUBSPEC_VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}') + if [ "$TAG" != "$PUBSPEC_VERSION" ]; then + echo "Error: tag '$TAG' doesn't match pubspec version '$PUBSPEC_VERSION'" + exit 1 + fi + + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Install Xcode tools + run: xcode-select --install 2>/dev/null || true + + - name: Build xcframework from Go source + run: | + chmod +x build_go.sh + ./build_go.sh + + - name: Verify xcframework exists + run: | + ls -lh ios/TailscaleKit.xcframework/ios-arm64/TailscaleKit.framework/TailscaleKit + ls -lh ios/TailscaleKit.xcframework/ios-arm64-simulator/TailscaleKit.framework/TailscaleKit + + - name: Dry run publish + run: dart pub publish --dry-run + + - name: Publish to pub.dev + run: dart pub publish --force + env: + PUB_TOKEN: ${{ secrets.PUB_TOKEN }} diff --git a/.pubignore b/.pubignore new file mode 100644 index 0000000..36f6401 --- /dev/null +++ b/.pubignore @@ -0,0 +1,19 @@ +# Git-specific +.git/ +.gitignore + +# Build artifacts (but NOT TailscaleKit.xcframework — that ships with the package) +.dart_tool/ +.packages +.pub/ +build/ + +# Go source and build tools (users don't need these) +ios/Go/ +ios/_current_slice +ios/.gitignore +build_go.sh +test/ + +# Override ios/.gitignore — the xcframework MUST ship with the package +!ios/TailscaleKit.xcframework/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0bdd89e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +## 0.1.0 + +- Initial release +- iOS support (arm64 device + simulator) +- `start()` / `stop()` — join/leave a Tailnet with an auth key +- `startProxy()` / `stopProxy()` — localhost TCP proxy through WireGuard tunnel +- `status()` / `tailscaleIP()` — query Tailscale connection state +- Built with `go build -buildmode=c-archive` (production Go approach) +- No VPN entitlement required — uses userspace netstack diff --git a/README.md b/README.md new file mode 100644 index 0000000..6865170 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# tsnet_flutter + +Embed [Tailscale's tsnet](https://pkg.go.dev/tailscale.com/tsnet) in Flutter apps. Provides a userspace WireGuard tunnel with a localhost TCP proxy — **no VPN entitlement needed** on iOS. + +## How it works + +``` +Flutter (Dart) → MethodChannel → Swift Plugin → Go static library (tsnet) + ↓ + WireGuard tunnel (userspace) + ↓ + Remote device (100.x.x.x) +``` + +The Go layer runs a local TCP proxy: your app connects to `localhost:PORT`, and traffic is forwarded through a WireGuard tunnel to the target device's Tailscale IP. Flutter doesn't know about Tailscale — it just sees a localhost port. + +## Usage + +```dart +import 'package:tsnet_flutter/tsnet_flutter.dart'; + +final tsnet = TsnetFlutter(); + +// Join the Tailnet +await tsnet.start(authKey: 'tskey-auth-...'); + +// Create a local proxy to the remote device +final localPort = await tsnet.startProxy('100.64.0.5', port: 5050); + +// Connect your client to the proxy +yourClient.connect('127.0.0.1', port: localPort); + +// Clean up +await tsnet.stopProxy(); +await tsnet.stop(); +``` + +## Platform support + +| Platform | Status | +|----------|--------| +| iOS | Supported (arm64 device + simulator) | +| Android | Planned | + +## Requirements + +- iOS 14.0+ +- Tailscale auth key (generate at [login.tailscale.com](https://login.tailscale.com/admin/settings/keys)) + +## Building from source + +The pre-built xcframework is included in the package. To rebuild from Go source: + +```bash +# Prerequisites: Go 1.23+, Xcode +./build_go.sh +``` + +## License + +BSD-3-Clause. See [LICENSE](LICENSE). diff --git a/ios/.pubignore b/ios/.pubignore new file mode 100644 index 0000000..6963917 --- /dev/null +++ b/ios/.pubignore @@ -0,0 +1,3 @@ +# Only ignore the build symlink — the xcframework ships with the package +_current_slice +Go/