Phase 3: Improve PPA and GPG CI workflows #82
Workflow file for this run
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
| # Main PPA build workflow | |
| # Triggers on PPA updates to build all packages | |
| name: PPA Build (Main) | |
| on: | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - "**.md" | |
| - "docs/**" | |
| # Generated APT repo files (pushed by build jobs, synced to gh-pages) | |
| - "dists/**" | |
| - "pool/**" | |
| - "db/**" | |
| - "lists/**" | |
| - "key.gpg" | |
| - "Release.gpg" | |
| - "keyrings/**" | |
| - "conf/**" | |
| pull_request: | |
| branches: [main] | |
| paths-ignore: | |
| - "**.md" | |
| - "docs/**" | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| lint-workflows: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Lint shell scripts | |
| run: | | |
| set -euo pipefail | |
| echo "Checking shell syntax..." | |
| find . -name "*.sh" -not -path "./.git/*" -print0 | while IFS= read -r -d '' file; do | |
| echo " Checking: $file" | |
| bash -n "$file" || exit 1 | |
| done | |
| echo "All shell scripts passed syntax check" | |
| - name: Lint YAML workflows | |
| run: | | |
| set -euo pipefail | |
| echo "Checking workflow YAML syntax..." | |
| for f in .github/workflows/*.yml; do | |
| echo " OK: $f" | |
| python3 -c "import yaml; yaml.safe_load(open('$f'))" || exit 1 | |
| done | |
| detect-changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| packages: ${{ steps.detect.outputs.packages }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: true | |
| - name: Detect changed submodules | |
| id: detect | |
| run: | | |
| set -euo pipefail | |
| # Get list of all submodules (as JSON array) | |
| SUBMODULES=$(git submodule status | awk '{print $2}' | grep '^src/diepxuan/' | sed 's|^src/diepxuan/||' | sed 's|/$||' | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| # For manual trigger, build all | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| echo "packages=$SUBMODULES" >> $GITHUB_OUTPUT | |
| echo "All packages will be built (manual trigger)" | |
| exit 0 | |
| fi | |
| # Get base ref | |
| BASE_REF="${{ github.base_ref }}" | |
| if [[ -z "$BASE_REF" ]]; then | |
| BASE_REF="main" | |
| fi | |
| # Get changed files | |
| CHANGES=$(git diff --name-only "origin/$BASE_REF...HEAD" 2>/dev/null || git diff --name-only HEAD~1...HEAD 2>/dev/null || echo "") | |
| echo "Changes detected: $CHANGES" | |
| # Find which submodules have changes (as JSON array) | |
| CHANGED_PACKAGES="[]" | |
| if [[ -n "$CHANGES" ]]; then | |
| for submodule in $(git submodule status | awk '{print $2}' | grep '^src/diepxuan/' | sed 's|^src/diepxuan/||' | sed 's|/$||'); do | |
| if echo "$CHANGES" | grep -q "^src/diepxuan/$submodule/"; then | |
| echo "Package '$submodule' has changes" | |
| CHANGED_PACKAGES=$(echo "$CHANGED_PACKAGES" | jq -c ". + [\"$submodule\"]") | |
| fi | |
| done | |
| fi | |
| if [[ "$CHANGED_PACKAGES" == "[]" ]]; then | |
| echo "No package changes detected" | |
| echo "packages=[]" >> $GITHUB_OUTPUT | |
| else | |
| echo "packages=$CHANGED_PACKAGES" >> $GITHUB_OUTPUT | |
| echo "Will build: $CHANGED_PACKAGES" | |
| fi | |
| package-build: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.packages != '[]' && needs.detect-changes.outputs.packages != '' | |
| permissions: | |
| contents: write | |
| strategy: | |
| max-parallel: 1 | |
| fail-fast: false | |
| matrix: | |
| package: ${{ fromJson(needs.detect-changes.outputs.packages) }} | |
| uses: diepxuan/.github/.github/workflows/debian-package-ppa.yml@main | |
| with: | |
| module: ${{ matrix.package }} | |
| secrets: | |
| GPG_KEY: ${{ secrets.GPG_KEY }} | |
| GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} | |
| GIT_COMMITTER_EMAIL: ${{ secrets.GIT_COMMITTER_EMAIL }} | |
| SSH_ID_RSA: ${{ secrets.SSH_ID_RSA }} | |
| verify-build: | |
| needs: package-build | |
| if: success() && needs.package-build.result == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| submodules: true | |
| - name: Setup GPG | |
| uses: diepxuan/.github/.github/actions/gpg-setup@main | |
| with: | |
| gpg_key: ${{ secrets.GPG_KEY }} | |
| gpg_key_id: ${{ secrets.GPG_KEY_ID }} | |
| committer_email: ${{ secrets.GIT_COMMITTER_EMAIL }} | |
| committer_name: ${{ github.repository_owner }} | |
| - name: Verify .deb packages | |
| run: | | |
| set -euo pipefail | |
| echo "Verifying .deb packages in pool..." | |
| DEB_COUNT=0 | |
| for deb in pool/main/*/*.deb; do | |
| [ -f "$deb" ] || continue | |
| DEB_COUNT=$((DEB_COUNT + 1)) | |
| echo " Verifying: $deb" | |
| dpkg-deb --info "$deb" > /dev/null 2>&1 || { echo "INVALID: $deb"; exit 1; } | |
| dpkg-deb --field "$deb" Package Version Architecture > /dev/null 2>&1 || { echo "MISSING FIELDS: $deb"; exit 1; } | |
| done | |
| echo "Verified $DEB_COUNT .deb packages" | |
| - name: Verify APT repository metadata | |
| run: | | |
| set -euo pipefail | |
| echo "Verifying APT repository metadata..." | |
| for release in dists/*/Release; do | |
| [ -f "$release" ] || continue | |
| DIST=$(echo "$release" | cut -d/ -f2) | |
| echo " Checking dist: $DIST" | |
| gpg --verify "dists/$DIST/Release.gpg" "dists/$DIST/Release" 2>&1 || { echo "GPG VERIFY FAILED: $DIST"; exit 1; } | |
| done | |
| echo "All APT metadata verified" | |
| sync-gh-pages: | |
| needs: [detect-changes, package-build] | |
| if: | | |
| always() && | |
| github.ref == 'refs/heads/main' && | |
| needs.detect-changes.outputs.packages != '[]' && | |
| needs.detect-changes.outputs.packages != '' && | |
| needs.package-build.result == 'success' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout main | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| path: main-worktree | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| - name: Checkout gh-pages | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: gh-pages | |
| path: gh-pages-worktree | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Sync generated files to gh-pages | |
| run: | | |
| set -euo pipefail | |
| cd gh-pages-worktree | |
| git config user.name "DiepXuan CI" | |
| git config user.email "ci@diepxuan.com" | |
| # Remove old generated files | |
| rm -rf dists pool db lists key.gpg Release.gpg keyrings conf | |
| # Copy new generated files from main | |
| if [ -d ../main-worktree/dists ]; then cp -r ../main-worktree/dists ./dists; else mkdir -p dists; fi | |
| if [ -d ../main-worktree/pool ]; then cp -r ../main-worktree/pool ./pool; else mkdir -p pool; fi | |
| if [ -d ../main-worktree/db ]; then cp -r ../main-worktree/db ./db; else mkdir -p db; fi | |
| if [ -d ../main-worktree/lists ]; then cp -r ../main-worktree/lists ./lists; else mkdir -p lists; fi | |
| if [ -f ../main-worktree/key.gpg ]; then cp ../main-worktree/key.gpg ./key.gpg; fi | |
| if [ -f ../main-worktree/Release.gpg ]; then cp ../main-worktree/Release.gpg ./Release.gpg; fi | |
| if [ -d ../main-worktree/keyrings ]; then cp -r ../main-worktree/keyrings ./keyrings; fi | |
| if [ -d ../main-worktree/conf ]; then cp -r ../main-worktree/conf ./conf; fi | |
| # Commit and push to gh-pages | |
| git add -A | |
| if ! git diff --cached --quiet; then | |
| git commit -m "ci: sync generated files from main@${{ github.sha }}" | |
| git push origin gh-pages | |
| else | |
| echo "No changes to sync to gh-pages" | |
| fi | |
| - name: Clean generated files from main | |
| run: | | |
| set -euo pipefail | |
| cd main-worktree | |
| git config user.name "DiepXuan CI" | |
| git config user.email "ci@diepxuan.com" | |
| # Remove generated files from main tracking | |
| # These paths are ignored in the workflow trigger, so this push won't trigger a new CI run | |
| git rm -rf --cached dists pool db lists key.gpg Release.gpg keyrings 2>/dev/null || true | |
| if ! git diff --cached --quiet; then | |
| git commit -m "ci: remove generated files (synced to gh-pages)" | |
| git push origin main | |
| else | |
| echo "No generated files to clean from main" | |
| fi |