Skip to content

Commit 3651607

Browse files
committed
feat: only set ATUIN_OUTPUT_VARS over ssh if used
This feature requires several extra calls to setup the file + download it before closing the terminal. This adds some latency If it's not being used, don't pay the latency cost.
1 parent be509ba commit 3651607

File tree

2 files changed

+100
-87
lines changed

2 files changed

+100
-87
lines changed

crates/atuin-desktop-runtime/src/blocks/script.rs

Lines changed: 52 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -676,21 +676,29 @@ impl Script {
676676
}
677677
};
678678

679-
// Create remote temp file for variable output
680-
let remote_temp_path = match ssh_pool
681-
.create_temp_file(&hostname, username.as_deref(), "atuin-desktop-vars")
682-
.await
683-
{
684-
Ok(path) => path,
685-
Err(e) => {
686-
let error_msg = format!("Failed to create remote temp file: {}", e);
687-
let _ = context.block_failed(error_msg.clone()).await;
688-
return (Err(error_msg.into()), Vec::new(), None);
679+
let uses_output_vars = code.contains("ATUIN_OUTPUT_VARS");
680+
681+
let remote_temp_path: Option<String> = if uses_output_vars {
682+
match ssh_pool
683+
.create_temp_file(&hostname, username.as_deref(), "atuin-desktop-vars")
684+
.await
685+
{
686+
Ok(path) => Some(path),
687+
Err(e) => {
688+
let error_msg = format!("Failed to create remote temp file: {}", e);
689+
let _ = context.block_failed(error_msg.clone()).await;
690+
return (Err(error_msg.into()), Vec::new(), None);
691+
}
689692
}
693+
} else {
694+
None
690695
};
691696

692-
// Prepend environment variable export to the code
693-
let code_with_vars = format!("export ATUIN_OUTPUT_VARS='{}'\n{}", remote_temp_path, code);
697+
let code_to_run = if let Some(ref path) = remote_temp_path {
698+
format!("export ATUIN_OUTPUT_VARS='{}'\n{}", path, code)
699+
} else {
700+
code.to_string()
701+
};
694702

695703
let channel_id = self.id.to_string();
696704
let (output_sender, mut output_receiver) = mpsc::channel::<SessionOutputLine>(100);
@@ -699,27 +707,26 @@ impl Script {
699707
let captured_output = Arc::new(RwLock::new(Vec::new()));
700708
let captured_output_clone = captured_output.clone();
701709

702-
// Take the cancellation receiver once at the start
703710
let mut cancel_rx = match cancellation_token.take_receiver() {
704711
Some(rx) => rx,
705712
None => {
706713
let error_msg = "Cancellation receiver already taken";
707714
let _ = context.block_failed(error_msg.to_string()).await;
708-
// Cleanup remote temp file
709-
let _ = ssh_pool
710-
.delete_file(&hostname, username.as_deref(), &remote_temp_path)
711-
.await;
715+
if let Some(ref path) = remote_temp_path {
716+
let _ = ssh_pool
717+
.delete_file(&hostname, username.as_deref(), path)
718+
.await;
719+
}
712720
return (Err(error_msg.into()), Vec::new(), None);
713721
}
714722
};
715723

716-
// Execute SSH command with cancellation support
717724
let exec_result = tokio::select! {
718725
result = ssh_pool.exec(
719726
&hostname,
720727
username.as_deref(),
721728
&self.interpreter,
722-
&code_with_vars,
729+
&code_to_run,
723730
&channel_id,
724731
output_sender,
725732
result_tx,
@@ -730,19 +737,19 @@ impl Script {
730737
tracing::trace!("Sending cancel to SSH execution for channel {channel_id}");
731738
let _ = ssh_pool.exec_cancel(&channel_id).await;
732739
let _ = context.block_cancelled().await;
733-
// Cleanup remote temp file
734-
let _ = ssh_pool.delete_file(&hostname, username.as_deref(), &remote_temp_path).await;
740+
if let Some(ref path) = remote_temp_path {
741+
let _ = ssh_pool.delete_file(&hostname, username.as_deref(), path).await;
742+
}
735743
return (Err("SSH script execution cancelled before start".into()), Vec::new(), None);
736744
}
737745
};
738746

739747
if let Err(e) = exec_result {
740748
let error_msg = format!("Failed to start SSH execution: {}", e);
741749
let _ = context.block_failed(error_msg.to_string()).await;
742-
// Cleanup remote temp file
743-
let _ = ssh_pool
744-
.delete_file(&hostname, username.as_deref(), &remote_temp_path)
745-
.await;
750+
if let Some(ref path) = remote_temp_path {
751+
let _ = ssh_pool.delete_file(&hostname, username.as_deref(), path).await;
752+
}
746753
return (Err(error_msg.into()), Vec::new(), None);
747754
}
748755
let context_clone = context.clone();
@@ -787,8 +794,9 @@ impl Script {
787794
let captured = captured_output.read().await.clone();
788795

789796
let _ = context.block_cancelled().await;
790-
// Cleanup remote temp file
791-
let _ = ssh_pool.delete_file(&hostname, username.as_deref(), &remote_temp_path).await;
797+
if let Some(ref path) = remote_temp_path {
798+
let _ = ssh_pool.delete_file(&hostname, username.as_deref(), path).await;
799+
}
792800
return (Err("SSH script execution cancelled".into()), captured, None);
793801
}
794802
_ = result_rx => {
@@ -799,25 +807,26 @@ impl Script {
799807
let _ = output_task.await;
800808
let captured = captured_output.read().await.clone();
801809

802-
// Read variables from remote temp file
803-
let vars = match ssh_pool
804-
.read_file(&hostname, username.as_deref(), &remote_temp_path)
805-
.await
806-
{
807-
Ok(contents) => {
808-
// Parse the file contents using fs_var::parse_vars
809-
Some(fs_var::parse_vars(&contents))
810-
}
811-
Err(e) => {
812-
tracing::warn!("Failed to read remote temp file for variables: {}", e);
813-
None
810+
let vars = if let Some(ref path) = remote_temp_path {
811+
match ssh_pool
812+
.read_file(&hostname, username.as_deref(), path)
813+
.await
814+
{
815+
Ok(contents) => Some(fs_var::parse_vars(&contents)),
816+
Err(e) => {
817+
tracing::warn!("Failed to read remote temp file for variables: {}", e);
818+
None
819+
}
814820
}
821+
} else {
822+
None
815823
};
816824

817-
// Cleanup remote temp file
818-
let _ = ssh_pool
819-
.delete_file(&hostname, username.as_deref(), &remote_temp_path)
820-
.await;
825+
if let Some(ref path) = remote_temp_path {
826+
let _ = ssh_pool
827+
.delete_file(&hostname, username.as_deref(), path)
828+
.await;
829+
}
821830

822831
(Ok(exit_code), captured, vars)
823832
}

crates/atuin-desktop-runtime/src/blocks/terminal.rs

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -214,57 +214,65 @@ impl Terminal {
214214
.take_receiver()
215215
.ok_or("Cancellation receiver already taken")?;
216216

217-
// Accumulator for terminal output (shared between reader tasks)
218217
let output_accumulator: Arc<RwLock<Vec<u8>>> = Arc::new(RwLock::new(Vec::new()));
219218

220-
// Setup fs_var for local terminal or remote path for SSH
219+
let templated_code = if !self.code.is_empty() {
220+
context.context_resolver.resolve_template(&self.code)?
221+
} else {
222+
String::new()
223+
};
224+
225+
let uses_output_vars = templated_code.contains("ATUIN_OUTPUT_VARS");
226+
227+
let ssh_host = context.context_resolver.ssh_host().cloned();
221228
let fs_var_handle: Option<crate::context::fs_var::FsVarHandle>;
222-
let remote_var_path: Option<(String, Option<String>, String)>; // (hostname, username, path)
229+
let remote_var_path: Option<String>;
223230

224-
if let Some(ssh_host) = context.context_resolver.ssh_host() {
231+
if let Some(ref host) = ssh_host {
225232
fs_var_handle = None;
226233

227-
// For SSH, create remote temp file
228-
let (username, hostname) = Self::parse_ssh_host(ssh_host);
229-
let ssh_pool = context
230-
.ssh_pool
231-
.clone()
232-
.ok_or("SSH pool not available in execution context")?;
234+
if uses_output_vars {
235+
let (username, hostname) = Self::parse_ssh_host(host);
236+
let ssh_pool = context
237+
.ssh_pool
238+
.clone()
239+
.ok_or("SSH pool not available in execution context")?;
233240

234-
let remote_path = ssh_pool
235-
.create_temp_file(&hostname, username.as_deref(), "atuin-desktop-vars")
236-
.await
237-
.map_err(|e| format!("Failed to create remote temp file: {}", e))?;
241+
let remote_path = ssh_pool
242+
.create_temp_file(&hostname, username.as_deref(), "atuin-desktop-vars")
243+
.await
244+
.map_err(|e| format!("Failed to create remote temp file: {}", e))?;
238245

239-
remote_var_path = Some((hostname, username, remote_path));
246+
remote_var_path = Some(remote_path);
247+
} else {
248+
remote_var_path = None;
249+
}
240250
} else {
241-
fs_var_handle =
242-
Some(crate::context::fs_var::setup().map_err(|e| {
243-
format!("Failed to setup temp file for output variables: {}", e)
244-
})?);
251+
if uses_output_vars {
252+
fs_var_handle =
253+
Some(crate::context::fs_var::setup().map_err(|e| {
254+
format!("Failed to setup temp file for output variables: {}", e)
255+
})?);
256+
} else {
257+
fs_var_handle = None;
258+
}
245259
remote_var_path = None;
246260
}
247261

248-
// Open PTY based on context (local or SSH)
249262
let cancellation_token_clone = cancellation_token.clone();
250-
let pty: Box<dyn PtyLike + Send> = if let Some((
251-
ref hostname,
252-
ref username,
253-
ref remote_path,
254-
)) = remote_var_path
255-
{
256-
// Get SSH pool from context
263+
let pty: Box<dyn PtyLike + Send> = if let Some(ref host) = ssh_host {
264+
let (username, hostname) = Self::parse_ssh_host(host);
257265
let ssh_pool = context
258266
.ssh_pool
259267
.clone()
260268
.ok_or("SSH pool not available in execution context")?;
261269

262-
// Create SSH PTY with cancellation support
263270
let (output_sender, mut output_receiver) = tokio::sync::mpsc::channel(100);
264271
let hostname_clone = hostname.clone();
265272
let username_clone = username.clone();
266273
let pty_id_str = self.id.to_string();
267274
let ssh_pool_clone = ssh_pool.clone();
275+
let remote_path_clone = remote_var_path.clone();
268276

269277
let initial_cols = self.cols;
270278
let initial_rows = self.rows;
@@ -282,8 +290,9 @@ impl Terminal {
282290
_ = &mut cancel_rx => {
283291
let _ = ssh_pool_clone.close_pty(&pty_id_str).await;
284292
let _ = context.block_cancelled().await;
285-
// Cleanup remote temp file if it was created
286-
let _ = ssh_pool_clone.delete_file(&hostname_clone, username_clone.as_deref(), remote_path).await;
293+
if let Some(ref path) = remote_path_clone {
294+
let _ = ssh_pool_clone.delete_file(&hostname_clone, username_clone.as_deref(), path).await;
295+
}
287296
return Err("SSH PTY connection cancelled".into());
288297
}
289298
};
@@ -431,25 +440,21 @@ impl Terminal {
431440
.emit_gc_event(GCEvent::PtyOpened(metadata.clone()))
432441
.await;
433442

434-
// For SSH terminals, export ATUIN_OUTPUT_VARS first
435-
if let Some((_, _, ref remote_path)) = remote_var_path {
443+
if let Some(ref remote_path) = remote_var_path {
436444
let export_cmd = format!("export ATUIN_OUTPUT_VARS='{}'\n", remote_path);
437445
if let Err(e) = pty_store.write_pty(self.id, export_cmd.into()).await {
438446
tracing::warn!("Failed to write export command to SSH PTY: {}", e);
439447
}
440448
}
441449

442-
// Write the command to the PTY after started event
443-
if !self.code.is_empty() {
444-
let command = context.context_resolver.resolve_template(&self.code)?;
445-
let command = if command.ends_with('\n') {
446-
command
450+
if !templated_code.is_empty() {
451+
let command = if templated_code.ends_with('\n') {
452+
templated_code.clone()
447453
} else {
448-
format!("{}\n", command)
454+
format!("{}\n", templated_code)
449455
};
450456

451457
if let Err(e) = pty_store.write_pty(self.id, command.into()).await {
452-
// Send error event if command writing fails
453458
let _ = context
454459
.block_failed(format!("Failed to write command to PTY: {}", e))
455460
.await;
@@ -490,12 +495,12 @@ impl Terminal {
490495
tracing::warn!("Failed to read terminal output variables: {}", e);
491496
}
492497
}
493-
} else if let Some((hostname, username, remote_path)) = remote_var_path {
494-
// SSH terminal
498+
} else if let (Some(ref host), Some(ref remote_path)) = (&ssh_host, &remote_var_path) {
499+
let (username, hostname) = Self::parse_ssh_host(host);
495500
let ssh_pool = context.ssh_pool.clone().unwrap();
496501

497502
match ssh_pool
498-
.read_file(&hostname, username.as_deref(), &remote_path)
503+
.read_file(&hostname, username.as_deref(), remote_path)
499504
.await
500505
{
501506
Ok(contents) => {
@@ -520,9 +525,8 @@ impl Terminal {
520525
}
521526
}
522527

523-
// Cleanup remote temp file
524528
let _ = ssh_pool
525-
.delete_file(&hostname, username.as_deref(), &remote_path)
529+
.delete_file(&hostname, username.as_deref(), remote_path)
526530
.await;
527531
}
528532

0 commit comments

Comments
 (0)