name: Weekly Flake Update on: schedule: # Every Friday at 22:00 UTC - cron: '0 22 * * 5' workflow_dispatch: {} jobs: prepare: runs-on: nix outputs: changed: ${{ steps.changes.outputs.changed }} branch_name: ${{ steps.commit.outputs.branch_name }} steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Configure Git run: | git config user.name "Flake Update Bot" git config user.email "bot@noreply.local" - name: Update flake inputs run: nix flake update - name: Check for changes id: changes run: | if git diff --quiet flake.lock; then echo "changed=false" >> $FORGEJO_OUTPUT echo "No changes to flake.lock" else echo "changed=true" >> $FORGEJO_OUTPUT echo "flake.lock has been updated" fi - name: Create branch and commit id: commit if: steps.changes.outputs.changed == 'true' run: | BRANCH_NAME="auto-update/$(date +%Y-%m-%d)" git checkout -b "$BRANCH_NAME" git add flake.lock git commit -m "chore: update flake inputs $(date +%Y-%m-%d)" git push origin "$BRANCH_NAME" echo "branch_name=$BRANCH_NAME" >> $FORGEJO_OUTPUT echo "Created and pushed branch: $BRANCH_NAME" - name: Extract LXC hosts if: steps.changes.outputs.changed == 'true' run: | set -euo pipefail HOSTS=$(nix eval --json .#colmena --apply 'x: builtins.filter (n: n != "meta" && builtins.elem "lxc" (x.${n}.deployment.tags or [])) (builtins.attrNames x)') # Validate the output is a non-empty JSON array if ! echo "$HOSTS" | jq -e '. | if type == "array" and length > 0 then true else false end' > /dev/null 2>&1; then echo "Error: No LXC hosts found or invalid JSON output: $HOSTS" exit 1 fi echo "$HOSTS" > hosts.json echo "Discovered hosts: $HOSTS" - name: Upload hosts list if: steps.changes.outputs.changed == 'true' uses: forgejo/upload-artifact@v4 with: name: hosts-list path: hosts.json retention-days: 1 build: needs: prepare if: needs.prepare.outputs.changed == 'true' runs-on: nix steps: - name: Checkout update branch uses: actions/checkout@v4 with: ref: ${{ needs.prepare.outputs.branch_name }} - name: Download hosts list uses: forgejo/download-artifact@v4 with: name: hosts-list - name: Build all hosts id: build run: | set -euo pipefail if [ ! -f hosts.json ]; then echo "Error: hosts.json not found" exit 1 fi HOSTS=$(cat hosts.json) echo "Building hosts: $HOSTS" FAILED_HOSTS="" SUCCESS_HOSTS="" declare -A BUILD_TIMES for HOST in $(echo "$HOSTS" | jq -r '.[]'); do echo "" echo "==========================================" echo "Building: $HOST" echo "==========================================" START_TIME=$(date +%s) if nix build ".#nixosConfigurations.${HOST}.config.system.build.toplevel" --no-link --quiet 2>&1 | tee "build-${HOST}.txt"; then END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) DURATION_FMT=$(printf '%dm %ds' $((DURATION/60)) $((DURATION%60))) BUILD_TIMES[$HOST]="$DURATION_FMT" echo "✓ Build succeeded: $HOST (${DURATION_FMT})" SUCCESS_HOSTS="$SUCCESS_HOSTS $HOST" else END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) DURATION_FMT=$(printf '%dm %ds' $((DURATION/60)) $((DURATION%60))) BUILD_TIMES[$HOST]="$DURATION_FMT" echo "✗ Build failed: $HOST (${DURATION_FMT})" FAILED_HOSTS="$FAILED_HOSTS $HOST" fi done echo "" echo "==========================================" echo "Build Summary" echo "==========================================" echo "Succeeded:$SUCCESS_HOSTS" echo "Failed:$FAILED_HOSTS" echo "" echo "Build times:" for HOST in "${!BUILD_TIMES[@]}"; do echo " $HOST: ${BUILD_TIMES[$HOST]}" done # Save results for report job echo "$FAILED_HOSTS" > failed-hosts.txt echo "$SUCCESS_HOSTS" > success-hosts.txt if [ -n "$FAILED_HOSTS" ]; then echo "Some builds failed" exit 1 fi continue-on-error: true - name: Upload build logs if: always() uses: forgejo/upload-artifact@v4 with: name: build-logs path: build-*.txt retention-days: 7 - name: Upload build results if: always() uses: forgejo/upload-artifact@v4 with: name: build-results path: | failed-hosts.txt success-hosts.txt retention-days: 1 - name: Check build result run: | if [ "${{ steps.build.outcome }}" == "failure" ]; then echo "Some builds failed - check build-logs artifact" exit 1 fi report: needs: [prepare, build] if: always() && needs.prepare.outputs.changed == 'true' runs-on: nix steps: - name: Download hosts list uses: forgejo/download-artifact@v4 with: name: hosts-list - name: Download build results uses: forgejo/download-artifact@v4 with: name: build-results continue-on-error: true - name: Download build logs if: needs.build.result == 'failure' uses: forgejo/download-artifact@v4 with: name: build-logs path: logs continue-on-error: true - name: Create Pull Request if: needs.build.result == 'success' env: FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} run: | HOSTS=$(cat hosts.json) HOST_LIST=$(echo "$HOSTS" | jq -r '.[] | "- " + .') curl -X POST \ -H "Authorization: token $FORGEJO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "chore: weekly flake update", "body": "Automated flake update from CI.\n\nThis PR updates all flake inputs and has been verified to build successfully.\n\n**Hosts built:**\n'"$(echo "$HOST_LIST" | sed 's/$/\\n/g' | tr -d '\n')"'\n\nGenerated on: '"$(date -Iseconds)"'", "head": "'"${{ needs.prepare.outputs.branch_name }}"'", "base": "master" }' \ "${{ forgejo.api_url }}/repos/${{ forgejo.repository }}/pulls" - name: Create Issue on failure if: needs.build.result == 'failure' env: FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} run: | FAILED_HOSTS="" FAILURE_DETAILS="" # Read failed hosts from build results if [ -f "failed-hosts.txt" ]; then for HOST in $(cat failed-hosts.txt); do FAILED_HOSTS="$FAILED_HOSTS\n- $HOST" # Get build log if available if [ -f "logs/build-${HOST}.txt" ]; then LOG_TAIL=$(tail -150 "logs/build-${HOST}.txt" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') FAILURE_DETAILS="$FAILURE_DETAILS\n\n
\n$HOST build log (last 150 lines)\n\n\`\`\`\n$LOG_TAIL\n\`\`\`\n
" fi done fi if [ -z "$FAILED_HOSTS" ]; then FAILED_HOSTS="\n- (Unable to determine failed hosts - check workflow logs)" fi ISSUE_BODY="Weekly flake update failed to build some hosts.\n\n**Branch:** \`${{ needs.prepare.outputs.branch_name }}\`\n**Run:** ${{ forgejo.api_url }}/${{ forgejo.repository }}/actions/runs/${{ forgejo.run_id }}\n\n**Failed hosts:**$FAILED_HOSTS\n$FAILURE_DETAILS\n\nGenerated on: $(date -Iseconds)" curl -X POST \ -H "Authorization: token $FORGEJO_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"title\": \"Build failure: weekly flake update $(date +%Y-%m-%d)\", \"body\": \"$ISSUE_BODY\" }" \ "${{ forgejo.api_url }}/repos/${{ forgejo.repository }}/issues" - name: Cleanup if: always() run: | nix-collect-garbage -d rm -rf result .direnv logs