diff --git a/.github/workflows/build-cli-linux.yml b/.github/workflows/build-cli-linux.yml new file mode 100644 index 000000000..cfd84e951 --- /dev/null +++ b/.github/workflows/build-cli-linux.yml @@ -0,0 +1,191 @@ +on: + workflow_dispatch: + workflow_call: + inputs: + version: + required: false + default: "" + type: string + ref: + type: string + required: false + default: "" + +name: "Build CLI for Linux" + +permissions: + contents: read + +jobs: + build-cli-linux: + name: Build CLI + runs-on: ${{ matrix.build-on }} + container: ${{ matrix.container }} + strategy: + fail-fast: false + matrix: + include: + - architecture: x86_64 + target-suffix: unknown-linux-gnu + build-on: ubuntu-22.04 + # Pinned by digest for reproducible builds; bump explicitly when newer manylinux_2_28 images ship. + container: quay.io/pypa/manylinux_2_28_x86_64@sha256:441c35fdc6ee809ff9260894f8468ab4fea8c15dc880f8700a3f81b7922c1cda + variant: standard + - architecture: aarch64 + target-suffix: unknown-linux-gnu + build-on: ubuntu-22.04-arm + # Pinned by digest for reproducible builds; bump explicitly when newer manylinux_2_28 images ship. + container: quay.io/pypa/manylinux_2_28_aarch64@sha256:8b5f2b4e8c072ae5aefeb659f22c03e1ff46e6a82f154b6c904b106c87e65ff7 + variant: standard + - architecture: x86_64 + target-suffix: unknown-linux-musl + build-on: ubuntu-22.04 + variant: musl + - architecture: aarch64 + target-suffix: unknown-linux-musl + build-on: ubuntu-22.04-arm + variant: musl + - architecture: x86_64 + target-suffix: unknown-linux-gnu + build-on: ubuntu-24.04 + variant: vulkan + - architecture: aarch64 + target-suffix: unknown-linux-gnu + build-on: ubuntu-24.04-arm + variant: vulkan + + steps: + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ inputs.ref }} + + - name: Update version in Cargo.toml + if: ${{ inputs.version != '' }} + shell: bash + env: + VERSION: ${{ inputs.version }} + run: bash scripts/set-cargo-version.sh "$VERSION" + + - name: Install Linux build dependencies (host runner) + if: matrix.container == '' + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + pkg-config \ + libssl-dev \ + libdbus-1-dev \ + libxcb1-dev + + if [ "${{ matrix.variant }}" = "vulkan" ]; then + sudo apt-get install -y \ + libvulkan-dev \ + libvulkan1 \ + glslc + fi + + if [ "${{ matrix.variant }}" = "musl" ]; then + sudo apt-get install -y musl-tools + fi + + - name: Install Linux build dependencies (manylinux container) + if: matrix.container != '' + run: | + # perl-core provides FindBin, File::Compare, etc. that openssl-sys's + # vendored openssl build needs; in AlmaLinux 8 these aren't standalone packages. + # clang provides libclang.so for bindgen (used by llama-cpp-sys-2). + # Defensive: avoid actions/checkout falling back to a tarball download if base image changes. + dnf install -y --setopt=install_weak_deps=False \ + openssl-devel \ + dbus-devel \ + libxcb-devel \ + cmake \ + perl-core \ + clang \ + git \ + tar + + - name: Cache Cargo artifacts + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + with: + key: ${{ matrix.architecture }}-${{ matrix.target-suffix }}-${{ matrix.build-on }}-${{ matrix.container || 'native' }} + + - name: Build CLI (host runner) + if: matrix.container == '' + env: + RUST_LOG: debug + RUST_BACKTRACE: 1 + run: | + source ./bin/activate-hermit + export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}" + rustup target add "${TARGET}" + echo "Building for target: ${TARGET}" + echo "Rust toolchain info:" + rustup show + + FEATURE_ARGS=() + if [ "${{ matrix.variant }}" = "vulkan" ]; then + FEATURE_ARGS=(--features vulkan) + fi + + if [ "${{ matrix.variant }}" = "musl" ]; then + cargo build --release --target ${TARGET} -p goose-cli --bin goose \ + --no-default-features \ + --features portable-default + else + cargo build --release --target ${TARGET} -p goose-cli --bin goose "${FEATURE_ARGS[@]}" + fi + + - name: Build CLI (manylinux container) + if: matrix.container != '' + env: + RUST_BACKTRACE: 1 + run: | + # Hermit's tool cache is host-runner-scoped; inside the container we + # bootstrap rustup directly and let rust-toolchain.toml pin the channel. + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --default-toolchain none --profile minimal --no-modify-path + export PATH="$HOME/.cargo/bin:$PATH" + TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}" + RUST_CHANNEL=$(grep '^channel' rust-toolchain.toml | cut -d'"' -f2) + if [ -z "$RUST_CHANNEL" ]; then + echo "Could not parse channel from rust-toolchain.toml" >&2 + exit 1 + fi + rustup toolchain install "$RUST_CHANNEL" --profile minimal \ + --component rustc,cargo --target "$TARGET" + rustup show + cargo build --release --target "$TARGET" -p goose-cli --bin goose + + - name: Package CLI + run: | + # Hermit isn't installed in the manylinux container; tar is all this step needs. + if [ "${{ matrix.container }}" = '' ]; then + source ./bin/activate-hermit + fi + export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}" + export VARIANT_SUFFIX="" + if [ "${{ matrix.variant }}" = "vulkan" ]; then + VARIANT_SUFFIX="-vulkan" + fi + + # Create a directory for the package contents + mkdir -p "target/${TARGET}/release/goose-package" + + # Copy the goose binary + cp "target/${TARGET}/release/goose" "target/${TARGET}/release/goose-package/" + + cd "target/${TARGET}/release" + tar -cjf "goose-${TARGET}${VARIANT_SUFFIX}.tar.bz2" -C goose-package . + tar -czf "goose-${TARGET}${VARIANT_SUFFIX}.tar.gz" -C goose-package . + echo "ARTIFACT_BZ2=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.tar.bz2" >> $GITHUB_ENV + echo "ARTIFACT_GZ=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.tar.gz" >> $GITHUB_ENV + + - name: Upload CLI artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: goose-${{ matrix.architecture }}-${{ matrix.target-suffix }}${{ matrix.variant != 'standard' && matrix.variant != 'musl' && format('-{0}', matrix.variant) || '' }} + path: | + ${{ env.ARTIFACT_BZ2 }} + ${{ env.ARTIFACT_GZ }} diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index f3b677c13..250458a37 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -1,14 +1,13 @@ -# This is a **reuseable** workflow that builds the CLI for multiple platforms. -# It doesn't get triggered on its own. It gets used in multiple workflows: -# - release.yml -# - canary.yml +# This is a reusable workflow that builds the CLI for multiple platforms. +# It doesn't get triggered on its own. It gets used by: +# - publish-npm.yml # # Platform Build Strategy: # - Linux standard (x86_64 + aarch64): Builds inside manylinux_2_28 container for glibc 2.28+ compat # - Linux Vulkan: Uses native Ubuntu 24.04 runners for newer Vulkan headers/tooling # - Linux musl: Uses native Ubuntu 22.04 runners with reduced features for musl compatibility -# - macOS: Uses native macOS runners for each architecture -# - Windows: Uses Windows runner with native MSVC build +# - macOS: Uses bundle-macos.yml to package CLI artifacts without Desktop +# - Windows: Uses bundle-windows.yml to package CLI artifacts without Desktop on: workflow_call: inputs: @@ -24,285 +23,46 @@ on: name: "Reusable workflow to build CLI" jobs: - build-cli: - name: Build CLI - runs-on: ${{ matrix.build-on }} - container: ${{ matrix.container }} - env: - MACOSX_DEPLOYMENT_TARGET: "12.0" + build-cli-macos: strategy: fail-fast: false matrix: - include: - - platform: linux - architecture: x86_64 - target-suffix: unknown-linux-gnu - build-on: ubuntu-22.04 - # Pinned by digest for reproducible builds; bump explicitly when newer manylinux_2_28 images ship. - container: quay.io/pypa/manylinux_2_28_x86_64@sha256:441c35fdc6ee809ff9260894f8468ab4fea8c15dc880f8700a3f81b7922c1cda - variant: standard - - platform: linux - architecture: aarch64 - target-suffix: unknown-linux-gnu - build-on: ubuntu-22.04-arm - # Pinned by digest for reproducible builds; bump explicitly when newer manylinux_2_28 images ship. - container: quay.io/pypa/manylinux_2_28_aarch64@sha256:8b5f2b4e8c072ae5aefeb659f22c03e1ff46e6a82f154b6c904b106c87e65ff7 - variant: standard - - platform: linux - architecture: x86_64 - target-suffix: unknown-linux-musl - build-on: ubuntu-22.04 - variant: musl - - platform: linux - architecture: aarch64 - target-suffix: unknown-linux-musl - build-on: ubuntu-22.04-arm - variant: musl - - platform: linux - architecture: x86_64 - target-suffix: unknown-linux-gnu - build-on: ubuntu-24.04 - variant: vulkan - - platform: linux - architecture: aarch64 - target-suffix: unknown-linux-gnu - build-on: ubuntu-24.04-arm - variant: vulkan - - platform: macos - architecture: x86_64 - target-suffix: apple-darwin - build-on: macos-15-intel - variant: standard - - platform: macos - architecture: aarch64 - target-suffix: apple-darwin - build-on: macos-latest - variant: standard - - platform: windows - architecture: x86_64 - target-suffix: pc-windows-msvc - build-on: windows-latest - variant: standard - - platform: windows - architecture: x86_64 - target-suffix: pc-windows-msvc - build-on: windows-2022 - variant: cuda + target: + - x86_64-apple-darwin + - aarch64-apple-darwin + uses: ./.github/workflows/bundle-macos.yml + permissions: + contents: read + with: + version: ${{ inputs.version }} + ref: ${{ inputs.ref }} + target: ${{ matrix.target }} + signing: false + package_cli: true + package_desktop: false - steps: - - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ inputs.ref }} + build-cli-windows: + strategy: + fail-fast: false + matrix: + windows_variant: + - standard + - cuda + uses: ./.github/workflows/bundle-windows.yml + permissions: + contents: read + with: + version: ${{ inputs.version }} + ref: ${{ inputs.ref }} + signing: false + package_cli: true + package_desktop: false + windows_variant: ${{ matrix.windows_variant }} - - name: Update version in Cargo.toml - if: ${{ inputs.version != '' }} - shell: bash - run: | - sed -i.bak 's/^version = ".*"/version = "'${{ inputs.version }}'"/' Cargo.toml - rm -f Cargo.toml.bak - - - name: Install Linux build dependencies (host runner) - if: matrix.platform == 'linux' && matrix.container == '' - run: | - sudo apt-get update - sudo apt-get install -y \ - build-essential \ - pkg-config \ - libssl-dev \ - libdbus-1-dev \ - libxcb1-dev - - if [ "${{ matrix.variant }}" = "vulkan" ]; then - sudo apt-get install -y \ - libvulkan-dev \ - libvulkan1 \ - glslc - fi - - if [ "${{ matrix.variant }}" = "musl" ]; then - sudo apt-get install -y musl-tools - fi - - - name: Install Linux build dependencies (manylinux container) - if: matrix.platform == 'linux' && matrix.container != '' - run: | - # perl-core provides FindBin, File::Compare, etc. that openssl-sys's - # vendored openssl build needs; in AlmaLinux 8 these aren't standalone packages. - # clang provides libclang.so for bindgen (used by llama-cpp-sys-2). - # Defensive: avoid actions/checkout falling back to a tarball download if base image changes. - dnf install -y --setopt=install_weak_deps=False \ - openssl-devel \ - dbus-devel \ - libxcb-devel \ - cmake \ - perl-core \ - clang \ - git \ - tar - - - name: Cache Cargo artifacts (Linux/macOS) - if: matrix.platform != 'windows' - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - key: ${{ matrix.architecture }}-${{ matrix.target-suffix }}-${{ matrix.build-on }}-${{ matrix.container || 'native' }}-macos-deployment-target-12 - - - name: Cache Cargo artifacts (Windows) - if: matrix.platform == 'windows' - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - key: windows-msvc-cli-${{ matrix.variant }} - - - name: Build CLI (Linux/macOS host runner) - if: matrix.platform != 'windows' && matrix.container == '' - env: - RUST_LOG: debug - RUST_BACKTRACE: 1 - run: | - source ./bin/activate-hermit - export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}" - rustup target add "${TARGET}" - echo "Building for target: ${TARGET}" - echo "Rust toolchain info:" - rustup show - - FEATURE_ARGS=() - if [ "${{ matrix.variant }}" = "vulkan" ]; then - FEATURE_ARGS=(--features vulkan) - fi - - if [ "${{ matrix.variant }}" = "musl" ]; then - cargo build --release --target ${TARGET} -p goose-cli --bin goose \ - --no-default-features \ - --features portable-default - else - cargo build --release --target ${TARGET} -p goose-cli "${FEATURE_ARGS[@]}" - fi - - - name: Build CLI (manylinux container) - if: matrix.platform == 'linux' && matrix.container != '' - env: - RUST_BACKTRACE: 1 - run: | - # Hermit's tool cache is host-runner-scoped; inside the container we - # bootstrap rustup directly and let rust-toolchain.toml pin the channel. - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ - | sh -s -- -y --default-toolchain none --profile minimal --no-modify-path - export PATH="$HOME/.cargo/bin:$PATH" - TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}" - RUST_CHANNEL=$(grep '^channel' rust-toolchain.toml | cut -d'"' -f2) - if [ -z "$RUST_CHANNEL" ]; then - echo "Could not parse channel from rust-toolchain.toml" >&2 - exit 1 - fi - rustup toolchain install "$RUST_CHANNEL" --profile minimal \ - --component rustc,cargo --target "$TARGET" - rustup show - cargo build --release --target "$TARGET" -p goose-cli - - - name: Setup Rust (Windows) - if: matrix.platform == 'windows' - shell: bash - run: | - rustup show - rustup target add x86_64-pc-windows-msvc - - - name: Install CUDA toolkit (Windows CUDA) - if: ${{ matrix.platform == 'windows' && matrix.variant == 'cuda' }} - uses: Jimver/cuda-toolkit@v0.2.35 - with: - cuda: '12.9.1' - method: 'local' - log-file-suffix: 'build-cli-windows-cuda.txt' - - - name: Set up MSVC developer environment (Windows CUDA) - if: ${{ matrix.platform == 'windows' && matrix.variant == 'cuda' }} - uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 - with: - arch: amd64 - - - name: Verify CUDA toolchain (Windows CUDA) - if: ${{ matrix.platform == 'windows' && matrix.variant == 'cuda' }} - shell: pwsh - env: - CUDA_COMPUTE_CAP: "80" - run: | - Write-Output "CUDA_PATH=$env:CUDA_PATH" - Write-Output "CUDA_COMPUTE_CAP=$env:CUDA_COMPUTE_CAP" - where.exe cl - where.exe nvcc - nvcc -V - - - name: Build CLI (Windows) - if: matrix.platform == 'windows' - shell: pwsh - env: - CUDA_COMPUTE_CAP: ${{ matrix.variant == 'cuda' && '80' || '' }} - run: | - Write-Output "Building Windows CLI executable..." - if ("${{ matrix.variant }}" -eq "cuda") { - cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --features cuda - } else { - cargo build --release --target x86_64-pc-windows-msvc -p goose-cli - } - - if (-not (Test-Path "./target/x86_64-pc-windows-msvc/release/goose.exe")) { - Write-Error "Windows CLI binary not found." - Get-ChildItem ./target/x86_64-pc-windows-msvc/release/ -ErrorAction SilentlyContinue - exit 1 - } - - Write-Output "Windows CLI binary found." - Get-Item ./target/x86_64-pc-windows-msvc/release/goose.exe - - - name: Package CLI (Linux/macOS) - if: matrix.platform != 'windows' - run: | - # Hermit isn't installed in the manylinux container; tar is all this step needs. - if [ "${{ matrix.container }}" = '' ]; then - source ./bin/activate-hermit - fi - export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}" - export VARIANT_SUFFIX="" - if [ "${{ matrix.variant }}" = "vulkan" ]; then - VARIANT_SUFFIX="-vulkan" - fi - - # Create a directory for the package contents - mkdir -p "target/${TARGET}/release/goose-package" - - # Copy the goose binary - cp "target/${TARGET}/release/goose" "target/${TARGET}/release/goose-package/" - - cd "target/${TARGET}/release" - tar -cjf "goose-${TARGET}${VARIANT_SUFFIX}.tar.bz2" -C goose-package . - tar -czf "goose-${TARGET}${VARIANT_SUFFIX}.tar.gz" -C goose-package . - echo "ARTIFACT_BZ2=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.tar.bz2" >> $GITHUB_ENV - echo "ARTIFACT_GZ=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.tar.gz" >> $GITHUB_ENV - - - name: Package CLI (Windows) - if: matrix.platform == 'windows' - shell: bash - run: | - export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}" - export VARIANT_SUFFIX="" - if [ "${{ matrix.variant }}" = "cuda" ]; then - VARIANT_SUFFIX="-cuda" - fi - - mkdir -p "target/${TARGET}/release/goose-package" - - cp "target/${TARGET}/release/goose.exe" "target/${TARGET}/release/goose-package/" - - cd "target/${TARGET}/release" - 7z a -tzip "goose-${TARGET}${VARIANT_SUFFIX}.zip" goose-package/ - echo "ARTIFACT_ZIP=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.zip" >> $GITHUB_ENV - - - name: Upload CLI artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: goose-${{ matrix.architecture }}-${{ matrix.target-suffix }}${{ matrix.variant != 'standard' && matrix.variant != 'musl' && format('-{0}', matrix.variant) || '' }} - path: | - ${{ env.ARTIFACT_BZ2 }} - ${{ env.ARTIFACT_GZ }} - ${{ env.ARTIFACT_ZIP }} + build-cli-linux: + uses: ./.github/workflows/build-cli-linux.yml + permissions: + contents: read + with: + version: ${{ inputs.version }} + ref: ${{ inputs.ref }} diff --git a/.github/workflows/bundle-desktop-intel.yml b/.github/workflows/bundle-desktop-intel.yml deleted file mode 100644 index 8cda0961b..000000000 --- a/.github/workflows/bundle-desktop-intel.yml +++ /dev/null @@ -1,203 +0,0 @@ -# This is a **reuseable** workflow that bundles the Desktop App for Intel macOS. -# It doesn't get triggered on its own. It gets used in multiple workflows: -# - release.yml -# - canary.yml -# - pr-comment-bundle-desktop.yml -on: - workflow_call: - inputs: - version: - description: 'Version to set for the build' - required: false - default: "" - type: string - signing: - description: 'Whether to perform signing and notarization' - required: false - default: false - type: boolean - quick_test: - description: 'Whether to perform the quick launch test' - required: false - default: true - type: boolean - ref: - type: string - required: false - default: '' - environment: - description: 'GitHub Environment containing signing secrets (e.g. "production"). Leave empty to skip.' - required: false - type: string - default: '' - -name: Reusable workflow to bundle desktop app for Intel Mac - -jobs: - bundle-desktop-intel: - runs-on: macos-latest - name: Bundle Desktop App on Intel macOS - environment: ${{ inputs.environment || '' }} - env: - MACOSX_DEPLOYMENT_TARGET: "12.0" - permissions: - id-token: write - contents: read - steps: - # Check initial disk space - - name: Check initial disk space - run: df -h - - - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - # Only pass ref if it's explicitly set, otherwise let checkout action use its default behavior - ref: ${{ inputs.ref != '' && inputs.ref || '' }} - - # Update versions before build - - name: Update versions - if: ${{ inputs.version != '' }} - run: | - # Update version in Cargo.toml - sed -i.bak 's/^version = ".*"/version = "'${{ inputs.version }}'"/' Cargo.toml - rm -f Cargo.toml.bak - - # Update version in package.json - source ./bin/activate-hermit - cd ui/desktop - npm pkg set "version=${{ inputs.version }}" - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - key: intel-macos-deployment-target-12 - - - - name: Build desktop backend for Intel macOS (x86_64) - run: | - source ./bin/activate-hermit - rustup target add x86_64-apple-darwin - cargo build --release -p goose-cli --bin goose --target x86_64-apple-darwin - - - - # Post-build cleanup to free space - - name: Post-build cleanup - run: | - echo "Performing post-build cleanup..." - # Remove debug artifacts - rm -rf target/debug || true - rm -rf target/x86_64-apple-darwin/debug || true - # Keep only what's needed for the next steps - rm -rf target/x86_64-apple-darwin/release/deps || true - rm -rf target/x86_64-apple-darwin/release/build || true - rm -rf target/x86_64-apple-darwin/release/incremental || true - # Check disk space after cleanup - df -h - - - name: Copy backend binary into Electron folder - run: | - mkdir -p ui/desktop/src/bin - rm -f ui/desktop/src/bin/goose - cp target/x86_64-apple-darwin/release/goose ui/desktop/src/bin/goose - chmod +x ui/desktop/src/bin/goose - ls -la ui/desktop/src/bin/ - - - name: Cache pnpm dependencies - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: | - ui/desktop/node_modules - .hermit/node/cache - key: intel-pnpm-cache-v1-${{ runner.os }}-${{ hashFiles('ui/pnpm-lock.yaml') }} - restore-keys: | - intel-pnpm-cache-v1-${{ runner.os }}- - - - name: Install dependencies - run: source ../../bin/activate-hermit && pnpm install --frozen-lockfile - working-directory: ui/desktop - - # Configure Electron builder for Intel architecture - - name: Configure for Intel build - run: | - # Set the architecture to x64 for Intel Mac build - jq '.build.mac.target[0].arch = "x64"' package.json > package.json.tmp && mv package.json.tmp package.json - working-directory: ui/desktop - - - name: Import Apple signing certificate - if: ${{ inputs.signing }} - uses: ./.github/actions/apple-codesign - with: - certificate-base64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} - certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - - # Check disk space before bundling - - name: Check disk space before bundling - run: df -h - - - name: Build App - env: - APPLE_ID: ${{ inputs.signing && secrets.APPLE_ID || '' }} - APPLE_ID_PASSWORD: ${{ inputs.signing && secrets.APPLE_ID_PASSWORD || '' }} - APPLE_TEAM_ID: ${{ inputs.signing && secrets.APPLE_TEAM_ID || '' }} - run: | - source ../../bin/activate-hermit - attempt=0 - max_attempts=2 - until [ $attempt -ge $max_attempts ]; do - pnpm run bundle:intel && break - attempt=$((attempt + 1)) - echo "Attempt $attempt failed. Retrying..." - sleep 5 - done - if [ $attempt -ge $max_attempts ]; then - echo "Action failed after $max_attempts attempts." - exit 1 - fi - working-directory: ui/desktop - - - name: Verify macOS updater resources - run: node scripts/verify-mac-update-resources.js "out/Goose-darwin-x64/Goose.app" - working-directory: ui/desktop - - - name: Clean up signing keychain - if: always() - run: | - if [ -n "$KEYCHAIN_PATH" ] && [ -f "$KEYCHAIN_PATH" ]; then - security delete-keychain "$KEYCHAIN_PATH" || true - fi - - - name: Final cleanup before artifact upload - run: | - echo "Performing final cleanup..." - # Remove build artifacts that are no longer needed - rm -rf target || true - # Check disk space after cleanup - df -h - - - name: Upload Desktop artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: Goose-darwin-x64 - path: ui/desktop/out/Goose-darwin-x64/Goose_intel_mac.zip - - - name: Quick launch test (macOS) - if: ${{ inputs.quick_test }} - run: | - # Ensure no quarantine attributes (if needed) - xattr -cr "ui/desktop/out/Goose-darwin-x64/Goose.app" - echo "Opening Goose.app..." - open -g "ui/desktop/out/Goose-darwin-x64/Goose.app" - - # Give the app a few seconds to start and write logs - sleep 5 - - # Check if it's running - if pgrep -f "Goose.app/Contents/MacOS/Goose" > /dev/null; then - echo "App appears to be running." - else - echo "App did not stay open. Possible crash or startup error." - exit 1 - fi - # Kill the app to clean up - pkill -f "Goose.app/Contents/MacOS/Goose" diff --git a/.github/workflows/bundle-desktop-linux.yml b/.github/workflows/bundle-desktop-linux.yml index 7f24f22af..c55c7d944 100644 --- a/.github/workflows/bundle-desktop-linux.yml +++ b/.github/workflows/bundle-desktop-linux.yml @@ -5,11 +5,6 @@ # - pr-comment-bundle-desktop.yml (when added) on: workflow_dispatch: - inputs: - branch: - description: 'Branch name to bundle app from' - required: true - type: string workflow_call: inputs: version: @@ -24,6 +19,9 @@ on: name: "Bundle Desktop (Linux)" +permissions: + contents: read + jobs: build-desktop-linux: name: Build Desktop (Linux, ${{ matrix.variant }}) @@ -41,19 +39,17 @@ jobs: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: - ref: ${{ inputs.ref || inputs.branch }} + ref: ${{ inputs.ref }} - name: Update versions if: ${{ inputs.version != '' }} + env: + VERSION: ${{ inputs.version }} run: | - # Update version in Cargo.toml - sed -i.bak 's/^version = ".*"/version = "'${{ inputs.version }}'"/' Cargo.toml - rm -f Cargo.toml.bak - - # Update version in package.json + bash scripts/set-cargo-version.sh "$VERSION" source ./bin/activate-hermit cd ui/desktop - npm pkg set "version=${{ inputs.version }}" + npm pkg set "version=${VERSION}" - name: Debug workflow info env: diff --git a/.github/workflows/bundle-desktop-manual.yml b/.github/workflows/bundle-desktop-manual.yml deleted file mode 100644 index 84f6d962b..000000000 --- a/.github/workflows/bundle-desktop-manual.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Manual Desktop Bundle (Unsigned) - -on: - workflow_dispatch: - inputs: - branch: - description: 'Branch name to bundle app from' - required: true - type: string - -jobs: - bundle-desktop-unsigned: - uses: ./.github/workflows/bundle-desktop.yml - permissions: - id-token: write - contents: read - with: - signing: false - ref: ${{ inputs.branch }} - - bundle-desktop-intel-unsigned: - uses: ./.github/workflows/bundle-desktop-intel.yml - permissions: - id-token: write - contents: read - with: - signing: false - ref: ${{ inputs.branch }} diff --git a/.github/workflows/bundle-desktop.yml b/.github/workflows/bundle-desktop.yml deleted file mode 100644 index 93940e52b..000000000 --- a/.github/workflows/bundle-desktop.yml +++ /dev/null @@ -1,238 +0,0 @@ -# This is a **reuseable** workflow that bundles the Desktop App for macOS. -# It doesn't get triggered on its own. It gets used in multiple workflows: -# - release.yml -# - canary.yml -# - pr-comment-bundle-desktop.yml -# - bundle-desktop-manual.yml -on: - workflow_call: - inputs: - version: - description: 'Version to set for the build' - required: false - default: "" - type: string - signing: - description: 'Whether to perform signing and notarization' - required: false - default: false - type: boolean - quick_test: - description: 'Whether to perform the quick launch test' - required: false - default: true - type: boolean - ref: - description: 'Git ref to checkout (branch, tag, or SHA). Defaults to main branch if not specified.' - required: false - type: string - default: '' - environment: - description: 'GitHub Environment containing signing secrets (e.g. "signing"). Leave empty to skip.' - required: false - type: string - default: '' - -name: Reusable workflow to bundle desktop app - -jobs: - bundle-desktop: - runs-on: macos-latest - name: Bundle Desktop App on macOS - environment: ${{ inputs.environment || '' }} - env: - MACOSX_DEPLOYMENT_TARGET: "12.0" - permissions: - id-token: write - contents: read - outputs: - artifact-url: ${{ steps.upload-app-bundle.outputs.artifact-url }} - steps: - # Debug information about the workflow and inputs - - name: Debug workflow info - env: - WORKFLOW_NAME: ${{ github.workflow }} - WORKFLOW_REF: ${{ github.ref }} - EVENT_NAME: ${{ github.event_name }} - REPOSITORY: ${{ github.repository }} - INPUT_REF: ${{ inputs.ref }} - INPUT_VERSION: ${{ inputs.version }} - INPUT_SIGNING: ${{ inputs.signing }} - INPUT_QUICK_TEST: ${{ inputs.quick_test }} - run: | - echo "=== Workflow Information ===" - echo "Workflow: ${WORKFLOW_NAME}" - echo "Ref: ${WORKFLOW_REF}" - echo "Event: ${EVENT_NAME}" - echo "Repo: ${REPOSITORY}" - echo "" - echo "=== Input Parameters ===" - echo "Build ref: ${INPUT_REF:-}" - echo "Version: ${INPUT_VERSION:-not set}" - echo "Signing: ${INPUT_SIGNING:-false}" - echo "Quick test: ${INPUT_QUICK_TEST:-true}" - - # Check initial disk space - - name: Check initial disk space - run: df -h - - - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - # Only pass ref if it's explicitly set, otherwise let checkout action use its default behavior - ref: ${{ inputs.ref != '' && inputs.ref || '' }} - - - name: Debug git status - run: | - echo "=== Git Status ===" - git status - echo "" - echo "=== Current Commit ===" - git rev-parse HEAD - git rev-parse --abbrev-ref HEAD - echo "" - echo "=== Recent Commits ===" - git log --oneline -n 5 - echo "" - echo "=== Remote Branches ===" - git branch -r - - # Update versions before build - - name: Update versions - if: ${{ inputs.version != '' }} - env: - VERSION: ${{ inputs.version }} - run: | - # Update version in Cargo.toml - sed -i.bak "s/^version = \".*\"/version = \"${VERSION}\"/" Cargo.toml - rm -f Cargo.toml.bak - - source ./bin/activate-hermit - # Update version in package.json - cd ui/desktop - npm pkg set "version=${VERSION}" - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - key: macos-deployment-target-12 - - # Build the project - - name: Build desktop backend - run: | - source ./bin/activate-hermit - cargo build --release -p goose-cli --bin goose - - # Post-build cleanup to free space - - name: Post-build cleanup - run: | - echo "Performing post-build cleanup..." - # Remove debug artifacts - rm -rf target/debug || true - # Keep only what's needed for the next steps - rm -rf target/release/deps || true - rm -rf target/release/build || true - rm -rf target/release/incremental || true - # Check disk space after cleanup - df -h - - - name: Copy backend binary into Electron folder - run: | - mkdir -p ui/desktop/src/bin - rm -f ui/desktop/src/bin/goose - cp target/release/goose ui/desktop/src/bin/goose - chmod +x ui/desktop/src/bin/goose - ls -la ui/desktop/src/bin/ - - - name: Cache pnpm dependencies - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: | - ui/desktop/node_modules - .hermit/node/cache - key: macos-pnpm-cache-v1-${{ runner.os }}-${{ hashFiles('ui/pnpm-lock.yaml') }} - restore-keys: | - macos-pnpm-cache-v1-${{ runner.os }}- - - - name: Install dependencies - run: source ../../bin/activate-hermit && pnpm install --frozen-lockfile - working-directory: ui/desktop - - - name: Import Apple signing certificate - if: ${{ inputs.signing }} - uses: ./.github/actions/apple-codesign - with: - certificate-base64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} - certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - - # Check disk space before bundling - - name: Check disk space before bundling - run: df -h - - - name: Build App - env: - APPLE_ID: ${{ inputs.signing && secrets.APPLE_ID || '' }} - APPLE_ID_PASSWORD: ${{ inputs.signing && secrets.APPLE_ID_PASSWORD || '' }} - APPLE_TEAM_ID: ${{ inputs.signing && secrets.APPLE_TEAM_ID || '' }} - run: | - source ../../bin/activate-hermit - attempt=0 - max_attempts=2 - until [ $attempt -ge $max_attempts ]; do - pnpm run bundle:default && break - attempt=$((attempt + 1)) - echo "Attempt $attempt failed. Retrying..." - sleep 5 - done - if [ $attempt -ge $max_attempts ]; then - echo "Action failed after $max_attempts attempts." - exit 1 - fi - working-directory: ui/desktop - - - name: Verify macOS updater resources - run: node scripts/verify-mac-update-resources.js "out/Goose-darwin-arm64/Goose.app" - working-directory: ui/desktop - - - name: Clean up signing keychain - if: always() - run: | - if [ -n "$KEYCHAIN_PATH" ] && [ -f "$KEYCHAIN_PATH" ]; then - security delete-keychain "$KEYCHAIN_PATH" || true - fi - - - name: Final cleanup before artifact upload - run: | - echo "Performing final cleanup..." - # Remove build artifacts that are no longer needed - rm -rf target || true - # Check disk space after cleanup - df -h - - - name: Upload Desktop artifact - id: upload-app-bundle - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: Goose-darwin-arm64 - path: ui/desktop/out/Goose-darwin-arm64/Goose.zip - - - name: Quick launch test (macOS) - if: ${{ inputs.quick_test }} - run: | - # Ensure no quarantine attributes (if needed) - xattr -cr "ui/desktop/out/Goose-darwin-arm64/Goose.app" - echo "Opening Goose.app..." - open -g "ui/desktop/out/Goose-darwin-arm64/Goose.app" - - # Give the app a few seconds to start and write logs - sleep 5 - - # Check if it's running - if pgrep -f "Goose.app/Contents/MacOS/Goose" > /dev/null; then - echo "App appears to be running." - else - echo "App did not stay open. Possible crash or startup error." - exit 1 - fi - # Kill the app to clean up - pkill -f "Goose.app/Contents/MacOS/Goose" diff --git a/.github/workflows/bundle-macos.yml b/.github/workflows/bundle-macos.yml new file mode 100644 index 000000000..77b3bc7fb --- /dev/null +++ b/.github/workflows/bundle-macos.yml @@ -0,0 +1,326 @@ +on: + workflow_dispatch: + inputs: + target: + description: 'macOS Rust target' + required: true + type: choice + default: aarch64-apple-darwin + options: + - aarch64-apple-darwin + - x86_64-apple-darwin + signing: + description: 'Whether to perform signing and notarization' + required: false + default: false + type: boolean + package_cli: + description: 'Whether to package and upload the CLI artifact' + required: false + default: false + type: boolean + package_desktop: + description: 'Whether to package and upload the Desktop artifact' + required: false + default: false + type: boolean + workflow_call: + inputs: + version: + description: 'Version to set for the build' + required: false + default: "" + type: string + target: + description: 'macOS Rust target: aarch64-apple-darwin or x86_64-apple-darwin' + required: true + type: string + signing: + description: 'Whether to perform signing and notarization' + required: false + default: false + type: boolean + package_cli: + description: 'Whether to package and upload the CLI artifact' + required: false + default: false + type: boolean + package_desktop: + description: 'Whether to package and upload the Desktop artifact' + required: false + default: false + type: boolean + ref: + description: 'Git ref to checkout (branch, tag, or SHA). Defaults to main branch if not specified.' + required: false + type: string + default: '' + +name: "Bundle CLI and Desktop (macOS)" + +jobs: + build-goose: + name: Build Goose (macOS) + runs-on: ${{ inputs.target == 'x86_64-apple-darwin' && 'macos-15-intel' || 'macos-latest' }} + env: + MACOSX_DEPLOYMENT_TARGET: "12.0" + permissions: + contents: read + steps: + - name: Debug workflow info + env: + WORKFLOW_NAME: ${{ github.workflow }} + WORKFLOW_REF: ${{ github.ref }} + EVENT_NAME: ${{ github.event_name }} + REPOSITORY: ${{ github.repository }} + INPUT_REF: ${{ inputs.ref }} + INPUT_VERSION: ${{ inputs.version }} + INPUT_TARGET: ${{ inputs.target }} + INPUT_SIGNING: ${{ inputs.signing }} + INPUT_PACKAGE_CLI: ${{ inputs.package_cli }} + INPUT_PACKAGE_DESKTOP: ${{ inputs.package_desktop }} + run: | + echo "=== Workflow Information ===" + echo "Workflow: ${WORKFLOW_NAME}" + echo "Ref: ${WORKFLOW_REF}" + echo "Event: ${EVENT_NAME}" + echo "Repo: ${REPOSITORY}" + echo "" + echo "=== Input Parameters ===" + echo "Build ref: ${INPUT_REF:-}" + echo "Version: ${INPUT_VERSION:-not set}" + echo "Target: ${INPUT_TARGET}" + echo "Signing: ${INPUT_SIGNING:-false}" + echo "Package CLI: ${INPUT_PACKAGE_CLI:-false}" + echo "Package Desktop: ${INPUT_PACKAGE_DESKTOP:-false}" + + - name: Check initial disk space + run: df -h + + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ inputs.ref != '' && inputs.ref || '' }} + + - name: Debug git status + run: | + echo "=== Git Status ===" + git status + echo "" + echo "=== Current Commit ===" + git rev-parse HEAD + git rev-parse --abbrev-ref HEAD + echo "" + echo "=== Recent Commits ===" + git log --oneline -n 5 + echo "" + echo "=== Remote Branches ===" + git branch -r + + - name: Update Cargo version + if: ${{ inputs.version != '' }} + env: + VERSION: ${{ inputs.version }} + run: bash scripts/set-cargo-version.sh "$VERSION" + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + with: + key: ${{ inputs.target == 'x86_64-apple-darwin' && 'intel-macos-deployment-target-12' || 'macos-deployment-target-12' }} + + - name: Build goose + env: + TARGET: ${{ inputs.target }} + run: | + source ./bin/activate-hermit + rustup target add "$TARGET" + cargo build --release -p goose-cli --bin goose --target "$TARGET" + + - name: Prepare binary artifact + env: + TARGET: ${{ inputs.target }} + run: | + mkdir -p artifacts + cp "target/${TARGET}/release/goose" "artifacts/internal-goose-${TARGET}" + + - name: Upload binary artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: internal-goose-${{ inputs.target }} + path: artifacts/internal-goose-${{ inputs.target }} + if-no-files-found: error + retention-days: 1 + overwrite: true + + package-cli: + name: Package CLI (macOS) + if: ${{ inputs.package_cli }} + needs: build-goose + runs-on: ubuntu-latest + permissions: {} + steps: + - name: Download binary artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: internal-goose-${{ inputs.target }} + path: package + + - name: Package CLI + env: + TARGET: ${{ inputs.target }} + run: | + mv "package/internal-goose-${TARGET}" package/goose + chmod +x package/goose + mkdir -p dist + tar -cjf "dist/goose-${TARGET}.tar.bz2" -C package . + tar -czf "dist/goose-${TARGET}.tar.gz" -C package . + + - name: Upload CLI artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: goose-${{ inputs.target }} + path: | + dist/goose-${{ inputs.target }}.tar.bz2 + dist/goose-${{ inputs.target }}.tar.gz + if-no-files-found: error + overwrite: true + + package-desktop: + name: Package Desktop (macOS) + if: ${{ inputs.package_desktop }} + needs: build-goose + runs-on: ${{ inputs.target == 'x86_64-apple-darwin' && 'macos-15-intel' || 'macos-latest' }} + environment: ${{ inputs.signing && 'signing' || null }} + env: + MACOSX_DEPLOYMENT_TARGET: "12.0" + permissions: + contents: read + outputs: + artifact-url: ${{ steps.upload-app-bundle.outputs.artifact-url }} + steps: + - name: Check initial disk space + run: df -h + + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ inputs.ref != '' && inputs.ref || '' }} + + - name: Update desktop version + if: ${{ inputs.version != '' }} + env: + VERSION: ${{ inputs.version }} + run: | + source ./bin/activate-hermit + cd ui/desktop + npm pkg set "version=${VERSION}" + + - name: Download binary artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: internal-goose-${{ inputs.target }} + path: ui/desktop/src/bin + + - name: Prepare desktop backend + env: + TARGET: ${{ inputs.target }} + run: | + rm -f ui/desktop/src/bin/goose + mv "ui/desktop/src/bin/internal-goose-${TARGET}" ui/desktop/src/bin/goose + chmod +x ui/desktop/src/bin/goose + ls -la ui/desktop/src/bin/ + + - name: Cache pnpm dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ui/desktop/node_modules + .hermit/node/cache + key: ${{ inputs.target == 'x86_64-apple-darwin' && 'intel-pnpm-cache-v1' || 'macos-pnpm-cache-v1' }}-${{ runner.os }}-${{ hashFiles('ui/pnpm-lock.yaml') }} + restore-keys: | + ${{ inputs.target == 'x86_64-apple-darwin' && 'intel-pnpm-cache-v1' || 'macos-pnpm-cache-v1' }}-${{ runner.os }}- + + - name: Install dependencies + run: source ../../bin/activate-hermit && pnpm install --frozen-lockfile + working-directory: ui/desktop + + - name: Configure for Intel build + if: ${{ inputs.target == 'x86_64-apple-darwin' }} + run: jq '.build.mac.target[0].arch = "x64"' package.json > package.json.tmp && mv package.json.tmp package.json + working-directory: ui/desktop + + - name: Import Apple signing certificate + if: ${{ inputs.signing }} + uses: ./.github/actions/apple-codesign + with: + certificate-base64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} + certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + + - name: Check disk space before bundling + run: df -h + + - name: Build App + env: + APPLE_ID: ${{ inputs.signing && secrets.APPLE_ID || '' }} + APPLE_ID_PASSWORD: ${{ inputs.signing && secrets.APPLE_ID_PASSWORD || '' }} + APPLE_TEAM_ID: ${{ inputs.signing && secrets.APPLE_TEAM_ID || '' }} + BUNDLE_SCRIPT: ${{ inputs.target == 'x86_64-apple-darwin' && 'bundle:intel' || 'bundle:default' }} + run: | + source ../../bin/activate-hermit + attempt=0 + max_attempts=2 + until [ $attempt -ge $max_attempts ]; do + pnpm run "$BUNDLE_SCRIPT" && break + attempt=$((attempt + 1)) + echo "Attempt $attempt failed. Retrying..." + sleep 5 + done + if [ $attempt -ge $max_attempts ]; then + echo "Action failed after $max_attempts attempts." + exit 1 + fi + working-directory: ui/desktop + + - name: Verify macOS updater resources + env: + APP_PATH: ${{ inputs.target == 'x86_64-apple-darwin' && 'out/Goose-darwin-x64/Goose.app' || 'out/Goose-darwin-arm64/Goose.app' }} + run: node scripts/verify-mac-update-resources.js "$APP_PATH" + working-directory: ui/desktop + + - name: Clean up signing keychain + if: always() + run: | + if [ -n "$KEYCHAIN_PATH" ] && [ -f "$KEYCHAIN_PATH" ]; then + security delete-keychain "$KEYCHAIN_PATH" || true + fi + + - name: Final cleanup before artifact upload + run: | + rm -f ui/desktop/src/bin/goose + df -h + + - name: Upload Desktop artifact + id: upload-app-bundle + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ inputs.target == 'x86_64-apple-darwin' && 'Goose-darwin-x64' || 'Goose-darwin-arm64' }} + path: ${{ inputs.target == 'x86_64-apple-darwin' && 'ui/desktop/out/Goose-darwin-x64/Goose_intel_mac.zip' || 'ui/desktop/out/Goose-darwin-arm64/Goose.zip' }} + if-no-files-found: error + overwrite: true + + - name: Quick launch test (macOS) + env: + APP_PATH: ${{ inputs.target == 'x86_64-apple-darwin' && 'ui/desktop/out/Goose-darwin-x64/Goose.app' || 'ui/desktop/out/Goose-darwin-arm64/Goose.app' }} + run: | + xattr -cr "$APP_PATH" + echo "Opening Goose.app..." + open -g "$APP_PATH" + sleep 5 + + if pgrep -f "Goose.app/Contents/MacOS/Goose" > /dev/null; then + echo "App appears to be running." + else + echo "App did not stay open. Possible crash or startup error." + exit 1 + fi + pkill -f "Goose.app/Contents/MacOS/Goose" diff --git a/.github/workflows/bundle-desktop-windows.yml b/.github/workflows/bundle-windows.yml similarity index 61% rename from .github/workflows/bundle-desktop-windows.yml rename to .github/workflows/bundle-windows.yml index a5c53c793..af28f531d 100644 --- a/.github/workflows/bundle-desktop-windows.yml +++ b/.github/workflows/bundle-windows.yml @@ -1,4 +1,4 @@ -name: "Bundle Desktop (Windows)" +name: "Bundle CLI and Desktop (Windows)" on: workflow_dispatch: @@ -8,10 +8,23 @@ on: required: false type: boolean default: false - windows_variant: - description: 'Windows artifact variant to build' + package_cli: + description: 'Whether to package and upload the CLI artifact' required: false - type: string + type: boolean + default: false + package_desktop: + description: 'Whether to package and upload the Desktop artifact' + required: false + type: boolean + default: false + windows_variant: + description: 'Windows artifact variant: standard or cuda' + required: false + type: choice + options: + - standard + - cuda default: 'standard' workflow_call: inputs: @@ -24,52 +37,46 @@ on: required: false type: boolean default: false + package_cli: + description: 'Whether to package and upload the CLI artifact' + required: false + type: boolean + default: false + package_desktop: + description: 'Whether to package and upload the Desktop artifact' + required: false + type: boolean + default: false ref: description: 'Git ref to checkout' required: false type: string default: '' windows_variant: - description: 'Windows artifact variant to build' + description: 'Windows artifact variant: standard or cuda' required: false type: string default: 'standard' -# Permissions required for OIDC authentication with Azure Trusted Signing permissions: - id-token: write # Required to fetch the OIDC token for Azure federated credentials - contents: read # Required by actions/checkout - actions: read # May be needed for some workflows + contents: read jobs: - build-desktop-windows: - name: Build Desktop (Windows) + build-goose-windows: + name: Build Goose (Windows) runs-on: ${{ inputs.windows_variant == 'cuda' && 'windows-2022' || 'windows-latest' }} - steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ inputs.ref != '' && inputs.ref || '' }} - - name: Set up Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 24.10.0 - - - name: Install pnpm - run: npm install -g pnpm@10.30.3 - - - name: Cache node_modules - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: | - node_modules - ui/desktop/node_modules - .hermit/node/cache - key: windows-pnpm-cache-v1-${{ runner.os }}-node24-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - windows-pnpm-cache-v1-${{ runner.os }}-node24- + - name: Update Cargo version + if: ${{ inputs.version != '' }} + shell: bash + env: + VERSION: ${{ inputs.version }} + run: bash scripts/set-cargo-version.sh "$VERSION" - name: Cache Rust dependencies uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 @@ -82,21 +89,21 @@ jobs: rustup show rustup target add x86_64-pc-windows-msvc - - name: Install CUDA toolkit (Windows CUDA) + - name: Install CUDA toolkit if: ${{ inputs.windows_variant == 'cuda' }} - uses: Jimver/cuda-toolkit@v0.2.35 + uses: Jimver/cuda-toolkit@3d45d157f327c09c04b50ee6ccdea2d9d017ec76 # v0.2.35 with: cuda: '12.9.1' method: 'local' - log-file-suffix: 'bundle-desktop-windows-cuda.txt' + log-file-suffix: 'bundle-windows-cuda.txt' - - name: Set up MSVC developer environment (Windows CUDA) + - name: Set up MSVC developer environment if: ${{ inputs.windows_variant == 'cuda' }} uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 with: arch: amd64 - - name: Verify CUDA toolchain (Windows CUDA) + - name: Verify CUDA toolchain if: ${{ inputs.windows_variant == 'cuda' }} shell: pwsh env: @@ -115,43 +122,111 @@ jobs: run: | $isCuda = "${{ inputs.windows_variant }}" -eq "cuda" - Write-Output "Building Windows ACP backend" if ($isCuda) { cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --bin goose --features cuda } else { cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --bin goose } - $binaryPath = "./target/x86_64-pc-windows-msvc/release/goose.exe" - # Verify build succeeded + $binaryPath = "./target/x86_64-pc-windows-msvc/release/goose.exe" if (-not (Test-Path $binaryPath)) { - Write-Error "Windows backend binary not found: $binaryPath" + Write-Error "Windows binary not found: $binaryPath" Get-ChildItem ./target/x86_64-pc-windows-msvc/release/ -ErrorAction SilentlyContinue exit 1 } - - Write-Output "Windows backend binary found." Get-Item $binaryPath + - name: Upload binary artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: internal-goose-x86_64-pc-windows-msvc${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} + path: target/x86_64-pc-windows-msvc/release/goose.exe + if-no-files-found: error + retention-days: 1 + overwrite: true + + package-cli-windows: + name: Package CLI (Windows) + if: ${{ inputs.package_cli }} + needs: build-goose-windows + runs-on: ${{ inputs.windows_variant == 'cuda' && 'windows-2022' || 'windows-latest' }} + steps: + - name: Download binary artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: internal-goose-x86_64-pc-windows-msvc${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} + path: package + + - name: Package CLI + shell: bash + env: + VARIANT_SUFFIX: ${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} + run: | + mkdir -p goose-package + cp package/goose.exe goose-package/ + 7z a -tzip "goose-x86_64-pc-windows-msvc${VARIANT_SUFFIX}.zip" goose-package/ + + - name: Upload CLI artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: goose-x86_64-pc-windows-msvc${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} + path: goose-x86_64-pc-windows-msvc${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}.zip + if-no-files-found: error + overwrite: true + + build-desktop-windows: + name: Build Desktop (Windows) + if: ${{ inputs.package_desktop }} + needs: build-goose-windows + runs-on: ${{ inputs.windows_variant == 'cuda' && 'windows-2022' || 'windows-latest' }} + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ inputs.ref != '' && inputs.ref || '' }} + + - name: Set up Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24.10.0 + + - name: Update desktop version + if: ${{ inputs.version != '' }} + shell: bash + env: + VERSION: ${{ inputs.version }} + run: | + cd ui/desktop + npm pkg set "version=${VERSION}" + + - name: Install pnpm + run: npm install -g pnpm@10.30.3 + + - name: Cache node_modules + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + node_modules + ui/desktop/node_modules + .hermit/node/cache + key: windows-pnpm-cache-v1-${{ runner.os }}-node24-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + windows-pnpm-cache-v1-${{ runner.os }}-node24- + + - name: Download binary artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: internal-goose-x86_64-pc-windows-msvc${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} + path: backend + - name: Prepare Windows binary shell: bash run: | - BACKEND_BINARY="./target/x86_64-pc-windows-msvc/release/goose.exe" - - if [ ! -f "$BACKEND_BINARY" ]; then - echo "Windows backend binary not found: $BACKEND_BINARY" - exit 1 - fi - - echo "Cleaning destination directory..." rm -rf ./ui/desktop/src/bin mkdir -p ./ui/desktop/src/bin - - echo "Copying Windows backend binary..." - cp -f "$BACKEND_BINARY" ./ui/desktop/src/bin/ + cp -f ./backend/goose.exe ./ui/desktop/src/bin/ if [ -d "./ui/desktop/src/platform/windows/bin" ]; then - echo "Copying Windows platform files..." for file in ./ui/desktop/src/platform/windows/bin/*.{exe,dll,cmd}; do filename="$(basename "$file")" if [ -f "$file" ] && [ "$filename" != "goose.exe" ]; then @@ -160,10 +235,8 @@ jobs: done if [ -d "./ui/desktop/src/platform/windows/bin/goose-npm" ]; then - echo "Setting up npm environment..." cp -r ./ui/desktop/src/platform/windows/bin/goose-npm/ ./ui/desktop/src/bin/goose-npm/ fi - echo "Windows-specific files copied successfully" fi - name: Force GitHub HTTPS for npm git dependencies @@ -179,13 +252,12 @@ jobs: ELECTRON_PLATFORM: win32 run: | cd ui/desktop - pnpm install --frozen-lockfile node scripts/build-main.js node scripts/prepare-platform-binaries.js pnpm run make --platform=win32 --arch=x64 - - name: Copy exe to final out folder and prepare flat distribution + - name: Prepare flat distribution shell: bash run: | cd ui/desktop @@ -195,29 +267,31 @@ jobs: mkdir -p ./dist-windows cp -r ./out/Goose-win32-x64/* ./dist-windows/ - echo "📋 Final flat distribution structure:" ls -la ./dist-windows/ - echo "📋 Binary files in resources/bin:" ls -la ./dist-windows/resources/bin/ - name: Upload unsigned distribution uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} + name: internal-windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} path: ui/desktop/dist-windows/ + if-no-files-found: error + retention-days: 1 + overwrite: true sign-desktop-windows: name: Sign Desktop (Windows) needs: build-desktop-windows - if: inputs.signing + if: ${{ inputs.package_desktop && inputs.signing }} runs-on: windows-latest environment: ${{ inputs.signing && 'signing' || null }} - + permissions: + id-token: write steps: - name: Download unsigned distribution uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} + name: internal-windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} path: dist-windows - name: Azure login @@ -253,44 +327,40 @@ jobs: - name: Create Windows zip package shell: bash - run: | - ZIP_NAME="Goose-win32-x64" - if [ "${{ inputs.windows_variant }}" = "cuda" ]; then - ZIP_NAME="${ZIP_NAME}-cuda" - fi - 7z a -tzip "${ZIP_NAME}.zip" dist-windows/ + env: + VARIANT_SUFFIX: ${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} + run: 7z a -tzip "Goose-win32-x64${VARIANT_SUFFIX}.zip" dist-windows/ - name: Upload signed Windows build uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} path: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}.zip + if-no-files-found: error + overwrite: true - # When signing is disabled, package the unsigned build directly package-desktop-windows: name: Package Desktop (Windows) needs: build-desktop-windows - if: ${{ !inputs.signing }} + if: ${{ inputs.package_desktop && !inputs.signing }} runs-on: windows-latest - steps: - name: Download unsigned distribution uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} + name: internal-windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} path: dist-windows - name: Create Windows zip package shell: bash - run: | - ZIP_NAME="Goose-win32-x64" - if [ "${{ inputs.windows_variant }}" = "cuda" ]; then - ZIP_NAME="${ZIP_NAME}-cuda" - fi - 7z a -tzip "${ZIP_NAME}.zip" dist-windows/ + env: + VARIANT_SUFFIX: ${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} + run: 7z a -tzip "Goose-win32-x64${VARIANT_SUFFIX}.zip" dist-windows/ - name: Upload Windows build uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }} path: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}.zip + if-no-files-found: error + overwrite: true diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index f93599880..3ad2e8af1 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -15,12 +15,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -# Permissions for SLSA attestation, AWS OIDC codesigning, and release creation permissions: - id-token: write # Required for Sigstore OIDC signing and AWS OIDC codesigning - contents: write # Required for creating releases and by actions/checkout - actions: read # Required by bundle-desktop-windows.yml reusable workflow - attestations: write # Required for SLSA build provenance attestations + contents: read jobs: # ------------------------------------ @@ -44,11 +40,11 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT # ------------------------------------ - # 2) Build CLI for multiple OS/Arch + # 2) Build CLI targets not produced by desktop workflows # ------------------------------------ - build-cli: + build-cli-linux: needs: [prepare-version] - uses: ./.github/workflows/build-cli.yml + uses: ./.github/workflows/build-cli-linux.yml with: version: ${{ needs.prepare-version.outputs.version }} @@ -58,7 +54,6 @@ jobs: install-script: name: Upload Install Script runs-on: ubuntu-latest - needs: [build-cli] steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -67,29 +62,33 @@ jobs: path: download_cli.sh # ------------------------------------------------------------ - # 4) Bundle Desktop App (macOS only) + # 4) Bundle CLI and Desktop (macOS ARM64) # ------------------------------------------------------------ - bundle-desktop: + bundle-macos-arm64: needs: [prepare-version] - uses: ./.github/workflows/bundle-desktop.yml + uses: ./.github/workflows/bundle-macos.yml permissions: - id-token: write contents: read with: version: ${{ needs.prepare-version.outputs.version }} + target: aarch64-apple-darwin + package_cli: true + package_desktop: true signing: false # ------------------------------------------------------------ - # 5) Bundle Desktop App (macOS Intel) + # 5) Bundle CLI and Desktop (macOS x64) # ------------------------------------------------------------ - bundle-desktop-intel: + bundle-macos-x64: needs: [prepare-version] - uses: ./.github/workflows/bundle-desktop-intel.yml + uses: ./.github/workflows/bundle-macos.yml permissions: - id-token: write contents: read with: version: ${{ needs.prepare-version.outputs.version }} + target: x86_64-apple-darwin + package_cli: true + package_desktop: true signing: false # ------------------------------------------------------------ @@ -102,39 +101,44 @@ jobs: version: ${{ needs.prepare-version.outputs.version }} # ------------------------------------------------------------ - # 6) Bundle Desktop App (Windows) + # 7) Bundle CLI and Desktop (Windows) # ------------------------------------------------------------ - bundle-desktop-windows: + bundle-windows: needs: [prepare-version] - uses: ./.github/workflows/bundle-desktop-windows.yml + uses: ./.github/workflows/bundle-windows.yml with: version: ${{ needs.prepare-version.outputs.version }} + package_cli: true + package_desktop: true signing: false - bundle-desktop-windows-cuda: + bundle-windows-cuda: needs: [prepare-version] - uses: ./.github/workflows/bundle-desktop-windows.yml + uses: ./.github/workflows/bundle-windows.yml with: version: ${{ needs.prepare-version.outputs.version }} + package_cli: true + package_desktop: true signing: false windows_variant: cuda # ------------------------------------ - # 7) Create/Update GitHub Release + # 8) Create/Update GitHub Release # ------------------------------------ release: name: Release runs-on: ubuntu-latest - needs: [build-cli, install-script, bundle-desktop, bundle-desktop-intel, bundle-desktop-linux, bundle-desktop-windows, bundle-desktop-windows-cuda] + needs: [build-cli-linux, install-script, bundle-macos-arm64, bundle-macos-x64, bundle-desktop-linux, bundle-windows, bundle-windows-cuda] permissions: contents: write id-token: write # Required for Sigstore OIDC signing attestations: write # Required for SLSA build provenance attestations steps: - - name: Download all artifacts + - name: Download release artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: + pattern: '!internal-*' merge-multiple: true - name: Attest build provenance diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 913909c56..4eecb324d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,8 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 + with: + cache: false - name: Run cargo fmt run: cargo fmt --check @@ -62,9 +64,6 @@ jobs: sudo apt update -y sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev - - name: Cache Cargo artifacts - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - name: Build and Test run: | gnome-keyring-daemon --components=secrets --daemonize --unlock <<< 'foobar' @@ -166,10 +165,6 @@ jobs: sudo apt update -y sudo apt install -y libdbus-1-dev libxcb1-dev - - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - key: msrv - - name: Check with MSRV toolchain run: cargo check --workspace --locked --all-targets env: @@ -185,8 +180,6 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 - - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - name: Lint run: | source ./bin/activate-hermit @@ -212,9 +205,6 @@ jobs: sudo apt update -y sudo apt install -y libdbus-1-dev libxcb1-dev - - name: Cache Cargo artifacts - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - name: Install Node.js Dependencies run: | source ./bin/activate-hermit @@ -242,19 +232,6 @@ jobs: - name: Checkout Code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - # Temporarily disabled due to GitHub Actions bug on macOS runners - # https://github.com/actions/runner-images/issues/13341 - # https://github.com/actions/runner/issues/4134 - # - name: Cache pnpm dependencies - # uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 - # with: - # path: | - # ui/desktop/node_modules - # .hermit/node/cache - # key: ci-pnpm-cache-v1-${{ runner.os }}-${{ hashFiles('ui/pnpm-lock.yaml') }} - # restore-keys: | - # ci-pnpm-cache-v1-${{ runner.os }}- - - name: Install Dependencies run: source ../../bin/activate-hermit && pnpm install --frozen-lockfile working-directory: ui/desktop diff --git a/.github/workflows/pr-comment-build-cli.yml b/.github/workflows/pr-comment-build-cli.yml deleted file mode 100644 index 70aaa24cc..000000000 --- a/.github/workflows/pr-comment-build-cli.yml +++ /dev/null @@ -1,151 +0,0 @@ -# This workflow is triggered by a comment on PR with the text ".build-cli" -# -# SECURITY: This workflow checks out and builds code from PRs. To prevent -# malicious code execution (GHSA-4h72-4h3w-4587, GHSA-mqm8-hhf6-wvjq), -# we verify the commenter has write access before proceeding. -on: - issue_comment: - types: [created] - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to comment on' - required: true - type: string - -# permissions needed for reacting to IssueOps commands on PRs -permissions: - pull-requests: write - checks: read - -name: Build CLI - -concurrency: - group: ${{ github.workflow }}-${{ (github.event.issue && github.event.issue.number) || github.event.inputs.pr_number }} - cancel-in-progress: true - -jobs: - trigger-on-command: - if: > - github.event_name == 'workflow_dispatch' || - (github.event.issue.pull_request && contains(github.event.comment.body, '.build-cli')) - name: Trigger on ".build-cli" PR comment - runs-on: ubuntu-latest - outputs: - continue: ${{ steps.security_check.outputs.authorized }} - pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }} - head_sha: ${{ steps.set_head_sha.outputs.head_sha || github.sha }} - steps: - # SECURITY: Verify commenter has write access BEFORE any checkout - # This prevents attackers from triggering builds on their own malicious PRs - - name: Verify commenter permissions - id: security_check - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - // workflow_dispatch requires repo write access, so it's inherently safe - if (context.eventName === 'workflow_dispatch') { - core.setOutput('authorized', 'true'); - console.log('✅ workflow_dispatch - authorized'); - return; - } - - const commenter = context.payload.comment.user.login; - console.log(`Checking permissions for: ${commenter}`); - - try { - const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: commenter - }); - - const allowed = ['admin', 'maintain', 'write'].includes(permission.permission); - console.log(`Permission level: ${permission.permission}, Authorized: ${allowed}`); - - if (!allowed) { - // Post a comment explaining the rejection - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.issue.number, - body: `âš ī¸ @${commenter} Only repository collaborators with write access can trigger builds.` - }); - core.setOutput('authorized', 'false'); - } else { - core.setOutput('authorized', 'true'); - } - } catch (error) { - console.log(`Permission check failed: ${error.message}`); - core.setOutput('authorized', 'false'); - } - - - name: Run command action - if: steps.security_check.outputs.authorized == 'true' && github.event_name == 'issue_comment' - uses: github/command@v2.0.3 - id: command - with: - command: ".build-cli" - skip_reviews: true - reaction: "eyes" - allowed_contexts: pull_request - - - name: Checkout code - if: steps.security_check.outputs.authorized == 'true' - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Get PR head SHA with gh - id: set_head_sha - if: steps.security_check.outputs.authorized == 'true' - run: | - echo "Get PR head SHA with gh" - HEAD_SHA=$(gh pr view "$ISSUE_NUMBER" --json headRefOid -q .headRefOid) - echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT - echo "head_sha=$HEAD_SHA" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ISSUE_NUMBER: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }} - - build-cli: - needs: [trigger-on-command] - if: ${{ needs.trigger-on-command.outputs.continue == 'true' }} - uses: ./.github/workflows/build-cli.yml - with: - ref: ${{ needs.trigger-on-command.outputs.head_sha }} - - pr-comment-cli: - name: PR Comment with CLI builds - runs-on: ubuntu-latest - needs: [trigger-on-command, build-cli] - permissions: - pull-requests: write - - steps: - - name: Download CLI artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - pattern: goose-* - path: cli-dist - merge-multiple: true - - - name: Comment on PR with CLI download links - uses: peter-evans/create-or-update-comment@v5 - with: - issue-number: ${{ needs.trigger-on-command.outputs.pr_number }} - body: | - ### CLI Builds - - Download CLI builds for different platforms: - - [đŸ“Ļ Linux (x86_64, Ubuntu 22.04-compatible)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-unknown-linux-gnu.zip) - - [đŸ“Ļ Linux (aarch64, Ubuntu 22.04-compatible)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-aarch64-unknown-linux-gnu.zip) - - [đŸ“Ļ Linux musl (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-unknown-linux-musl.zip) - - [đŸ“Ļ Linux musl (aarch64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-aarch64-unknown-linux-musl.zip) - - [đŸ“Ļ Linux Vulkan (x86_64, Ubuntu 24.04+)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-unknown-linux-gnu-vulkan.zip) - - [đŸ“Ļ Linux Vulkan (aarch64, Ubuntu 24.04+)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-aarch64-unknown-linux-gnu-vulkan.zip) - - [đŸ“Ļ macOS (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-apple-darwin.zip) - - [đŸ“Ļ macOS (aarch64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-aarch64-apple-darwin.zip) - - [đŸ“Ļ Windows (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-pc-windows-msvc.zip) - - [đŸ“Ļ Windows CUDA (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-pc-windows-msvc-cuda.zip) - - These links are provided by nightly.link and will work even if you're not logged into GitHub. - diff --git a/.github/workflows/pr-comment-bundle-intel.yml b/.github/workflows/pr-comment-bundle-intel.yml deleted file mode 100644 index 6b8fba33c..000000000 --- a/.github/workflows/pr-comment-bundle-intel.yml +++ /dev/null @@ -1,115 +0,0 @@ -# This workflow is triggered by a comment on PR with the text ".bundle-intel" -# It bundles the Intel Desktop App, then creates a PR comment with a link to download the app. - -on: - issue_comment: - types: [created] - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to comment on' - required: true - type: string - -# permissions needed for reacting to IssueOps commands on PRs -permissions: - pull-requests: write - checks: read - -name: Bundle Intel Desktop App - -concurrency: - group: ${{ github.workflow }}-${{ (github.event.issue && github.event.issue.number) || github.event.inputs.pr_number }} - cancel-in-progress: true - -jobs: - trigger-on-command: - if: > - github.event_name == 'workflow_dispatch' || - (github.event.issue.pull_request && contains(github.event.comment.body, '.bundle-intel')) - name: Trigger on ".bundle-intel" PR comment - runs-on: ubuntu-latest - outputs: - continue: 'true' - # Cannot use github.event.pull_request.number since the trigger is 'issue_comment' - pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }} - head_sha: ${{ steps.set_head_sha.outputs.head_sha || github.sha }} - steps: - - name: Run command action - uses: github/command@3442f3fa1efe01bdb024b157083c337902d17372 # v2.0.3 - id: command - with: - command: ".bundle-intel" - skip_reviews: true - reaction: "eyes" - allowed_contexts: pull_request - - - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Get PR head SHA with gh - id: set_head_sha - run: | - echo "Get PR head SHA with gh" - HEAD_SHA=$(gh pr view "$ISSUE_NUMBER" --json headRefOid -q .headRefOid) - echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT - echo "head_sha=$HEAD_SHA" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ISSUE_NUMBER: ${{ steps.command.outputs.issue_number }} - - bundle-desktop-intel: - # Only run this if ".bundle-intel" command is detected. - needs: [trigger-on-command] - if: ${{ needs.trigger-on-command.outputs.continue == 'true' }} - uses: ./.github/workflows/bundle-desktop-intel.yml - permissions: - id-token: write - contents: read - with: - signing: false - ref: ${{ needs.trigger-on-command.outputs.head_sha }} - - pr-comment-intel: - name: PR Comment with macOS Intel App - runs-on: ubuntu-latest - needs: [trigger-on-command, bundle-desktop-intel] - permissions: - pull-requests: write - - steps: - - name: Download Intel artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: Goose-darwin-x64 - path: intel-dist - - - name: Comment on PR with Intel download link - uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 - with: - issue-number: ${{ needs.trigger-on-command.outputs.pr_number }} - body: | - ### macOS Intel Desktop App (x64) - - [đŸ’ģ Download macOS Desktop App (Intel x64, unsigned)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-darwin-x64.zip) - - **Instructions:** - - The easiest way is to just run the following script: - - `./scripts/pre-release.sh` - - script which will download the latest release (or you can specify the release you need), does the - unzip, xattr to get it out of quarantine and signs it. - - If you need to do this manually: - - * Download the file - * Unzip - * run `xattr -r -d com.apple.quarantine '/path/to/Goose.app'` - * optionally run `codesign --force --deep --sign - --entitlements ui/desktop/entitlements.plist '/path/to/Goose.app'` - * start the app - - The signing step is only needed if you do something that uses mac entitlements like speech to text - - This link is provided by nightly.link and will work even if you're not logged into GitHub. diff --git a/.github/workflows/pr-comment-bundle-windows.yml b/.github/workflows/pr-comment-bundle-windows.yml deleted file mode 100644 index b6a784005..000000000 --- a/.github/workflows/pr-comment-bundle-windows.yml +++ /dev/null @@ -1,100 +0,0 @@ -# This workflow is triggered by a comment on PR with the text ".bundle-windows" -# It bundles the Windows Desktop App, then creates a PR comment with a link to download the app. - -on: - issue_comment: - types: [created] - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to comment on' - required: true - type: string - -# permissions needed for reacting to IssueOps commands on PRs and AWS OIDC authentication -permissions: - pull-requests: write - checks: read - id-token: write # Required for AWS OIDC authentication in called workflow - contents: read # Required by actions/checkout in called workflow - actions: read # May be needed for some workflows - -name: Bundle Windows Desktop App - -concurrency: - group: ${{ github.workflow }}-${{ (github.event.issue && github.event.issue.number) || github.event.inputs.pr_number }} - cancel-in-progress: true - -jobs: - trigger-on-command: - if: > - github.event_name == 'workflow_dispatch' || - (github.event.issue.pull_request && contains(github.event.comment.body, '.bundle-windows')) - name: Trigger on ".bundle-windows" PR comment - runs-on: ubuntu-latest - outputs: - continue: 'true' - # Cannot use github.event.pull_request.number since the trigger is 'issue_comment' - pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }} - head_sha: ${{ steps.set_head_sha.outputs.head_sha || github.sha }} - steps: - - name: Run command action - uses: github/command@3442f3fa1efe01bdb024b157083c337902d17372 # v2.0.3 - id: command - with: - command: ".bundle-windows" - skip_reviews: true - reaction: "eyes" - allowed_contexts: pull_request - - - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Get PR head SHA with gh - id: set_head_sha - run: | - echo "Get PR head SHA with gh" - HEAD_SHA=$(gh pr view "$ISSUE_NUMBER" --json headRefOid -q .headRefOid) - echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT - echo "head_sha=$HEAD_SHA" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ISSUE_NUMBER: ${{ steps.command.outputs.issue_number }} - - bundle-desktop-windows: - # Only run this if ".bundle-windows" command is detected. - needs: [trigger-on-command] - if: ${{ needs.trigger-on-command.outputs.continue == 'true' }} - uses: ./.github/workflows/bundle-desktop-windows.yml - with: - signing: false - ref: ${{ needs.trigger-on-command.outputs.head_sha }} - - - pr-comment-windows: - name: PR Comment with Windows App - runs-on: ubuntu-latest - needs: [trigger-on-command, bundle-desktop-windows] - permissions: - pull-requests: write - - steps: - - name: Download Windows artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: Goose-win32-x64 - path: windows-dist - - - name: Comment on PR with Windows download link - uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 - with: - issue-number: ${{ needs.trigger-on-command.outputs.pr_number }} - body: | - ### Windows Desktop App - - [đŸĒŸ Download Windows Desktop App (x64, signed)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-win32-x64.zip) - - **Instructions:** - After downloading, unzip the file and run Goose.exe. The app is signed for Windows. - - This link is provided by nightly.link and will work even if you're not logged into GitHub. diff --git a/.github/workflows/pr-comment-bundle.yml b/.github/workflows/pr-comment-bundle.yml deleted file mode 100644 index 14096d4a2..000000000 --- a/.github/workflows/pr-comment-bundle.yml +++ /dev/null @@ -1,206 +0,0 @@ -# This workflow is triggered by a comment on PR with the text ".bundle" -# It bundles the ARM64 Desktop App, then creates a PR comment with a link to download the app. -# -# SECURITY: This workflow checks out and builds code from PRs. To prevent -# malicious code execution (GHSA-4h72-4h3w-4587), we verify the commenter -# has write access before proceeding. -on: - issue_comment: - types: [created] - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to comment on' - required: true - type: string - -# permissions needed for reacting to IssueOps commands on PRs -permissions: - pull-requests: write - checks: read - -name: Bundle ARM64 Desktop App - -concurrency: - group: ${{ github.workflow }}-${{ (github.event.issue && github.event.issue.number) || github.event.inputs.pr_number }} - cancel-in-progress: true - -jobs: - trigger-on-command: - if: > - github.event_name == 'workflow_dispatch' || - (github.event.issue.pull_request && contains(github.event.comment.body, '.bundle')) - name: Trigger on ".bundle" PR comment - runs-on: ubuntu-latest - outputs: - continue: ${{ steps.security_check.outputs.authorized }} - pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }} - pr_sha: ${{ steps.get_pr_info.outputs.sha }} - steps: - # SECURITY: Verify commenter has write access BEFORE any checkout - # This prevents attackers from triggering builds on their own malicious PRs - - name: Verify commenter permissions - id: security_check - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - // workflow_dispatch requires repo write access, so it's inherently safe - if (context.eventName === 'workflow_dispatch') { - core.setOutput('authorized', 'true'); - console.log('✅ workflow_dispatch - authorized'); - return; - } - - const commenter = context.payload.comment.user.login; - console.log(`Checking permissions for: ${commenter}`); - - try { - const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: commenter - }); - - const allowed = ['admin', 'maintain', 'write'].includes(permission.permission); - console.log(`Permission level: ${permission.permission}, Authorized: ${allowed}`); - - if (!allowed) { - // Post a comment explaining the rejection - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.issue.number, - body: `âš ī¸ @${commenter} Only repository collaborators with write access can trigger builds.` - }); - core.setOutput('authorized', 'false'); - } else { - core.setOutput('authorized', 'true'); - } - } catch (error) { - console.log(`Permission check failed: ${error.message}`); - core.setOutput('authorized', 'false'); - } - - - name: Debug workflow trigger - if: steps.security_check.outputs.authorized == 'true' - env: - WORKFLOW_NAME: ${{ github.workflow }} - WORKFLOW_REF: ${{ github.ref }} - EVENT_NAME: ${{ github.event_name }} - EVENT_ACTION: ${{ github.event.action }} - ACTOR: ${{ github.actor }} - REPOSITORY: ${{ github.repository }} - run: | - echo "=== Workflow Trigger Info ===" - echo "Workflow: ${WORKFLOW_NAME}" - echo "Ref: ${WORKFLOW_REF}" - echo "Event: ${EVENT_NAME}" - echo "Action: ${EVENT_ACTION}" - echo "Actor: ${ACTOR}" - echo "Repository: ${REPOSITORY}" - - - name: Run command action - if: steps.security_check.outputs.authorized == 'true' - uses: github/command@3442f3fa1efe01bdb024b157083c337902d17372 # v2.0.3 - id: command - with: - command: ".bundle" - skip_reviews: true - reaction: "eyes" - allowed_contexts: pull_request - - # Get the PR's SHA - - name: Get PR info - id: get_pr_info - if: steps.security_check.outputs.authorized == 'true' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - let prNumber; - - if (context.eventName === 'workflow_dispatch') { - prNumber = context.payload.inputs.pr_number; - } else { - prNumber = context.payload.issue.number; - } - - if (!prNumber) { - throw new Error('No PR number found'); - } - - console.log('Using PR number:', prNumber); - - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: parseInt(prNumber, 10) - }); - - console.log('PR Details:', { - number: pr.number, - head: { - ref: pr.head.ref, - sha: pr.head.sha, - label: pr.head.label - }, - base: { - ref: pr.base.ref, - sha: pr.base.sha, - label: pr.base.label - } - }); - - core.setOutput('sha', pr.head.sha); - - bundle-desktop: - needs: [trigger-on-command] - if: ${{ needs.trigger-on-command.outputs.continue == 'true' }} - uses: ./.github/workflows/bundle-desktop.yml - permissions: - id-token: write - contents: read - with: - signing: false - ref: ${{ needs.trigger-on-command.outputs.pr_sha }} - - pr-comment-arm64: - name: PR Comment with macOS ARM64 App - runs-on: ubuntu-latest - needs: [trigger-on-command, bundle-desktop] - permissions: - pull-requests: write - - steps: - - name: Download ARM64 artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: Goose-darwin-arm64 - path: arm64-dist - - - name: Comment on PR with ARM64 download link - uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 - with: - issue-number: ${{ needs.trigger-on-command.outputs.pr_number }} - body: | - ### macOS ARM64 Desktop App (Apple Silicon) - - [📱 Download macOS Desktop App (arm64, unsigned)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-darwin-arm64.zip) - - **Instructions:** - - The easiest way is to just run the following script: - - `./scripts/pre-release.sh` - - script which will download the latest release (or you can specify the release you need), does the - unzip, xattr to get it out of quarantine and signs it. - - If you need to do this manually: - - * Download the file - * Unzip - * run `xattr -r -d com.apple.quarantine '/path/to/Goose.app'` - * optionally run `codesign --force --deep --sign - --entitlements ui/desktop/entitlements.plist '/path/to/Goose.app'` - * start the app - - The signing step is only needed if you do something that uses mac entitlements like speech to text diff --git a/.github/workflows/pr-smoke-test.yml b/.github/workflows/pr-smoke-test.yml index 7c89f6677..666c49000 100644 --- a/.github/workflows/pr-smoke-test.yml +++ b/.github/workflows/pr-smoke-test.yml @@ -62,9 +62,6 @@ jobs: sudo apt update -y sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - name: Build Binary for Smoke Tests run: | cargo build --bin goose diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 1653b1dc3..f0c6ce0e8 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -7,9 +7,7 @@ on: concurrency: ${{ github.workflow }}-${{ github.ref }} permissions: - contents: write - pull-requests: write - id-token: write # Required for npm trusted publishing (OIDC) + contents: read jobs: build-cli: @@ -39,6 +37,7 @@ jobs: - name: Download freshly-built goose CLI binaries uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: + pattern: goose-* path: /tmp/cli-artifacts - name: Place binaries into goose-binary packages @@ -146,6 +145,8 @@ jobs: runs-on: ubuntu-latest needs: [build] environment: npm-production-publishing + permissions: + id-token: write # Required for npm trusted publishing (OIDC) steps: - name: Download built packages uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/workflows/python-sdk-wheels.yml b/.github/workflows/python-sdk-wheels.yml index d01d4008c..ffa7070c6 100644 --- a/.github/workflows/python-sdk-wheels.yml +++ b/.github/workflows/python-sdk-wheels.yml @@ -62,9 +62,6 @@ jobs: if: runner.os != 'Linux' uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 - - name: Cache Cargo artifacts - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - name: Build wheel shell: bash run: just --justfile crates/goose-sdk/justfile python-wheel diff --git a/.github/workflows/release-branches.yml b/.github/workflows/release-branches.yml index d2a63dbf4..74721d9ba 100644 --- a/.github/workflows/release-branches.yml +++ b/.github/workflows/release-branches.yml @@ -6,10 +6,13 @@ on: jobs: bundle-desktop: if: startsWith(github.head_ref, 'release/') - uses: ./.github/workflows/bundle-desktop.yml + uses: ./.github/workflows/bundle-macos.yml permissions: - id-token: write contents: read + with: + target: aarch64-apple-darwin + package_cli: false + package_desktop: true comment-on-pr: needs: bundle-desktop diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5e4c140bd..a6352e188 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,11 +11,7 @@ on: name: Release permissions: - id-token: write # Required for Sigstore OIDC signing and AWS OIDC (Windows signing) - contents: write # Required for creating releases and by actions/checkout - actions: read # May be needed for some workflows - pull-requests: write # Required for npm publish workflow - attestations: write # Required for SLSA build provenance attestations + contents: read env: # Set this repository Actions variable to "true" in GitHub Settings > Secrets and variables @@ -28,10 +24,10 @@ concurrency: jobs: # ------------------------------------ - # 1) Build CLI for multiple OS/Arch + # 1) Build CLI targets not produced by desktop workflows # ------------------------------------ - build-cli: - uses: ./.github/workflows/build-cli.yml + build-cli-linux: + uses: ./.github/workflows/build-cli-linux.yml # ------------------------------------ # 2) Upload Install CLI Script @@ -39,7 +35,6 @@ jobs: install-script: name: Upload Install Script runs-on: ubuntu-latest - needs: [build-cli] steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -48,30 +43,30 @@ jobs: path: download_cli.sh # ------------------------------------------------------------ - # 3) Bundle Desktop App (macOS) + # 3) Bundle CLI and Desktop (macOS ARM64) # ------------------------------------------------------------ - bundle-desktop: - uses: ./.github/workflows/bundle-desktop.yml + bundle-macos-arm64: + uses: ./.github/workflows/bundle-macos.yml permissions: - id-token: write contents: read with: + target: aarch64-apple-darwin + package_cli: true + package_desktop: true signing: ${{ startsWith(github.ref, 'refs/tags/') }} - environment: ${{ startsWith(github.ref, 'refs/tags/') && 'signing' || '' }} - secrets: inherit # ------------------------------------------------------------ - # 4) Bundle Desktop App (macOS) + # 4) Bundle CLI and Desktop (macOS x64) # ------------------------------------------------------------ - bundle-desktop-intel: - uses: ./.github/workflows/bundle-desktop-intel.yml + bundle-macos-x64: + uses: ./.github/workflows/bundle-macos.yml permissions: - id-token: write contents: read with: + target: x86_64-apple-darwin + package_cli: true + package_desktop: true signing: ${{ startsWith(github.ref, 'refs/tags/') }} - environment: ${{ startsWith(github.ref, 'refs/tags/') && 'signing' || '' }} - secrets: inherit # ------------------------------------------------------------ # 5) Bundle Desktop App (Linux) @@ -79,29 +74,29 @@ jobs: bundle-desktop-linux: uses: ./.github/workflows/bundle-desktop-linux.yml -# # ------------------------------------------------------------ -# # 6) Bundle Desktop App (Windows) -# # ------------------------------------------------------------ - bundle-desktop-windows: - uses: ./.github/workflows/bundle-desktop-windows.yml + # ------------------------------------------------------------ + # 6) Bundle CLI and Desktop (Windows) + # ------------------------------------------------------------ + bundle-windows: + uses: ./.github/workflows/bundle-windows.yml permissions: id-token: write contents: read - actions: read with: + package_cli: true + package_desktop: true signing: ${{ startsWith(github.ref, 'refs/tags/') }} - secrets: inherit - bundle-desktop-windows-cuda: - uses: ./.github/workflows/bundle-desktop-windows.yml + bundle-windows-cuda: + uses: ./.github/workflows/bundle-windows.yml permissions: id-token: write contents: read - actions: read with: + package_cli: true + package_desktop: true signing: ${{ startsWith(github.ref, 'refs/tags/') }} windows_variant: cuda - secrets: inherit # ------------------------------------ # 7) Create/Update GitHub Release @@ -110,17 +105,20 @@ jobs: name: Release if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest - needs: [build-cli, install-script, bundle-desktop, bundle-desktop-intel, bundle-desktop-linux, bundle-desktop-windows, bundle-desktop-windows-cuda] + needs: [build-cli-linux, install-script, bundle-macos-arm64, bundle-macos-x64, bundle-desktop-linux, bundle-windows, bundle-windows-cuda] permissions: contents: write id-token: write # Required for Sigstore OIDC signing attestations: write # Required for SLSA build provenance attestations steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - - name: Download all artifacts + - name: Download release artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: + pattern: '!internal-*' merge-multiple: true - name: Generate macOS update manifest diff --git a/.github/workflows/test-finder.yml b/.github/workflows/test-finder.yml deleted file mode 100644 index c00476d77..000000000 --- a/.github/workflows/test-finder.yml +++ /dev/null @@ -1,209 +0,0 @@ -name: Daily Test Coverage Finder - -on: - # schedule: - # # Run daily at 2 AM UTC - PAUSED - # - cron: '0 2 * * *' - workflow_dispatch: - inputs: - dry_run: - description: 'Dry run (no PR creation)' - required: false - default: false - type: boolean - -permissions: - contents: write - pull-requests: write - -jobs: - find-untested-code: - runs-on: ubuntu-latest - container: - image: ghcr.io/aaif-goose/goose:latest - options: --user root - env: - GOOSE_PROVIDER: ${{ vars.GOOSE_PROVIDER || 'openai' }} - GOOSE_MODEL: ${{ vars.GOOSE_MODEL || 'gpt-5' }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - HOME: /tmp/goose-home - - steps: - - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - fetch-depth: 0 - - - name: Install build and analysis tools - run: | - apt-get update - apt-get install -y jq ripgrep build-essential - - - name: Find untested code and create working test - id: find_untested - run: | - # Ensure the HOME directory structure exists - mkdir -p $HOME/.local/share/goose/sessions - mkdir -p $HOME/.config/goose - - # Create analysis and test creation script - cat << 'EOF' > /tmp/create_working_test.txt - Your task is to find ONE untested function in the Rust codebase and create a working test for it. - - Requirements: - 1. The function MUST be in the crates/ directory - 2. It MUST have actual logic (not just a simple getter/setter) - 3. It MUST not already have a test - 4. Prefer functions with complexity but that are still testable in isolation - 5. Focus on the goose crate first, then goose-cli, then others - - Process: - 0. Immediately write these requirements in your TODO list tool and periodically check against them - 1. Find a suitable untested function (use your `analyze` tool and ripgrep) - 2. Write a comprehensive unit test for it - 3. Apply your changes to the codebase - 4. Run the test with: cargo test --test - 5. If the test fails: - - Read and understand the error message - - Fix the test code - - Apply the fix and run the test again - - Repeat up to 3 times until the test passes - 6. Once the test passes, run `cargo fmt` to format all the code properly - 7. After formatting, save the final changes as a git diff to /tmp/test_addition.patch - 8. If successful, write the name of the function you tested to /tmp/function_tested.txt (just the function name, e.g., "check_tool_call" or "MyStruct::my_method") - 9. Only create the patch file if the test actually passes - - Important: - - Only add ONE test for ONE function - - The test MUST compile and pass before creating the patch - - Keep changes minimal and focused - - Include a descriptive test name that explains what is being tested - EOF - - goose run -i /tmp/create_working_test.txt --with-builtin developer - - # Debug: Check what files were created - echo "Checking for patch file..." - if [ -f /tmp/test_addition.patch ]; then - echo "Patch file exists" - echo "Patch size: $(wc -c < /tmp/test_addition.patch) bytes" - echo "First few lines of patch:" - head -5 /tmp/test_addition.patch || true - else - echo "No patch file found at /tmp/test_addition.patch" - fi - - # Check for new commits that Goose might have made - COMMITS_AHEAD=$(git rev-list HEAD --not --remotes=origin --count 2>/dev/null || echo "0") - echo "Commits ahead of origin: $COMMITS_AHEAD" - - # Check if we have changes to create a PR from - # Either: 1) A patch file exists, OR 2) There are new commits - if [ -f /tmp/test_addition.patch ] && [ -s /tmp/test_addition.patch ] || [ "$COMMITS_AHEAD" -gt 0 ]; then - echo "Changes detected (patch file or new commits)" - echo "Attempting to apply patch..." - # Apply the patch for the PR - git apply /tmp/test_addition.patch 2>/dev/null || echo "Patch already applied or changes already present" - echo "patch_created=true" >> $GITHUB_OUTPUT - echo "Test creation successful - patch file created" - - # Try to get the function name from Goose's output file - if [ -f /tmp/function_tested.txt ]; then - FUNC_NAME=$(cat /tmp/function_tested.txt | head -1) - else - # Fallback: Extract from test name in the actual changes (staged or committed) - # Try staged changes first, then last commit, then patch file - FUNC_NAME=$(git diff --cached | grep "^+.*fn test_" | head -1 | sed 's/.*fn test_//' | sed 's/(.*//' || git diff HEAD~1 | grep "^+.*fn test_" | head -1 | sed 's/.*fn test_//' | sed 's/(.*//' || grep "fn test_" /tmp/test_addition.patch 2>/dev/null | head -1 | sed 's/.*fn test_//' | sed 's/(.*//' || echo "function") - fi - # Clean up the function name (remove any trailing whitespace or special chars) - FUNC_NAME=$(echo "$FUNC_NAME" | tr -d '\n\r' | sed 's/[[:space:]]*$//') - echo "function_name=${FUNC_NAME}" >> $GITHUB_OUTPUT - else - echo "patch_created=false" >> $GITHUB_OUTPUT - echo "No patch file created - either no suitable function found or test failed" - fi - - - name: Extract token metrics - id: metrics - run: | - # Find the most recently created session file in the goose sessions directory - SESSION_DIR="$HOME/.local/share/goose/sessions" - if [ -d "$SESSION_DIR" ]; then - SESSION_FILE=$(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1) - if [ -f "$SESSION_FILE" ]; then - echo "Found session file: $SESSION_FILE" - # Current context size metrics - TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.total_tokens // 0') - INPUT_TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.input_tokens // 0') - OUTPUT_TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.output_tokens // 0') - echo "total_tokens=${TOKENS}" >> $GITHUB_OUTPUT - echo "input_tokens=${INPUT_TOKENS}" >> $GITHUB_OUTPUT - echo "output_tokens=${OUTPUT_TOKENS}" >> $GITHUB_OUTPUT - # Accumulated API usage metrics - ACC_TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.accumulated_total_tokens // 0') - ACC_INPUT_TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.accumulated_input_tokens // 0') - ACC_OUTPUT_TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.accumulated_output_tokens // 0') - echo "accumulated_total_tokens=${ACC_TOKENS}" >> $GITHUB_OUTPUT - echo "accumulated_input_tokens=${ACC_INPUT_TOKENS}" >> $GITHUB_OUTPUT - echo "accumulated_output_tokens=${ACC_OUTPUT_TOKENS}" >> $GITHUB_OUTPUT - echo "Token usage - Total: ${TOKENS}, Input: ${INPUT_TOKENS}, Output: ${OUTPUT_TOKENS}" - echo "Accumulated usage - Total: ${ACC_TOKENS}, Input: ${ACC_INPUT_TOKENS}, Output: ${ACC_OUTPUT_TOKENS}" - fi - fi - - - name: Create Pull Request - if: steps.find_untested.outputs.patch_created == 'true' && github.event.inputs.dry_run != 'true' - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "test: add test for ${{ steps.find_untested.outputs.function_name }}" - title: "test: add test coverage for ${{ steps.find_untested.outputs.function_name }}" - draft: true - body: | - ## 🤖 Automated Test Addition - - This PR was automatically generated by goose to improve test coverage. - - ### What changed? - Added a unit test for a previously untested function. - - ### Why? - Part of our daily automated test coverage improvement initiative. goose analyzes the codebase to find untested but important functions and creates focused unit tests for them. - - ### Review checklist - - [ ] Test is meaningful and actually tests the function - - [ ] Test name is descriptive - - [ ] Test passes locally - - [ ] No unnecessary changes included - - ### Metrics - #### Current Context Size - - **Total**: ${{ steps.metrics.outputs.total_tokens }} tokens - - **Input**: ${{ steps.metrics.outputs.input_tokens }} tokens - - **Output**: ${{ steps.metrics.outputs.output_tokens }} tokens - #### API Usage (Billable) - - **Total**: ${{ steps.metrics.outputs.accumulated_total_tokens }} tokens - - **Input**: ${{ steps.metrics.outputs.accumulated_input_tokens }} tokens - - **Output**: ${{ steps.metrics.outputs.accumulated_output_tokens }} tokens - - --- - *Generated by the Daily Test Coverage Finder workflow* - branch: goose/test-coverage-${{ github.run_number }} - delete-branch: true - labels: | - goose-generated - test - automated - - - name: Summary - if: always() - env: - PATCH_CREATED: ${{ steps.find_untested.outputs.patch_created }} - FUNCTION_NAME: ${{ steps.find_untested.outputs.function_name }} - run: | - if [ "$PATCH_CREATED" = "true" ]; then - echo "✅ Successfully found untested code and created a test" - echo "📝 Function tested: $FUNCTION_NAME" - else - echo "â„šī¸ No suitable untested code found today" - fi diff --git a/.github/workflows/update-hacktoberfest-leaderboard.yml b/.github/workflows/update-hacktoberfest-leaderboard.yml deleted file mode 100644 index 8c9989a79..000000000 --- a/.github/workflows/update-hacktoberfest-leaderboard.yml +++ /dev/null @@ -1,255 +0,0 @@ -name: Update Hacktoberfest Leaderboard - -on: - schedule: - # Runs every hour at the start of the hour during October (UTC) - - cron: '0 * * 10 *' - workflow_dispatch: - -jobs: - update-leaderboard: - runs-on: ubuntu-latest - if: github.repository == 'aaif-goose/goose' - permissions: - contents: read - pull-requests: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Update Leaderboard - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const issueNumber = 4775; // Update if needed - - // Track the Goose repo only - const REPOS = [ - 'aaif-goose/goose' - ]; - - const POINT_VALUES = { small: 5, medium: 10, large: 15 }; - - - const calculatePoints = (labels) => { - const size = labels.find(label => POINT_VALUES[label.name.toLowerCase()]); - return size ? POINT_VALUES[size.name.toLowerCase()] : POINT_VALUES.small; - }; - - const getBiggestPRSize = (prs) => { - const sizes = prs.map(pr => { - const sizeLabel = pr.labels.find(label => POINT_VALUES[label.name.toLowerCase()]); - return sizeLabel ? sizeLabel.name.toLowerCase() : 'small'; - }); - if (sizes.includes('large')) return 'large'; - if (sizes.includes('medium')) return 'medium'; - return 'small'; - }; - - // Strict October window (UTC) - const now = new Date(); - const year = now.getUTCFullYear(); - const startDate = new Date(Date.UTC(year, 9, 1, 0, 0, 0)); // Oct 1, 00:00:00 UTC - const endDate = new Date(Date.UTC(year, 9, 31, 23, 59, 59)); // Oct 31, 23:59:59 UTC - - const fetchRecentPRs = async (fullRepo) => { - try { - console.log(`Fetching recent PRs for ${fullRepo}`); - const [repoOwner, repoName] = fullRepo.split('/'); - - let allPRs = []; - let page = 1; - - // Paginate through closed PRs sorted by updated desc - while (true) { - const { data: prs } = await github.rest.pulls.list({ - owner: repoOwner, - repo: repoName, - state: 'closed', - sort: 'updated', - direction: 'desc', - per_page: 100, - page - }); - - allPRs = allPRs.concat(prs); - - if (prs.length < 100) break; - - // Early exit if the oldest updated item on this page precedes October - const oldestUpdated = new Date(prs[prs.length - 1].updated_at); - if (oldestUpdated < startDate) break; - - page++; - } - - console.log(`Fetched ${allPRs.length} PRs for ${fullRepo}`); - - // Strict: must be merged in October AND have label exactly "hacktoberfest" - const hacktoberfestPRs = allPRs - .filter(pr => { - if (!pr.merged_at) return false; - const mergedAt = new Date(pr.merged_at); - const inOctober = mergedAt >= startDate && mergedAt <= endDate; - const isHacktoberfest = pr.labels.some( - label => label.name.toLowerCase() === 'hacktoberfest' - ); - return inOctober && isHacktoberfest; - }) - .map(pr => ({ - user: pr.user.login, - points: calculatePoints(pr.labels), - repo: fullRepo, - prNumber: pr.number, - prTitle: pr.title, - labels: pr.labels - })); - - return hacktoberfestPRs; - } catch (error) { - console.warn(`Error fetching PRs for ${fullRepo}: ${error.message}`); - return []; - } - }; - - const generateLeaderboard = async () => { - try { - const allPRs = (await Promise.all(REPOS.map(fetchRecentPRs))).flat(); - - const leaderboard = allPRs.reduce((acc, pr) => { - if (!acc[pr.user]) { - acc[pr.user] = { points: 0, prs: 0, userPRs: [] }; - } - acc[pr.user].points += pr.points; - acc[pr.user].prs += 1; - acc[pr.user].userPRs.push(pr); - return acc; - }, {}); - - // Tie-breaker rank for biggest PR size - const sizeOrder = { small: 1, medium: 2, large: 3 }; - - const sorted = Object.entries(leaderboard) - .sort(([, a], [, b]) => { - if (b.points !== a.points) return b.points - a.points; - const aBig = getBiggestPRSize(a.userPRs); - const bBig = getBiggestPRSize(b.userPRs); - return sizeOrder[bBig] - sizeOrder[aBig]; - }) - .map(([username, data], index) => ({ - rank: index + 1, - username, - points: data.points, - prs: data.prs, - biggestPR: getBiggestPRSize(data.userPRs) - })); - - return sorted; - } catch (error) { - console.error(`Error generating leaderboard: ${error.message}`); - return []; - } - }; - - const updateIssue = async (leaderboardData) => { - const rankStars = (rank) => (rank <= 3 ? '⭐⭐⭐' : rank <= 10 ? '⭐' : ''); - - const tableRows = leaderboardData - .map(entry => `| ${entry.rank} ${rankStars(entry.rank)} | @${entry.username} | ${entry.points} | ${entry.prs} | ${entry.biggestPR} |`) - .join('\n'); - - const issueBody = `# 🏆 Hacktoberfest 2025 Goose Leaderboard 🏆 - Hello, lovely contributors! As Hacktoberfest 2025 and the crisp Fall breeze refreshes us, we wanted to make the contribution process extra fun. Check our live leaderboard below to see who our top contributors are this year in real-time. Not only does this recognize your efforts, it also brings an amplified competitive vibe with each contribution. - - ### 🌟 **Current Rankings:** - - | Rank | Contributor | Points | PRs | Biggest PR to Date | - |------|-------------|--------|-----|--------------------| - ${tableRows} - - ### 📜 How It Works: - The top 20 contributors will earn the first ever goose swag from the swag shop along with LLM credits! To earn your place in the leaderboard, we have created a points system that is explained below. As you complete a task by successfully merging a PR, you will automatically be granted a certain # of points. - - #### đŸ’¯ Point System - | Weight | Points Awarded | Description | - |---------|-------------|-------------| - | 🐭 **Small** | 5 points | For smaller tasks that take limited time to complete and/or don't require any product knowledge. | - | 🐰 **Medium** | 10 points | For average tasks that take additional time to complete and/or require some product knowledge. | - | 🐂 **Large** | 15 points | For heavy tasks that takes lots of time to complete and/or possibly require deep product knowledge. | - - #### 🎁 Rewards - - Made it to the **top 5**? Our Top 5 Contributors with the most points will be awarded $100 gift cards to our brand new goose swag shop and $100 of LLM credits! - - Reached the top **6-10**? Our Top 6-10 Contributors with the most points will be awarded $50 gift cards to our brand new goose swag shop and $50 of LLM credits! - - Landed in the top **11-20**? Our Top 11-20 Contributors with the most points will be awarded $25 of LLM credits! Keep an eye on your progress to make sure you're one step ahead! - - ### FAQ - - **Frequency of Updates:** The leaderboard will be updated every hour. - - **Criteria:** Rankings are based on how many points you earn across all approved PRs in the goose repo. To ensure your PRs are successfully merged: - - Ensure your contributions are aligned with our project's Code of Conduct. - - Refer to the goose repo's Contributing Guide. - - **Tie-Breakers:** In the event of a tie in total points, the contributor with the highest value single contribution (large > medium > small) will be ranked higher. - - ### 🚀 Get Featured: - Want to see your name climbing our ranks? - - Explore our issues with the labels \`good-first-issue\`, \`no-code\` and \`hacktoberfest\` in the goose repo' Project Hub: - - - **goose** - - Hacktoberfest Project Hub - - Contributing Guide - - Excited to see everyone's hard work. Thank you so much for your invaluable contributions, and let the fun competition begin! - - Last updated: ${new Date().toUTCString()}`; - - try { - await github.rest.issues.update({ - owner, - repo, - issue_number: issueNumber, - body: issueBody - }); - console.log("Issue updated successfully!"); - } catch (err) { - console.error(`Failed to update issue #${issueNumber}: ${err.message}`); - throw err; - } - }; - - // Main execution - const leaderboardData = await generateLeaderboard(); - if (leaderboardData.length > 0) { - await updateIssue(leaderboardData); - } else { - console.log("No leaderboard data to update."); - const emptyIssueBody = `# 🏆 Hacktoberfest 2025 Goose Leaderboard 🏆 - - Hello, lovely contributors! As Hacktoberfest 2025 and the crisp Fall breeze refreshes us, we wanted to make the contribution process extra fun. Check our live leaderboard below to see who our top contributors are this year in real-time. Not only does this recognize everyone's efforts, it also brings an amplified competitive vibe with each contribution. - - ### 🌟 **Current Rankings:** - - | Rank | Contributor | Points | PRs | Biggest PR to Date | - |------|-------------|--------|-----|--------------------| - | | | | | | - - No qualifying PRs found at this time. Check back soon! - - Last updated: ${new Date().toUTCString()}`; - - try { - await github.rest.issues.update({ - owner, - repo, - issue_number: issueNumber, - body: emptyIssueBody - }); - console.log("Updated issue with empty leaderboard message."); - } catch (err) { - console.error(`Failed to update issue #${issueNumber} (empty state): ${err.message}`); - throw err; - } - } diff --git a/Justfile b/Justfile index be971a398..63772d3d7 100644 --- a/Justfile +++ b/Justfile @@ -25,7 +25,7 @@ release-binary: # Build Windows executable on a Windows host [unix] release-windows: - @echo "just release-windows requires a Windows host because Goose Windows releases build the MSVC target. Use .github/workflows/bundle-desktop-windows.yml for CI builds." + @echo "just release-windows requires a Windows host because Goose Windows releases build the MSVC target. Use .github/workflows/bundle-windows.yml for CI builds." @exit 1 [windows] @@ -197,7 +197,7 @@ make-ui: # make GUI with latest Windows binary on a Windows host [unix] make-ui-windows: - @echo "just make-ui-windows requires a Windows host because Goose Windows releases build the MSVC target. Use .github/workflows/bundle-desktop-windows.yml for CI builds." + @echo "just make-ui-windows requires a Windows host because Goose Windows releases build the MSVC target. Use .github/workflows/bundle-windows.yml for CI builds." @exit 1 [windows] diff --git a/scripts/set-cargo-version.sh b/scripts/set-cargo-version.sh new file mode 100755 index 000000000..1ab3fb153 --- /dev/null +++ b/scripts/set-cargo-version.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +version=${1:?Version is required} + +sed -i.bak "s/^version = \".*\"/version = \"${version}\"/" Cargo.toml +rm -f Cargo.toml.bak