[docs](trx-rs): generate the example config and correct the manual
trx-rs.toml.example was maintained by hand and had fallen well behind: no
[[rigs]], no [[remotes]], no [timeouts], no bandplan or decode-history
settings, and a [frontends.http].default_rig_id that had been renamed.
Generate it from the config structs instead, so a new field shows up the moment
it exists, and add a test that fails when the checked-in copy drifts:
cargo run -p trx-config --example generate_example
Section comments come from a small table; a section without an entry is still
emitted, so forgetting a comment can never drop a setting from the example.
The manual was wrong about the basics. It listed five config search paths, none
of which the loader has ever looked at (the real order is ./trx-rs.toml → XDG →
/etc), called --print-config output "fully commented" when it carries no
comments at all, and documented a TRX_PLUGIN_DIRS variable no code reads. It
also still described [frontends.rigctl].port as the bind port years after
rig_ports replaced it. Fixed, and the new configuration features are written
up alongside.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
Generated
+1
@@ -3141,6 +3141,7 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"toml",
|
"toml",
|
||||||
|
"toml_edit 0.22.27",
|
||||||
"tracing",
|
"tracing",
|
||||||
"trx-core",
|
"trx-core",
|
||||||
"trx-decode-log",
|
"trx-decode-log",
|
||||||
|
|||||||
@@ -93,13 +93,17 @@ The wizard walks you through rig selection, serial port detection, audio
|
|||||||
settings, and frontend options, then writes `trx-server.toml` and
|
settings, and frontend options, then writes `trx-server.toml` and
|
||||||
`trx-client.toml`.
|
`trx-client.toml`.
|
||||||
|
|
||||||
Alternatively, generate example configs and edit them by hand:
|
Alternatively, copy `trx-rs.toml.example` — a commented example covering every
|
||||||
|
setting — and edit it by hand:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./target/release/trx-server --print-config > trx-server.toml
|
cp trx-rs.toml.example trx-rs.toml
|
||||||
./target/release/trx-client --print-config > trx-client.toml
|
./target/release/trx-server --check-config --config trx-rs.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`--check-config` reports everything wrong with a config without starting
|
||||||
|
anything. `--print-config` prints the same settings without comments.
|
||||||
|
|
||||||
### 4. Run
|
### 4. Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -107,6 +111,9 @@ Alternatively, generate example configs and edit them by hand:
|
|||||||
./target/release/trx-client --config trx-client.toml
|
./target/release/trx-client --config trx-client.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A single `trx-rs.toml` can configure both: the server reads its `[trx-server]`
|
||||||
|
section and the client reads `[trx-client]`.
|
||||||
|
|
||||||
Open the configured HTTP frontend address in a browser (default `http://localhost:8080`).
|
Open the configured HTTP frontend address in a browser (default `http://localhost:8080`).
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|||||||
+133
-22
@@ -17,30 +17,61 @@ frontends.
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Both `trx-server` and `trx-client` use TOML configuration files. Use
|
Both `trx-server` and `trx-client` read TOML. The server takes its settings
|
||||||
`--print-config` to generate a fully commented example.
|
from the `[trx-server]` section and the client from `[trx-client]`, so one
|
||||||
|
`trx-rs.toml` can configure both — or each may live in its own file with the
|
||||||
|
section header left off.
|
||||||
|
|
||||||
|
`trx-rs.toml.example` in the repository root is a complete, commented example
|
||||||
|
generated from the config definitions themselves. `--print-config` prints the
|
||||||
|
same settings without the comments.
|
||||||
|
|
||||||
### File Locations
|
### File Locations
|
||||||
|
|
||||||
**trx-server** lookup order:
|
Both binaries use the same lookup order:
|
||||||
1. `--config <FILE>`
|
|
||||||
2. `./trx-server.toml`
|
|
||||||
3. `~/.trx-server.toml`
|
|
||||||
4. `~/.config/trx-rs/server.toml`
|
|
||||||
5. `/etc/trx-rs/server.toml`
|
|
||||||
|
|
||||||
**trx-client** lookup order:
|
|
||||||
1. `--config <FILE>`
|
1. `--config <FILE>`
|
||||||
2. `./trx-client.toml`
|
2. `./trx-rs.toml`
|
||||||
3. `~/.config/trx-rs/client.toml`
|
3. `~/.config/trx-rs/trx-rs.toml`
|
||||||
4. `/etc/trx-rs/client.toml`
|
4. `/etc/trx-rs/trx-rs.toml`
|
||||||
|
|
||||||
CLI arguments override config file values.
|
CLI arguments override config file values.
|
||||||
|
|
||||||
### Environment Variables
|
### Checking a Config
|
||||||
|
|
||||||
- `TRX_PLUGIN_DIRS`: additional plugin directories (path-separated), used by
|
`--check-config` loads the file, reports every problem it finds — unknown keys,
|
||||||
both server and client.
|
invalid values, listeners fighting over a port — and exits without starting
|
||||||
|
anything:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
trx-server --check-config --config trx-rs.toml
|
||||||
|
trx-client --check-config --config trx-rs.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Unknown keys are warnings by default, so a config written for a newer version
|
||||||
|
still runs on an older binary. `--strict-config` makes them fatal.
|
||||||
|
|
||||||
|
`trx-configurator --check <FILE>` runs the same checks.
|
||||||
|
|
||||||
|
### Environment Variables and Secrets
|
||||||
|
|
||||||
|
Any string in the config may reference an environment variable as `${VAR}`;
|
||||||
|
an unset variable is an error rather than an empty value.
|
||||||
|
|
||||||
|
Credentials can be kept out of the config entirely by pointing at a file
|
||||||
|
instead. Every secret has a `*_file` sibling — set one or the other, never
|
||||||
|
both:
|
||||||
|
|
||||||
|
| Inline key | File key | Contents |
|
||||||
|
|------------|----------|----------|
|
||||||
|
| `[listen.auth].tokens` | `tokens_file` | one token per line |
|
||||||
|
| `[[remotes]].auth.token` | `token_file` | the token |
|
||||||
|
| `[frontends.http.auth].rx_passphrase` | `rx_passphrase_file` | the passphrase |
|
||||||
|
| `[frontends.http.auth].control_passphrase` | `control_passphrase_file` | the passphrase |
|
||||||
|
| `[frontends.http_json.auth].tokens` | `tokens_file` | one token per line |
|
||||||
|
|
||||||
|
Blank lines and `#` comments are ignored in the list files. A config that holds
|
||||||
|
credentials inline and is readable by group or others is flagged at startup.
|
||||||
|
|
||||||
### Server Options
|
### Server Options
|
||||||
|
|
||||||
@@ -96,6 +127,7 @@ CLI arguments override config file values.
|
|||||||
| Field | Type | Default | Description |
|
| Field | Type | Default | Description |
|
||||||
|-------|------|---------|-------------|
|
|-------|------|---------|-------------|
|
||||||
| `tokens` | string[] | `[]` | Allowed auth tokens (empty = no auth) |
|
| `tokens` | string[] | `[]` | Allowed auth tokens (empty = no auth) |
|
||||||
|
| `tokens_file` | string | — | Read tokens from this file, one per line |
|
||||||
|
|
||||||
#### `[audio]`
|
#### `[audio]`
|
||||||
|
|
||||||
@@ -197,6 +229,29 @@ Notes:
|
|||||||
Files are appended in JSON Lines format. Supported date tokens: `%YYYY%`,
|
Files are appended in JSON Lines format. Supported date tokens: `%YYYY%`,
|
||||||
`%MM%`, `%DD%` (UTC).
|
`%MM%`, `%DD%` (UTC).
|
||||||
|
|
||||||
|
#### `[decoders]`
|
||||||
|
|
||||||
|
| Field | Type | Default | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `enabled` | string[] | all decoders | Decoders to run for this rig |
|
||||||
|
| `output_dir` | string | `"$XDG_CACHE_HOME/trx-rs"` | Base directory for decoders that write images |
|
||||||
|
|
||||||
|
Valid decoder names: `aprs`, `aprs_hf`, `ais`, `cw`, `ft2`, `ft4`, `ft8`,
|
||||||
|
`lrpt`, `sstv`, `vdes`, `wefax`, `wspr` — the same names `[[sdr.channels]]`
|
||||||
|
uses. An unrecognised name is a config error.
|
||||||
|
|
||||||
|
Every decoder runs by default, which costs real CPU on a small machine. On a
|
||||||
|
station that only works digital modes, listing just what you use is worth it:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[decoders]
|
||||||
|
enabled = ["ft8", "ft4", "wspr"]
|
||||||
|
```
|
||||||
|
|
||||||
|
`sstv`, `wefax` and `lrpt` write images into a subdirectory of `output_dir`
|
||||||
|
named after the decoder. `ais` and `vdes` additionally require an SDR channel
|
||||||
|
configured to feed them.
|
||||||
|
|
||||||
#### Multi-Rig Configuration
|
#### Multi-Rig Configuration
|
||||||
|
|
||||||
Use `[[rigs]]` arrays instead of the flat `[rig]` section for multi-rig setups:
|
Use `[[rigs]]` arrays instead of the flat `[rig]` section for multi-rig setups:
|
||||||
@@ -246,6 +301,25 @@ Rigs without an explicit `id` get auto-generated IDs like `ft817_0`, `soapysdr_1
|
|||||||
| Field | Type | Default | Description |
|
| Field | Type | Default | Description |
|
||||||
|-------|------|---------|-------------|
|
|-------|------|---------|-------------|
|
||||||
| `token` | string | — | Auth token (must not be empty if set) |
|
| `token` | string | — | Auth token (must not be empty if set) |
|
||||||
|
| `token_file` | string | — | Read the token from this file instead |
|
||||||
|
|
||||||
|
#### `[[remotes]]`
|
||||||
|
|
||||||
|
Preferred over the single `[remote]` section: one entry per rig, each mapping a
|
||||||
|
short name to a server and an optional server-side rig id.
|
||||||
|
|
||||||
|
| Field | Type | Default | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `name` | string | — | Short name used everywhere in the client |
|
||||||
|
| `url` | string | — | Server address (`host:port`) |
|
||||||
|
| `rig_id` | string | — | Rig id on a multi-rig server |
|
||||||
|
| `auth.token` | string | — | Auth token |
|
||||||
|
| `auth.token_file` | string | — | Read the token from this file instead |
|
||||||
|
| `poll_interval_ms` | u64 | `750` | State poll interval |
|
||||||
|
|
||||||
|
The `name` is the key used by `default_rig_name`, `rigctl.rig_ports`,
|
||||||
|
`audio.rig_urls`, `audio.rig_ports` and `decode_history_retention_min_by_rig`.
|
||||||
|
A name in any of those maps that no remote answers to is a config error.
|
||||||
|
|
||||||
#### `[frontends.http]`
|
#### `[frontends.http]`
|
||||||
|
|
||||||
@@ -254,6 +328,31 @@ Rigs without an explicit `id` get auto-generated IDs like `ft817_0`, `soapysdr_1
|
|||||||
| `enabled` | bool | `true` | Enable web UI |
|
| `enabled` | bool | `true` | Enable web UI |
|
||||||
| `listen` | ip | `127.0.0.1` | Bind address |
|
| `listen` | ip | `127.0.0.1` | Bind address |
|
||||||
| `port` | u16 | `8080` | Bind port |
|
| `port` | u16 | `8080` | Bind port |
|
||||||
|
| `default_rig_name` | string | — | Remote selected on startup |
|
||||||
|
| `initial_map_zoom` | u8 | `10` | Starting zoom for the APRS map |
|
||||||
|
| `show_sdr_gain_control` | bool | `true` | Expose the RF gain control |
|
||||||
|
| `bandplan_enabled` | bool | `true` | Show the bandplan strip |
|
||||||
|
| `bandplan_region` | string | `"iaru_r1"` | `iaru_r1`, `iaru_r2`, or `iaru_r3` |
|
||||||
|
| `decode_history_retention_min` | u64 | `1440` | Decode history retention |
|
||||||
|
| `decode_history_retention_min_by_rig` | table | `{}` | Per-remote retention override |
|
||||||
|
| `spectrum_coverage_margin_hz` | u32 | `50000` | Centre-retune guard margin |
|
||||||
|
| `spectrum_usable_span_ratio` | f32 | `0.92` | Usable fraction of the sampled span |
|
||||||
|
|
||||||
|
#### `[frontends.http.auth]`
|
||||||
|
|
||||||
|
| Field | Type | Default | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `enabled` | bool | `false` | Require a passphrase |
|
||||||
|
| `rx_passphrase` | string | — | Passphrase granting receive-only access |
|
||||||
|
| `rx_passphrase_file` | string | — | Read it from this file instead |
|
||||||
|
| `control_passphrase` | string | — | Passphrase granting full control |
|
||||||
|
| `control_passphrase_file` | string | — | Read it from this file instead |
|
||||||
|
| `tx_access_control_enabled` | bool | `true` | Hide TX from unauthenticated users |
|
||||||
|
| `session_ttl_min` | u64 | `480` | Session lifetime |
|
||||||
|
| `cookie_secure` | bool | `false` | Set Secure on the session cookie (needs HTTPS) |
|
||||||
|
| `cookie_same_site` | string | `"Lax"` | `Strict`, `Lax`, or `None` |
|
||||||
|
|
||||||
|
With `enabled = true`, at least one passphrase must be set.
|
||||||
|
|
||||||
#### `[frontends.rigctl]`
|
#### `[frontends.rigctl]`
|
||||||
|
|
||||||
@@ -261,7 +360,11 @@ Rigs without an explicit `id` get auto-generated IDs like `ft817_0`, `soapysdr_1
|
|||||||
|-------|------|---------|-------------|
|
|-------|------|---------|-------------|
|
||||||
| `enabled` | bool | `false` | Enable Hamlib rigctl |
|
| `enabled` | bool | `false` | Enable Hamlib rigctl |
|
||||||
| `listen` | ip | `127.0.0.1` | Bind address |
|
| `listen` | ip | `127.0.0.1` | Bind address |
|
||||||
| `port` | u16 | `4532` | Bind port |
|
| `rig_ports` | table | `{}` | Remote name → local port; one listener each |
|
||||||
|
|
||||||
|
One listener is started per `rig_ports` entry, each routing to its rig, so
|
||||||
|
`rig_ports` must name at least one remote when the frontend is enabled. The
|
||||||
|
older single `port` key and `--rigctl-port` are ignored.
|
||||||
|
|
||||||
#### `[frontends.http_json]`
|
#### `[frontends.http_json]`
|
||||||
|
|
||||||
@@ -271,13 +374,17 @@ Rigs without an explicit `id` get auto-generated IDs like `ft817_0`, `soapysdr_1
|
|||||||
| `listen` | ip | `127.0.0.1` | Bind address |
|
| `listen` | ip | `127.0.0.1` | Bind address |
|
||||||
| `port` | u16 | `0` | Bind port (0 = ephemeral) |
|
| `port` | u16 | `0` | Bind port (0 = ephemeral) |
|
||||||
| `auth.tokens` | string[] | `[]` | Allowed auth tokens |
|
| `auth.tokens` | string[] | `[]` | Allowed auth tokens |
|
||||||
|
| `auth.tokens_file` | string | — | Read tokens from this file, one per line |
|
||||||
|
|
||||||
#### `[frontends.audio]`
|
#### `[frontends.audio]`
|
||||||
|
|
||||||
| Field | Type | Default | Description |
|
| Field | Type | Default | Description |
|
||||||
|-------|------|---------|-------------|
|
|-------|------|---------|-------------|
|
||||||
| `enabled` | bool | `true` | Enable audio client |
|
| `enabled` | bool | `true` | Enable audio client |
|
||||||
| `server_port` | u16 | `4531` | Server audio port |
|
| `server_url` | string | — | Audio endpoint for every remote |
|
||||||
|
| `rig_urls` | table | `{}` | Remote name → audio URL (wins over `server_url`) |
|
||||||
|
| `server_port` | u16 | `4531` | Fallback port when no URL is configured |
|
||||||
|
| `rig_ports` | table | `{}` | Remote name → port; superseded by `rig_urls` |
|
||||||
| `bridge.enabled` | bool | `false` | Enable local CPAL audio bridge |
|
| `bridge.enabled` | bool | `false` | Enable local CPAL audio bridge |
|
||||||
| `bridge.rx_output_device` | string | — | Local playback device |
|
| `bridge.rx_output_device` | string | — | Local playback device |
|
||||||
| `bridge.tx_input_device` | string | — | Local capture device |
|
| `bridge.tx_input_device` | string | — | Local capture device |
|
||||||
@@ -290,13 +397,17 @@ loopback on Linux, BlackHole on macOS).
|
|||||||
### CLI Override Summary
|
### CLI Override Summary
|
||||||
|
|
||||||
**trx-server:**
|
**trx-server:**
|
||||||
`--config`, `--print-config`, `--rig`, `--access`, `--callsign`, `--listen`,
|
`--config`, `--print-config`, `--check-config`, `--strict-config`, `--rig`,
|
||||||
`--port`. SDR options are file-only.
|
`--access`, `--callsign`, `--listen`, `--port`. SDR options are file-only.
|
||||||
|
|
||||||
**trx-client:**
|
**trx-client:**
|
||||||
`--config`, `--print-config`, `--url`, `--token`, `--poll-interval`,
|
`--config`, `--print-config`, `--check-config`, `--strict-config`, `--url`,
|
||||||
`--frontend`, `--http-listen`, `--http-port`, `--rigctl-listen`,
|
`--token`, `--poll-interval`, `--rig-id`, `--frontend`, `--http-listen`,
|
||||||
`--rigctl-port`, `--http-json-listen`, `--http-json-port`, `--callsign`.
|
`--http-port`, `--rigctl-listen`, `--http-json-listen`, `--http-json-port`,
|
||||||
|
`--callsign`.
|
||||||
|
|
||||||
|
`--listen` on the server overrides the bind address of both the control
|
||||||
|
listener and every rig's audio listener.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ trx-core = { path = "../trx-core" }
|
|||||||
trx-decode-log = { path = "../decoders/trx-decode-log" }
|
trx-decode-log = { path = "../decoders/trx-decode-log" }
|
||||||
trx-reporting = { path = "../trx-reporting" }
|
trx-reporting = { path = "../trx-reporting" }
|
||||||
serde_ignored = "0.1"
|
serde_ignored = "0.1"
|
||||||
|
toml_edit = "0.22"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
//! Regenerate `trx-rs.toml.example` from the config structs.
|
||||||
|
//!
|
||||||
|
//! Run from anywhere in the workspace:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! cargo run -p trx-config --example generate_example
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! A test in `trx_config::example` fails when the checked-in file no longer
|
||||||
|
//! matches, which is the reminder to run this.
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() -> std::io::Result<()> {
|
||||||
|
let target: PathBuf = std::env::args()
|
||||||
|
.nth(1)
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../trx-rs.toml.example")
|
||||||
|
});
|
||||||
|
|
||||||
|
std::fs::write(&target, trx_config::example::combined_example())?;
|
||||||
|
println!("Wrote {}", target.display());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -807,13 +807,10 @@ impl ClientConfig {
|
|||||||
|
|
||||||
/// Generate an example configuration wrapped under the `[trx-client]`
|
/// Generate an example configuration wrapped under the `[trx-client]`
|
||||||
/// section header, suitable for use in a combined `trx-rs.toml` file.
|
/// section header, suitable for use in a combined `trx-rs.toml` file.
|
||||||
pub fn example_combined_toml() -> String {
|
/// The example configuration used by `--print-config` and by the
|
||||||
#[derive(serde::Serialize)]
|
/// generated `trx-rs.toml.example`.
|
||||||
struct Wrapper {
|
pub fn example_config() -> Self {
|
||||||
#[serde(rename = "trx-client")]
|
ClientConfig {
|
||||||
inner: ClientConfig,
|
|
||||||
}
|
|
||||||
let example = ClientConfig {
|
|
||||||
general: GeneralConfig {
|
general: GeneralConfig {
|
||||||
callsign: Some("N0CALL".to_string()),
|
callsign: Some("N0CALL".to_string()),
|
||||||
website_url: Some("https://haxx.space".to_string()),
|
website_url: Some("https://haxx.space".to_string()),
|
||||||
@@ -879,8 +876,21 @@ impl ClientConfig {
|
|||||||
http_json: HttpJsonFrontendConfig::default(),
|
http_json: HttpJsonFrontendConfig::default(),
|
||||||
audio: AudioClientConfig::default(),
|
audio: AudioClientConfig::default(),
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
toml::to_string_pretty(&Wrapper { inner: example }).unwrap_or_default()
|
}
|
||||||
|
|
||||||
|
/// Generate an example configuration wrapped under the `[trx-client]`
|
||||||
|
/// section header, suitable for use in a combined `trx-rs.toml` file.
|
||||||
|
pub fn example_combined_toml() -> String {
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct Wrapper {
|
||||||
|
#[serde(rename = "trx-client")]
|
||||||
|
inner: ClientConfig,
|
||||||
|
}
|
||||||
|
toml::to_string_pretty(&Wrapper {
|
||||||
|
inner: ClientConfig::example_config(),
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
//! Generating the example configuration from the config structs.
|
||||||
|
//!
|
||||||
|
//! `trx-rs.toml.example` used to be maintained by hand and had fallen years
|
||||||
|
//! behind the code — no `[[rigs]]`, no `[[remotes]]`, no `[timeouts]`, no
|
||||||
|
//! bandplan settings. It is now produced from the structs themselves, so a new
|
||||||
|
//! field appears in the example the moment it exists, and a test fails if the
|
||||||
|
//! checked-in copy drifts.
|
||||||
|
//!
|
||||||
|
//! Section comments come from the table below. A section without an entry is
|
||||||
|
//! still emitted — only its explanatory text is missing — so forgetting to add
|
||||||
|
//! one can never drop a setting from the example.
|
||||||
|
|
||||||
|
use toml_edit::{DocumentMut, Item};
|
||||||
|
|
||||||
|
use crate::{ClientConfig, ServerConfig};
|
||||||
|
|
||||||
|
const HEADER: &str = "\
|
||||||
|
# trx-rs example configuration
|
||||||
|
#
|
||||||
|
# Generated from the config structs; regenerate with:
|
||||||
|
# cargo run -p trx-config --example generate_example
|
||||||
|
#
|
||||||
|
# Both sections are optional: trx-server reads [trx-server], trx-client reads
|
||||||
|
# [trx-client], and either may live in its own file with the section header
|
||||||
|
# omitted. Any string may use ${ENV_VAR}, and credentials may be moved out of
|
||||||
|
# this file with the matching *_file keys.
|
||||||
|
#
|
||||||
|
# Check a config without starting anything:
|
||||||
|
# trx-server --check-config --config trx-rs.toml
|
||||||
|
# trx-client --check-config --config trx-rs.toml
|
||||||
|
";
|
||||||
|
|
||||||
|
/// Explanatory comments for config sections, keyed by dotted path.
|
||||||
|
const SECTION_COMMENTS: &[(&str, &str)] = &[
|
||||||
|
("trx-server", "Server: drives the radio hardware."),
|
||||||
|
(
|
||||||
|
"trx-server.general",
|
||||||
|
"Station identity. Coordinates feed PSKReporter and the map.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"trx-server.rig",
|
||||||
|
"Single-rig layout. For several radios, delete this and use [[rigs]].",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"trx-server.rig.access",
|
||||||
|
"How to reach the radio: serial, tcp, or sdr.",
|
||||||
|
),
|
||||||
|
("trx-server.behavior", "CAT polling and retry behaviour."),
|
||||||
|
(
|
||||||
|
"trx-server.listen",
|
||||||
|
"JSON control listener that trx-client connects to.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"trx-server.listen.auth",
|
||||||
|
"Tokens clients must present. Empty means no authentication.\n\
|
||||||
|
Use tokens_file = \"/etc/trx-rs/tokens\" to keep them out of this file.",
|
||||||
|
),
|
||||||
|
("trx-server.audio", "Opus audio stream for trx-client."),
|
||||||
|
(
|
||||||
|
"trx-server.decoders",
|
||||||
|
"Which decoders run. Trimming this list saves real CPU on small boxes.\n\
|
||||||
|
Valid names: aprs, aprs_hf, ais, cw, ft2, ft4, ft8, lrpt, sstv, vdes, wefax, wspr.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"trx-server.pskreporter",
|
||||||
|
"Report FT8/FT4/WSPR spots to pskreporter.info.",
|
||||||
|
),
|
||||||
|
("trx-server.aprsfi", "Forward received APRS frames to APRS-IS."),
|
||||||
|
("trx-server.decode_logs", "Write decodes to JSON Lines files."),
|
||||||
|
(
|
||||||
|
"trx-server.sdr",
|
||||||
|
"SoapySDR pipeline; used when [rig.access] type = \"sdr\".",
|
||||||
|
),
|
||||||
|
("trx-server.sdr.gain", "\"auto\" for hardware AGC, or \"manual\"."),
|
||||||
|
("trx-server.sdr.squelch", "Software squelch on demodulated audio."),
|
||||||
|
(
|
||||||
|
"trx-server.sdr.noise_blanker",
|
||||||
|
"Impulse-noise suppression on the IQ stream.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"trx-server.timeouts",
|
||||||
|
"Timeout and buffer tuning. The defaults suit most setups.",
|
||||||
|
),
|
||||||
|
("trx-client", "Client: exposes the radio to users."),
|
||||||
|
("trx-client.general", "Labels shown in the web UI."),
|
||||||
|
(
|
||||||
|
"trx-client.remote",
|
||||||
|
"Legacy single-remote form; prefer [[remotes]] below.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"trx-client.frontends.http",
|
||||||
|
"Web UI. default_rig_name and the per-rig maps are keyed by the\n\
|
||||||
|
[[remotes]] name, not the server-side rig id.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"trx-client.frontends.http.auth",
|
||||||
|
"Passphrase login for the web UI. rx_passphrase_file and\n\
|
||||||
|
control_passphrase_file keep the secrets out of this file.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"trx-client.frontends.rigctl",
|
||||||
|
"Hamlib-compatible TCP interface, one listener per rig.",
|
||||||
|
),
|
||||||
|
("trx-client.frontends.http_json", "JSON-over-TCP control interface."),
|
||||||
|
("trx-client.frontends.audio", "Where to fetch the audio stream from."),
|
||||||
|
(
|
||||||
|
"trx-client.frontends.audio.bridge",
|
||||||
|
"Play RX audio on a local sound device and capture TX from one.",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Render the combined `trx-rs.toml.example` contents.
|
||||||
|
pub fn combined_example() -> String {
|
||||||
|
let mut doc = DocumentMut::new();
|
||||||
|
doc.decor_mut().set_prefix(HEADER);
|
||||||
|
|
||||||
|
doc.insert("trx-server", section_item(&ServerConfig::example_config()));
|
||||||
|
doc.insert("trx-client", section_item(&ClientConfig::example_config()));
|
||||||
|
|
||||||
|
// Each section was serialized on its own, so both carry table positions
|
||||||
|
// starting at zero and would otherwise render interleaved.
|
||||||
|
renumber_tables(&mut doc);
|
||||||
|
|
||||||
|
for (path, comment) in SECTION_COMMENTS {
|
||||||
|
annotate(&mut doc, path, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renumber every table so the document renders in tree order.
|
||||||
|
fn renumber_tables(doc: &mut DocumentMut) {
|
||||||
|
fn walk(item: &mut Item, next: &mut usize) {
|
||||||
|
match item {
|
||||||
|
Item::Table(table) => {
|
||||||
|
table.set_position(*next);
|
||||||
|
*next += 1;
|
||||||
|
for (_, child) in table.iter_mut() {
|
||||||
|
walk(child, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item::ArrayOfTables(array) => {
|
||||||
|
for table in array.iter_mut() {
|
||||||
|
table.set_position(*next);
|
||||||
|
*next += 1;
|
||||||
|
for (_, child) in table.iter_mut() {
|
||||||
|
walk(child, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut next = 0;
|
||||||
|
for (_, item) in doc.as_table_mut().iter_mut() {
|
||||||
|
walk(item, &mut next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize one config into a toml_edit table.
|
||||||
|
fn section_item<T: serde::Serialize>(config: &T) -> Item {
|
||||||
|
let rendered = toml::to_string_pretty(config).unwrap_or_default();
|
||||||
|
let doc: DocumentMut = rendered.parse().expect("serialized config must re-parse");
|
||||||
|
Item::Table(doc.as_table().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attach a comment above the table at `path`, if it exists.
|
||||||
|
fn annotate(doc: &mut DocumentMut, path: &str, comment: &str) {
|
||||||
|
let mut item: Option<&mut Item> = None;
|
||||||
|
for segment in path.split('.') {
|
||||||
|
let next = match item {
|
||||||
|
None => doc.get_mut(segment),
|
||||||
|
Some(current) => current.as_table_mut().and_then(|t| t.get_mut(segment)),
|
||||||
|
};
|
||||||
|
match next {
|
||||||
|
Some(found) => item = Some(found),
|
||||||
|
None => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(table) = item.and_then(|i| i.as_table_mut()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let body: String = comment
|
||||||
|
.lines()
|
||||||
|
.map(|line| format!("# {}\n", line.trim_start()))
|
||||||
|
.collect();
|
||||||
|
table.decor_mut().set_prefix(format!("\n{body}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::ConfigFile;
|
||||||
|
|
||||||
|
/// The checked-in example must match what the structs produce, so a new
|
||||||
|
/// config field cannot land without showing up in the example.
|
||||||
|
#[test]
|
||||||
|
fn test_checked_in_example_is_up_to_date() {
|
||||||
|
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../trx-rs.toml.example")
|
||||||
|
.canonicalize()
|
||||||
|
.expect("example file must exist");
|
||||||
|
let on_disk = std::fs::read_to_string(&path).expect("example file must be readable");
|
||||||
|
assert_eq!(
|
||||||
|
on_disk,
|
||||||
|
combined_example(),
|
||||||
|
"trx-rs.toml.example is out of date; regenerate with \
|
||||||
|
`cargo run -p trx-config --example generate_example`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_example_loads_and_validates() {
|
||||||
|
let mut file = tempfile::NamedTempFile::new().unwrap();
|
||||||
|
std::io::Write::write_all(&mut file, combined_example().as_bytes()).unwrap();
|
||||||
|
|
||||||
|
let server = ServerConfig::load_from_file(file.path()).expect("server section loads");
|
||||||
|
assert!(
|
||||||
|
server.unknown_keys.is_empty(),
|
||||||
|
"the generated example must not contain unknown keys: {:?}",
|
||||||
|
server.unknown_keys
|
||||||
|
);
|
||||||
|
server.config.validate().expect("server section validates");
|
||||||
|
|
||||||
|
let client = ClientConfig::load_from_file(file.path()).expect("client section loads");
|
||||||
|
assert!(
|
||||||
|
client.unknown_keys.is_empty(),
|
||||||
|
"the generated example must not contain unknown keys: {:?}",
|
||||||
|
client.unknown_keys
|
||||||
|
);
|
||||||
|
client.config.validate().expect("client section validates");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every section that gained a comment must still exist under that path.
|
||||||
|
#[test]
|
||||||
|
fn test_section_comments_match_real_sections() {
|
||||||
|
let doc: DocumentMut = combined_example().parse().unwrap();
|
||||||
|
for (path, _) in SECTION_COMMENTS {
|
||||||
|
let mut item = None;
|
||||||
|
for segment in path.split('.') {
|
||||||
|
item = match item {
|
||||||
|
None => doc.get(segment),
|
||||||
|
Some(current) => current.as_table().and_then(|t| t.get(segment)),
|
||||||
|
};
|
||||||
|
assert!(item.is_some(), "commented section [{path}] no longer exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
//! it with, so the two can never drift apart.
|
//! it with, so the two can never drift apart.
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
pub mod example;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
pub mod secrets;
|
pub mod secrets;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|||||||
@@ -727,13 +727,10 @@ impl ServerConfig {
|
|||||||
|
|
||||||
/// Generate an example configuration wrapped under the `[trx-server]`
|
/// Generate an example configuration wrapped under the `[trx-server]`
|
||||||
/// section header, suitable for use in a combined `trx-rs.toml` file.
|
/// section header, suitable for use in a combined `trx-rs.toml` file.
|
||||||
pub fn example_combined_toml() -> String {
|
/// The example configuration used by `--print-config` and by the
|
||||||
#[derive(serde::Serialize)]
|
/// generated `trx-rs.toml.example`.
|
||||||
struct Wrapper {
|
pub fn example_config() -> Self {
|
||||||
#[serde(rename = "trx-server")]
|
ServerConfig {
|
||||||
inner: ServerConfig,
|
|
||||||
}
|
|
||||||
let example = ServerConfig {
|
|
||||||
general: GeneralConfig {
|
general: GeneralConfig {
|
||||||
callsign: Some("N0CALL".to_string()),
|
callsign: Some("N0CALL".to_string()),
|
||||||
log_level: Some("info".to_string()),
|
log_level: Some("info".to_string()),
|
||||||
@@ -763,8 +760,21 @@ impl ServerConfig {
|
|||||||
sdr: SdrConfig::default(),
|
sdr: SdrConfig::default(),
|
||||||
timeouts: TimeoutsConfig::default(),
|
timeouts: TimeoutsConfig::default(),
|
||||||
rigs: Vec::new(),
|
rigs: Vec::new(),
|
||||||
};
|
}
|
||||||
toml::to_string_pretty(&Wrapper { inner: example }).unwrap_or_default()
|
}
|
||||||
|
|
||||||
|
/// Generate an example configuration wrapped under the `[trx-server]`
|
||||||
|
/// section header, suitable for use in a combined `trx-rs.toml` file.
|
||||||
|
pub fn example_combined_toml() -> String {
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct Wrapper {
|
||||||
|
#[serde(rename = "trx-server")]
|
||||||
|
inner: ServerConfig,
|
||||||
|
}
|
||||||
|
toml::to_string_pretty(&Wrapper {
|
||||||
|
inner: ServerConfig::example_config(),
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+105
-4
@@ -1,36 +1,60 @@
|
|||||||
|
# trx-rs example configuration
|
||||||
|
#
|
||||||
|
# Generated from the config structs; regenerate with:
|
||||||
|
# cargo run -p trx-config --example generate_example
|
||||||
|
#
|
||||||
|
# Both sections are optional: trx-server reads [trx-server], trx-client reads
|
||||||
|
# [trx-client], and either may live in its own file with the section header
|
||||||
|
# omitted. Any string may use ${ENV_VAR}, and credentials may be moved out of
|
||||||
|
# this file with the matching *_file keys.
|
||||||
|
#
|
||||||
|
# Check a config without starting anything:
|
||||||
|
# trx-server --check-config --config trx-rs.toml
|
||||||
|
# trx-client --check-config --config trx-rs.toml
|
||||||
|
|
||||||
|
# Server: drives the radio hardware.
|
||||||
[trx-server]
|
[trx-server]
|
||||||
rigs = []
|
rigs = []
|
||||||
|
|
||||||
|
# Station identity. Coordinates feed PSKReporter and the map.
|
||||||
[trx-server.general]
|
[trx-server.general]
|
||||||
callsign = "N0CALL"
|
callsign = "N0CALL"
|
||||||
log_level = "info"
|
log_level = "info"
|
||||||
latitude = 52.2297
|
latitude = 52.2297
|
||||||
longitude = 21.0122
|
longitude = 21.0122
|
||||||
|
|
||||||
|
# Single-rig layout. For several radios, delete this and use [[rigs]].
|
||||||
[trx-server.rig]
|
[trx-server.rig]
|
||||||
model = "ft817"
|
model = "ft817"
|
||||||
initial_freq_hz = 144300000
|
initial_freq_hz = 144300000
|
||||||
initial_mode = "USB"
|
initial_mode = "USB"
|
||||||
|
|
||||||
|
# How to reach the radio: serial, tcp, or sdr.
|
||||||
[trx-server.rig.access]
|
[trx-server.rig.access]
|
||||||
type = "serial"
|
type = "serial"
|
||||||
port = "/dev/ttyUSB0"
|
port = "/dev/ttyUSB0"
|
||||||
baud = 9600
|
baud = 9600
|
||||||
|
|
||||||
|
# CAT polling and retry behaviour.
|
||||||
[trx-server.behavior]
|
[trx-server.behavior]
|
||||||
poll_interval_ms = 500
|
poll_interval_ms = 500
|
||||||
poll_interval_tx_ms = 100
|
poll_interval_tx_ms = 100
|
||||||
max_retries = 3
|
max_retries = 3
|
||||||
retry_base_delay_ms = 100
|
retry_base_delay_ms = 100
|
||||||
|
vfo_prime = true
|
||||||
|
|
||||||
|
# JSON control listener that trx-client connects to.
|
||||||
[trx-server.listen]
|
[trx-server.listen]
|
||||||
enabled = true
|
enabled = true
|
||||||
listen = "127.0.0.1"
|
listen = "127.0.0.1"
|
||||||
port = 4530
|
port = 4530
|
||||||
|
|
||||||
|
# Tokens clients must present. Empty means no authentication.
|
||||||
|
# Use tokens_file = "/etc/trx-rs/tokens" to keep them out of this file.
|
||||||
[trx-server.listen.auth]
|
[trx-server.listen.auth]
|
||||||
tokens = []
|
tokens = []
|
||||||
|
|
||||||
|
# Opus audio stream for trx-client.
|
||||||
[trx-server.audio]
|
[trx-server.audio]
|
||||||
enabled = true
|
enabled = true
|
||||||
listen = "127.0.0.1"
|
listen = "127.0.0.1"
|
||||||
@@ -42,25 +66,52 @@ channels = 2
|
|||||||
frame_duration_ms = 20
|
frame_duration_ms = 20
|
||||||
bitrate_bps = 256000
|
bitrate_bps = 256000
|
||||||
|
|
||||||
|
# Report FT8/FT4/WSPR spots to pskreporter.info.
|
||||||
[trx-server.pskreporter]
|
[trx-server.pskreporter]
|
||||||
enabled = false
|
enabled = false
|
||||||
host = "report.pskreporter.info"
|
host = "report.pskreporter.info"
|
||||||
port = 4739
|
port = 4739
|
||||||
|
|
||||||
|
# Forward received APRS frames to APRS-IS.
|
||||||
[trx-server.aprsfi]
|
[trx-server.aprsfi]
|
||||||
enabled = false
|
enabled = false
|
||||||
host = "rotate.aprs.net"
|
host = "rotate.aprs.net"
|
||||||
port = 14580
|
port = 14580
|
||||||
passcode = -1
|
passcode = -1
|
||||||
|
beacon = false
|
||||||
|
beacon_interval_secs = 1200
|
||||||
|
beacon_symbol_table = "/"
|
||||||
|
beacon_symbol_code = "-"
|
||||||
|
|
||||||
|
# Write decodes to JSON Lines files.
|
||||||
[trx-server.decode_logs]
|
[trx-server.decode_logs]
|
||||||
enabled = false
|
enabled = false
|
||||||
dir = "/path/to/log/dir"
|
dir = "/Users/sjg/Library/Caches/trx-rs/decoders"
|
||||||
aprs_file = "TRXRS-APRS-%YYYY%-%MM%-%DD%.log"
|
aprs_file = "TRXRS-APRS-%YYYY%-%MM%-%DD%.log"
|
||||||
cw_file = "TRXRS-CW-%YYYY%-%MM%-%DD%.log"
|
cw_file = "TRXRS-CW-%YYYY%-%MM%-%DD%.log"
|
||||||
ft8_file = "TRXRS-FT8-%YYYY%-%MM%-%DD%.log"
|
ft8_file = "TRXRS-FT8-%YYYY%-%MM%-%DD%.log"
|
||||||
wspr_file = "TRXRS-WSPR-%YYYY%-%MM%-%DD%.log"
|
wspr_file = "TRXRS-WSPR-%YYYY%-%MM%-%DD%.log"
|
||||||
|
wefax_file = "TRXRS-WEFAX-%YYYY%-%MM%-%DD%.log"
|
||||||
|
|
||||||
|
# Which decoders run. Trimming this list saves real CPU on small boxes.
|
||||||
|
# Valid names: aprs, aprs_hf, ais, cw, ft2, ft4, ft8, lrpt, sstv, vdes, wefax, wspr.
|
||||||
|
[trx-server.decoders]
|
||||||
|
enabled = [
|
||||||
|
"aprs",
|
||||||
|
"aprs_hf",
|
||||||
|
"ais",
|
||||||
|
"cw",
|
||||||
|
"ft2",
|
||||||
|
"ft4",
|
||||||
|
"ft8",
|
||||||
|
"lrpt",
|
||||||
|
"sstv",
|
||||||
|
"vdes",
|
||||||
|
"wefax",
|
||||||
|
"wspr",
|
||||||
|
]
|
||||||
|
|
||||||
|
# SoapySDR pipeline; used when [rig.access] type = "sdr".
|
||||||
[trx-server.sdr]
|
[trx-server.sdr]
|
||||||
sample_rate = 1920000
|
sample_rate = 1920000
|
||||||
bandwidth = 1500000
|
bandwidth = 1500000
|
||||||
@@ -69,16 +120,35 @@ center_offset_hz = 100000
|
|||||||
channels = []
|
channels = []
|
||||||
max_virtual_channels = 4
|
max_virtual_channels = 4
|
||||||
|
|
||||||
|
# "auto" for hardware AGC, or "manual".
|
||||||
[trx-server.sdr.gain]
|
[trx-server.sdr.gain]
|
||||||
mode = "auto"
|
mode = "auto"
|
||||||
value = 30.0
|
value = 30.0
|
||||||
|
|
||||||
|
# Software squelch on demodulated audio.
|
||||||
[trx-server.sdr.squelch]
|
[trx-server.sdr.squelch]
|
||||||
enabled = false
|
enabled = false
|
||||||
threshold_db = -65.0
|
threshold_db = -65.0
|
||||||
hysteresis_db = 3.0
|
hysteresis_db = 3.0
|
||||||
tail_ms = 180
|
tail_ms = 180
|
||||||
|
|
||||||
|
# Impulse-noise suppression on the IQ stream.
|
||||||
|
[trx-server.sdr.noise_blanker]
|
||||||
|
enabled = false
|
||||||
|
threshold = 10.0
|
||||||
|
|
||||||
|
# Timeout and buffer tuning. The defaults suit most setups.
|
||||||
|
[trx-server.timeouts]
|
||||||
|
command_exec_timeout_ms = 10000
|
||||||
|
poll_refresh_timeout_ms = 8000
|
||||||
|
io_timeout_ms = 10000
|
||||||
|
request_timeout_ms = 12000
|
||||||
|
rig_task_channel_buffer = 32
|
||||||
|
|
||||||
|
# Client: exposes the radio to users.
|
||||||
|
[trx-client]
|
||||||
|
|
||||||
|
# Labels shown in the web UI.
|
||||||
[trx-client.general]
|
[trx-client.general]
|
||||||
callsign = "N0CALL"
|
callsign = "N0CALL"
|
||||||
website_url = "https://haxx.space"
|
website_url = "https://haxx.space"
|
||||||
@@ -86,24 +156,49 @@ website_name = "haxx.space"
|
|||||||
ais_vessel_url_base = "https://www.vesselfinder.com/?mmsi="
|
ais_vessel_url_base = "https://www.vesselfinder.com/?mmsi="
|
||||||
log_level = "info"
|
log_level = "info"
|
||||||
|
|
||||||
|
# Legacy single-remote form; prefer [[remotes]] below.
|
||||||
[trx-client.remote]
|
[trx-client.remote]
|
||||||
url = "192.168.1.100:9000"
|
|
||||||
rig_id = "hf"
|
|
||||||
poll_interval_ms = 750
|
poll_interval_ms = 750
|
||||||
|
|
||||||
[trx-client.remote.auth]
|
[trx-client.remote.auth]
|
||||||
|
|
||||||
|
[[trx-client.remotes]]
|
||||||
|
name = "home-hf"
|
||||||
|
url = "192.168.1.100:4530"
|
||||||
|
rig_id = "hf"
|
||||||
|
poll_interval_ms = 750
|
||||||
|
|
||||||
|
[trx-client.remotes.auth]
|
||||||
token = "my-token"
|
token = "my-token"
|
||||||
|
|
||||||
|
[[trx-client.remotes]]
|
||||||
|
name = "home-vhf"
|
||||||
|
url = "192.168.1.100:4530"
|
||||||
|
rig_id = "vhf"
|
||||||
|
poll_interval_ms = 750
|
||||||
|
|
||||||
|
[trx-client.remotes.auth]
|
||||||
|
token = "my-token"
|
||||||
|
|
||||||
|
# Web UI. default_rig_name and the per-rig maps are keyed by the
|
||||||
|
# [[remotes]] name, not the server-side rig id.
|
||||||
[trx-client.frontends.http]
|
[trx-client.frontends.http]
|
||||||
enabled = true
|
enabled = true
|
||||||
listen = "127.0.0.1"
|
listen = "127.0.0.1"
|
||||||
port = 8080
|
port = 8080
|
||||||
default_rig_id = "hf"
|
default_rig_name = "home-hf"
|
||||||
initial_map_zoom = 10
|
initial_map_zoom = 10
|
||||||
spectrum_coverage_margin_hz = 50000
|
spectrum_coverage_margin_hz = 50000
|
||||||
spectrum_usable_span_ratio = 0.9200000166893005
|
spectrum_usable_span_ratio = 0.9200000166893005
|
||||||
show_sdr_gain_control = true
|
show_sdr_gain_control = true
|
||||||
|
bandplan_enabled = true
|
||||||
|
bandplan_region = "iaru_r1"
|
||||||
|
decode_history_retention_min = 1440
|
||||||
|
|
||||||
|
[trx-client.frontends.http.decode_history_retention_min_by_rig]
|
||||||
|
|
||||||
|
# Passphrase login for the web UI. rx_passphrase_file and
|
||||||
|
# control_passphrase_file keep the secrets out of this file.
|
||||||
[trx-client.frontends.http.auth]
|
[trx-client.frontends.http.auth]
|
||||||
enabled = false
|
enabled = false
|
||||||
rx_passphrase = "rx-passphrase-example"
|
rx_passphrase = "rx-passphrase-example"
|
||||||
@@ -113,6 +208,7 @@ session_ttl_min = 480
|
|||||||
cookie_secure = false
|
cookie_secure = false
|
||||||
cookie_same_site = "Lax"
|
cookie_same_site = "Lax"
|
||||||
|
|
||||||
|
# Hamlib-compatible TCP interface, one listener per rig.
|
||||||
[trx-client.frontends.rigctl]
|
[trx-client.frontends.rigctl]
|
||||||
enabled = false
|
enabled = false
|
||||||
listen = "127.0.0.1"
|
listen = "127.0.0.1"
|
||||||
@@ -120,6 +216,7 @@ port = 4532
|
|||||||
|
|
||||||
[trx-client.frontends.rigctl.rig_ports]
|
[trx-client.frontends.rigctl.rig_ports]
|
||||||
|
|
||||||
|
# JSON-over-TCP control interface.
|
||||||
[trx-client.frontends.http_json]
|
[trx-client.frontends.http_json]
|
||||||
enabled = true
|
enabled = true
|
||||||
listen = "127.0.0.1"
|
listen = "127.0.0.1"
|
||||||
@@ -128,12 +225,16 @@ port = 0
|
|||||||
[trx-client.frontends.http_json.auth]
|
[trx-client.frontends.http_json.auth]
|
||||||
tokens = []
|
tokens = []
|
||||||
|
|
||||||
|
# Where to fetch the audio stream from.
|
||||||
[trx-client.frontends.audio]
|
[trx-client.frontends.audio]
|
||||||
enabled = true
|
enabled = true
|
||||||
server_port = 4531
|
server_port = 4531
|
||||||
|
|
||||||
|
[trx-client.frontends.audio.rig_urls]
|
||||||
|
|
||||||
[trx-client.frontends.audio.rig_ports]
|
[trx-client.frontends.audio.rig_ports]
|
||||||
|
|
||||||
|
# Play RX audio on a local sound device and capture TX from one.
|
||||||
[trx-client.frontends.audio.bridge]
|
[trx-client.frontends.audio.bridge]
|
||||||
enabled = false
|
enabled = false
|
||||||
bitrate_bps = 192000
|
bitrate_bps = 192000
|
||||||
|
|||||||
Reference in New Issue
Block a user