Files
sausage/host/src/main.rs
T

140 lines
4.7 KiB
Rust

//! The `sausage` binary: subcommands only, each delegates to its module.
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser)]
#[command(version, about)]
struct Cli {
/// Shared host-user compile cache (otherwise SAUSAGE_CACHE or the XDG cache folder).
#[arg(long, global = true)]
cache: Option<PathBuf>,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
/// Create a world with default tools and /etc/sausage/config.yaml.
Init {
world: PathBuf,
#[arg(long)]
userland: Option<PathBuf>,
},
/// Start a world: the harness, its services and a console.
Run {
world: PathBuf,
/// Run the native Rust builder without namespace isolation.
#[arg(long)]
no_builder_sandbox: bool,
},
/// Run a shell in a world. With `-c`, run one command and exit with its status.
Sh {
world: PathBuf,
#[arg(short = 'c')]
command: Option<String>,
#[arg(long)]
userland: Option<PathBuf>,
},
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
if let Some(cache) = cli.cache {
sausage::process::configure_cache(cache)?;
}
match cli.command {
Command::Init { world, userland } => {
let bundle = userland
.or_else(|| std::env::var_os("SAUSAGE_USERLAND").map(PathBuf::from))
.ok_or_else(|| {
anyhow::anyhow!(
"Copy tools failed: default tools unavailable. Set SAUSAGE_USERLAND or pass --userland."
)
})?;
use std::io::{IsTerminal, Write};
let terminal = std::io::stderr().is_terminal();
let mut progress_line = false;
let initialized = sausage::world::World::init_with_progress(&world, &bundle, |line| {
if terminal && line.starts_with("Precompile tools ") {
eprint!("\r\x1b[2K{line}");
let _ = std::io::stderr().flush();
progress_line = true;
} else {
if progress_line {
eprintln!();
progress_line = false;
}
eprintln!("{line}");
}
})
.await;
if progress_line {
eprintln!();
}
initialized?;
eprintln!("Next steps:");
eprintln!(
" Edit {} to configure providers and API keys.",
world.join("world/etc/sausage/config.yaml").display()
);
eprintln!(
" sausage run '{}'",
world.to_string_lossy().replace('\'', "'\\''")
);
Ok(())
}
Command::Run {
world,
no_builder_sandbox,
} => {
anyhow::ensure!(
world.join("world").is_dir(),
"No world at {}. Run sausage init {} first.",
world.display(),
world.display()
);
sausage::api::run_with_userland(
&world,
None,
sausage::process::RuntimeOptions { no_builder_sandbox },
)
.await
}
Command::Sh {
world,
command,
userland,
} => {
use sausage::exec::{Command, Exec, Options, Stream};
let bundle =
userland.or_else(|| std::env::var_os("SAUSAGE_USERLAND").map(PathBuf::from));
let world = sausage::world::World::open_with_userland(&world, bundle.as_deref())?;
let runtime = sausage::process::Runtime::new(world)?;
let script =
command.ok_or_else(|| anyhow::anyhow!("interactive shell is not implemented"))?;
let status = runtime
.exec(
Command::Shell(script),
Options {
cwd: "/".into(),
..Default::default()
},
&mut |stream, bytes| {
use std::io::Write;
match stream {
Stream::Stdout => {
let _ = std::io::stdout().write_all(bytes);
}
Stream::Stderr => {
let _ = std::io::stderr().write_all(bytes);
}
}
},
)
.await?;
std::process::exit(status);
}
}
}