Skip to main content

ssh_cli/sftp/
setup.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! SFTP option plumbing and session bootstrap (A7 split).
3#![forbid(unsafe_code)]
4#![allow(unused_imports)]
5
6use super::*;
7
8#[derive(Debug, Default, Clone)]
9/// Per-invocation SFTP options resolved at the CLI boundary.
10pub struct SftpOptions {
11    /// SSH password (resolved).
12    pub password: Option<secrecy::SecretString>,
13    /// Private key path.
14    pub key: Option<String>,
15    /// Key passphrase (resolved).
16    pub key_passphrase: Option<secrecy::SecretString>,
17    /// Total connect+op timeout ms.
18    pub timeout: Option<crate::domain::TimeoutMs>,
19    /// Replace divergent host key.
20    pub replace_host_key: bool,
21    /// Emit JSON success envelopes.
22    pub json: bool,
23    /// Use ssh-agent (CLI/XDG only).
24    pub use_agent: bool,
25    /// Agent socket / named pipe path.
26    pub agent_socket: Option<String>,
27    /// Recursive tree transfer.
28    pub recursive: bool,
29}
30
31/// Applies CLI overrides onto a VPS record (incl. agent — G-SFTP-18).
32pub(crate) fn apply_sftp_options(record: &mut crate::vps::model::VpsRecord, opts: &SftpOptions) {
33    if let Some(ref pwd) = opts.password {
34        record.password = pwd.clone();
35    }
36    if let Some(ref k) = opts.key {
37        if let Ok(kp) = crate::domain::KeyPath::try_new(k.as_str()) {
38            record.key_path = Some(kp);
39        }
40    }
41    if let Some(ref kp) = opts.key_passphrase {
42        record.key_passphrase = Some(kp.clone());
43    }
44    if let Some(t) = opts.timeout {
45        record.timeout_ms = t;
46    }
47    if opts.use_agent {
48        record.use_agent = true;
49    }
50    if let Some(ref sock) = opts.agent_socket {
51        record.agent_socket = Some(sock.clone());
52        record.use_agent = true;
53    }
54}
55
56pub(crate) async fn connect_client(
57    vps_key: &str,
58    config_override: Option<&std::path::Path>,
59    opts: &SftpOptions,
60) -> anyhow::Result<SshClient> {
61    let mut record = vps::find_by_name(config_override, vps_key)?
62        .ok_or_else(|| SshCliError::VpsNotFound(vps_key.to_owned()))?;
63    apply_sftp_options(&mut record, opts);
64    let path = vps::resolve_config_path(config_override)?;
65    let cfg = vps::build_connection_config(&record, Some(&path), opts.replace_host_key);
66    let client = SshClient::connect(cfg).await?;
67    Ok(client)
68}
69
70pub(crate) fn remote_str(p: &Path) -> String {
71    p.to_string_lossy().into_owned()
72}