chore: version bumps #16
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
| # Build and publish release artifacts when a version tag is pushed. | |
| # | |
| # Produces: | |
| # - Python wheel (.whl) for the ai-horde-stats-exporter package | |
| # - Dashboard tarball (horde-dashboards-<version>.tar.gz) with app/ and infra/ dirs | |
| # - Release manifest JSON (release-manifest.json) with filenames, checksums, and version | |
| # - SHA-256 checksums file (checksums.txt) | |
| # | |
| # All artifacts are attached to a GitHub Release created from the tag. | |
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| packages: write | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install uv and Python | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| python-version: "3.12" | |
| enable-cache: true | |
| - name: Extract version from tag | |
| id: version | |
| run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" | |
| - name: Build wheel | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ steps.version.outputs.version }}" | |
| # Ensure the wheel version matches the release tag. | |
| # This is authoritative — pyproject.toml carries a development | |
| # placeholder that is overwritten here at release time. | |
| cd packages/ai-horde-stats-exporter | |
| sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml | |
| uv build --wheel --out-dir ../../dist/ | |
| - name: Assemble dashboard tarball | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ steps.version.outputs.version }}" | |
| STAGING=$(mktemp -d) | |
| # App dashboards (from the Python package source tree) | |
| mkdir -p "$STAGING/app" | |
| cp packages/ai-horde-stats-exporter/src/ai_horde_stats_exporter/dashboards/*.json \ | |
| "$STAGING/app/" | |
| # Infra dashboards (from the top-level dashboards directory) | |
| mkdir -p "$STAGING/infra" | |
| find dashboards -name '*.json' | while read -r src; do | |
| dest="$STAGING/infra/$(basename "$src")" | |
| python3 scripts/convert_grafana_v2_to_classic.py "$src" "$dest" | |
| done | |
| # Guardrail: release artifact must contain classic dashboard JSON. | |
| python3 - <<'PY' | |
| import json | |
| import pathlib | |
| base = pathlib.Path("$STAGING") / "infra" | |
| bad = [] | |
| for path in sorted(base.glob("*.json")): | |
| data = json.loads(path.read_text()) | |
| if isinstance(data, dict) and str(data.get("apiVersion", "")).startswith("dashboard.grafana.app/"): | |
| bad.append(f"{path.name}: still has apiVersion={data.get('apiVersion')}") | |
| if "panels" not in data: | |
| bad.append(f"{path.name}: missing panels") | |
| if "templating" not in data: | |
| bad.append(f"{path.name}: missing templating") | |
| if bad: | |
| raise SystemExit("\n".join(bad)) | |
| PY | |
| tar -czf "dist/horde-dashboards-${VERSION}.tar.gz" -C "$STAGING" app infra | |
| rm -rf "$STAGING" | |
| - name: Generate checksums and manifest | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ steps.version.outputs.version }}" | |
| cd dist | |
| # checksums.txt — one line per file | |
| sha256sum * > checksums.txt | |
| # Structured manifest for automation consumers | |
| WHEEL_FILE=$(ls *.whl) | |
| DASHBOARD_FILE="horde-dashboards-${VERSION}.tar.gz" | |
| WHEEL_SHA=$(sha256sum "$WHEEL_FILE" | cut -d' ' -f1) | |
| DASHBOARD_SHA=$(sha256sum "$DASHBOARD_FILE" | cut -d' ' -f1) | |
| cat > release-manifest.json <<EOF | |
| { | |
| "version": "${VERSION}", | |
| "tag": "${GITHUB_REF_NAME}", | |
| "artifacts": { | |
| "wheel": { | |
| "filename": "${WHEEL_FILE}", | |
| "sha256": "${WHEEL_SHA}" | |
| }, | |
| "dashboards": { | |
| "filename": "${DASHBOARD_FILE}", | |
| "sha256": "${DASHBOARD_SHA}" | |
| } | |
| } | |
| } | |
| EOF | |
| # Compact the JSON (remove leading whitespace from heredoc) | |
| python3 -c " | |
| import json, pathlib | |
| p = pathlib.Path('release-manifest.json') | |
| p.write_text(json.dumps(json.loads(p.read_text()), indent=2) + '\n') | |
| " | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-artifacts | |
| path: dist/ | |
| images: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - package: ai-horde-stats-exporter | |
| manifest_key: ai_horde_stats_exporter | |
| image: ghcr.io/haidra-org/ai-horde-stats-exporter | |
| - package: ai-horde-service-alerts | |
| manifest_key: ai_horde_service_alerts | |
| image: ghcr.io/haidra-org/ai-horde-service-alerts | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Compute image metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ matrix.image }} | |
| tags: | | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{major}} | |
| type=raw,value=latest | |
| type=sha,format=short | |
| - name: Build and push image | |
| id: build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: packages/${{ matrix.package }} | |
| file: packages/${{ matrix.package }}/Dockerfile | |
| platforms: linux/amd64 | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| provenance: true | |
| sbom: true | |
| - name: Emit per-image manifest fragment | |
| env: | |
| MATRIX_KEY: ${{ matrix.manifest_key }} | |
| IMAGE: ${{ matrix.image }} | |
| DIGEST: ${{ steps.build.outputs.digest }} | |
| run: | | |
| set -euo pipefail | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| mkdir -p image-manifests | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| import pathlib | |
| fragment = { | |
| os.environ["MATRIX_KEY"]: { | |
| "ref": f"{os.environ['IMAGE']}:{os.environ['GITHUB_REF_NAME'][1:]}", | |
| "digest": os.environ["DIGEST"], | |
| } | |
| } | |
| out = pathlib.Path("image-manifests") / f"{os.environ['MATRIX_KEY']}.json" | |
| out.write_text(json.dumps(fragment, indent=2) + "\n") | |
| PY | |
| - name: Upload image manifest fragment | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: image-manifest-${{ matrix.manifest_key }} | |
| path: image-manifests/ | |
| manifest: | |
| needs: [build, images] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-artifacts | |
| path: dist/ | |
| - name: Download image manifest fragments | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: image-manifest-* | |
| path: image-manifests/ | |
| merge-multiple: true | |
| - name: Augment release manifest with image refs | |
| run: | | |
| set -euo pipefail | |
| python3 - <<'PY' | |
| import json | |
| import pathlib | |
| manifest_path = pathlib.Path("dist/release-manifest.json") | |
| data = json.loads(manifest_path.read_text()) | |
| images: dict = {} | |
| for fragment in sorted(pathlib.Path("image-manifests").glob("*.json")): | |
| images.update(json.loads(fragment.read_text())) | |
| if not images: | |
| raise SystemExit("no image manifest fragments found") | |
| data["images"] = images | |
| manifest_path.write_text(json.dumps(data, indent=2) + "\n") | |
| PY | |
| - name: Refresh checksums | |
| run: | | |
| set -euo pipefail | |
| cd dist | |
| sha256sum * > checksums.txt | |
| - name: Upload updated artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-artifacts-final | |
| path: dist/ | |
| overwrite: true | |
| release: | |
| needs: manifest | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-artifacts-final | |
| path: dist/ | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release create "$GITHUB_REF_NAME" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --title "$GITHUB_REF_NAME" \ | |
| --generate-notes \ | |
| dist/* |