name: tinyproxy CI on: workflow_dispatch: push: branches: ["main"] tags: ["v*"] pull_request: branches: ["main"] concurrency: group: tinyproxy-${{ github.ref }} cancel-in-progress: false env: ARTIFACT_RETENTION_DAYS: 30 LIBEVENT_VERSION: 2.1.12-stable LIBEVENT_TARBALL: libevent-2.1.12-stable.tar.gz LIBEVENT_URL: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz jobs: libevent-source: name: Prepare libevent source runs-on: ubuntu-latest env: LIBEVENT_VERSION: 2.1.12-stable LIBEVENT_TARBALL: libevent-2.1.12-stable.tar.gz LIBEVENT_URL: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz steps: - name: Cache libevent tarball id: cache-libevent-tarball uses: actions/cache@v4 with: path: .cache/libevent/libevent-2.1.12-stable.tar.gz key: libevent-tarball-2.1.12-stable - name: Download libevent tarball if: steps.cache-libevent-tarball.outputs.cache-hit != 'true' run: | set -eu mkdir -p .cache/libevent curl -fL --retry 5 --retry-delay 2 --retry-all-errors \ "$LIBEVENT_URL" \ -o ".cache/libevent/$LIBEVENT_TARBALL" - name: Upload libevent tarball uses: actions/upload-artifact@v4 with: name: libevent-tarball path: .cache/libevent/libevent-2.1.12-stable.tar.gz linux-musl-x86_64: name: Linux musl x86_64 static runs-on: ubuntu-latest needs: libevent-source container: image: &alpine_image alpine:3.23 steps: - &checkout uses: actions/checkout@v4 - &download_libevent_source name: Download libevent tarball uses: actions/download-artifact@v4 with: name: libevent-tarball path: .cache/libevent - &extract_libevent_build name: Extract libevent source run: | set -eu rm -rf libevent mkdir -p libevent tar xf .cache/libevent/libevent-2.1.12-stable.tar.gz \ -C libevent \ --strip-components=1 - &install_linux_deps name: Install deps run: | apk add --no-cache \ build-base \ python3 \ strace \ haproxy - &build_static name: Build shell: sh run: | set -eu mkdir -p dist/debug dist/prod make clean all STATIC=1 EXTRA_CFLAGS="-Og -g3 -Wpedantic" LIBEVENT_SRC=./libevent cp bin/tinyproxy dist/debug/tinyproxy make clean all STATIC=1 LIBEVENT_SRC=./libevent cp bin/tinyproxy dist/prod/tinyproxy strip dist/prod/tinyproxy - &verify_static name: Verify static binary shell: sh run: | set -eu apk add --no-cache pax-utils file dist/prod/tinyproxy scanelf -n dist/prod/tinyproxy if scanelf -n dist/prod/tinyproxy | grep -q 'lib'; then echo "prod binary has dynamic library dependencies" exit 1 fi file dist/debug/tinyproxy scanelf -n dist/debug/tinyproxy if scanelf -n dist/debug/tinyproxy | grep -q 'lib'; then echo "debug binary has dynamic library dependencies" exit 1 fi - &test name: Test run: make test - &package_name name: Set package names shell: sh run: | os="$(printf '%s' '${{ runner.os }}' | tr '[:upper:]' '[:lower:]')" arch="$(printf '%s' '${{ runner.arch }}' | tr '[:upper:]' '[:lower:]')" echo "TINYPROXY_PACKAGE=tinyproxy-${os}-${arch}" >> "$GITHUB_ENV" - &package_unix name: Package artifact shell: sh run: | mkdir -p dist/tinyproxy cp dist/prod/tinyproxy dist/tinyproxy/ cp -r examples dist/tinyproxy/ tar -C dist -czf "${TINYPROXY_PACKAGE}.tar.gz" tinyproxy mkdir -p dist/tinyproxy-debug cp dist/debug/tinyproxy dist/tinyproxy-debug/ cp -r examples dist/tinyproxy-debug/ tar -C dist -czf "${TINYPROXY_PACKAGE}-debug.tar.gz" tinyproxy-debug - &upload_unix name: Upload artifacts uses: actions/upload-artifact@v4 with: name: ${{ env.TINYPROXY_PACKAGE }} path: | ${{ env.TINYPROXY_PACKAGE }}.tar.gz ${{ env.TINYPROXY_PACKAGE }}-debug.tar.gz if-no-files-found: error retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} linux-musl-arm64: name: Linux musl arm64 static runs-on: ubuntu-24.04-arm needs: libevent-source container: image: *alpine_image permissions: contents: read steps: - uses: laverdet/alpine-arm64@7f0f72ee2f71eb2324e5888e8b6e42b1b53e6160 if: runner.arch == 'ARM64' - *checkout - *download_libevent_source - *extract_libevent_build - *install_linux_deps - *build_static - *verify_static - *test - *package_name - *package_unix - *upload_unix linux-riscv64: name: Linux riscv64 (test only) runs-on: ubuntu-latest continue-on-error: true steps: - uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v4 with: platforms: riscv64 - name: Build and test under riscv64 run: | docker run --rm \ --platform linux/riscv64 \ -v "$PWD:/work" \ -w /work \ riscv64/debian:unstable \ sh -euxc ' apt-get update apt-get install -y --no-install-recommends \ build-essential \ libevent-dev \ pkg-config \ python3 \ ca-certificates make clean all LIBEVENT_SRC=./libevent make test \ CONCURRENCY=20 \ TOTAL=20 \ FD_LIMIT=512 \ LARGE_ROUNDTRIP_SIZE=4096 \ SEQUENTIAL_CONNECTIONS=20 ' freebsd-amd64: name: FreeBSD amd64 runs-on: ubuntu-latest steps: - *checkout - name: Build and test on FreeBSD uses: vmactions/freebsd-vm@v1 with: release: "14.2" usesh: true prepare: | pkg install -y gmake pkgconf libevent python3 pcre2 haproxy run: | set -eu gmake clean all EXTRA_CFLAGS="-Og -g3 -Wpedantic" # BSD is running in a VM. Lower the requirements. # GitHub Actions runner VM # -> FreeBSD VM/emulator gmake test \ CONCURRENCY=100 TOTAL=100 FD_LIMIT=2560 \ LARGE_ROUNDTRIP_SIZE=65536 SEQUENTIAL_CONNECTIONS=100 gmake clean all mkdir -p dist/tinyproxy cp bin/tinyproxy dist/tinyproxy/ cp -r examples dist/tinyproxy/ tar -C dist -czf tinyproxy-freebsd-amd64.tar.gz tinyproxy - name: Upload artifact uses: actions/upload-artifact@v4 with: name: tinyproxy-freebsd-amd64 path: tinyproxy-freebsd-amd64.tar.gz if-no-files-found: error retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} macos: name: macOS build runs-on: macos-15 steps: - *checkout - name: Install deps run: | brew list libevent >/dev/null 2>&1 || brew install libevent brew list haproxy >/dev/null 2>&1 || brew install haproxy - name: Build run: | set -eu mkdir -p dist/debug dist/prod make clean all STATIC=0 EXTRA_CFLAGS="-Og -g3 -Wpedantic" cp bin/tinyproxy dist/debug/tinyproxy make clean all STATIC=0 cp bin/tinyproxy dist/prod/tinyproxy strip dist/prod/tinyproxy - *test - *package_name - *package_unix - *upload_unix windows: name: Windows build runs-on: windows-2022 continue-on-error: true steps: - *checkout - name: Set up MSYS2 uses: msys2/setup-msys2@v2 with: msystem: UCRT64 update: true install: >- base-devel mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-pkgconf mingw-w64-ucrt-x86_64-libevent mingw-w64-ucrt-x86_64-python - name: Build shell: msys2 {0} run: | make clean make all - name: Test shell: msys2 {0} run: make test - name: Package artifact shell: msys2 {0} run: | set -eu mkdir -p dist/tinyproxy if [ -f bin/tinyproxy.exe ]; then cp bin/tinyproxy.exe dist/tinyproxy/ else cp bin/tinyproxy dist/tinyproxy/tinyproxy.exe fi copy_deps() { local file="$1" ldd "$file" | awk ' /=>/ { dll = $3 if (dll ~ /^\/ucrt64\/bin\//) { print dll } } /^[[:space:]]*\/ucrt64\/bin\// { print $1 } ' | sort -u | while read -r dll; do base="$(basename "$dll")" if [ ! -f "dist/tinyproxy/$base" ]; then echo "Copying DLL: $dll" cp "$dll" dist/tinyproxy/ copy_deps "dist/tinyproxy/$base" fi done } echo "Runtime DLL dependencies:" ldd dist/tinyproxy/tinyproxy.exe || true copy_deps dist/tinyproxy/tinyproxy.exe echo "Packaged files:" find dist/tinyproxy -maxdepth 1 -type f -printf '%f\n' | sort powershell.exe -NoProfile -Command \ "Compress-Archive -Path dist/tinyproxy -DestinationPath tinyproxy-windows-x64.zip -Force" - name: Upload CI artifact uses: actions/upload-artifact@v4 with: name: tinyproxy-windows-x64 path: tinyproxy-windows-x64.zip if-no-files-found: error retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} release: name: GitHub Release runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') needs: - linux-musl-x86_64 - linux-musl-arm64 - freebsd-amd64 - macos - windows permissions: contents: write steps: - name: Download CI artifacts uses: actions/download-artifact@v4 with: path: release-assets pattern: tinyproxy-* merge-multiple: true - name: Show release assets run: | find release-assets -type f -maxdepth 2 -print - name: Create GitHub Release env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} run: | set -eu tag="${GITHUB_REF_NAME}" body_file="$(mktemp)" if printf '%s\n' "$tag" | grep -Eq '^v0\.'; then cat > "$body_file" <<'EOF' > [!WARNING] > This is an experimental v0.x release. > > APIs, config formats, behavior, and compatibility may change without notice. EOF else : > "$body_file" fi if ! gh release view "$tag" >/dev/null 2>&1; then gh release create "$tag" \ --title "$tag" \ --notes-file "$body_file" \ --generate-notes fi echo "Assets currently on release:" gh release view "$tag" --json assets --jq '.assets[].name' for asset in release-assets/*; do name="$(basename "$asset")" existing="$( gh release view "$tag" --json assets --jq '.assets[].name' | awk -v target="$name" 'tolower($0) == tolower(target) { print; exit }' )" if [ -n "$existing" ]; then echo "Deleting existing release asset: $existing" gh release delete-asset "$tag" "$existing" -y fi echo "Uploading release asset: $name" gh release upload "$tag" "$asset" done docker: name: Docker image runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') needs: - linux-musl-x86_64 - linux-musl-arm64 permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - name: Download Linux artifacts uses: actions/download-artifact@v4 with: path: docker-artifacts pattern: tinyproxy-linux-* merge-multiple: true - name: Prepare Docker rootfs run: | mkdir -p rootfs-amd64 rootfs-arm64 mkdir -p tmp/amd64 tmp/arm64 tar -xzf docker-artifacts/tinyproxy-linux-x64.tar.gz -C tmp/amd64 tar -xzf docker-artifacts/tinyproxy-linux-arm64.tar.gz -C tmp/arm64 mkdir -p rootfs-amd64/usr/bin rootfs-amd64/etc mkdir -p rootfs-arm64/usr/bin rootfs-arm64/etc cp tmp/amd64/tinyproxy/tinyproxy rootfs-amd64/usr/bin/tinyproxy cp tmp/arm64/tinyproxy/tinyproxy rootfs-arm64/usr/bin/tinyproxy chmod 0755 rootfs-amd64/usr/bin/tinyproxy chmod 0755 rootfs-arm64/usr/bin/tinyproxy file rootfs-amd64/usr/bin/tinyproxy file rootfs-arm64/usr/bin/tinyproxy - name: Set image name run: | image="ghcr.io/${GITHUB_REPOSITORY}" echo "IMAGE_NAME=${image,,}" >> "$GITHUB_ENV" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Login to GHCR uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - name: Build and push image uses: docker/build-push-action@v7 with: context: . file: ./github.Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: | ${{ env.IMAGE_NAME }}:${{ github.ref_name }} labels: | org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.version=${{ github.ref_name }} org.opencontainers.image.revision=${{ github.sha }}