feat(host): unified host + native pairing over the management API
`serve --native` now runs the GameStream host AND the native punktfunk/1 (QUIC) host in ONE process, sharing a single NativePairing handle with the management API — so native pairing is operable from the web console instead of journalctl. - gamestream::serve gains a native_port: spawns crate::m3::serve in the same runtime and passes the shared NativePairing to mgmt::run. Validated live: one process binds both RTSP 48010 and QUIC 9777. - mgmt API: new `native` endpoints — GET /native/pair (status), POST /native/pair/arm (mint a fresh, time-limited PIN to DISPLAY), DELETE /native/pair (disarm), GET/DELETE /native/clients (list/unpair). GameStream-only hosts report enabled:false. OpenAPI regenerated (checked-in doc + drift test). - main.rs: serve --native / --native-port flags. The native host arms pairing on demand (the operator reads the PIN from the console; the SPAKE2 ceremony is host-shows-PIN). New mgmt + native_pairing tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -144,17 +144,30 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the GameStream control plane (blocks): mDNS advertisement, the nvhttp servers, and
|
||||
/// the management REST API.
|
||||
pub fn serve(mgmt: crate::mgmt::Options) -> Result<()> {
|
||||
/// Run the host (blocks): mDNS, the nvhttp servers, and the management REST API.
|
||||
/// `native_port = Some(p)` makes this the **unified** host — it also runs the native punktfunk/1
|
||||
/// QUIC server on `p` in the same process, sharing one [`crate::native_pairing`] handle with the
|
||||
/// management API so the web console can arm pairing and show the PIN. `None` = GameStream only
|
||||
/// (the mgmt API's native endpoints report `enabled: false`).
|
||||
pub fn serve(mgmt: crate::mgmt::Options, native_port: Option<u16>) -> Result<()> {
|
||||
let host = Host::detect()?;
|
||||
let identity = cert::ServerIdentity::load_or_create().context("host certificate")?;
|
||||
let state = Arc::new(AppState::new(host, identity));
|
||||
// The shared native-pairing handle exists only when we run the native host; it links the QUIC
|
||||
// ceremony and the management API.
|
||||
let native = match native_port {
|
||||
Some(_) => Some(Arc::new(
|
||||
crate::native_pairing::NativePairing::load_with(None, None, false)
|
||||
.context("native pairing store")?,
|
||||
)),
|
||||
None => None,
|
||||
};
|
||||
tracing::info!(
|
||||
hostname = %state.host.hostname,
|
||||
uniqueid = %state.host.uniqueid,
|
||||
ip = %state.host.local_ip,
|
||||
"punktfunk GameStream host (P1.1: serverinfo + pairing + mDNS)"
|
||||
native = native_port.is_some(),
|
||||
"punktfunk host (GameStream P1.1: serverinfo + pairing + mDNS)"
|
||||
);
|
||||
let rt = tokio::runtime::Runtime::new().context("build tokio runtime")?;
|
||||
rt.block_on(async move {
|
||||
@@ -163,7 +176,22 @@ pub fn serve(mgmt: crate::mgmt::Options) -> Result<()> {
|
||||
let _advert = mdns::advertise(&state.host).context("mDNS advertise")?;
|
||||
rtsp::spawn(state.clone()).context("start RTSP server")?;
|
||||
control::spawn(state.clone()).context("start ENet control server")?;
|
||||
tokio::try_join!(nvhttp::run(state.clone()), crate::mgmt::run(state, mgmt))?;
|
||||
match (native_port, native) {
|
||||
(Some(port), Some(np)) => {
|
||||
tracing::info!(port, "unified host: also serving native punktfunk/1 (QUIC)");
|
||||
tokio::try_join!(
|
||||
nvhttp::run(state.clone()),
|
||||
crate::mgmt::run(state.clone(), mgmt, Some(np.clone())),
|
||||
crate::m3::serve(crate::m3::native_serve_opts(port), np),
|
||||
)?;
|
||||
}
|
||||
_ => {
|
||||
tokio::try_join!(
|
||||
nvhttp::run(state.clone()),
|
||||
crate::mgmt::run(state, mgmt, None)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user