diff --git a/README.md b/README.md index 62ada2e..fee6374 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ``` sh Usage: muvm [-c=CPU_LIST]... [-e=ENV]... [--mem=MEM] [--vram=VRAM] [--passt-socket=PATH] [-f= FEX_IMAGE]... [-m] [-i] [-t] [--privileged] [-p=<[[IP:][HOST_PORT]:]GUEST_PORT[/PROTOCOL]>]... [ ---emu=EMU] COMMAND [COMMAND_ARGS]... +--emu=EMU] [-x=COMMAND]... COMMAND [COMMAND_ARGS]... Available positional items: COMMAND the command you want to execute in the vm @@ -54,6 +54,9 @@ Available options: Valid options are "box" and "fex". If this argument is not present, muvm will try to use FEX, falling back to Box if it can't be found. + -x, --execute-pre=COMMAND Command to run inside the VM before guest server starts. + Can be used for e.g. setting up additional mounts. + Can be specified multiple times. -h, --help Prints help information ``` diff --git a/crates/muvm/src/bin/muvm.rs b/crates/muvm/src/bin/muvm.rs index 3d1c699..8a62fa0 100644 --- a/crates/muvm/src/bin/muvm.rs +++ b/crates/muvm/src/bin/muvm.rs @@ -15,6 +15,7 @@ use krun_sys::{ }; use log::debug; use muvm::cli_options::options; +use muvm::config::Configuration; use muvm::cpu::{get_fallback_cores, get_performance_cores}; use muvm::env::{find_muvm_exec, prepare_env_vars}; use muvm::hidpipe_server::spawn_hidpipe_server; @@ -73,6 +74,12 @@ fn main() -> Result { } let options = options().fallback_to_usage().run(); + let config = Configuration::parse_config_file()?; + + let mut init_commands = options.init_commands; + if let Some(init_command) = config.execute_pre { + init_commands.insert(0, init_command); + } let (_lock_file, command, command_args, env) = match launch_or_lock( options.command, @@ -346,11 +353,12 @@ fn main() -> Result { } } - let username = env::var("USER").context("Failed to get username from environment")?; - let user = User::from_name(&username) + let uid = getuid().as_raw(); + let user = User::from_uid(uid.into()) .map_err(Into::into) .and_then(|user| user.ok_or_else(|| anyhow!("requested entry not found"))) - .with_context(|| format!("Failed to get user `{username}` from user database"))?; + .with_context(|| format!("Failed to get user `{uid}` from user database"))?; + let workdir_path = CString::new( user.dir .to_str() @@ -379,6 +387,7 @@ fn main() -> Result { let muvm_guest_path = find_muvm_exec("muvm-guest")?; + let cwd = env::current_dir()?; let display = env::var("DISPLAY").ok(); let guest_config = GuestConfiguration { command: Launch { @@ -389,12 +398,13 @@ fn main() -> Result { tty: false, privileged: false, }, - username, - uid: getuid().as_raw(), + uid, gid: getgid().as_raw(), host_display: display, merged_rootfs: options.merged_rootfs, emulator: options.emulator, + cwd, + init_commands, }; let mut muvm_config_file = NamedTempFile::new() .context("Failed to create a temporary file to store the muvm guest config")?; diff --git a/crates/muvm/src/cli_options.rs b/crates/muvm/src/cli_options.rs index 9dd01f3..92e608c 100644 --- a/crates/muvm/src/cli_options.rs +++ b/crates/muvm/src/cli_options.rs @@ -21,6 +21,7 @@ pub struct Options { pub privileged: bool, pub publish_ports: Vec, pub emulator: Option, + pub init_commands: Vec, pub command: PathBuf, pub command_args: Vec, } @@ -132,6 +133,15 @@ pub fn options() -> OptionParser { ) .argument::("[[IP:][HOST_PORT]:]GUEST_PORT[/PROTOCOL]") .many(); + let init_commands = long("execute-pre") + .short('x') + .help( + "Command to run inside the VM before guest server starts. + Can be used for e.g. setting up additional mounts. + Can be specified multiple times.", + ) + .argument("COMMAND") + .many(); let command = positional("COMMAND").help("the command you want to execute in the vm"); let command_args = any::("COMMAND_ARGS", |arg| { (!["--help", "-h"].contains(&&*arg)).then_some(arg) @@ -152,6 +162,7 @@ pub fn options() -> OptionParser { privileged, publish_ports, emulator, + init_commands, // positionals command, command_args, diff --git a/crates/muvm/src/config.rs b/crates/muvm/src/config.rs new file mode 100644 index 0000000..d290601 --- /dev/null +++ b/crates/muvm/src/config.rs @@ -0,0 +1,73 @@ +use std::{ + env::{self, VarError}, + io, + path::{Path, PathBuf}, +}; + +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Default)] +pub struct Configuration { + pub execute_pre: Option, +} + +fn get_var_if_exists(name: &str) -> Option> { + match env::var(name) { + Ok(val) => Some(Ok(val)), + Err(VarError::NotPresent) => None, + Err(e) => Some(Err(e.into())), + } +} + +fn get_user_config_path() -> Result { + let mut path = get_var_if_exists("XDG_CONFIG_DIR").map_or_else( + || { + let base = env::var("HOME").context("Failed to get HOME")?; + let mut base = PathBuf::from(base); + base.push(".config"); + Ok(base) + }, + |p| p.map(PathBuf::from), + )?; + path.push("muvm"); + path.push("config.json"); + Ok(path) +} + +fn get_global_config_path() -> Result { + Ok(PathBuf::from("/etc/muvm/config.json")) +} + +fn get_system_config_path() -> Result { + Ok(PathBuf::from("/usr/lib/muvm/config.json")) +} + +const CONFIG_PATHS: &[fn() -> Result] = &[ + get_user_config_path, + get_global_config_path, + get_system_config_path, +]; + +fn read_to_string_if_exists(p: &Path) -> Option> { + match std::fs::read_to_string(p) { + Ok(content) => Some(Ok(content)), + Err(err) if err.kind() == io::ErrorKind::NotFound => None, + Err(e) => Some(Err(e.into())), + } +} + +impl Configuration { + pub fn parse_config_file() -> Result { + let Some(content) = CONFIG_PATHS.iter().find_map(|get_config_path| { + let config_path = match get_config_path() { + Ok(path) => path, + Err(e) => return Some(Err(e)), + }; + read_to_string_if_exists(&config_path) + }) else { + return Ok(Default::default()); + }; + Ok(serde_json::from_str(&content?)?) + } +} diff --git a/crates/muvm/src/guest/bin/muvm-guest.rs b/crates/muvm/src/guest/bin/muvm-guest.rs index 0a4eb3f..892e989 100644 --- a/crates/muvm/src/guest/bin/muvm-guest.rs +++ b/crates/muvm/src/guest/bin/muvm-guest.rs @@ -5,7 +5,7 @@ use std::panic::catch_unwind; use std::process::Command; use std::{cmp, env, fs, thread}; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use muvm::guest::box64::setup_box; use muvm::guest::bridge::pipewire::start_pwbridge; use muvm::guest::bridge::x11::start_x11bridge; @@ -94,13 +94,19 @@ fn main() -> Result<()> { } } + for init_command in options.init_commands { + let code = Command::new(&init_command) + .current_dir(&options.cwd) + .spawn()? + .wait()?; + if !code.success() { + return Err(anyhow!("Executing `{}` failed", init_command.display())); + } + } + configure_network()?; - let run_path = match setup_user( - options.username, - Uid::from(options.uid), - Gid::from(options.gid), - ) { + let run_path = match setup_user(Uid::from(options.uid), Gid::from(options.gid)) { Ok(p) => p, Err(err) => return Err(err).context("Failed to set up user, bailing out"), }; diff --git a/crates/muvm/src/guest/user.rs b/crates/muvm/src/guest/user.rs index feb4324..193c0aa 100644 --- a/crates/muvm/src/guest/user.rs +++ b/crates/muvm/src/guest/user.rs @@ -8,7 +8,7 @@ use anyhow::{anyhow, Context, Result}; use nix::sys::wait::{waitpid, WaitStatus}; use nix::unistd::{fork, setresgid, setresuid, ForkResult, Gid, Uid, User}; -pub fn setup_user(username: String, uid: Uid, gid: Gid) -> Result { +pub fn setup_user(uid: Uid, gid: Gid) -> Result { setup_directories(uid, gid)?; setresgid(gid, gid, Gid::from(0)).context("Failed to setgid")?; @@ -24,10 +24,10 @@ pub fn setup_user(username: String, uid: Uid, gid: Gid) -> Result { // See https://doc.rust-lang.org/std/env/fn.set_var.html#safety env::set_var("XDG_RUNTIME_DIR", &path); - let user = User::from_name(&username) + let user = User::from_uid(uid) .map_err(Into::into) .and_then(|user| user.ok_or_else(|| anyhow!("requested entry not found"))) - .with_context(|| format!("Failed to get user `{username}` from user database"))?; + .with_context(|| format!("Failed to get user `{uid}` from user database"))?; { // SAFETY: Safe if and only if `muvm-guest` program is not multithreaded. diff --git a/crates/muvm/src/lib.rs b/crates/muvm/src/lib.rs index 456af4d..50cc517 100644 --- a/crates/muvm/src/lib.rs +++ b/crates/muvm/src/lib.rs @@ -1,4 +1,5 @@ pub mod cli_options; +pub mod config; pub mod cpu; pub mod env; pub mod hidpipe_common; diff --git a/crates/muvm/src/utils/launch.rs b/crates/muvm/src/utils/launch.rs index 01400c9..7b95dca 100644 --- a/crates/muvm/src/utils/launch.rs +++ b/crates/muvm/src/utils/launch.rs @@ -38,12 +38,13 @@ impl FromStr for Emulator { #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] pub struct GuestConfiguration { pub command: Launch, - pub username: String, pub uid: u32, pub gid: u32, pub host_display: Option, pub merged_rootfs: bool, pub emulator: Option, + pub cwd: PathBuf, + pub init_commands: Vec, } pub const PULSE_SOCKET: u32 = 3333;