From 321e0b6c885df88eca3e9c5ff6b24782a1c9d843 Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 11:29:50 -0700 Subject: [PATCH 01/15] Add Linux arm64 packaging and CI support --- .github/workflows/build-linux.yml | 30 +++++++--- .github/workflows/release.yml | 26 +++++++++ Makefile | 53 +++++++++++------- linux/packaging/nfpm.yaml | 3 +- scripts/ci/format.sh | 30 ++++++++-- scripts/ci/publish-to-s3.sh | 89 +++++++++++++++++++++--------- scripts/ci/verify_linux_package.sh | 13 ++++- 7 files changed, 181 insertions(+), 63 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index db457ec42d..d12481d4c3 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -12,13 +12,22 @@ on: installer_base_name: required: true type: string + linux_arch: + required: false + type: string + default: amd64 jobs: build-linux: + strategy: + fail-fast: false + matrix: ${{ fromJSON(inputs.linux_arch == 'all' && '{"include":[{"arch":"amd64","runner":"ubuntu-latest"},{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' || inputs.linux_arch == 'arm64' && '{"include":[{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' || '{"include":[{"arch":"amd64","runner":"ubuntu-latest"}]}') }} env: BUILD_TYPE: ${{ inputs.build_type }} VERSION: ${{ inputs.version }} - runs-on: ubuntu-latest + TARGET_ARCH: ${{ matrix.arch }} + FULL_INSTALLER_NAME: ${{ inputs.installer_base_name }}${{ inputs.build_type != 'production' && format('-{0}', inputs.build_type) || '' }}${{ matrix.arch == 'arm64' && '-arm64' || '' }} + runs-on: ${{ matrix.runner }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -52,9 +61,9 @@ jobs: continue-on-error: true with: path: ~/.cache/apt/archives - key: ${{ runner.os }}-apt-archives-v1-${{ hashFiles('.github/workflows/build-linux.yml') }} + key: ${{ runner.os }}-${{ matrix.arch }}-apt-archives-v1-${{ hashFiles('.github/workflows/build-linux.yml') }} restore-keys: | - ${{ runner.os }}-apt-archives-v1- + ${{ runner.os }}-${{ matrix.arch }}-apt-archives-v1- - name: Install Linux dependencies run: | @@ -122,16 +131,19 @@ jobs: BUILD_TYPE: ${{ inputs.build_type }} VERSION: ${{ inputs.version }} INSTALLER_NAME: ${{ inputs.installer_base_name }} + LINUX_TARGET_ARCH: ${{ matrix.arch }} + LINUX_CC_AMD64: gcc + LINUX_CC_ARM64: gcc - name: Verify Linux package contents run: | - ./scripts/ci/verify_linux_package.sh "${{ inputs.installer_base_name }}${{ inputs.build_type != 'production' && format('-{0}', inputs.build_type) || '' }}.deb" + ./scripts/ci/verify_linux_package.sh "./${{ env.FULL_INSTALLER_NAME }}.deb" "${{ matrix.arch }}" - name: Install .deb and verify postinst started daemon shell: bash run: | set -euxo pipefail - deb="./${{ inputs.installer_base_name }}${{ inputs.build_type != 'production' && format('-{0}', inputs.build_type) || '' }}.deb" + deb="./${{ env.FULL_INSTALLER_NAME }}.deb" test -f "$deb" sudo apt-get install -y "$deb" @@ -199,13 +211,13 @@ jobs: - name: Upload Linux build uses: actions/upload-artifact@v4 with: - name: lantern-installer-rpm - path: ${{ inputs.installer_base_name }}${{ inputs.build_type != 'production' && format('-{0}', inputs.build_type) || '' }}.rpm + name: lantern-installer-rpm-${{ matrix.arch }} + path: ${{ env.FULL_INSTALLER_NAME }}.rpm retention-days: 2 - name: Upload Linux build uses: actions/upload-artifact@v4 with: - name: lantern-installer-deb - path: ${{ inputs.installer_base_name }}${{ inputs.build_type != 'production' && format('-{0}', inputs.build_type) || '' }}.deb + name: lantern-installer-deb-${{ matrix.arch }} + path: ${{ env.FULL_INSTALLER_NAME }}.deb retention-days: 2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3a3fcb9df..48974a4a15 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,6 +22,15 @@ on: required: false type: string default: "linux" + linux_arch: + description: "Linux arch to build when Linux is included" + required: false + type: choice + options: + - all + - amd64 + - arm64 + default: all cleanup_on_failure: description: "Delete draft release on failure" required: false @@ -49,6 +58,7 @@ jobs: version: ${{ steps.meta.outputs.version }} installer_base_name: ${{ steps.meta.outputs.installer_base_name }} platform: ${{ steps.meta.outputs.platform }} + linux_arch: ${{ steps.meta.outputs.linux_arch }} is_test_run: ${{ steps.meta.outputs.is_test_run }} steps: - name: Checkout repo @@ -72,12 +82,14 @@ jobs: VERSION=$(./scripts/ci/version.sh generate nightly) RELEASE_TAG="v${VERSION}" PLATFORM="all" + LINUX_ARCH="all" ;; workflow_dispatch) # Manual trigger BUILD_TYPE="${{ github.event.inputs.build_type }}" PLATFORM="${{ github.event.inputs.platforms }}" + LINUX_ARCH="${{ github.event.inputs.linux_arch }}" case "$BUILD_TYPE" in nightly|beta) @@ -135,6 +147,11 @@ jobs: all) ;; *) VERSION="${VERSION%-$PLATFORM}" ;; esac + + case "$PLATFORM" in + all|linux) LINUX_ARCH="all" ;; + *) LINUX_ARCH="amd64" ;; + esac ;; esac @@ -147,6 +164,7 @@ jobs: echo " Release Tag: $RELEASE_TAG" echo " Version: $VERSION" echo " Platform: $PLATFORM" + echo " Linux Arch: $LINUX_ARCH" echo " Installer: $INSTALLER_BASE_NAME" # Test run: workflow_dispatch from non-default branch @@ -163,6 +181,7 @@ jobs: echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT echo "platform=$PLATFORM" >> $GITHUB_OUTPUT + echo "linux_arch=$LINUX_ARCH" >> $GITHUB_OUTPUT echo "installer_base_name=$INSTALLER_BASE_NAME" >> $GITHUB_OUTPUT echo "is_test_run=$IS_TEST_RUN" >> $GITHUB_OUTPUT @@ -235,6 +254,7 @@ jobs: version: ${{ needs.set-metadata.outputs.version }} build_type: ${{ needs.set-metadata.outputs.build_type }} installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} + linux_arch: ${{ needs.set-metadata.outputs.linux_arch }} enable_ip_check: ${{ needs.set-metadata.outputs.build_type == 'nightly' }} build-windows: @@ -365,6 +385,7 @@ jobs: RELEASE_TAG: ${{ needs.set-metadata.outputs.release_tag }} INSTALLER_BASE_NAME: ${{ needs.set-metadata.outputs.installer_base_name }} PLATFORM: ${{ needs.set-metadata.outputs.platform }} + LINUX_ARCH: ${{ needs.set-metadata.outputs.linux_arch }} BUCKET: ${{ vars.S3_RELEASES_BUCKET }} GITHUB_REF_NAME: ${{ github.ref_name }} steps: @@ -445,6 +466,7 @@ jobs: RELEASE_TAG: ${{ needs.set-metadata.outputs.release_tag }} INSTALLER_BASE_NAME: ${{ needs.set-metadata.outputs.installer_base_name }} PLATFORM: ${{ needs.set-metadata.outputs.platform }} + LINUX_ARCH: ${{ needs.set-metadata.outputs.linux_arch }} BUCKET: ${{ vars.S3_RELEASES_BUCKET }} GITHUB_REF_NAME: ${{ github.ref_name }} steps: @@ -487,6 +509,10 @@ jobs: upload_if_exists "lantern-installer-dmg/${FULL_NAME}.dmg" upload_if_exists "lantern-installer-exe/${FULL_NAME}.exe" upload_if_exists "lantern-installer-apk/${FULL_NAME}.apk" + upload_if_exists "lantern-installer-deb-amd64/${FULL_NAME}.deb" + upload_if_exists "lantern-installer-rpm-amd64/${FULL_NAME}.rpm" + upload_if_exists "lantern-installer-deb-arm64/${FULL_NAME}-arm64.deb" + upload_if_exists "lantern-installer-rpm-arm64/${FULL_NAME}-arm64.rpm" upload_if_exists "lantern-installer-deb/${FULL_NAME}.deb" upload_if_exists "lantern-installer-rpm/${FULL_NAME}.rpm" upload_if_exists "lantern-installer-ipa/${FULL_NAME}.ipa" diff --git a/Makefile b/Makefile index 138886ed22..b0745ede40 100644 --- a/Makefile +++ b/Makefile @@ -39,13 +39,20 @@ LINUX_LIB := $(LANTERN_LIB_NAME).so LINUX_LIB_AMD64 := $(BIN_DIR)/linux-amd64/$(LANTERN_LIB_NAME).so LINUX_LIB_ARM64 := $(BIN_DIR)/linux-arm64/$(LANTERN_LIB_NAME).so LINUX_LIB_BUILD := $(BIN_DIR)/linux/$(LINUX_LIB) -LINUX_INSTALLER_DEB := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE)).deb -LINUX_INSTALLER_RPM := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE)).rpm -LINUX_INSTALLER_ARCH := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE)).pkg.tar.zst +LINUX_TARGET_ARCH ?= amd64 +LINUX_PACKAGE_ARCH_SUFFIX := $(if $(filter amd64,$(LINUX_TARGET_ARCH)),,-$(LINUX_TARGET_ARCH)) +LINUX_INSTALLER_DEB := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE))$(LINUX_PACKAGE_ARCH_SUFFIX).deb +LINUX_INSTALLER_RPM := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE))$(LINUX_PACKAGE_ARCH_SUFFIX).rpm +LINUX_INSTALLER_ARCH := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE))$(LINUX_PACKAGE_ARCH_SUFFIX).pkg.tar.zst LINUX_SERVICE_NAME := lanternd LINUX_SERVICE_SRC := $(RADIANCE_REPO)/cmd/lanternd LINUX_SERVICE_BUILD_AMD64 := $(BIN_DIR)/linux-amd64/$(LINUX_SERVICE_NAME) LINUX_SERVICE_BUILD_ARM64 := $(BIN_DIR)/linux-arm64/$(LINUX_SERVICE_NAME) +LINUX_SERVICE_BUILD_TARGET := $(BIN_DIR)/linux-$(LINUX_TARGET_ARCH)/$(LINUX_SERVICE_NAME) +LINUX_BUNDLE_DIR_X64 := build/linux/x64/release/bundle +LINUX_BUNDLE_DIR_ARM64 := build/linux/arm64/release/bundle +LINUX_CC_AMD64 ?= x86_64-linux-gnu-gcc +LINUX_CC_ARM64 ?= aarch64-linux-gnu-gcc LINUX_PKG_ROOT := linux/packaging LINUX_SERVICE_DST := $(LINUX_PKG_ROOT)/usr/sbin LINUX_PKG_SYSTEMD_DIR := $(LINUX_PKG_ROOT)/usr/lib/systemd/system @@ -260,20 +267,20 @@ install-linux-deps: linux-arm64: $(LINUX_LIB_ARM64) $(LINUX_LIB_ARM64): $(GO_SOURCES) - CC=aarch64-linux-gnu-gcc GOARCH=arm64 LIB_NAME=$@ make desktop-lib + CC=$(LINUX_CC_ARM64) GOOS=linux GOARCH=arm64 LIB_NAME=$@ $(MAKE) desktop-lib .PHONY: linux-amd64 linux-amd64: $(LINUX_LIB_AMD64) $(LINUX_LIB_AMD64): $(GO_SOURCES) - CC=x86_64-linux-gnu-gcc GOARCH=amd64 LIB_NAME=$@ make desktop-lib + CC=$(LINUX_CC_AMD64) GOOS=linux GOARCH=amd64 LIB_NAME=$@ $(MAKE) desktop-lib .PHONY: linux -linux: linux-amd64 +linux: linux-$(LINUX_TARGET_ARCH) mkdir -p $(BIN_DIR)/linux - cp $(LINUX_LIB_AMD64) $(LINUX_LIB_BUILD) + cp $(BIN_DIR)/linux-$(LINUX_TARGET_ARCH)/$(LINUX_LIB) $(LINUX_LIB_BUILD) -.PHONY: linux-service-amd64 linux-service-arm64 stage-linux-service +.PHONY: linux-service-amd64 linux-service-arm64 linux-service stage-linux-service linux-service-amd64: $(GO_SOURCES) $(call MKDIR_P,$(dir $(LINUX_SERVICE_BUILD_AMD64))) @@ -291,10 +298,12 @@ linux-service-arm64: $(GO_SOURCES) -o $(LINUX_SERVICE_BUILD_ARM64) $(LINUX_SERVICE_SRC) @echo "Built Linux service: $(LINUX_SERVICE_BUILD_ARM64)" -stage-linux-service: linux-service-amd64 +linux-service: linux-service-$(LINUX_TARGET_ARCH) + +stage-linux-service: linux-service @echo "Staging systemd unit + service binary $(LINUX_PKG_ROOT)..." $(call MKDIR_P,$(LINUX_SERVICE_DST)) - $(call COPY_FILE,$(LINUX_SERVICE_BUILD_AMD64),$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME)) + $(call COPY_FILE,$(LINUX_SERVICE_BUILD_TARGET),$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME)) $(call MKDIR_P,$(LINUX_PKG_SYSTEMD_DIR)) $(call COPY_FILE,$(LINUX_SYSTEMD_UNIT_SRC),$(LINUX_SYSTEMD_UNIT_DST)) @@ -309,21 +318,25 @@ linux-release: clean linux-release-ci linux-release-ci: linux pubget gen @echo "Building Flutter app (release) for Linux..." flutter build linux --release $(DART_DEFINES) - - cp $(LINUX_LIB_BUILD) build/linux/x64/release/bundle $(MAKE) stage-linux-service - patchelf --set-rpath '$$ORIGIN/lib' build/linux/x64/release/bundle/lantern || true - @echo "Packaging deb, rpm, and archlinux with nfpm..." - VERSION=$(APP_VERSION) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \ + @if [ -d "$(LINUX_BUNDLE_DIR_ARM64)" ] && [ "$(LINUX_TARGET_ARCH)" = "arm64" ]; then \ + BUNDLE_DIR="$(LINUX_BUNDLE_DIR_ARM64)"; \ + else \ + BUNDLE_DIR="$(LINUX_BUNDLE_DIR_X64)"; \ + fi; \ + echo "Using Linux bundle dir: $$BUNDLE_DIR"; \ + cp "$(LINUX_LIB_BUILD)" "$$BUNDLE_DIR"; \ + patchelf --set-rpath '$$ORIGIN/lib' "$$BUNDLE_DIR/lantern" || true; \ + VERSION=$(APP_VERSION) GOARCH=$(LINUX_TARGET_ARCH) LINUX_BUNDLE_SRC="$$BUNDLE_DIR/" SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \ LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) LANTERND_DST=/usr/sbin/$(LINUX_SERVICE_NAME) \ - nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p deb -t $(LINUX_INSTALLER_DEB) - VERSION=$(APP_VERSION) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \ + nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p deb -t $(LINUX_INSTALLER_DEB); \ + VERSION=$(APP_VERSION) GOARCH=$(LINUX_TARGET_ARCH) LINUX_BUNDLE_SRC="$$BUNDLE_DIR/" SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \ LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) LANTERND_DST=/usr/sbin/$(LINUX_SERVICE_NAME) \ - nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p rpm -t $(LINUX_INSTALLER_RPM) - VERSION=$(APP_VERSION) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \ + nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p rpm -t $(LINUX_INSTALLER_RPM); \ + VERSION=$(APP_VERSION) GOARCH=$(LINUX_TARGET_ARCH) LINUX_BUNDLE_SRC="$$BUNDLE_DIR/" SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \ LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) LANTERND_DST=/usr/bin/$(LINUX_SERVICE_NAME) \ - nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p archlinux -t $(LINUX_INSTALLER_ARCH) + nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p archlinux -t $(LINUX_INSTALLER_ARCH) .PHONY: verify-linux-package verify-linux-package: diff --git a/linux/packaging/nfpm.yaml b/linux/packaging/nfpm.yaml index d8ba32080e..9dc110ec67 100644 --- a/linux/packaging/nfpm.yaml +++ b/linux/packaging/nfpm.yaml @@ -12,9 +12,10 @@ priority: optional contents: # Flutter release bundle - - src: build/linux/x64/release/bundle/ + - src: "${LINUX_BUNDLE_SRC}" dst: /usr/lib/lantern type: tree + expand: true # lanternd service binary - src: "${LANTERND_SRC}" diff --git a/scripts/ci/format.sh b/scripts/ci/format.sh index 8fd04c8973..e0480fa262 100755 --- a/scripts/ci/format.sh +++ b/scripts/ci/format.sh @@ -12,6 +12,7 @@ set -euo pipefail # BUILD_TYPE: production, beta, or nightly # GITHUB_REF_NAME: branch/tag name (e.g., main, v9.0.11) # GITHUB_SHA: commit SHA (required for release-notes) +# LINUX_ARCH: amd64, arm64, or all (optional, defaults to amd64) FORMAT="${1:?Format required: release-notes, job-summary, or slack}" RELEASE_TAG="${RELEASE_TAG:?RELEASE_TAG required}" @@ -20,6 +21,7 @@ PLATFORM="${PLATFORM:?PLATFORM required}" BUCKET="${BUCKET:?BUCKET required}" BUILD_TYPE="${BUILD_TYPE:?BUILD_TYPE required}" GITHUB_REF_NAME="${GITHUB_REF_NAME:?GITHUB_REF_NAME required}" +LINUX_ARCH="${LINUX_ARCH:-amd64}" # Strip 'v' prefix for S3 paths and version display VERSION="${RELEASE_TAG#v}" @@ -37,6 +39,14 @@ should_include() { [[ "$PLATFORM" == "all" ]] || [[ "$PLATFORM" == *"$platform"* ]] } +include_linux_amd64() { + [[ "$LINUX_ARCH" == "amd64" || "$LINUX_ARCH" == "all" ]] +} + +include_linux_arm64() { + [[ "$LINUX_ARCH" == "arm64" || "$LINUX_ARCH" == "all" ]] +} + case "$FORMAT" in release-notes) GITHUB_SHA="${GITHUB_SHA:?GITHUB_SHA required for release-notes}" @@ -71,8 +81,14 @@ release-notes) fi if should_include "linux"; then - echo "- [Linux (.deb)](${LATEST_URL}/${FULL_INSTALLER_NAME}.deb) ([permalink](${VERSION_URL}/${FULL_INSTALLER_NAME}.deb))" - echo "- [Linux (.rpm)](${LATEST_URL}/${FULL_INSTALLER_NAME}.rpm) ([permalink](${VERSION_URL}/${FULL_INSTALLER_NAME}.rpm))" + if include_linux_amd64; then + echo "- [Linux AMD64 (.deb)](${LATEST_URL}/${FULL_INSTALLER_NAME}.deb) ([permalink](${VERSION_URL}/${FULL_INSTALLER_NAME}.deb))" + echo "- [Linux AMD64 (.rpm)](${LATEST_URL}/${FULL_INSTALLER_NAME}.rpm) ([permalink](${VERSION_URL}/${FULL_INSTALLER_NAME}.rpm))" + fi + if include_linux_arm64; then + echo "- [Linux ARM64 (.deb)](${LATEST_URL}/${FULL_INSTALLER_NAME}-arm64.deb) ([permalink](${VERSION_URL}/${FULL_INSTALLER_NAME}-arm64.deb))" + echo "- [Linux ARM64 (.rpm)](${LATEST_URL}/${FULL_INSTALLER_NAME}-arm64.rpm) ([permalink](${VERSION_URL}/${FULL_INSTALLER_NAME}-arm64.rpm))" + fi fi if should_include "ios" && [[ "$BUILD_TYPE" == "beta" || "$BUILD_TYPE" == "production" ]]; then @@ -104,8 +120,14 @@ slack) fi if should_include "linux"; then - text="${text}\n• Linux <${LATEST_URL}/${FULL_INSTALLER_NAME}.deb|${FULL_INSTALLER_NAME}.deb> (<${VERSION_URL}/${FULL_INSTALLER_NAME}.deb|permalink>)" - text="${text}\n• Linux <${LATEST_URL}/${FULL_INSTALLER_NAME}.rpm|${FULL_INSTALLER_NAME}.rpm> (<${VERSION_URL}/${FULL_INSTALLER_NAME}.rpm|permalink>)" + if include_linux_amd64; then + text="${text}\n• Linux AMD64 <${LATEST_URL}/${FULL_INSTALLER_NAME}.deb|${FULL_INSTALLER_NAME}.deb> (<${VERSION_URL}/${FULL_INSTALLER_NAME}.deb|permalink>)" + text="${text}\n• Linux AMD64 <${LATEST_URL}/${FULL_INSTALLER_NAME}.rpm|${FULL_INSTALLER_NAME}.rpm> (<${VERSION_URL}/${FULL_INSTALLER_NAME}.rpm|permalink>)" + fi + if include_linux_arm64; then + text="${text}\n• Linux ARM64 <${LATEST_URL}/${FULL_INSTALLER_NAME}-arm64.deb|${FULL_INSTALLER_NAME}-arm64.deb> (<${VERSION_URL}/${FULL_INSTALLER_NAME}-arm64.deb|permalink>)" + text="${text}\n• Linux ARM64 <${LATEST_URL}/${FULL_INSTALLER_NAME}-arm64.rpm|${FULL_INSTALLER_NAME}-arm64.rpm> (<${VERSION_URL}/${FULL_INSTALLER_NAME}-arm64.rpm|permalink>)" + fi fi if should_include "ios" && [[ "$BUILD_TYPE" == "beta" || "$BUILD_TYPE" == "production" ]]; then diff --git a/scripts/ci/publish-to-s3.sh b/scripts/ci/publish-to-s3.sh index 0fe8ef5361..ee4e5a3893 100755 --- a/scripts/ci/publish-to-s3.sh +++ b/scripts/ci/publish-to-s3.sh @@ -16,6 +16,7 @@ set -euo pipefail # BUCKET: S3 bucket name (required) # AWS_ACCESS_KEY_ID: AWS credentials (required) # AWS_SECRET_ACCESS_KEY: AWS credentials (required) +# LINUX_ARCH: amd64, arm64, or all (optional, defaults to all) BUILD_TYPE="${1:?Build type required}" VERSION="${2:?Version required}" @@ -23,6 +24,7 @@ INSTALLER_BASE_NAME="${3:?Installer base name required}" PLATFORMS="${4:?Platforms required}" BUCKET="${BUCKET:?BUCKET environment variable required}" +LINUX_ARCH="${LINUX_ARCH:-all}" # All builds use the same path structure: releases/{build_type}/{version}/ VERSION_PREFIX="releases/${BUILD_TYPE}/${VERSION}" @@ -44,25 +46,14 @@ should_upload() { [[ "$PLATFORMS" == "all" ]] || [[ "$PLATFORMS" == *"$platform"* ]] } -# Upload a single artifact if it exists -# Returns: 0=success, 1=not found, 2=upload failed -upload_artifact() { +# Upload a single file +# Returns: 0=success, 2=upload failed +upload_file() { local platform="$1" - local extension="$2" - local artifact_dir="lantern-installer-${extension}" - # Construct full filename with build type (Makefile appends it) - local filename="${INSTALLER_BASE_NAME}" - [[ -n "$BUILD_TYPE" && "$BUILD_TYPE" != "production" ]] && filename="${filename}-${BUILD_TYPE}" - filename="${filename}.${extension}" - local filepath="${artifact_dir}/${filename}" - - if [[ ! -f "$filepath" ]]; then - echo "⊘ Skipping $platform ($filename not found)" - return 1 - fi - + local filepath="$2" + local filename + filename="$(basename "$filepath")" echo "↑ Uploading $platform: $filename" - # Upload to versioned path if ! aws s3 cp "$filepath" "s3://${BUCKET}/${VERSION_PREFIX}/${filename}" --acl public-read; then echo "✗ Failed to upload $filename to versioned path" >&2 @@ -81,29 +72,73 @@ upload_artifact() { return 0 } -# platform:extension +# Upload an artifact from known directories/naming +# Returns: 0=success, 1=not found, 2=upload failed +upload_artifact() { + local platform="$1" + local extension="$2" + local arch="${3:-}" + + local base_name="${INSTALLER_BASE_NAME}" + [[ -n "$BUILD_TYPE" && "$BUILD_TYPE" != "production" ]] && base_name="${base_name}-${BUILD_TYPE}" + + local filename + local -a candidate_dirs=() + if [[ "$arch" == "arm64" ]]; then + filename="${base_name}-arm64.${extension}" + candidate_dirs=("lantern-installer-${extension}-arm64") + elif [[ "$arch" == "amd64" ]]; then + filename="${base_name}.${extension}" + candidate_dirs=("lantern-installer-${extension}-amd64" "lantern-installer-${extension}") + else + filename="${base_name}.${extension}" + candidate_dirs=("lantern-installer-${extension}") + fi + + local filepath="" + for dir in "${candidate_dirs[@]}"; do + local candidate="${dir}/${filename}" + if [[ -f "$candidate" ]]; then + filepath="$candidate" + break + fi + done + + if [[ -z "$filepath" ]]; then + echo "⊘ Skipping $platform ($filename not found)" + return 1 + fi + + upload_file "$platform" "$filepath" +} + +# platform:extension:arch(optional) declare -a artifacts=( - "macos:dmg" - "windows:exe" - "android:apk" - "linux:deb" - "linux:rpm" - "ios:ipa" + "macos:dmg:" + "windows:exe:" + "android:apk:" + "ios:ipa:" ) +if [[ "$LINUX_ARCH" == "all" || "$LINUX_ARCH" == "amd64" ]]; then + artifacts+=("linux:deb:amd64" "linux:rpm:amd64") +fi +if [[ "$LINUX_ARCH" == "all" || "$LINUX_ARCH" == "arm64" ]]; then + artifacts+=("linux:deb:arm64" "linux:rpm:arm64") +fi + uploaded=0 skipped=0 failed=0 for artifact in "${artifacts[@]}"; do - platform="${artifact%%:*}" - extension="${artifact##*:}" + IFS=':' read -r platform extension arch <<<"$artifact" if ! should_upload "$platform"; then continue fi - upload_artifact "$platform" "$extension" + upload_artifact "$platform" "$extension" "${arch:-}" result=$? case $result in diff --git a/scripts/ci/verify_linux_package.sh b/scripts/ci/verify_linux_package.sh index 93bb6fc3c8..8c1b2d1822 100755 --- a/scripts/ci/verify_linux_package.sh +++ b/scripts/ci/verify_linux_package.sh @@ -1,17 +1,26 @@ #!/usr/bin/env bash set -euo pipefail -if [[ $# -ne 1 ]]; then - echo "usage: $0 " +if [[ $# -lt 1 || $# -gt 2 ]]; then + echo "usage: $0 [expected-arch]" exit 2 fi DEB_PATH="$1" +EXPECTED_ARCH="${2:-}" if [[ ! -f "$DEB_PATH" ]]; then echo "deb package not found: $DEB_PATH" exit 1 fi +if [[ -n "$EXPECTED_ARCH" ]]; then + PKG_ARCH="$(dpkg-deb -f "$DEB_PATH" Architecture)" + if [[ "$PKG_ARCH" != "$EXPECTED_ARCH" ]]; then + echo "package architecture mismatch: expected '$EXPECTED_ARCH', got '$PKG_ARCH'" + exit 1 + fi +fi + TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT From 2f9724dd559c401ebf63bf30eb7ddda2540f8ff1 Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 11:31:04 -0700 Subject: [PATCH 02/15] Fix Linux arch workflow input wiring --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 48974a4a15..89f5584c7b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -254,7 +254,6 @@ jobs: version: ${{ needs.set-metadata.outputs.version }} build_type: ${{ needs.set-metadata.outputs.build_type }} installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} - linux_arch: ${{ needs.set-metadata.outputs.linux_arch }} enable_ip_check: ${{ needs.set-metadata.outputs.build_type == 'nightly' }} build-windows: @@ -286,6 +285,7 @@ jobs: version: ${{ needs.set-metadata.outputs.version }} build_type: ${{ needs.set-metadata.outputs.build_type }} installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} + linux_arch: ${{ needs.set-metadata.outputs.linux_arch }} build-ios: needs: [set-metadata, release-create, release-approval] From 4786b7e3fc4b33e3891eb8ef39619fe1f6c3d7ff Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 11:35:57 -0700 Subject: [PATCH 03/15] Fix Linux workflow matrix selection --- .github/workflows/build-linux.yml | 6 +++--- .github/workflows/release.yml | 25 ++++++++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index d12481d4c3..82e9d41ee0 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -12,16 +12,16 @@ on: installer_base_name: required: true type: string - linux_arch: + linux_matrix_json: required: false type: string - default: amd64 + default: '{"include":[{"arch":"amd64","runner":"ubuntu-latest"}]}' jobs: build-linux: strategy: fail-fast: false - matrix: ${{ fromJSON(inputs.linux_arch == 'all' && '{"include":[{"arch":"amd64","runner":"ubuntu-latest"},{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' || inputs.linux_arch == 'arm64' && '{"include":[{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' || '{"include":[{"arch":"amd64","runner":"ubuntu-latest"}]}') }} + matrix: ${{ fromJSON(inputs.linux_matrix_json) }} env: BUILD_TYPE: ${{ inputs.build_type }} VERSION: ${{ inputs.version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 89f5584c7b..19d2364f52 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,6 +59,7 @@ jobs: installer_base_name: ${{ steps.meta.outputs.installer_base_name }} platform: ${{ steps.meta.outputs.platform }} linux_arch: ${{ steps.meta.outputs.linux_arch }} + linux_matrix_json: ${{ steps.meta.outputs.linux_matrix_json }} is_test_run: ${{ steps.meta.outputs.is_test_run }} steps: - name: Checkout repo @@ -148,10 +149,22 @@ jobs: *) VERSION="${VERSION%-$PLATFORM}" ;; esac - case "$PLATFORM" in - all|linux) LINUX_ARCH="all" ;; - *) LINUX_ARCH="amd64" ;; - esac + case "$PLATFORM" in + all|linux) LINUX_ARCH="all" ;; + *) LINUX_ARCH="amd64" ;; + esac + ;; + esac + + case "$LINUX_ARCH" in + all) + LINUX_MATRIX_JSON='{"include":[{"arch":"amd64","runner":"ubuntu-latest"},{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' + ;; + arm64) + LINUX_MATRIX_JSON='{"include":[{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' + ;; + amd64|*) + LINUX_MATRIX_JSON='{"include":[{"arch":"amd64","runner":"ubuntu-latest"}]}' ;; esac @@ -165,6 +178,7 @@ jobs: echo " Version: $VERSION" echo " Platform: $PLATFORM" echo " Linux Arch: $LINUX_ARCH" + echo " Linux Matrix: $LINUX_MATRIX_JSON" echo " Installer: $INSTALLER_BASE_NAME" # Test run: workflow_dispatch from non-default branch @@ -182,6 +196,7 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT echo "platform=$PLATFORM" >> $GITHUB_OUTPUT echo "linux_arch=$LINUX_ARCH" >> $GITHUB_OUTPUT + echo "linux_matrix_json=$LINUX_MATRIX_JSON" >> $GITHUB_OUTPUT echo "installer_base_name=$INSTALLER_BASE_NAME" >> $GITHUB_OUTPUT echo "is_test_run=$IS_TEST_RUN" >> $GITHUB_OUTPUT @@ -285,7 +300,7 @@ jobs: version: ${{ needs.set-metadata.outputs.version }} build_type: ${{ needs.set-metadata.outputs.build_type }} installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} - linux_arch: ${{ needs.set-metadata.outputs.linux_arch }} + linux_matrix_json: ${{ needs.set-metadata.outputs.linux_matrix_json }} build-ios: needs: [set-metadata, release-create, release-approval] From 48f5c46509ff8e88c88247efdc12bb0851a80b2b Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 11:37:33 -0700 Subject: [PATCH 04/15] Simplify Linux arch workflow selection --- .github/workflows/build-linux.yml | 12 +++++++++--- .github/workflows/release.yml | 17 +---------------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 82e9d41ee0..a39e0ccdf7 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -12,16 +12,22 @@ on: installer_base_name: required: true type: string - linux_matrix_json: + linux_arch: required: false type: string - default: '{"include":[{"arch":"amd64","runner":"ubuntu-latest"}]}' + default: all jobs: build-linux: strategy: fail-fast: false - matrix: ${{ fromJSON(inputs.linux_matrix_json) }} + matrix: + include: + - arch: amd64 + runner: ubuntu-latest + - arch: arm64 + runner: ubuntu-24.04-arm + if: ${{ inputs.linux_arch == 'all' || inputs.linux_arch == matrix.arch }} env: BUILD_TYPE: ${{ inputs.build_type }} VERSION: ${{ inputs.version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 19d2364f52..6c73d39bca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,7 +59,6 @@ jobs: installer_base_name: ${{ steps.meta.outputs.installer_base_name }} platform: ${{ steps.meta.outputs.platform }} linux_arch: ${{ steps.meta.outputs.linux_arch }} - linux_matrix_json: ${{ steps.meta.outputs.linux_matrix_json }} is_test_run: ${{ steps.meta.outputs.is_test_run }} steps: - name: Checkout repo @@ -156,18 +155,6 @@ jobs: ;; esac - case "$LINUX_ARCH" in - all) - LINUX_MATRIX_JSON='{"include":[{"arch":"amd64","runner":"ubuntu-latest"},{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' - ;; - arm64) - LINUX_MATRIX_JSON='{"include":[{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' - ;; - amd64|*) - LINUX_MATRIX_JSON='{"include":[{"arch":"amd64","runner":"ubuntu-latest"}]}' - ;; - esac - # Installer base name - Makefile will append build type INSTALLER_BASE_NAME="lantern-installer" @@ -178,7 +165,6 @@ jobs: echo " Version: $VERSION" echo " Platform: $PLATFORM" echo " Linux Arch: $LINUX_ARCH" - echo " Linux Matrix: $LINUX_MATRIX_JSON" echo " Installer: $INSTALLER_BASE_NAME" # Test run: workflow_dispatch from non-default branch @@ -196,7 +182,6 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT echo "platform=$PLATFORM" >> $GITHUB_OUTPUT echo "linux_arch=$LINUX_ARCH" >> $GITHUB_OUTPUT - echo "linux_matrix_json=$LINUX_MATRIX_JSON" >> $GITHUB_OUTPUT echo "installer_base_name=$INSTALLER_BASE_NAME" >> $GITHUB_OUTPUT echo "is_test_run=$IS_TEST_RUN" >> $GITHUB_OUTPUT @@ -300,7 +285,7 @@ jobs: version: ${{ needs.set-metadata.outputs.version }} build_type: ${{ needs.set-metadata.outputs.build_type }} installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} - linux_matrix_json: ${{ needs.set-metadata.outputs.linux_matrix_json }} + linux_arch: ${{ needs.set-metadata.outputs.linux_arch }} build-ios: needs: [set-metadata, release-create, release-approval] From b938786ceaa3d743eefdb7f6cfe25872c8394b5e Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 11:38:23 -0700 Subject: [PATCH 05/15] Fix Linux arch matrix expression --- .github/workflows/build-linux.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index a39e0ccdf7..609b375e75 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -21,13 +21,7 @@ jobs: build-linux: strategy: fail-fast: false - matrix: - include: - - arch: amd64 - runner: ubuntu-latest - - arch: arm64 - runner: ubuntu-24.04-arm - if: ${{ inputs.linux_arch == 'all' || inputs.linux_arch == matrix.arch }} + matrix: ${{ fromJSON(inputs.linux_arch == 'arm64' && '{"include":[{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' || inputs.linux_arch == 'amd64' && '{"include":[{"arch":"amd64","runner":"ubuntu-latest"}]}' || '{"include":[{"arch":"amd64","runner":"ubuntu-latest"},{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}') }} env: BUILD_TYPE: ${{ inputs.build_type }} VERSION: ${{ inputs.version }} From 0eea1691757915fd2b8a1925a8557e6049b0af24 Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 11:41:55 -0700 Subject: [PATCH 06/15] Fix release workflow input routing --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3a3fcb9df..b746134e42 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -235,7 +235,6 @@ jobs: version: ${{ needs.set-metadata.outputs.version }} build_type: ${{ needs.set-metadata.outputs.build_type }} installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} - enable_ip_check: ${{ needs.set-metadata.outputs.build_type == 'nightly' }} build-windows: needs: [set-metadata, release-create, release-approval] @@ -251,6 +250,7 @@ jobs: version: ${{ needs.set-metadata.outputs.version }} build_type: ${{ needs.set-metadata.outputs.build_type }} installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} + enable_ip_check: ${{ needs.set-metadata.outputs.build_type == 'nightly' }} build-linux: needs: [set-metadata, release-create, release-approval] From eca90f6315d1759e5b24b7aa78014e75739e541b Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 11:49:32 -0700 Subject: [PATCH 07/15] Polish Linux arch release flow --- .github/workflows/release.yml | 8 ++++---- Makefile | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8e3c74f7a6..5f6982457a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -148,10 +148,10 @@ jobs: *) VERSION="${VERSION%-$PLATFORM}" ;; esac - case "$PLATFORM" in - all|linux) LINUX_ARCH="all" ;; - *) LINUX_ARCH="amd64" ;; - esac + case "$PLATFORM" in + all|linux) LINUX_ARCH="all" ;; + *) LINUX_ARCH="amd64" ;; + esac ;; esac diff --git a/Makefile b/Makefile index b0745ede40..bc0ea1df57 100644 --- a/Makefile +++ b/Makefile @@ -320,11 +320,15 @@ linux-release-ci: linux pubget gen flutter build linux --release $(DART_DEFINES) $(MAKE) stage-linux-service - @if [ -d "$(LINUX_BUNDLE_DIR_ARM64)" ] && [ "$(LINUX_TARGET_ARCH)" = "arm64" ]; then \ + @if [ "$(LINUX_TARGET_ARCH)" = "arm64" ]; then \ BUNDLE_DIR="$(LINUX_BUNDLE_DIR_ARM64)"; \ else \ BUNDLE_DIR="$(LINUX_BUNDLE_DIR_X64)"; \ fi; \ + if [ ! -d "$$BUNDLE_DIR" ]; then \ + echo "Expected Linux bundle dir not found: $$BUNDLE_DIR"; \ + exit 1; \ + fi; \ echo "Using Linux bundle dir: $$BUNDLE_DIR"; \ cp "$(LINUX_LIB_BUILD)" "$$BUNDLE_DIR"; \ patchelf --set-rpath '$$ORIGIN/lib' "$$BUNDLE_DIR/lantern" || true; \ From fa94631c864aac32dd81457563f0d36432bc344a Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 11:55:39 -0700 Subject: [PATCH 08/15] Fix Flutter setup on Linux arm64 CI --- .github/workflows/build-linux.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 609b375e75..2bda5620ab 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -104,13 +104,34 @@ jobs: - name: Install the ninja build tool uses: seanmiddleditch/gha-setup-ninja@master - - name: Install Flutter + - name: Load Flutter version + shell: bash + run: | + set -euo pipefail + FLUTTER_VERSION=$(grep -E '^[[:space:]]*flutter:' .github/flutter-version.yaml | sed -E 's/.*"([^"]+)".*/\1/') + if [[ -z "$FLUTTER_VERSION" ]]; then + echo "Failed to parse Flutter version from .github/flutter-version.yaml" >&2 + exit 1 + fi + echo "FLUTTER_VERSION=$FLUTTER_VERSION" >> "$GITHUB_ENV" + + - name: Install Flutter (amd64) uses: subosito/flutter-action@v2.22.0 + if: ${{ matrix.arch == 'amd64' }} with: channel: stable flutter-version-file: .github/flutter-version.yaml cache: true + - name: Install Flutter (arm64 fallback) + if: ${{ matrix.arch == 'arm64' }} + shell: bash + run: | + set -euo pipefail + git clone --depth 1 --branch "$FLUTTER_VERSION" https://github.com/flutter/flutter.git "$HOME/flutter" + echo "$HOME/flutter/bin" >> "$GITHUB_PATH" + flutter --version + - name: Install dependencies run: make install-linux-deps From 0aa7070f66333bc054739369615f72feddc0d5f9 Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 12:03:41 -0700 Subject: [PATCH 09/15] Fix arm64 Flutter fallback PATH --- .github/workflows/build-linux.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 2bda5620ab..b1c845231f 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -128,8 +128,10 @@ jobs: shell: bash run: | set -euo pipefail - git clone --depth 1 --branch "$FLUTTER_VERSION" https://github.com/flutter/flutter.git "$HOME/flutter" - echo "$HOME/flutter/bin" >> "$GITHUB_PATH" + FLUTTER_HOME="$HOME/flutter" + git clone --depth 1 --branch "$FLUTTER_VERSION" https://github.com/flutter/flutter.git "$FLUTTER_HOME" + echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH" + export PATH="$FLUTTER_HOME/bin:$PATH" flutter --version - name: Install dependencies From b47233849dd992d67a7a72cde0e361278d7d30ae Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 12:28:06 -0700 Subject: [PATCH 10/15] Gate Linux smoke IP check by architecture --- .github/workflows/build-linux.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index b1c845231f..8a3dd4824d 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -215,7 +215,11 @@ jobs: run: | set -euxo pipefail TEST_START_UTC="$(date -u '+%Y-%m-%d %H:%M:%S')" - sg lantern -c "env PATH=$PATH HOME=$HOME xvfb-run -a flutter test integration_test/vpn/linux_connect_smoke_test.dart -d linux --dart-define=DISABLE_SYSTEM_TRAY=true --dart-define=ENABLE_IP_CHECK=true" + ENABLE_IP_CHECK=false + if [[ "${TARGET_ARCH}" == "amd64" ]]; then + ENABLE_IP_CHECK=true + fi + sg lantern -c "env PATH=$PATH HOME=$HOME xvfb-run -a flutter test integration_test/vpn/linux_connect_smoke_test.dart -d linux --dart-define=DISABLE_SYSTEM_TRAY=true --dart-define=ENABLE_IP_CHECK=${ENABLE_IP_CHECK}" sudo journalctl -u lanternd.service --since "$TEST_START_UTC" --no-pager > /tmp/lanternd-journal-ui-smoke.log From 382f5ccf8ed0b0251e2293134af723248105821c Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 12:32:17 -0700 Subject: [PATCH 11/15] Update scripts/ci/publish-to-s3.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/ci/publish-to-s3.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/ci/publish-to-s3.sh b/scripts/ci/publish-to-s3.sh index ee4e5a3893..0e34222c21 100755 --- a/scripts/ci/publish-to-s3.sh +++ b/scripts/ci/publish-to-s3.sh @@ -26,6 +26,14 @@ PLATFORMS="${4:?Platforms required}" BUCKET="${BUCKET:?BUCKET environment variable required}" LINUX_ARCH="${LINUX_ARCH:-all}" +case "$LINUX_ARCH" in + amd64|arm64|all) + ;; + *) + echo "✗ Invalid LINUX_ARCH value: '$LINUX_ARCH'. Expected 'amd64', 'arm64', or 'all'." >&2 + exit 1 + ;; +esac # All builds use the same path structure: releases/{build_type}/{version}/ VERSION_PREFIX="releases/${BUILD_TYPE}/${VERSION}" LATEST_PREFIX="releases/${BUILD_TYPE}/latest" From 3ce830f2056d3a83b9f09f0682a5b45cb1365a91 Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 12:30:22 -0700 Subject: [PATCH 12/15] Set explicit token permissions for Linux workflow --- .github/workflows/build-linux.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 8a3dd4824d..b88ad91972 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -17,6 +17,9 @@ on: type: string default: all +permissions: + contents: "read" + jobs: build-linux: strategy: From ba556080eb8ba5a657e4b7dd1fd633599ea2e430 Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 13:58:24 -0700 Subject: [PATCH 13/15] Fail fast Linux UI smoke test in CI --- .github/workflows/build-linux.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index b88ad91972..f36d434ee9 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -222,9 +222,21 @@ jobs: if [[ "${TARGET_ARCH}" == "amd64" ]]; then ENABLE_IP_CHECK=true fi - sg lantern -c "env PATH=$PATH HOME=$HOME xvfb-run -a flutter test integration_test/vpn/linux_connect_smoke_test.dart -d linux --dart-define=DISABLE_SYSTEM_TRAY=true --dart-define=ENABLE_IP_CHECK=${ENABLE_IP_CHECK}" + set +e + sg lantern -c "env PATH=$PATH HOME=$HOME timeout --signal=TERM --kill-after=30s 8m xvfb-run -a flutter test integration_test/vpn/linux_connect_smoke_test.dart -d linux --reporter=expanded --dart-define=DISABLE_SYSTEM_TRAY=true --dart-define=ENABLE_IP_CHECK=${ENABLE_IP_CHECK}" + SMOKE_EXIT=$? + set -e sudo journalctl -u lanternd.service --since "$TEST_START_UTC" --no-pager > /tmp/lanternd-journal-ui-smoke.log + if [[ "$SMOKE_EXIT" -eq 124 ]]; then + echo "Linux UI connect/disconnect smoke timed out after 8m" + tail -n 200 /tmp/lanternd-journal-ui-smoke.log || true + exit 1 + fi + if [[ "$SMOKE_EXIT" -ne 0 ]]; then + tail -n 200 /tmp/lanternd-journal-ui-smoke.log || true + exit "$SMOKE_EXIT" + fi if ! grep -Eq 'IPC request.*path=/service/start' /tmp/lanternd-journal-ui-smoke.log; then echo "Missing /service/start IPC request in lanternd journal" From ffba53a10b60aa73af12a37a241ea75adb65f301 Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 19 Mar 2026 14:03:45 -0700 Subject: [PATCH 14/15] Clean up connect smoke failure path --- .../vpn/connect_smoke_harness.dart | 118 +++++++++++------- 1 file changed, 73 insertions(+), 45 deletions(-) diff --git a/integration_test/vpn/connect_smoke_harness.dart b/integration_test/vpn/connect_smoke_harness.dart index 4acca5cf6b..7c5681fe4c 100644 --- a/integration_test/vpn/connect_smoke_harness.dart +++ b/integration_test/vpn/connect_smoke_harness.dart @@ -6,10 +6,7 @@ import 'package:lantern/core/common/app_eum.dart'; import '../utils/widget_wait_utils.dart'; -const _vpnStateKeyPrefixes = [ - 'vpn.switch.', - 'vpn.status.', -]; +const _vpnStateKeyPrefixes = ['vpn.switch.', 'vpn.status.']; const _observableStates = [ VPNStatus.connected, @@ -47,8 +44,9 @@ Future _fetchPublicIpOnce() async { return null; } - final body = - await response.transform(const SystemEncoding().decoder).join(); + final body = await response + .transform(const SystemEncoding().decoder) + .join(); final ip = body.trim(); if (ip.isNotEmpty && InternetAddress.tryParse(ip) != null) { return ip; @@ -77,17 +75,17 @@ Future _fetchPublicIpWithRetry({ fail('Failed to fetch public IP: $reason'); } -Future _assertPublicIpChangesFromBaseline(String baselineIp) async { +Future _didPublicIpChangeFromBaseline(String baselineIp) async { final deadline = DateTime.now().add(const Duration(seconds: 60)); while (DateTime.now().isBefore(deadline)) { final current = await _fetchPublicIpOnce(); if (current != null && current.isNotEmpty && current != baselineIp) { debugPrint('IP check: detected public IP change after connect'); - return; + return true; } await Future.delayed(const Duration(seconds: 3)); } - fail('Public IP did not change after VPN connected (baseline: $baselineIp)'); + return false; } class _VpnStateFinders { @@ -140,14 +138,15 @@ class _VpnStateFinders { return state; } - final debugKeys = tester.allWidgets - .map((w) => w.key) - .whereType() - .map((k) => k.toString()) - .where((k) => k.contains('vpn.') || k.contains('onboarding.')) - .toSet() - .toList() - ..sort(); + final debugKeys = + tester.allWidgets + .map((w) => w.key) + .whereType() + .map((k) => k.toString()) + .where((k) => k.contains('vpn.') || k.contains('onboarding.')) + .toSet() + .toList() + ..sort(); fail( '${reason ?? 'Timed out waiting for VPN state'}. Last observed: ${current()?.name ?? 'unknown'}. ' 'Visible keyed widgets: $debugKeys', @@ -184,6 +183,34 @@ Future _waitForVpnToggleWithOnboardingHandling( fail('VPN toggle not visible'); } +Future _disconnectVpn( + WidgetTester tester, { + required Finder vpnToggle, + required _VpnStateFinders vpnStateFinders, +}) async { + final currentState = vpnStateFinders.current(); + if (currentState != VPNStatus.connected && + currentState != VPNStatus.connecting) { + return; + } + + await WidgetWaitUtils.waitForFinder( + tester, + vpnToggle, + timeout: const Duration(seconds: 15), + reason: 'VPN toggle not available for disconnect', + ); + await tester.tap(vpnToggle); + await tester.pump(const Duration(milliseconds: 200)); + + await vpnStateFinders.waitFor( + tester, + expected: const [VPNStatus.disconnected], + timeout: const Duration(seconds: 45), + reason: 'VPN did not return to disconnected state within 45 seconds', + ); +} + Future runConnectSmokeHarness( WidgetTester tester, { bool enableIpCheck = false, @@ -303,36 +330,37 @@ Future runConnectSmokeHarness( ); } - await tester.tap(vpnToggle); - await tester.pump(const Duration(milliseconds: 200)); + bool ipChanged = true; + try { + await tester.tap(vpnToggle); + await tester.pump(const Duration(milliseconds: 200)); - await vpnStateFinders.waitFor( - tester, - expected: const [VPNStatus.connected], - timeout: const Duration(seconds: 45), - reason: 'VPN did not reach connected state within 45 seconds', - ); + await vpnStateFinders.waitFor( + tester, + expected: const [VPNStatus.connected], + timeout: const Duration(seconds: 45), + reason: 'VPN did not reach connected state within 45 seconds', + ); - if (enableIpCheck && baselinePublicIp != null) { - debugPrint('IP check: waiting for IP change after connect'); - await Future.delayed(const Duration(seconds: 3)); - await _assertPublicIpChangesFromBaseline(baselinePublicIp); - debugPrint('IP check: passed'); + if (enableIpCheck && baselinePublicIp != null) { + debugPrint('IP check: waiting for IP change after connect'); + await Future.delayed(const Duration(seconds: 3)); + ipChanged = await _didPublicIpChangeFromBaseline(baselinePublicIp); + if (ipChanged) { + debugPrint('IP check: passed'); + } + } + } finally { + await _disconnectVpn( + tester, + vpnToggle: vpnToggle, + vpnStateFinders: vpnStateFinders, + ); } - await WidgetWaitUtils.waitForFinder( - tester, - vpnToggle, - timeout: const Duration(seconds: 15), - reason: 'VPN toggle not available for disconnect', - ); - await tester.tap(vpnToggle); - await tester.pump(const Duration(milliseconds: 200)); - - await vpnStateFinders.waitFor( - tester, - expected: const [VPNStatus.disconnected], - timeout: const Duration(seconds: 45), - reason: 'VPN did not return to disconnected state within 45 seconds', - ); + if (enableIpCheck && baselinePublicIp != null && !ipChanged) { + fail( + 'Public IP did not change after VPN connected (baseline: $baselinePublicIp)', + ); + } } From bcab4b363cbe045eb82a2e80bddfd384d3a98251 Mon Sep 17 00:00:00 2001 From: atavism Date: Fri, 20 Mar 2026 06:05:16 -0700 Subject: [PATCH 15/15] code review updates --- .github/workflows/build-linux.yml | 2 +- .github/workflows/release.yml | 8 ++++++++ scripts/ci/format.sh | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index f36d434ee9..eb0321293d 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -24,7 +24,7 @@ jobs: build-linux: strategy: fail-fast: false - matrix: ${{ fromJSON(inputs.linux_arch == 'arm64' && '{"include":[{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' || inputs.linux_arch == 'amd64' && '{"include":[{"arch":"amd64","runner":"ubuntu-latest"}]}' || '{"include":[{"arch":"amd64","runner":"ubuntu-latest"},{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}') }} + matrix: ${{ fromJSON(inputs.linux_arch == 'arm64' && '{"include":[{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}' || inputs.linux_arch == 'amd64' && '{"include":[{"arch":"amd64","runner":"ubuntu-latest"}]}' || inputs.linux_arch == 'all' && '{"include":[{"arch":"amd64","runner":"ubuntu-latest"},{"arch":"arm64","runner":"ubuntu-24.04-arm"}]}') }} env: BUILD_TYPE: ${{ inputs.build_type }} VERSION: ${{ inputs.version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f6982457a..da98e08e70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -155,6 +155,14 @@ jobs: ;; esac + case "$LINUX_ARCH" in + all|amd64|arm64) ;; + *) + echo "Error: Invalid linux_arch '$LINUX_ARCH' (expected: all, amd64, arm64)" >&2 + exit 1 + ;; + esac + # Installer base name - Makefile will append build type INSTALLER_BASE_NAME="lantern-installer" diff --git a/scripts/ci/format.sh b/scripts/ci/format.sh index e0480fa262..5d9c0f3355 100755 --- a/scripts/ci/format.sh +++ b/scripts/ci/format.sh @@ -23,6 +23,14 @@ BUILD_TYPE="${BUILD_TYPE:?BUILD_TYPE required}" GITHUB_REF_NAME="${GITHUB_REF_NAME:?GITHUB_REF_NAME required}" LINUX_ARCH="${LINUX_ARCH:-amd64}" +case "$LINUX_ARCH" in +amd64 | arm64 | all) ;; +*) + echo "Error: Invalid LINUX_ARCH '$LINUX_ARCH' (expected: amd64, arm64, all)" >&2 + exit 1 + ;; +esac + # Strip 'v' prefix for S3 paths and version display VERSION="${RELEASE_TAG#v}"