use super::prelude::*;
use users::{get_current_gid, get_current_uid};
use std::{io::IsTerminal, path::Path};
const TOOLBX_ENV: &str = "/run/.toolboxenv";
const CONTAINER_ENV: &str = "/run/.containerenv";
const OS_RELEASE: &str = "/etc/os-release";
#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Toolbx {
name: String,
}
impl fmt::Display for Toolbx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "toolbx '{}'", self.name)
}
}
impl Toolbx {
pub fn new(name: Option<String>) -> Result<Toolbx, NewToolbxError> {
let name = match name {
Some(name) if !name.is_empty() => name,
_ => match Toolbx::default_name() {
Ok(toolbx_name) => toolbx_name,
Err(e) => return Err(NewToolbxError::UnknownDefault(e)),
},
};
let ret = Self { name: name.clone() };
ret.start()
.map_err(|e| NewToolbxError::CannotStart { source: e, name })?;
Ok(ret)
}
pub fn start(&self) -> Result<(), StartToolbxError> {
let output = std::process::Command::new("podman")
.args(["start", &self.name])
.output()
.map_err(|e| match e.kind() {
std::io::ErrorKind::NotFound => StartToolbxError::NeedPodman,
_ => StartToolbxError::IoError(e),
})?;
if output.status.success() {
Ok(())
} else {
let matcher = OutputMatcher::new(&output);
if matcher.starts_with("Error: no container with name or ID")
&& matcher.contains("found: no such container")
{
Err(StartToolbxError::NonExistent(self.name.clone()))
} else {
Err(StartToolbxError::Podman(output))
}
}
}
pub fn current() -> Result<Toolbx, CurrentToolbxError> {
if !detect() {
return Err(CurrentToolbxError::NotAToolbx);
}
let content = std::fs::read_to_string(CONTAINER_ENV).map_err(|e| {
CurrentToolbxError::Environment {
env_file: CONTAINER_ENV.to_string(),
source: e,
}
})?;
let name = content
.lines()
.find(|line| line.contains("name=\""))
.ok_or_else(|| CurrentToolbxError::Name(CONTAINER_ENV.to_string()))?
.trim_start_matches("name=\"")
.trim_end_matches('"');
Ok(Toolbx {
name: name.to_string(),
})
}
pub fn default_name() -> Result<String, DefaultToolbxError> {
debug!("Determining default toolbx name via {}", OS_RELEASE);
let content =
std::fs::read_to_string(OS_RELEASE).map_err(|e| DefaultToolbxError::UnknownOs {
file: OS_RELEASE.to_string(),
source: e,
})?;
let id = content
.lines()
.find(|line| line.starts_with("ID="))
.map(|line| line.trim_start_matches("ID=").trim_matches('"'))
.ok_or(DefaultToolbxError::Id)?;
let version_id = content
.lines()
.find(|line| line.starts_with("VERSION_ID="))
.map(|line| line.trim_start_matches("VERSION_ID=").trim_matches('"'))
.ok_or(DefaultToolbxError::VersionId)?;
Ok(format!("{}-toolbox-{}", id, version_id))
}
}
#[async_trait]
impl environment::IsEnvironment for Toolbx {
type Err = Error;
async fn exists(&self) -> bool {
if detect() {
true
} else if let Environment::Host(host) = environment::current() {
#[allow(irrefutable_let_patterns)]
if let Ok(mut cmd) = host
.execute(crate::environment::cmd!("toolbox", "--version"))
.await
{
cmd.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.await
.map(|status| status.success())
.unwrap_or(false)
} else {
false
}
} else {
false
}
}
async fn execute(&self, command: CommandLine) -> Result<Command, Self::Err> {
debug!("preparing execution: {}", command);
let mut cmd: Command;
match environment::current() {
Environment::Distrobox(_) => {
return Err(Error::Unimplemented(
"running in a toolbx from a distrobox".to_string(),
));
}
Environment::Toolbx(t) => {
if self == &t {
if command.get_privileged() {
cmd = Command::new("sudo");
if !command.get_interactive() {
cmd.arg("-n");
}
cmd.arg(command.command());
} else {
cmd = Command::new(command.command());
}
cmd.args(command.args());
} else {
return Err(Error::Unimplemented(
"running in a toolbx from another toolbx".to_string(),
));
}
}
Environment::Host(_) => {
cmd = Command::new("podman");
cmd.args(["exec", "-i"]);
cmd.arg("--user");
cmd.arg(format!("{}:{}", get_current_uid(), get_current_gid()));
cmd.arg("--workdir");
cmd.arg(std::env::current_dir().map_err(Error::UnknownCwd)?);
for var in environment::read_env_vars() {
cmd.args(["-e", &var]);
}
cmd.args(["--detach-keys", ""]);
if std::io::stdout().is_terminal() && std::io::stdin().is_terminal() {
cmd.arg("-t");
}
cmd.arg(&self.name);
if command.get_privileged() {
cmd.args(["sudo", "-S", "-E"]);
}
cmd.arg(command.command()).args(command.args());
}
#[cfg(test)]
Environment::Mock(_) => unimplemented!(),
}
trace!("full command: {:?}", cmd);
Ok(cmd)
}
}
pub fn detect() -> bool {
Path::new(TOOLBX_ENV).exists()
}
#[derive(Debug, ThisError, Display)]
pub enum StartToolbxError {
NeedPodman,
Podman(std::process::Output),
NonExistent(String),
IoError(#[from] std::io::Error),
}
#[derive(Debug, ThisError, Display)]
pub enum NewToolbxError {
UnknownDefault(#[from] DefaultToolbxError),
CannotStart {
source: StartToolbxError,
name: String,
},
}
#[derive(Debug, ThisError, Display)]
pub enum CurrentToolbxError {
Environment {
env_file: String,
source: std::io::Error,
},
NotAToolbx,
Name(String),
}
#[derive(Debug, ThisError, Display)]
pub enum DefaultToolbxError {
UnknownOs {
file: String,
source: std::io::Error,
},
Id,
VersionId,
}
#[derive(Debug, ThisError, Display)]
pub enum Error {
UnknownCwd(#[from] std::io::Error),
Unimplemented(String),
}