diff --git a/.github/actions/build-appimage/action.yml b/.github/actions/build-appimage/action.yml deleted file mode 100644 index 339c2a6..0000000 --- a/.github/actions/build-appimage/action.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: 'Build AppImage' -description: > - Packages a self-contained .NET binary into an AppImage for linux-x64. - Requires the repository to be checked out and the binary to exist at the given path. - Note: FUSE is not available on GitHub-hosted runners, so appimagetool runs via - --appimage-extract-and-run. - -inputs: - binary-path: - description: 'Path to the compiled binary to package' - required: true - version: - description: 'Application version (no v-prefix; may contain + for nightly builds)' - required: true - is-nightly: - description: 'Set to "true" to replace + with ~ in the AppStream version' - required: false - default: 'false' - asset-prefix: - description: 'Filename prefix for the output AppImage' - required: false - default: 'OpenSSH-GUI' - appimagetool-tag: - description: 'Release tag of appimagetool to download' - required: false - default: 'continuous' - -outputs: - appimage-name: - description: 'Filename of the produced AppImage' - value: ${{ steps.build.outputs.appimage-name }} - -runs: - using: composite - steps: - - name: Install AppImage build dependencies - shell: bash - run: | - set -euo pipefail - sudo apt-get update -qq - sudo apt-get install -y --no-install-recommends librsvg2-bin appstream - - - name: Download appimagetool - shell: bash - run: | - set -euo pipefail - TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/${{ inputs.appimagetool-tag }}/appimagetool-x86_64.AppImage" - echo "::notice::Downloading appimagetool from $TOOL_URL" - wget --progress=dot:giga "$TOOL_URL" -O appimagetool - chmod +x appimagetool - - - name: Assemble and build AppImage - id: build - shell: bash - run: | - set -euo pipefail - - BINARY_PATH="${{ inputs.binary-path }}" - VERSION="${{ inputs.version }}" - IS_NIGHTLY="${{ inputs.is-nightly }}" - ASSET_PREFIX="${{ inputs.asset-prefix }}" - APP_ID="io.github.frequency403.openssh_gui" - - # Validate all required source files exist before doing any work - for REQUIRED in \ - "$BINARY_PATH" \ - "images/openssh-gui.svg" \ - "appimage/${APP_ID}.metainfo.xml" \ - "appimage/AppRun" - do - if [[ ! -f "$REQUIRED" ]]; then - echo "::error::Required file not found: $REQUIRED" - exit 1 - fi - done - - # Prepare AppDir layout - mkdir -p "AppDir/usr/bin" - mkdir -p "AppDir/usr/share/icons/hicolor/256x256/apps" - mkdir -p "AppDir/usr/share/icons/hicolor/scalable/apps" - mkdir -p "AppDir/usr/share/applications" - mkdir -p "AppDir/usr/share/metainfo" - - cp "$BINARY_PATH" AppDir/usr/bin/openssh-gui - chmod +x AppDir/usr/bin/openssh-gui - - # Icons - rsvg-convert -w 256 -h 256 images/openssh-gui.svg \ - -o AppDir/usr/share/icons/hicolor/256x256/apps/openssh-gui.png - cp images/openssh-gui.svg \ - AppDir/usr/share/icons/hicolor/scalable/apps/openssh-gui.svg - cp AppDir/usr/share/icons/hicolor/256x256/apps/openssh-gui.png AppDir/openssh-gui.png - cp AppDir/usr/share/icons/hicolor/256x256/apps/openssh-gui.png AppDir/appicon.png - - # Metainfo: patch placeholder version and date - METAINFO_DEST="AppDir/usr/share/metainfo/${APP_ID}.metainfo.xml" - cp "appimage/${APP_ID}.metainfo.xml" "$METAINFO_DEST" - - APPSTREAM_VERSION="$VERSION" - if [[ "$IS_NIGHTLY" == "true" ]]; then - APPSTREAM_VERSION="${APPSTREAM_VERSION//+/~}" - fi - sed -i \ - "s|||" \ - "$METAINFO_DEST" - - # Generate .desktop file from metainfo - appstreamcli make-desktop-file \ - "$METAINFO_DEST" \ - "AppDir/usr/share/applications/${APP_ID}.desktop" - cp "AppDir/usr/share/applications/${APP_ID}.desktop" \ - "AppDir/${APP_ID}.desktop" - - cp appimage/AppRun AppDir/AppRun - chmod +x AppDir/AppRun - - # Assemble AppImage (--appimage-extract-and-run bypasses FUSE requirement) - APPIMAGE_NAME="${ASSET_PREFIX}-x86_64.AppImage" - ARCH=x86_64 ./appimagetool --appimage-extract-and-run AppDir "$APPIMAGE_NAME" - - if [[ ! -f "$APPIMAGE_NAME" ]]; then - echo "::error::appimagetool did not produce $APPIMAGE_NAME" - exit 1 - fi - - echo "appimage-name=$APPIMAGE_NAME" >> "$GITHUB_OUTPUT" - echo "::notice::AppImage built successfully: $APPIMAGE_NAME" \ No newline at end of file diff --git a/.github/actions/dotnet-publish/action.yml b/.github/actions/dotnet-publish/action.yml deleted file mode 100644 index 5ee1116..0000000 --- a/.github/actions/dotnet-publish/action.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: 'Publish .NET Application' -description: > - Runs dotnet publish for a single-file, ReadyToRun, self-contained Release build. - Assumes the .NET SDK is already set up in the environment. - -inputs: - project-path: - description: 'Relative path to the .csproj file to publish' - required: true - runtime: - description: 'Target RID (e.g. win-x64, linux-x64, osx-x64)' - required: true - version: - description: 'Version string passed to MSBuild /p:Version (no v-prefix)' - required: true - output-dir: - description: 'Output directory relative to the workspace root' - required: false - default: './publish' - extra-msbuild-args: - description: 'Additional MSBuild property flags, e.g. "-p:IsNightly=true"' - required: false - default: '' - -runs: - using: composite - steps: - - name: Publish application - shell: bash - run: | - set -euo pipefail - dotnet publish "${{ inputs.project-path }}" \ - --configuration Release \ - --runtime "${{ inputs.runtime }}" \ - --output "${{ inputs.output-dir }}" \ - -p:PublishSingleFile=true \ - -p:PublishReadyToRun=true \ - -p:IncludeNativeLibrariesForSelfExtract=true \ - -p:Version="${{ inputs.version }}" \ - ${{ inputs.extra-msbuild-args }} \ No newline at end of file diff --git a/.github/actions/dotnet-setup/action.yml b/.github/actions/dotnet-setup/action.yml deleted file mode 100644 index fa575f3..0000000 --- a/.github/actions/dotnet-setup/action.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: 'Setup .NET Environment' -description: > - Checks out the repository, auto-detects the TargetFramework from - Directory.Build.props, sets up the .NET SDK, and restores the NuGet cache. - -outputs: - dotnet-version: - description: 'Resolved .NET version string, e.g. "10.0.x"' - value: ${{ steps.detect-tfm.outputs.version }} - -runs: - using: composite - steps: - - name: Detect TargetFramework from Directory.Build.props - id: detect-tfm - shell: bash - run: | - set -euo pipefail - PROPS_FILE="Directory.Build.props" - if [[ ! -f "$PROPS_FILE" ]]; then - echo "::error::$PROPS_FILE not found in workspace root" - exit 1 - fi - TFM=$(grep -oPm1 '(?<=net)[0-9.]+' "$PROPS_FILE" || true) - if [[ -z "$TFM" ]]; then - echo "::error::Could not extract from $PROPS_FILE" - exit 1 - fi - echo "version=${TFM}.x" >> "$GITHUB_OUTPUT" - echo "::notice::Resolved .NET version: ${TFM}.x" - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: ${{ steps.detect-tfm.outputs.version }} - - - name: Restore NuGet cache - uses: actions/cache@v5 - with: - path: ~/.nuget/packages - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props', '**/Directory.Build.props') }} - restore-keys: | - ${{ runner.os }}-nuget- \ No newline at end of file diff --git a/.github/workflows/build-and-package.yml b/.github/workflows/build-and-package.yml index d1981cf..ab805bf 100644 --- a/.github/workflows/build-and-package.yml +++ b/.github/workflows/build-and-package.yml @@ -6,10 +6,6 @@ on: version: required: true type: string - is_nightly: - required: false - type: boolean - default: false asset_name_prefix: required: false type: string @@ -34,9 +30,16 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v6 - + - name: Setup .NET environment - uses: ./.github/actions/dotnet-setup + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + cache: true + cache-dependency-path: | + **/*.csproj + **/Directory.Packages.props + **/Directory.Build.props - name: Normalize version and build flags id: meta @@ -47,20 +50,32 @@ jobs: VERSION="${VERSION#v}" echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT" - EXTRA_ARGS="" - if [[ "${{ inputs.is_nightly }}" == "true" ]]; then - EXTRA_ARGS="-p:IsNightly=true" - fi - echo "EXTRA_ARGS=$EXTRA_ARGS" >> "$GITHUB_OUTPUT" + - name: Install/Restore dependencies + shell: bash + run: dotnet restore + + - name: Build application + shell: bash + run: | + set -euo pipefail + dotnet build OpenSSH_GUI/OpenSSH_GUI.csproj \ + --configuration Release \ + --no-restore \ + --runtime "${{ matrix.target }}" \ + -p:Version="${{ steps.meta.outputs.VERSION }}" - name: Publish application - uses: ./.github/actions/dotnet-publish - with: - project-path: OpenSSH_GUI/OpenSSH_GUI.csproj - runtime: ${{ matrix.target }} - version: ${{ steps.meta.outputs.VERSION }} - output-dir: ./publish - extra-msbuild-args: ${{ steps.meta.outputs.EXTRA_ARGS }} + shell: bash + run: | + set -euo pipefail + dotnet publish OpenSSH_GUI/OpenSSH_GUI.csproj \ + --configuration Release \ + --runtime "${{ matrix.target }}" \ + --output ./publish \ + -p:PublishSingleFile=true \ + -p:PublishReadyToRun=true \ + -p:IncludeNativeLibrariesForSelfExtract=true \ + -p:Version="${{ steps.meta.outputs.VERSION }}" - name: Rename artifact for distribution id: rename @@ -76,22 +91,81 @@ jobs: mv "$SOURCE" "./publish/$ASSET_NAME" echo "ASSET_NAME=$ASSET_NAME" >> "$GITHUB_OUTPUT" + - name: Prepare AppImage inputs + if: matrix.target == 'linux-x64' + id: appimage-meta + shell: bash + run: | + set -euo pipefail + + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends librsvg2-bin appstream + + APP_ID="io.github.frequency403.openssh_gui" + APPIMAGE_NAME="${{ inputs.asset_name_prefix }}-x86_64.AppImage" + APPSTREAM_VERSION="${{ steps.meta.outputs.VERSION }}" + + mkdir -p AppDir/usr/bin + mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps + mkdir -p AppDir/usr/share/icons/hicolor/scalable/apps + mkdir -p AppDir/usr/share/applications + mkdir -p AppDir/usr/share/metainfo + + cp "./publish/${{ steps.rename.outputs.ASSET_NAME }}" AppDir/usr/bin/openssh-gui + chmod +x AppDir/usr/bin/openssh-gui + + rsvg-convert -w 256 -h 256 images/openssh-gui.svg \ + -o AppDir/usr/share/icons/hicolor/256x256/apps/openssh-gui.png + cp images/openssh-gui.svg \ + AppDir/usr/share/icons/hicolor/scalable/apps/openssh-gui.svg + cp AppDir/usr/share/icons/hicolor/256x256/apps/openssh-gui.png AppDir/openssh-gui.png + cp AppDir/usr/share/icons/hicolor/256x256/apps/openssh-gui.png AppDir/appicon.png + + METAINFO_DEST="AppDir/usr/share/metainfo/${APP_ID}.metainfo.xml" + cp "appimage/${APP_ID}.metainfo.xml" "$METAINFO_DEST" + sed -i \ + "s|||" \ + "$METAINFO_DEST" + + appstreamcli make-desktop-file \ + "$METAINFO_DEST" \ + "AppDir/usr/share/applications/${APP_ID}.desktop" + cp "AppDir/usr/share/applications/${APP_ID}.desktop" \ + "AppDir/${APP_ID}.desktop" + + cp appimage/AppRun AppDir/AppRun + chmod +x AppDir/AppRun + + echo "APPIMAGE_NAME=$APPIMAGE_NAME" >> "$GITHUB_OUTPUT" + echo "LINUX_ASSET_NAME=${{ steps.rename.outputs.ASSET_NAME }}" >> "$GITHUB_ENV" + echo "APPSTREAM_VERSION=$APPSTREAM_VERSION" >> "$GITHUB_ENV" + - name: Build AppImage if: matrix.target == 'linux-x64' - id: appimage - uses: ./.github/actions/build-appimage + uses: AppImageCrafters/build-appimage-action@master with: - binary-path: ./publish/${{ steps.rename.outputs.ASSET_NAME }} - version: ${{ steps.meta.outputs.VERSION }} - is-nightly: ${{ inputs.is_nightly }} - asset-prefix: ${{ inputs.asset_name_prefix }} + recipe: appimage/AppImageBuilder.yml + + - name: Rename AppImage + if: matrix.target == 'linux-x64' + shell: bash + run: | + set -euo pipefail + BUILT_APPIMAGE="$(find . -maxdepth 1 -type f -name '*.AppImage' | head -n 1)" + + if [[ -z "$BUILT_APPIMAGE" ]]; then + echo "::error::No AppImage was produced" + exit 1 + fi + + mv "$BUILT_APPIMAGE" "${{ steps.appimage-meta.outputs.APPIMAGE_NAME }}" - name: Upload AppImage if: matrix.target == 'linux-x64' uses: actions/upload-artifact@v7 with: - name: ${{ steps.appimage.outputs.appimage-name }} - path: ${{ steps.appimage.outputs.appimage-name }} + name: ${{ steps.appimage-meta.outputs.APPIMAGE_NAME }} + path: ${{ steps.appimage-meta.outputs.APPIMAGE_NAME }} if-no-files-found: error - name: Upload .desktop file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 556f3a8..7bc9ab4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6 - with: + with: fetch-depth: '1' ref: ${{ github.ref }} @@ -37,18 +37,72 @@ jobs: uses: ./.github/actions/determine-version build: - needs: [ test, prepare ] + needs: [test, prepare] uses: ./.github/workflows/build-and-package.yml with: version: ${{ needs.prepare.outputs.version }} - is_nightly: false asset_name_prefix: OpenSSH-GUI + create_installer: + name: Create Windows Installer + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: [test, prepare, build] + + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + + - name: Download Windows binary + uses: actions/download-artifact@v8 + with: + name: OpenSSH-GUI-win-x64.exe + path: ./publish + + - name: Install build dependencies + run: | + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends \ + nsis \ + librsvg2-bin \ + imagemagick + + - name: Convert SVG to multi-resolution ICO + run: | + set -euo pipefail + for SIZE in 16 32 48 64 128 256; do + rsvg-convert -w "$SIZE" -h "$SIZE" \ + images/openssh-gui.svg -o "/tmp/icon-${SIZE}.png" + done + convert \ + /tmp/icon-16.png /tmp/icon-32.png /tmp/icon-48.png \ + /tmp/icon-64.png /tmp/icon-128.png /tmp/icon-256.png \ + installer-icon.ico + + - name: Substitute installer variables + env: + VERSION: ${{ needs.prepare.outputs.version }} + run: | + sed \ + -e "s|@@VERSION@@|${VERSION}|g" \ + -e "s|@@SOURCE_EXE@@|publish\\OpenSSH-GUI-win-x64.exe|g" \ + installer/OpenSSH-GUI.nsi > installer.nsi + + - name: Build installer + run: makensis installer.nsi + + - name: Upload installer artifact + uses: actions/upload-artifact@v7 + with: + name: OpenSSH-GUI-${{ needs.prepare.outputs.version }}-Setup + path: OpenSSH-GUI-${{ needs.prepare.outputs.version }}-Setup.exe + if-no-files-found: error + release: name: Create GitHub Release runs-on: ubuntu-latest timeout-minutes: 15 - needs: [ test, prepare, build ] + needs: [test, prepare, build, create_installer] steps: - name: Checkout repository @@ -60,6 +114,8 @@ jobs: path: artifacts/ - name: Prepare and validate release assets + env: + VERSION: ${{ needs.prepare.outputs.version }} run: | set -euo pipefail rm -rf release-assets @@ -75,13 +131,17 @@ jobs: cp "$FILE" "$DEST" done + # Raw win-x64 binary is an intermediate artifact only — + # the installer replaces it as the Windows distribution unit + rm -f "release-assets/OpenSSH-GUI-win-x64.exe" + cp LICENSE release-assets/LICENSE # 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-${VERSION}-Setup.exe" \ "OpenSSH-GUI-osx-x64" \ "OpenSSH-GUI-x86_64.AppImage" \ "appicon.png" \ diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml index 2e4d28a..25b0a22 100644 --- a/.github/workflows/deploy-release.yml +++ b/.github/workflows/deploy-release.yml @@ -48,7 +48,7 @@ jobs: - name: Checkout PKGBUILD only uses: actions/checkout@v6 with: - ref: ${{ github.event.release.tag_name }} + ref: ${{ github.event.release.tag_name || inputs.tag }} - name: Update PKGBUILD version run: | @@ -70,17 +70,14 @@ jobs: commit_message: "Update to ${{ needs.get_version.outputs.tag }}" force_push: 'false' regenerate_srcinfo: 'false' - updpkgsums: 'true' + updpkgsums: 'true' winget_publish: name: Update Winget Package runs-on: ubuntu-slim - needs: get_version + needs: aur_publish timeout-minutes: 10 steps: - - name: Checkout repository - uses: actions/checkout@v6 - - name: Update Winget manifest uses: vedantmgoyal9/winget-releaser@main with: @@ -89,5 +86,5 @@ jobs: fork-user: ${{ github.repository_owner }} version: ${{ needs.get_version.outputs.version }} release-tag: ${{ needs.get_version.outputs.tag }} - installers-regex: 'win-x64\.exe$' - token: ${{ secrets.WINGET_PUBLISH_TOKEN }} + installers-regex: 'Setup\.exe$' + token: ${{ secrets.WINGET_PUBLISH_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 124ee6a..2b9a5d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,14 @@ jobs: uses: actions/checkout@v6 - name: Setup .NET environment - uses: ./.github/actions/dotnet-setup + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + cache: true + cache-dependency-path: | + **/*.csproj + **/Directory.Packages.props + **/Directory.Build.props - name: Restore dependencies run: dotnet restore OpenSSH_GUI.slnx diff --git a/.github/workflows/version-guard.yml b/.github/workflows/version-guard.yml new file mode 100644 index 0000000..a184f9e --- /dev/null +++ b/.github/workflows/version-guard.yml @@ -0,0 +1,60 @@ +name: Version Guard (release -> master) + +on: + pull_request: + branches: + - master + types: + - opened + - synchronize + - reopened + - edited + +jobs: + validate-version: + runs-on: ubuntu-latest + + steps: + - name: Checkout target (master) + uses: actions/checkout@v6 + with: + ref: master + fetch-depth: 0 + + - name: Extract master version + id: master_version + uses: ./.github/actions/determine-version + + + - name: Checkout PR branch + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + + - name: Extract release version + id: release_version + uses: ./.github/actions/determine-version + + - name: Compare versions + shell: bash + run: | + MASTER="${{ steps.master_version.outputs.version }}" + RELEASE="${{ steps.release_version.outputs.version }}" + + echo "Master: $MASTER" + echo "Release: $RELEASE" + + python3 - << 'EOF' + import sys + from packaging.version import Version + + master = Version("${{ steps.master_version.outputs.version }}") + release = Version("${{ steps.release_version.outputs.version }}") + + if release <= master: + print(f"Release version {release} must be greater than master {master}") + sys.exit(1) + + print("Version check passed") + EOF \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 8d1548d..7aa6a2d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ enable default https://github.com/frequency403/OpenSSH-GUI - 3.2.0 + 3.2.1 true @@ -15,4 +15,9 @@ <_Parameter2>$(PackageProjectUrl) + + + + AVLN3001 + \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets index 03ab852..35e4484 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,37 +1,12 @@ - - - - - - - - $([System.String]::Copy('$(GitCommitHash)').Trim()) - unknown - - - - + BeforeTargets="GetAssemblyVersion;GenerateAssemblyInfo"> 1.0.0 - false - - false - $(GitCommitHash) - - $(BaseVersion) - $(BaseVersion) - $(BaseVersion)-$(GitCommitHash) - $(BaseVersion)-$(GitCommitHash) + $(BaseVersion) + $(BaseVersion) diff --git a/OpenSSH_GUI.Core/Configuration/LoggerConfiguration.cs b/OpenSSH_GUI.Core/Configuration/LoggerConfiguration.cs index 72d17f6..ab91fce 100644 --- a/OpenSSH_GUI.Core/Configuration/LoggerConfiguration.cs +++ b/OpenSSH_GUI.Core/Configuration/LoggerConfiguration.cs @@ -8,7 +8,7 @@ public record LoggerConfiguration { #if DEBUG private const string LogTemplate = - "[{Timestamp:yyyy/MM/dd HH:mm:ss}] [{Level:u3}] ({FileName}:{LineNumber}): {Message:lj}{NewLine}{Exception}"; + "[{Timestamp:yyyy/MM/dd HH:mm:ss}] [{Level:u3}] ({ClassName}:{LineNumber}): {Message:lj}{NewLine}{Exception}"; #else private const string LogTemplate = "[{Timestamp:yyyy/MM/dd HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}"; diff --git a/OpenSSH_GUI.SshConfig/Extensions/SshConfigurationExtensions.cs b/OpenSSH_GUI.SshConfig/Extensions/SshConfigurationExtensions.cs index b704e5c..181e032 100644 --- a/OpenSSH_GUI.SshConfig/Extensions/SshConfigurationExtensions.cs +++ b/OpenSSH_GUI.SshConfig/Extensions/SshConfigurationExtensions.cs @@ -55,8 +55,7 @@ public IConfigurationBuilder AddSshConfig(string path, bool optional, /// /// The to use to access the file. /// - /// Path relative to the base path stored in of - /// . + /// Path relative to the base path stored in /// /// Whether the file is optional. /// Whether the configuration should be reloaded if the file changes. diff --git a/OpenSSH_GUI/Logging/Enricher/CallerEnricher.cs b/OpenSSH_GUI/Logging/Enricher/CallerEnricher.cs index d90386f..50a3457 100644 --- a/OpenSSH_GUI/Logging/Enricher/CallerEnricher.cs +++ b/OpenSSH_GUI/Logging/Enricher/CallerEnricher.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Reflection; using Serilog.Core; using Serilog.Events; @@ -12,7 +13,8 @@ namespace OpenSSH_GUI.Logging.Enricher; public sealed class CallerEnricher : ILogEventEnricher { private const string LineNumberProperty = "LineNumber"; - private const string FileNameProperty = "FileName"; + private const string ClassNameProperty = "ClassName"; + private const string ClassNameDefaultValue = ""; // Serilog-internal namespaces to skip when walking the stack private static readonly string[] SerilogNamespaces = @@ -29,12 +31,33 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { var frame = FindCallerFrame(); var lineNumber = frame?.GetFileLineNumber() ?? 0; - var fileName = Path.GetFileName(frame?.GetFileName()) ?? ""; + var frameMethod = frame?.GetMethod(); + var className = GetDeclaringClassName(frameMethod); logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(LineNumberProperty, lineNumber)); - logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(FileNameProperty, fileName)); + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(ClassNameProperty, className)); } + private static string GetDeclaringClassName(MethodBase? methodBase) + { + if(methodBase == null) return ClassNameDefaultValue; + var declaringType = methodBase.DeclaringType; + var declaringTypeName = declaringType?.Name; + if(declaringTypeName is null) return ClassNameDefaultValue; + while (declaringTypeName.Contains('<')) + { + if (declaringType?.DeclaringType is not null) + { + declaringTypeName = declaringType.DeclaringType.Name; + } + else + { + break; + } + } + return declaringTypeName; + } + /// /// Walks the stack to find the first frame outside of Serilog, system namespaces, /// and the enricher itself. diff --git a/OpenSSH_GUI/Program.cs b/OpenSSH_GUI/Program.cs index 8beaaee..f072e6f 100644 --- a/OpenSSH_GUI/Program.cs +++ b/OpenSSH_GUI/Program.cs @@ -20,7 +20,6 @@ namespace OpenSSH_GUI; -// REFACTOR: Change Readme.MD accordingly to new Project functionality; [UsedImplicitly] internal sealed class Program { diff --git a/README.md b/README.md index aac0f87..140930f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # OpenSSH GUI +[![Development Build](https://github.com/frequency403/OpenSSH-GUI/actions/workflows/staging.yml/badge.svg?branch=development)](https://github.com/frequency403/OpenSSH-GUI/actions/workflows/staging.yml) +[![Release](https://github.com/frequency403/OpenSSH-GUI/actions/workflows/build.yml/badge.svg)](https://github.com/frequency403/OpenSSH-GUI/actions/workflows/build.yml) +![GitHub Release](https://img.shields.io/github/v/release/frequency403/OpenSSH-GUI?sort=semver&display_name=tag&logo=keeweb&link=https%3A%2F%2Fgithub.com%2Ffrequency403%2FOpenSSH-GUI%2Freleases%2Flatest) +![AUR Version](https://img.shields.io/aur/version/openssh-gui-bin?style=plastic&logo=archlinux&label=openssh-gui-bin&link=https%3A%2F%2Faur.archlinux.org%2Fpackages%2Fopenssh-gui-bin) +![AUR Version](https://img.shields.io/aur/version/openssh-gui-git?style=plastic&logo=archlinux&label=openssh-gui-git&link=https%3A%2F%2Faur.archlinux.org%2Fpackages%2Fopenssh-gui-git) +![WinGet Package Version](https://img.shields.io/winget/v/frequency403.OpenSSHGUI?style=plastic&logo=gitforwindows) + + A cross-platform desktop application for managing SSH keys, known hosts, and authorized keys — built with Avalonia UI, ReactiveUI, and .NET 10. The goal of this project is to give users a modern, keyboard-friendly GUI for everything that usually requires `ssh-keygen` or hand-editing text files. It runs on **Windows**, **Linux**, and **macOS** and works entirely locally — no cloud, no telemetry. diff --git a/appimage/AppImageBuilder.yml b/appimage/AppImageBuilder.yml new file mode 100644 index 0000000..8da2dc0 --- /dev/null +++ b/appimage/AppImageBuilder.yml @@ -0,0 +1,20 @@ +version: 1 + +AppDir: + path: ./AppDir + + app_info: + id: io.github.frequency403.openssh_gui + name: OpenSSH GUI + icon: openssh-gui + version: ${APPSTREAM_VERSION} + exec: usr/bin/openssh-gui + exec_args: "$@" + + runtime: + env: + APPDIR_LIBRARY_PATH: "$APPDIR/usr/lib" + +AppImage: + arch: x86_64 + update-information: guess \ No newline at end of file diff --git a/installer/OpenSSH-GUI.nsi b/installer/OpenSSH-GUI.nsi new file mode 100644 index 0000000..c1a655c --- /dev/null +++ b/installer/OpenSSH-GUI.nsi @@ -0,0 +1,87 @@ +; ============================================================ +; OpenSSH-GUI — NSIS Installer Script +; Placeholders (@@VAR@@) are substituted at build time +; via sed in the create_installer job of deploy-release.yml +; ============================================================ + +!define APP_NAME "OpenSSH GUI" +!define APP_VERSION "@@VERSION@@" +!define PUBLISHER "frequency403" +!define EXE_NAME "OpenSSH-GUI.exe" +!define INSTALL_DIR "$PROGRAMFILES64\OpenSSH-GUI" +!define REG_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenSSH-GUI" +!define SOURCE_EXE "@@SOURCE_EXE@@" + +; ── Output ──────────────────────────────────────────────── +Name "${APP_NAME} ${APP_VERSION}" +OutFile "OpenSSH-GUI-${APP_VERSION}-Setup.exe" +InstallDir "${INSTALL_DIR}" +InstallDirRegKey HKLM "Software\OpenSSH-GUI" "Install_Dir" +RequestExecutionLevel admin +SetCompressor /SOLID lzma + +; ── Icons ───────────────────────────────────────────────── +Icon "installer-icon.ico" + +; ── MUI2 ────────────────────────────────────────────────── +!include "MUI2.nsh" + +!define MUI_ABORTWARNING +!define MUI_ICON "installer-icon.ico" +!define MUI_UNICON "installer-icon.ico" + +!define MUI_WELCOMEPAGE_TITLE "Installing ${APP_NAME} ${APP_VERSION}" +!define MUI_WELCOMEPAGE_TEXT "This wizard will install ${APP_NAME} on your computer.$\r$\n$\r$\nClick Next to continue." +!define MUI_FINISHPAGE_RUN "$INSTDIR\${EXE_NAME}" +!define MUI_FINISHPAGE_RUN_TEXT "Launch ${APP_NAME}" + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +!insertmacro MUI_LANGUAGE "English" + +; ── Install ─────────────────────────────────────────────── +Section "Install" SEC_MAIN + SetOutPath "$INSTDIR" + + ; Artifact rename: build-time name → final EXE name + File "/oname=${EXE_NAME}" "${SOURCE_EXE}" + + ; Start Menu + CreateDirectory "$SMPROGRAMS\${APP_NAME}" + CreateShortcut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" \ + "$INSTDIR\${EXE_NAME}" "" "$INSTDIR\${EXE_NAME}" 0 + CreateShortcut "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" \ + "$INSTDIR\uninstall.exe" + + WriteUninstaller "$INSTDIR\uninstall.exe" + + ; Add/Remove Programs + WriteRegStr HKLM "${REG_KEY}" "DisplayName" "${APP_NAME}" + WriteRegStr HKLM "${REG_KEY}" "DisplayVersion" "${APP_VERSION}" + WriteRegStr HKLM "${REG_KEY}" "Publisher" "${PUBLISHER}" + WriteRegStr HKLM "${REG_KEY}" "InstallLocation" "$INSTDIR" + WriteRegStr HKLM "${REG_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe" /S' + WriteRegStr HKLM "${REG_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' + WriteRegDWORD HKLM "${REG_KEY}" "NoModify" 1 + WriteRegDWORD HKLM "${REG_KEY}" "NoRepair" 1 +SectionEnd + +; ── Uninstall ───────────────────────────────────────────── +Section "Uninstall" + Delete "$INSTDIR\${EXE_NAME}" + Delete "$INSTDIR\uninstall.exe" + RMDir "$INSTDIR" + + Delete "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" + Delete "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" + RMDir "$SMPROGRAMS\${APP_NAME}" + + DeleteRegKey HKLM "${REG_KEY}" + DeleteRegKey HKLM "Software\OpenSSH-GUI" +SectionEnd diff --git a/openssh-gui-bin/PKGBUILD b/openssh-gui-bin/PKGBUILD index 28501eb..adc11e6 100644 --- a/openssh-gui-bin/PKGBUILD +++ b/openssh-gui-bin/PKGBUILD @@ -19,4 +19,11 @@ source=( "LICENSE::https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/v${pkgver}/LICENSE" ) -sha256sums=('SKIP' 'SKIP' 'SKIP' 'SKIP') \ No newline at end of file +sha256sums=('SKIP' 'SKIP' 'SKIP' 'SKIP') + +package() { + install -Dm755 "${srcdir}/openssh-gui" "${pkgdir}/usr/bin/openssh-gui" + install -Dm644 "${srcdir}/openssh-gui-icon" "${pkgdir}/usr/share/pixmaps/openssh-gui.png" + install -Dm644 "${srcdir}/openssh-gui-desktop" "${pkgdir}/usr/share/applications/io.github.frequency403.openssh_gui.desktop" + install -Dm644 "${srcdir}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" +} \ No newline at end of file diff --git a/openssh-gui-git/PKGBUILD b/openssh-gui-git/PKGBUILD index 0c3498f..6d2ef41 100644 --- a/openssh-gui-git/PKGBUILD +++ b/openssh-gui-git/PKGBUILD @@ -1,13 +1,14 @@ pkgname=openssh-gui-git _pkgname=OpenSSH-GUI -pkgver=2.2.1.r0.g845610b +pkgver=3.2.0.20260612.a1b2c3d pkgrel=1 pkgdesc="A GUI for OpenSSH configuration and management (Sourcepackage)" arch=('x86_64') url="https://github.com/frequency403/OpenSSH-GUI" license=('MIT') +options=('!strip') -depends=('dotnet-runtime-10.0') +depends=('icu' 'openssl' 'zlib' 'krb5' 'libx11') makedepends=('git' 'dotnet-sdk-10.0' 'librsvg') provides=('openssh-gui') @@ -16,28 +17,18 @@ conflicts=('openssh-gui' 'openssh-gui-bin' 'openssh-gui-nightly') source=("git+${url}.git#branch=development") sha256sums=('SKIP') -pkgver() { - cd "${srcdir}/${_pkgname}" - - local base count hash - base=$(grep -oP '(?<=)[^<]+' Directory.Build.props) - count=$(git rev-list --count HEAD) - hash=$(git rev-parse --short HEAD) - - printf "%s.r%s.g%s\n" "$base" "$count" "$hash" -} - build() { cd "${srcdir}/${_pkgname}" dotnet publish OpenSSH_GUI/OpenSSH_GUI.csproj \ - --configuration Release \ - --runtime linux-x64 \ - --output publish \ - -p:PublishSingleFile=true \ - -p:PublishReadyToRun=true \ - -p:IncludeNativeLibrariesForSelfExtract=true \ - -p:SelfContained=false + --configuration Release \ + --runtime linux-x64 \ + --output publish \ + -p:SelfContained=true \ + -p:PublishSingleFile=true \ + -p:PublishReadyToRun=false \ + -p:IncludeNativeLibrariesForSelfExtract=true \ + -p:InformationalVersion="${pkgver}" rsvg-convert -w 256 -h 256 images/openssh-gui.svg -o appicon-256.png } @@ -50,7 +41,7 @@ package() { install -Dm644 "appicon-256.png" \ "${pkgdir}/usr/share/icons/hicolor/256x256/apps/openssh-gui.png" - + install -Dm644 "images/openssh-gui.svg" \ "${pkgdir}/usr/share/icons/hicolor/scalable/apps/openssh-gui.svg" diff --git a/openssh-gui.desktop b/openssh-gui.desktop index e3e8da2..50e85f7 100644 --- a/openssh-gui.desktop +++ b/openssh-gui.desktop @@ -1,10 +1,14 @@ [Desktop Entry] -Version=1.0 +Version=1.5 Type=Application Name=OpenSSH GUI -Comment=A GUI for OpenSSH configuration and management +GenericName=SSH Configuration Manager +Comment=GUI for OpenSSH key and configuration management +Keywords=SSH;OpenSSH;Security;Network;RemoteAccess;Key; +TryExec=openssh-gui Exec=openssh-gui Icon=openssh-gui -Categories=Network;RemoteAccess; +Categories=Network;RemoteAccess;Security;System; Terminal=false +StartupNotify=true StartupWMClass=OpenSSH_GUI