feat(sbx,ai): snapshot all backends w/ local export; robust AI + rejoin
- /sbx save [--local] and new /sbx vmsave/vmsnaps: snapshot Docker, Multipass, and VirtualBox; --local also writes a portable artifact (docker .tar / VBox .ova) under hh-snapshots/ that survives pruning. - sandbox reappears for anyone who leaves and rejoins (host replays status + screen snapshot + ACL on Joined); SbxStatus ready handler is now idempotent so it never wipes scrollback. - received files auto-bridge into the hosted sandbox (ft::tar_path). - AI agent: translate Ollama's cryptic 404 into model/host/fix guidance. - bootstrap installs the AI layer (Ollama + default model) by default, with consent gates; --no-ai opts out, --yes skips prompts. - help menu lists the new save/vm flags. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,31 @@ fn tar_dir(dir: &Path) -> Result<Vec<u8>> {
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Tar any local path (file or directory) to bytes for injecting into a
|
||||
/// sandbox — the archive's single top-level entry is named after the path's
|
||||
/// basename, so it extracts as `<dest>/<base>`. Enforces `MAX_SIZE`. Returns
|
||||
/// `(base_name, tar_bytes)`.
|
||||
pub fn tar_path(path: &Path) -> Result<(String, Vec<u8>)> {
|
||||
let meta = std::fs::metadata(path).with_context(|| format!("not found: {}", path.display()))?;
|
||||
let base = path
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.context("path has no final component")?
|
||||
.to_string();
|
||||
let mut buf = Vec::new();
|
||||
{
|
||||
let mut tb = tar::Builder::new(&mut buf);
|
||||
if meta.is_dir() {
|
||||
tb.append_dir_all(&base, path).context("tar directory")?;
|
||||
} else {
|
||||
tb.append_path_with_name(path, &base).context("tar file")?;
|
||||
}
|
||||
tb.finish()?;
|
||||
}
|
||||
anyhow::ensure!(buf.len() <= MAX_SIZE, "too large ({})", human(buf.len()));
|
||||
Ok((base, buf))
|
||||
}
|
||||
|
||||
/// Persist received bytes under `downloads`. Directories (tar) are extracted
|
||||
/// with a guard rejecting absolute paths and `..` escapes (zip-slip).
|
||||
pub fn save(downloads: &Path, offer: &Offer, data: &[u8]) -> Result<PathBuf> {
|
||||
|
||||
Reference in New Issue
Block a user