cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix: report degraded appliance status from every fallible serve step

Dami committed f0d2fd0
serve_command hand-copied the 'mark_degraded then return Err' publisher
block five times, and the QUIC idle-timeout conversion between the
preflight and endpoint-bind steps used a bare ?, so that failure exited
the daemon without ever publishing a degraded status.

All fallible steps between publisher creation and clean shutdown now
flow through one degrade_on_error helper, which closes the missed path
and makes the reporting contract impossible to forget for new steps.
commit
f0d2fd04e92f7c5a8599b9624de697aa8a1a0ad7
parent
c4ae230c5481b60e496d4cfcb39c65f71e538bb0
author
Dami <dami.etoile@gmail.com>
1 file changed, +61 -61
Mcrates/sigil-host/src/main.rs122+++++++++++++++---------------
M crates/sigil-host/src/main.rs +61 -61
@@ -837,6 +837,23 @@ fn validate_capture_probe_args(args: &CaptureProbeArgs, configured_framerate: u3
837837 Ok(())838838 }839839 840+/// Report the failing startup/shutdown step as a degraded appliance status841+/// before surfacing the error. Every fallible step between publisher creation842+/// and clean shutdown must flow through this helper so no failure class can843+/// exit the daemon without updating the published status.844+async fn degrade_on_error<T>(845+ publisher: Option<&appliance::RuntimePublisher>,846+ code: appliance::RuntimeErrorCode,847+ result: Result<T>,848+) -> Result<T> {849+ if result.is_err()850+ && let Some(publisher) = publisher851+ {852+ let _ = publisher.mark_degraded(code).await;853+ }854+ result855+}856+840857 async fn serve_command(args: ServeArgs) -> Result<()> {841858 validate_development_auth_bypass(args.dev_allow_unauthorized, args.max_runtime_seconds)?;842859 let configured_service = args.config.is_some();
@@ -874,17 +891,12 @@ async fn serve_command(args: ServeArgs) -> Result<()> {
874891 "warning=development-authorization-bypass-active; do not expose or ship this host"875892 );876893 }877- let authorization = match authorization_result {878- Ok(authorization) => authorization,879- Err(error) => {880- if let Some(publisher) = publisher.as_ref() {881- let _ = publisher882- .mark_degraded(appliance::RuntimeErrorCode::AuthorizationState)883- .await;884- }885- return Err(error);886- }887- };894+ let authorization = degrade_on_error(895+ publisher.as_ref(),896+ appliance::RuntimeErrorCode::AuthorizationState,897+ authorization_result,898+ )899+ .await?;888900 889901 let preflight_result: Result<(InputBackend, Option<PointerPositionTracker>)> = async {890902 let pointer_surface_dimensions = if config.source == VideoSource::TestPattern {
@@ -950,41 +962,36 @@ async fn serve_command(args: ServeArgs) -> Result<()> {
950962 Ok((input_backend, pointer_positions))951963 }952964 .await;953- let (input_backend, pointer_positions) = match preflight_result {954- Ok(preflight) => preflight,955- Err(error) => {956- if let Some(publisher) = publisher.as_ref() {957- let _ = publisher958- .mark_degraded(appliance::RuntimeErrorCode::Preflight)959- .await;960- }961- return Err(error);962- }963- };964-965- let idle_timeout = CONNECTION_IDLE_TIMEOUT966- .try_into()967- .context("converting bounded QUIC idle timeout")?;965+ let (input_backend, pointer_positions) = degrade_on_error(966+ publisher.as_ref(),967+ appliance::RuntimeErrorCode::Preflight,968+ preflight_result,969+ )970+ .await?;971+972+ let idle_timeout = degrade_on_error(973+ publisher.as_ref(),974+ appliance::RuntimeErrorCode::EndpointBind,975+ CONNECTION_IDLE_TIMEOUT976+ .try_into()977+ .context("converting bounded QUIC idle timeout"),978+ )979+ .await?;968980 let transport_config = QuicTransportConfig::builder()969981 .max_idle_timeout(Some(idle_timeout))970982 .keep_alive_interval(CONNECTION_KEEP_ALIVE_INTERVAL)971983 .build();972- let endpoint = match Endpoint::builder(presets::N0)973- .secret_key(secret)974- .transport_config(transport_config)975- .bind()976- .await977- {978- Ok(endpoint) => endpoint,979- Err(error) => {980- if let Some(publisher) = publisher.as_ref() {981- let _ = publisher982- .mark_degraded(appliance::RuntimeErrorCode::EndpointBind)983- .await;984- }985- return Err(error).context("binding iroh endpoint");986- }987- };984+ let endpoint = degrade_on_error(985+ publisher.as_ref(),986+ appliance::RuntimeErrorCode::EndpointBind,987+ Endpoint::builder(presets::N0)988+ .secret_key(secret)989+ .transport_config(transport_config)990+ .bind()991+ .await992+ .context("binding iroh endpoint"),993+ )994+ .await?;988995 let node_id = endpoint.id();989996 match tokio::time::timeout(Duration::from_secs(10), endpoint.online()).await {990997 Ok(()) => info!(%node_id, "iroh endpoint is online"),
@@ -1057,17 +1064,12 @@ async fn serve_command(args: ServeArgs) -> Result<()> {
10571064 tokio::time::sleep(Duration::from_secs(seconds)).await;10581065 info!(seconds, "maximum runtime reached");10591066 } else {1060- let signal = match wait_for_shutdown_signal().await {1061- Ok(signal) => signal,1062- Err(error) => {1063- if let Some(publisher) = publisher.as_ref() {1064- let _ = publisher1065- .mark_degraded(appliance::RuntimeErrorCode::ShutdownSignal)1066- .await;1067- }1068- return Err(error);1069- }1070- };1067+ let signal = degrade_on_error(1068+ publisher.as_ref(),1069+ appliance::RuntimeErrorCode::ShutdownSignal,1070+ wait_for_shutdown_signal().await,1071+ )1072+ .await?;10711073 info!(signal, "shutdown signal received");10721074 }10731075
@@ -1076,14 +1078,12 @@ async fn serve_command(args: ServeArgs) -> Result<()> {
10761078 {10771079 warn!(%error, "publishing stopping appliance status failed; continuing shutdown");10781080 }1079- if let Err(error) = router.shutdown().await {1080- if let Some(publisher) = publisher.as_ref() {1081- let _ = publisher1082- .mark_degraded(appliance::RuntimeErrorCode::RouterShutdown)1083- .await;1084- }1085- return Err(error).context("shutting down iroh router");1086- }1081+ degrade_on_error(1082+ publisher.as_ref(),1083+ appliance::RuntimeErrorCode::RouterShutdown,1084+ router.shutdown().await.context("shutting down iroh router"),1085+ )1086+ .await?;10871087 if let Some(publisher) = publisher10881088 && let Err(error) = publisher.finish_clean().await10891089 {