Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ body:
id: version
attributes:
label: Application version
placeholder: "0.34.0"
placeholder: "0.35.0"
validations:
required: true
- type: dropdown
Expand Down
67 changes: 19 additions & 48 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Signed Windows release
name: Windows release

on:
push:
Expand All @@ -7,6 +7,8 @@ on:

permissions:
contents: write
id-token: write
attestations: write

jobs:
release:
Expand Down Expand Up @@ -44,70 +46,31 @@ jobs:
- name: Build application
run: pyinstaller --noconfirm AntennaPatternLab.spec

- name: Import code-signing certificate
id: certificate
shell: pwsh
env:
CERTIFICATE_BASE64: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD }}
run: |
if (-not $env:CERTIFICATE_BASE64 -or -not $env:CERTIFICATE_PASSWORD) {
throw "Code-signing secrets are not configured."
}
$pfx = Join-Path $env:RUNNER_TEMP "code-signing.pfx"
[IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:CERTIFICATE_BASE64))
$password = ConvertTo-SecureString $env:CERTIFICATE_PASSWORD -AsPlainText -Force
$certificate = Import-PfxCertificate -FilePath $pfx -CertStoreLocation Cert:\CurrentUser\My -Password $password |
Where-Object HasPrivateKey |
Select-Object -First 1
if (-not $certificate.Thumbprint) { throw "Certificate import failed." }
"thumbprint=$($certificate.Thumbprint)" >> $env:GITHUB_OUTPUT
Remove-Item -LiteralPath $pfx -Force

- name: Locate signing and installer tools
- name: Install Inno Setup
id: tools
shell: pwsh
run: |
choco install innosetup --no-progress -y
$signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" |
Sort-Object FullName -Descending |
Select-Object -First 1
$iscc = Get-Item "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe"
if (-not $signtool -or -not $iscc) { throw "Required build tools were not found." }
"signtool=$($signtool.FullName)" >> $env:GITHUB_OUTPUT
if (-not $iscc) { throw "Inno Setup was not found." }
"iscc=$($iscc.FullName)" >> $env:GITHUB_OUTPUT

- name: Sign application
shell: pwsh
run: |
& "${{ steps.tools.outputs.signtool }}" sign `
/fd SHA256 `
/sha1 "${{ steps.certificate.outputs.thumbprint }}" `
/td SHA256 `
/tr http://timestamp.digicert.com `
"dist\AntennaPatternLab\AntennaPatternLab.exe"
if ($LASTEXITCODE -ne 0) { throw "Application signing failed." }

- name: Build signed installer and update manifest
- name: Build installer and update manifest
shell: pwsh
run: |
.\build_installer.ps1 `
-Compiler "${{ steps.tools.outputs.iscc }}" `
-SignTool "${{ steps.tools.outputs.signtool }}" `
-CertificateThumbprint "${{ steps.certificate.outputs.thumbprint }}" `
-ReleaseBaseUrl "https://github.com/bubakbubak500/AntennaPatternLab/releases/latest/download"

- name: Verify signatures and package application
- name: Package application and publish checksums
shell: pwsh
run: |
$project = Get-Content pyproject.toml -Raw
$version = [regex]::Match($project, '(?m)^version\s*=\s*"([^"]+)"').Groups[1].Value
$installer = "release\AntennaPatternLab-$version-setup-win-x64.exe"
foreach ($file in @("dist\AntennaPatternLab\AntennaPatternLab.exe", $installer)) {
$signature = Get-AuthenticodeSignature -FilePath $file
if ($signature.Status -ne "Valid") {
throw "Invalid Authenticode signature for $file`: $($signature.Status)"
}
$signature = Get-AuthenticodeSignature -FilePath $installer
if ($signature.Status -ne "NotSigned") {
throw "Unexpected installer signing state: $($signature.Status)"
}
Compress-Archive -Path "dist\AntennaPatternLab\*" `
-DestinationPath "release\AntennaPatternLab-$version-win-x64.zip"
Expand All @@ -116,6 +79,13 @@ jobs:
ForEach-Object { "$($_.Hash.ToLowerInvariant()) $([IO.Path]::GetFileName($_.Path))" } |
Set-Content release\SHA256SUMS.txt -Encoding ascii

- name: Attest release build provenance
uses: actions/attest@v4
with:
subject-path: |
release/AntennaPatternLab-*-setup-win-x64.exe
release/AntennaPatternLab-*-win-x64.zip

- name: Publish GitHub Release
shell: pwsh
env:
Expand All @@ -129,5 +99,6 @@ jobs:
"release\release-manifest.json" `
"release\SHA256SUMS.txt" `
--title "Antenna Pattern Lab $version" `
--generate-notes `
--notes-file "docs\RELEASE_NOTES_0.35.0.md" `
--latest `
--verify-tag
26 changes: 17 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ without requiring a server.

## Download

Download the current signed Windows installer from
Download the current Windows installer from
[GitHub Releases](https://github.com/bubakbubak500/AntennaPatternLab/releases/latest).

The application can check the same official GitHub release channel for updates.
Automatic checks are opt-in. Every downloaded installer is accepted only after its
SHA-256 digest matches the published release manifest, and it is never launched
without an additional confirmation.
It checks in the background on every startup and fails silently when offline.
Every downloaded installer is accepted only after its SHA-256 digest matches the
published release manifest, and it is never launched without an additional
confirmation.

> [!WARNING]
> Current Windows packages are not Authenticode-signed. Windows can therefore
> display **Unknown publisher** or a Microsoft Defender SmartScreen warning.
> Download only from this repository's Releases page. Each release includes
> SHA-256 checksums and GitHub build-provenance attestations.

## Main features

Expand All @@ -38,13 +45,14 @@ without an additional confirmation.
- local SQLite storage and diagnostic export;
- English and Czech application UI;
- verified, consent-driven setup assistance for WSJT-X and Hamlib;
- signed Windows application and installer releases.
- reproducible Windows releases with checksums and GitHub build provenance.

## Quick start

1. Download the latest installer from
[Releases](https://github.com/bubakbubak500/AntennaPatternLab/releases/latest).
2. Verify that Windows reports the expected publisher before installing.
2. Compare the installer's SHA-256 with `SHA256SUMS.txt`; Windows currently
reports an unknown publisher because the release is unsigned.
3. Start Antenna Pattern Lab and complete the first-run assistant.
4. Use **Help → Add demo data** to explore the application without a radio.
5. For live collection, enter your callsign and locator, select the band and mode,
Expand Down Expand Up @@ -99,9 +107,9 @@ Build an unsigned local installer:
.\build_installer.ps1
```

Official releases are built and Authenticode-signed by GitHub Actions. Signing
credentials are stored only as encrypted GitHub Actions secrets and are never
committed to this repository. See [Release process](docs/RELEASING.md).
Official releases are built by GitHub Actions, accompanied by SHA-256 checksums
and a GitHub artifact attestation. Authenticode signing can be added later
without changing the update channel. See [Release process](docs/RELEASING.md).

## Contributing

Expand Down
8 changes: 5 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ mitigation. You should receive an initial response within seven days.
## Release integrity

Official Windows releases are published only through GitHub Releases. The
application and installer are Authenticode-signed, release checksums are
published, and the in-app updater verifies the installer's SHA-256 before making
it available to launch.
current application and installer are not Authenticode-signed. Release checksums
and GitHub build-provenance attestations are published, and the in-app updater
verifies the installer's SHA-256 before making it available to launch. Windows
may show an unknown-publisher warning until a trusted signing certificate is
introduced.
23 changes: 23 additions & 0 deletions docs/RELEASE_NOTES_0.35.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Antenna Pattern Lab 0.35.0

This release establishes the public GitHub update channel and improves Windows
shell integration.

## What changed

- The application now checks the official GitHub Releases channel in the
background on every startup. Offline or temporary GitHub failures are silent.
- The Updates dialog now explains the download, SHA-256 verification, and
installation flow instead of exposing an editable manifest URL.
- The application process, Start menu shortcut, and desktop shortcut now share
the stable Windows AppUserModelID `OK7PS.AntennaPatternLab`.
- The installer deploys the application icon explicitly for Windows shortcuts.
- Release assets include `SHA256SUMS.txt`, an update manifest, and a GitHub
build-provenance attestation.

## Windows signing notice

The application and installer in this release are **not Authenticode-signed**.
Windows can display an **Unknown publisher** or Microsoft Defender SmartScreen
warning. Download only from the official AntennaPatternLab GitHub Releases page
and verify the SHA-256 checksum supplied with the release.
25 changes: 10 additions & 15 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,25 @@

Official releases are created by `.github/workflows/release.yml`.

## Required repository secrets

- `WINDOWS_SIGNING_CERTIFICATE_BASE64`: a Base64-encoded, password-protected PFX
code-signing certificate;
- `WINDOWS_SIGNING_CERTIFICATE_PASSWORD`: the PFX password.

The certificate must support Windows code signing and chain to a certificate
authority trusted by supported Windows versions. Never commit the PFX or password.
No release secrets are currently required. Packages are intentionally published
without an Authenticode signature until a trusted code-signing certificate is
available. The release page and application clearly disclose this.

## Create a release

1. Update the version in `pyproject.toml`.
2. Ensure the Inno Setup definition and application package use the same version.
3. Merge the version change through an approved pull request.
4. Create and push a signed or annotated tag named `vMAJOR.MINOR.PATCH`.
4. Create and push a tag named `vMAJOR.MINOR.PATCH`.
5. The release workflow verifies that the tag matches the project version.
6. GitHub Actions runs tests, builds the application, signs the application EXE,
builds and signs the installer and uninstaller, verifies Authenticode, creates
the update manifest and checksums, and publishes a GitHub Release.
6. GitHub Actions runs tests, builds the application and installer, confirms that
the installer is unsigned, creates the update manifest and checksums, records
GitHub build-provenance attestations, and publishes a GitHub Release.

The update manifest is always available at:

`https://github.com/bubakbubak500/AntennaPatternLab/releases/latest/download/release-manifest.json`

If certificate renewal is required, replace the two secrets before creating a
release. Existing releases remain timestamp-valid after the certificate expires,
provided the timestamp was created while the certificate was valid.
When Authenticode signing is added later, keep the same asset names and manifest
URL so installed applications remain on the same update channel. Never commit a
PFX file or its password.
9 changes: 6 additions & 3 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ CSV export can be used for independent analysis or archival.

## 9. Updates

Open **Help → Updates** to check the official GitHub release channel. Automatic
checks at startup are disabled until you opt in.
Open **Settings → Updates** to check the official GitHub release channel. Automatic
checks run in the background on every startup. If the internet or GitHub is
unavailable, startup continues normally and no error is shown.

The update process:

Expand All @@ -110,7 +111,9 @@ The update process:
4. publishes the `.exe` only after the digest matches;
5. asks before launching the verified installer.

Always confirm that Windows displays the expected Authenticode publisher.
Current releases are not Authenticode-signed, so Windows may report an unknown
publisher or show a SmartScreen warning. Download only from the official GitHub
Releases page and compare the installer with the published SHA-256 checksum.

## 10. Diagnostics and data

Expand Down
8 changes: 5 additions & 3 deletions installer/AntennaPatternLab.iss
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#define MyAppName "Antenna Pattern Lab"
#ifndef MyAppVersion
#define MyAppVersion "0.34.0"
#define MyAppVersion "0.35.0"
#endif
#define MyAppPublisher "OK7PS"
#define MyAppExeName "AntennaPatternLab.exe"
Expand Down Expand Up @@ -28,6 +28,7 @@ SolidCompression=yes
WizardStyle=modern dynamic
SetupIconFile=..\src\antenna_pattern_lab\assets\app-icon.ico
UninstallDisplayIcon={app}\{#MyAppExeName}
ChangesAssociations=yes
CloseApplications=yes
RestartApplications=no
VersionInfoVersion={#MyAppVersion}.0
Expand Down Expand Up @@ -70,11 +71,12 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{

[Files]
Source: "..\dist\AntennaPatternLab\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\src\antenna_pattern_lab\assets\app-icon.ico"; DestDir: "{app}"; DestName: "AntennaPatternLab.ico"; Flags: ignoreversion

[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\AntennaPatternLab.ico"; AppUserModelID: "OK7PS.AntennaPatternLab"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\AntennaPatternLab.ico"; AppUserModelID: "OK7PS.AntennaPatternLab"; Tasks: desktopicon

[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "antenna-pattern-lab"
version = "0.34.0"
version = "0.35.0"
description = "Empirical antenna coverage analysis from PSK Reporter FT8 spots"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion src/antenna_pattern_lab/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Antenna Pattern Lab."""

__version__ = "0.34.0"
__version__ = "0.35.0"
14 changes: 13 additions & 1 deletion src/antenna_pattern_lab/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,20 @@ def application_icon_path() -> Path:
return Path(__file__).resolve().parent / "assets" / "app-icon.png"


def set_windows_app_user_model_id() -> None:
"""Give Windows one stable identity for the process and its shortcuts."""
if sys.platform != "win32":
return
import ctypes

ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
"OK7PS.AntennaPatternLab"
)


def main() -> int:
logging.basicConfig(level=logging.INFO)
set_windows_app_user_model_id()
application = QApplication(sys.argv)
application.setApplicationName("Antenna Pattern Lab")
application.setOrganizationName("OK7PS")
Expand All @@ -43,7 +55,7 @@ def main() -> int:
window = MainWindow(repository)
window.show()
QTimer.singleShot(0, window.show_setup_if_needed)
QTimer.singleShot(1500, window.check_updates_if_enabled)
QTimer.singleShot(1500, window.check_updates_at_startup)
return application.exec()


Expand Down
13 changes: 3 additions & 10 deletions src/antenna_pattern_lab/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,7 @@ def __init__(self, repository: SpotRepository, settings: QSettings | None = None
saved_language = str(self.settings.value("language", "CZE"))
self.language_code = saved_language if saved_language in TRANSLATIONS else "CZE"
self.setWindowTitle("Antenna Pattern Lab · FT8 / WSPR")
self.setWindowIcon(QApplication.instance().windowIcon())
self.resize(1180, 760)
self._build_ui()
self._connect_signals()
Expand Down Expand Up @@ -2403,18 +2404,10 @@ def show_setup_if_needed(self) -> None:
def _open_updates(self) -> None:
UpdateDialog(self.settings, self.language_code, self).exec()

def check_updates_if_enabled(self) -> None:
if not bool(int(self.settings.value("automatic_update_checks", 0))):
return
url = str(
self.settings.value("release_manifest_url", DEFAULT_RELEASE_MANIFEST_URL)
).strip()
if not url:
return

def check_updates_at_startup(self) -> None:
def worker() -> None:
try:
result = check_for_update(url, __version__)
result = check_for_update(DEFAULT_RELEASE_MANIFEST_URL, __version__)
except Exception as exc:
self.bridge.update_failed.emit(str(exc))
else:
Expand Down
Loading
Loading