Skip to content

Release Client

Release Client #16

name: Release Client
# 当推送 tag 时触发,也可以手动触发
# 构建矩阵在多 runner 上产出二进制后,各 runner 直接 SCP 上传到服务器的
# ${DEPLOY_PATH}/client/<tag>/ 目录(版本隔离),不使用 Artifact 中转,避免存储配额问题
# 全部平台上传完成后由 finalize-client 在服务端生成校验和并更新 latest 软链接
# 行为对齐 release-server.yml(SCP 直传,不依赖 Releases/Artifacts 分发)
on:
push:
tags:
- 'v*'
workflow_dispatch:
# 最小权限:仅需 checkout 拉代码;缓存用 runtime token,上传走 SCP/SSH 密钥,均不依赖 GITHUB_TOKEN
permissions:
contents: read
jobs:
build-client:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
binary: rust-tunnel-client
asset: rust-tunnel-client-linux-x86_64
- os: macos-latest
target: x86_64-apple-darwin
binary: rust-tunnel-client
asset: rust-tunnel-client-macos-x86_64
- os: macos-latest
target: aarch64-apple-darwin
binary: rust-tunnel-client
asset: rust-tunnel-client-macos-aarch64
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: rust-tunnel-client.exe
asset: rust-tunnel-client-windows-x86_64.exe
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Install Rust with target
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: ${{ matrix.target }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
cache-targets: true
cache-on-failure: true
- name: Install musl tools (Linux only)
if: runner.os == 'Linux'
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: musl-tools
version: 1.0
- name: Build client
env:
RUSTFLAGS: ${{ matrix.os == 'windows-latest' && '-C target-feature=+crt-static' || '' }}
run: cargo build --release -p rust-tunnel-client --target ${{ matrix.target }}
- name: Strip binary (non-Windows)
if: runner.os != 'Windows'
run: strip target/${{ matrix.target }}/release/${{ matrix.binary }}
# 各平台自行 SCP 直传:appleboy/scp-action 是 Docker action,只能跑在 Linux runner,
# 因此这里统一用原生 ssh/scp(Windows runner 走 git-bash,自带 OpenSSH 客户端)
- name: Upload binary to remote server
shell: bash
env:
SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
SSH_HOST: ${{ secrets.SERVER_HOST }}
SSH_USER: ${{ secrets.SERVER_USER }}
SSH_PORT: ${{ secrets.SERVER_PORT }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
run: |
set -euo pipefail
mkdir -p "${HOME}/.ssh"
KEY="${HOME}/.ssh/rt_deploy_key"
printf '%s\n' "${SSH_KEY}" > "${KEY}"
chmod 600 "${KEY}"
PORT="${SSH_PORT:-22}"
DEST="${DEPLOY_PATH}/client/${GITHUB_REF_NAME}"
SSH_OPTS=(-i "${KEY}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR)
echo "Uploading ${{ matrix.asset }} -> ${DEST}/"
ssh "${SSH_OPTS[@]}" -p "${PORT}" "${SSH_USER}@${SSH_HOST}" "mkdir -p '${DEST}'"
scp "${SSH_OPTS[@]}" -P "${PORT}" \
"target/${{ matrix.target }}/release/${{ matrix.binary }}" \
"${SSH_USER}@${SSH_HOST}:${DEST}/${{ matrix.asset }}"
rm -f "${KEY}"
build-gui:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: rust-tunnel-client-gui
asset: rust-tunnel-client-gui-linux-x86_64
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: rust-tunnel-client-gui.exe
asset: rust-tunnel-client-gui-windows-x86_64.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
cache-targets: true
cache-on-failure: true
- name: Install Linux GUI deps
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y mold libgtk-3-dev librsvg2-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev pkg-config libdbus-1-dev libxdo-dev
- name: Build GUI
run: cargo build --release -p rust-tunnel-client-gui --target ${{ matrix.target }}
- name: Upload GUI to remote
shell: bash
env:
SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
SSH_HOST: ${{ secrets.SERVER_HOST }}
SSH_USER: ${{ secrets.SERVER_USER }}
SSH_PORT: ${{ secrets.SERVER_PORT }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
run: |
set -euo pipefail
mkdir -p "${HOME}/.ssh"
KEY="${HOME}/.ssh/rt_deploy_key"
printf '%s\n' "${SSH_KEY}" > "${KEY}"
chmod 600 "${KEY}"
PORT="${SSH_PORT:-22}"
DEST="${DEPLOY_PATH}/client/${GITHUB_REF_NAME}"
SSH_OPTS=(-i "${KEY}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR)
ssh "${SSH_OPTS[@]}" -p "${PORT}" "${SSH_USER}@${SSH_HOST}" "mkdir -p '${DEST}'"
scp "${SSH_OPTS[@]}" -P "${PORT}" "target/${{ matrix.target }}/release/${{ matrix.binary }}" "${SSH_USER}@${SSH_HOST}:${DEST}/${{ matrix.asset }}"
rm -f "${KEY}"
build-gui-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: aarch64-apple-darwin,x86_64-apple-darwin
- uses: Swatinem/rust-cache@v2
with:
cache-targets: true
cache-on-failure: true
- name: Build universal2 binary
run: |
cargo build --release -p rust-tunnel-client-gui --target aarch64-apple-darwin
cargo build --release -p rust-tunnel-client-gui --target x86_64-apple-darwin
mkdir -p target/universal2
lipo -create target/aarch64-apple-darwin/release/rust-tunnel-client-gui target/x86_64-apple-darwin/release/rust-tunnel-client-gui -output target/universal2/rust-tunnel-client-gui
- name: Bundle .app and dmg
env:
VERSION: ${{ github.ref_name }}
GUI_BIN: target/universal2/rust-tunnel-client-gui
run: |
bash scripts/bundle-macos.sh
ls -lh dist/
- name: Upload dmg
shell: bash
env:
SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
SSH_HOST: ${{ secrets.SERVER_HOST }}
SSH_USER: ${{ secrets.SERVER_USER }}
SSH_PORT: ${{ secrets.SERVER_PORT }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
run: |
set -euo pipefail
mkdir -p "${HOME}/.ssh"
KEY="${HOME}/.ssh/rt_deploy_key"
printf '%s\n' "${SSH_KEY}" > "${KEY}"
chmod 600 "${KEY}"
PORT="${SSH_PORT:-22}"
DEST="${DEPLOY_PATH}/client/${GITHUB_REF_NAME}"
DMG="dist/RustTunnel-${GITHUB_REF_NAME}-macos-universal2.dmg"
SSH_OPTS=(-i "${KEY}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR)
ssh "${SSH_OPTS[@]}" -p "${PORT}" "${SSH_USER}@${SSH_HOST}" "mkdir -p '${DEST}'"
scp "${SSH_OPTS[@]}" -P "${PORT}" "$DMG" "${SSH_USER}@${SSH_HOST}:${DEST}/"
rm -f "${KEY}"
finalize-client:
needs: [build-client, build-gui, build-gui-macos]
runs-on: ubuntu-latest
steps:
- name: Checksum, permissions and latest symlink
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
port: ${{ secrets.SERVER_PORT }}
script: |
set -e
BASE="${{ secrets.DEPLOY_PATH }}/client"
TAG="${{ github.ref_name }}"
cd "${BASE}/${TAG}"
chmod +x rust-tunnel-client-* RustTunnel-*.dmg 2>/dev/null || true
sha256sum rust-tunnel-client-* RustTunnel-*.dmg 2>/dev/null > SHA256SUMS || sha256sum rust-tunnel-client-* > SHA256SUMS
echo "--- ${BASE}/${TAG} ---"
ls -lh
echo "--- SHA256SUMS ---"
cat SHA256SUMS
ln -sfn "${TAG}" "${BASE}/latest"
ls -ld "${BASE}/latest"
echo "Client ${TAG} deployed successfully to ${BASE}/${TAG}/ (latest -> ${TAG})"