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