Skip to main content

lean_ctx/
proxy_autostart.rs

1#[cfg(any(target_os = "macos", target_os = "linux"))]
2use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream};
3#[cfg(any(target_os = "macos", target_os = "linux"))]
4use std::path::PathBuf;
5#[cfg(any(target_os = "macos", target_os = "linux"))]
6use std::time::{Duration, Instant};
7
8#[cfg(target_os = "macos")]
9const PLIST_LABEL: &str = "com.leanctx.proxy";
10#[cfg(target_os = "linux")]
11const SYSTEMD_SERVICE: &str = "lean-ctx-proxy";
12
13/// PID the proxy on this instance's configured port reports in `/health`,
14/// on every platform. Used by the lifecycle commands to stop the proxy this
15/// instance owns — never by process name (#2176). The caller still gates the
16/// PID by command line and process identity before signalling it.
17pub(crate) fn recorded_proxy_pid() -> Option<u32> {
18    let port = crate::core::proxy_endpoint::default_port();
19    // The account's default port belongs to the default install. Another
20    // instance (directory override, sandbox HOME) owns a proxy only on a port
21    // it configured itself; otherwise `stop` from a scratch data dir stopped
22    // the user's real proxy (5 Nov 2026).
23    if !crate::daemon_autostart::controls_user_services()
24        && port == crate::core::proxy_endpoint::uid_based_port()
25    {
26        return None;
27    }
28    health_proxy_pid(port)
29}
30
31/// PID a lean-ctx proxy on `port` reports in `/health`. Only an answer that
32/// identifies itself as the lean-ctx proxy counts (#2176 review #5); the PID
33/// is still only a candidate — `ipc::process` gates it by command line,
34/// executable and user (the caller's uid / token SID) before any signal.
35#[must_use]
36pub fn health_proxy_pid(port: u16) -> Option<u32> {
37    let response = ureq::get(&format!("http://127.0.0.1:{port}/health"))
38        .config()
39        .timeout_global(Some(std::time::Duration::from_millis(500)))
40        .build()
41        .call()
42        .ok()?;
43    let body = response.into_body().read_to_string().ok()?;
44    proxy_pid_from_health(&body)
45}
46
47fn proxy_pid_from_health(body: &str) -> Option<u32> {
48    let health: serde_json::Value = serde_json::from_str(body).ok()?;
49    if health.get("status").and_then(serde_json::Value::as_str) != Some("ok")
50        || !health_identifies_lean_ctx_proxy(body)
51    {
52        return None;
53    }
54    let pid = u32::try_from(health.get("pid")?.as_u64()?).ok()?;
55    (pid > 0).then_some(pid)
56}
57
58fn health_identifies_lean_ctx_proxy(body: &str) -> bool {
59    serde_json::from_str::<serde_json::Value>(body)
60        .ok()
61        .and_then(|health| health.get("service")?.as_str().map(str::to_owned))
62        .is_some_and(|service| service == "lean-ctx-proxy")
63}
64
65/// The lean-ctx proxy of this user listening on `port`, gated like every
66/// other lifecycle target: `/health` names it, and the PID passes the
67/// command-line, executable, user and identity checks.
68#[must_use]
69pub fn proxy_on_port(port: u16) -> Option<crate::ipc::process::ServiceProcess> {
70    use crate::ipc::process::{RecordedPid, ServiceKind, ServiceSweep, find_service_processes};
71    let pid = health_proxy_pid(port)?;
72    find_service_processes(
73        &[RecordedPid::bare(pid)],
74        ServiceSweep::Only(ServiceKind::Proxy),
75    )
76    .into_iter()
77    .next()
78}
79
80#[cfg(any(target_os = "macos", target_os = "linux"))]
81fn port_is_open(port: u16) -> bool {
82    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
83    TcpStream::connect_timeout(&addr, Duration::from_millis(150)).is_ok()
84}
85
86/// Stop a proxy already owning `port` before a managed service is started.
87///
88/// Returns `false` when the listener cannot be positively identified as a
89/// lean-ctx proxy or when it does not release the port. Managed startup must
90/// fail closed in either case; otherwise launchd/systemd would enter a restart
91/// loop on `EADDRINUSE`.
92#[cfg(any(target_os = "macos", target_os = "linux"))]
93fn release_proxy_port(port: u16, quiet: bool) -> bool {
94    if !port_is_open(port) {
95        return true;
96    }
97
98    // The account's default port belongs to the default install: another
99    // instance never signals the proxy listening there, whatever `/health`
100    // reports (the same rule as `recorded_proxy_pid`, 6 Nov 2026).
101    if !crate::daemon_autostart::controls_user_services()
102        && port == crate::core::proxy_endpoint::uid_based_port()
103    {
104        if !quiet {
105            eprintln!(
106                "  Refusing managed proxy startup: port {port} belongs to the default install."
107            );
108        }
109        return false;
110    }
111
112    // #2176 review: the `/health` PID is signalled only while it is this
113    // user's lean-ctx proxy (command line, executable, identity); never this
114    // process or its ancestors. Anything else fails closed.
115    let Some(proxy) = proxy_on_port(port) else {
116        if !quiet {
117            eprintln!(
118                "  Refusing managed proxy startup: port {port} is occupied by an unidentified service."
119            );
120        }
121        return false;
122    };
123    let pid = proxy.pid;
124
125    proxy.signal(crate::ipc::process::StopSignal::Terminate);
126    let graceful_deadline = Instant::now() + Duration::from_secs(2);
127    while Instant::now() < graceful_deadline {
128        if !proxy.is_running() && !port_is_open(port) {
129            if !quiet {
130                eprintln!(
131                    "  Handed port {port} from standalone proxy PID {pid} to managed service."
132                );
133            }
134            return true;
135        }
136        std::thread::sleep(Duration::from_millis(50));
137    }
138
139    if proxy.is_running() {
140        proxy.signal(crate::ipc::process::StopSignal::Kill);
141    }
142    let force_deadline = Instant::now() + Duration::from_secs(1);
143    while Instant::now() < force_deadline {
144        if !port_is_open(port) {
145            if !quiet {
146                eprintln!(
147                    "  Handed port {port} from standalone proxy PID {pid} to managed service."
148                );
149            }
150            return true;
151        }
152        std::thread::sleep(Duration::from_millis(50));
153    }
154
155    if !quiet {
156        eprintln!("  Refusing managed proxy startup: port {port} was not released by PID {pid}.");
157    }
158    false
159}
160
161pub fn install(port: u16, quiet: bool) -> bool {
162    let binary = find_binary();
163    install_with_binary(&binary, port, quiet)
164}
165
166/// Install (or rewrite) and start the proxy autostart service. Refused for
167/// another instance (a directory override or a sandbox HOME): the service
168/// manager addresses the unit by name, so the stop, port hand-off, unit
169/// rewrite and `enable --now` would act on the default install's proxy
170/// (6 Nov 2026, see [`crate::daemon_autostart::controls_user_services`]).
171pub fn install_with_binary(binary: &str, port: u16, quiet: bool) -> bool {
172    if !crate::daemon_autostart::controls_user_services() {
173        if !quiet {
174            eprintln!("  {}", crate::daemon_autostart::not_this_instance_message());
175        }
176        return false;
177    }
178    if binary.is_empty() {
179        if !quiet {
180            tracing::error!("Cannot find lean-ctx binary for autostart");
181        }
182        return false;
183    }
184
185    #[cfg(target_os = "macos")]
186    {
187        install_launchagent(binary, port, quiet)
188    }
189
190    #[cfg(target_os = "linux")]
191    {
192        install_systemd(binary, port, quiet)
193    }
194
195    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
196    {
197        let _ = (binary, quiet);
198        println!("  Autostart not supported on this platform");
199        println!("  Run manually: lean-ctx proxy start --port={port}");
200        false
201    }
202}
203
204/// Stop the proxy's autostart service. A no-op for another instance (a
205/// directory override or a sandbox HOME): the service manager addresses the
206/// unit by name, and it belongs to the default install (5 Nov 2026, see
207/// [`crate::daemon_autostart::controls_user_services`]).
208pub fn stop() {
209    if !crate::daemon_autostart::controls_user_services() {
210        return;
211    }
212    #[cfg(target_os = "macos")]
213    {
214        let plist_path = launchagent_path();
215        if plist_path.exists() {
216            crate::core::launchd::bootout(PLIST_LABEL, &plist_path);
217        }
218    }
219
220    #[cfg(target_os = "linux")]
221    {
222        let _ = std::process::Command::new("systemctl")
223            .args(["--user", "stop", SYSTEMD_SERVICE])
224            .output();
225    }
226}
227
228pub fn start() -> bool {
229    start_on_port(crate::core::proxy_endpoint::default_port())
230}
231
232/// Start the installed manager after atomically taking ownership of `port`.
233/// This also handles the case where an IDE/dashboard spawned a standalone
234/// proxy while the manager was temporarily unloaded.
235pub fn start_on_port(port: u16) -> bool {
236    if !crate::daemon_autostart::controls_user_services() {
237        return false;
238    }
239    #[cfg(target_os = "macos")]
240    {
241        let plist_path = launchagent_path();
242        if plist_path.exists() {
243            crate::core::launchd::bootout(PLIST_LABEL, &plist_path);
244            if !release_proxy_port(port, false) {
245                return false;
246            }
247            return crate::core::launchd::bootstrap(PLIST_LABEL, &plist_path);
248        }
249        false
250    }
251
252    #[cfg(target_os = "linux")]
253    {
254        let _ = std::process::Command::new("systemctl")
255            .args(["--user", "stop", SYSTEMD_SERVICE])
256            .output();
257        if release_proxy_port(port, false) {
258            std::process::Command::new("systemctl")
259                .args(["--user", "start", SYSTEMD_SERVICE])
260                .status()
261                .is_ok_and(|status| status.success())
262        } else {
263            false
264        }
265    }
266
267    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
268    {
269        let _ = port;
270        false
271    }
272}
273
274/// Stop, disable and remove the proxy autostart service. A no-op for another
275/// instance, like [`install_with_binary`].
276pub fn uninstall(quiet: bool) {
277    if !crate::daemon_autostart::controls_user_services() {
278        if !quiet {
279            println!("  {}", crate::daemon_autostart::not_this_instance_message());
280        }
281        return;
282    }
283    #[cfg(target_os = "macos")]
284    uninstall_launchagent(quiet);
285
286    #[cfg(target_os = "linux")]
287    uninstall_systemd(quiet);
288}
289
290/// Whether this platform has a proxy-autostart backend (LaunchAgent on macOS,
291/// systemd user service on Linux). Windows and other targets have none, so a
292/// missing autostart there must not be treated as a failure by `doctor` (#416).
293pub fn is_supported() -> bool {
294    cfg!(any(target_os = "macos", target_os = "linux"))
295}
296
297/// Returns true if the proxy autostart is installed (plist/systemd service file exists).
298/// Always false for another instance: the service is not its own.
299pub fn is_installed() -> bool {
300    if !crate::daemon_autostart::controls_user_services() {
301        return false;
302    }
303    #[cfg(target_os = "macos")]
304    {
305        launchagent_path().exists()
306    }
307    #[cfg(target_os = "linux")]
308    {
309        systemd_path().exists()
310    }
311    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
312    {
313        false
314    }
315}
316
317/// Returns true when the installed manager is actively responsible for the proxy.
318/// Callers must not spawn a second foreground proxy while this is true.
319/// Always false for another instance (no `systemctl`/`launchctl` query).
320pub fn is_loaded() -> bool {
321    if !crate::daemon_autostart::controls_user_services() {
322        return false;
323    }
324    #[cfg(target_os = "macos")]
325    {
326        is_installed() && crate::core::launchd::is_loaded(PLIST_LABEL)
327    }
328    #[cfg(target_os = "linux")]
329    {
330        is_installed()
331            && std::process::Command::new("systemctl")
332                .args(["--user", "is-active", "--quiet", SYSTEMD_SERVICE])
333                .status()
334                .is_ok_and(|status| status.success())
335    }
336    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
337    {
338        false
339    }
340}
341
342pub fn status() {
343    if !crate::daemon_autostart::controls_user_services() {
344        println!("  {}", crate::daemon_autostart::not_this_instance_message());
345        return;
346    }
347    #[cfg(target_os = "macos")]
348    {
349        let plist_path = launchagent_path();
350        if plist_path.exists() {
351            println!("  LaunchAgent: installed at {}", plist_path.display());
352            if crate::core::launchd::is_loaded(PLIST_LABEL) {
353                println!("  Status: loaded");
354            } else {
355                println!("  Status: not loaded (run: lean-ctx proxy start)");
356            }
357        } else {
358            println!("  LaunchAgent: not installed");
359        }
360    }
361
362    #[cfg(target_os = "linux")]
363    {
364        let service_path = systemd_path();
365        if service_path.exists() {
366            println!("  systemd user service: installed");
367            let output = std::process::Command::new("systemctl")
368                .args(["--user", "is-active", SYSTEMD_SERVICE])
369                .output();
370            match output {
371                Ok(o) => {
372                    let state = String::from_utf8_lossy(&o.stdout).trim().to_string();
373                    println!("  Status: {state}");
374                }
375                Err(_) => println!("  Status: unknown"),
376            }
377        } else {
378            println!("  systemd service: not installed");
379        }
380    }
381
382    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
383    {
384        println!("  Autostart not available on this platform");
385    }
386}
387
388#[cfg(target_os = "macos")]
389fn launchagent_path() -> PathBuf {
390    dirs::home_dir()
391        .unwrap_or_else(|| PathBuf::from("/tmp"))
392        .join("Library/LaunchAgents")
393        .join(format!("{PLIST_LABEL}.plist"))
394}
395
396#[cfg(target_os = "macos")]
397fn install_launchagent(binary: &str, port: u16, quiet: bool) -> bool {
398    let plist_dir = dirs::home_dir()
399        .unwrap_or_else(|| PathBuf::from("/tmp"))
400        .join("Library/LaunchAgents");
401    let _ = std::fs::create_dir_all(&plist_dir);
402
403    let plist_path = plist_dir.join(format!("{PLIST_LABEL}.plist"));
404    // Stop the registered job first, then take over any standalone proxy an
405    // IDE/dashboard may have spawned while launchd was unloaded. Starting the
406    // new job with an occupied port causes an unconditional KeepAlive loop.
407    crate::core::launchd::bootout(PLIST_LABEL, &plist_path);
408    if !release_proxy_port(port, quiet) {
409        return false;
410    }
411    // GH #439: proxy logs are STATE — resolve through the typed dir so a
412    // post-split install writes to $XDG_STATE_HOME/lean-ctx/logs instead of a
413    // re-created ~/.lean-ctx. Legacy single-dir installs still resolve here.
414    let log_dir = crate::core::paths::state_dir()
415        .unwrap_or_else(|_| std::env::temp_dir().join("lean-ctx"))
416        .join("logs");
417    let _ = std::fs::create_dir_all(&log_dir);
418
419    // #356: wrap the launchd invocation in a deny-~/Documents seatbelt sandbox
420    // so the proxy (a TCC-standalone process) can never trip the privacy prompt.
421    let port_arg = format!("--port={port}");
422    let program_args = crate::core::tcc_guard_sandbox::program_args_xml(
423        &crate::core::tcc_guard_sandbox::wrap_launchd_args(binary, &["proxy", "start", &port_arg]),
424        "        ",
425    );
426
427    // #449: pin the directory layout. A launchd-spawned proxy inherits only
428    // launchd's minimal environment (no HOME, no XDG vars), so it resolves a
429    // *different* config/data dir than the CLI that installed it — it never sees
430    // the user's config.toml edits (live-upstream reload reads nothing) and
431    // derives a mismatched session token. Bake the exact dirs this CLI resolves
432    // into the plist so the managed proxy always agrees with the CLI.
433    let env_vars = crate::core::tcc_guard_sandbox::pinned_layout_env_xml();
434
435    let plist = format!(
436        r#"<?xml version="1.0" encoding="UTF-8"?>
437<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
438<plist version="1.0">
439<dict>
440    <key>Label</key>
441    <string>{PLIST_LABEL}</string>
442    <key>ProgramArguments</key>
443    <array>
444{program_args}
445    </array>
446{env_vars}    <key>RunAtLoad</key>
447    <true/>
448    <key>KeepAlive</key>
449    <true/>
450    <key>StandardOutPath</key>
451    <string>{stdout}</string>
452    <key>StandardErrorPath</key>
453    <string>{stderr}</string>
454</dict>
455</plist>"#,
456        stdout = log_dir.join("proxy.stdout.log").display(),
457        stderr = log_dir.join("proxy.stderr.log").display(),
458    );
459
460    let _ = std::fs::write(&plist_path, &plist);
461
462    let ok = crate::core::launchd::bootstrap(PLIST_LABEL, &plist_path);
463
464    if !quiet {
465        if ok {
466            println!("  Installed LaunchAgent: {}", plist_path.display());
467            println!("  Proxy will start on login and restart if stopped");
468        } else {
469            println!("  Created LaunchAgent at {}", plist_path.display());
470            println!("  Load reported a problem; check: launchctl print {PLIST_LABEL}");
471        }
472    }
473    ok
474}
475
476#[cfg(target_os = "macos")]
477fn uninstall_launchagent(quiet: bool) {
478    let plist_path = launchagent_path();
479    if !plist_path.exists() {
480        if !quiet {
481            println!("  LaunchAgent not installed, nothing to remove");
482        }
483        return;
484    }
485
486    crate::core::launchd::bootout(PLIST_LABEL, &plist_path);
487
488    let _ = std::fs::remove_file(&plist_path);
489    if !quiet {
490        println!("  Removed LaunchAgent: {}", plist_path.display());
491    }
492}
493
494#[cfg(target_os = "linux")]
495fn systemd_path() -> PathBuf {
496    dirs::home_dir()
497        .unwrap_or_else(|| PathBuf::from("/tmp"))
498        .join(".config/systemd/user")
499        .join(format!("{SYSTEMD_SERVICE}.service"))
500}
501
502#[cfg(target_os = "linux")]
503fn install_systemd(binary: &str, port: u16, quiet: bool) -> bool {
504    let service_dir = dirs::home_dir()
505        .unwrap_or_else(|| PathBuf::from("/tmp"))
506        .join(".config/systemd/user");
507    let _ = std::fs::create_dir_all(&service_dir);
508
509    let service_path = service_dir.join(format!("{SYSTEMD_SERVICE}.service"));
510
511    let _ = std::process::Command::new("systemctl")
512        .args(["--user", "stop", SYSTEMD_SERVICE])
513        .output();
514    if !release_proxy_port(port, quiet) {
515        return false;
516    }
517
518    let unit = format!(
519        r"[Unit]
520Description=lean-ctx API Proxy
521After=network.target
522StartLimitIntervalSec=300
523StartLimitBurst=5
524
525[Service]
526Type=simple
527ExecStart={binary} proxy start --port={port}
528Restart=on-failure
529RestartSec=5
530StandardOutput=journal
531StandardError=journal
532Environment=RUST_LOG=info
533
534[Install]
535WantedBy=default.target
536"
537    );
538
539    let _ = std::fs::write(&service_path, &unit);
540
541    let _ = std::process::Command::new("systemctl")
542        .args(["--user", "daemon-reload"])
543        .output();
544
545    let result = std::process::Command::new("systemctl")
546        .args(["--user", "enable", "--now", SYSTEMD_SERVICE])
547        .output();
548
549    if !quiet {
550        match &result {
551            Ok(o) if o.status.success() => {
552                println!("  Installed systemd user service: {SYSTEMD_SERVICE}");
553                println!("  Proxy will start on login and restart if stopped");
554            }
555            Ok(o) => {
556                let err = String::from_utf8_lossy(&o.stderr);
557                println!("  Created service file but enable failed: {err}");
558            }
559            Err(e) => {
560                println!("  Created service file at {}", service_path.display());
561                println!("  Could not enable: {e}");
562            }
563        }
564    }
565    result.is_ok_and(|output| output.status.success())
566}
567
568#[cfg(target_os = "linux")]
569fn uninstall_systemd(quiet: bool) {
570    let service_path = systemd_path();
571    if !service_path.exists() {
572        if !quiet {
573            println!("  systemd service not installed, nothing to remove");
574        }
575        return;
576    }
577
578    let _ = std::process::Command::new("systemctl")
579        .args(["--user", "stop", SYSTEMD_SERVICE])
580        .output();
581    let _ = std::process::Command::new("systemctl")
582        .args(["--user", "disable", SYSTEMD_SERVICE])
583        .output();
584    let _ = std::fs::remove_file(&service_path);
585    let _ = std::process::Command::new("systemctl")
586        .args(["--user", "daemon-reload"])
587        .output();
588
589    if !quiet {
590        println!("  Removed systemd service: {SYSTEMD_SERVICE}");
591    }
592}
593
594pub fn find_binary() -> String {
595    crate::core::portable_binary::resolve_portable_binary()
596}
597
598#[cfg(test)]
599mod tests {
600    use super::{health_identifies_lean_ctx_proxy, proxy_pid_from_health};
601    #[cfg(any(target_os = "macos", target_os = "linux"))]
602    use super::{port_is_open, release_proxy_port};
603    #[cfg(any(target_os = "macos", target_os = "linux"))]
604    use std::io::{Read, Write};
605    #[cfg(any(target_os = "macos", target_os = "linux"))]
606    use std::net::TcpListener;
607
608    #[test]
609    fn health_pid_parser_accepts_only_well_formed_health() {
610        assert_eq!(
611            proxy_pid_from_health(r#"{"status":"ok","service":"lean-ctx-proxy","pid":4242}"#),
612            Some(4242)
613        );
614        // #2176 review #5: an answer that does not identify the lean-ctx
615        // proxy names no candidate.
616        assert_eq!(proxy_pid_from_health(r#"{"status":"ok","pid":4242}"#), None);
617        assert_eq!(
618            proxy_pid_from_health(r#"{"status":"ok","service":"other","pid":4242}"#),
619            None
620        );
621        assert_eq!(
622            proxy_pid_from_health(r#"{"status":"busy","service":"lean-ctx-proxy","pid":4242}"#),
623            None
624        );
625        assert_eq!(
626            proxy_pid_from_health(r#"{"status":"ok","service":"lean-ctx-proxy","pid":0}"#),
627            None
628        );
629        assert_eq!(proxy_pid_from_health(r#"{"status":"ok"}"#), None);
630        assert_eq!(proxy_pid_from_health("not json"), None);
631        assert!(health_identifies_lean_ctx_proxy(
632            r#"{"status":"ok","service":"lean-ctx-proxy","pid":4242}"#
633        ));
634        assert!(!health_identifies_lean_ctx_proxy(
635            r#"{"status":"ok","pid":4242}"#
636        ));
637    }
638
639    #[test]
640    #[cfg(any(target_os = "macos", target_os = "linux"))]
641    fn managed_handoff_fails_closed_for_unidentified_listener() {
642        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
643        let port = listener.local_addr().unwrap().port();
644        let server = std::thread::spawn(move || {
645            for _ in 0..2 {
646                let (mut stream, _) = listener.accept().unwrap();
647                stream
648                    .set_read_timeout(Some(std::time::Duration::from_secs(1)))
649                    .unwrap();
650                let mut request = [0_u8; 512];
651                if stream.read(&mut request).unwrap_or(0) > 0 {
652                    let body = r#"{"status":"ok"}"#;
653                    write!(
654                        stream,
655                        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
656                        body.len()
657                    )
658                    .unwrap();
659                }
660            }
661        });
662
663        assert!(!release_proxy_port(port, true));
664        server.join().unwrap();
665    }
666
667    #[cfg(unix)]
668    struct ChildGuard(std::process::Child);
669
670    #[cfg(unix)]
671    impl Drop for ChildGuard {
672        fn drop(&mut self) {
673            let _ = self.0.kill();
674            let _ = self.0.wait();
675        }
676    }
677
678    /// #2176 review: a listener that claims to be the lean-ctx proxy in
679    /// `/health` but is not a lean-ctx `proxy start` process (here the test
680    /// binary) is never signalled; the hand-off fails closed.
681    #[test]
682    #[cfg(unix)]
683    fn managed_handoff_never_signals_an_unverified_health_pid() {
684        let reservation = TcpListener::bind("127.0.0.1:0").unwrap();
685        let port = reservation.local_addr().unwrap().port();
686        drop(reservation);
687
688        let child = std::process::Command::new(std::env::current_exe().unwrap())
689            .args([
690                "--ignored",
691                "--exact",
692                "proxy_autostart::tests::managed_handoff_test_child",
693                "--nocapture",
694            ])
695            .env("LEAN_CTX_HANDOFF_TEST_PORT", port.to_string())
696            .spawn()
697            .unwrap();
698        let mut child = ChildGuard(child);
699
700        let ready_deadline = std::time::Instant::now() + std::time::Duration::from_secs(3);
701        while !port_is_open(port) && std::time::Instant::now() < ready_deadline {
702            std::thread::sleep(std::time::Duration::from_millis(25));
703        }
704        assert!(port_is_open(port), "test proxy did not bind port {port}");
705        assert!(!release_proxy_port(port, true));
706        assert!(
707            child.0.try_wait().unwrap().is_none(),
708            "an unverified /health PID must never be signalled"
709        );
710        assert!(port_is_open(port));
711    }
712
713    #[test]
714    #[ignore = "helper process for managed_handoff_never_signals_an_unverified_health_pid"]
715    #[cfg(unix)]
716    fn managed_handoff_test_child() {
717        let port: u16 = std::env::var("LEAN_CTX_HANDOFF_TEST_PORT")
718            .unwrap()
719            .parse()
720            .unwrap();
721        let listener = TcpListener::bind(("127.0.0.1", port)).unwrap();
722        loop {
723            let (mut stream, _) = listener.accept().unwrap();
724            stream
725                .set_read_timeout(Some(std::time::Duration::from_secs(1)))
726                .unwrap();
727            let mut request = [0_u8; 512];
728            if stream.read(&mut request).unwrap_or(0) > 0 {
729                let body = format!(
730                    r#"{{"status":"ok","service":"lean-ctx-proxy","pid":{}}}"#,
731                    std::process::id()
732                );
733                write!(
734                    stream,
735                    "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
736                    body.len()
737                )
738                .unwrap();
739            }
740        }
741    }
742}