feat: Add Docker release workflow and enhance release process with Te… #4
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| inputs: | |
| release_tag: | |
| description: "Tag name to publish (example: v1.2.0). Leave empty for build-only run." | |
| required: false | |
| type: string | |
| publish: | |
| description: "Publish GitHub release" | |
| required: false | |
| default: false | |
| type: boolean | |
| make_public: | |
| description: "Make release public immediately (false = draft + prerelease)" | |
| required: false | |
| default: false | |
| type: boolean | |
| include_termux: | |
| description: "Publish Termux ARM64 and ARMv7 bundles" | |
| required: false | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: write | |
| jobs: | |
| build-binaries: | |
| name: Build ${{ matrix.platform }}-${{ matrix.arch }} | |
| runs-on: ${{ matrix.os }} | |
| continue-on-error: ${{ matrix.optional }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: windows-latest | |
| platform: windows | |
| arch: amd64 | |
| optional: false | |
| - os: ubuntu-latest | |
| platform: linux | |
| arch: amd64 | |
| optional: false | |
| - os: macos-13 | |
| platform: macos | |
| arch: amd64 | |
| optional: false | |
| - os: macos-14 | |
| platform: macos | |
| arch: arm64 | |
| optional: false | |
| - os: ubuntu-24.04-arm | |
| platform: linux | |
| arch: arm64 | |
| optional: true | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install build dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -r requirements.txt pyinstaller | |
| - name: Build standalone binary | |
| run: pyinstaller --noconfirm --clean --onefile --name MasterHttpRelayVPN --paths src main.py | |
| - name: Smoke test binary | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ runner.os }}" = "Windows" ]; then | |
| BIN="dist/MasterHttpRelayVPN.exe" | |
| else | |
| BIN="dist/MasterHttpRelayVPN" | |
| chmod +x "$BIN" | |
| fi | |
| "$BIN" --version | |
| "$BIN" --help >/dev/null | |
| - name: Package release artifact (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $version = ((Get-Content "src/core/constants.py" -Raw) -match '__version__\s*=\s*"([^"]+)"') > $null; $Matches[1] | |
| if (-not $version) { throw "Could not read version" } | |
| New-Item -ItemType Directory -Path "release-assets" -Force | Out-Null | |
| $staging = "release-staging" | |
| Remove-Item -Recurse -Force $staging -ErrorAction SilentlyContinue | |
| New-Item -ItemType Directory -Path $staging -Force | Out-Null | |
| Copy-Item "dist/MasterHttpRelayVPN.exe" "$staging/MasterHttpRelayVPN.exe" | |
| foreach ($f in @("README.md", "README_FA.md", "config.example.json", "start.bat", "start.sh")) { | |
| if (Test-Path $f) { Copy-Item $f $staging } | |
| } | |
| $archive = "MasterHttpRelayVPN-$version-${{ matrix.platform }}-${{ matrix.arch }}.zip" | |
| Compress-Archive -Path "$staging/*" -DestinationPath "release-assets/$archive" -Force | |
| $hash = (Get-FileHash "release-assets/$archive" -Algorithm SHA256).Hash.ToLower() | |
| "$hash $archive" | Out-File -FilePath "release-assets/$archive.sha256" -Encoding ascii | |
| - name: Package release artifact (non-Windows) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| version=$(python - <<'PY' | |
| import re | |
| from pathlib import Path | |
| t = Path('src/core/constants.py').read_text(encoding='utf-8') | |
| m = re.search(r'__version__\s*=\s*"([^"]+)"', t) | |
| print(m.group(1) if m else '0.0.0') | |
| PY | |
| ) | |
| mkdir -p release-assets release-staging | |
| rm -rf release-staging/* | |
| cp dist/MasterHttpRelayVPN release-staging/MasterHttpRelayVPN | |
| chmod +x release-staging/MasterHttpRelayVPN | |
| for f in README.md README_FA.md config.example.json start.sh start.bat; do | |
| [ -f "$f" ] && cp "$f" release-staging/ | |
| done | |
| archive="MasterHttpRelayVPN-${version}-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz" | |
| tar -C release-staging -czf "release-assets/$archive" . | |
| sha256sum "release-assets/$archive" | awk '{print $1 " " FILENAME}' FILENAME="$archive" > "release-assets/$archive.sha256" | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: release-assets/* | |
| build-termux: | |
| name: Build termux-arm64-armv7 | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' || github.event.inputs.include_termux == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Build Termux binaries (arm64 + armv7) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| version=$(python - <<'PY' | |
| import re | |
| from pathlib import Path | |
| t = Path('src/core/constants.py').read_text(encoding='utf-8') | |
| m = re.search(r'__version__\s*=\s*"([^"]+)"', t) | |
| print(m.group(1) if m else '0.0.0') | |
| PY | |
| ) | |
| mkdir -p release-assets termux-dist | |
| build_termux_arch () { | |
| arch="$1" | |
| platform="$2" | |
| image="$3" | |
| rm -rf dist build *.spec || true | |
| docker run --rm --platform "$platform" \ | |
| -v "$PWD:/work" \ | |
| -w /work \ | |
| "$image" \ | |
| bash -lc ' | |
| set -euo pipefail | |
| pkg update -y | |
| pkg install -y python clang make pkg-config libffi openssl rust binutils | |
| python -m pip install --upgrade pip | |
| pip install pyinstaller | |
| pip install -r requirements.txt | |
| pyinstaller --noconfirm --clean --onefile --name MasterHttpRelayVPN --paths src main.py | |
| ' | |
| if [ ! -f dist/MasterHttpRelayVPN ]; then | |
| echo "Missing Termux binary output for ${arch}" >&2 | |
| exit 1 | |
| fi | |
| cp dist/MasterHttpRelayVPN "termux-dist/MasterHttpRelayVPN-${arch}" | |
| chmod +x "termux-dist/MasterHttpRelayVPN-${arch}" | |
| } | |
| build_termux_arch "arm64" "linux/arm64" "termux/termux-docker:aarch64" | |
| build_termux_arch "armv7" "linux/arm/v7" "termux/termux-docker:arm" | |
| for arch in arm64 armv7; do | |
| staging="termux-staging-${arch}" | |
| rm -rf "$staging" | |
| mkdir -p "$staging" | |
| cp "termux-dist/MasterHttpRelayVPN-${arch}" "$staging/MasterHttpRelayVPN" | |
| [ -f config.example.json ] && cp config.example.json "$staging/" | |
| [ -f README.md ] && cp README.md "$staging/" | |
| [ -f README_FA.md ] && cp README_FA.md "$staging/" | |
| printf '%s\n' \ | |
| '#!/data/data/com.termux/files/usr/bin/bash' \ | |
| 'set -euo pipefail' \ | |
| 'chmod +x ./MasterHttpRelayVPN' \ | |
| 'exec ./MasterHttpRelayVPN "$@"' \ | |
| > "$staging/termux-run.sh" | |
| chmod +x "$staging/termux-run.sh" | |
| archive="MasterHttpRelayVPN-${version}-termux-${arch}.zip" | |
| (cd "$staging" && zip -qr "../release-assets/${archive}" .) | |
| sha256sum "release-assets/${archive}" | awk '{print $1 " " FILENAME}' FILENAME="$archive" > "release-assets/${archive}.sha256" | |
| done | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-termux | |
| path: release-assets/* | |
| publish-release: | |
| name: Publish GitHub Release | |
| runs-on: ubuntu-latest | |
| needs: [build-binaries, build-termux] | |
| if: always() && !cancelled() && (startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true' && startsWith(github.event.inputs.release_tag, 'v'))) | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: release-assets | |
| - name: Flatten artifact directories | |
| run: | | |
| mkdir -p final-assets | |
| find release-assets -type f -exec cp {} final-assets/ \; | |
| ls -lah final-assets | |
| - name: Fail if no artifacts | |
| run: | | |
| set -euo pipefail | |
| count=$(find final-assets -type f | wc -l) | |
| echo "Artifact count: ${count}" | |
| if [ "${count}" -eq 0 ]; then | |
| echo "No artifacts were produced." | |
| exit 1 | |
| fi | |
| - name: Create GitHub release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || github.event.inputs.release_tag }} | |
| files: final-assets/* | |
| generate_release_notes: true | |
| prerelease: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.make_public != 'true' }} | |
| draft: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.make_public != 'true' }} | |
| make_latest: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.make_public == 'true') && 'true' || 'false' }} |