Fix backend-switch AWS auth + add /workspace backup and terminal file drag-and-drop #8

Merged
jknapp merged 9 commits from fix/backend-switch-aws-creds into main 2026-06-30 22:20:00 +00:00
2 changed files with 15 additions and 14 deletions
Showing only changes of commit 3e2e3f231b - Show all commits
+10 -13
View File
@@ -279,11 +279,11 @@ impl ExecSessionManager {
}
}
/// Upload a host file into the container's `/tmp` under `dest_name`, building the
/// tar archive by streaming from the file inside a blocking task. Unlike reading
/// the whole file into a `Vec` and then handing it to `write_file_to_container`
/// (which holds two full-size copies — the data and the tar), this keeps only
/// the single tar buffer, and the synchronous file IO runs off the async worker.
/// Upload a host file into the container's `/tmp` under `dest_name`. The file is
/// read and packed into the tar inside a blocking task, so the synchronous IO
/// runs off the async worker. The tar's declared entry size is taken from the
/// bytes actually read (not a separate `stat`), so a file changing size between
/// a size check and the read can't desync the header and corrupt the archive.
/// Returns the in-container path (`/tmp/<dest_name>`).
pub async fn upload_host_file_to_container(
container_id: &str,
@@ -295,21 +295,18 @@ pub async fn upload_host_file_to_container(
let dest_for_blk = dest_name.clone();
let tar_buf = tokio::task::spawn_blocking(move || -> Result<Vec<u8>, String> {
let mut file = std::fs::File::open(&host_path)
let data = std::fs::read(&host_path)
.map_err(|e| format!("Failed to read {}: {}", host_path, e))?;
let size = file
.metadata()
.map_err(|e| format!("Failed to stat {}: {}", host_path, e))?
.len();
let mut tar_buf = Vec::new();
let mut tar_buf = Vec::with_capacity(data.len() + 1024);
{
let mut builder = tar::Builder::new(&mut tar_buf);
let mut header = tar::Header::new_gnu();
header.set_size(size);
// Size comes from the bytes in hand, so header and payload can't disagree.
header.set_size(data.len() as u64);
header.set_mode(0o644);
header.set_cksum();
builder
.append_data(&mut header, &dest_for_blk, &mut file)
.append_data(&mut header, &dest_for_blk, &data[..])
.map_err(|e| format!("Failed to create tar entry: {}", e))?;
builder
.finish()
+5 -1
View File
@@ -196,7 +196,11 @@ export default function ProjectCard({ project }: Props) {
setError(null);
const bytes = await commands.downloadContainerBackup(project.id, hostPath);
const mb = (bytes / (1024 * 1024)).toFixed(1);
setProgressMsg(`Backup saved (${mb} MB). Note: includes MCP/config — may contain MCP API keys. Keep it private.`);
const msg = `Backup saved (${mb} MB). Note: includes MCP/config — may contain MCP API keys. Keep it private.`;
setProgressMsg(msg);
// Auto-clear so the transient confirmation doesn't linger in the card
// status; guard against clobbering a newer message (e.g. a later op).
setTimeout(() => setProgressMsg((prev) => (prev === msg ? null : prev)), 8000);
} catch (e) {
setError(String(e));
} finally {