name: Release

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:

permissions:
  contents: write

jobs:
  build:
    name: ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - { os: ubuntu-latest,  target: x86_64-unknown-linux-gnu,       cross: false }
          - { os: ubuntu-latest,  target: x86_64-unknown-linux-musl,      cross: true  }
          - { os: ubuntu-latest,  target: aarch64-unknown-linux-gnu,      cross: true  }
          - { os: ubuntu-latest,  target: aarch64-unknown-linux-musl,     cross: true  }
          - { os: ubuntu-latest,  target: armv7-unknown-linux-musleabihf, cross: true  }
          - { os: ubuntu-latest,  target: arm-unknown-linux-musleabi,     cross: true  }
          - { os: macos-latest,   target: x86_64-apple-darwin,            cross: false }
          - { os: macos-latest,   target: aarch64-apple-darwin,           cross: false }
          - { os: windows-latest, target: x86_64-pc-windows-msvc,         cross: false }
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Install cross
        if: matrix.cross
        uses: taiki-e/install-action@v2
        with:
          tool: cross

      - name: Build (cargo)
        if: ${{ !matrix.cross }}
        run: cargo build --release --locked --target ${{ matrix.target }}

      - name: Build (cross)
        if: matrix.cross
        run: cross build --release --locked --target ${{ matrix.target }}

      # UPX compression for musl targets only — these go to size-constrained
      # routers / embedded boxes. Desktops (gnu, macOS, Windows) keep the raw
      # binary: UPX adds tiny startup overhead and trips some Windows AVs, and
      # the size win matters less when 100 MB of disk is cheap.
      - name: Install UPX
        if: contains(matrix.target, '-musl')
        run: sudo apt-get update && sudo apt-get install -y upx-ucl

      - name: Compress with UPX
        if: contains(matrix.target, '-musl')
        run: upx --best --lzma "target/${{ matrix.target }}/release/srunc"

      # Build a Debian .deb for the glibc Linux targets — .deb is for
      # Debian/Ubuntu which use glibc, not musl.
      - name: Install cargo-deb
        if: matrix.target == 'x86_64-unknown-linux-gnu' || matrix.target == 'aarch64-unknown-linux-gnu'
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-deb

      - name: Build .deb
        if: matrix.target == 'x86_64-unknown-linux-gnu' || matrix.target == 'aarch64-unknown-linux-gnu'
        run: cargo deb --target ${{ matrix.target }} --no-build --no-strip

      - name: Package
        shell: bash
        run: |
          bin=srunc
          ext=""
          [ "${{ runner.os }}" = "Windows" ] && ext=".exe"
          dist="srunc-${{ matrix.target }}"
          mkdir -p staging/"$dist"
          cp "target/${{ matrix.target }}/release/$bin$ext" staging/"$dist"/
          cp LICENSE staging/"$dist"/ 2>/dev/null || true
          cp README.md staging/"$dist"/ 2>/dev/null || true
          # Ship service files + config example with every archive so users
          # can drop them straight onto routers / boxes.
          cp -r docs staging/"$dist"/ 2>/dev/null || true
          cd staging
          if [ "${{ runner.os }}" = "Windows" ]; then
            7z a "$dist.zip" "$dist" >/dev/null
          else
            tar czf "$dist.tar.gz" "$dist"
          fi

      # Pick up the .deb produced by cargo-deb (only present on gnu targets).
      - name: Stage .deb
        if: matrix.target == 'x86_64-unknown-linux-gnu' || matrix.target == 'aarch64-unknown-linux-gnu'
        shell: bash
        run: find target/${{ matrix.target }}/debian -name '*.deb' -exec cp {} staging/ \;

      - uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.target }}
          path: |
            staging/*.tar.gz
            staging/*.zip
            staging/*.deb

  ipk:
    name: ${{ matrix.arch }} .ipk
    needs: build
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          # OpenWrt SDK arch → which musl tarball from `build` to bundle.
          # We pick the most common router architectures; others can build
          # locally by dropping a binary into packaging/openwrt/luci-app-srunc/files/.
          - { arch: "x86_64-24.10.0",                       musl_target: "x86_64-unknown-linux-musl"      }
          - { arch: "aarch64_cortex-a53-24.10.0",           musl_target: "aarch64-unknown-linux-musl"     }
          - { arch: "aarch64_cortex-a72-24.10.0",           musl_target: "aarch64-unknown-linux-musl"     }
          - { arch: "aarch64_generic-24.10.0",              musl_target: "aarch64-unknown-linux-musl"     }
          - { arch: "arm_cortex-a7_neon-vfpv4-24.10.0",     musl_target: "armv7-unknown-linux-musleabihf" }
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # Pull the matching prebuilt musl binary from the `build` matrix.
      - name: Download prebuilt binary
        uses: actions/download-artifact@v4
        with:
          name: ${{ matrix.musl_target }}
          path: prebuilt/

      - name: Stage binary into the package
        run: |
          # luci.mk auto-installs everything under root/* → / on the target,
          # preserving executable bits via `cp -pR`. Drop the binary at
          # root/usr/sbin/srunc — no custom INSTALL_BIN rule needed (and any
          # custom Package/luci-app-srunc/install we define would be clobbered
          # by luci.mk's unconditional template anyway).
          tar -xzf prebuilt/srunc-${{ matrix.musl_target }}.tar.gz -C prebuilt/
          mkdir -p packaging/openwrt/luci-app-srunc/root/usr/sbin
          cp prebuilt/srunc-${{ matrix.musl_target }}/srunc \
             packaging/openwrt/luci-app-srunc/root/usr/sbin/srunc
          chmod +x packaging/openwrt/luci-app-srunc/root/usr/sbin/srunc
          ls -la packaging/openwrt/luci-app-srunc/root/usr/sbin/

      - name: Build .ipk via openwrt SDK
        uses: openwrt/gh-action-sdk@main
        env:
          ARCH: ${{ matrix.arch }}
          FEED_DIR: ${{ github.workspace }}/packaging/openwrt
          FEEDNAME: srunc
          PACKAGES: luci-app-srunc
          V: s
          # Skip the SDK's opinionated source-tree checks. shfmt nags
          # at our procd init.d formatting and patch-refresh wants a git
          # context that doesn't exist inside the SDK container.
          NO_SHFMT_CHECK: 1
          NO_REFRESH_CHECK: 1

      - name: Stage .ipk
        shell: bash
        run: |
          mkdir -p staging
          find bin -name 'luci-app-srunc_*.ipk' -exec cp {} staging/ \;
          ls -la staging/

      - uses: actions/upload-artifact@v4
        with:
          name: luci-app-srunc-ipk-${{ matrix.arch }}
          path: staging/*.ipk

  release:
    name: Publish release
    needs: [build, ipk]
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/')
    steps:
      - uses: actions/download-artifact@v4
        with:
          path: dist
          merge-multiple: true

      - name: Checksums
        run: |
          cd dist
          shopt -s nullglob
          sha256sum *.tar.gz *.zip *.ipk *.deb > SHA256SUMS
          cat SHA256SUMS

      - uses: softprops/action-gh-release@v2
        with:
          files: dist/*

  publish:
    # Publish srunc to crates.io. Runs in parallel with `release` — the
    # GitHub Release is a separate artifact channel, so a transient publish
    # failure must not block the GH Release upload (and vice versa).
    #
    # One-time setup: add `CARGO_REGISTRY_TOKEN` to repo secrets
    # (Settings → Secrets and variables → Actions). Token from
    # https://crates.io/me with scope `publish-new + publish-update`.
    name: Publish to crates.io
    needs: [build, ipk]
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable

      - name: Sanity-check tag matches Cargo.toml
        run: |
          tag_ver="${GITHUB_REF_NAME#v}"
          ws_ver=$(grep -m1 '^version' Cargo.toml | cut -d'"' -f2)
          [ "$tag_ver" = "$ws_ver" ] || {
            echo "tag $tag_ver != package version $ws_ver"; exit 1;
          }

      - uses: katyo/publish-crates@v2
        with:
          registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
