Capsa is experimental software. APIs may change without notice.
Skip to content

Console

The console provides raw serial port access. For command execution, use the agent instead.

When to Use the Console

  • Waiting for boot output
  • Interactive troubleshooting
  • VMs without agent support

Enabling Console Access

Enable the console on your VM builder:

rust,no_run
let vm = capsa::vm(boot)
    .console_enabled()
    .build()
    .await?;

let console = vm.console().await?;

Waiting for Boot

Use wait_for() to detect when the VM has booted:

rust,no_run
// Wait for login prompt
console.wait_for("login:", Duration::from_secs(30)).await?;

// Or wait for shell prompt
console.wait_for("# ", Duration::from_secs(30)).await?;

Writing to Console

Send text to the console:

rust,no_run
// Write a line (includes newline)
console.write_line("root").await?;

// Write raw bytes
console.write(b"password\n").await?;

Reading Output

Read available output without blocking:

rust,no_run
let output = console.read_available().await?;
println!("Console output: {}", output);

Control Signals

Send control characters:

rust,no_run
// Send Ctrl+C (interrupt)
console.send_interrupt().await?;

// Send Ctrl+D (EOF)
console.send_eof().await?;

Split Reader/Writer

For advanced async I/O, split the console:

rust,no_run
let (mut reader, mut writer) = console.into_split().await?;

// Now you can read and write concurrently
tokio::spawn(async move {
    let mut buf = [0u8; 1024];
    loop {
        let n = reader.read(&mut buf).await.unwrap();
        if n == 0 { break; }
        print!("{}", String::from_utf8_lossy(&buf[..n]));
    }
});

writer.write_all(b"ls /\n").await?;

Capturing Boot Failures

When a VM fails to boot, use the console to capture output:

rust,no_run
let vm = capsa::vm(boot)
    .console_enabled()
    .build()
    .await?;

let console = vm.console().await?;

// Try to wait for prompt
match console.wait_for("# ", Duration::from_secs(30)).await {
    Ok(_) => println!("Boot successful"),
    Err(e) => {
        // Capture whatever output we got
        let output = console.read_available().await?;
        eprintln!("Boot failed: {}", e);
        eprintln!("Console output:\n{}", output);
    }
}

Complete Example

rust,no_run
use capsa::boot::LinuxDirectBoot;
use std::time::Duration;

async fn boot_and_run() -> capsa::Result<()> {
    let boot = LinuxDirectBoot::new("./kernel", "./initrd");

    let vm = capsa::vm(boot)
        .cpus(2)
        .memory_mb(1024)
        .cmdline_arg("console", "ttyS0")
        .console_enabled()
        .build()
        .await?;

    let console = vm.console().await?;

    // Wait for login prompt
    console.wait_for("login:", Duration::from_secs(30)).await?;

    // Log in
    console.write_line("root").await?;
    console.wait_for("# ", Duration::from_secs(5)).await?;

    // Run a command manually
    console.write_line("uname -a").await?;

    // Read output
    tokio::time::sleep(Duration::from_millis(100)).await;
    let output = console.read_available().await?;
    println!("Output: {}", output);

    vm.shutdown().await?;
    Ok(())
}

Next Steps

Released under the MIT License.