Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
299 changes: 299 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
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
Comment thread
polaz marked this conversation as resolved.

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
Comment thread
polaz marked this conversation as resolved.
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
Comment thread
polaz marked this conversation as resolved.

- 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
Comment thread
polaz marked this conversation as resolved.
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 ..
Comment thread
polaz marked this conversation as resolved.

- 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
Comment thread
polaz marked this conversation as resolved.

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 }}"
43 changes: 43 additions & 0 deletions packaging/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# structured-proxy configuration.
#
# This is a TEMPLATE. The service ships disabled because the proxy needs a
# service-specific config before it can do anything useful: at minimum an
# upstream gRPC address and one proto descriptor set. Edit this file, then:
#
# sudo systemctl enable --now structured-proxy
#
# Read by /usr/lib/systemd/system/structured-proxy.service via:
# ExecStart=/usr/bin/structured-proxy --config /etc/structured-proxy/config.yaml
#
# Marked as a config file in the RPM/DEB package, so local edits survive
# upgrades. Full reference: https://github.com/structured-world/structured-proxy
#
# Any file path referenced below (descriptor sets, JWT/OIDC PEM keys, JWKS
# caches) must live somewhere the hardened unit can read. The service runs with
# ProtectHome=yes and ProtectSystem=strict, so /home, /root and /run/user are
# hidden and most of the filesystem is read-only — keep referenced files under
# /etc/structured-proxy (readable) rather than an operator home directory.

# HTTP listen address for the transcoded REST surface.
listen:
http: "0.0.0.0:8080"

# Upstream gRPC service this proxy transcodes to. REQUIRED — replace with your
# service address.
upstream:
default: "http://127.0.0.1:50051"

# Pre-compiled proto descriptor sources (one or more, merged into one pool).
# REQUIRED for any routes to be generated. Build one with:
# buf build -o my-service.descriptor.bin
# # or: protoc --descriptor_set_out=my-service.descriptor.bin --include_imports *.proto
descriptors: []
# - file: "/etc/structured-proxy/my-service.descriptor.bin"

# Service identity (drives the /health response and metrics namespace).
service:
name: "structured-proxy"

# CORS: empty list = permissive (dev). List exact origins for production.
cors:
origins: []
6 changes: 6 additions & 0 deletions packaging/deb/debian/changelog
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
structured-proxy (0.0.0) unstable; urgency=medium

* Placeholder. CI overwrites this file with the real changelog entry
generated from the release tag before invoking dpkg-buildpackage.

-- Release Bot <oss@sw.foundation> Mon, 11 May 2026 00:00:00 +0000
18 changes: 18 additions & 0 deletions packaging/deb/debian/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Source: structured-proxy
Section: net
Priority: optional
Maintainer: Structured World Foundation <oss@sw.foundation>
Build-Depends: debhelper-compat (= 13)
Standards-Version: 4.6.2
Homepage: https://github.com/structured-world/structured-proxy

Package: structured-proxy
Architecture: amd64 arm64
Depends: ${misc:Depends}, systemd, adduser
Description: Universal gRPC to REST transcoding proxy
structured-proxy is a config-driven gRPC to REST transcoding proxy. One
binary serves any gRPC service from a proto descriptor set and a YAML config,
with no code generation and no custom handlers.
.
The service ships disabled: set an upstream address and proto descriptors in
/etc/structured-proxy/config.yaml, then enable it with systemctl.
Loading