Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/management.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Console management integration

The opt-in `/v1` HTTP API is independent of RustDesk's client protocol ports. Set `RD_MANAGEMENT_BIND` to a private socket address, `RD_MANAGEMENT_TOKEN` to an independent random Bearer credential of at least 32 characters, and `RD_MANAGEMENT_DIR` to a persistent directory for management state. Do not publish this listener to the client-facing network.
The opt-in `/v1` HTTP API is independent of RustDesk's client protocol ports and is served by hbbs only. Set `RD_MANAGEMENT_BIND` to a private socket address, `RD_MANAGEMENT_TOKEN` to an independent random Bearer credential of at least 32 characters, and `RD_MANAGEMENT_DIR` to a persistent directory for management state. Do not publish this listener to the client-facing network.

hbbs exposes `GET /v1/status`, `GET /v1/config`, `PUT /v1/config`, `GET /v1/logs`, `GET /v1/peers`, `GET/PUT /v1/bans`, `POST /v1/restart`, `GET /v1/events` (SSE), `GET /v1/relays`, `GET /v1/relays/:id/sessions`, `DELETE /v1/relays/:id/sessions/:uuid`, `GET/PUT /v1/relays/:id/config`, `GET /v1/relays/:id/logs`, `POST /v1/relays/:id/restart` and `GET /v1/control` (WebSocket upgrade). hbbr additionally exposes `GET /v1/sessions`, `DELETE /v1/sessions/:uuid` and `POST /v1/restart`; it has no peers endpoint. All endpoints require authorization. API version is 1. Session closure is asynchronous (`202`, `state: closing`). IP bans check both relay endpoints. Reported target IDs are not authenticated source identities.
hbbs exposes `GET /v1/status`, `GET /v1/config`, `PUT /v1/config`, `GET /v1/logs`, `GET /v1/peers`, `GET/PUT /v1/bans`, `POST /v1/restart`, `GET /v1/events` (SSE), `GET /v1/relays`, `GET /v1/relays/:id/sessions`, `DELETE /v1/relays/:id/sessions/:uuid`, `GET/PUT /v1/relays/:id/config`, `GET /v1/relays/:id/logs`, `POST /v1/relays/:id/restart` and `GET /v1/control` (WebSocket upgrade). hbbr exposes no HTTP listener; it is managed entirely through the hbbs control channel. All endpoints require authorization. API version is 1. Session closure is asynchronous (`202`, `state: closing`). IP bans check both relay endpoints. Reported target IDs are not authenticated source identities.

Relay aggregation: when `RD_HBBS_API_URL` is set on hbbr, it opens an outbound WebSocket to the hbbs management API (`/v1/control`), registers with its relay identity and reports heartbeats, session lifecycle events and session snapshots. hbbs fans the active ban policy out to every registered relay (on update and on registration), aggregates relay sessions behind `GET /v1/relays/:id/sessions`, forwards disconnect requests through `DELETE /v1/relays/:id/sessions/:uuid`, proxies relay-scoped management requests (`GET/PUT /v1/relays/:id/config`, `GET /v1/relays/:id/logs`, `POST /v1/relays/:id/restart`) over the control channel using request ids and HTTP-equivalent responses (a relay that stays silent fails the proxy request after 10 seconds), and folds each relay address reported at registration into the live relay server list (static `relay-servers` plus registered relays; dynamic `relay-servers` updates replace the static baseline and keep the registered addresses). hbbr identity comes from `RD_RELAY_ID` (or `HOSTNAME`); `RD_RELAY_ADDRESS` optionally advertises the address clients use to reach the relay and is folded into the live relay server list only when set — the relay id (often a container hostname) is never broadcast as an address. A re-registering relay replaces its previous connection, and registrations silent for 45 seconds are dropped. `GET /v1/events` streams aggregated events (`peer_online`, `peer_offline`, `session_opened`, `session_closed`, `relay_registered`, `relay_disconnected`, `bans_replaced`) as server-sent events with a 15 second keep-alive.

Expand All @@ -12,8 +12,8 @@ Files are service-specific: `hbbs-config.json`, `hbbr-config.json`, `hbbs-bans.j

`GET /v1/logs?tail=N` returns up to 2000 recent in-memory log lines (default 200) captured from the standard log format; it requires no container log access. `GET /v1/relays/:id/logs` proxies the same read for a registered relay.

`POST /v1/restart` (directly on each service and proxied per relay) asks the supervisor to bring the service back: the process exits shortly after the response is flushed (`202`, `state: restarting`), and the persisted configuration including pending-restart entries is applied on the next startup. Relay restarts are fire-and-forget: the relay acknowledges, exits and drops off the control channel, and registers again once it is back.
`POST /v1/restart` (on hbbs and proxied per relay) asks the supervisor to bring the service back: the process exits shortly after the response is flushed (`202`, `state: restarting`), and the persisted configuration including pending-restart entries is applied on the next startup. Relay restarts are fire-and-forget: the relay acknowledges, exits and drops off the control channel, and registers again once it is back.

Managed settings override startup inputs. Schema and validation are provided by `management-schema.json`. The API returns key placeholders, never the private key. It exposes only `id_ed25519.pub` as the public key.

The published `databk/rustdesk-server` image ships hbbs and hbbr with the management API compiled in; deploy each binary as its own container (for example through the Console repository's Compose `server` profile) and set `RD_MANAGEMENT_BIND` to enable the listener. The Console repository supplies the combined Compose deployment, permission enforcement, UI, integration workflow and detailed lifecycle documentation in `docs/server-management.md`. Existing binaries remain compatible with deployments that do not enable management.
The published `databk/rustdesk-server` image ships hbbs and hbbr with the management API compiled in; deploy each binary as its own container (for example through the Console repository's Compose `server` profile), set `RD_MANAGEMENT_BIND` on hbbs to enable its listener, and set `RD_HBBS_API_URL` on hbbr so it registers on the control channel. The Console repository supplies the combined Compose deployment, permission enforcement, UI, integration workflow and detailed lifecycle documentation in `docs/server-management.md`. Existing binaries remain compatible with deployments that do not enable management.
19 changes: 0 additions & 19 deletions src/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@

// Registered relay control channels (hbbs only). Each entry describes one
// live reverse connection established by an hbbr instance.
struct RelayConnection {

Check warning on line 89 in src/management.rs

View workflow job for this annotation

GitHub Actions / Compile Check

struct `RelayConnection` is never constructed
id: String,
info: Value,
address: Option<String>,
Expand All @@ -98,9 +98,9 @@
tx: mpsc::UnboundedSender<Message>,
}

static RELAYS: Lazy<RwLock<HashMap<String, RelayConnection>>> =

Check warning on line 101 in src/management.rs

View workflow job for this annotation

GitHub Actions / Compile Check

static `RELAYS` is never used
Lazy::new(|| RwLock::new(HashMap::new()));
static RELAY_GENERATION: AtomicU64 = AtomicU64::new(0);

Check warning on line 103 in src/management.rs

View workflow job for this annotation

GitHub Actions / Compile Check

static `RELAY_GENERATION` is never used

// Registered by hbbs to fold discovered relay addresses back into the live
// relay server list (static configuration plus registered relays).
Expand All @@ -113,7 +113,7 @@
}

// Addresses reported by registered relays (used to merge the live relay list).
pub fn registered_relay_addresses() -> Vec<String> {

Check warning on line 116 in src/management.rs

View workflow job for this annotation

GitHub Actions / Run Tests

function `registered_relay_addresses` is never used

Check warning on line 116 in src/management.rs

View workflow job for this annotation

GitHub Actions / Compile Check

function `registered_relay_addresses` is never used
RELAYS
.read()
.unwrap()
Expand All @@ -122,7 +122,7 @@
.collect()
}

fn sync_relay_list() {

Check warning on line 125 in src/management.rs

View workflow job for this annotation

GitHub Actions / Compile Check

function `sync_relay_list` is never used
let addresses: Vec<String> = RELAYS
.read()
.unwrap()
Expand All @@ -134,7 +134,7 @@
}
}

fn relay_command(relay_id: &str, payload: Value) -> ResultType<()> {

Check warning on line 137 in src/management.rs

View workflow job for this annotation

GitHub Actions / Compile Check

function `relay_command` is never used
let relays = RELAYS.read().unwrap();
let relay = relays
.get(relay_id)
Expand All @@ -146,7 +146,7 @@
Ok(())
}

fn fanout_bans() {

Check warning on line 149 in src/management.rs

View workflow job for this annotation

GitHub Actions / Compile Check

function `fanout_bans` is never used
let payload = json!({"type": "bans", "bans": *BANS.read().unwrap()});
let text = payload.to_string();
for relay in RELAYS.read().unwrap().values() {
Expand All @@ -157,12 +157,12 @@
// Pending request-response exchanges with registered relays: hbbs HTTP
// handlers await the completion channel while the relay socket loop resolves
// it from the "response" control message.
struct PendingRelayRequest {

Check warning on line 160 in src/management.rs

View workflow job for this annotation

GitHub Actions / Compile Check

struct `PendingRelayRequest` is never constructed
relay: String,
responder: oneshot::Sender<Value>,
}

static RELAY_REQUESTS: Lazy<std::sync::Mutex<HashMap<u64, PendingRelayRequest>>> =

Check warning on line 165 in src/management.rs

View workflow job for this annotation

GitHub Actions / Compile Check

static `RELAY_REQUESTS` is never used
Lazy::new(|| std::sync::Mutex::new(HashMap::new()));
static RELAY_REQUEST_SEQUENCE: AtomicU64 = AtomicU64::new(1);
const RELAY_REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
Expand Down Expand Up @@ -740,23 +740,6 @@
Json(json!({"data":PEERS.read().unwrap().clone()})).into_response()
}

async fn sessions(Extension(state): Extension<ApiState>) -> Response {
if state.service != "hbbr" {
return StatusCode::NOT_FOUND.into_response();
}
Json(json!({"data":sessions_snapshot()})).into_response()
}

async fn disconnect(Extension(state): Extension<ApiState>, Path(uuid): Path<String>) -> Response {
if state.service != "hbbr" {
return StatusCode::NOT_FOUND.into_response();
}
if disconnect_session(&uuid) {
return (StatusCode::ACCEPTED, Json(json!({"state":"closing"}))).into_response();
}
StatusCode::NOT_FOUND.into_response()
}

async fn bans() -> Json<Bans> {
Json(BANS.read().unwrap().clone())
}
Expand Down Expand Up @@ -1262,8 +1245,6 @@
.route("/v1/config", get(config).put(replace_config))
.route("/v1/logs", get(logs))
.route("/v1/peers", get(peers))
.route("/v1/sessions", get(sessions))
.route("/v1/sessions/:uuid", axum::routing::delete(disconnect))
.route("/v1/bans", get(bans).put(replace_bans))
.route("/v1/restart", axum::routing::post(restart))
.route("/v1/events", get(events))
Expand Down
5 changes: 2 additions & 3 deletions src/relay_server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,9 @@ pub async fn start_with_bind(bind_addr: Option<IpAddr>, port: &str, key: &str) -
// of being reported as pending restart.
crate::management::set_dynamic_apply(Box::new(command::apply_dynamic));
// The managed state backs the control channel's proxied
// configuration requests even when no local listener is
// bound, so initialize it unconditionally.
// configuration requests; hbbr never exposes a local
// management listener and is aggregated through hbbs only.
crate::management::init_managed_state("hbbr", port)?;
crate::management::start("hbbr", port)?;
let control_url = std::env::var("RD_HBBS_API_URL").unwrap_or_default();
if !control_url.is_empty() {
let token = std::env::var("RD_MANAGEMENT_TOKEN").unwrap_or_default();
Expand Down
Loading