Skip to content

Extract CatHub into the standalone treitforge/cathub project #540

Description

@rtreit

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:

  1. Existing QsoRipper unified files containing [cat_hub] continue to work without changes.
  2. Existing explicit standalone config/cathub.toml files continue to parse.
  3. 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.
  4. Provide an explicit migration command that can extract [cat_hub] into a standalone bare CatHub file.
  5. Refuse to overwrite an existing destination without explicit confirmation.
  6. Preserve the original unified file and create a backup before any requested removal of the embedded section.
  7. Do not automatically split a user's configuration during install or upgrade.
  8. 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:

  1. 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.
  2. 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:

  1. a configured CatHub executable path
  2. a CatHub binary bundled with a QsoRipper distribution
  3. cathub found on PATH
  4. 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

  • A fresh clone of treitforge/cathub builds and tests without a QsoRipper checkout.
  • CatHub contains no source dependency on QsoRipper logging or engine crates.
  • CatHub can run independently using its own default TOML path.
  • CatHub continues to accept an explicitly supplied QsoRipper unified file containing [cat_hub].
  • Existing unified QsoRipper configuration continues to work without an automatic migration.
  • A safe command can explicitly extract [cat_hub] into a standalone CatHub file.
  • QsoRipper can use an externally managed CatHub by endpoint only.
  • QsoRipper can optionally launch a configured, bundled, or PATH-resolved CatHub release.
  • QsoRipper logging remains fully usable when CatHub is absent or offline.
  • Rust and .NET QsoRipper engines consume the same pinned CatHub protocol version.
  • Existing deployed WinKeyer broker clients have a documented compatibility path.
  • CatHub owns its public config schema and semantic validation.
  • QsoRipper no longer compiles or publishes the CatHub daemon from its Rust workspace.
  • Normal QsoRipper builds require neither network access nor a sibling CatHub checkout.
  • CatHub's standalone CI and QsoRipper's integration CI are green on their supported platforms.
  • Documentation clearly distinguishes CatHub-managed configuration, QsoRipper-managed unified configuration, and external endpoint-only integration.
  • Shutdown, crash, timeout, and device-disconnect paths continue to leave CAT PTT and WinKeyer PTT unkeyed.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions