Make and Update GitHub Releases (Weekly Cleanup + Rebuild) #225
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Make and Update GitHub Releases (Weekly Cleanup + Rebuild) | |
| on: | |
| workflow_dispatch: {} | |
| schedule: | |
| # Weekly (Monday 00:00 UTC) | |
| - cron: "0 0 * * 1" | |
| concurrency: | |
| group: releases-cleanup-rebuild | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| jobs: | |
| cleanup-github-releases: | |
| name: 🧹 Cleanup Old Managed Releases & Tags (managed prefixes) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Workflow "ownership" prefixes (only these will be touched) | |
| MANAGED_TAG_PREFIXES: >- | |
| AD-SSO-APIs-Integration | |
| All-Repository-Files | |
| BlueTeam-Tools | |
| Core-ScriptLibrary | |
| GPOs-Templates | |
| ITSM-Templates-SVR | |
| ITSM-Templates-WKS | |
| READMEs-Files-Package | |
| SysAdmin-Tools | |
| steps: | |
| - name: 🧰 Install Dependencies (gh) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y gh | |
| - name: 🔐 Verify GitHub CLI Auth | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| gh auth status | |
| - name: 🗑️ Delete ALL matching Releases (tag_name starts with managed prefix) | |
| id: del_releases | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| repo="${GITHUB_REPOSITORY}" | |
| owner="${repo%/*}" | |
| name="${repo#*/}" | |
| echo "Repository: ${repo}" | |
| echo "Managed prefixes:" | |
| for p in ${MANAGED_TAG_PREFIXES}; do | |
| echo " - $p" | |
| done | |
| deleted=0 | |
| # Get ALL releases (paginated) and extract tag_name | |
| mapfile -t release_tags < <( | |
| gh api --paginate "repos/${owner}/${name}/releases?per_page=100" --jq '.[].tag_name' | |
| ) | |
| if [[ "${#release_tags[@]}" -eq 0 ]]; then | |
| echo "No releases found." | |
| echo "deleted_releases=0" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| for tag in "${release_tags[@]}"; do | |
| for prefix in ${MANAGED_TAG_PREFIXES}; do | |
| # Delete ANY release whose tag_name begins with "<prefix>-" | |
| if [[ "$tag" == "${prefix}-"* ]]; then | |
| echo "Deleting release: ${tag}" | |
| # IMPORTANT: release delete uses --repo to avoid "not a git repository" | |
| gh release delete "${tag}" --yes --repo "${repo}" || true | |
| deleted=$((deleted + 1)) | |
| break | |
| fi | |
| done | |
| done | |
| echo "Deleted releases: ${deleted}" | |
| echo "deleted_releases=${deleted}" >> "$GITHUB_OUTPUT" | |
| - name: 🗑️ Delete ALL matching Tags (name starts with managed prefix) | |
| id: del_tags | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| repo="${GITHUB_REPOSITORY}" | |
| owner="${repo%/*}" | |
| name="${repo#*/}" | |
| deleted=0 | |
| # Use git/matching-refs for reliable pagination over tag refs | |
| for prefix in ${MANAGED_TAG_PREFIXES}; do | |
| echo "Scanning tags for prefix: ${prefix}-" | |
| mapfile -t refs < <( | |
| gh api --paginate "repos/${owner}/${name}/git/matching-refs/tags/${prefix}-" --jq '.[].ref' | |
| ) | |
| for ref in "${refs[@]}"; do | |
| # ref like: refs/tags/<tagname> | |
| tag="${ref#refs/tags/}" | |
| echo "Deleting tag ref: ${ref}" | |
| # NOTE: gh api has NO --repo flag. Always use full endpoint. | |
| gh api -X DELETE "repos/${owner}/${name}/git/refs/tags/${tag}" >/dev/null 2>&1 || true | |
| deleted=$((deleted + 1)) | |
| done | |
| done | |
| echo "Deleted tags: ${deleted}" | |
| echo "deleted_tags=${deleted}" >> "$GITHUB_OUTPUT" | |
| - name: 📋 Publish Cleanup Summary (View Runs) | |
| if: always() | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| del_rel="${{ steps.del_releases.outputs.deleted_releases || '0' }}" | |
| del_tag="${{ steps.del_tags.outputs.deleted_tags || '0' }}" | |
| { | |
| echo "## 🧹 Cleanup Old Managed Releases & Tags" | |
| echo | |
| echo "- **Workflow:** \`${{ github.workflow }}\`" | |
| echo "- **Event:** \`${{ github.event_name }}\`" | |
| echo "- **Ref:** \`${{ github.ref }}\`" | |
| echo "- **Commit:** \`${{ github.sha }}\`" | |
| echo | |
| echo "### Results" | |
| echo | |
| echo "- **Deleted releases:** \`${del_rel}\`" | |
| echo "- **Deleted tags:** \`${del_tag}\`" | |
| echo | |
| echo "### Managed prefixes" | |
| echo | |
| echo '```text' | |
| for p in ${MANAGED_TAG_PREFIXES}; do echo "$p"; done | |
| echo '```' | |
| echo | |
| echo "### Notes" | |
| echo "- Release deletions are best-effort (\`|| true\`) to avoid breaking weekly maintenance." | |
| echo "- Tag deletions use REST endpoints and are paginated per prefix." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| update-github-releases: | |
| name: 🚀 Rebuild Managed Releases | |
| needs: cleanup-github-releases | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 40 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| release_name: | |
| - AD-SSO-APIs-Integration | |
| - All-Repository-Files | |
| - BlueTeam-Tools | |
| - Core-ScriptLibrary | |
| - GPOs-Templates | |
| - ITSM-Templates-SVR | |
| - ITSM-Templates-WKS | |
| - READMEs-Files-Package | |
| - SysAdmin-Tools | |
| steps: | |
| - name: 📦 Checkout Repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: true | |
| - name: 🧰 Install Dependencies (zip) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y zip | |
| - name: 🏷️ Compute Version Tag | |
| id: tag | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| short_sha="${GITHUB_SHA::7}" | |
| date_tag="$(date -u +%Y%m%d)" | |
| echo "version_tag=${{ matrix.release_name }}-${date_tag}-${short_sha}" >> "$GITHUB_OUTPUT" | |
| - name: 📦 Build Release Artifact | |
| shell: bash | |
| env: | |
| RELEASE_NAME: ${{ matrix.release_name }} | |
| run: | | |
| set -euo pipefail | |
| rm -rf artifacts temp || true | |
| mkdir -p artifacts temp | |
| copy_root_meta() { | |
| [[ -f README.md ]] && cp README.md temp/ || true | |
| [[ -f LICENSE.txt ]] && cp LICENSE.txt temp/ || true | |
| [[ -f LICENSE ]] && cp LICENSE temp/ || true | |
| [[ -f CHANGELOG.md ]] && cp CHANGELOG.md temp/ || true | |
| } | |
| case "$RELEASE_NAME" in | |
| BlueTeam-Tools|Core-ScriptLibrary|ITSM-Templates-SVR|ITSM-Templates-WKS|SysAdmin-Tools) | |
| [[ -d "$RELEASE_NAME" ]] || { echo "::error::Missing dir: $RELEASE_NAME"; exit 1; } | |
| cp -r "$RELEASE_NAME" temp/ | |
| copy_root_meta | |
| ;; | |
| GPOs-Templates) | |
| [[ -d "SysAdmin-Tools/GroupPolicyObjects-Templates" ]] || { echo "::error::Missing dir: SysAdmin-Tools/GroupPolicyObjects-Templates"; exit 1; } | |
| cp -r SysAdmin-Tools/GroupPolicyObjects-Templates/* temp/ | |
| [[ -f SysAdmin-Tools/ActiveDirectory-Management/Export-n-Import-GPOsTool.ps1 ]] && \ | |
| cp SysAdmin-Tools/ActiveDirectory-Management/Export-n-Import-GPOsTool.ps1 temp/ || true | |
| copy_root_meta | |
| ;; | |
| READMEs-Files-Package) | |
| [[ -f README.md ]] && cp README.md temp/main-README.md || true | |
| find . -type f -iname "README.md" ! -path "./README.md" | while read -r file; do | |
| dir="$(dirname "$file")" | |
| name="$(basename "$dir")" | |
| cp "$file" "temp/${name}-README.md" | |
| done | |
| ;; | |
| All-Repository-Files) | |
| for dir in BlueTeam-Tools Core-ScriptLibrary ITSM-Templates-SVR ITSM-Templates-WKS SysAdmin-Tools; do | |
| [[ -d "$dir" ]] && cp -r "$dir" temp/ || echo "::warning::Missing dir (skipped): $dir" | |
| done | |
| copy_root_meta | |
| ;; | |
| AD-SSO-APIs-Integration) | |
| [[ -d "SysAdmin-Tools/ActiveDirectory-SSO-Integrations" ]] || { echo "::error::Missing dir: SysAdmin-Tools/ActiveDirectory-SSO-Integrations"; exit 1; } | |
| cp -r SysAdmin-Tools/ActiveDirectory-SSO-Integrations/* temp/ | |
| copy_root_meta | |
| ;; | |
| *) | |
| echo "::error::Unknown release: $RELEASE_NAME" | |
| exit 1 | |
| ;; | |
| esac | |
| (cd temp && zip -r "../artifacts/${RELEASE_NAME}.zip" .) | |
| sha256sum "artifacts/${RELEASE_NAME}.zip" > "artifacts/${RELEASE_NAME}.sha256.txt" | |
| - name: 📝 Extract Changelog Section | |
| id: changelog | |
| shell: bash | |
| env: | |
| RELEASE_NAME: ${{ matrix.release_name }} | |
| run: | | |
| set -euo pipefail | |
| if [[ ! -f CHANGELOG.md ]]; then | |
| echo "body=No CHANGELOG.md found in repository root." >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| section="## ${RELEASE_NAME}" | |
| body="$(awk -v section="$section" ' | |
| $0 == section {found=1; next} | |
| /^## / && found {exit} | |
| found {print} | |
| ' CHANGELOG.md | sed -e 's/[[:space:]]\+$//' )" | |
| if [[ -z "${body// }" ]]; then | |
| body="No changelog available for ${RELEASE_NAME}." | |
| fi | |
| echo "body<<EOF" >> "$GITHUB_OUTPUT" | |
| echo "$body" >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| - name: 🚀 Create/Update GitHub Release (upload assets) | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.tag.outputs.version_tag }} | |
| name: ${{ steps.tag.outputs.version_tag }} | |
| body: ${{ steps.changelog.outputs.body }} | |
| draft: false | |
| prerelease: false | |
| files: | | |
| artifacts/${{ matrix.release_name }}.zip | |
| artifacts/${{ matrix.release_name }}.sha256.txt | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: 📦 Upload Build Artifacts (Actions) | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-${{ matrix.release_name }} | |
| path: | | |
| artifacts/${{ matrix.release_name }}.zip | |
| artifacts/${{ matrix.release_name }}.sha256.txt | |
| retention-days: 30 | |
| - name: 📋 Publish Release Summary (View Runs) | |
| if: always() | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| REL="${{ matrix.release_name }}" | |
| TAG="${{ steps.tag.outputs.version_tag }}" | |
| ZIP="artifacts/${REL}.zip" | |
| SHA="artifacts/${REL}.sha256.txt" | |
| size_bytes="0" | |
| if [[ -f "${ZIP}" ]]; then | |
| size_bytes="$(wc -c < "${ZIP}" | tr -d ' ')" | |
| fi | |
| { | |
| echo "## 🚀 Release Rebuild — ${REL}" | |
| echo | |
| echo "- **Tag:** \`${TAG}\`" | |
| echo "- **Artifact:** \`${ZIP}\`" | |
| echo "- **Size (bytes):** \`${size_bytes}\`" | |
| echo | |
| echo "### Integrity" | |
| if [[ -f "${SHA}" ]]; then | |
| echo | |
| echo '```text' | |
| cat "${SHA}" | |
| echo '```' | |
| else | |
| echo "- _SHA256 file not found:_ \`${SHA}\`" | |
| fi | |
| echo | |
| echo "### Changelog" | |
| echo | |
| if [[ -n "${{ steps.changelog.outputs.body }}" ]]; then | |
| echo "${{ steps.changelog.outputs.body }}" | |
| else | |
| echo "_No changelog section extracted._" | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| summary: | |
| name: 📌 Weekly Release Maintenance Summary | |
| needs: [cleanup-github-releases, update-github-releases] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: 📋 Publish Workflow Summary (View Runs) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| { | |
| echo "## 📌 Weekly Release Maintenance Summary" | |
| echo | |
| echo "- **Cleanup job:** **${{ needs.cleanup-github-releases.result }}**" | |
| echo "- **Rebuild job:** **${{ needs.update-github-releases.result }}**" | |
| echo | |
| echo "### Notes" | |
| echo "- Cleanup deletes only managed prefixes." | |
| echo "- Each matrix leg publishes its own release summary with tag, artifact size, SHA256, and changelog." | |
| } >> "$GITHUB_STEP_SUMMARY" |