ping-rust 0.1.8

Menu-driven installer and manager for the shoes proxy server
name: Release

on:
  push:
    tags:
      - "v[0-9]+.[0-9]+.[0-9]+"
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: false

jobs:
  build:
    name: Build ${{ matrix.target }}
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        target:
          - x86_64-unknown-linux-musl
          - aarch64-unknown-linux-musl
    steps:
      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
      - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
      - uses: taiki-e/install-action@07b4745e0c39a41822af610387492e3e53aa222b # v2
        with:
          tool: cross
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          key: release-${{ matrix.target }}

      - name: Build static binary
        run: cross build --locked --release --target "${{ matrix.target }}"

      - name: Install ARM64 runtime emulator
        if: matrix.target == 'aarch64-unknown-linux-musl'
        run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends qemu-user-static

      - name: Verify and package binary
        env:
          TARGET: ${{ matrix.target }}
        shell: bash
        run: |
          set -euo pipefail
          binary="target/${TARGET}/release/ping-rust"
          test -x "${binary}"

          file "${binary}"
          if readelf -l "${binary}" | grep -q "Requesting program interpreter"; then
            echo "error: ${TARGET} binary contains a dynamic program interpreter" >&2
            exit 1
          fi
          if readelf -d "${binary}" 2>/dev/null | grep -q "(NEEDED)"; then
            echo "error: ${TARGET} binary contains dynamic library dependencies" >&2
            exit 1
          fi

          if [ "${TARGET}" = "aarch64-unknown-linux-musl" ]; then
            version_output="$(qemu-aarch64-static "${binary}" --version)"
          else
            version_output="$("${binary}" --version)"
          fi
          printf '%s\n' "${version_output}"
          printf '%s\n' "${version_output}" \
            | grep -Eq '^ping-rust [0-9]+\.[0-9]+\.[0-9]+([.+-][0-9A-Za-z.-]+)?$'

          package_dir="dist/${TARGET}"
          mkdir -p "${package_dir}"
          install -m 0755 "${binary}" "${package_dir}/ping-rust"
          tar --sort=name --owner=0 --group=0 --numeric-owner \
            --mtime='UTC 1970-01-01' -C "${package_dir}" -cf - ping-rust \
            | gzip -9n > "dist/ping-rust-${TARGET}.tar.gz"
          sha256sum "dist/ping-rust-${TARGET}.tar.gz"

      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: ping-rust-${{ matrix.target }}
          path: dist/ping-rust-${{ matrix.target }}.tar.gz
          if-no-files-found: error

  release:
    name: Publish GitHub Release
    if: startsWith(github.ref, 'refs/tags/v')
    needs: build
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5

      - name: Verify tag matches Cargo version
        shell: bash
        run: |
          set -euo pipefail
          manifest_version="$(python3 -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["version"])')"
          tag_version="${GITHUB_REF_NAME#v}"
          if [ "${manifest_version}" != "${tag_version}" ]; then
            echo "error: tag ${GITHUB_REF_NAME} does not match Cargo.toml version ${manifest_version}" >&2
            exit 1
          fi

      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          path: dist
          merge-multiple: true

      - name: Generate checksums
        shell: bash
        run: |
          set -euo pipefail
          cd dist
          sha256sum ping-rust-*.tar.gz | sort -k 2 > SHA256SUMS
          sha256sum --check SHA256SUMS
          cat SHA256SUMS

      - name: Create GitHub Release
        env:
          GH_TOKEN: ${{ github.token }}
        shell: bash
        run: |
          set -euo pipefail
          if gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1; then
            gh release upload "${GITHUB_REF_NAME}" dist/* --clobber
          else
            gh release create "${GITHUB_REF_NAME}" dist/* \
              --verify-tag \
              --title "ping-rust ${GITHUB_REF_NAME}" \
              --generate-notes
          fi

      - name: Verify published one-click installer
        shell: bash
        run: |
          set -euo pipefail
          install_dir="${RUNNER_TEMP}/ping-rust-bin"
          bash scripts/install.sh \
            --version "${GITHUB_REF_NAME}" \
            --install-dir "${install_dir}" \
            --quiet \
            --no-bootstrap
          "${install_dir}/ping-rust" --version \
            | grep -Fx "ping-rust ${GITHUB_REF_NAME#v}"
          test -L "${install_dir}/prs"
          test "$(readlink "${install_dir}/prs")" = ping-rust
          test ! -e "${install_dir}/sb"
          "${install_dir}/prs" --version \
            | grep -Fx "ping-rust ${GITHUB_REF_NAME#v}"
          "${install_dir}/ping-rust" self-update \
            --version "${GITHUB_REF_NAME}" \
            --force
          "${install_dir}/ping-rust" --version \
            | grep -Fx "ping-rust ${GITHUB_REF_NAME#v}"

          conflict_dir="${RUNNER_TEMP}/ping-rust-conflict"
          mkdir -p "${conflict_dir}"
          printf 'owned-by-another-program\n' > "${conflict_dir}/prs"
          bash scripts/install.sh \
            --version "${GITHUB_REF_NAME}" \
            --install-dir "${conflict_dir}" \
            --quiet \
            --no-bootstrap
          grep -Fx 'owned-by-another-program' "${conflict_dir}/prs"
          test -x "${conflict_dir}/ping-rust"

          legacy_dir="${RUNNER_TEMP}/ping-rust-legacy-alias"
          mkdir -p "${legacy_dir}"
          ln -s ping-rust "${legacy_dir}/sb"
          bash scripts/install.sh \
            --version "${GITHUB_REF_NAME}" \
            --install-dir "${legacy_dir}" \
            --quiet \
            --no-bootstrap
          test ! -e "${legacy_dir}/sb"
          test "$(readlink "${legacy_dir}/prs")" = ping-rust

          bootstrap_dir="${RUNNER_TEMP}/ping-rust-bootstrap"
          bootstrap_output="${RUNNER_TEMP}/ping-rust-bootstrap-output.txt"
          mkdir -p "${bootstrap_dir}"
          bash scripts/install.sh \
            --version "${GITHUB_REF_NAME}" \
            --install-dir "${bootstrap_dir}" \
            > "${bootstrap_output}"
          ! grep -Eq '选择协议|输入端口' "${bootstrap_output}"
          grep -q '首次安装:自动部署 VLESS-REALITY' "${bootstrap_output}"
          grep -q 'vless://' "${bootstrap_output}"
          bootstrap_uri="$(sudo "${bootstrap_dir}/ping-rust" url)"
          printf '%s' "${bootstrap_uri}" | grep -q '^vless://'
          sudo systemctl is-active --quiet shoes.service
          bootstrap_info="$(sudo "${bootstrap_dir}/ping-rust" info)"
          bootstrap_port="$(
            printf '%s' "${bootstrap_info}" \
              | sed -nE 's/.*0\.0\.0\.0:([0-9]+).*/\1/p' \
              | head -n 1
          )"
          test -n "${bootstrap_port}"
          sudo ss -H -lnt \
            | awk '{print $4}' \
            | grep -Eq "(^|:)${bootstrap_port}$"
          second_bootstrap="$(sudo "${bootstrap_dir}/ping-rust" bootstrap)"
          printf '%s' "${second_bootstrap}" \
            | grep -q '检测到已有 shoes 配置,跳过默认 VLESS-REALITY 部署。'
          rm -f "${bootstrap_output}"
          sudo "${bootstrap_dir}/ping-rust" uninstall --purge