From fc68a8cb0904912411566b5185e0fbf2cdc35ea4 Mon Sep 17 00:00:00 2001 From: Hureru <3507039083@qq.com> Date: Fri, 5 Jun 2026 17:10:25 +0800 Subject: [PATCH 1/2] chore: derive release version from git tags --- .github/workflows/release.yml | 30 +++++++++-- BUILD.md | 96 ++++++++++++++--------------------- Cargo.lock | 10 ++-- Cargo.toml | 4 +- GITHUB_ACTIONS.md | 43 ++++++++-------- build.rs | 83 ++++++++++++++++++++++++++++++ src/main.rs | 2 +- 7 files changed, 176 insertions(+), 92 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f5764d..67c1697 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,9 @@ on: tags: - 'v*' +permissions: + contents: write + env: CARGO_TERM_COLOR: always @@ -18,7 +21,14 @@ jobs: steps: - name: Get version from tag id: get_version - run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + shell: bash + run: | + tag="${GITHUB_REF_NAME}" + if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then + echo "Release tags must use vMAJOR.MINOR.PATCH, for example v1.2.3" >&2 + exit 1 + fi + echo "version=${tag#v}" >> "$GITHUB_OUTPUT" - name: Create Release id: create_release @@ -26,12 +36,12 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ github.ref }} - release_name: SSHWarden ${{ steps.get_version.outputs.version }} + tag_name: ${{ github.ref_name }} + release_name: SSHWarden v${{ steps.get_version.outputs.version }} draft: false prerelease: false body: | - ## SSHWarden ${{ steps.get_version.outputs.version }} + ## SSHWarden v${{ steps.get_version.outputs.version }} ### 下载 @@ -52,6 +62,8 @@ jobs: name: Build Release - ${{ matrix.os }} needs: create-release runs-on: ${{ matrix.os }} + env: + SSHWARDEN_VERSION: ${{ needs.create-release.outputs.version }} strategy: matrix: include: @@ -85,6 +97,16 @@ jobs: - name: Build release run: cargo build --release --verbose + - name: Verify release version + shell: bash + run: | + version_output="$(target/release/${{ matrix.artifact_name }} --version)" + echo "$version_output" + if [[ "$version_output" != *"${SSHWARDEN_VERSION}" ]]; then + echo "Expected release version ${SSHWARDEN_VERSION}" >&2 + exit 1 + fi + - name: Package release (Windows) if: matrix.os == 'windows-latest' shell: bash diff --git a/BUILD.md b/BUILD.md index 0f4a74c..b4f2dd5 100644 --- a/BUILD.md +++ b/BUILD.md @@ -120,7 +120,19 @@ cp ../README.md . 创建 `scripts/package-windows.ps1`: ```powershell -# 构建 release +# 读取发布版本:CI 设置 SSHWARDEN_VERSION,本地发布可在 vX.Y.Z tag 上运行 +if ($env:SSHWARDEN_VERSION) { + $version = $env:SSHWARDEN_VERSION +} else { + $tag = git describe --tags --exact-match --match "v[0-9]*" + if ($LASTEXITCODE -ne 0) { + throw "Set SSHWARDEN_VERSION or run this script from a vX.Y.Z tag" + } + $version = $tag.Trim() -replace '^v', '' +} + +# 构建 release;build.rs 会把版本注入二进制 +$env:SSHWARDEN_VERSION = $version cargo build --release # 创建发布目录 @@ -134,7 +146,6 @@ Copy-Item "README.md" -Destination $releaseDir Copy-Item "LICENSE" -Destination $releaseDir -ErrorAction SilentlyContinue # 创建 zip -$version = "0.1.0" $zipName = "sshwarden-$version-windows-x64.zip" Compress-Archive -Path "$releaseDir\*" -DestinationPath $zipName -Force @@ -154,7 +165,15 @@ powershell -ExecutionPolicy Bypass -File scripts/package-windows.ps1 #!/bin/bash set -e -# 构建 release +# 读取发布版本:CI 设置 SSHWARDEN_VERSION,本地发布可在 vX.Y.Z tag 上运行 +VERSION="${SSHWARDEN_VERSION:-}" +if [ -z "$VERSION" ]; then + TAG=$(git describe --tags --exact-match --match 'v[0-9]*') + VERSION="${TAG#v}" +fi +export SSHWARDEN_VERSION="$VERSION" + +# 构建 release;build.rs 会把版本注入二进制 cargo build --release # 创建发布目录 @@ -171,7 +190,6 @@ cp README.md "$RELEASE_DIR/" chmod +x "$RELEASE_DIR/sshwarden" # 创建 tar.gz -VERSION="0.1.0" PLATFORM=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) TAR_NAME="sshwarden-$VERSION-$PLATFORM-$ARCH.tar.gz" @@ -250,61 +268,21 @@ cargo build --release --target x86_64-pc-windows-gnu ## 持续集成 (CI) -### GitHub Actions 示例 - -创建 `.github/workflows/release.yml`: - -```yaml -name: Release - -on: - push: - tags: - - 'v*' - -jobs: - build: - strategy: - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v3 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - - name: Build - run: cargo build --release - - - name: Package (Windows) - if: matrix.os == 'windows-latest' - run: | - mkdir release-package - cp target/release/sshwarden.exe release-package/ - cp config.toml.example release-package/ - cp README.md release-package/ - Compress-Archive -Path release-package/* -DestinationPath sshwarden-windows-x64.zip - - - name: Package (Unix) - if: matrix.os != 'windows-latest' - run: | - mkdir release-package - cp target/release/sshwarden release-package/ - cp config.toml.example release-package/ - cp README.md release-package/ - tar -czf sshwarden-${{ matrix.os }}.tar.gz -C release-package . - - - name: Upload Release - uses: softprops/action-gh-release@v1 - with: - files: | - sshwarden-*.zip - sshwarden-*.tar.gz +### GitHub Actions 发布 + +仓库中的 `.github/workflows/release.yml` 已配置 tag 发布: + +- 只响应 `vMAJOR.MINOR.PATCH` 格式的 tag,例如 `v0.2.0`。 +- workflow 从 tag 解析版本,并在 release 构建中设置 `SSHWARDEN_VERSION`。 +- `build.rs` 会把该版本注入二进制的 `--version` 输出和 Windows 文件版本信息。 +- 发布包名使用 `sshwarden-{version}-{platform}-{arch}` 格式。 + +发布时不要修改 `Cargo.toml` 里的版本;创建并推送 tag 即可触发发布: + +```bash +VERSION=0.2.0 +git tag -a "v$VERSION" -m "Release v$VERSION" +git push origin "v$VERSION" ``` ## 故障排除 diff --git a/Cargo.lock b/Cargo.lock index 7c9ea91..ae6eaa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5227,7 +5227,7 @@ dependencies = [ [[package]] name = "sshwarden" -version = "2.2.0" +version = "0.0.0-dev" dependencies = [ "anyhow", "base64", @@ -5256,7 +5256,7 @@ dependencies = [ [[package]] name = "sshwarden-agent" -version = "2.2.0" +version = "0.0.0-dev" dependencies = [ "anyhow", "base64", @@ -5279,7 +5279,7 @@ dependencies = [ [[package]] name = "sshwarden-api" -version = "2.2.0" +version = "0.0.0-dev" dependencies = [ "aes", "anyhow", @@ -5306,7 +5306,7 @@ dependencies = [ [[package]] name = "sshwarden-config" -version = "2.2.0" +version = "0.0.0-dev" dependencies = [ "anyhow", "serde", @@ -5317,7 +5317,7 @@ dependencies = [ [[package]] name = "sshwarden-ui" -version = "2.2.0" +version = "0.0.0-dev" dependencies = [ "anyhow", "scopeguard", diff --git a/Cargo.toml b/Cargo.toml index 82fd5ad..abca156 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ [package] name = "sshwarden" -version = "2.2.0" +version = "0.0.0-dev" edition = "2021" license = "GPL-3.0" publish = false @@ -46,7 +46,7 @@ windows = { version = "0.61", features = [ winresource = "0.1" [workspace.package] -version = "2.2.0" +version = "0.0.0-dev" license = "GPL-3.0" edition = "2021" publish = false diff --git a/GITHUB_ACTIONS.md b/GITHUB_ACTIONS.md index bfa65f7..5cc3932 100644 --- a/GITHUB_ACTIONS.md +++ b/GITHUB_ACTIONS.md @@ -32,7 +32,7 @@ ### 2. Release (`.github/workflows/release.yml`) **触发条件**: -- 推送以 `v` 开头的 tag(例如 `v0.1.0`) +- 推送符合 `vMAJOR.MINOR.PATCH` 的 tag(例如 `v0.2.0`) **功能**: - 创建 GitHub Release @@ -63,40 +63,35 @@ git push origin main ### 发布新版本 -1. **更新版本号** +发布版本只通过 Git tag 声明;不要为了发布去修改 `Cargo.toml`。仓库中的 Cargo 版本保持 `0.0.0-dev` 作为开发占位值,Release workflow 会从 `vX.Y.Z` tag 注入 `SSHWARDEN_VERSION`,构建出的二进制版本和产物名称都会使用 tag 版本。 -编辑 `Cargo.toml`: -```toml -[package] -version = "0.2.0" # 更新版本号 -``` - -2. **提交更改** +1. **确认 main 已准备好发布** ```bash -git add Cargo.toml -git commit -m "chore: bump version to 0.2.0" -git push origin main +git switch main +git pull --ff-only +cargo test --verbose ``` -3. **创建并推送 tag** +2. **创建并推送 tag** ```bash -# 创建 tag -git tag -a v0.2.0 -m "Release version 0.2.0" +VERSION=0.2.0 -# 推送 tag -git push origin v0.2.0 +git tag -a "v$VERSION" -m "Release v$VERSION" +git push origin "v$VERSION" ``` -4. **自动发布** +3. **自动发布** 推送 tag 后,GitHub Actions 会自动: +- 校验 tag 是否符合 `vMAJOR.MINOR.PATCH` 格式 +- 将 tag 版本注入 release 构建 - 在三个平台上构建 release 版本 - 创建 GitHub Release -- 上传打包好的二进制文件 +- 上传带版本号的二进制压缩包 -5. **编辑 Release Notes(可选)** +4. **编辑 Release Notes(可选)** 访问 https://github.com/Hureru/SSHWarden/releases,编辑自动创建的 release,添加更新日志。 @@ -175,7 +170,13 @@ cargo build --release --verbose git push origin :refs/tags/v0.1.0 ``` -2. **权限问题** +2. **Tag 格式不符合发布规范** + ``` + Release tags must use vMAJOR.MINOR.PATCH, for example v1.2.3 + ``` + 解决: 使用 `vX.Y.Z` 格式重新创建 tag,例如 `v0.2.0`。 + +3. **权限问题** ``` Error: Resource not accessible by integration ``` diff --git a/build.rs b/build.rs index d0c20c1..8cbef09 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,15 @@ +use std::process::Command; + fn main() { + let app_version = release_version().unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_owned()); + println!("cargo:rustc-env=SSHWARDEN_VERSION={app_version}"); + println!("cargo:rerun-if-env-changed=SSHWARDEN_VERSION"); + println!("cargo:rerun-if-env-changed=GITHUB_REF_NAME"); + println!("cargo:rerun-if-env-changed=GITHUB_REF_TYPE"); + println!("cargo:rerun-if-changed=.git/HEAD"); + println!("cargo:rerun-if-changed=.git/packed-refs"); + println!("cargo:rerun-if-changed=.git/refs/tags"); + if std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "windows" { // Rebuild resources when manifest/icon changes. println!("cargo:rerun-if-changed=app.manifest"); @@ -7,6 +18,78 @@ fn main() { let mut res = winresource::WindowsResource::new(); res.set_manifest_file("app.manifest"); res.set_icon("crates/sshwarden-ui/assets/sshwarden.ico"); + res.set("FileVersion", &app_version); + res.set("ProductVersion", &app_version); res.compile().expect("Failed to compile Windows resources"); } } + +fn release_version() -> Option { + if let Ok(version) = std::env::var("SSHWARDEN_VERSION") { + return normalize_version(&version); + } + + if std::env::var("GITHUB_REF_TYPE").as_deref() == Ok("tag") { + if let Ok(tag) = std::env::var("GITHUB_REF_NAME") { + return normalize_version(&tag); + } + } + + let output = Command::new("git") + .args(["describe", "--tags", "--exact-match", "--match", "v[0-9]*"]) + .output() + .ok()?; + + if !output.status.success() { + return None; + } + + let tag = String::from_utf8(output.stdout).ok()?; + normalize_version(&tag) +} + +fn normalize_version(value: &str) -> Option { + let trimmed = value.trim(); + let version = trimmed.strip_prefix('v').unwrap_or(trimmed); + + if is_semver_like(version) { + Some(version.to_owned()) + } else { + None + } +} + +fn is_semver_like(version: &str) -> bool { + let core_end = version + .find(|character| character == '-' || character == '+') + .unwrap_or(version.len()); + let core = &version[..core_end]; + let suffix = &version[core_end..]; + + let mut parts = core.split('.'); + let Some(major) = parts.next() else { + return false; + }; + let Some(minor) = parts.next() else { + return false; + }; + let Some(patch) = parts.next() else { + return false; + }; + + parts.next().is_none() + && [major, minor, patch].iter().all(|part| is_numeric(part)) + && valid_suffix(suffix) +} + +fn is_numeric(value: &str) -> bool { + !value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit()) +} + +fn valid_suffix(value: &str) -> bool { + value.is_empty() + || (value.len() > 1 + && value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'+' | b'.'))) +} diff --git a/src/main.rs b/src/main.rs index 8802357..cc7b4a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -129,7 +129,7 @@ type CachedKeyTuples = Arc>; #[derive(Parser)] #[command( name = "sshwarden", - version, + version = env!("SSHWARDEN_VERSION"), about = "SSH Agent backed by Bitwarden vault" )] struct Cli { From 5288d63a1603ecc1fd130d87e0da98630271b2cb Mon Sep 17 00:00:00 2001 From: Hureru <3507039083@qq.com> Date: Fri, 5 Jun 2026 17:15:31 +0800 Subject: [PATCH 2/2] chore: satisfy clippy for version parsing --- build.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.rs b/build.rs index 8cbef09..e070b3f 100644 --- a/build.rs +++ b/build.rs @@ -60,9 +60,7 @@ fn normalize_version(value: &str) -> Option { } fn is_semver_like(version: &str) -> bool { - let core_end = version - .find(|character| character == '-' || character == '+') - .unwrap_or(version.len()); + let core_end = version.find(['-', '+']).unwrap_or(version.len()); let core = &version[..core_end]; let suffix = &version[core_end..];