diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 2e68ff3..a1aebcf 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -9,6 +9,7 @@ on: permissions: contents: write + pull-requests: write concurrency: group: auto-tag-${{ github.ref }} @@ -16,11 +17,10 @@ concurrency: jobs: create-tag: - name: Create and Push Tag - # Only run when a release/* branch was actually merged (not just closed) + name: Create Tag and Sync Development if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 steps: - name: Checkout repository @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 token: ${{ secrets.PAT_TOKEN }} - - name: Extract and validate version from branch name + - name: Extract and validate version id: version uses: ./.github/actions/determine-version @@ -37,9 +37,9 @@ jobs: id: check run: | set -euo pipefail - if git rev-parse "refs/tags/${{ steps.version.outputs.TAG }}" >/dev/null 2>&1; then + if git rev-parse "refs/tags/${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then echo "exists=true" >> "$GITHUB_OUTPUT" - echo "::warning::Tag ${{ steps.version.outputs.TAG }} already exists, skipping." + echo "::warning::Tag ${{ steps.version.outputs.tag }} already exists, skipping." else echo "exists=false" >> "$GITHUB_OUTPUT" fi @@ -50,7 +50,43 @@ jobs: set -euo pipefail git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git tag -a "${{ steps.version.outputs.TAG }}" \ - -m "Release ${{ steps.version.outputs.VERSION }}" - git push origin "${{ steps.version.outputs.TAG }}" - echo "::notice::Tag ${{ steps.version.outputs.TAG }} pushed successfully." \ No newline at end of file + git tag -a "${{ steps.version.outputs.tag }}" \ + -m "Release ${{ steps.version.outputs.version }}" + git push origin "${{ steps.version.outputs.tag }}" + echo "::notice::Tag ${{ steps.version.outputs.tag }} pushed successfully." + + - name: Cherry-pick release commit into development + if: steps.check.outputs.exists == 'false' + env: + GH_TOKEN: ${{ secrets.PAT_TOKEN }} + run: | + set -euo pipefail + + RELEASE_SHA="${{ github.event.pull_request.head.sha }}" + VERSION="${{ steps.version.outputs.version }}" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git fetch origin development + git checkout -B development origin/development + + if git cherry-pick "$RELEASE_SHA"; then + git push origin development + echo "::notice::Cherry-pick of $RELEASE_SHA into development succeeded." + else + git cherry-pick --abort + + SYNC_BRANCH="chore/sync-release-${VERSION}-to-development" + git checkout -B "$SYNC_BRANCH" origin/development + git push origin "$SYNC_BRANCH" + + gh pr create \ + --base development \ + --head "$SYNC_BRANCH" \ + --title "chore: sync release ${VERSION} into development" \ + --body "$(printf 'Automatic cherry-pick of release \`%s\` (commit \`%s\`) into \`development\` failed due to conflicts.\n\nPlease apply and resolve manually:\n\n```\ngit fetch origin\ngit checkout %s\ngit cherry-pick %s\n# resolve conflicts\ngit push origin %s\n```' \ + "$VERSION" "$RELEASE_SHA" "$SYNC_BRANCH" "$RELEASE_SHA" "$SYNC_BRANCH")" + + echo "::warning::Cherry-pick conflict. PR created: $SYNC_BRANCH → development." + fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 96f6419..d4f2800 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build and Release +name: Build and Release on: push: @@ -13,9 +13,15 @@ concurrency: cancel-in-progress: false # Never cancel an in-flight release jobs: + test: + name: Run Tests + uses: ./.github/workflows/test.yml + prepare: name: Prepare Metadata runs-on: ubuntu-latest + timeout-minutes: 5 + needs: test outputs: version: ${{ steps.version.outputs.version }} tag: ${{ steps.version.outputs.tag }} @@ -28,7 +34,7 @@ jobs: uses: ./.github/actions/determine-version build: - needs: prepare + needs: [ test, prepare ] uses: ./.github/workflows/build-and-package.yml with: version: ${{ needs.prepare.outputs.version }} @@ -39,7 +45,7 @@ jobs: name: Create GitHub Release runs-on: ubuntu-latest timeout-minutes: 15 - needs: [ prepare, build ] + needs: [ test, prepare, build ] steps: - name: Checkout repository @@ -68,12 +74,15 @@ jobs: cp LICENSE release-assets/LICENSE - # Assert all expected files are present before creating the release + # Assert all expected files are present before creating the release. + # appicon.png and .desktop are required by openssh-gui-bin PKGBUILD source URLs. for EXPECTED in \ "OpenSSH-GUI-linux-x64" \ "OpenSSH-GUI-win-x64.exe" \ "OpenSSH-GUI-osx-x64" \ - "OpenSSH-GUI-x86_64.AppImage" + "OpenSSH-GUI-x86_64.AppImage" \ + "appicon.png" \ + "io.github.frequency403.openssh_gui.desktop" do if [[ ! -f "release-assets/$EXPECTED" ]]; then echo "::error::Expected release asset missing: $EXPECTED" @@ -95,13 +104,13 @@ jobs: name: Update AUR Packages runs-on: ubuntu-latest timeout-minutes: 15 - needs: [ prepare, release ] + needs: [ test, prepare, release ] steps: - name: Checkout repository uses: actions/checkout@v4 with: - fetch-depth: 0 + fetch-depth: 1 - name: Download build artifacts uses: actions/download-artifact@v4 @@ -140,12 +149,15 @@ jobs: SHA_LICENSE=$(sha256sum LICENSE | cut -d' ' -f1) sed -i "s/^pkgver=.*/pkgver=$VERSION/" openssh-gui-bin/PKGBUILD + sed -i "s/^pkgrel=.*/pkgrel=1/" openssh-gui-bin/PKGBUILD sed -i "s/^sha256sums=.*/sha256sums=('$SHA_BIN' '$SHA_ICON' '$SHA_DESKTOP' '$SHA_LICENSE')/" \ openssh-gui-bin/PKGBUILD + sed -i "s/^pkgver=.*/pkgver=$VERSION/" openssh-gui-git/PKGBUILD + sed -i "s/^pkgrel=.*/pkgrel=1/" openssh-gui-git/PKGBUILD echo "Updated PKGBUILDs:" - grep -E '^(pkgver=|sha256sums=)' openssh-gui-bin/PKGBUILD + grep -E '^(pkgver=|pkgrel=|sha256sums=)' openssh-gui-bin/PKGBUILD - name: Publish AUR (openssh-gui-bin) uses: KSXGitHub/github-actions-deploy-aur@v4.1.1 @@ -169,23 +181,19 @@ jobs: winget: name: Update Winget Package - runs-on: ubuntu-latest + runs-on: ubuntu-slim timeout-minutes: 10 - needs: [ prepare, release ] + needs: [ test, prepare, release ] steps: - name: Checkout Repository uses: actions/checkout@v4 - - - name: Install Komac - id: komac - uses: ./.github/actions/install-komac - name: Update Winget manifest - run: | - set -euo pipefail - "${{ steps.komac.outputs.komac-path }}" update "frequency403.OpenSSHGUI" \ - --version "${{ needs.prepare.outputs.version }}" \ - --urls "https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare.outputs.tag }}/OpenSSH-GUI-win-x64.exe" \ - --submit \ - --token "${{ secrets.WINGET_GITHUB_TOKEN }}" \ No newline at end of file + uses: vedantmgoyal9/winget-releaser@main + with: + identifier: frequency403.OpenSSHGUI + version: ${{ needs.prepare.outputs.version }} + release-tag: ${{ needs.prepare.outputs.tag }} + installers-regex: 'win-x64\.exe$' + token: ${{ secrets.WINGET_GITHUB_TOKEN }} diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 3591675..06cd6a8 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -1,4 +1,4 @@ -name: Staging / Nightly Build +name: Staging / Development Build on: push: @@ -9,19 +9,22 @@ permissions: contents: write concurrency: - group: nightly-${{ github.ref }} - cancel-in-progress: true # Supersede previous nightly on rapid pushes + group: staging-${{ github.ref }} + cancel-in-progress: true # Supersede previous run on rapid pushes jobs: + test: + name: Run Tests + uses: ./.github/workflows/test.yml + prepare: name: Prepare Metadata runs-on: ubuntu-latest timeout-minutes: 5 + needs: test outputs: - version: ${{ steps.meta.outputs.version }} - base_version: ${{ steps.meta.outputs.base_version }} - git_hash: ${{ steps.meta.outputs.git_hash }} - build_date: ${{ steps.meta.outputs.build_date }} + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} steps: - name: Checkout repository @@ -31,92 +34,38 @@ jobs: id: version uses: ./.github/actions/determine-version - - name: Resolve build metadata - id: meta - run: | - set -euo pipefail - HASH=$(git rev-parse --short HEAD) - DATE=$(date +%Y-%m-%d) - BASE_VERSION="${{ steps.version.outputs.version }}" - - VERSION="${BASE_VERSION}+${HASH}" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "base_version=$BASE_VERSION" >> "$GITHUB_OUTPUT" - echo "git_hash=$HASH" >> "$GITHUB_OUTPUT" - echo "build_date=$DATE" >> "$GITHUB_OUTPUT" - echo "::notice::Nightly version: $VERSION" - - build: - needs: prepare - uses: ./.github/workflows/build-and-package.yml - with: - version: ${{ needs.prepare.outputs.version }} - is_nightly: true - asset_name_prefix: OpenSSH-GUI-nightly - - deploy-aur-nightly: - name: Update AUR Nightly Package + deploy-aur-git: + name: Update AUR Git Package runs-on: ubuntu-latest timeout-minutes: 15 - needs: [ prepare, build ] + needs: [ test, prepare ] steps: - name: Checkout repository uses: actions/checkout@v4 with: - fetch-depth: 0 + fetch-depth: 1 - - name: Download build artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts/ - - - name: Prepare and update nightly PKGBUILD + - name: Update openssh-gui-git PKGBUILD run: | set -euo pipefail - rm -rf aur-assets - mkdir -p aur-assets - - find artifacts -type f | while read -r FILE; do - DEST="aur-assets/$(basename "$FILE")" - if [[ -f "$DEST" ]]; then - echo "::error::AUR artifact name collision: $(basename "$FILE")" - exit 1 - fi - cp "$FILE" "$DEST" - done - - test -f aur-assets/OpenSSH-GUI-nightly-linux-x64 \ - || { echo "::error::Missing nightly linux binary"; exit 1; } - test -f aur-assets/appicon.png \ - || { echo "::error::Missing appicon.png"; exit 1; } - test -f "aur-assets/io.github.frequency403.openssh_gui.desktop" \ - || { echo "::error::Missing .desktop file"; exit 1; } - test -f LICENSE \ - || { echo "::error::Missing LICENSE"; exit 1; } - DATE=$(date +%Y%m%d) - AUR_VERSION="${{ needs.prepare.outputs.base_version }}.${DATE}.${{ needs.prepare.outputs.git_hash }}" - echo "AUR_VERSION=$AUR_VERSION" >> "$GITHUB_ENV" - - SHA_BIN=$(sha256sum aur-assets/OpenSSH-GUI-nightly-linux-x64 | cut -d' ' -f1) - SHA_ICON=$(sha256sum aur-assets/appicon.png | cut -d' ' -f1) - SHA_DESKTOP=$(sha256sum "aur-assets/io.github.frequency403.openssh_gui.desktop" | cut -d' ' -f1) - SHA_LICENSE=$(sha256sum LICENSE | cut -d' ' -f1) + HASH=$(git rev-parse --short HEAD) + # pkgver must only contain [a-zA-Z0-9._] — no + or - + AUR_VERSION="${{ needs.prepare.outputs.version }}.${DATE}.${HASH}" - sed -i "s/^pkgver=.*/pkgver=$AUR_VERSION/" openssh-gui-nightly/PKGBUILD - sed -i "s/^sha256sums=.*/sha256sums=('$SHA_BIN' '$SHA_ICON' '$SHA_DESKTOP' '$SHA_LICENSE')/" \ - openssh-gui-nightly/PKGBUILD + sed -i "s/^pkgver=.*/pkgver=$AUR_VERSION/" openssh-gui-git/PKGBUILD + sed -i "s/^pkgrel=.*/pkgrel=1/" openssh-gui-git/PKGBUILD echo "Updated PKGBUILD:" - grep -E '^(pkgver=|sha256sums=)' openssh-gui-nightly/PKGBUILD + grep -E '^(pkgver=|pkgrel=)' openssh-gui-git/PKGBUILD - - name: Publish AUR (openssh-gui-nightly) + - name: Publish AUR (openssh-gui-git) uses: KSXGitHub/github-actions-deploy-aur@v4.1.1 with: - pkgname: openssh-gui-nightly - pkgbuild: ./openssh-gui-nightly/PKGBUILD + pkgname: openssh-gui-git + pkgbuild: ./openssh-gui-git/PKGBUILD commit_username: ${{ github.repository_owner }} commit_email: ${{ github.repository_owner }}@users.noreply.github.com ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} - commit_message: "Nightly update ${{ env.AUR_VERSION }}" \ No newline at end of file + commit_message: "Development update ${{ needs.prepare.outputs.tag }}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 202c2c6..060c411 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,15 @@ name: Unit Tests on: push: - branches: [ main, master, development ] + branches: + - master + - main pull_request: - branches: [ main, master, development ] + branches: + - master + - main + - development + workflow_call: concurrency: group: tests-${{ github.ref }}-${{ github.event_name }} @@ -19,7 +25,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - + - name: Setup .NET environment uses: ./.github/actions/dotnet-setup @@ -30,4 +36,4 @@ jobs: run: dotnet build OpenSSH_GUI.slnx --configuration Release --no-restore - name: Run tests - run: dotnet test OpenSSH_GUI.slnx --configuration Release --no-build --verbosity normal \ No newline at end of file + run: dotnet test OpenSSH_GUI.slnx --configuration Release --no-build --verbosity normal diff --git a/Directory.Build.props b/Directory.Build.props index 7627ab1..537f9c4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ enable default https://github.com/frequency403/OpenSSH-GUI - 3.1.3 + 3.1.4 true diff --git a/OpenSSH_GUI.Core/Lib/Keys/SshKeyFileInformation.cs b/OpenSSH_GUI.Core/Lib/Keys/SshKeyFileInformation.cs index 6463ea4..c315d9a 100644 --- a/OpenSSH_GUI.Core/Lib/Keys/SshKeyFileInformation.cs +++ b/OpenSSH_GUI.Core/Lib/Keys/SshKeyFileInformation.cs @@ -24,7 +24,7 @@ public SshKeyFileInformation(SshKeyFileSource keyFileSource) FileInfo = !string.IsNullOrWhiteSpace(keyFileSource.AbsolutePath) ? new FileInfo(keyFileSource.AbsolutePath) - : new FileInfo(Assembly.GetExecutingAssembly().Location); + : new FileInfo(AppContext.BaseDirectory); FileName = FileInfo.Name; FullFileName = FileInfo.FullName; diff --git a/openssh-gui-bin/PKGBUILD b/openssh-gui-bin/PKGBUILD index cd5acac..2fe387e 100644 --- a/openssh-gui-bin/PKGBUILD +++ b/openssh-gui-bin/PKGBUILD @@ -1,5 +1,5 @@ pkgname=openssh-gui-bin -pkgver=3.1.3 +pkgver=3.1.4 pkgrel=1 pkgdesc="A GUI for OpenSSH configuration and management (Binary version)" arch=('x86_64') diff --git a/openssh-gui-nightly/PKGBUILD b/openssh-gui-nightly/PKGBUILD deleted file mode 100644 index 61cb549..0000000 --- a/openssh-gui-nightly/PKGBUILD +++ /dev/null @@ -1,26 +0,0 @@ -pkgname=openssh-gui-nightly -pkgver=3.0.0.19700101.unknown -pkgrel=1 -pkgdesc="A GUI for OpenSSH configuration and management (Nightly build)" -arch=('x86_64') -url="https://github.com/frequency403/OpenSSH-GUI" -license=('MIT') -depends=('icu' 'openssl' 'zlib' 'krb5' 'libx11') -options=('!strip') -provides=('openssh-gui') -conflicts=('openssh-gui' 'openssh-gui-bin' 'openssh-gui-git') - -_relurl="${url}/releases/download/nightly" - -source=("${pkgname}-${pkgver}::${_relurl}/OpenSSH-GUI-nightly-linux-x64" - "${pkgname}-icon-${pkgver}.png::${_relurl}/appicon.png" - "${pkgname}-desktop-${pkgver}.desktop::${_relurl}/io.github.frequency403.openssh_gui.desktop" - "${pkgname}-license-${pkgver}::${_relurl}/LICENSE") -sha256sums=('SKIP' 'SKIP' 'SKIP' 'SKIP') - -package() { - install -Dm755 "${pkgname}-${pkgver}" "${pkgdir}/usr/bin/openssh-gui" - install -Dm644 "${pkgname}-icon-${pkgver}.png" "${pkgdir}/usr/share/icons/hicolor/256x256/apps/openssh-gui.png" - install -Dm644 "${pkgname}-desktop-${pkgver}.desktop" "${pkgdir}/usr/share/applications/io.github.frequency403.openssh_gui.desktop" - install -Dm644 "${pkgname}-license-${pkgver}" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" -} \ No newline at end of file