Table of Contents

Files and rootfs

File transfer

client.Files (IFileClient) copies files and directories between the local machine and a sandbox. The files travel as a tar stream over exec, so the sandbox image needs tar and sh.

// Single files: the remote path names the file, and its parent directory is created.
await client.Files.UploadAsync("agent-1", "settings.json", "/workspace/config/settings.json");
await client.Files.DownloadAsync("agent-1", "/workspace/out/report.csv", "report.csv");

// Streams, with the Unix mode of the uploaded file.
using (var script = new MemoryStream(Encoding.UTF8.GetBytes("#!/bin/sh\necho ready\n")))
{
    await client.Files.UploadAsync(
        "agent-1",
        script,
        "/workspace/bin/start.sh",
        UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
}

await using (var destination = File.Create("app.log"))
{
    await client.Files.DownloadAsync("agent-1", "/var/log/app.log", destination);
}

// Whole directories.
await client.Files.UploadDirectoryAsync("agent-1", "./src", "/workspace/src");
await client.Files.DownloadDirectoryAsync("agent-1", "/workspace/out", "./out");
  • Uploads create the remote parent directories. A remote file path must name a file, not end with /.
  • A non-seekable upload stream is buffered in memory first, because the tar header needs the size up front. Uploaded stream files default to mode 0644 unless you pass one.
  • DownloadAsync to a path writes a temporary file next to the target and replaces the target only on success.
  • DownloadDirectoryAsync refuses archive entries that would land outside the target directory, and links.
  • A failing remote tar during an upload throws OpenShellException with its exit code and error output; a download whose archive is truncated, empty, or unexpected throws InvalidDataException.

Rootfs staging

Compute drivers that boot from a root filesystem archive (for example the VM driver) read it from a gateway staging slot. client.Rootfs (IRootfsClient) stages a local tar archive and returns a token; RootfsDriverConfig.WithStagingToken puts the token into the template's driver configuration.

// Copies the archive into a gateway staging slot (the client must share the gateway's filesystem)
// and returns the token that names it.
var token = await client.Rootfs.StageAsync("/srv/images/agent-rootfs.tar");

// Boot a sandbox from it: the token goes into the driver config of the template.
var sandbox = await client.Sandboxes.CreateAsync("vm-agent", new SandboxSpec
{
    Template = new SandboxTemplateSpec
    {
        DriverConfigJson = RootfsDriverConfig.WithStagingToken(token),
    },
});

Only a client that shares the gateway's filesystem can complete the copy, so this is a local-gateway workflow. The gateway enforces a size limit and reclaims unused slots (RootfsStagingSlot.ExpiresAt). BeginStagingAsync allocates a slot without copying, when you want to write the archive yourself. WithStagingToken keeps any settings already in the driver configuration JSON you pass, and throws when it already contains a token.