diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 3c310244..13fdadfc 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -9,8 +9,17 @@ on: permissions: actions: write + contents: write jobs: + public-key: + if: inputs.release_tag != '' + uses: ./.github/workflows/pub-key.yml + with: + release_tag: ${{ inputs.release_tag }} + secrets: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + update: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 2cf6546c..464fcdc3 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -12,6 +12,7 @@ on: permissions: contents: write + actions: read jobs: build: @@ -26,6 +27,7 @@ jobs: with: target: linux release_tag: ${{ inputs.release_tag }} + secrets: inherit deb: name: build and release deb x64 & arm64 @@ -56,9 +58,9 @@ jobs: find "$GITHUB_WORKSPACE/dist/deb" -name "v2rayn_*_arm64.deb" -exec mv {} "$GITHUB_WORKSPACE/dist/deb/v2rayN-linux-arm64.deb" \; || true echo "==== Dist tree ====" ls -R "$GITHUB_WORKSPACE/dist/deb" || true - - name: Upload DEBs to release - uses: svenstaro/upload-release-action@v2 - with: { file: "dist/deb/**/*.deb", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true } + - name: Upload DEB artifact + uses: actions/upload-artifact@v7 + with: { name: release-deb, path: "dist/deb/**/*.deb", if-no-files-found: error } rpm: name: build and release rpm x64 & arm64 @@ -115,9 +117,9 @@ jobs: find "$GITHUB_WORKSPACE/dist/rpm" -name "v2rayN-*-1*.aarch64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true echo "==== Dist tree ====" ls -R "$GITHUB_WORKSPACE/dist/rpm" || true - - name: Upload RPMs to release - uses: svenstaro/upload-release-action@v2 - with: { file: "dist/rpm/**/*.rpm", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true } + - name: Upload RPM artifact + uses: actions/upload-artifact@v7 + with: { name: release-rpm, path: "dist/rpm/**/*.rpm", if-no-files-found: error } rpm-riscv64: name: build and release rpm riscv64 @@ -155,25 +157,9 @@ jobs: find "$GITHUB_WORKSPACE/dist/rpm-riscv64" -name "*.riscv64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm-riscv64/v2rayN-linux-rhel-riscv64.rpm" \; || true echo "==== Dist tree ====" ls -R "$GITHUB_WORKSPACE/dist/rpm-riscv64" || true - - name: Upload RPMs to release - shell: bash - env: { GITHUB_TOKEN: "${{ github.token }}" } - run: | - set -euo pipefail - shopt -s globstar nullglob - files=(dist/rpm-riscv64/**/*.rpm) - (( ${#files[@]} )) || { echo "No RPMs found."; exit 1; } - api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}" - upload_url="$(curl -fsSL -H "Authorization: Bearer ${GITHUB_TOKEN}" "$api" | jq -r '.upload_url // empty' | sed 's/{?name,label}//')" - [[ "$upload_url" ]] || { echo "Release upload URL not found: ${RELEASE_TAG}"; exit 1; } - for f in "${files[@]}"; do - echo "Uploading ${f##*/}" - curl -fsSL -X POST \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - -H "Content-Type: application/x-rpm" \ - --data-binary @"$f" \ - "${upload_url}?name=${f##*/}" - done + - name: Upload RPM artifact + uses: actions/upload-artifact@v7 + with: { name: release-rpm-riscv64, path: "dist/rpm-riscv64/**/*.rpm", if-no-files-found: error } deb-riscv64: name: build and release deb riscv64 @@ -212,25 +198,9 @@ jobs: find "$GITHUB_WORKSPACE/dist/deb-riscv64" -name "*.deb" -exec mv {} "$GITHUB_WORKSPACE/dist/deb-riscv64/v2rayN-linux-riscv64.deb" \; || true echo "==== Dist tree ====" ls -R "$GITHUB_WORKSPACE/dist/deb-riscv64" || true - - name: Upload DEBs to release - shell: bash - env: { GITHUB_TOKEN: "${{ github.token }}" } - run: | - set -euo pipefail - shopt -s globstar nullglob - files=(dist/deb-riscv64/**/*.deb) - (( ${#files[@]} )) || { echo "No DEBs found."; exit 1; } - api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}" - upload_url="$(curl -fsSL -H "Authorization: Bearer ${GITHUB_TOKEN}" "$api" | jq -r '.upload_url // empty' | sed 's/{?name,label}//')" - [[ "$upload_url" ]] || { echo "Release upload URL not found: ${RELEASE_TAG}"; exit 1; } - for f in "${files[@]}"; do - echo "Uploading ${f##*/}" - curl -fsSL -X POST \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - -H "Content-Type: application/vnd.debian.binary-package" \ - --data-binary @"$f" \ - "${upload_url}?name=${f##*/}" - done + - name: Upload DEB artifact + uses: actions/upload-artifact@v7 + with: { name: release-deb-riscv64, path: "dist/deb-riscv64/**/*.deb", if-no-files-found: error } deb-loong64: name: build and release deb loong64 @@ -378,9 +348,9 @@ jobs: find "$GITHUB_WORKSPACE/dist/deb-loong64" -name "*.deb" -exec mv {} "$GITHUB_WORKSPACE/dist/deb-loong64/v2rayN-linux-loong64.deb" \; || true echo "==== Dist tree ====" ls -R "$GITHUB_WORKSPACE/dist/deb-loong64" - - name: Upload DEBs to release - uses: svenstaro/upload-release-action@v2 - with: { file: "dist/deb-loong64/**/*.deb", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true } + - name: Upload DEB artifact + uses: actions/upload-artifact@v7 + with: { name: release-deb-loong64, path: "dist/deb-loong64/**/*.deb", if-no-files-found: error } rpm-loong64: name: build and release rpm loong64 @@ -528,6 +498,48 @@ jobs: find "$GITHUB_WORKSPACE/dist/rpm-loong64" -name "*.loongarch64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm-loong64/v2rayN-linux-rhel-loong64.rpm" \; || true echo "==== Dist tree ====" ls -R "$GITHUB_WORKSPACE/dist/rpm-loong64" - - name: Upload RPMs to release - uses: svenstaro/upload-release-action@v2 - with: { file: "dist/rpm-loong64/**/*.rpm", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true } + - name: Upload RPM artifact + uses: actions/upload-artifact@v7 + with: { name: release-rpm-loong64, path: "dist/rpm-loong64/**/*.rpm", if-no-files-found: error } + + release-deb: + name: sign and upload deb x64 arm64 + needs: deb + uses: ./.github/workflows/upload-sign.yml + with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-deb, file_pattern: "*.deb" } + secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" } + + release-rpm: + name: sign and upload rpm x64 arm64 + needs: rpm + uses: ./.github/workflows/upload-sign.yml + with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-rpm, file_pattern: "*.rpm" } + secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" } + + release-rpm-riscv64: + name: sign and upload rpm riscv64 + needs: rpm-riscv64 + uses: ./.github/workflows/upload-sign.yml + with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-rpm-riscv64, file_pattern: "*.rpm" } + secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" } + + release-deb-riscv64: + name: sign and upload deb riscv64 + needs: deb-riscv64 + uses: ./.github/workflows/upload-sign.yml + with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-deb-riscv64, file_pattern: "*.deb" } + secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" } + + release-deb-loong64: + name: sign and upload deb loong64 + needs: deb-loong64 + uses: ./.github/workflows/upload-sign.yml + with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-deb-loong64, file_pattern: "*.deb" } + secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" } + + release-rpm-loong64: + name: sign and upload rpm loong64 + needs: rpm-loong64 + uses: ./.github/workflows/upload-sign.yml + with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-rpm-loong64, file_pattern: "*.rpm" } + secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" } diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml index c54d49d7..47b6dbe0 100644 --- a/.github/workflows/build-osx.yml +++ b/.github/workflows/build-osx.yml @@ -12,6 +12,7 @@ on: permissions: contents: write + actions: read jobs: build: @@ -26,6 +27,7 @@ jobs: with: target: macos release_tag: ${{ inputs.release_tag }} + secrets: inherit dmg: name: package and release macOS dmg @@ -62,13 +64,24 @@ jobs: - name: Package dmg run: ./package-osx.sh macos-$Arch v2rayN-macos-$Arch ${{ inputs.release_tag }} - - name: Sleep for race condition between matrix jobs - run: sleep "$(od -An -N2 -tu2 /dev/urandom | awk 'NR==1{printf "%.2f", $1/5461}')" - - - name: Upload dmg to release - uses: svenstaro/upload-release-action@v2 + - name: Upload dmg artifact + uses: actions/upload-artifact@v7 with: - file: ${{ github.workspace }}/v2rayN*.dmg - tag: ${{ inputs.release_tag }} - file_glob: true - prerelease: true + name: release-dmg-${{ matrix.arch }} + path: v2rayN*.dmg + if-no-files-found: error + + release-dmg: + name: sign and upload macOS dmg + if: inputs.release_tag != '' + needs: dmg + strategy: + matrix: + arch: [ x64, arm64 ] + uses: ./.github/workflows/upload-sign.yml + with: + release_tag: ${{ inputs.release_tag }} + artifact_name: release-dmg-${{ matrix.arch }} + file_pattern: '*.dmg' + secrets: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} diff --git a/.github/workflows/build-windows-desktop.yml b/.github/workflows/build-windows-desktop.yml index 9a9579a4..a473b96b 100644 --- a/.github/workflows/build-windows-desktop.yml +++ b/.github/workflows/build-windows-desktop.yml @@ -12,6 +12,7 @@ on: permissions: contents: write + actions: read jobs: build: @@ -26,3 +27,4 @@ jobs: with: target: windows-desktop release_tag: ${{ inputs.release_tag }} + secrets: inherit diff --git a/.github/workflows/build-windows-x86.yml b/.github/workflows/build-windows-x86.yml index fba71a12..dc539ae0 100644 --- a/.github/workflows/build-windows-x86.yml +++ b/.github/workflows/build-windows-x86.yml @@ -9,6 +9,7 @@ on: permissions: contents: write + actions: read jobs: package-x86: @@ -222,10 +223,25 @@ jobs: -DestinationPath "${{ matrix.zip_name }}" ` -Force - - name: Upload zip archive to release - uses: svenstaro/upload-release-action@v2 + - name: Upload zip artifact + uses: actions/upload-artifact@v7 with: - file: ${{ matrix.zip_name }} - tag: ${{ inputs.release_tag }} - prerelease: true - overwrite: true + name: release-windows-x86-${{ matrix.flavor }} + path: ${{ matrix.zip_name }} + if-no-files-found: error + + release-x86: + name: sign and upload windows x86 + if: inputs.release_tag != '' + needs: package-x86 + strategy: + fail-fast: false + matrix: + flavor: [ wpf, desktop ] + uses: ./.github/workflows/upload-sign.yml + with: + release_tag: ${{ inputs.release_tag }} + artifact_name: release-windows-x86-${{ matrix.flavor }} + file_pattern: '*.zip' + secrets: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 4bcba3d6..6dd1b238 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -12,6 +12,7 @@ on: permissions: contents: write + actions: read jobs: build: @@ -27,3 +28,4 @@ jobs: with: target: windows release_tag: ${{ inputs.release_tag }} + secrets: inherit diff --git a/.github/workflows/package-zip.yml b/.github/workflows/package-zip.yml index 1c36c25f..3b6e7116 100644 --- a/.github/workflows/package-zip.yml +++ b/.github/workflows/package-zip.yml @@ -9,9 +9,13 @@ on: target: # windows linux macos windows-desktop required: true type: string + secrets: + GPG_PRIVATE_KEY: + required: true permissions: contents: write + actions: read jobs: package: @@ -55,13 +59,23 @@ jobs: if: inputs.target == 'windows-desktop' run: mv "v2rayN-$Target-$Arch.zip" "v2rayN-$Target-$Arch-desktop.zip" - - name: Sleep for race condition between matrix jobs - run: sleep "$(od -An -N2 -tu2 /dev/urandom | awk 'NR==1{printf "%.2f", $1/5461}')" - - - name: Upload zip archive to release - uses: svenstaro/upload-release-action@v2 + - name: Upload zip artifact + uses: actions/upload-artifact@v7 with: - file: ${{ github.workspace }}/v2rayN*.zip - tag: ${{ inputs.release_tag }} - file_glob: true - prerelease: true + name: release-${{ inputs.target }}-${{ matrix.arch }} + path: v2rayN*.zip + if-no-files-found: error + + release: + name: sign and upload zip x64 arm64 + needs: package + strategy: + matrix: + arch: [ x64, arm64 ] + uses: ./.github/workflows/upload-sign.yml + with: + release_tag: ${{ inputs.release_tag }} + artifact_name: release-${{ inputs.target }}-${{ matrix.arch }} + file_pattern: '*.zip' + secrets: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} diff --git a/.github/workflows/pub-key.yml b/.github/workflows/pub-key.yml new file mode 100644 index 00000000..cd8b283d --- /dev/null +++ b/.github/workflows/pub-key.yml @@ -0,0 +1,48 @@ +name: publish GPG public key + +on: + workflow_call: + inputs: + release_tag: + required: true + type: string + secrets: + GPG_PRIVATE_KEY: + required: true + workflow_dispatch: + inputs: + release_tag: + required: true + type: string + +permissions: + contents: write + +jobs: + publish: + runs-on: ubuntu-26.04 + steps: + - name: Import GPG private key + shell: bash + env: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + run: | + set -euo pipefail + install -m 700 -d ~/.gnupg + printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import + + - name: Export GPG public key + shell: bash + run: | + set -euo pipefail + fingerprint="$(gpg --batch --list-secret-keys --with-colons | awk -F: '$1 == "fpr" { print $10; exit }')" + [[ -n "$fingerprint" ]] || { echo "No GPG secret key found."; exit 1; } + gpg --batch --armor --export "$fingerprint" > v2rayN-public-key.asc + + - name: Upload GPG public key to release + uses: svenstaro/upload-release-action@v2 + with: + file: v2rayN-public-key.asc + tag: ${{ inputs.release_tag }} + prerelease: true + overwrite: true diff --git a/.github/workflows/upload-sign.yml b/.github/workflows/upload-sign.yml new file mode 100644 index 00000000..5ee3c294 --- /dev/null +++ b/.github/workflows/upload-sign.yml @@ -0,0 +1,76 @@ +name: sign and upload release files + +on: + workflow_call: + inputs: + release_tag: + required: true + type: string + artifact_name: + required: true + type: string + file_pattern: + required: false + type: string + default: '**/*' + content_type: + required: false + type: string + default: application/octet-stream + secrets: + GPG_PRIVATE_KEY: + required: true + +permissions: + contents: write + actions: read + +jobs: + upload: + runs-on: ubuntu-26.04 + steps: + - name: Download release artifact + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.artifact_name }} + path: dist + + - name: Import GPG private key + shell: bash + env: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + run: | + set -euo pipefail + install -m 700 -d ~/.gnupg + printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import + + - name: Sign release files + shell: bash + run: | + set -euo pipefail + cd dist + shopt -s globstar nullglob + files=( ${{ inputs.file_pattern }} ) + (( ${#files[@]} )) || { echo "No files matched: ${{ inputs.file_pattern }}"; exit 1; } + for f in "${files[@]}"; do + [[ -f "$f" ]] || continue + gpg --batch --yes --armor --detach-sign --output "$f.sig" "$f" + done + + - name: Upload release files + uses: svenstaro/upload-release-action@v2 + with: + file: dist/${{ inputs.file_pattern }} + tag: ${{ inputs.release_tag }} + file_glob: true + prerelease: true + overwrite: true + + - name: Upload release signatures + uses: svenstaro/upload-release-action@v2 + with: + file: dist/${{ inputs.file_pattern }}.sig + tag: ${{ inputs.release_tag }} + file_glob: true + prerelease: true + overwrite: true diff --git a/README.md b/README.md index 4d728c9b..b6f0c570 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,15 @@ Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details. ## Telegram Channel [github_2dust](https://t.me/github_2dust) + + +## GPG 签名校验 (GPG Verification) + +为了预防镜像站、运营商或 CDN 劫持,本项目发布的 Release 文件已使用 GPG 进行签名,以供用户校验文件的真实性与完整性。 + +To prevent hijacking by mirror sites, ISPs, or CDNs, the Release files of this project are signed with GPG for users to verify their authenticity and integrity. + +### 公钥指纹 (Key Fingerprint) +```text +76945E9F3E9A168F8070F195805D661C134DFAF68903C199463C31E5AE903AE0 +```