Summary
Move the CatHub daemon, library, protocol contracts, configuration ownership, tests, documentation, and release pipeline from the QsoRipper source tree into the existing standalone repository at https://github.com/treitforge/cathub.
CatHub is independently useful. Its core responsibility is to let multiple applications safely share radio CAT control, Hamlib NET endpoints, virtual serial endpoints, PTT ownership, and a physical WinKeyer. None of those responsibilities require QsoRipper logging functionality.
QsoRipper should remain a first-class CatHub client and may optionally launch and configure CatHub, but QsoRipper logging must not require CatHub to be installed or running.
Architectural decision
CatHub becomes an independently versioned and released product.
QsoRipper depends on CatHub through stable client interfaces:
- Hamlib NET for normalized rig state and control
- the typed WinKeyer broker gRPC API
- a versioned CatHub protocol dependency for generated Rust and .NET clients
- an optional pinned CatHub release artifact when QsoRipper distributions choose to bundle or launch it
QsoRipper must not depend on the CatHub daemon source crate by path, and CatHub must not depend on QsoRipper engine, logger, UI, storage, ADIF, QRZ, or station-profile code.
The standalone daemon remains optional. If it is unavailable, QsoRipper continues to log locally and reports rig-control or CW integration as unavailable without blocking normal operation.
Current-state audit
The current code is already close to a separable boundary:
src/rust/qsoripper-cathub is its own library and binary crate.
- Its runtime dependencies are third-party workspace dependencies. It does not depend on another QsoRipper Rust crate.
- QsoRipper rig control already consumes CatHub through a standard Hamlib NET endpoint.
- QsoRipper CW keying already consumes CatHub through the typed loopback WinKeyer broker API.
- CatHub already accepts both configuration layouts:
- standalone TOML with top-level
[radio], [winkeyer], [[serial_endpoint]], [[hamlib_net]], and [[winkeyer_endpoint]]
- a unified QsoRipper TOML document with the same settings nested under
[cat_hub]
config/cathub.toml is already a complete standalone example.
The remaining coupling is ownership and packaging rather than core runtime logic:
- CatHub inherits version, repository metadata, dependencies, and lints from the QsoRipper Cargo workspace.
- CatHub's
build.rs reads WinKeyer broker protobuf files from QsoRipper's root proto/services directory.
- The broker protobuf package and C# namespace are currently branded as
qsoripper.services and QsoRipper.Services.
- QsoRipper's Rust and .NET engines mirror CatHub's TOML schema and validation.
- QsoRipper setup protobufs, setup services, CLI, and GUI expose full CatHub daemon settings.
- QsoRipper's launcher, build script, publish layout, start/stop/log scripts, and documentation assume the daemon is built from the QsoRipper workspace.
- CatHub's default config path, legacy environment override, log directory, and log filename are QsoRipper-owned names.
- The CatHub architecture and operating documentation currently live primarily in QsoRipper.
- The launcher currently assumes a published
qsoripper-cathub executable and a fixed readiness port.
Target repository structure
The treitforge/cathub repository should own, at minimum:
- the CatHub daemon binary
- the reusable CatHub core/library code
- the authoritative protobuf contracts
- generated or packaged Rust and .NET protocol clients
- the authoritative TOML model, defaults, validation, and migration logic
- configuration examples
- radio, dialect, Hamlib NET, serial endpoint, PTT, and WinKeyer tests
- architecture, setup, troubleshooting, compatibility, and security documentation
- Windows and Linux build, test, packaging, and release automation
- release checksums and version metadata
A small Cargo workspace is appropriate, for example:
cathub: daemon and CLI
cathub-core: radio, endpoint, arbitration, and WinKeyer implementation
cathub-protocol: generated Rust protocol types and clients
The exact internal crate split may be adjusted during extraction, but public protocol and configuration ownership must remain clearly separate from QsoRipper.
Preserve useful source history when importing the existing crate. Use a temporary clone plus git filter-repo, git subtree split, or an equivalent non-destructive history extraction, then merge that history into the existing initialized CatHub repository.
Configuration ownership and compatibility
Standalone default
A standalone CatHub installation owns its own configuration by default:
- Windows:
%APPDATA%\cathub\cathub.toml
- Linux:
$XDG_CONFIG_HOME/cathub/cathub.toml, falling back to $HOME/.config/cathub/cathub.toml
- explicit CLI override:
cathub --config <path>
- environment override:
CATHUB_CONFIG_PATH
The standalone file uses the already-supported bare layout with top-level CatHub tables.
CatHub should also move its default state/log location under the CatHub application directory and use CatHub-owned names. Existing CATHUB_LOG filtering remains supported.
Preserve QsoRipper's unified file
Do not remove the combined-file workflow. It is useful when QsoRipper manages the local station stack.
For a QsoRipper-managed deployment:
- QsoRipper continues to store daemon settings under
[cat_hub] in its unified config.toml.
- The QsoRipper launcher passes that exact path to CatHub explicitly.
- CatHub reads and writes only the
cat_hub subtree and ignores unrelated QsoRipper sections.
- Prefer an explicit section selector such as
--section cat_hub for managed launches, while retaining automatic detection for compatibility.
- QsoRipper setup saves must preserve unknown CatHub keys, comments where practical, and unrelated sections.
- A malformed or newer CatHub section must not prevent the QsoRipper engine from starting.
This keeps the current one-file operator experience without making it CatHub's standalone default.
External CatHub mode
When CatHub is installed and managed independently:
- QsoRipper stores only client integration settings, such as the Hamlib NET host/port and WinKeyer broker endpoint/client name.
- QsoRipper does not silently rewrite CatHub's external TOML file.
- The UI reports the configured endpoint, connection status, detected CatHub version, and whether CatHub is QsoRipper-managed or external.
- Full daemon configuration is opened through a CatHub-owned configuration surface.
Authoritative configuration tooling
CatHub must own configuration parsing and semantic validation. QsoRipper should not retain a second hand-maintained copy of every backend, endpoint, permission, default, and validation rule.
Provide CatHub-owned commands backed by the same configuration library used by the daemon, including:
cathub config validate
cathub config print-effective
cathub config migrate
- a machine-readable output mode for integration and diagnostics
- atomic save behavior with validation before replacement
- a backup before migration or destructive replacement
- clear reporting of settings that require a daemon restart
A CatHub-owned loopback management/status API may wrap the same library for GUI use. If added, it must use unique request/response envelopes, bind safely to loopback by default, and keep mutation explicitly controlled.
QsoRipper's existing CatHub settings UI should either consume this CatHub-owned management surface or become a launcher/integration view that opens the CatHub configuration tool. It must not remain the authoritative implementation of the CatHub schema.
Migration behavior
Migration must be safe and reversible:
- Existing QsoRipper unified files containing
[cat_hub] continue to work without changes.
- Existing explicit standalone
config/cathub.toml files continue to parse.
- The old
QSORIPPER_CONFIG_PATH behavior may remain as a documented compatibility fallback for one transition period, but new standalone documentation and installs use CATHUB_CONFIG_PATH.
- Provide an explicit migration command that can extract
[cat_hub] into a standalone bare CatHub file.
- Refuse to overwrite an existing destination without explicit confirmation.
- Preserve the original unified file and create a backup before any requested removal of the embedded section.
- Do not automatically split a user's configuration during install or upgrade.
- Document how to move in both directions: unified managed mode to standalone mode, and standalone mode to QsoRipper-managed unified mode.
Protocol ownership and versioning
Move the WinKeyer broker service and its request, response, event, status, and enum protobuf files into the CatHub repository. CatHub becomes the single source of truth for this wire contract.
Publish versioned client artifacts:
- a Rust
cathub-protocol crate containing generated client types
- a
CatHub.Protocol NuGet package, or an equivalent versioned package containing generated C# client types and the source proto descriptors
Adopt a CatHub-owned versioned package and C# namespace, such as cathub.v1 and CatHub.Protocol.V1.
Because the existing wire contract uses qsoripper.services, migration must not silently break deployed clients. Use one of these compatible approaches:
- Serve the existing service and the new CatHub-namespaced service side by side for a documented transition release, then remove the legacy service in a later major release.
- Preserve the current wire package in the first standalone release, transfer its ownership to CatHub, and introduce the CatHub namespace only in a separately versioned protocol revision.
The implementation must choose and document one approach before QsoRipper removes its local proto files.
QsoRipper Rust and .NET clients must consume the same pinned protocol release. Run buf lint and buf breaking against the last released CatHub contract.
Configuration DTOs that describe CatHub-specific settings should also become CatHub-owned if they remain part of a public management API. QsoRipper-specific setup envelopes may reference or adapt those types, but must not become their source of truth.
QsoRipper integration after extraction
Rig control
Keep the existing Hamlib NET integration. QsoRipper's Rust and .NET engines connect to a configured CatHub endpoint exactly as they connect to any compatible rigctld endpoint.
Do not add a source dependency on CatHub core for rig state.
CW keying
Replace QsoRipper-local generated WinKeyer broker types with the pinned CatHub protocol package in both engines.
Preserve:
- loopback endpoint validation
- stable client naming
- ownership-scoped cancellation
- event streaming
- speed-pot behavior
- safety and maximum-transmit handling
- graceful failure when CatHub is unavailable
Launcher and distribution
Change the QsoRipper launcher from a workspace artifact assumption to an external component contract.
Resolution order should be explicit and testable:
- a configured CatHub executable path
- a CatHub binary bundled with a QsoRipper distribution
cathub found on PATH
- a clear unavailable state with installation guidance
Do not download or update executables implicitly during ordinary startup.
For release packaging, QsoRipper may bundle a pinned CatHub release. Record the CatHub version and checksum in a dependency manifest and verify the checksum during packaging.
Replace fixed-port readiness assumptions with a CatHub-owned health/status check or readiness derived from the selected configuration. Surface version incompatibility distinctly from process failure.
The launcher must continue to start CatHub before dependent engines and UIs when CatHub is selected, and must stop only the process instance it started.
Build and tests
Remove CatHub source compilation and publication from QsoRipper's normal Rust workspace and build.ps1.
QsoRipper's normal build must not require a sibling CatHub checkout or network access. Optional packaging or end-to-end commands may consume a pinned release artifact explicitly.
Retain QsoRipper-side client and integration tests:
- Rust and .NET WinKeyer broker client conformance
- rig snapshot reads through CatHub's Hamlib NET endpoint
- launcher discovery, version, readiness, startup ordering, and shutdown ownership
- managed unified config behavior
- external config behavior
- missing and incompatible CatHub behavior
- cross-platform path handling
CatHub owns daemon implementation tests, parser tests, endpoint transcript tests, arbitration tests, reconnect tests, safety tests, and hardware soak procedures.
The existing CatHub frequency probes compare CatHub with QsoRipper engine behavior and may remain in QsoRipper as integration diagnostics. Any reusable CatHub-only client code should move to the CatHub repository or consume the published protocol package.
Documentation ownership
Move the authoritative CatHub design documents, generated architecture report, standalone setup instructions, configuration reference, and troubleshooting material to treitforge/cathub.
QsoRipper retains concise integration documentation covering:
- installing or locating CatHub
- managed versus external mode
- pointing QsoRipper rig control and CW keying at CatHub
- the optional unified
[cat_hub] workflow
- supported CatHub protocol/version range
- links to the authoritative CatHub documentation
Update stale qsoripper-cathub binary, log, path, package, and repository references throughout both repositories. Provide compatibility aliases only where they materially ease migration, and document their removal timeline.
Phased implementation plan
Phase 1: Record the boundary
- Add an architecture decision describing CatHub as an independent product and QsoRipper as an optional client.
- Inventory every file to move, retain, split, or delete.
- Define protocol compatibility and naming policy.
- Define managed and external configuration modes.
- Define release/version compatibility policy.
- Create scoped sub-issues in the appropriate repository before partial implementation PRs.
Phase 2: Bootstrap the standalone project
- Import the existing crate history into
treitforge/cathub.
- Create standalone Cargo workspace metadata, lockfile, lints, license metadata, README, contribution guidance, and CI.
- Remove inherited QsoRipper workspace paths and metadata.
- Move the canonical sample configuration and core architecture documents.
- Make
cargo build, cargo test, cargo fmt --check, and cargo clippy --all-targets -- -D warnings pass from a fresh standalone clone on Windows and Linux.
Phase 3: Transfer contracts and configuration ownership
- Move the WinKeyer broker protobuf contract.
- Establish versioned Rust and .NET protocol packages.
- Add compatibility for the existing QsoRipper-namespaced wire service.
- Add
buf lint and buf breaking gates.
- Change standalone defaults to CatHub-owned config, state, and log paths.
- Add CatHub-owned validate, effective-config, and migration commands.
- Test both bare and embedded
[cat_hub] layouts.
- Document atomic save, backup, unknown-field preservation, and restart behavior.
Phase 4: Move and release the daemon
- Move runtime source, tests, fixtures, and CatHub-only scripts.
- Add Windows and Linux packaging.
- Publish an initial standalone prerelease with checksums and protocol artifacts.
- Validate radio CAT, Hamlib NET, serial endpoints, PTT arbitration, WinKeyer brokerage, reconnect, and shutdown safety.
- Document hardware-assisted validation separately from required deterministic CI.
Phase 5: Convert QsoRipper to an external client
- Remove CatHub from the QsoRipper Cargo workspace.
- Replace local broker proto generation in Rust and .NET with the pinned CatHub protocol dependency.
- Remove the QsoRipper server's path dev-dependency on
qsoripper-cathub.
- Replace duplicated CatHub config validation and full-schema ownership with the CatHub-owned integration surface.
- Update GUI, CLI, setup status, and launcher behavior for managed and external modes.
- Update build and packaging to consume an explicit released CatHub artifact only when requested.
- Preserve optional unified configuration by passing QsoRipper's config path and section explicitly.
- Add compatibility and absence tests for both engines and all affected UIs.
Phase 6: Cleanup and stable release
- Remove migrated CatHub runtime, canonical docs, sample, scripts, and broker proto files from QsoRipper.
- Reserve removed QsoRipper protobuf field numbers and names when deprecating setup fields.
- Keep only QsoRipper-specific integration docs and diagnostics.
- Publish a stable CatHub release and declare QsoRipper's supported version range.
- Run QsoRipper's full Rust, .NET, proto, coverage, and cross-engine gates.
- Run CatHub's full standalone gates on Windows and Linux.
- Complete a hardware smoke test with simultaneous QsoRipper, N1MM, and at least one Hamlib NET client.
Acceptance criteria
Non-goals
- Rewriting working CAT, Hamlib NET, dialect, arbitration, or WinKeyer behavior during the repository move.
- Making CatHub mandatory for QsoRipper.
- Removing QsoRipper's direct WinKeyer or ordinary external
rigctld support.
- Removing the useful unified QsoRipper TOML workflow.
- Automatically moving or deleting user configuration.
- Bundling automatic background updates into the first extraction.
Related work
Summary
Move the CatHub daemon, library, protocol contracts, configuration ownership, tests, documentation, and release pipeline from the QsoRipper source tree into the existing standalone repository at https://github.com/treitforge/cathub.
CatHub is independently useful. Its core responsibility is to let multiple applications safely share radio CAT control, Hamlib NET endpoints, virtual serial endpoints, PTT ownership, and a physical WinKeyer. None of those responsibilities require QsoRipper logging functionality.
QsoRipper should remain a first-class CatHub client and may optionally launch and configure CatHub, but QsoRipper logging must not require CatHub to be installed or running.
Architectural decision
CatHub becomes an independently versioned and released product.
QsoRipper depends on CatHub through stable client interfaces:
QsoRipper must not depend on the CatHub daemon source crate by path, and CatHub must not depend on QsoRipper engine, logger, UI, storage, ADIF, QRZ, or station-profile code.
The standalone daemon remains optional. If it is unavailable, QsoRipper continues to log locally and reports rig-control or CW integration as unavailable without blocking normal operation.
Current-state audit
The current code is already close to a separable boundary:
src/rust/qsoripper-cathubis its own library and binary crate.[radio],[winkeyer],[[serial_endpoint]],[[hamlib_net]], and[[winkeyer_endpoint]][cat_hub]config/cathub.tomlis already a complete standalone example.The remaining coupling is ownership and packaging rather than core runtime logic:
build.rsreads WinKeyer broker protobuf files from QsoRipper's rootproto/servicesdirectory.qsoripper.servicesandQsoRipper.Services.qsoripper-cathubexecutable and a fixed readiness port.Target repository structure
The
treitforge/cathubrepository should own, at minimum:A small Cargo workspace is appropriate, for example:
cathub: daemon and CLIcathub-core: radio, endpoint, arbitration, and WinKeyer implementationcathub-protocol: generated Rust protocol types and clientsThe exact internal crate split may be adjusted during extraction, but public protocol and configuration ownership must remain clearly separate from QsoRipper.
Preserve useful source history when importing the existing crate. Use a temporary clone plus
git filter-repo,git subtree split, or an equivalent non-destructive history extraction, then merge that history into the existing initialized CatHub repository.Configuration ownership and compatibility
Standalone default
A standalone CatHub installation owns its own configuration by default:
%APPDATA%\cathub\cathub.toml$XDG_CONFIG_HOME/cathub/cathub.toml, falling back to$HOME/.config/cathub/cathub.tomlcathub --config <path>CATHUB_CONFIG_PATHThe standalone file uses the already-supported bare layout with top-level CatHub tables.
CatHub should also move its default state/log location under the CatHub application directory and use CatHub-owned names. Existing
CATHUB_LOGfiltering remains supported.Preserve QsoRipper's unified file
Do not remove the combined-file workflow. It is useful when QsoRipper manages the local station stack.
For a QsoRipper-managed deployment:
[cat_hub]in its unifiedconfig.toml.cat_hubsubtree and ignores unrelated QsoRipper sections.--section cat_hubfor managed launches, while retaining automatic detection for compatibility.This keeps the current one-file operator experience without making it CatHub's standalone default.
External CatHub mode
When CatHub is installed and managed independently:
Authoritative configuration tooling
CatHub must own configuration parsing and semantic validation. QsoRipper should not retain a second hand-maintained copy of every backend, endpoint, permission, default, and validation rule.
Provide CatHub-owned commands backed by the same configuration library used by the daemon, including:
cathub config validatecathub config print-effectivecathub config migrateA CatHub-owned loopback management/status API may wrap the same library for GUI use. If added, it must use unique request/response envelopes, bind safely to loopback by default, and keep mutation explicitly controlled.
QsoRipper's existing CatHub settings UI should either consume this CatHub-owned management surface or become a launcher/integration view that opens the CatHub configuration tool. It must not remain the authoritative implementation of the CatHub schema.
Migration behavior
Migration must be safe and reversible:
[cat_hub]continue to work without changes.config/cathub.tomlfiles continue to parse.QSORIPPER_CONFIG_PATHbehavior may remain as a documented compatibility fallback for one transition period, but new standalone documentation and installs useCATHUB_CONFIG_PATH.[cat_hub]into a standalone bare CatHub file.Protocol ownership and versioning
Move the WinKeyer broker service and its request, response, event, status, and enum protobuf files into the CatHub repository. CatHub becomes the single source of truth for this wire contract.
Publish versioned client artifacts:
cathub-protocolcrate containing generated client typesCatHub.ProtocolNuGet package, or an equivalent versioned package containing generated C# client types and the source proto descriptorsAdopt a CatHub-owned versioned package and C# namespace, such as
cathub.v1andCatHub.Protocol.V1.Because the existing wire contract uses
qsoripper.services, migration must not silently break deployed clients. Use one of these compatible approaches:The implementation must choose and document one approach before QsoRipper removes its local proto files.
QsoRipper Rust and .NET clients must consume the same pinned protocol release. Run
buf lintandbuf breakingagainst the last released CatHub contract.Configuration DTOs that describe CatHub-specific settings should also become CatHub-owned if they remain part of a public management API. QsoRipper-specific setup envelopes may reference or adapt those types, but must not become their source of truth.
QsoRipper integration after extraction
Rig control
Keep the existing Hamlib NET integration. QsoRipper's Rust and .NET engines connect to a configured CatHub endpoint exactly as they connect to any compatible
rigctldendpoint.Do not add a source dependency on CatHub core for rig state.
CW keying
Replace QsoRipper-local generated WinKeyer broker types with the pinned CatHub protocol package in both engines.
Preserve:
Launcher and distribution
Change the QsoRipper launcher from a workspace artifact assumption to an external component contract.
Resolution order should be explicit and testable:
cathubfound onPATHDo not download or update executables implicitly during ordinary startup.
For release packaging, QsoRipper may bundle a pinned CatHub release. Record the CatHub version and checksum in a dependency manifest and verify the checksum during packaging.
Replace fixed-port readiness assumptions with a CatHub-owned health/status check or readiness derived from the selected configuration. Surface version incompatibility distinctly from process failure.
The launcher must continue to start CatHub before dependent engines and UIs when CatHub is selected, and must stop only the process instance it started.
Build and tests
Remove CatHub source compilation and publication from QsoRipper's normal Rust workspace and
build.ps1.QsoRipper's normal build must not require a sibling CatHub checkout or network access. Optional packaging or end-to-end commands may consume a pinned release artifact explicitly.
Retain QsoRipper-side client and integration tests:
CatHub owns daemon implementation tests, parser tests, endpoint transcript tests, arbitration tests, reconnect tests, safety tests, and hardware soak procedures.
The existing CatHub frequency probes compare CatHub with QsoRipper engine behavior and may remain in QsoRipper as integration diagnostics. Any reusable CatHub-only client code should move to the CatHub repository or consume the published protocol package.
Documentation ownership
Move the authoritative CatHub design documents, generated architecture report, standalone setup instructions, configuration reference, and troubleshooting material to
treitforge/cathub.QsoRipper retains concise integration documentation covering:
[cat_hub]workflowUpdate stale
qsoripper-cathubbinary, log, path, package, and repository references throughout both repositories. Provide compatibility aliases only where they materially ease migration, and document their removal timeline.Phased implementation plan
Phase 1: Record the boundary
Phase 2: Bootstrap the standalone project
treitforge/cathub.cargo build,cargo test,cargo fmt --check, andcargo clippy --all-targets -- -D warningspass from a fresh standalone clone on Windows and Linux.Phase 3: Transfer contracts and configuration ownership
buf lintandbuf breakinggates.[cat_hub]layouts.Phase 4: Move and release the daemon
Phase 5: Convert QsoRipper to an external client
qsoripper-cathub.Phase 6: Cleanup and stable release
Acceptance criteria
treitforge/cathubbuilds and tests without a QsoRipper checkout.[cat_hub].[cat_hub]into a standalone CatHub file.Non-goals
rigctldsupport.Related work