mod keys;
use keys::*;
mod checkout;
use checkout::*;
mod commit;
use commit::*;
mod log;
use log::*;
mod ls;
use ls::*;
mod wipe;
use wipe::*;
mod zfs;
use zfs::*;
#[cfg(feature = "fuse")]
pub mod mount;
#[cfg(feature = "fuse")]
pub use mount::*;
use crate::{
config::{Key, SymmetricKey, YubikeyCRConfig, YubikeyCRKey},
prelude::*,
};
use abscissa_core::{Command, Configurable, Runnable};
use clap::{ArgGroup, Parser};
use std::path::PathBuf;
use std::str::FromStr;
pub const CONFIG_FILE: &str = "zerostash.toml";
#[derive(Debug, Parser)]
pub enum ZerostashCmd {
Checkout(Checkout),
Commit(Commit),
Log(Log),
Ls(Ls),
#[cfg(feature = "fuse")]
Mount(Mount),
Keys(Keys),
Wipe(Wipe),
#[clap(subcommand)]
Zfs(Zfs),
}
#[derive(Command, Debug, Parser)]
#[clap(author, about, version)]
pub struct EntryPoint {
#[clap(subcommand)]
cmd: Box<ZerostashCmd>,
#[clap(short, long, default_value_t = 0)]
pub verbose: usize,
#[clap(short, long, value_name = "PATH")]
pub config: Option<String>,
#[clap(long)]
pub insecure_config: bool,
}
#[derive(clap::Args, Clone, Debug)]
#[clap(group(
ArgGroup::new("key")
.args(&["keyfile", "keystring", "yubikey"]),
))]
pub struct StashArgs {
pub stash: String,
#[clap(flatten)]
pub symmetric_key: SymmetricKey,
#[clap(short, long, value_name = "PATH")]
pub keyfile: Option<PathBuf>,
#[clap(short = 'K', value_name = "TOML", long)]
pub keystring: Option<String>,
#[clap(short, long)]
pub yubikey: bool,
#[clap(long)]
pub commit_id: Option<infinitree::tree::CommitId>,
}
impl StashArgs {
pub(crate) fn key(&self) -> Option<Key> {
let args = self.clone();
if let Some(path) = args.keyfile {
Some(Key::KeyFile { path })
} else if let Some(s) = args.keystring {
Some(toml::from_str(&s).expect("Invalid TOML"))
} else if args.yubikey {
Some(Key::Yubikey(YubikeyCRKey {
credentials: self.symmetric_key.clone(),
config: YubikeyCRConfig::default(),
}))
} else if !self.symmetric_key.is_empty() {
Some(Key::Userpass(self.symmetric_key.clone()))
} else {
None
}
}
pub(crate) fn parse_stash(&self) -> crate::config::Stash {
crate::config::Stash::from_str(&self.stash).unwrap()
}
pub(crate) fn open_with(&self, key: Option<Key>) -> Stash {
let mut stash = crate::config::Stash::from_str(&self.stash)
.unwrap()
.open_or_new(key)
.unwrap();
if let Some(commit) = self.commit_id {
stash.filter_commits(infinitree::tree::CommitFilter::UpTo(commit));
}
stash
}
pub(crate) fn open(&self) -> Stash {
self.open_with(self.key())
}
}
impl Runnable for EntryPoint {
fn run(&self) {
use ZerostashCmd::*;
abscissa_tokio::run(&APP, async move {
match &*self.cmd {
Checkout(cmd) => cmd.run().await,
Commit(cmd) => cmd.run().await,
Log(cmd) => cmd.run().await,
Ls(cmd) => cmd.run().await,
Keys(cmd) => cmd.run().await,
Wipe(cmd) => cmd.run().await,
Zfs(cmd) => cmd.run().await,
#[cfg(feature = "fuse")]
Mount(cmd) => cmd.run().await,
}
})
.unwrap()
}
}
impl Configurable<ZerostashConfig> for EntryPoint {
fn config_path(&self) -> Option<PathBuf> {
let filename = self
.config
.as_ref()
.map(PathBuf::from)
.unwrap_or_else(ZerostashConfig::path);
if filename.exists() {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let file_mode = std::fs::metadata(&filename).ok()?.mode();
if !self.insecure_config && (file_mode & 0o700) != (file_mode & 0o777) {
panic!(
"Config file {filename:?} must not be accessible for other users! Try running `chmod 600 {filename:?}`"
)
}
}
Some(filename)
} else {
None
}
}
}