name: Update GitHub Versions (New) on: workflow_dispatch: schedule: # Runs 4x daily: 00:00, 06:00, 12:00, 18:00 UTC - cron: "0 0,6,12,18 * * *" permissions: contents: write pull-requests: write env: VERSIONS_FILE: frontend/public/json/github-versions.json BRANCH_NAME: automated/update-github-versions AUTOMATED_PR_LABEL: "automated pr" jobs: update-github-versions: if: github.repository == 'community-scripts/ProxmoxVE' runs-on: ubuntu-latest steps: - name: Generate a token id: generate-token uses: actions/create-github-app-token@v1 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Generate a token for PR approval and merge id: generate-token-merge uses: actions/create-github-app-token@v1 with: app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }} private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }} - name: Checkout Repository uses: actions/checkout@v4 with: ref: main - name: Extract GitHub versions from install scripts env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail echo "=========================================" echo " Extracting GitHub versions from scripts" echo "=========================================" # Initialize versions array versions_json="[]" # Function to add a version entry add_version() { local slug="$1" local repo="$2" local version="$3" local pinned="$4" local date="$5" versions_json=$(echo "$versions_json" | jq \ --arg slug "$slug" \ --arg repo "$repo" \ --arg version "$version" \ --argjson pinned "$pinned" \ --arg date "$date" \ '. += [{"slug": $slug, "repo": $repo, "version": $version, "pinned": $pinned, "date": $date}]') } # Get list of slugs from JSON files echo "" echo "=== Scanning JSON files for slugs ===" for json_file in frontend/public/json/*.json; do [[ ! -f "$json_file" ]] && continue # Skip non-app JSON files basename_file=$(basename "$json_file") case "$basename_file" in metadata.json|versions.json|github-versions.json|dependency-check.json|update-apps.json) continue ;; esac # Extract slug from JSON slug=$(jq -r '.slug // empty' "$json_file" 2>/dev/null) [[ -z "$slug" ]] && continue # Find corresponding install script install_script="install/${slug}-install.sh" [[ ! -f "$install_script" ]] && continue # Look for fetch_and_deploy_gh_release calls # Pattern: fetch_and_deploy_gh_release "app" "owner/repo" ["mode"] ["version"] while IFS= read -r line; do # Skip commented lines [[ "$line" =~ ^[[:space:]]*# ]] && continue # Extract repo and version from fetch_and_deploy_gh_release if [[ "$line" =~ fetch_and_deploy_gh_release[[:space:]]+\"[^\"]*\"[[:space:]]+\"([^\"]+)\"([[:space:]]+\"([^\"]+)\")?([[:space:]]+\"([^\"]+)\")? ]]; then repo="${BASH_REMATCH[1]}" mode="${BASH_REMATCH[3]:-tarball}" pinned_version="${BASH_REMATCH[5]:-latest}" # Check if version is pinned (not "latest" and not empty) is_pinned=false target_version="" if [[ -n "$pinned_version" && "$pinned_version" != "latest" ]]; then is_pinned=true target_version="$pinned_version" fi # Fetch version from GitHub if [[ "$is_pinned" == "true" ]]; then # For pinned versions, verify it exists and get date response=$(gh api "repos/${repo}/releases/tags/${target_version}" 2>/dev/null || echo '{}') if echo "$response" | jq -e '.tag_name' > /dev/null 2>&1; then version=$(echo "$response" | jq -r '.tag_name') date=$(echo "$response" | jq -r '.published_at // empty') add_version "$slug" "$repo" "$version" "true" "$date" echo "[$slug] ✓ $version (pinned)" else echo "[$slug] ⚠ pinned version $target_version not found" fi else # Fetch latest release response=$(gh api "repos/${repo}/releases/latest" 2>/dev/null || echo '{}') if echo "$response" | jq -e '.tag_name' > /dev/null 2>&1; then version=$(echo "$response" | jq -r '.tag_name') date=$(echo "$response" | jq -r '.published_at // empty') add_version "$slug" "$repo" "$version" "false" "$date" echo "[$slug] ✓ $version" else # Try tags as fallback version=$(gh api "repos/${repo}/tags" --jq '.[0].name // empty' 2>/dev/null || echo "") if [[ -n "$version" ]]; then add_version "$slug" "$repo" "$version" "false" "" echo "[$slug] ✓ $version (from tags)" else echo "[$slug] ⚠ no version found" fi fi fi break # Only first match per script fi done < <(grep 'fetch_and_deploy_gh_release' "$install_script" 2>/dev/null || true) done # Save versions file echo "$versions_json" | jq --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ '{generated: $date, versions: (. | sort_by(.slug))}' > "$VERSIONS_FILE" total=$(echo "$versions_json" | jq 'length') echo "" echo "=========================================" echo " Total versions extracted: $total" echo "=========================================" - name: Check for changes id: check-changes run: | # Check if file is new (untracked) or has changes if [[ ! -f "$VERSIONS_FILE" ]]; then echo "changed=false" >> "$GITHUB_OUTPUT" echo "Versions file was not created" elif ! git ls-files --error-unmatch "$VERSIONS_FILE" &>/dev/null; then # File exists but is not tracked - it's new echo "changed=true" >> "$GITHUB_OUTPUT" echo "New file created: $VERSIONS_FILE" elif git diff --quiet "$VERSIONS_FILE" 2>/dev/null; then echo "changed=false" >> "$GITHUB_OUTPUT" echo "No changes detected" else echo "changed=true" >> "$GITHUB_OUTPUT" echo "Changes detected:" git diff --stat "$VERSIONS_FILE" 2>/dev/null || true fi - name: Commit and push changes if: steps.check-changes.outputs.changed == 'true' run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" git add "$VERSIONS_FILE" git commit -m "chore: update github-versions.json" git checkout -b $BRANCH_NAME || git checkout $BRANCH_NAME git push origin $BRANCH_NAME --force - name: Create pull request if not exists if: steps.check-changes.outputs.changed == 'true' env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | PR_EXISTS=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number') if [ -z "$PR_EXISTS" ]; then gh pr create --title "[Github Action] Update github-versions.json" \ --body "This PR is auto-generated by a Github Action to update the github-versions.json file." \ --head $BRANCH_NAME \ --base main \ --label "$AUTOMATED_PR_LABEL" fi - name: Approve pull request if: steps.check-changes.outputs.changed == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number') if [ -n "$PR_NUMBER" ]; then gh pr review $PR_NUMBER --approve fi - name: Approve pull request and merge if: steps.check-changes.outputs.changed == 'true' env: GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }} run: | git config --global user.name "github-actions-automege[bot]" git config --global user.email "github-actions-automege[bot]@users.noreply.github.com" PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number') if [ -n "$PR_NUMBER" ]; then gh pr review $PR_NUMBER --approve gh pr merge $PR_NUMBER --squash --admin fi