fix: Use shared workflow for builder cleanup #605
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: Build Image Variants | |
| on: | |
| repository_dispatch: | |
| types: | |
| - build | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: {} | |
| env: | |
| REPO_NAME: snosi | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'push' }} | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| attestations: write | |
| strategy: | |
| matrix: | |
| include: | |
| - profile: cayo | |
| description: "Cayo Linux Server Image" | |
| - profile: cayoloaded | |
| description: "Cayo Loaded Linux Server Image" | |
| - profile: snow | |
| description: "Snow Linux OS Image" | |
| - profile: snowloaded | |
| description: "Snow Loaded Linux OS Image" | |
| - profile: snowfield | |
| description: "Snowfield Linux OS Image" | |
| - profile: snowfieldloaded | |
| description: "Snow Field Loaded Linux OS Image" | |
| steps: | |
| - name: Show disk space (before) | |
| run: sudo df -h | |
| - name: Free Disk Space (Ubuntu) | |
| uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 | |
| - name: Mount BTRFS for podman storage | |
| uses: ublue-os/container-storage-action@main | |
| with: | |
| target-dir: /var/lib/containers | |
| - name: Show disk space (after) | |
| run: sudo df -h | |
| - name: Checkout repository | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - name: Check mkosi package duplicates | |
| run: ./check-duplicate-packages.sh | |
| - name: Generate build date | |
| id: date | |
| run: echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT | |
| - name: Generate version tag | |
| id: version | |
| run: echo "tag=$(date +%Y%m%d%H%M%S)" >> $GITHUB_OUTPUT | |
| - name: setup-mkosi | |
| uses: systemd/mkosi@3c3a08fb07d27fbe473625aa0725655cfb2c68bf | |
| - name: Build Image | |
| run: | | |
| sudo mkosi --profile ${{ matrix.profile }} \ | |
| --image-version "${{ steps.version.outputs.tag }}" \ | |
| build | |
| - name: Package image | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.profile }} | |
| run: | | |
| sudo ./shared/outformat/image/buildah-package.sh \ | |
| output/${{ matrix.profile }} \ | |
| "$IMAGE:${{ steps.version.outputs.tag }}" \ | |
| "org.opencontainers.image.title=${{ matrix.profile }}" \ | |
| "org.opencontainers.image.description=${{ matrix.description }}" \ | |
| "org.opencontainers.image.version=${{ steps.version.outputs.tag }}" \ | |
| "org.opencontainers.image.created=${{ steps.date.outputs.date }}" \ | |
| "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/${{ env.REPO_NAME }}/blob/${{ github.sha }}/mkosi.conf" \ | |
| "org.opencontainers.image.url=https://github.com/${{ github.repository_owner }}/${{ env.REPO_NAME }}/tree/${{ github.sha }}" \ | |
| "org.opencontainers.image.documentation=https://raw.githubusercontent.com/${{ github.repository_owner }}/${{ env.REPO_NAME }}/${{ github.sha }}/README.md" | |
| - name: Chunk image | |
| if: github.event_name != 'pull_request' | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.profile }} | |
| MAX_LAYERS: 128 | |
| run: | | |
| sudo ./shared/outformat/image/chunkah-package.sh \ | |
| "$IMAGE:${{ steps.version.outputs.tag }}" \ | |
| $(date -d '${{ github.event.head_commit.timestamp }}' +%s) | |
| - name: Smoke test - verify SUID bit on sudo | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.profile }} | |
| run: | | |
| mode=$(sudo podman run --rm "$IMAGE:${{ steps.version.outputs.tag }}" stat -c '%a' /usr/bin/sudo) | |
| echo "sudo permissions: $mode" | |
| if [[ "$mode" != "4755" ]]; then | |
| echo "::error::SUID bit not set on /usr/bin/sudo (expected 4755, got $mode)" | |
| exit 1 | |
| fi | |
| - name: Setup Syft | |
| id: setup-syft | |
| if: github.event_name != 'pull_request' | |
| uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 | |
| with: | |
| syft-version: v1.39.0 | |
| - name: Generate SBOM | |
| if: github.event_name != 'pull_request' | |
| id: generate-sbom | |
| env: | |
| PROFILE: ${{ matrix.profile }} | |
| SYFT_CMD: ${{ steps.setup-syft.outputs.cmd }} | |
| run: | | |
| SBOM="$(mktemp -d)/sbom.json" | |
| export SYFT_PARALLELISM=$(($(nproc)*2)) | |
| sudo "${SYFT_CMD}" --source-name "${PROFILE}" "output/${PROFILE}" -o "syft-json=${SBOM}" | |
| du -sh "${SBOM}" | |
| echo "SBOM=${SBOM}" >> "$GITHUB_OUTPUT" | |
| - name: Push image | |
| if: github.event_name != 'pull_request' | |
| id: push | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.profile }} | |
| run: | | |
| sudo buildah push \ | |
| --compression-format=zstd:chunked \ | |
| --digestfile=/tmp/image-digest \ | |
| --creds="${{ github.actor }}:${{ secrets.GHCR_PAT }}" \ | |
| "$IMAGE:${{ steps.version.outputs.tag }}" \ | |
| "docker://$IMAGE:${{ steps.version.outputs.tag }}" | |
| DIGEST=$(sudo cat /tmp/image-digest) | |
| if [[ ! "$DIGEST" =~ ^sha256: ]]; then | |
| echo "::error::Failed to extract valid digest: $DIGEST" | |
| exit 1 | |
| fi | |
| echo "digest=$DIGEST" >> $GITHUB_OUTPUT | |
| sudo buildah tag \ | |
| "$IMAGE:${{ steps.version.outputs.tag }}" \ | |
| "$IMAGE:latest" | |
| sudo buildah push \ | |
| --compression-format=zstd:chunked \ | |
| --creds="${{ github.actor }}:${{ secrets.GHCR_PAT }}" \ | |
| "$IMAGE:latest" \ | |
| "docker://$IMAGE:latest" | |
| - name: Log in to ghcr.io | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GHCR_PAT }} | |
| - name: Install ORAS | |
| if: github.event_name != 'pull_request' | |
| uses: oras-project/setup-oras@38de303aac69abb66f3e6255b7198bff35f323e3 # v2 | |
| - name: Login to GHCR with ORAS | |
| if: github.event_name != 'pull_request' | |
| env: | |
| GHCR_PAT: ${{ secrets.GHCR_PAT }} | |
| run: | | |
| echo "${GHCR_PAT}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin | |
| - name: Install Cosign | |
| if: github.event_name != 'pull_request' | |
| uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 | |
| with: | |
| cosign-release: "v2.6.1" | |
| - name: Upload SBOM | |
| if: github.event_name != 'pull_request' | |
| id: upload-sbom | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.profile }} | |
| DIGEST: ${{ steps.push.outputs.digest }} | |
| SBOM: ${{ steps.generate-sbom.outputs.SBOM }} | |
| run: | | |
| cd "$(dirname "${SBOM}")" | |
| oras attach \ | |
| --artifact-type application/vnd.syft+json \ | |
| --annotation "filename=$(basename "${SBOM}")" \ | |
| "${IMAGE}@${DIGEST}" \ | |
| "$(basename "${SBOM}")" | |
| sbom_digest=$(oras discover --format json "${IMAGE}@${DIGEST}" | jq -r '[.referrers[] | select(.artifactType == "application/vnd.syft+json")] | last | .digest') | |
| if [[ -z "${sbom_digest}" || "${sbom_digest}" == "null" ]]; then | |
| echo "::error::Failed to discover SBOM artifact digest" | |
| exit 1 | |
| fi | |
| echo "sbom_digest=${sbom_digest}" >> "$GITHUB_OUTPUT" | |
| - name: Sign SBOM | |
| if: github.event_name != 'pull_request' | |
| env: | |
| COSIGN_EXPERIMENTAL: "false" | |
| COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.profile }} | |
| SBOM_DIGEST: ${{ steps.upload-sbom.outputs.sbom_digest }} | |
| run: | | |
| cosign sign --key env://COSIGN_PRIVATE_KEY --yes \ | |
| "${IMAGE}@${SBOM_DIGEST}" | |
| - name: Sign image | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| cosign login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GHCR_PAT }}" | |
| cosign sign --key env://COSIGN_PRIVATE_KEY --yes \ | |
| "$IMAGE@${{ steps.push.outputs.digest }}" | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.profile }} | |
| COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} | |
| - name: Attest build provenance | |
| if: github.event_name != 'pull_request' | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 | |
| with: | |
| subject-name: ghcr.io/${{ github.repository_owner }}/${{ matrix.profile }} | |
| subject-digest: ${{ steps.push.outputs.digest }} | |
| push-to-registry: true | |
| - name: Move manifests | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| sudo ./manifestmv.sh | |
| - name: Upload manifests to R2 | |
| if: github.event_name != 'pull_request' | |
| uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c | |
| with: | |
| r2-account-id: ${{ secrets.R2_ACCOUNT_ID }} | |
| r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }} | |
| r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }} | |
| r2-bucket: frostyardrepo | |
| source-dir: output/manifests | |
| destination-dir: manifests/ | |
| - name: cleanup build output | |
| run: | | |
| sudo -E mkosi clean | |
| release: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.build.result == 'success' | |
| continue-on-error: true | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Install ORAS | |
| uses: oras-project/setup-oras@38de303aac69abb66f3e6255b7198bff35f323e3 # v2 | |
| - name: Login to GHCR with ORAS | |
| env: | |
| GHCR_PAT: ${{ secrets.GHCR_PAT }} | |
| run: echo "${GHCR_PAT}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin | |
| - name: Resolve previous and current snowloaded tags | |
| id: versions | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/snowloaded | |
| run: | | |
| set -euo pipefail | |
| tags=$(oras repo tags "$IMAGE" | grep -E '^[0-9]{14}$' | sort -r || true) | |
| if [[ -z "$tags" ]]; then | |
| echo "::warning::No timestamped tags found on $IMAGE; skipping release" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| current=$(echo "$tags" | head -n1) | |
| previous=$(echo "$tags" | sed -n '2p') | |
| if [[ -z "$previous" ]]; then | |
| echo "::warning::Only one timestamped tag on $IMAGE; nothing to diff against; skipping release" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "previous=$previous" >> "$GITHUB_OUTPUT" | |
| echo "current=$current" >> "$GITHUB_OUTPUT" | |
| - name: Generate changelog | |
| id: changelog | |
| if: steps.versions.outputs.skip != 'true' | |
| uses: frostyard/changelog-generator@main | |
| with: | |
| image: ghcr.io/${{ github.repository_owner }}/snowloaded | |
| previous-tag: ${{ steps.versions.outputs.previous }} | |
| current-tag: ${{ steps.versions.outputs.current }} | |
| output-file: changelog.md | |
| - name: Compute release tag and title | |
| id: release-meta | |
| if: steps.versions.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CURRENT: ${{ steps.versions.outputs.current }} | |
| run: | | |
| set -euo pipefail | |
| date_part="${CURRENT:0:4}-${CURRENT:4:2}-${CURRENT:6:2}" | |
| hh="${CURRENT:8:2}"; mm="${CURRENT:10:2}"; ss="${CURRENT:12:2}" | |
| # Daily counter: find highest existing N in releases matching "<date>.N" | |
| existing=$(gh release list --repo "$GITHUB_REPOSITORY" --limit 200 \ | |
| --json tagName --jq \ | |
| "[.[] | .tagName | select(startswith(\"${date_part}.\")) \ | |
| | sub(\"^${date_part}\\\\.\"; \"\") | select(test(\"^[0-9]+$\")) | tonumber] | max // 0") | |
| next=$(( existing + 1 )) | |
| tag="${date_part}.${next}" | |
| title="Build ${date_part} ${hh}:${mm}:${ss} UTC" | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| echo "title=$title" >> "$GITHUB_OUTPUT" | |
| - name: Create GitHub Release | |
| if: steps.versions.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ steps.release-meta.outputs.tag }} | |
| TITLE: ${{ steps.release-meta.outputs.title }} | |
| run: | | |
| gh release create "$TAG" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --target "$GITHUB_SHA" \ | |
| --title "$TITLE" \ | |
| --notes-file changelog.md |