name: Bootstrap Script Testing

on:
  push:
    branches: [master, develop, xap]
    paths:
    - 'util/env-bootstrap.sh'
    - '.github/workflows/bootstrap_testing.yml'
  pull_request:
    paths:
    - 'util/env-bootstrap.sh'
    - '.github/workflows/bootstrap_testing.yml'
  workflow_dispatch:

permissions:
  contents: read

jobs:
  prep:
    runs-on: ubuntu-latest

    outputs:
      any_changed: ${{ steps.file_changes.outputs.any_changed }}

    steps:
    - name: Get changed files
      id: file_changes
      if: ${{ github.event_name == 'pull_request' }}
      uses: tj-actions/changed-files@v47
      with:
        use_rest_api: true
        files: |
          util/env-bootstrap.sh
          .github/workflows/bootstrap_testing.yml

  bootstrap-test-linux:
    name: Bootstrap (Linux)

    needs: prep
    if: ${{ github.event_name != 'pull_request' || needs.prep.outputs.any_changed == 'true' }}

    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        distribution:
          # Ubuntu/Debian based
          - debian:11
          - debian:12
          - debian:13
          - ubuntu:20.04
          - ubuntu:22.04
          - ubuntu:24.04

          # RHEL/CentOS/Fedora based
          - fedora:41
          - fedora:42
          - fedora:43
          - rockylinux:8
          - rockylinux:9
          - rockylinux/rockylinux:10
          - almalinux:8
          - almalinux:9
          - almalinux:10

          # OpenSUSE based (we skip Tumbleweed as it has issues with package versions between pattern installs and other dependencies preinstalled into the base container)
          - opensuse/leap:latest

          # Gentoo-based
          - gentoo/stage3:latest

          # Arch based
          - archlinux:latest
          - cachyos/cachyos:latest
          - manjarolinux/base:latest

    container:
      image: ${{ matrix.distribution }}
      options: --privileged

    steps:
      - name: Install base dependencies
        run: |
          # Attempt to run the package installation up to 10 times to mitigate transient network issues
          for n in $(seq 1 10); do
            {
              echo "Attempt #$n of 10 to install base dependencies:"
              case "${{ matrix.distribution }}" in
                *ubuntu*|*debian*)
                  apt-get update
                  apt-get install -y sudo git passwd
                  ;;
                *fedora*|*rockylinux*|*almalinux*)
                  dnf install -y sudo git passwd findutils  # findutils=xargs
                  ;;
                *suse*)
                  zypper --non-interactive refresh
                  zypper --non-interactive install sudo git shadow findutils  # findutils=xargs
                  ;;
                *gentoo*)
                  emerge-webrsync
                  emerge --noreplace --ask=n sudo dev-vcs/git shadow findutils  # findutils=xargs
                  ;;
                *archlinux*|*cachyos*|*manjaro*)
                  pacman -Syu --noconfirm
                  pacman -S --noconfirm sudo git
                  ;;
              esac
            } && break || sleep 10
          done

          # Fix PAM configuration for sudo in containers
          # Fix /etc/shadow permissions - common issue in container environments
          chmod 640 /etc/shadow || chmod 400 /etc/shadow || true

          # Disable problematic PAM modules that commonly fail in RHEL-like containers
          sed -i 's/^session.*pam_systemd.so/#&/' /etc/pam.d/sudo || true
          sed -i 's/^session.*pam_loginuid.so/#&/' /etc/pam.d/sudo || true

          # Ensure proper sudoers configuration
          echo 'Defaults !requiretty' >> /etc/sudoers
          echo 'Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"' >> /etc/sudoers

      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 1
          submodules: recursive
          path: qmk_firmware

      - name: Create test user
        run: |
          # Create a test user for the bootstrap script
          useradd -m -s /bin/bash -U testuser
          echo 'testuser:testpassword' | chpasswd || true
          
          # Configure passwordless sudo
          echo "root ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers  # some distros complain about root not being in sudoers
          echo "testuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
          
          # Test sudo functionality
          sudo -u testuser whoami || echo "Sudo test failed, but continuing..."

      - name: Move QMK repository to test user home
        run: |
          # Add upstream remote to the cloned repository so `qmk doctor` doesn't flag a warning
          git -C qmk_firmware remote add upstream https://github.com/qmk/qmk_firmware.git
          # Move the QMK repository to the test user's home directory
          mv qmk_firmware /home/testuser/qmk_firmware
          chown -R testuser:testuser /home/testuser/qmk_firmware

      - name: Run bootstrap script
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Ensure the bootstrap script can access sudo
          sudo -u testuser --preserve-env=GITHUB_TOKEN bash -c "
            export CONFIRM=1
            export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
            cd /home/testuser
            bash /home/testuser/qmk_firmware/util/env-bootstrap.sh
          "

      - name: Install dependencies
        run: |
          sudo -u testuser bash -c "
            /home/testuser/.local/share/uv/tools/qmk/bin/python -m pip install -r /home/testuser/qmk_firmware/requirements.txt
          "

      - name: Test QMK CLI
        run: |
          sudo -u testuser bash -c "
            export PATH=/home/testuser/.local/bin:\$PATH
            cd /home/testuser
            qmk setup -y -H /home/testuser/qmk_firmware  # setup implies doctor, no need to run it separately
            cd /home/testuser/qmk_firmware
            qmk mass-compile -j $(nproc) -e DUMP_CI_METADATA=yes -f 'keyboard_name==*onekey*' -km reset -p || touch .failed  # Compile a bunch of different platforms
          "
          
          cd /home/testuser/qmk_firmware
          ./util/ci/generate_failure_markdown.sh > $GITHUB_STEP_SUMMARY || true
          [ ! -e .failed ] || exit 1

  bootstrap-test-macos:
    name: Bootstrap (macOS)

    needs: prep
    if: ${{ github.event_name != 'pull_request' || needs.prep.outputs.any_changed == 'true' }}

    strategy:
      fail-fast: false
      matrix:
        os:
          - macos-14        # Apple Silicon ARM64
          - macos-15        # Apple Silicon ARM64
          - macos-15-intel  # Intel x64
          - macos-26        # Apple Silicon ARM64

    runs-on: ${{ matrix.os }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 1
          submodules: recursive

      - name: Run bootstrap script
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Add upstream remote to the cloned repository so `qmk doctor` doesn't flag a warning
          git remote add upstream https://github.com/qmk/qmk_firmware.git
          # Run the bootstrap script
          export CONFIRM=1
          sh ./util/env-bootstrap.sh

      - name: Install dependencies
        run: |
          $HOME/.local/share/uv/tools/qmk/bin/python -m pip install -r requirements.txt

      - name: Test QMK CLI
        run: |
          # Add QMK CLI to PATH (bootstrap script installs it to ~/.local/bin on macOS)
          export PATH="$HOME/.local/bin:$PATH"
          qmk setup -y -H .  # setup implies doctor, no need to run it separately
          qmk mass-compile -j $(sysctl -n hw.ncpu) -e DUMP_CI_METADATA=yes -f 'keyboard_name==*onekey*' -km reset || touch .failed  # Compile a bunch of different platforms
          
          ./util/ci/generate_failure_markdown.sh > $GITHUB_STEP_SUMMARY || true
          [ ! -e .failed ] || exit 1

  bootstrap-test-windows:
    name: Bootstrap (Windows)

    needs: prep
    if: ${{ github.event_name != 'pull_request' || needs.prep.outputs.any_changed == 'true' }}

    strategy:
      fail-fast: false
      matrix:
        msys-variant:
          - mingw64
          - clang64
          - ucrt64

    runs-on: windows-latest
    defaults:
      run:
        shell: msys2 {0}

    steps:
      - name: Install MSYS2
        uses: msys2/setup-msys2@v2
        with:
          msystem: ${{ matrix.msys-variant }}
          pacboy: >-
            git:

      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 1
          submodules: recursive

      - name: Run bootstrap script
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Add upstream remote to the cloned repository so `qmk doctor` doesn't flag a warning
          git remote add upstream https://github.com/qmk/qmk_firmware.git
          # Run the bootstrap script
          export CONFIRM=1
          sh ./util/env-bootstrap.sh

      - name: Install dependencies
        run: |
          /opt/uv/tools/qmk/Scripts/python -m pip install -r requirements.txt

      - name: Test QMK CLI
        run: |
          # Add QMK CLI to PATH (bootstrap script installs it to /opt/uv/tools/bin on Windows MSYS2)
          export PATH="/opt/uv/tools/bin:$PATH"
          qmk setup -y -H .  # setup implies doctor, no need to run it separately
          qmk mass-compile -j $(nproc) -e DUMP_CI_METADATA=yes -f 'keyboard_name==*onekey*' -km reset || touch .failed  # Compile a bunch of different platforms
          
          ./util/ci/generate_failure_markdown.sh > $GITHUB_STEP_SUMMARY || true
          [ ! -e .failed ] || exit 1

