mirror of
https://github.com/Kruhlmann/punlock.git
synced 2025-10-28 06:33:34 +00:00
Initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
1123
Cargo.lock
generated
Normal file
1123
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
Normal file
26
Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "punlock"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "punlock"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.98"
|
||||||
|
cfg-if = "1.0.0"
|
||||||
|
clap = { version = "4.5.37", features = ["derive"] }
|
||||||
|
directories = "6.0.0"
|
||||||
|
futures = "0.3.31"
|
||||||
|
jmespath = "0.3.0"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
regex = "1.11.1"
|
||||||
|
rpassword = "7.4.0"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_json = "1.0.140"
|
||||||
|
tokio = { version = "1.44.2", features = ["full"] }
|
||||||
|
toml = "0.8.20"
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
users = "0.11.0"
|
||||||
108
src/bitwarden.rs
Normal file
108
src/bitwarden.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::{config::PunlockConfigurationEntry, email::Email};
|
||||||
|
|
||||||
|
pub struct Bitwarden<S> {
|
||||||
|
email: Email,
|
||||||
|
session: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bitwarden<()> {
|
||||||
|
pub fn new(email: Email) -> Self {
|
||||||
|
Self { email, session: () }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn authenticate(self) -> anyhow::Result<Bitwarden<String>> {
|
||||||
|
Command::new("bw")
|
||||||
|
.args(&["logout"])
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.status()
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
loop {
|
||||||
|
let password = rpassword::prompt_password(&format!(
|
||||||
|
"Enter bitwarden password for {}: ",
|
||||||
|
self.email
|
||||||
|
))
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "read password"))
|
||||||
|
.unwrap_or("".to_string());
|
||||||
|
if password.trim().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let out = Command::new("bw")
|
||||||
|
.args(&["login", self.email.as_ref(), &password, "--raw"])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "bw login"))?;
|
||||||
|
|
||||||
|
if !out.status.success() {
|
||||||
|
let error = String::from_utf8_lossy(&out.stderr);
|
||||||
|
tracing::error!(?error, "bitwarden error");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let session = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||||
|
if session.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Ok(Bitwarden::<String> {
|
||||||
|
email: self.email,
|
||||||
|
session,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bitwarden<String> {
|
||||||
|
pub async fn fetch(&self, entry: &PunlockConfigurationEntry) -> anyhow::Result<String> {
|
||||||
|
let bw = Command::new("bw")
|
||||||
|
.args(&["get", "item", &entry.id, "--session", &self.session])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "bw get"))?;
|
||||||
|
|
||||||
|
let output = bw
|
||||||
|
.wait_with_output()
|
||||||
|
.await
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "bw output"))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let err = String::from_utf8_lossy(&output.stderr);
|
||||||
|
anyhow::bail!("`bw get item` failed: {}", err.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: serde_json::Value = serde_json::from_slice(&output.stdout)
|
||||||
|
.inspect_err(|error| tracing::error!(?error, json = ?output.stdout, "invalid json"))?;
|
||||||
|
|
||||||
|
let expr = jmespath::compile(&entry.query)
|
||||||
|
.inspect_err(|error| tracing::error!(?error, ?entry, "input/expression mismatch"))?;
|
||||||
|
let result = expr
|
||||||
|
.search(&data)
|
||||||
|
.inspect_err(|error| tracing::error!(?error, ?expr, "query failed to apply"))?;
|
||||||
|
|
||||||
|
let secret = match result.as_string() {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => anyhow::bail!("invalid item"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn logout(&self) -> anyhow::Result<()> {
|
||||||
|
Command::new("bw")
|
||||||
|
.args(&["logout", "--session", &self.session])
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.status()
|
||||||
|
.await
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "bw logout"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/config.rs
Normal file
100
src/config.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::{fs::File, io::AsyncWriteExt};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
email::Email,
|
||||||
|
statics::{LATEST_CONFIGURATION_VERSION, SYSTEM_CONFIG_PATH_CANDIDATES},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct PunlockConfigurationEntry {
|
||||||
|
pub id: String,
|
||||||
|
pub query: String,
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct PartialPunlockConfiguration {
|
||||||
|
pub version: Option<String>,
|
||||||
|
pub email: Option<String>,
|
||||||
|
pub entries: Option<Vec<PunlockConfigurationEntry>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Path> for PartialPunlockConfiguration {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(path: &Path) -> anyhow::Result<Self> {
|
||||||
|
tracing::debug!(?path, "loading configuration");
|
||||||
|
let content = std::fs::read_to_string(path).inspect_err(|error| {
|
||||||
|
tracing::error!(?error, ?path, "unable to read configuration file");
|
||||||
|
})?;
|
||||||
|
let config: PartialPunlockConfiguration =
|
||||||
|
toml::from_str(&content).inspect_err(|error| {
|
||||||
|
tracing::error!(?error, ?path, "configuration is invalid");
|
||||||
|
})?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialPunlockConfiguration {
|
||||||
|
pub fn try_from_default_path() -> anyhow::Result<Self> {
|
||||||
|
for path in SYSTEM_CONFIG_PATH_CANDIDATES.iter() {
|
||||||
|
tracing::debug!(?path, "inspecting configuration candidate");
|
||||||
|
if path.exists() {
|
||||||
|
tracing::debug!(?path, "configuration file exists");
|
||||||
|
let cfg: PartialPunlockConfiguration =
|
||||||
|
path.as_path().try_into().inspect_err(|error| {
|
||||||
|
tracing::error!(?error, ?path, "unable to load configuration")
|
||||||
|
})?;
|
||||||
|
return Ok(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anyhow::bail!("no default configuration found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct PunlockConfiguration {
|
||||||
|
pub version: String,
|
||||||
|
pub email: Email,
|
||||||
|
pub entries: Vec<PunlockConfigurationEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<PartialPunlockConfiguration> for PunlockConfiguration {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: PartialPunlockConfiguration) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
version: value
|
||||||
|
.version
|
||||||
|
.unwrap_or(LATEST_CONFIGURATION_VERSION.to_string()),
|
||||||
|
email: if let Some(e) = value.email {
|
||||||
|
e.as_str().try_into().inspect_err(
|
||||||
|
|error| tracing::error!(?error, email = ?e, "invalid email in configuration"),
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
Email::from_stdin()
|
||||||
|
},
|
||||||
|
entries: value.entries.unwrap_or(Vec::new()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PunlockConfiguration {
|
||||||
|
pub async fn write_to_disk(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let contents = toml::to_string_pretty(self)?;
|
||||||
|
let mut file = File::create(path).await.inspect_err(|error| {
|
||||||
|
tracing::error!(?path, ?error, "unable to create configuration file")
|
||||||
|
})?;
|
||||||
|
file.write_all(contents.as_bytes())
|
||||||
|
.await
|
||||||
|
.inspect_err(|error| {
|
||||||
|
tracing::error!(?path, ?error, "unable to write configuration to file")
|
||||||
|
})?;
|
||||||
|
tracing::debug!(?path, "wrote configuration");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/email.rs
Normal file
59
src/email.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::statics::EMAIL_REGEX;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Email(String);
|
||||||
|
|
||||||
|
impl Email {
|
||||||
|
pub fn from_stdin() -> Self {
|
||||||
|
loop {
|
||||||
|
eprint!("Please enter your email: ");
|
||||||
|
std::io::stdout()
|
||||||
|
.flush()
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "stdout flush"))
|
||||||
|
.ok();
|
||||||
|
let mut input = String::new();
|
||||||
|
std::io::stdin()
|
||||||
|
.read_line(&mut input)
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "stdin readline"))
|
||||||
|
.ok();
|
||||||
|
let email = input.trim();
|
||||||
|
if EMAIL_REGEX.is_match(email) {
|
||||||
|
return Self(email.to_string());
|
||||||
|
} else {
|
||||||
|
tracing::error!(?email, "invalid email");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_ref(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Email {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> anyhow::Result<Self> {
|
||||||
|
if EMAIL_REGEX.is_match(value) {
|
||||||
|
Ok(Self(value.to_string()))
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("invalid email {}", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for Email {
|
||||||
|
fn into(self) -> String {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Email {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/lib.rs
Normal file
5
src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod bitwarden;
|
||||||
|
pub mod config;
|
||||||
|
pub mod email;
|
||||||
|
pub mod statics;
|
||||||
|
pub mod store;
|
||||||
52
src/main.rs
Normal file
52
src/main.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use punlock::{
|
||||||
|
bitwarden::Bitwarden,
|
||||||
|
config::{PartialPunlockConfiguration, PunlockConfiguration},
|
||||||
|
statics::USER_CONFIG_FILE_PATH,
|
||||||
|
store::UnmountedSecretStore,
|
||||||
|
};
|
||||||
|
use tracing_subscriber::{
|
||||||
|
EnvFilter, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(name = "punlock", about = "PasswordUNLOCKer")]
|
||||||
|
struct Cli {
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub config: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let formattter = tracing_subscriber::fmt::Layer::new()
|
||||||
|
.with_thread_names(true)
|
||||||
|
.with_span_events(FmtSpan::FULL);
|
||||||
|
let filter = EnvFilter::try_from_default_env()?;
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(formattter)
|
||||||
|
.with(filter)
|
||||||
|
.try_init()
|
||||||
|
.inspect_err(|error| eprintln!("error configuring tracing subscriber: {error}"))?;
|
||||||
|
tracing::debug!("tracing initialized");
|
||||||
|
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let config: PartialPunlockConfiguration = if let Some(path) = cli.config {
|
||||||
|
<&Path as TryInto<PartialPunlockConfiguration>>::try_into(path.as_path())?
|
||||||
|
} else {
|
||||||
|
PartialPunlockConfiguration::try_from_default_path()?
|
||||||
|
};
|
||||||
|
let config: PunlockConfiguration = config.try_into()?;
|
||||||
|
|
||||||
|
config
|
||||||
|
.write_to_disk(USER_CONFIG_FILE_PATH.as_path())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let bitwarden = Bitwarden::new(config.email).authenticate().await?;
|
||||||
|
let store = UnmountedSecretStore::new(bitwarden)
|
||||||
|
.into_platform_store()
|
||||||
|
.await?;
|
||||||
|
store.write_secrets(&config.entries).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
21
src/statics.rs
Normal file
21
src/statics.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use directories::ProjectDirs;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref LATEST_CONFIGURATION_VERSION: &'static str = "1.0.0";
|
||||||
|
pub static ref CONFIG_FILE_NAME: &'static str = "config.toml";
|
||||||
|
pub static ref PROJECT_DIRS: ProjectDirs =
|
||||||
|
ProjectDirs::from("dev", "kruhlmann", "punlock").unwrap();
|
||||||
|
pub static ref USER_CONFIG_FILE_PATH: PathBuf =
|
||||||
|
PROJECT_DIRS.config_dir().join(CONFIG_FILE_NAME.to_string());
|
||||||
|
pub static ref SYSTEM_CONFIG_PATH_CANDIDATES: Vec<PathBuf> = vec![
|
||||||
|
PathBuf::from(CONFIG_FILE_NAME.to_string()),
|
||||||
|
USER_CONFIG_FILE_PATH.to_path_buf(),
|
||||||
|
PathBuf::from("/etc/punlock/").join("CONFIG_FILE_NAME"),
|
||||||
|
]
|
||||||
|
.to_vec();
|
||||||
|
pub static ref EMAIL_REGEX: Regex = Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").unwrap();
|
||||||
|
}
|
||||||
121
src/store.rs
Normal file
121
src/store.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use std::{path::PathBuf, process::Stdio};
|
||||||
|
|
||||||
|
use futures::{StreamExt, stream::FuturesUnordered};
|
||||||
|
use tokio::{fs::File, io::AsyncWriteExt, process::Command};
|
||||||
|
|
||||||
|
use crate::{bitwarden::Bitwarden, config::PunlockConfigurationEntry, statics::PROJECT_DIRS};
|
||||||
|
|
||||||
|
pub struct UnmountedSecretStore {
|
||||||
|
bitwarden: Bitwarden<String>,
|
||||||
|
root_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnmountedSecretStore {
|
||||||
|
pub fn new(bitwarden: Bitwarden<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
bitwarden,
|
||||||
|
root_path: PROJECT_DIRS.cache_dir().to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnmountedSecretStore {
|
||||||
|
pub async fn into_platform_store(self) -> anyhow::Result<UnixSecretStore> {
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(target_os = "linux")] {
|
||||||
|
let store = UnixSecretStore::new(self.bitwarden, self.root_path).unmount().await?.mount().await?;
|
||||||
|
Ok(store)
|
||||||
|
} else if #[cfg(target_os = "macos")] {
|
||||||
|
// mount_ramdisk_macos(mount_point)?;
|
||||||
|
panic!("todo");
|
||||||
|
} else {
|
||||||
|
panic!("todo");
|
||||||
|
// debug!("On Windows or unsupported OS: using plain dir at {}", mount_point.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UnixSecretStore {
|
||||||
|
bitwarden: Bitwarden<String>,
|
||||||
|
root_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnixSecretStore {
|
||||||
|
pub fn new(bitwarden: Bitwarden<String>, root_path: PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
bitwarden,
|
||||||
|
root_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write_secrets(
|
||||||
|
&self,
|
||||||
|
entries: &Vec<PunlockConfigurationEntry>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut tasks = FuturesUnordered::new();
|
||||||
|
for entry in entries.iter() {
|
||||||
|
tasks.push(async move {
|
||||||
|
let secret = self.bitwarden.fetch(&entry).await?;
|
||||||
|
let path = self.root_path.join(&entry.path);
|
||||||
|
tokio::fs::create_dir_all(path.parent().unwrap_or(&path)).await.inspect_err(|error| tracing::error!(?error, "create secret directory"))?;
|
||||||
|
let mut file = File::create(path).await.inspect_err(
|
||||||
|
|error| tracing::error!(?error, id = ?entry.id, path = ?entry.path, "create secret file"),
|
||||||
|
)?;
|
||||||
|
file.write_all(secret.as_bytes()).await.inspect_err(
|
||||||
|
|error| tracing::error!(?error, id = ?entry.id, path = ?entry.path, "write secret"),
|
||||||
|
)?;
|
||||||
|
Ok::<(String, String), anyhow::Error>((entry.id.clone(), entry.path.clone()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
while let Some(result) = tasks.next().await {
|
||||||
|
match result {
|
||||||
|
Ok((id, path)) => tracing::info!(?id, ?path, "load secret"),
|
||||||
|
Err(error) => tracing::error!(?error, "load secret"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn unmount(self) -> anyhow::Result<Self> {
|
||||||
|
if self.root_path.exists() {
|
||||||
|
Command::new("sudo")
|
||||||
|
.args(&["umount", self.root_path.to_str().unwrap()])
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.status()
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
tokio::fs::remove_dir_all(&self.root_path).await.ok();
|
||||||
|
}
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn mount(self) -> anyhow::Result<Self> {
|
||||||
|
tokio::fs::create_dir_all(&self.root_path)
|
||||||
|
.await
|
||||||
|
.inspect_err(
|
||||||
|
|error| tracing::error!(?error, path = ?self.root_path, "unable to create secret path"),
|
||||||
|
)?;
|
||||||
|
let status = Command::new("sudo")
|
||||||
|
.args(&["mount", "-t", "tmpfs", "-o", "size=50M", "tmpfs"])
|
||||||
|
.arg(&self.root_path)
|
||||||
|
.status()
|
||||||
|
.await
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "mount failed"))?;
|
||||||
|
if !status.success() {
|
||||||
|
anyhow::bail!("mount command failed with {}", status);
|
||||||
|
}
|
||||||
|
tracing::debug!(path = ?self.root_path, "tmpfs mounted");
|
||||||
|
|
||||||
|
let uid = users::get_current_uid();
|
||||||
|
let gid = users::get_current_gid();
|
||||||
|
Command::new("sudo")
|
||||||
|
.args(&["chown", &format!("{}:{}", uid, gid)])
|
||||||
|
.arg(&self.root_path)
|
||||||
|
.status()
|
||||||
|
.await
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "chown failed"))?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user