From c7f610e6cd33809be041a0eb3d7ac220f71f5285 Mon Sep 17 00:00:00 2001 From: Riccardo Manfrin <3090891+riccardomanfrin@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:21:20 +0200 Subject: [PATCH] [misc, android] Build and lint the mobile Go code in CI (#7641) Nothing in CI compiles the files behind //go:build android or //go:build ios. The android bridge builds 7 of its 23 files on linux and skips client.go; the iOS SDK is not built at all. The linter matrix picks a GOOS by picking a runner OS, so it loads the same file set as the host build and never sees them either. A type error in client/android/client.go therefore passes every check on its PR, merges, and is discovered by netbirdio/android-client after sync-tag.yml fires trigger_android_bump on the release tag. The new Mobile workflow cross-compiles ./client/android/... for the GOARCH values gomobile ships and ./client/ios/..., and vets the android bridge. The new Android and iOS lint jobs run golangci-lint with GOOS/GOARCH in the job env. No NDK, Xcode or gomobile is needed: these are library packages, so the compiler type-checks them without a link step, and the dependency graph drags in the android/ios-tagged files across client/iface, client/internal/dns and client/internal/routemanager with them. Linting those files for the first time surfaces one gosec G101 on the SSH password-required marker. It is a sentinel string the Java side matches on, not a credential, so it is suppressed at the declaration. --- .github/workflows/golangci-lint.yml | 46 +++++++++++++ .github/workflows/mobile-build-validation.yml | 64 +++++++++++++++++++ client/android/ssh_client.go | 2 + 3 files changed, 112 insertions(+) create mode 100644 .github/workflows/mobile-build-validation.yml diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 586e1235b..ff36a0854 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -80,3 +80,49 @@ jobs: skip-save-cache: true cache-invalidation-interval: 0 args: --timeout=20m + + # Separate job rather than extra rows in the matrix above: those rows pick a + # GOOS by picking a runner OS, while android/ios are cross-compiled from + # ubuntu — an `include` entry with os: ubuntu-latest would merge into the + # Linux row instead of adding one. The package path is restricted because a + # whole-repo run under GOOS=android pulls *_linux.go files into packages that + # have no android counterpart. + golangci-mobile: + strategy: + fail-fast: false + matrix: + include: + - goos: android + goarch: arm64 + packages: ./client/android/... + display_name: Android + - goos: ios + goarch: arm64 + packages: ./client/ios/... + display_name: iOS + name: ${{ matrix.display_name }} + runs-on: ubuntu-latest + timeout-minutes: 25 + env: + CGO_ENABLED: 0 + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + steps: + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Install Go + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + with: + go-version-file: "go.mod" + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee #v9.2.1 + with: + version: latest + install-mode: binary + skip-cache: true + skip-save-cache: true + cache-invalidation-interval: 0 + args: --timeout=20m ${{ matrix.packages }} diff --git a/.github/workflows/mobile-build-validation.yml b/.github/workflows/mobile-build-validation.yml new file mode 100644 index 000000000..613a39b3d --- /dev/null +++ b/.github/workflows/mobile-build-validation.yml @@ -0,0 +1,64 @@ +name: Mobile + +on: + push: + branches: + - main + - "release-*" + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + android_build: + name: "Android / Build" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + goarch: [arm64, arm, amd64, "386"] + env: + CGO_ENABLED: 0 + GOOS: android + GOARCH: ${{ matrix.goarch }} + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Install Go + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + with: + go-version-file: "go.mod" + - name: Build Android bridge + run: go build ./client/android/... + - name: Vet Android bridge + if: matrix.goarch == 'arm64' + run: go vet ./client/android/... + + ios_build: + name: "iOS / Build" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + goarch: [arm64, amd64] + env: + CGO_ENABLED: 0 + GOOS: ios + GOARCH: ${{ matrix.goarch }} + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Install Go + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + with: + go-version-file: "go.mod" + # No `go vet` counterpart: every ios target requires external (cgo) + # linking, which needs an Xcode toolchain the runner does not have. + - name: Build iOS SDK + run: go build ./client/ios/... diff --git a/client/android/ssh_client.go b/client/android/ssh_client.go index 2822b6539..9a11044ba 100644 --- a/client/android/ssh_client.go +++ b/client/android/ssh_client.go @@ -31,6 +31,8 @@ const ( // PasswordRequiredMarker tells Java to prompt for a password and retry. It is // a string because gomobile flattens errors to their message, so a sentinel // value would not survive the binding. +// +//nolint:gosec // G101 false positive: a sentinel marker, not a credential const PasswordRequiredMarker = "netbird-ssh-password-required" // HostKeyUnknownMarker tells Java to show the fingerprint and, on confirmation,