guestctl/src/cli.rs

116 lines
2.8 KiB
Rust

use clap::{Parser, Subcommand};
use std::{path::PathBuf, str::FromStr};
/// VM controller for `AirgapOS`
#[derive(Parser, Clone, Debug)]
pub struct App {
// global options go here
#[arg(long, global = true, default_value = "/var/run/netvm.pid")]
pub lockfile: PathBuf,
//
#[command(subcommand)]
pub subcommand: Commands,
}
#[derive(Subcommand, Clone, Debug)]
pub enum Commands {
/// Start a headless VM in the background.
Start {
#[arg(long, default_value = "1G")]
memory: String,
},
/// Stop a headless VM.
Stop,
/// Open a VM in the foreground with a serial terminal.
Shell,
/// Get the hostname and uptime of a running VM.
Status,
/// Attach a USB device to a running VM.
Attach {
/// The device to attach.
device: DeviceIdentifier,
},
/// Push a file to a currently running VM.
Push {
/// The local path to push.
local_path: PathBuf,
/// The remote path to push to.
remote_path: PathBuf,
},
/// Pull a file from a currently running VM.
Pull {
/// The remote path to pull.
remote_path: PathBuf,
/// The local path to pull to.
local_path: PathBuf,
},
/// Run a command in a currently running VM.
Run {
/// The command to run.
command: String,
/// Arguments to pass to the running command.
args: Vec<String>,
},
}
/// An attachable USB device identifier.
#[derive(Clone, Debug)]
pub struct DeviceIdentifier {
/// The Vendor ID.
pub vendor_id: u16,
/// The Device ID.
pub device_id: u16,
}
/// An error encountered while parsing a USB device identifier
#[derive(thiserror::Error, Debug)]
pub enum DeviceIdentifierFromStrError {
#[error("could not split input by colon; expected output similar to `lsusb`")]
CouldNotSplitByColon,
#[error("could not parse hex from vendor or device ID")]
Hex(#[from] hex::FromHexError),
#[error("could not decode u64 from bytes: {0:?}")]
BadBytes(Vec<u8>),
}
impl FromStr for DeviceIdentifier {
type Err = DeviceIdentifierFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((first, last)) = s.split_once(':') else {
return Err(DeviceIdentifierFromStrError::CouldNotSplitByColon);
};
let vendor_id = u16::from_be_bytes(
hex::decode(first)?
.try_into()
.map_err(DeviceIdentifierFromStrError::BadBytes)?,
);
let device_id = u16::from_be_bytes(
hex::decode(last)?
.try_into()
.map_err(DeviceIdentifierFromStrError::BadBytes)?,
);
Ok(Self {
vendor_id,
device_id,
})
}
}