put: report through the view and ask the host-key question through the Prompter

put() loses its MultiProgress parameter: the summary, pre-flight and
connection spinners become view messages, the per-file SFTP transfer
reports determinate progress through view.progress (upload_file takes
a byte-count callback instead of an indicatif bar), and the display is
released through view.suspend on every exit path. The hardcoded
trust-on-first-use prompt in the SSH host-key verification becomes the
Prompter::accept_host_key port (fail-closed by default; the terminal
prompter prints the authenticity banner and confirms), so a remote
frontend can surface its own host-key dialog.
This commit is contained in:
2026-09-18 20:42:17 +02:00
parent bb76e41908
commit 54cb04ba27
6 changed files with 62 additions and 93 deletions
+9 -11
View File
@@ -20,14 +20,13 @@ use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use std::path::{Path, PathBuf};
use std::time::Duration;
use indicatif::ProgressBar;
use log::debug;
use serde::Deserialize;
use sha2::{Digest, Sha256};
use ssh2::{CheckResult, HostKeyType, KnownHostFileKind, KnownHosts, Session};
use crate::data::embed_data;
use crate::ui::prompt;
use crate::report::Prompter;
/// Pinned SSH host key fingerprints, loaded from the bundled
/// `host_keys.yml` data file (same pattern as `distro_info.yml`): data
@@ -303,6 +302,7 @@ pub fn connect(
port: u16,
login: &str,
config: &SshConfig,
prompter: &dyn Prompter,
) -> Result<Session, Box<dyn std::error::Error>> {
let tcp = tcp_connect(host, port)?;
@@ -326,7 +326,7 @@ pub fn connect(
let (key, key_type) = session
.host_key()
.ok_or_else(|| format!("{host} offered no host key"))?;
verify_host_key(host, port, key, key_type)?;
verify_host_key(host, port, key, key_type, prompter)?;
authenticate(&session, host, login, config)?;
@@ -353,6 +353,7 @@ fn verify_host_key(
port: u16,
key: &[u8],
key_type: HostKeyType,
prompter: &dyn Prompter,
) -> Result<(), Box<dyn std::error::Error>> {
let fingerprint = fingerprint(key);
@@ -387,12 +388,7 @@ fn verify_host_key(
format!("[{host}]:{port}")
};
// The banner is plain output: the confirmation prompt itself
// must stay a single line for its redraw logic
println!("The authenticity of host '{display}' can't be established.");
println!("{key_type_desc} key fingerprint is {fingerprint}.");
let accepted = prompt::confirm("Accept and store this host key?", false)?;
if !accepted {
if !prompter.accept_host_key(&display, key_type_desc, &fingerprint) {
return Err(format!("Host key for {display} rejected, aborting upload").into());
}
if let Some(name) = key_type_name(key_type) {
@@ -592,7 +588,7 @@ pub fn upload_file(
local: &Path,
remote: &str,
host: &str,
bar: &ProgressBar,
on_progress: &dyn Fn(u64),
) -> Result<(), Box<dyn std::error::Error>> {
let mut local_file =
fs::File::open(local).map_err(|e| format!("cannot open '{}': {}", local.display(), e))?;
@@ -602,6 +598,7 @@ pub fn upload_file(
.map_err(|e| format!("cannot create remote file {remote} on {host}: {e}"))?;
let mut buf = [0u8; 32 * 1024];
let mut uploaded: u64 = 0;
loop {
let n = local_file.read(&mut buf)?;
if n == 0 {
@@ -610,7 +607,8 @@ pub fn upload_file(
remote_file
.write_all(&buf[..n])
.map_err(|e| format!("failed uploading to {remote} on {host}: {e}"))?;
bar.inc(n as u64);
uploaded += n as u64;
on_progress(uploaded);
}
// Close explicitly: quota-exceeded and similar failures only surface in