Build smaller Tailscale binary #1253
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build smaller Tailscale binary | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Specific Tailscale version tag to build (e.g. v1.68.0). Leave empty to use latest.' | |
| required: false | |
| type: string | |
| force_build: | |
| description: 'Force build even if versions match' | |
| required: false | |
| default: false | |
| type: boolean | |
| schedule: | |
| - cron: '0 3 * * *' | |
| env: | |
| SOFTWARE_NAME: "Tailscale" | |
| FILE_NAME: "tailscaled" | |
| REPO: "tailscale/tailscale" | |
| REPO_SMALL: "Admonstrator/glinet-tailscale-updater" | |
| GIT_AUTHOR_NAME: "Admonstrator" | |
| jobs: | |
| check-versions: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| TAG: ${{ steps.tag.outputs.TAG }} | |
| TAG_SMALL: ${{ steps.tag_small.outputs.TAG_SMALL }} | |
| SHOULD_BUILD: ${{ steps.compare.outputs.SHOULD_BUILD }} | |
| steps: | |
| - name: Get latest ${{ env.SOFTWARE_NAME }} tag | |
| id: tag | |
| run: | | |
| if [ -n "${{ inputs.version }}" ]; then | |
| latest_tag="${{ inputs.version }}" | |
| echo "Using manually specified version: $latest_tag" | |
| else | |
| latest_tag=$(curl -s "https://api.github.com/repos/${{ env.REPO }}/releases/latest" | jq -r '.tag_name // empty') | |
| echo "Latest ${{ env.SOFTWARE_NAME }} Tag: $latest_tag" | |
| fi | |
| echo "TAG=$latest_tag" >> "$GITHUB_OUTPUT" | |
| - name: Get latest ${{ env.SOFTWARE_NAME }} Small tag | |
| id: tag_small | |
| run: | | |
| latest_tag_small=$( | |
| curl -s "https://api.github.com/repos/${{ env.REPO_SMALL }}/releases/latest" \ | |
| | grep -oP '"tag_name": "\K(.*)(?=")' || echo "" | |
| ) | |
| echo "TAG_SMALL=$latest_tag_small" >> "$GITHUB_OUTPUT" | |
| echo "Latest ${{ env.SOFTWARE_NAME }} Small Tag: $latest_tag_small" | |
| - name: Compare upstream and local tags | |
| id: compare | |
| run: | | |
| upstream_tag="${{ steps.tag.outputs.TAG }}" | |
| local_tag="${{ steps.tag_small.outputs.TAG_SMALL }}" | |
| should_build="false" | |
| if [ -z "$upstream_tag" ]; then | |
| echo "No upstream tag detected. Skipping build." | |
| elif [ -z "$local_tag" ]; then | |
| echo "No local release tag found. Build is required." | |
| should_build="true" | |
| elif [ "$upstream_tag" = "$local_tag" ]; then | |
| echo "Tags are equal ($upstream_tag). Skipping build." | |
| else | |
| newest_tag=$(printf '%s\n%s\n' "$local_tag" "$upstream_tag" | sort -V | tail -n 1) | |
| if [ "$newest_tag" = "$upstream_tag" ]; then | |
| echo "Upstream tag ($upstream_tag) is newer than local tag ($local_tag)." | |
| should_build="true" | |
| else | |
| echo "Local tag ($local_tag) is newer than upstream tag ($upstream_tag). Skipping build." | |
| fi | |
| fi | |
| echo "SHOULD_BUILD=$should_build" >> "$GITHUB_OUTPUT" | |
| build: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| needs: check-versions | |
| if: needs.check-versions.outputs.SHOULD_BUILD == 'true' || (github.event_name == 'workflow_dispatch' && (inputs.force_build || inputs.version != '')) | |
| env: | |
| TAG: ${{ needs.check-versions.outputs.TAG }} | |
| strategy: | |
| matrix: | |
| go-version: [stable] | |
| os: [linux] | |
| platform: | |
| - amd64 | |
| - arm | |
| - arm64 | |
| - mips | |
| - mipsle | |
| - mips64 | |
| - mips64le | |
| steps: | |
| - name: Checkout ${{ env.SOFTWARE_NAME }} repository | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ env.REPO }} | |
| ref: ${{ env.TAG }} | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: ${{ matrix.go-version }} | |
| - name: Download Go modules | |
| run: go mod download | |
| - name: Cross-compile | |
| run: | | |
| # Get version information | |
| eval `CGO_ENABLED=0 go run ./cmd/mkversion` | |
| # Generate custom build number based on GitHub run number, or use timestamp if unavailable | |
| if [ -n "${GITHUB_RUN_NUMBER}" ]; then | |
| CUSTOM_BUILD_NUMBER="${GITHUB_RUN_NUMBER}" | |
| else | |
| CUSTOM_BUILD_NUMBER="$(date +%s)" | |
| fi | |
| CUSTOM_VERSION_SUFFIX="-tiny.by.admon.${CUSTOM_BUILD_NUMBER}" | |
| # Append custom version suffix to Tailscale version | |
| VERSION_SHORT_CUSTOM="${VERSION_SHORT}${CUSTOM_VERSION_SUFFIX}" | |
| VERSION_LONG_CUSTOM="${VERSION_LONG}${CUSTOM_VERSION_SUFFIX}" | |
| echo "Original Tailscale version: ${VERSION_SHORT}" | |
| echo "Custom build version: ${VERSION_SHORT_CUSTOM}" | |
| # Prepare feature set for smaller binary | |
| # 1. CORE & NETWORKING | |
| KEEP="dns,osrouter,iptables,netstack,health,logtail,portmapper,usermetrics,useroutes,cli" | |
| # 2. ROUTING & NODES | |
| KEEP="${KEEP},advertiseexitnode,useexitnode,advertiseroutes" | |
| KEEP="${KEEP},clientmetrics,cliconndiag,doctor" | |
| # 4. SECURITY & AUTH | |
| KEEP="${KEEP},acme,bakedroots,tailnetlock,unixsocketidentity" | |
| # 5. EXTRA | |
| KEEP="${KEEP},appconnectors,cachenetmap,colorable,conn25,debug,gro" | |
| KEEP="${KEEP},hujsonconf,lazywg,listenrawdisco,portlist,serve,ssh,useproxy,relayserver" | |
| # Finalize build tags | |
| BUILD_TAGS=$(go run ./cmd/featuretags --min --add="${KEEP}") | |
| # Set ldflags with custom version info and stripping | |
| LDFLAGS="-w -s -X tailscale.com/version.longStamp=${VERSION_LONG_CUSTOM} -X tailscale.com/version.shortStamp=${VERSION_SHORT_CUSTOM}" | |
| # Build the binary | |
| if [[ "${{ matrix.platform }}" == "mipsle" ]]; then | |
| CGO_ENABLED=0 GOOS=${{ matrix.os }} GOARCH=${{ matrix.platform }} GOMIPS=softfloat \ | |
| go build -tags="${BUILD_TAGS}" -trimpath -ldflags "${LDFLAGS}" \ | |
| -o "${{ env.FILE_NAME }}-${{ matrix.os }}-${{ matrix.platform }}" tailscale.com/cmd/${{ env.FILE_NAME }} | |
| else | |
| CGO_ENABLED=0 GOOS=${{ matrix.os }} GOARCH=${{ matrix.platform }} \ | |
| go build -tags="${BUILD_TAGS}" -trimpath -ldflags "${LDFLAGS}" \ | |
| -o "${{ env.FILE_NAME }}-${{ matrix.os }}-${{ matrix.platform }}" tailscale.com/cmd/${{ env.FILE_NAME }} | |
| fi | |
| echo "${VERSION_SHORT_CUSTOM}" > "version.txt" | |
| - name: Upload built binary | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ env.FILE_NAME }}-${{ matrix.os }}-${{ matrix.platform }} | |
| path: ./${{ env.FILE_NAME }}-${{ matrix.os }}-${{ matrix.platform }} | |
| - name: Upload version file | |
| if: matrix.platform == 'amd64' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: version-file | |
| path: ./version.txt | |
| publish: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| needs: | |
| - build | |
| - check-versions | |
| if: needs.check-versions.outputs.SHOULD_BUILD == 'true' || (github.event_name == 'workflow_dispatch' && (inputs.force_build || inputs.version != '')) | |
| env: | |
| TAG: ${{ needs.check-versions.outputs.TAG }} | |
| steps: | |
| - name: Get UPX latest version | |
| id: get-upx-version | |
| run: | | |
| upx_version=$( | |
| curl -s https://api.github.com/repos/upx/upx/releases/latest \ | |
| | jq -r '.tag_name' \ | |
| | cut -c 2- | |
| ) | |
| echo "UPX_VERSION=${upx_version}" >> "$GITHUB_ENV" | |
| echo "version=${upx_version}" >> "$GITHUB_OUTPUT" | |
| - name: Download UPX | |
| env: | |
| UPX_VERSION: ${{ steps.get-upx-version.outputs.version }} | |
| run: | | |
| wget -q "https://github.com/upx/upx/releases/latest/download/upx-${UPX_VERSION}-amd64_linux.tar.xz" | |
| tar --to-stdout -xf "upx-${UPX_VERSION}-amd64_linux.tar.xz" \ | |
| "upx-${UPX_VERSION}-amd64_linux/upx" > "${PWD}/upx" | |
| chmod -v +x "${PWD}/upx" | |
| - name: Download built binaries | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: ${{ env.FILE_NAME }}-* | |
| - name: Download version file | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: version-file | |
| path: . | |
| - name: Moving files | |
| run: | | |
| for dir in "${{ env.FILE_NAME }}-"*; do | |
| mv -v "${dir}" "${dir}.d" | |
| mv -v "${dir}.d/${{ env.FILE_NAME }}-"* . | |
| rmdir -v "${dir}.d" | |
| done | |
| chmod -v +x "${{ env.FILE_NAME }}-"* | |
| - name: Compress Binary with UPX | |
| run: | | |
| for file in "${{ env.FILE_NAME }}-"*; do | |
| if [[ "$file" == *"mips64"* || "$file" == *"mips64le"* ]]; then | |
| echo "Skipping UPX compression for $file due to unsupported format" | |
| else | |
| "${PWD}/upx" --lzma --best --no-progress "$file" | |
| fi | |
| done | |
| - name: Create checksums | |
| run: | | |
| sha256sum "${{ env.FILE_NAME }}-"* > "checksums.txt" | |
| - name: Checkout ${{ env.SOFTWARE_NAME }} Small repository | |
| uses: actions/checkout@v4 | |
| with: | |
| path: tools | |
| repository: ${{ env.REPO_SMALL }} | |
| fetch-tags: true | |
| - name: Create tag in ${{ env.SOFTWARE_NAME }} Small repository | |
| if: github.event_name == 'schedule' | |
| run: | | |
| cd tools | |
| if git rev-parse --quiet --verify "refs/tags/${{ env.TAG }}"; then | |
| echo "Tag already exists" | |
| exit 0 | |
| else | |
| echo "Tag does not exist, creating" | |
| git tag "${{ env.TAG }}" | |
| git push --tags | |
| fi | |
| - name: Create Release | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| name: Small ${{ env.SOFTWARE_NAME }} ${{ env.TAG }} | |
| tag: ${{ env.TAG }} | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| draft: false | |
| prerelease: false | |
| removeArtifacts: true | |
| replacesArtifacts: true | |
| allowUpdates: true | |
| artifacts: | | |
| ${{ env.FILE_NAME }}-* | |
| checksums.txt | |
| version.txt | |
| body: | | |
| Small ${{ env.SOFTWARE_NAME }} build ${{ env.TAG }} | |
| For a complete changelog go to https://github.com/${{ env.REPO }}/releases/tag/${{ env.TAG }} | |
| This release was created by: | |
| * Building a combined binary of `tailscale` and `tailscaled` with the `ts_include_cli` build tag | |
| * Optimized for router/embedded use - removes desktop/enterprise features while keeping essential router functionality | |
| * Keeps critical features: DNS, routes, exit nodes, health checks, peer API, NAT traversal, and more | |
| * Including `unixsocketidentity` feature for proper LocalAPI access control on Unix/OpenWrt systems | |
| * Including `ssh` feature for Tailscale SSH support | |
| * Compressing the binary with UPX | |
| To use both programs, rename `tailscaled-OS-ARCH` to `tailscaled` and create a symbolic link (`ln -sv tailscaled tailscale`). When invoked as `tailscale`, it acts as the CLI; when invoked as `tailscaled`, it acts as the daemon. |