diff --git a/.github/workflows/build-artifacts.yml b/.github/workflows/build-artifacts.yml deleted file mode 100644 index 5f80e31..0000000 --- a/.github/workflows/build-artifacts.yml +++ /dev/null @@ -1,172 +0,0 @@ -name: "Build artifacts" - -on: - workflow_dispatch: - -env: - TERMSCP_VERSION: "1.0.0" - -jobs: - build-binaries: - name: Build - ${{ matrix.platform.release_for }} - strategy: - matrix: - platform: - - release_for: MacOS-x86_64 - os: macos-latest - platform: macos - target: x86_64-apple-darwin - - - release_for: MacOS-aarch64 - os: macos-latest - platform: macos - target: aarch64-apple-darwin - - - release_for: Linux-x86_64 - os: ubuntu-latest - platform: linux - target: x86_64-unknown-linux-gnu - debian_suffix: amd64 - - - release_for: Linux-aarch64 - os: ubuntu-24.04-arm - platform: linux - target: aarch64-unknown-linux-gnu - debian_suffix: arm64 - - - release_for: Windows-x86_64 - os: windows-latest - platform: windows - target: x86_64-pc-windows-msvc - - - release_for: Windows-aarch64 - os: windows-11-arm - platform: windows - target: aarch64-pc-windows-msvc - - runs-on: ${{ matrix.platform.os }} - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - targets: ${{ matrix.platform.target }} - - - name: Install dependencies (Linux) - if: matrix.platform.platform == 'linux' - run: | - sudo apt-get update - sudo apt-get install -y \ - make \ - libgit2-dev \ - build-essential \ - pkg-config \ - libbsd-dev \ - libcap-dev \ - libcups2-dev \ - libgnutls28-dev \ - libicu-dev \ - libjansson-dev \ - libkeyutils-dev \ - libldap2-dev \ - zlib1g-dev \ - libpam0g-dev \ - libacl1-dev \ - libarchive-dev \ - flex \ - bison \ - libntirpc-dev \ - libtracker-sparql-3.0-dev \ - libglib2.0-dev \ - libdbus-1-dev \ - libsasl2-dev \ - libunistring-dev \ - libdbus-1-dev \ - cpanminus; - sudo cpanm Parse::Yapp::Driver - - - name: Install dependencies (MacOS) - if: matrix.platform.platform == 'macos' - run: | - brew update - brew install \ - bison \ - cpanminus \ - cups \ - flex \ - gettext \ - gmp \ - gnutls \ - icu4c \ - jansson \ - libarchive \ - libbsd \ - libunistring \ - libgit2 \ - libtirpc \ - openldap \ - pkg-config \ - zlib - brew link --force bison - brew link --force cups - brew link --force flex - brew link --force gettext - brew link --force gmp - brew link --force gnutls - brew link --force icu4c - brew link --force jansson - brew link --force libarchive - brew link --force libbsd - brew link --force libgit2 - brew link --force libtirpc - brew link --force libunistring - brew link --force openldap - brew link --force zlib - cpanm Parse::Yapp::Driver - - name: Build release (MacOS Intel) - if: matrix.platform.target == 'x86_64-apple-darwin' - run: cargo build --release --no-default-features --features keyring --target ${{ matrix.platform.target }} - - - name: Build release (others) - if: matrix.platform.target != 'x86_64-apple-darwin' - run: cargo build --release --features smb-vendored --target ${{ matrix.platform.target }} - - - name: Build deb - if: matrix.platform.platform == 'linux' - run: | - cargo install cargo-deb - cargo deb --target ${{ matrix.platform.target }} --features smb-vendored - - - name: Prepare artifact files (Posix) - if: matrix.platform.platform != 'windows' - run: | - mkdir -p .artifact - mv target/${{ matrix.platform.target }}/release/termscp .artifact/termscp - tar -czf .artifact/termscp-v${{ env.TERMSCP_VERSION }}-${{ matrix.platform.target }}.tar.gz -C .artifact termscp - ls -l .artifact/ - - name: Upload artifact (Posix) - if: matrix.platform.platform != 'windows' - uses: actions/upload-artifact@v4 - with: - if-no-files-found: error - retention-days: 1 - name: termscp-${{ matrix.platform.target }} - path: .artifact/termscp-v${{ env.TERMSCP_VERSION }}-${{ matrix.platform.target }}.tar.gz - - - name: Upload artifact (Windows) - if: matrix.platform.platform == 'windows' - uses: actions/upload-artifact@v4 - with: - if-no-files-found: error - retention-days: 1 - name: termscp-v${{ env.TERMSCP_VERSION }}-${{ matrix.platform.target }} - path: target/${{ matrix.platform.target }}/release/termscp.exe - - - name: Upload artifact (Deb) - if: matrix.platform.platform == 'linux' - uses: actions/upload-artifact@v4 - with: - if-no-files-found: error - retention-days: 1 - name: termscp-${{ matrix.platform.target }}-deb - path: target/debian/termscp_${{ env.TERMSCP_VERSION }}-1_${{ matrix.platform.debian_suffix }}.deb diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7507c3e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,368 @@ +name: Release + +on: + workflow_dispatch: + inputs: + version: + description: "Version to release, e.g. 1.1.0 (no leading v)" + required: true + type: string + dry_run: + description: "Dry run: build & compute everything, push/publish nothing" + required: true + type: boolean + default: true + +permissions: + contents: read + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + version: ${{ inputs.version }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + token: ${{ secrets.RELEASE_PAT }} + persist-credentials: true + + - name: Configure git identity + run: | + git config user.name "veeso" + git config user.email "christian.visintin@veeso.dev" + + - name: Install git-cliff + uses: taiki-e/install-action@56545b37b57562edd73171cb6c62cc509db4c34e # v2 + with: + tool: git-cliff + + - name: Bump version + env: + VERSION: ${{ inputs.version }} + run: dist/release/bump_version.sh "$VERSION" "$(date +%F)" + + - name: Generate CHANGELOG + env: + VERSION: ${{ inputs.version }} + run: git-cliff --tag "v$VERSION" -o CHANGELOG.md + + - name: Generate release notes + run: git-cliff --latest --strip header -o RELEASE_NOTES.md + + - name: Upload release notes + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: release-notes + path: RELEASE_NOTES.md + retention-days: 1 + if-no-files-found: error + + - name: Rebuild site CSS + run: npx tailwindcss@3 -i site/input.css -o site/output.css --minify + + - name: Show diff (dry run) + if: ${{ inputs.dry_run }} + run: git --no-pager diff + + - name: Commit & push version bump + if: ${{ !inputs.dry_run }} + env: + VERSION: ${{ inputs.version }} + run: | + rm -f RELEASE_NOTES.md + git add -A + git commit -m "chore: release v$VERSION" + git push origin HEAD:main + + build: + needs: prepare + name: build-${{ matrix.target }} + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + kind: linux + deb_suffix: amd64 + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + kind: linux + deb_suffix: arm64 + - target: aarch64-apple-darwin + os: macos-latest + kind: macos + features: "--features smb-vendored" + - target: x86_64-apple-darwin + os: macos-latest + kind: macos + features: "--no-default-features --features keyring" + - target: x86_64-pc-windows-msvc + os: windows-latest + kind: windows + - target: aarch64-pc-windows-msvc + os: windows-11-arm + kind: windows + runs-on: ${{ matrix.os }} + env: + VERSION: ${{ needs.prepare.outputs.version }} + TARGET: ${{ matrix.target }} + FEATURES: ${{ matrix.features }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + ref: ${{ inputs.dry_run && github.sha || 'main' }} + persist-credentials: false + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + with: + targets: ${{ matrix.target }} + + # ---- Linux: zigbuild against old glibc (see Task 1) ---- + - name: Install zig + cargo-zigbuild (Linux) + if: matrix.kind == 'linux' + run: | + pipx install ziglang + cargo install --locked cargo-zigbuild cargo-deb + - name: Install samba build deps (Linux) + if: matrix.kind == 'linux' + run: | + sudo apt-get update + sudo apt-get install -y make build-essential pkg-config libdbus-1-dev \ + flex bison cpanminus libacl1-dev + sudo cpanm Parse::Yapp::Driver + - name: Build (Linux) + if: matrix.kind == 'linux' + run: cargo zigbuild --release --features smb-vendored --target "$TARGET.2.17" + - name: Build deb (Linux) + if: matrix.kind == 'linux' + run: cargo deb --no-build --target "$TARGET" --features smb-vendored + + # ---- macOS ---- + - name: Install deps (macOS) + if: matrix.kind == 'macos' + run: | + brew update + brew install bison cpanminus cups flex gettext gmp gnutls icu4c jansson \ + libarchive libbsd libunistring libgit2 libtirpc openldap pkg-config zlib + for p in bison cups flex gettext gmp gnutls icu4c jansson libarchive \ + libbsd libgit2 libtirpc libunistring openldap zlib; do brew link --force "$p"; done + cpanm Parse::Yapp::Driver + - name: Build (macOS) + if: matrix.kind == 'macos' + run: cargo build --release $FEATURES --target "$TARGET" + + # ---- Windows ---- + - name: Build (Windows) + if: matrix.kind == 'windows' + run: cargo build --release --features smb-vendored --target "$env:TARGET" + + # ---- Package posix (tar.gz) ---- + - name: Package (posix) + if: matrix.kind != 'windows' + run: | + mkdir -p .artifact + cp "target/$TARGET/release/termscp" .artifact/termscp + tar -czf ".artifact/termscp-v$VERSION-$TARGET.tar.gz" -C .artifact termscp + shasum -a 256 ".artifact/termscp-v$VERSION-$TARGET.tar.gz" | awk '{print $1}' > ".artifact/$TARGET.sha256" + + # ---- Package windows (zip) ---- + - name: Package (windows) + if: matrix.kind == 'windows' + shell: pwsh + run: | + New-Item -ItemType Directory -Force .artifact | Out-Null + Copy-Item "target/$env:TARGET/release/termscp.exe" .artifact/termscp.exe + Compress-Archive -Path .artifact/termscp.exe -DestinationPath ".artifact/termscp-v$env:VERSION-$env:TARGET.zip" + (Get-FileHash ".artifact/termscp-v$env:VERSION-$env:TARGET.zip" -Algorithm SHA256).Hash.ToLower() | Out-File -NoNewline ".artifact/$env:TARGET.sha256" + + - name: Move deb into artifact dir (Linux) + if: matrix.kind == 'linux' + run: cp target/"$TARGET"/debian/*.deb .artifact/ + + - name: Upload build artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: build-${{ matrix.target }} + path: .artifact/* + retention-days: 1 + if-no-files-found: error + + publish-homebrew: + needs: [prepare, build] + runs-on: ubuntu-latest + env: + VERSION: ${{ needs.prepare.outputs.version }} + steps: + - name: Download build artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + pattern: build-* + path: dl + merge-multiple: true + + - name: Checkout homebrew tap + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + repository: veeso/homebrew-termscp + token: ${{ secrets.RELEASE_PAT }} + path: tap + persist-credentials: true + + - name: Rewrite formula + run: | + set -euo pipefail + cd "$GITHUB_WORKSPACE" + SHA_MAC_ARM=$(cat dl/aarch64-apple-darwin.sha256) + SHA_MAC_X64=$(cat dl/x86_64-apple-darwin.sha256) + SHA_LIN_ARM=$(cat dl/aarch64-unknown-linux-gnu.sha256) + SHA_LIN_X64=$(cat dl/x86_64-unknown-linux-gnu.sha256) + BASE="https://github.com/veeso/termscp/releases/latest/download" + cat > tap/Formula/termscp.rb </dev/null || true + cp dist/chocolatey/*.nupkg out/ + + - name: Upload assets artifact (dry run) + if: ${{ inputs.dry_run }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: release-assets-dryrun + path: out/* + retention-days: 3 + + - name: Create GitHub release + if: ${{ !inputs.dry_run }} + env: + GH_TOKEN: ${{ secrets.RELEASE_PAT }} + run: | + gh release create "v$VERSION" out/* \ + --title "v$VERSION" \ + --notes-file notes/RELEASE_NOTES.md + + publish-choco: + needs: [prepare, release] + if: ${{ !inputs.dry_run }} + runs-on: windows-latest + env: + VERSION: ${{ needs.prepare.outputs.version }} + steps: + - name: Download nupkg from release + env: + GH_TOKEN: ${{ secrets.RELEASE_PAT }} + run: gh release download "v$env:VERSION" --repo veeso/termscp --pattern "*.nupkg" + + - name: Push to Chocolatey + env: + CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }} + run: | + choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/ + choco push (Get-ChildItem *.nupkg).Name --source https://push.chocolatey.org/ diff --git a/dist/release/bump_version.sh b/dist/release/bump_version.sh new file mode 100755 index 0000000..67f9c2f --- /dev/null +++ b/dist/release/bump_version.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Bump termscp version across all tracked locations. +# Usage: bump_version.sh [date] [root] +set -euo pipefail + +VERSION="${1:?usage: bump_version.sh [date] [root]}" +DATE="${2:-$(date +%F)}" +ROOT="${3:-$(git rev-parse --show-toplevel)}" + +# in-place sed that works on both GNU and BSD/macOS +sedi() { perl -0777 -pi -e "$1" "$2"; } + +# Cargo.toml — only the top-level package version (line-anchored), not deps +sedi "s/^version = \"[0-9][0-9A-Za-z.\\-]*\"/version = \"$VERSION\"/m" "$ROOT/Cargo.toml" + +# install.sh — the literal default assignment only +sedi "s/^TERMSCP_VERSION=\"[0-9][0-9A-Za-z.\\-]*\"/TERMSCP_VERSION=\"$VERSION\"/m" "$ROOT/install.sh" + +# README.md — version + release date +sedi "s/Current version: [0-9][0-9A-Za-z.\\-]* [0-9]{4}-[0-9]{2}-[0-9]{2}/Current version: $VERSION $DATE/" "$ROOT/README.md" + +# site: home.html + every lang json — "termscp X is NOW out" +sedi "s/termscp [0-9][0-9A-Za-z.\\-]* is NOW out/termscp $VERSION is NOW out/g" "$ROOT/site/html/home.html" +for f in "$ROOT"/site/lang/*.json; do + sedi "s/termscp [0-9][0-9A-Za-z.\\-]* is NOW out/termscp $VERSION is NOW out/g" "$f" +done + +# site/get-started.html — nupkg + deb download URLs +sedi "s/termscp\\.[0-9][0-9A-Za-z.\\-]*\\.nupkg/termscp.$VERSION.nupkg/g" "$ROOT/site/html/get-started.html" +sedi "s/termscp_[0-9][0-9A-Za-z.\\-]*_amd64\\.deb/termscp_${VERSION}_amd64.deb/g" "$ROOT/site/html/get-started.html" + +# chocolatey nuspec +sedi "s#[0-9][0-9A-Za-z.\\-]*#$VERSION#" "$ROOT/dist/chocolatey/termscp.nuspec" + +# chocolatey install script — release tag + asset name in the URLs (checksums set later by CI) +sedi "s#releases/download/v[0-9][0-9A-Za-z.\\-]*/termscp-v[0-9][0-9A-Za-z.\\-]*-#releases/download/v$VERSION/termscp-v$VERSION-#g" "$ROOT/dist/chocolatey/tools/chocolateyinstall.ps1" + +echo "Bumped to $VERSION ($DATE)" diff --git a/dist/release/test_bump_version.sh b/dist/release/test_bump_version.sh new file mode 100755 index 0000000..1951418 --- /dev/null +++ b/dist/release/test_bump_version.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +BUMP="$SCRIPT_DIR/bump_version.sh" + +ROOT="$(mktemp -d)" +trap 'rm -rf "$ROOT"' EXIT + +# --- build a fixture tree mirroring the real layout, all at 1.0.0 --- +mkdir -p "$ROOT/site/html" "$ROOT/site/lang" "$ROOT/dist/chocolatey/tools" + +cat > "$ROOT/Cargo.toml" <<'EOF' +[package] +name = "termscp" +version = "1.0.0" + +[dependencies] +foo = { version = "1.0.0" } +EOF + +cat > "$ROOT/install.sh" <<'EOF' +TERMSCP_VERSION="1.0.0" +set_termscp_version() { + TERMSCP_VERSION="$1" +} +EOF + +cat > "$ROOT/README.md" <<'EOF' +

Current version: 1.0.0 2026-04-18

+EOF + +cat > "$ROOT/site/html/home.html" <<'EOF' +termscp 1.0.0 is NOW out! Download it from +EOF + +cat > "$ROOT/site/html/get-started.html" <<'EOF' +Github +
wget -O termscp.deb https://github.com/veeso/termscp/releases/latest/download/termscp_1.0.0_amd64.deb
+EOF + +for lang in en it fr es zh-CN; do + cat > "$ROOT/site/lang/$lang.json" <<'EOF' +{ "versionAlert": "termscp 1.0.0 is NOW out! Download it from" } +EOF +done + +cat > "$ROOT/dist/chocolatey/termscp.nuspec" <<'EOF' +1.0.0 +EOF + +cat > "$ROOT/dist/chocolatey/tools/chocolateyinstall.ps1" <<'EOF' +$url = 'https://github.com/veeso/termscp/releases/download/v1.0.0/termscp-v1.0.0-aarch64-pc-windows-msvc.zip' +$url = 'https://github.com/veeso/termscp/releases/download/v1.0.0/termscp-v1.0.0-x86_64-pc-windows-msvc.zip' +EOF + +# --- run the bump --- +"$BUMP" 1.1.0 2026-06-07 "$ROOT" + +fail() { echo "FAIL: $1"; exit 1; } +have() { grep -q -- "$2" "$ROOT/$1" || fail "$1 missing: $2"; } +missing() { ! grep -q -- "$2" "$ROOT/$1" || fail "$1 still has: $2"; } + +# package version bumped, dependency version NOT touched +have "Cargo.toml" 'version = "1.1.0"' +have "Cargo.toml" 'foo = { version = "1.0.0" }' + +have "install.sh" 'TERMSCP_VERSION="1.1.0"' +have "README.md" 'Current version: 1.1.0 2026-06-07' +missing "README.md" '2026-04-18' + +have "site/html/home.html" 'termscp 1.1.0 is NOW out' +have "site/lang/en.json" 'termscp 1.1.0 is NOW out' +have "site/lang/zh-CN.json" 'termscp 1.1.0 is NOW out' +have "site/html/get-started.html" 'termscp.1.1.0.nupkg' +have "site/html/get-started.html" 'termscp_1.1.0_amd64.deb' +have "dist/chocolatey/termscp.nuspec" '1.1.0' +have "dist/chocolatey/tools/chocolateyinstall.ps1" 'releases/download/v1.1.0/termscp-v1.1.0-' +missing "dist/chocolatey/tools/chocolateyinstall.ps1" 'v1.0.0' + +# --- idempotency: running again at same version is a no-op (no error) --- +"$BUMP" 1.1.0 2026-06-07 "$ROOT" +have "Cargo.toml" 'version = "1.1.0"' + +echo "PASS"