Skip to content

ci(windows): build and bundle qtkeychain for SmartLink credential persistence. Principle XI.#3634

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
NF0T:feat/windows-qtkeychain
Jun 19, 2026
Merged

ci(windows): build and bundle qtkeychain for SmartLink credential persistence. Principle XI.#3634
ten9876 merged 1 commit into
aethersdr:mainfrom
NF0T:feat/windows-qtkeychain

Conversation

@NF0T

@NF0T NF0T commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Closes #3600

Problem

SmartLink credential persistence — saving the Auth0 refresh token to the OS credential store so users aren't prompted for their password on every launch — is fully implemented in application code behind #ifdef HAVE_KEYCHAIN. HAVE_KEYCHAIN is defined only when CMake finds Qt6Keychain at configure time.

On Windows CI, find_package(Qt6Keychain QUIET) has always returned not-found: nothing in the Windows build pipeline installs or builds qtkeychain. The feature silently compiled out of every Windows release artifact (installer, portable ZIP, MSIX), making credential persistence a no-op for all Windows users.

macOS Apple Silicon already has it via brew install qtkeychain. Linux AppImage and macOS Intel are a separate gap tracked in a follow-on issue.

Root cause

# windows-installer.yml — before this PR
modules: 'qtmultimedia qtserialport qtwebsockets qtshadertools'
# qtkeychain: not installed, not built

find_package(Qt6Keychain QUIET) fails silently → HAVE_KEYCHAIN undefined → saveCredentials() and tryAutoLogin() in SmartLinkClient.cpp compile as empty no-ops.

Implementation

Follows the established pattern used by hidapi, FFTW3, and DeepFilterNet3: a setup script downloads and builds from source, CI caches the result, CMake finds it via a staged third_party/ directory.

scripts/setup/setup-qtkeychain.ps1 (new)

  • Downloads qtkeychain 0.16.0 from GitHub and builds with CMake + Ninja + MSVC against the Qt installation at QT_ROOT_DIR (set by install-qt-action) so the qtkeychain ABI matches the application's Qt exactly
  • Passes -DBUILD_TRANSLATIONS=OFF to avoid a Qt6LinguistTools dependency on unused locale files
  • Uses cmake --install to stage the full package layout — headers, import lib, DLL, and cmake config files — to third_party/qtkeychain/; the cmake config files are what find_package(Qt6Keychain) actually resolves against

CMakeLists.txt

  • On WIN32, prepends ${CMAKE_SOURCE_DIR}/third_party/qtkeychain to CMAKE_PREFIX_PATH immediately before the existing find_package(Qt6Keychain QUIET) call
  • Linux and macOS untouched — they continue to find the system or brew package

.github/workflows/windows-installer.yml

  • Cache step for third_party/qtkeychain, keyed on the script hash (invalidates on version bump)
  • Setup step placed after Install Qt6 and before Configure
  • qt6keychain.dll pre-staged to the Qt bin directory before windeployqt runs — windeployqt treats any Qt6* DLL as an official Qt module and looks for it in the Qt installation to scan transitive dependencies; without this it aborts with exit code 1
  • qt6keychain.dll copy in the deploy block covers all three artifacts (portable ZIP, Inno installer, MSIX)

No application code changes

The credential save/load paths in SmartLinkClient.cpp are already correct and already work on macOS Apple Silicon. This PR only makes the Windows build pipeline supply the dependency those paths require.

Testing

Validated end-to-end via workflow_dispatch on NF0T/AetherSDR (run 27658397042):

  • Setup qtkeychain step completed — qtkeychain 0.16.0 built against Qt 6.8.3 MSVC 2022
  • CMake configure output: Qt6Keychain found — SmartLink credential persistence enabled
  • Full build succeeded with HAVE_KEYCHAIN defined
  • Deploy step completed — qt6keychain.dll present in artifact

Principle XI (Fixes Are Demonstrated).

🤖 Generated with Claude Code

@NF0T NF0T requested review from a team as code owners June 17, 2026 02:41

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, @NF0T — clean, well-scoped, and it correctly closes a real silent-no-op gap on Windows. The implementation faithfully follows the hidapi/FFTW3/DeepFilterNet3 setup-script pattern, the find_package PREPEND is properly WIN32-guarded so Linux/macOS are untouched, error handling in the PowerShell script is solid ($ErrorActionPreference = "Stop" plus per-step $LASTEXITCODE checks), and the windeployqt pre-stage workaround is both correct and well-commented. All six checks (build, check-windows, check-macos, CodeQL, analyze) are green, and the deploy-block copies are Test-Path-guarded so they also work on a cache hit. No application code changed, as advertised.

One substantive, non-blocking note:

Cache key doesn't capture the Qt version (windows-installer.yml:46-49). The key is qtkeychain-${{ runner.os }}-${{ hashFiles('scripts/setup/setup-qtkeychain.ps1') }} — identical to the opus/fftw/hidapi/deepfilter pattern, so this matches convention. But qtkeychain is the one dependency here that links against Qt and whose ABI must match the app's Qt exactly (the PR body calls this out). The workflow pins Qt at 6.8.* (line 24), a floating minor. A future bump of that line to e.g. 6.9.* would not invalidate this cache, so CI could silently reuse a qtkeychain built against the old Qt minor against a newer Qt — exactly the ABI mismatch the build-from-source approach exists to avoid. (Within 6.8.x patch bumps this is ABI-stable, so the practical risk is low and only bites on a minor bump.)

Cheap hardening: fold the Qt version into the key, e.g. key: qtkeychain-${{ runner.os }}-6.8-${{ hashFiles('scripts/setup/setup-qtkeychain.ps1') }}, or hash the workflow file alongside the script. Your call whether it's worth it now vs. a comment for whoever next bumps Qt.

Minor / optional: the tarball download has no SHA-256 verification — consistent with the other setup scripts, so not a regression, just noting it as future defense-in-depth for the third-party fetch.

Nothing here blocks merge.


🤖 aethersdr-agent · cost: $5.2686 · model: claude-opus-4-8

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the full diff against the existing windows-installer.yml and the established hidapi/FFTW3/DeepFilterNet3 dependency pattern. This is clean, well-scoped, and the explanation matches what the code actually does. Nice work, @NF0T — thanks for closing the silent Windows credential-persistence gap.

What checks out

  • Scope is tight: the three files are exactly the build pipeline, no application code, consistent with the "SmartLinkClient.cpp is already correct" claim.
  • The setup script mirrors the other setup-*.ps1 scripts: $ErrorActionPreference = "Stop", per-step $LASTEXITCODE guards on tar/configure/build/install, an idempotency guard on Qt6KeychainConfig.cmake, and absolute-path normalization. Error handling at the download/extract/build boundaries is solid.
  • The cache step matches the sibling deps (path + runner.os + script hash), and the cmake --install staging produces the layout find_package(Qt6Keychain QUIET) resolves against — with the list(PREPEND CMAKE_PREFIX_PATH ...) correctly WIN32-gated so Linux/macOS are untouched.
  • The DLL is copied into the deploy/ dir inside the "Deploy Qt" step, so it's captured by all three downstream artifacts (portable ZIP via Compress-Archive deploy\*, Inno, and MSIX -DeployDir deploy) — the PR claim is accurate.

One thing worth a note (non-blocking)

The cache key (qtkeychain-${{ runner.os }}-${{ hashFiles('setup-qtkeychain.ps1') }}) is copied verbatim from the other deps, but qtkeychain differs from them in one way: Opus/FFTW3/hidapi/DeepFilterNet3 are Qt-independent C libraries, whereas qt6keychain.dll links Qt6Core and was built against whatever Qt install-qt-action resolved 6.8.* to in the run that populated the cache. On a cache hit in a later run with a newer 6.8 patch, the stale-built DLL is reused against newer Qt.

In practice this is low-risk — Qt 6 guarantees forward binary compatibility across the 6.x series and qtkeychain only touches Qt6Core, and the version is floated forward (never pinned back), so the cached build is always equal-or-older than the runtime Qt. So no action strictly required. If you want belt-and-suspenders, you could fold the resolved Qt version into the key (it's the one dep where the Qt ABI is actually a cache input), but it's fine to leave as-is to stay consistent with the other entries.

The workflow_dispatch validation on your fork (qtkeychain built against 6.8.3, HAVE_KEYCHAIN defined, DLL in artifact) is exactly the end-to-end evidence this needs. LGTM.


🤖 aethersdr-agent · cost: $2.3721 · model: claude-opus-4-8

@jensenpat jensenpat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Downloads Qtkeychain from upstream github and builds during CI run. Thanks Ryan

…sistence. Principle XI.

Add qtkeychain 0.16.0 to the Windows release pipeline so HAVE_KEYCHAIN is
defined and SmartLink credential persistence (Auth0 refresh token in Windows
Credential Manager) is active in all shipped Windows artifacts.

- scripts/setup/setup-qtkeychain.ps1: download, build, and cmake-install
  qtkeychain against QT_ROOT_DIR; stages cmake config files, import lib,
  and DLL to third_party/qtkeychain/ for find_package resolution
- CMakeLists.txt: prepend third_party/qtkeychain to CMAKE_PREFIX_PATH on
  WIN32 before find_package(Qt6Keychain QUIET)
- windows-installer.yml: cache + setup steps (after Qt install, before
  Configure); qt6keychain.dll pre-staged to Qt bin dir before windeployqt
  (windeployqt looks for Qt6* DLLs in the Qt installation to resolve
  transitive deps); explicit qt6keychain.dll copy in deploy block

Validated via workflow_dispatch on NF0T/AetherSDR (run 27658397042):
Qt6Keychain found in configure output, full build succeeded, DLL present
in artifact.

Closes aethersdr#3600

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@NF0T NF0T force-pushed the feat/windows-qtkeychain branch from 335f92e to 6c598a8 Compare June 17, 2026 16:59

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — clean, well-scoped, demonstrated CI/build fix that activates SmartLink credential persistence on Windows release artifacts (no app code).

Verified:

  • Setup script builds qtkeychain 0.16.0 against QT_ROOT_DIR (ABI matches the app's Qt), is idempotent, has $ErrorActionPreference=Stop + per-step $LASTEXITCODE checks, and cmake --installs the find_package-resolvable layout. Follows the hidapi/FFTW3/DFN pattern.
  • The WIN32 CMAKE_PREFIX_PATH prepend is correctly scoped (Linux/macOS untouched) and harmless where qtkeychain isn't staged — which is why the regular check-windows CI still passes (no regression).
  • Workflow wiring is right: cache keyed on the script hash, setup after Install-Qt/before Configure, and the qt6keychain.dll→Qt-bin pre-stage with a clear comment on the real windeployqt quirk; DLL copied into the deploy block (ZIP/installer/MSIX).
  • Demonstrated end-to-end on the fork (Principle XI): built against Qt 6.8.3, HAVE_KEYCHAIN defined, DLL deployed.

CI green across all six, already core-dev-approved. One non-blocking note: the download isn't checksum-verified — consistent with the existing setup scripts, so I'll file a follow-up to pin SHA256s across all of them rather than gate this. Nice work @NF0T.

@ten9876 ten9876 merged commit dc30591 into aethersdr:main Jun 19, 2026
6 checks passed
ten9876 added a commit that referenced this pull request Jun 19, 2026
…rsistence (#3639)

Linux AppImage counterpart to the Windows fix (#3634): the AppImage shipped
without Qt6Keychain, so SmartLink/MQTT credential persistence silently compiled
out (no Auth0 refresh token stored, no log line explaining why).

- scripts/setup/setup-qtkeychain.sh — builds qtkeychain 0.16.0 from source
  against the active Qt (ABI match with the aqt Qt the AppImage links).
  LIBSECRET_SUPPORT=OFF selects the pure Qt-DBus Secret Service backend
  (KDE Wallet / GNOME Keyring), so only Qt6 DBus is bundled — no extra native deps.
- .github/workflows/appimage.yml — setup before configure, -DREQUIRE_KEYCHAIN=ON,
  and stage the lib dir on LD_LIBRARY_PATH so linuxdeploy bundles libqt6keychain.so.
- CMakeLists.txt — new REQUIRE_KEYCHAIN option (mirrors REQUIRE_SERIALPORT): a
  missing Qt6Keychain becomes a hard build failure for release/packaged builds
  instead of a silent compile-out. The EXISTS-guarded third_party/qtkeychain
  prepend supersedes #3634's WIN32-only one (covers both platforms).
- windows-installer.yml — pass -DREQUIRE_KEYCHAIN=ON too, now that the guard
  exists and #3634 stages qtkeychain there (its own follow-up note).
- SmartLinkClient.cpp — log credential-persistence availability at construction
  (qCWarning under aether.smartlink when disabled) so the compiled-out case is
  diagnosable instead of silent.

Rebased onto main after #3634 merged; CMakeLists conflict resolved by collapsing
the two prepends into the general EXISTS-guarded form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ten9876 added a commit that referenced this pull request Jun 19, 2026
…rsistence (#3639) (#3640)

## Problem

The Linux AppImage is built **without Qt6Keychain**, so `HAVE_KEYCHAIN`
is undefined and SmartLink (and MQTT) credential persistence silently
compiles out. The Auth0 refresh token is never stored, the email is
remembered (it lives in `AppSettings`, not the keychain) but the
password/login is not, and there's **no log line** explaining why.

The reporter confirmed a from-source build on the same Manjaro/KDE box
stores and retrieves the credential through KDE Wallet — so the system
wallet is fine. The gap is purely AppImage packaging. Fixes #3639.

This is the Linux counterpart to the Windows packaging fix in #3634.

## Changes

- **`scripts/setup/setup-qtkeychain.sh`** (new) — builds qtkeychain
0.16.0 from source against the active Qt and stages it in
`third_party/qtkeychain`. Building from source (vs `apt install
qtkeychain-qt6-dev`) guarantees an ABI match with the aqt Qt 6.8.3 the
AppImage links. `LIBSECRET_SUPPORT=OFF` selects qtkeychain's **pure
Qt-D-Bus Secret Service backend**, which talks to **KDE Wallet
(kwalletd)** and GNOME Keyring over the session bus with no extra native
runtime deps to bundle — only Qt6 DBus.
- **`.github/workflows/appimage.yml`** — runs the setup before
configure, passes `-DREQUIRE_KEYCHAIN=ON`, and adds the staged lib dir
to `LD_LIBRARY_PATH` so `linuxdeploy` resolves and bundles
`libqt6keychain.so` (Qt6 DBus comes in transitively).
- **`CMakeLists.txt`** — adds `REQUIRE_KEYCHAIN` (mirrors the existing
`REQUIRE_SERIALPORT` guard) so a missing keychain is a **hard build
failure** for release/packaged builds instead of a silent compile-out;
prepends the `third_party/qtkeychain` staging dir to
`CMAKE_PREFIX_PATH`.
- **`src/core/SmartLinkClient.cpp`** — logs credential-persistence
availability at construction (a `qCWarning` under `aether.smartlink` /
**Help → Support → SmartLink** when disabled), so the compiled-out case
is diagnosable instead of failing silently.

## Validation note

⚠️ The AppImage workflow triggers only on `tags: ['v*']` and
`workflow_dispatch` — it will **not** run automatically on this PR. A
maintainer should dispatch it on `ci/appimage-qtkeychain` (Actions →
AppImage → Run workflow) and confirm:
- the build log prints `Qt6Keychain found — SmartLink credential
persistence enabled`
- `-DREQUIRE_KEYCHAIN=ON` would now fail the build if keychain were
missing
- the produced AppImage contains `libqt6keychain.so`
(`--appimage-extract` then `find squashfs-root -iname '*keychain*'`)
- on a KDE box, login → restart → auto-login works and KDE Wallet shows
an AetherSDR entry

## Follow-up (out of scope)

Windows (#3634) could also pass `-DREQUIRE_KEYCHAIN=ON` now that the
guard exists, to get the same release-time protection.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@NF0T NF0T deleted the feat/windows-qtkeychain branch June 20, 2026 01:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Option to save SmartLink password to avoid re-entering it on every connection

3 participants