@@ -51,6 +51,7 @@ struct App {
5151 copy_reset_time : Option < Instant > ,
5252 pty_cmd_tx : Option < tokio:: sync:: mpsc:: UnboundedSender < PtyCommand > > ,
5353 cloudflared_pid : Arc < AtomicU32 > ,
54+ relay_server_pid : Arc < AtomicU32 > ,
5455}
5556
5657impl App {
@@ -65,6 +66,7 @@ impl App {
6566 copy_reset_time : None ,
6667 pty_cmd_tx : None ,
6768 cloudflared_pid : Arc :: new ( AtomicU32 :: new ( 0 ) ) ,
69+ relay_server_pid : Arc :: new ( AtomicU32 :: new ( 0 ) ) ,
6870 }
6971 }
7072
@@ -130,6 +132,11 @@ impl App {
130132 info ! ( "Killing cloudflared (pid {})" , pid) ;
131133 unsafe { libc:: kill ( pid as i32 , libc:: SIGTERM ) ; }
132134 }
135+ let relay_pid = self . relay_server_pid . load ( Ordering :: Relaxed ) ;
136+ if relay_pid != 0 {
137+ info ! ( "Killing relay-server (pid {})" , relay_pid) ;
138+ unsafe { libc:: kill ( relay_pid as i32 , libc:: SIGTERM ) ; }
139+ }
133140 std:: process:: exit ( 0 ) ;
134141 }
135142 _ => {
@@ -312,9 +319,75 @@ fn main() {
312319 // Create pty command channel (sender stays in main thread)
313320 let ( pty_cmd_tx, pty_cmd_rx) = tokio:: sync:: mpsc:: unbounded_channel :: < PtyCommand > ( ) ;
314321
322+ // Spawn relay-server as a child process
323+ let relay_server_pid = Arc :: new ( AtomicU32 :: new ( 0 ) ) ;
324+ {
325+ // Find relay-server binary: next to our binary, or in ~/.terminal-remote/bin/
326+ let relay_bin = std:: env:: current_exe ( )
327+ . ok ( )
328+ . and_then ( |p| p. parent ( ) . map ( |d| d. join ( "relay-server" ) ) )
329+ . filter ( |p| p. exists ( ) )
330+ . or_else ( || {
331+ let home = std:: env:: var ( "HOME" ) . ok ( ) ?;
332+ let p = std:: path:: PathBuf :: from ( home) . join ( ".terminal-remote/bin/relay-server" ) ;
333+ p. exists ( ) . then_some ( p)
334+ } ) ;
335+
336+ match relay_bin {
337+ Some ( bin) => {
338+ info ! ( "Starting relay-server from: {}" , bin. display( ) ) ;
339+ match Command :: new ( & bin) . spawn ( ) {
340+ Ok ( child) => {
341+ let pid = child. id ( ) ;
342+ info ! ( "relay-server started (pid {})" , pid) ;
343+ relay_server_pid. store ( pid, Ordering :: Relaxed ) ;
344+ }
345+ Err ( e) => {
346+ error ! ( "Failed to spawn relay-server: {}" , e) ;
347+ }
348+ }
349+ }
350+ None => {
351+ warn ! ( "relay-server binary not found, assuming it is already running" ) ;
352+ }
353+ }
354+ }
355+
315356 // Shared PID for killing cloudflared on quit
316357 let cloudflared_pid = Arc :: new ( AtomicU32 :: new ( 0 ) ) ;
317358
359+ // Install signal handler so relay-server and cloudflared are killed
360+ // even if mac-client is terminated via SIGTERM/SIGINT (e.g. launchctl stop)
361+ {
362+ let relay_pid = relay_server_pid. clone ( ) ;
363+ let cf_pid = cloudflared_pid. clone ( ) ;
364+ unsafe {
365+ let cleanup = move || {
366+ let rpid = relay_pid. load ( Ordering :: Relaxed ) ;
367+ if rpid != 0 {
368+ libc:: kill ( rpid as i32 , libc:: SIGTERM ) ;
369+ }
370+ let cpid = cf_pid. load ( Ordering :: Relaxed ) ;
371+ if cpid != 0 {
372+ libc:: kill ( cpid as i32 , libc:: SIGTERM ) ;
373+ }
374+ libc:: _exit ( 0 ) ;
375+ } ;
376+ // Store in a static so the closure lives forever
377+ static mut CLEANUP : Option < Box < dyn Fn ( ) > > = None ;
378+ CLEANUP = Some ( Box :: new ( cleanup) ) ;
379+ extern "C" fn handler ( _sig : libc:: c_int ) {
380+ unsafe {
381+ if let Some ( ref f) = CLEANUP {
382+ f ( ) ;
383+ }
384+ }
385+ }
386+ libc:: signal ( libc:: SIGTERM , handler as libc:: sighandler_t ) ;
387+ libc:: signal ( libc:: SIGINT , handler as libc:: sighandler_t ) ;
388+ }
389+ }
390+
318391 // Spawn background thread with Tokio runtime
319392 let ui_tx_bg = ui_tx. clone ( ) ;
320393 let cloudflared_pid_bg = cloudflared_pid. clone ( ) ;
@@ -413,6 +486,7 @@ fn main() {
413486 app. bg_handle = Some ( bg_handle) ;
414487 app. pty_cmd_tx = Some ( pty_cmd_tx) ;
415488 app. cloudflared_pid = cloudflared_pid;
489+ app. relay_server_pid = relay_server_pid;
416490
417491 info ! ( "Entering main event loop" ) ;
418492
0 commit comments