name: Nix Flake Update on: schedule: - cron: '0 0 * * *' # Run daily at midnight UTC workflow_dispatch: # Allow manual trigger env: BRANCH_NAME: auto-update-flake-${{ github.run_number }} HYDRA_INSTANCE: https://hydra.zoeys.computer HYDRA_PROJECT: config HYDRA_JOBSET: pr-${{ github.run_number }} jobs: check-existing-pr: runs-on: ubuntu-latest outputs: pr_exists: ${{ steps.check-pr.outputs.pr_exists }} steps: - name: Check for existing PR id: check-pr env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | existing_pr=$(gh pr list --repo ${{ github.repository }} --head "auto-update-flake-" --state open --json number --jq length) echo "pr_exists=$existing_pr" >> $GITHUB_OUTPUT update-flake: needs: check-existing-pr if: needs.check-existing-pr.outputs.pr_exists == '0' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 with: fetch-depth: 0 # Fetch all history for all branches and tags - name: Install Nix uses: cachix/install-nix-action@v30 - name: Update flake dependencies id: update-flake run: | git config user.name github-actions git config user.email github-actions@github.com nix flake update --accept-flake-config git diff if [[ -n $(git status -s) ]]; then echo "CHANGED=true" >> $GITHUB_OUTPUT echo "Changes detected:" git status -s else echo "CHANGED=false" >> $GITHUB_OUTPUT echo "No changes detected." fi - name: Create branch and commit changes if: steps.update-flake.outputs.CHANGED == 'true' run: | git checkout -b ${{ env.BRANCH_NAME }} git add . git commit -m "chore: update flake dependencies" git push origin ${{ env.BRANCH_NAME }} - name: Create Pull Request if: steps.update-flake.outputs.CHANGED == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh pr create --title "Auto-update Nix flake dependencies" \ --body "This PR updates the Nix flake dependencies." \ --base main \ --head ${{ env.BRANCH_NAME }} - name: Create Hydra jobset if: steps.update-flake.outputs.CHANGED == 'true' run: | AUTH_HEADER="Authorization: Basic $(echo -n '${{ secrets.HYDRA_USERNAME }}:${{ secrets.HYDRA_PASSWORD }}' | base64)" curl -X PUT -H "Content-Type: application/json" \ -H "$AUTH_HEADER" \ -d '{ "enabled": 1, "visible": true, "keepnr": 3, "schedulingshares": 100, "checkinterval": 60, "description": "PR #${{ github.event.pull_request.number }} - Auto-update flake dependencies", "flake": "github:${{ github.repository }}/${{ env.BRANCH_NAME }}", "type": 1 }' \ "${{ env.HYDRA_INSTANCE }}/jobset/${{ env.HYDRA_PROJECT }}/${{ env.HYDRA_JOBSET }}" - name: Trigger Hydra build if: steps.update-flake.outputs.CHANGED == 'true' run: | AUTH_HEADER="Authorization: Basic $(echo -n '${{ secrets.HYDRA_USERNAME }}:${{ secrets.HYDRA_PASSWORD }}' | base64)" curl -X POST -H "Content-Type: application/json" \ -H "$AUTH_HEADER" \ -H "Origin: ${{ env.HYDRA_INSTANCE }}" \ -d '{"jobsets": ["${{ env.HYDRA_PROJECT }}:${{ env.HYDRA_JOBSET }}"]}' \ "${{ env.HYDRA_INSTANCE }}/api/push" - name: Wait for Hydra build if: steps.update-flake.outputs.CHANGED == 'true' id: wait-for-build run: | AUTH_HEADER="Authorization: Basic $(echo -n '${{ secrets.HYDRA_USERNAME }}:${{ secrets.HYDRA_PASSWORD }}' | base64)" max_attempts=60 # 30 minutes (30 * 2 minutes) attempt=0 while [ $attempt -lt $max_attempts ]; do response=$(curl -s -H "$AUTH_HEADER" \ "${{ env.HYDRA_INSTANCE }}/api/jobsets?project=${{ env.HYDRA_PROJECT }}") status=$(echo "$response" | jq -r '.[] | select(.name == "${{ env.HYDRA_JOBSET }}") | .nrfailed') if [ "$status" = "0" ]; then echo "BUILD_SUCCESS=true" >> $GITHUB_OUTPUT exit 0 elif [ "$status" != "null" ] && [ "$status" != "0" ]; then echo "BUILD_SUCCESS=false" >> $GITHUB_OUTPUT exit 0 fi sleep 120 # Wait for 2 minutes before checking again ((attempt++)) done echo "BUILD_SUCCESS=false" >> $GITHUB_OUTPUT # Timeout, consider as failure - name: Merge PR if build succeeds if: steps.update-flake.outputs.CHANGED == 'true' && steps.wait-for-build.outputs.BUILD_SUCCESS == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | pr_number=$(gh pr list --head ${{ env.BRANCH_NAME }} --json number --jq '.[0].number') gh pr merge $pr_number --merge - name: Comment on PR if build fails if: steps.update-flake.outputs.CHANGED == 'true' && steps.wait-for-build.outputs.BUILD_SUCCESS != 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | pr_number=$(gh pr list --head ${{ env.BRANCH_NAME }} --json number --jq '.[0].number') gh pr comment $pr_number --body "The Hydra build failed. This PR will be updated and retried in 24 hours." - name: Schedule retry if: steps.update-flake.outputs.CHANGED == 'true' && steps.wait-for-build.outputs.BUILD_SUCCESS != 'true' uses: peter-evans/repository-dispatch@v2 with: event-type: retry-flake-update client-payload: '{"pr_number": "${{ steps.create-pr.outputs.pull-request-number }}"}' retry-update: runs-on: ubuntu-latest if: github.event.action == 'retry-flake-update' steps: - name: Checkout repository uses: actions/checkout@v3 with: fetch-depth: 0 - name: Install Nix uses: cachix/install-nix-action@v22 - name: Checkout PR branch run: | pr_number="${{ github.event.client_payload.pr_number }}" branch_name=$(gh pr view $pr_number --json headRefName -q .headRefName) git checkout $branch_name env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Update flake dependencies run: nix flake update - name: Commit and push changes run: | git config user.name github-actions git config user.email github-actions@github.com git add flake.lock git commit -m "Auto-update flake dependencies (retry)" git push origin HEAD - name: Trigger Hydra build run: | AUTH_HEADER="Authorization: Basic $(echo -n '${{ secrets.HYDRA_USERNAME }}:${{ secrets.HYDRA_PASSWORD }}' | base64)" curl -X POST -H "Content-Type: application/json" \ -H "$AUTH_HEADER" \ -H "Origin: ${{ env.HYDRA_INSTANCE }}" \ -d '{"jobsets": ["${{ env.HYDRA_PROJECT }}:${{ env.HYDRA_JOBSET }}"]}' \ "${{ env.HYDRA_INSTANCE }}/api/push" - name: Wait for Hydra build id: wait-for-build run: | AUTH_HEADER="Authorization: Basic $(echo -n '${{ secrets.HYDRA_USERNAME }}:${{ secrets.HYDRA_PASSWORD }}' | base64)" max_attempts=60 # 30 minutes (30 * 2 minutes) attempt=0 while [ $attempt -lt $max_attempts ]; do response=$(curl -s -H "$AUTH_HEADER" \ "${{ env.HYDRA_INSTANCE }}/api/jobsets?project=${{ env.HYDRA_PROJECT }}") status=$(echo "$response" | jq -r '.[] | select(.name == "${{ env.HYDRA_JOBSET }}") | .nrfailed') if [ "$status" = "0" ]; then echo "BUILD_SUCCESS=true" >> $GITHUB_OUTPUT exit 0 elif [ "$status" != "null" ] && [ "$status" != "0" ]; then echo "BUILD_SUCCESS=false" >> $GITHUB_OUTPUT exit 0 fi sleep 120 # Wait for 2 minutes before checking again ((attempt++)) done echo "BUILD_SUCCESS=false" >> $GITHUB_OUTPUT # Timeout, consider as failure - name: Merge PR if build succeeds if: steps.wait-for-build.outputs.BUILD_SUCCESS == 'true' run: | pr_number="${{ github.event.client_payload.pr_number }}" gh pr merge $pr_number --merge env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Schedule another retry if build fails if: steps.wait-for-build.outputs.BUILD_SUCCESS != 'true' uses: peter-evans/create-or-update-comment@v3 with: issue-number: ${{ github.event.client_payload.pr_number }} body: | The Hydra build failed again. This PR will be updated and retried in 24 hours. - name: Retry update after 24 hours if: steps.wait-for-build.outputs.BUILD_SUCCESS != 'true' uses: peter-evans/repository-dispatch@v2 with: event-type: retry-flake-update client-payload: '{"pr_number": "${{ github.event.client_payload.pr_number }}"}'