docs(qbzd): document the LAN-first bind and the token in the standalone README; posture line stops nagging gated setups

The public README never mentioned that the control API binds 0.0.0.0 by
default nor how to restrict it; the boot line also told users who had set
[server] token that anyone could control playback.

cc
This commit is contained in:
vicrodh committed 2026-09-13 03:36:51 -06:00
1 parent 08226a4ffd
commit 048fd37054
3 files changed
+61 -10

No files matched your search

+31 -9
View File
@@ -136,11 +136,18 @@ pub fn foreign_qbzd(addr: &str) -> String {
/// `warning:` — an open LAN renderer (Sonos/Chromecast posture) is the
/// intended default, the Origin shield already guards browsers, and this line
/// just orients the operator toward the two ways to restrict it further.
/// `addr` is the bound `ip:port`.
pub fn lan_posture_note(addr: &str) -> String {
format!(
"control plane listening on {addr} — anyone on your network can control playback (set [server] bind = \"127.0.0.1\" or [server] token in qbzd.toml to restrict)"
)
/// With `[server] token` set the API is already gated, so the line says that
/// instead of nagging. `addr` is the bound `ip:port`.
pub fn lan_posture_note(addr: &str, token_set: bool) -> String {
if token_set {
format!(
"control plane listening on {addr} — LAN clients must send the [server] token (Authorization: Bearer)"
)
} else {
format!(
"control plane listening on {addr} — anyone on your network can control playback (set [server] bind = \"127.0.0.1\" or [server] token in qbzd.toml to restrict)"
)
}
}
/// Version skew — daemon and CLI run different bin semvers (02 §1.6). A warning:
@@ -234,11 +241,12 @@ mod tests {
use super::*;
#[test]
fn lan_posture_note_renders_the_verbatim_copy() {
fn lan_posture_note_renders_the_verbatim_copy_when_the_api_is_open() {
// FB6 (successor to the old LAN-exposure warning) — one INFO line logged
// by the daemon at boot when the control API is NOT loopback-only. This
// test pins the exact wording so it cannot drift from the spec.
let rendered = lan_posture_note("0.0.0.0:6789");
// by the daemon at boot when the control API is NOT loopback-only and
// no token gates it. This test pins the exact wording so it cannot
// drift from the spec.
let rendered = lan_posture_note("0.0.0.0:6789", false);
assert!(
rendered.contains("control plane listening on 0.0.0.0:6789"),
"{rendered}"
@@ -253,4 +261,18 @@ mod tests {
"{rendered}"
);
}
#[test]
fn lan_posture_note_with_a_token_does_not_nag() {
let rendered = lan_posture_note("0.0.0.0:6789", true);
assert!(
rendered.contains("control plane listening on 0.0.0.0:6789"),
"{rendered}"
);
assert!(rendered.contains("[server] token"), "{rendered}");
assert!(
!rendered.contains("anyone on your network"),
"a gated API must not be described as open: {rendered}"
);
}
}
+9 -1
View File
@@ -89,7 +89,15 @@ pub async fn run(roots: ProfileRoots, cfg: QbzdConfig, warns: Vec<String>, orbit
// FB6: the default bind is now 0.0.0.0 — LAN-first posture (Sonos/
// Chromecast parity), not a misconfiguration. One INFO line, not a
// stderr warning; loopback binds stay silent.
log::info!("{}", crate::cli::copy::lan_posture_note(&bind_addr.to_string()));
let token_set = cfg
.server
.token
.as_deref()
.is_some_and(|t| !t.trim().is_empty());
log::info!(
"{}",
crate::cli::copy::lan_posture_note(&bind_addr.to_string(), token_set)
);
}
// 6.-9. compose stores + runtime + restore credentials + restore session.
+21
View File
@@ -82,6 +82,27 @@ budgets exclude active streaming buffers, decoders and offline downloads. The
policy applies live without reopening the output device.
## Network access
By default `qbzd run` listens on `0.0.0.0:8182`: any device on your network
can control playback, the queue and your playlists, the same way a Sonos or
Chromecast renderer is open on the LAN. Browsers are blocked by an Origin
check; other programs are not. The daemon logs one line about this at boot.
To restrict it, edit `~/.config/qbzd/qbzd.toml`:
```toml
[server]
bind = "127.0.0.1" # local clients only
# or keep the LAN bind and require a token on every request:
token = "choose-a-long-random-string"
```
With a token set, clients send `Authorization: Bearer <token>`; the `qbzd`
CLI on the same machine picks it up from `qbzd.toml` automatically (or from
`QBZD_TOKEN`). `qbzd setup` has the same field on its Network screen
("Access token").
## OpenRC and runit
Do not reuse the systemd unit on a non-systemd host. `qbzd` generates a