From ff311c04f1c9ab9825238821e6cb2488f7352ce2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 26 Oct 2026 13:39:02 +0000 Subject: [PATCH] Attach Linux packages to GitHub Releases The build-linux-packages workflow already built .deb/.rpm files on publish, but only uploaded Actions artifacts. Add a follow-up job that downloads those packages, writes SHA256SUMS, and uploads them to the triggering release with gh release upload. workflow_dispatch now requires a tag so past releases can be rebuilt and backfilled. Checkout uses that tag (or github.event.release.tag_name). GITHUB_TOKEN with contents: write is enough when release-drafter publishes the release. Docs now point at the release assets from 3.3.1 on. Co-authored-by: Kenneth Belitzky --- .github/workflows/build-linux-packages.yaml | 125 +++++++++++++++++--- README.md | 2 +- docs/installation.md | 27 +++-- packaging/README.md | 36 +++--- 4 files changed, 145 insertions(+), 45 deletions(-) diff --git a/.github/workflows/build-linux-packages.yaml b/.github/workflows/build-linux-packages.yaml index 98715bf..ea590bc 100644 --- a/.github/workflows/build-linux-packages.yaml +++ b/.github/workflows/build-linux-packages.yaml @@ -6,10 +6,17 @@ on: - published workflow_dispatch: inputs: - version: - description: 'Package version (e.g., 3.3.0)' - required: false - default: '' + tag: + description: 'Release tag to check out, build, and attach packages to (e.g. v3.3.1)' + required: true + type: string + +# GITHUB_TOKEN can attach assets to a release published by release-drafter. +permissions: + contents: write + +env: + RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag }} jobs: build-packages: @@ -23,6 +30,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v7 + with: + ref: ${{ env.RELEASE_TAG }} - name: Set up Python uses: actions/setup-python@v6 @@ -34,17 +43,16 @@ jobs: run: | set -euo pipefail - # Use workflow input if provided, otherwise extract from pyproject.toml - if [ -n "${{ github.event.inputs.version }}" ]; then - VERSION="${{ github.event.inputs.version }}" - elif [ "${GITHUB_EVENT_NAME}" = "release" ]; then - VERSION="${GITHUB_REF_NAME#v}" - else - VERSION=$(python -c "import tomllib; f = open('pyproject.toml', 'rb'); data = tomllib.load(f); f.close(); print(data['project']['version'])") + TAG="${RELEASE_TAG}" + if [ -z "${TAG}" ]; then + echo "A release tag is required (release event or workflow_dispatch tag input)" + exit 1 fi - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "Package version: ${VERSION}" + VERSION="${TAG#v}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "Package version: ${VERSION} (tag ${TAG})" - name: Install nfpm run: | @@ -113,6 +121,26 @@ jobs: # List generated packages ls -lh dist/*.{deb,rpm} 2>/dev/null || true + VERSION="${{ steps.version.outputs.version }}" + ARCH="${{ steps.arch.outputs.nfpm_arch }}" + shopt -s nullglob + packages=(dist/*.deb dist/*.rpm) + if [ "${#packages[@]}" -eq 0 ]; then + echo "nfpm did not produce a .deb or .rpm" + exit 1 + fi + for pkg in "${packages[@]}"; do + base="$(basename "${pkg}")" + if [[ "${base}" != *"${VERSION}"* ]]; then + echo "Package name is missing version ${VERSION}: ${base}" + exit 1 + fi + if [[ "${base}" != *"${ARCH}"* && "${base}" != *x86_64* && "${base}" != *aarch64* ]]; then + echo "Package name is missing architecture: ${base}" + exit 1 + fi + done + - name: Upload package artifacts uses: actions/upload-artifact@v7 with: @@ -122,9 +150,76 @@ jobs: dist/*.rpm retention-days: 90 + upload-release-assets: + name: Upload packages to GitHub Release + needs: build-packages + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all package artifacts + uses: actions/download-artifact@v7 + with: + path: dist + pattern: structkit-* + merge-multiple: true + + - name: Generate checksums and upload to release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: | + set -euo pipefail + shopt -s nullglob + + TAG="${RELEASE_TAG}" + if [ -z "${TAG}" ]; then + echo "A release tag is required (release event or workflow_dispatch tag input)" + exit 1 + fi + VERSION="${TAG#v}" + + packages=(dist/*.deb dist/*.rpm) + if [ "${#packages[@]}" -eq 0 ]; then + echo "No .deb or .rpm packages found to upload" + exit 1 + fi + + for pkg in "${packages[@]}"; do + base="$(basename "${pkg}")" + if [[ "${base}" != *"${VERSION}"* ]]; then + echo "Package name is missing version ${VERSION}: ${base}" + exit 1 + fi + if [[ "${base}" != *amd64* && "${base}" != *arm64* && "${base}" != *x86_64* && "${base}" != *aarch64* ]]; then + echo "Package name is missing architecture: ${base}" + exit 1 + fi + done + + ( + cd dist + sha256sum -- *.deb *.rpm | sort -k2 > SHA256SUMS + echo "SHA256SUMS:" + cat SHA256SUMS + ) + + echo "Uploading to release ${TAG}:" + ls -lh dist/*.deb dist/*.rpm dist/SHA256SUMS + + gh release upload "${TAG}" dist/*.deb dist/*.rpm dist/SHA256SUMS --clobber + + - name: Summarize release assets + run: | + { + echo "## Packages attached to ${RELEASE_TAG}" + echo "" + echo "Uploaded .deb, .rpm, and SHA256SUMS to https://github.com/${{ github.repository }}/releases/tag/${RELEASE_TAG}" + } >> "$GITHUB_STEP_SUMMARY" + summary: name: Build Summary - needs: build-packages + needs: [build-packages, upload-release-assets] runs-on: ubuntu-latest if: always() @@ -137,4 +232,4 @@ jobs: echo "- Debian/Ubuntu (.deb) - amd64, arm64" >> $GITHUB_STEP_SUMMARY echo "- Fedora/RHEL (.rpm) - amd64, arm64" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "Download artifacts from the Actions tab above." >> $GITHUB_STEP_SUMMARY + echo "Packages are attached to the GitHub release (when the upload job succeeds) and also available as Actions artifacts." >> $GITHUB_STEP_SUMMARY diff --git a/README.md b/README.md index c625771..157a972 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ These are install paths for [StructKit](https://github.com/httpdss/structkit), n - [Agent skills](https://github.com/httpdss/structkit-skills) — inspect, preview, generate, validate from an assistant - [pre-commit](https://github.com/httpdss/structkit-pre-commit) — validate structures before they land - [Homebrew](https://github.com/httpdss/homebrew-structkit) — `brew install httpdss/structkit/structkit` -- Linux `.deb` / `.rpm` — download from [build-linux-packages](https://github.com/httpdss/structkit/actions/workflows/build-linux-packages.yaml) workflow artifacts (not attached to GitHub Releases), or build locally per [packaging/README.md](packaging/README.md) +- Linux `.deb` / `.rpm` — download from the [GitHub release](https://github.com/httpdss/structkit/releases) (attached from 3.3.1 on), or build locally per [packaging/README.md](packaging/README.md) - [Backstage](https://github.com/httpdss/backstage-plugin-structkit) — scaffolder actions for platform teams - [Hermes plugin](https://github.com/httpdss/hermes-plugin-structkit) — safe StructKit tools inside Hermes diff --git a/docs/installation.md b/docs/installation.md index 0989801..a62d287 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -47,30 +47,31 @@ Refer to the [Quick Start](quickstart.md) guide for more options. ## Linux packages (.deb / .rpm) -Native `.deb` (Debian/Ubuntu) and `.rpm` (Fedora/RHEL) packages are built by the [`build-linux-packages`](https://github.com/httpdss/structkit/actions/workflows/build-linux-packages.yaml) GitHub Actions workflow on each published release and on manual workflow dispatch. They are uploaded as **Actions workflow artifacts** (90-day retention). They are **not** attached to GitHub Releases. +Native `.deb` (Debian/Ubuntu) and `.rpm` (Fedora/RHEL) packages are attached to each [GitHub Release](https://github.com/httpdss/structkit/releases) starting with **3.3.1**. The [`build-linux-packages`](https://github.com/httpdss/structkit/actions/workflows/build-linux-packages.yaml) workflow builds them on every published release (and on manual `workflow_dispatch` with a tag) and uploads them to that release. A `SHA256SUMS` file is published alongside the packages. The same files are also kept as Actions artifacts (90-day retention). -To install a release package: +Asset names include the version and architecture, for example: -1. Open the [build-linux-packages workflow runs](https://github.com/httpdss/structkit/actions/workflows/build-linux-packages.yaml) and select the run for the release you want. -2. Download the artifact for your architecture: - - `structkit-VERSION-amd64-deb` / `structkit-VERSION-arm64-deb` - - `structkit-VERSION-amd64-rpm` / `structkit-VERSION-arm64-rpm` -3. Install from the downloaded file. +- `structkit_3.3.1_amd64.deb` / `structkit_3.3.1_arm64.deb` +- `structkit-3.3.1-1.x86_64.rpm` / `structkit-3.3.1-1.aarch64.rpm` -Debian/Ubuntu: +Debian/Ubuntu (amd64): ```sh -sudo dpkg -i structkit_VERSION_amd64.deb -# if needed: sudo apt-get install -f +curl -fsSL -o structkit_3.3.1_amd64.deb \ + https://github.com/httpdss/structkit/releases/download/v3.3.1/structkit_3.3.1_amd64.deb +sudo apt install ./structkit_3.3.1_amd64.deb ``` -Fedora/RHEL: +Fedora/RHEL (x86_64): ```sh -sudo dnf install ./structkit-VERSION-1.x86_64.rpm -# or: sudo rpm -ivh structkit-VERSION-1.x86_64.rpm +curl -fsSL -o structkit-3.3.1-1.x86_64.rpm \ + https://github.com/httpdss/structkit/releases/download/v3.3.1/structkit-3.3.1-1.x86_64.rpm +sudo dnf install ./structkit-3.3.1-1.x86_64.rpm ``` +For arm64, download `structkit_3.3.1_arm64.deb` or `structkit-3.3.1-1.aarch64.rpm` from the same release URL and install the local file the same way. Replace `3.3.1` / `v3.3.1` with the version you want. + To build packages locally instead, follow [packaging/README.md](https://github.com/httpdss/structkit/blob/main/packaging/README.md). ## GitHub Actions diff --git a/packaging/README.md b/packaging/README.md index 70f4f4c..fb1318b 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -95,13 +95,13 @@ The built packages will be in the `dist/` directory: ## Installing Packages -Packages are **not** attached to GitHub Releases. Download them from the [build-linux-packages](https://github.com/httpdss/structkit/actions/workflows/build-linux-packages.yaml) workflow run artifacts for the release, or build them locally with the steps above. +Packages are attached to each [GitHub Release](https://github.com/httpdss/structkit/releases) starting with 3.3.1. Download the `.deb` / `.rpm` (and `SHA256SUMS`) from the release, or build them locally with the steps above. ### Debian/Ubuntu ```bash -# Download the .deb artifact from the workflow run, then: -sudo dpkg -i structkit_VERSION_amd64.deb +# Download the .deb from the GitHub release, then: +sudo apt install ./structkit_VERSION_amd64.deb # If there are dependency issues, resolve them with: sudo apt-get install -f @@ -110,7 +110,7 @@ sudo apt-get install -f ### Fedora/RHEL ```bash -# Download the .rpm artifact from the workflow run, then: +# Download the .rpm from the GitHub release, then: sudo dnf install ./structkit-VERSION-1.x86_64.rpm # Or using rpm directly: @@ -131,32 +131,36 @@ structkit --help The `.github/workflows/build-linux-packages.yaml` workflow automatically builds packages when: 1. **On release** — Triggered when a new GitHub release is published -2. **Manual dispatch** — Can be triggered manually from the Actions tab +2. **Manual dispatch** — Can be triggered from the Actions tab with a required `tag` input (for example `v3.3.1`) to rebuild that tag and attach packages to that release The workflow: +- Checks out the release tag (or the `workflow_dispatch` tag) - Builds packages for both `amd64` and `arm64` architectures - Creates both `.deb` and `.rpm` packages (4 total artifacts) - Uploads packages as GitHub Actions artifacts (90-day retention) -- Does **not** attach packages to GitHub Releases -- Can be triggered with a custom version via workflow dispatch +- Attaches the packages and a `SHA256SUMS` file to the corresponding GitHub Release ### Triggering a Manual Build 1. Go to **Actions** → **build-linux-packages** 2. Click **Run workflow** -3. Optionally specify a version (defaults to version in `pyproject.toml`) +3. Enter the release tag to backfill (for example `v3.3.1`) 4. Click **Run workflow** ### Downloading Built Packages -After the workflow completes: -1. Go to the workflow run page -2. Scroll to **Artifacts** section -3. Download the desired package(s): - - `structkit-VERSION-amd64-deb` - - `structkit-VERSION-arm64-deb` - - `structkit-VERSION-amd64-rpm` - - `structkit-VERSION-arm64-rpm` +After the workflow completes, download packages from the [GitHub release](https://github.com/httpdss/structkit/releases) for that tag: + +- `structkit_VERSION_amd64.deb` / `structkit_VERSION_arm64.deb` +- `structkit-VERSION-1.x86_64.rpm` / `structkit-VERSION-1.aarch64.rpm` +- `SHA256SUMS` + +The same files are also available as Actions artifacts: + +- `structkit-VERSION-amd64-deb` +- `structkit-VERSION-arm64-deb` +- `structkit-VERSION-amd64-rpm` +- `structkit-VERSION-arm64-rpm` ## Package Contents