Add GNU Privacy Guard as a Windows FMA (#50025)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** #50020 # What this does Adds **GNU Privacy Guard** as a Windows Fleet-maintained app. One of the 11 apps split out of #48501 that failed the FMA validator; #50016 shipped the 6 that passed. ## Why it was failing The install itself worked — the validator logged `New application detected at: C:\Program Files\GnuPG`. The *script* never returned: ``` 20:18:36 INFO msg="Executing install script..." app="GNU Privacy Guard" 20:28:36 ERROR msg="Error executing install script: exit status 1" # exactly 10:00 later 20:28:36 INFO msg="New application detected at: C:\Program Files\GnuPG" ``` Ten minutes on the nose is the validator's `executeScript` timeout. The cause is a PowerShell detail rather than anything wrong with the installer: **`Start-Process -Wait` waits for the process *and all of its descendants***. GnuPG's installer starts `gpg-agent`, `dirmngr`, `keyboxd` and `scdaemon` and leaves them resident, so `-Wait` never returns. The same run left the installer `.exe` locked in the validator's temp dir, which is the other tell that a child process was still alive. The install script now follows the pattern already established by [`ollama_install.ps1`](ee/maintained-apps/inputs/winget/scripts/ollama_install.ps1): start with `-PassThru` (no `-Wait`), wait on the installer process alone with a 7-minute cap (below the caller's 10-minute script budget), poll for the Add/Remove Programs entry so a fast-returning installer can't be mistaken for a finished one, then stop the daemons. Stopping the daemons also fixes the uninstall, which would otherwise fail on files those processes hold open. The uninstall script stops them up front, uses NSIS's `_?=<dir>` switch so the uninstaller runs in place instead of relaunching itself detached from `%TEMP%`, and polls the ARP key to confirm removal. ## Notes - Clean ARP `DisplayName` (`GNU Privacy Guard`), so exact name matching — no `fuzzy_match_name` needed. Publisher `The GnuPG Project`. - Ships a new catalog icon and website asset. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [x] Timeouts are implemented and retries are limited to avoid infinite loops ## Testing - [x] FMA CI validator (install → detect → uninstall) **passes** on the SYSTEM-context Windows runner — [run 30384069714](https://github.com/fleetdm/fleet/actions/runs/30384069714) (`All checks passed`) - [x] Generated output verified locally: manifest SHA matches the winget manifest, exists/patched queries reviewed for name + publisher correctness, `apps.json` is valid JSON with a description filled in. - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added GNU Privacy Guard as a supported Windows application in the maintained apps catalog. * Added install/upgrade detection and uninstall support for Windows. * Added GNU Privacy Guard to the software catalog (Security category). * Added a dedicated GNU Privacy Guard icon to the software interface for proper name-based display. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "GNU Privacy Guard",
|
||||
"slug": "gnupg/windows",
|
||||
"package_identifier": "GnuPG.GnuPG",
|
||||
"unique_identifier": "GNU Privacy Guard",
|
||||
"program_publisher": "The GnuPG Project",
|
||||
"install_script_path": "ee/maintained-apps/inputs/winget/scripts/gnupg_install.ps1",
|
||||
"uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/gnupg_uninstall.ps1",
|
||||
"installer_arch": "x64",
|
||||
"installer_type": "exe",
|
||||
"installer_scope": "",
|
||||
"default_categories": ["Security"]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
# Learn more about .exe install scripts:
|
||||
# http://fleetdm.com/learn-more-about/exe-install-scripts
|
||||
|
||||
$exeFilePath = "${env:INSTALLER_PATH}"
|
||||
|
||||
# The installer stalls on a modal dialog with no interactive desktop and never
|
||||
# exits. Closing its window lets it run through to the section that writes the
|
||||
# Add/Remove Programs entry; killing it instead would leave a partial install.
|
||||
$daemons = @("gpg-agent", "dirmngr", "keyboxd", "scdaemon", "gpg-connect-agent", "gpgconf", "gpa", "launch-gpa")
|
||||
$installTimeoutSeconds = 420
|
||||
$pollSeconds = 10
|
||||
$graceSeconds = 30
|
||||
|
||||
$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
||||
$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
||||
# Uninstall info is written with SHCTX, so it can land per-user.
|
||||
$userKey = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
||||
|
||||
function Test-GnuPGRegistered {
|
||||
$null -ne (Get-ChildItem -Path @($machineKey, $machineKey32on64, $userKey) -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |
|
||||
Where-Object { $_.DisplayName -like "GNU Privacy Guard*" } |
|
||||
Select-Object -First 1)
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$process = Start-Process -FilePath "$exeFilePath" -ArgumentList "/S" -PassThru
|
||||
# Keeps .ExitCode readable after the process ends.
|
||||
$null = $process.Handle
|
||||
|
||||
$elapsed = 0
|
||||
while (-not $process.HasExited -and ($elapsed -lt $installTimeoutSeconds)) {
|
||||
Start-Sleep -Seconds $pollSeconds
|
||||
$elapsed += $pollSeconds
|
||||
$process.Refresh()
|
||||
if ($process.HasExited) { break }
|
||||
|
||||
$children = @(Get-Process -Name $daemons -ErrorAction SilentlyContinue |
|
||||
Select-Object -ExpandProperty Name -Unique)
|
||||
$windowTitle = ""
|
||||
try { $windowTitle = $process.MainWindowTitle } catch { }
|
||||
|
||||
Write-Host "Installing... ($elapsed seconds, registered: $(Test-GnuPGRegistered), window: '$windowTitle', children: $($children -join ', '))"
|
||||
|
||||
if ($elapsed -ge $graceSeconds -and $process.MainWindowHandle -ne [IntPtr]::Zero) {
|
||||
Write-Host "Installer is showing a window ('$windowTitle'); closing it so the install can continue."
|
||||
$null = $process.CloseMainWindow()
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $process.HasExited) {
|
||||
Write-Host "Installer still running after ${installTimeoutSeconds}s; stopping it."
|
||||
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 2
|
||||
} else {
|
||||
Write-Host "Install exit code: $($process.ExitCode)"
|
||||
}
|
||||
|
||||
# Stop the resident daemons; they hold file locks the uninstall needs released.
|
||||
foreach ($name in $daemons) {
|
||||
Stop-Process -Name $name -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Registration is the success signal: a killed installer's exit code says nothing.
|
||||
if (-not (Test-GnuPGRegistered)) {
|
||||
Write-Host "GnuPG did not register in Add/Remove Programs."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
Write-Host "GnuPG is registered in Add/Remove Programs."
|
||||
Exit 0
|
||||
|
||||
} catch {
|
||||
Write-Host "Error: $_"
|
||||
Exit 1
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
$softwareName = "GNU Privacy Guard"
|
||||
$softwarePublisher = "The GnuPG Project"
|
||||
|
||||
# The daemons hold file locks and would block -Wait, so stop them first and wait
|
||||
# only on the uninstaller process.
|
||||
$daemons = @("gpg-agent", "dirmngr", "keyboxd", "scdaemon", "gpg-connect-agent", "gpgconf")
|
||||
$timeoutSeconds = 300
|
||||
|
||||
$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
||||
$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
||||
# Uninstall info is written with SHCTX, so it can land per-user.
|
||||
$userKey = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
||||
$userKey32on64 = 'HKCU:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
||||
$exitCode = 0
|
||||
|
||||
function Get-GnuPGUninstallKey {
|
||||
Get-ChildItem -Path @($machineKey, $machineKey32on64, $userKey, $userKey32on64) -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |
|
||||
Where-Object { $_.DisplayName -like "$softwareName*" -and $_.Publisher -eq $softwarePublisher } |
|
||||
Select-Object -First 1
|
||||
}
|
||||
|
||||
foreach ($daemon in $daemons) {
|
||||
Stop-Process -Name $daemon -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
try {
|
||||
$key = Get-GnuPGUninstallKey
|
||||
if (-not $key) {
|
||||
Write-Host "Uninstall entry not found for '$softwareName'."
|
||||
Exit 0
|
||||
}
|
||||
|
||||
$uninstallString = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
|
||||
Write-Host "Uninstall string: $uninstallString"
|
||||
|
||||
# Handles quoted paths, unquoted paths with spaces, and bare tokens.
|
||||
$uninstallCommand = $uninstallString
|
||||
if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
|
||||
$uninstallCommand = $Matches[1]
|
||||
} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
|
||||
$uninstallCommand = $Matches[1]
|
||||
} elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') {
|
||||
$uninstallCommand = $Matches[1]
|
||||
}
|
||||
|
||||
# NSIS uninstallers relaunch from %TEMP% and detach by default; "_?=<dir>"
|
||||
# runs in place so this stays synchronous. Must be the last argument.
|
||||
$installDir = Split-Path -Parent $uninstallCommand
|
||||
$uninstallArgs = "/S _?=$installDir"
|
||||
|
||||
Write-Host "Uninstall command: $uninstallCommand"
|
||||
Write-Host "Uninstall args: $uninstallArgs"
|
||||
|
||||
$process = Start-Process -FilePath $uninstallCommand -ArgumentList $uninstallArgs -PassThru
|
||||
# Keeps .ExitCode readable after the process ends.
|
||||
$null = $process.Handle
|
||||
|
||||
if (-not $process.WaitForExit($timeoutSeconds * 1000)) {
|
||||
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
|
||||
Write-Host "Uninstall timed out after $timeoutSeconds seconds"
|
||||
Exit 1603
|
||||
}
|
||||
|
||||
$exitCode = $process.ExitCode
|
||||
Write-Host "Uninstall exit code: $exitCode"
|
||||
} catch {
|
||||
Write-Host "Error: $_"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
# Stop anything restarted, then wait for the ARP entry to clear.
|
||||
foreach ($daemon in $daemons) {
|
||||
Stop-Process -Name $daemon -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$elapsed = 0
|
||||
while ((Get-GnuPGUninstallKey) -and ($elapsed -lt 120)) {
|
||||
Start-Sleep -Seconds 5
|
||||
$elapsed += 5
|
||||
Write-Host "Waiting for the uninstall to finish... ($elapsed seconds)"
|
||||
}
|
||||
|
||||
if (Get-GnuPGUninstallKey) {
|
||||
Write-Host "'$softwareName' is still registered after the uninstall."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
Exit $exitCode
|
||||
@@ -3753,6 +3753,13 @@
|
||||
"unique_identifier": "com.GeorgSeifert.Glyphs3",
|
||||
"description": "Glyphs is a font editor."
|
||||
},
|
||||
{
|
||||
"name": "GNU Privacy Guard",
|
||||
"slug": "gnupg/windows",
|
||||
"platform": "windows",
|
||||
"unique_identifier": "GNU Privacy Guard",
|
||||
"description": "GNU Privacy Guard is an implementation of the OpenPGP standard for encrypting and signing data and communications."
|
||||
},
|
||||
{
|
||||
"name": "Go",
|
||||
"slug": "go/windows",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.5.21",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM programs WHERE name = 'GNU Privacy Guard' AND publisher = 'The GnuPG Project';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'GNU Privacy Guard' AND publisher = 'The GnuPG Project' AND version_compare(version, '2.5.21') < 0);"
|
||||
},
|
||||
"installer_url": "https://gnupg.org/ftp/gcrypt/binary/gnupg-w32-2.5.21_20260702.exe",
|
||||
"install_script_ref": "8c2ff75e",
|
||||
"uninstall_script_ref": "d1a2230f",
|
||||
"sha256": "6246c925a73167253444afc24a0deb83a3f43b7d636af84d6aaf48a98a62f024",
|
||||
"default_categories": [
|
||||
"Security"
|
||||
]
|
||||
}
|
||||
],
|
||||
"refs": {
|
||||
"8c2ff75e": "# Learn more about .exe install scripts:\n# http://fleetdm.com/learn-more-about/exe-install-scripts\n\n$exeFilePath = \"${env:INSTALLER_PATH}\"\n\n# The installer stalls on a modal dialog with no interactive desktop and never\n# exits. Closing its window lets it run through to the section that writes the\n# Add/Remove Programs entry; killing it instead would leave a partial install.\n$daemons = @(\"gpg-agent\", \"dirmngr\", \"keyboxd\", \"scdaemon\", \"gpg-connect-agent\", \"gpgconf\", \"gpa\", \"launch-gpa\")\n$installTimeoutSeconds = 420\n$pollSeconds = 10\n$graceSeconds = 30\n\n$machineKey = 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$machineKey32on64 = 'HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n# Uninstall info is written with SHCTX, so it can land per-user.\n$userKey = 'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n\nfunction Test-GnuPGRegistered {\n $null -ne (Get-ChildItem -Path @($machineKey, $machineKey32on64, $userKey) -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |\n Where-Object { $_.DisplayName -like \"GNU Privacy Guard*\" } |\n Select-Object -First 1)\n}\n\ntry {\n\n$process = Start-Process -FilePath \"$exeFilePath\" -ArgumentList \"/S\" -PassThru\n# Keeps .ExitCode readable after the process ends.\n$null = $process.Handle\n\n$elapsed = 0\nwhile (-not $process.HasExited -and ($elapsed -lt $installTimeoutSeconds)) {\n Start-Sleep -Seconds $pollSeconds\n $elapsed += $pollSeconds\n $process.Refresh()\n if ($process.HasExited) { break }\n\n $children = @(Get-Process -Name $daemons -ErrorAction SilentlyContinue |\n Select-Object -ExpandProperty Name -Unique)\n $windowTitle = \"\"\n try { $windowTitle = $process.MainWindowTitle } catch { }\n\n Write-Host \"Installing... ($elapsed seconds, registered: $(Test-GnuPGRegistered), window: '$windowTitle', children: $($children -join ', '))\"\n\n if ($elapsed -ge $graceSeconds -and $process.MainWindowHandle -ne [IntPtr]::Zero) {\n Write-Host \"Installer is showing a window ('$windowTitle'); closing it so the install can continue.\"\n $null = $process.CloseMainWindow()\n }\n}\n\nif (-not $process.HasExited) {\n Write-Host \"Installer still running after ${installTimeoutSeconds}s; stopping it.\"\n Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue\n Start-Sleep -Seconds 2\n} else {\n Write-Host \"Install exit code: $($process.ExitCode)\"\n}\n\n# Stop the resident daemons; they hold file locks the uninstall needs released.\nforeach ($name in $daemons) {\n Stop-Process -Name $name -Force -ErrorAction SilentlyContinue\n}\n\n# Registration is the success signal: a killed installer's exit code says nothing.\nif (-not (Test-GnuPGRegistered)) {\n Write-Host \"GnuPG did not register in Add/Remove Programs.\"\n Exit 1\n}\n\nWrite-Host \"GnuPG is registered in Add/Remove Programs.\"\nExit 0\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n",
|
||||
"d1a2230f": "$softwareName = \"GNU Privacy Guard\"\n$softwarePublisher = \"The GnuPG Project\"\n\n# The daemons hold file locks and would block -Wait, so stop them first and wait\n# only on the uninstaller process.\n$daemons = @(\"gpg-agent\", \"dirmngr\", \"keyboxd\", \"scdaemon\", \"gpg-connect-agent\", \"gpgconf\")\n$timeoutSeconds = 300\n\n$machineKey = 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$machineKey32on64 = 'HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n# Uninstall info is written with SHCTX, so it can land per-user.\n$userKey = 'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$userKey32on64 = 'HKCU:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$exitCode = 0\n\nfunction Get-GnuPGUninstallKey {\n Get-ChildItem -Path @($machineKey, $machineKey32on64, $userKey, $userKey32on64) -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |\n Where-Object { $_.DisplayName -like \"$softwareName*\" -and $_.Publisher -eq $softwarePublisher } |\n Select-Object -First 1\n}\n\nforeach ($daemon in $daemons) {\n Stop-Process -Name $daemon -Force -ErrorAction SilentlyContinue\n}\n\ntry {\n $key = Get-GnuPGUninstallKey\n if (-not $key) {\n Write-Host \"Uninstall entry not found for '$softwareName'.\"\n Exit 0\n }\n\n $uninstallString = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }\n Write-Host \"Uninstall string: $uninstallString\"\n\n # Handles quoted paths, unquoted paths with spaces, and bare tokens.\n $uninstallCommand = $uninstallString\n if ($uninstallCommand -match '^\\s*\"([^\"]+)\"\\s*(.*)$') {\n $uninstallCommand = $Matches[1]\n } elseif ($uninstallCommand -match '(?i)^\\s*(.+?\\.exe)\\s*(.*)$') {\n $uninstallCommand = $Matches[1]\n } elseif ($uninstallCommand -match '^\\s*(\\S+)\\s*(.*)$') {\n $uninstallCommand = $Matches[1]\n }\n\n # NSIS uninstallers relaunch from %TEMP% and detach by default; \"_?=<dir>\"\n # runs in place so this stays synchronous. Must be the last argument.\n $installDir = Split-Path -Parent $uninstallCommand\n $uninstallArgs = \"/S _?=$installDir\"\n\n Write-Host \"Uninstall command: $uninstallCommand\"\n Write-Host \"Uninstall args: $uninstallArgs\"\n\n $process = Start-Process -FilePath $uninstallCommand -ArgumentList $uninstallArgs -PassThru\n # Keeps .ExitCode readable after the process ends.\n $null = $process.Handle\n\n if (-not $process.WaitForExit($timeoutSeconds * 1000)) {\n Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue\n Write-Host \"Uninstall timed out after $timeoutSeconds seconds\"\n Exit 1603\n }\n\n $exitCode = $process.ExitCode\n Write-Host \"Uninstall exit code: $exitCode\"\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n\n# Stop anything restarted, then wait for the ARP entry to clear.\nforeach ($daemon in $daemons) {\n Stop-Process -Name $daemon -Force -ErrorAction SilentlyContinue\n}\n\n$elapsed = 0\nwhile ((Get-GnuPGUninstallKey) -and ($elapsed -lt 120)) {\n Start-Sleep -Seconds 5\n $elapsed += 5\n Write-Host \"Waiting for the uninstall to finish... ($elapsed seconds)\"\n}\n\nif (Get-GnuPGUninstallKey) {\n Write-Host \"'$softwareName' is still registered after the uninstall.\"\n Exit 1\n}\n\nExit $exitCode\n"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import * as React from "react";
|
||||
|
||||
import type { SVGProps } from "react";
|
||||
|
||||
const Gnupg = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={32} height={32} {...props}>
|
||||
<image
|
||||
width={32}
|
||||
height={32}
|
||||
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAARGVYSWZNTQAqAAAACAABh2kABAAAAAEAAAAaAAAAAAADoAEAAwAAAAEAAQAAoAIABAAAAAEAAAAwoAMABAAAAAEAAAAwAAAAANs3bAwAAAT5SURBVGgFzVm/a1RBEN4LqSw8EYsUIsIZC7EUEe0sJF2sLFLYSyBF/gHxH0gh5B+wSGFnd1jYKUEsrYwBC4srRDxFK+H5vr37HvPmzezb9+5OXXjZvd3ZmW9mZ2Z/ZBCWVI7efite/x6G0+8hfieT0wbnzY1RuHcxhNHZEPavDwYNgh4dCzE5eF8ULz+HMP7QBAssAAywLFBOKobxJ5enYefmud44ek3cfVMUh++aoLeujsLD8/mAtsZFQeV3b4zC4e3uq9JdgacfC1qU9dGtCwtZURqkqyLZCkhrEXibMACDi8kCl8KHWNi+VLf45vOigIvBtU4e1MckD9nOU8Cweti70piLQH78aVjzc7gVQW/8mlayJ2eGsY0xqUjNUIaMisG80QChCYICb1lHA29bmYYM1dFFibQCGeApzFJM4er0k+4UJyVWwldAgbcYVUISAjqh1sQSgyNjTc+Jv+VEEigGKwdfykX8VMXCVA42FIBLVJPYUOBBEzck1U9yWSMTgR4f2nKsrT3eqmciGE3PqbkQdtb9V80NSmYcBOzO8Zcg+zRTmdf1GH/nBnq10pyojFZTQGcczMEySktEhqkc7Sw15cs6J/BNYwglKhfylleCh/WxAbmlA3jwiJuW4RaSv3W8kG5eKWCdbSQjtHHatBhGuo7gyVse7tjXVvP8BLpKAWsSllgWD7y3enJuqi0tmqKTY5QZFbCiG8TY5nNKzuql+EiLpujkGM9YUYE+yyiZ/Ys2MSdd6F8A0zLpKrofv5H211IE1qRV9OlYkzJwi/MK3GgtRZAa85j26U/FWio+gC+pAP2sD6guc1IKtPFZ+1sgU0D6KgDs/0UQe08sOfG5nrLMKsdk4J44gpjrneHY3aoArODtwJIxAElXYNub64EGT+zMCN4c9x40jqtzVLyMewAk+GW0cVB89nXYeCSLp+HEw9k6LHUyqUPQR+j66PJ+SdDxjhHKe4YoL+6Pws/JNIxFn25GBXTnqvI/AONEC9+Ge1iggQXuiHeh7b0QZoFcV4x4YfzZhcY6CotLAyd0qQkWxsCX48/gf3B3VHv49VwctHgRbA1iELYVPvJKoJ51PV502/2SoHKt8k6cUhyPwlGBuGTGc7gnjP20jnmPJlFLzbsx/BzA8bK3c1zey/fSL9YRczlndpw27riwqiU7+iRcrvxS1rHmsg/WhqvgYQBZDrJgjNmqlVRz94VCnKNrXm1dF7ICGUL6XF64RyDouD+Av15BrgbBIq0GlZk4xvReKRAnizd/qUDczMqxvhaHUPDzTpb0/0Oim9dJ+jmN+6zCVMZdUfFeyk8Cd5lZ2RHEIkPWDnNYBRZYexXgAToCKEHwyQYr7MUc8bCWGNFXX4Gyg37JCYvWAAy/p8+SH0BzQ9O5HzSW8egV5IG6oUAc9JZOznTaBMxgBRn8n5+MIx20kqVpSOE6pLUVwOgCSpC5VcOKj65l/JtVyzfAg7+vAEY1E/T1KY5wsoL/c4XMzJOYn1YAEjooofO9ddOC7xOsdCcq06gT4EHbrgCoHCUAGDsivsrnf5T+Xu6fAIkgRZ0FFHJ0aQEP8jwFSkIZVAR+Z30a8N/GhYEq4OC/3H+zzgVwR1bylvozlZksQdkrICdbOVqO92m37soO014KkJfcjNjXpaYr6k2uC4+FFJCCkAoZC+jXgQuwKAh2BL2VoSJBxz9/AB0sxIHGJDU8AAAAAElFTkSuQmCC"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default Gnupg;
|
||||
@@ -440,6 +440,7 @@ import Gitify from "./Gitify";
|
||||
import GitKraken from "./GitKraken";
|
||||
import GitupApp from "./GitupApp";
|
||||
import Glyphs from "./Glyphs";
|
||||
import Gnupg from "./Gnupg";
|
||||
import Go from "./Go";
|
||||
import Go2Shell from "./Go2Shell";
|
||||
import GoanywhereOpenpgpStudio from "./GoanywhereOpenpgpStudio";
|
||||
@@ -1585,6 +1586,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = {
|
||||
gitkraken: GitKraken,
|
||||
gitup: GitupApp,
|
||||
glyphs: Glyphs,
|
||||
"gnu privacy guard": Gnupg,
|
||||
go: Go,
|
||||
go2shell: Go2Shell,
|
||||
"goanywhere openpgp studio": GoanywhereOpenpgpStudio,
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Reference in New Issue
Block a user