structured-proxy 2.2.1

Universal gRPC→REST transcoding proxy — config-driven, works with any gRPC service
Documentation
name: Release

# Builds native packages for a published release and ships them to
# repo.sw.foundation. Runs after release-plz creates the GitHub release/tag;
# can also be re-run manually against an existing tag.
on:
  release:
    types: [created]
  workflow_dispatch:
    inputs:
      tag_name:
        description: "Release tag to build (e.g. v2.1.0)"
        required: true

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: 0
  TAG_NAME: ${{ github.event.release.tag_name || inputs.tag_name }}

permissions:
  contents: write

jobs:
  build-musl:
    name: Build musl (${{ matrix.target }})
    runs-on: ${{ matrix.runs-on }}
    strategy:
      matrix:
        include:
          - target: x86_64-unknown-linux-musl
            artifact: structured-proxy-linux-amd64
            runs-on: ubuntu-latest
          - target: aarch64-unknown-linux-musl
            artifact: structured-proxy-linux-arm64
            runs-on: ubuntu-24.04-arm
    steps:
      - uses: actions/checkout@v6
        with:
          # Build from the exact released tag, so a manual re-run for an older
          # tag packages that tag's commit rather than whatever ref triggered
          # the workflow (which would mislabel the assets with the wrong version).
          ref: ${{ env.TAG_NAME }}

      - name: Install Rust + musl target
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Install musl tools
        run: |
          sudo apt-get update
          sudo apt-get install -y musl-tools

      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}

      - name: Build static binary
        run: |
          # `redis` is compiled in so the packaged `shield.redis_url` config
          # works (multi-instance shared rate limits) instead of silently
          # falling back to per-process counters. It is a pure-Rust dependency,
          # so it stays musl-static-clean.
          cargo build --release --target ${{ matrix.target }} --features redis --bin structured-proxy
          strip target/${{ matrix.target }}/release/structured-proxy || true

      - name: Package
        run: |
          cp target/${{ matrix.target }}/release/structured-proxy \
             structured-proxy-${{ env.TAG_NAME }}-${{ matrix.artifact }}

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.artifact }}
          path: structured-proxy-${{ env.TAG_NAME }}-${{ matrix.artifact }}
          retention-days: 1

      - name: Attach binary to release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ env.TAG_NAME }}
          files: structured-proxy-${{ env.TAG_NAME }}-${{ matrix.artifact }}

  build-rpm:
    name: Build RPM (${{ matrix.fedora }}/${{ matrix.arch }})
    runs-on: ${{ matrix.runs-on }}
    needs: build-musl
    container:
      image: registry.fedoraproject.org/fedora:${{ matrix.fedora }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - { fedora: "42", arch: x86_64,  runs-on: ubuntu-latest,      bin_artifact: structured-proxy-linux-amd64 }
          - { fedora: "42", arch: aarch64, runs-on: ubuntu-24.04-arm,   bin_artifact: structured-proxy-linux-arm64 }
          - { fedora: "43", arch: x86_64,  runs-on: ubuntu-latest,      bin_artifact: structured-proxy-linux-amd64 }
          - { fedora: "43", arch: aarch64, runs-on: ubuntu-24.04-arm,   bin_artifact: structured-proxy-linux-arm64 }
          - { fedora: "44", arch: x86_64,  runs-on: ubuntu-latest,      bin_artifact: structured-proxy-linux-amd64 }
          - { fedora: "44", arch: aarch64, runs-on: ubuntu-24.04-arm,   bin_artifact: structured-proxy-linux-arm64 }
    steps:
      - name: Install rpmbuild + helpers
        run: |
          dnf install -y --setopt=install_weak_deps=False \
            rpm-build rpmdevtools systemd-rpm-macros findutils tar
          rpmdev-setuptree

      - uses: actions/checkout@v6
        with:
          # Build from the exact released tag, so a manual re-run for an older
          # tag packages that tag's commit rather than whatever ref triggered
          # the workflow (which would mislabel the assets with the wrong version).
          ref: ${{ env.TAG_NAME }}

      - name: Download prebuilt binary
        uses: actions/download-artifact@v4
        with:
          name: ${{ matrix.bin_artifact }}
          path: /tmp/bin/

      - name: Stage sources
        run: |
          VERSION=$(echo "${{ env.TAG_NAME }}" | sed 's/^v//')
          echo "VERSION=$VERSION" >> "$GITHUB_ENV"
          BIN=$(find /tmp/bin -type f -name 'structured-proxy-*' | head -1)
          install -m 0755 "$BIN"                              ~/rpmbuild/SOURCES/structured-proxy
          install -m 0644 packaging/structured-proxy.service  ~/rpmbuild/SOURCES/structured-proxy.service
          install -m 0644 packaging/config.yaml               ~/rpmbuild/SOURCES/config.yaml
          install -m 0644 packaging/structured-proxy.sysusers ~/rpmbuild/SOURCES/structured-proxy.sysusers
          install -m 0644 LICENSE                             ~/rpmbuild/SOURCES/LICENSE
          install -m 0644 README.md                           ~/rpmbuild/SOURCES/README.md

      - name: Build RPM
        run: |
          rpmbuild -bb \
            --define "_topdir $HOME/rpmbuild" \
            --define "version $VERSION" \
            --target ${{ matrix.arch }} \
            packaging/rpm/structured-proxy.spec
          find ~/rpmbuild/RPMS -name '*.rpm' -print

      - name: Lint with rpmlint (advisory)
        continue-on-error: true
        run: |
          dnf install -y --setopt=install_weak_deps=False rpmlint || true
          rpmlint $(find ~/rpmbuild/RPMS -name '*.rpm') || true

      - name: Stage artifact
        run: |
          mkdir -p /tmp/artifacts
          cp ~/rpmbuild/RPMS/${{ matrix.arch }}/*.rpm /tmp/artifacts/

      - name: Upload RPM artifact
        uses: actions/upload-artifact@v4
        with:
          name: rpm-fc${{ matrix.fedora }}-${{ matrix.arch }}
          path: /tmp/artifacts/*.rpm
          if-no-files-found: error

      - name: Attach RPM to release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ env.TAG_NAME }}
          files: /tmp/artifacts/*.rpm

  build-deb:
    name: Build DEB (${{ matrix.codename }}/${{ matrix.arch }})
    runs-on: ${{ matrix.runs-on }}
    needs: build-musl
    container:
      image: ${{ matrix.image }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - { codename: bookworm, image: "debian:bookworm", arch: amd64, runs-on: ubuntu-latest,    bin_artifact: structured-proxy-linux-amd64 }
          - { codename: bookworm, image: "debian:bookworm", arch: arm64, runs-on: ubuntu-24.04-arm, bin_artifact: structured-proxy-linux-arm64 }
          - { codename: jammy,    image: "ubuntu:jammy",    arch: amd64, runs-on: ubuntu-latest,    bin_artifact: structured-proxy-linux-amd64 }
          - { codename: jammy,    image: "ubuntu:jammy",    arch: arm64, runs-on: ubuntu-24.04-arm, bin_artifact: structured-proxy-linux-arm64 }
          - { codename: noble,    image: "ubuntu:noble",    arch: amd64, runs-on: ubuntu-latest,    bin_artifact: structured-proxy-linux-amd64 }
          - { codename: noble,    image: "ubuntu:noble",    arch: arm64, runs-on: ubuntu-24.04-arm, bin_artifact: structured-proxy-linux-arm64 }
    steps:
      - name: Install debhelper + dpkg-dev
        env:
          DEBIAN_FRONTEND: noninteractive
        run: |
          apt-get update
          # build-essential satisfies the implicit Build-Depends pulled in by
          # debhelper-compat (= 13); without it dpkg-checkbuilddeps aborts.
          apt-get install -y --no-install-recommends \
            debhelper dpkg-dev dh-make ca-certificates findutils \
            build-essential adduser

      - uses: actions/checkout@v6
        with:
          # Build from the exact released tag, so a manual re-run for an older
          # tag packages that tag's commit rather than whatever ref triggered
          # the workflow (which would mislabel the assets with the wrong version).
          ref: ${{ env.TAG_NAME }}

      - name: Download prebuilt binary
        uses: actions/download-artifact@v4
        with:
          name: ${{ matrix.bin_artifact }}
          path: /tmp/bin/

      - name: Assemble source tree
        run: |
          VERSION=$(echo "${{ env.TAG_NAME }}" | sed 's/^v//')
          echo "VERSION=$VERSION" >> "$GITHUB_ENV"
          BUILD=/tmp/structured-proxy-$VERSION
          mkdir -p "$BUILD/packaging"
          BIN=$(find /tmp/bin -type f -name 'structured-proxy-*' | head -1)
          install -m 0755 "$BIN" "$BUILD/structured-proxy"
          cp -r packaging/structured-proxy.service packaging/config.yaml \
                packaging/structured-proxy.sysusers \
                "$BUILD/packaging/"
          cp -r packaging/deb/debian "$BUILD/"
          cp LICENSE README.md "$BUILD/"
          # Rewrite the placeholder changelog with the actual version.
          cat > "$BUILD/debian/changelog" <<CHANGELOG
          structured-proxy ($VERSION) unstable; urgency=medium

            * Automated release $VERSION; see CHANGELOG.md upstream.

           -- Release Bot <oss@sw.foundation>  $(date -R)
          CHANGELOG
          echo "BUILD_DIR=$BUILD" >> "$GITHUB_ENV"

      - name: Build DEB
        working-directory: ${{ env.BUILD_DIR }}
        run: |
          dpkg-buildpackage -us -uc -b -a${{ matrix.arch }}
          ls -la ..

      - name: Lint with lintian (advisory)
        continue-on-error: true
        run: |
          apt-get install -y --no-install-recommends lintian || true
          lintian /tmp/structured-proxy_*_${{ matrix.arch }}.deb || true

      - name: Stage artifact
        run: |
          mkdir -p /tmp/artifacts
          cp /tmp/structured-proxy_${VERSION}_${{ matrix.arch }}.deb /tmp/artifacts/

      - name: Upload DEB artifact
        uses: actions/upload-artifact@v4
        with:
          name: deb-${{ matrix.codename }}-${{ matrix.arch }}
          path: /tmp/artifacts/*.deb
          if-no-files-found: error

      - name: Attach DEB to release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ env.TAG_NAME }}
          files: /tmp/artifacts/*.deb

  publish-repo:
    name: Publish to repo.sw.foundation
    runs-on: ubuntu-latest
    needs: [build-rpm, build-deb]
    steps:
      - uses: actions/checkout@v6
        with:
          # Build from the exact released tag, so a manual re-run for an older
          # tag packages that tag's commit rather than whatever ref triggered
          # the workflow (which would mislabel the assets with the wrong version).
          ref: ${{ env.TAG_NAME }}

      - name: Repo metadata
        run: |
          mkdir -p /tmp/repo-meta
          cp packaging/manifest.json /tmp/repo-meta/

      - name: Upload repo-meta artifact
        uses: actions/upload-artifact@v4
        with:
          name: repo-meta-structured-proxy
          path: /tmp/repo-meta/

      - name: Generate release bot token
        id: app-token
        uses: actions/create-github-app-token@v1
        with:
          app-id: ${{ secrets.RELEASER_APP_ID }}
          private-key: ${{ secrets.RELEASER_APP_PRIVATE_KEY }}
          repositories: repo

      - name: Trigger repo publish
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
        run: |
          gh api repos/structured-world/repo/dispatches \
            --method POST \
            -f event_type=publish-from-structured-proxy \
            -f client_payload[structured_proxy_run_id]="${{ github.run_id }}" \
            -f client_payload[structured_proxy_repo]="${{ github.repository }}"