diff --git a/src/cloudbox.rs b/src/cloudbox.rs index d512d50..191e7ab 100644 --- a/src/cloudbox.rs +++ b/src/cloudbox.rs @@ -709,7 +709,13 @@ pub fn start_background() { let s = shared().lock().unwrap(); (s.last_used.elapsed().as_secs(), s.run.is_running(), s.rebuilding) }; - if running && !rebuilding && !is_busy() && !HELD_BY_PERSON.load(Ordering::SeqCst) && idle >= IDLE_SLEEP_SECS && !viewer_open() { + let awake = running && !rebuilding && !is_busy() && !HELD_BY_PERSON.load(Ordering::SeqCst) && !viewer_open(); + // A browser on the desktop was left open on purpose (a login page the person walks + // back to): it buys the box four times the idle time before parking. Freezing the + // container froze Chromium and websockify together, so a bot that "opened the + // browser" left the person a viewer that never showed anything. + let limit = if awake && browser_running() { IDLE_SLEEP_SECS * 4 } else { IDLE_SLEEP_SECS }; + if awake && idle >= limit && vnc_clients() == 0 { let _ = sleep_now(); } if !rebuilding { @@ -731,6 +737,20 @@ fn viewer_open() -> bool { VIEWER_OPEN.load(Ordering::SeqCst) } +/// Viewer tabs open on the box's desktop: every noVNC page keeps a websockify connection to +/// x11vnc on :5900 (:170C hex), so a live connection means the person is looking at it now. +fn vnc_clients() -> usize { + dk(&["exec", CONTAINER, "sh", "-c", + "awk '$4==\"01\" && ($2 ~ /:170C$/ || $3 ~ /:170C$/) {n++} END {print n+0}' /proc/net/tcp /proc/net/tcp6 2>/dev/null"]) + .ok() + .and_then(|s| parse_count(&s)) + .unwrap_or(0) +} + +fn parse_count(s: &str) -> Option { + s.trim().lines().last()?.trim().parse().ok() +} + // ---------- snapshots (the durable store) ---------- /// Save the bot's home folder to this Mac. Returns its size in bytes. @@ -1461,6 +1481,14 @@ pub fn shutdown() { mod tests { use super::*; + #[test] + fn vnc_probe_parses_only_a_count() { + assert_eq!(parse_count("3"), Some(3)); + assert_eq!(parse_count(" 2 \n"), Some(2)); + assert_eq!(parse_count(""), None); + assert_eq!(parse_count("Error response from daemon: container paused"), None); + } + #[test] fn secret_names_follow_the_original_rule() { assert!(valid_secret_name("CRM_API_TOKEN"));