mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
Refactor/project structure (#3085)
* refactor project structure * environment-based default registry * fix tests * update build container * use docker platform for iso build emulation * simplify compat * Fix docker platform spec in run-compat.sh * handle riscv compat * fix bug with dep error exists attr * undo removal of sorting * use qemu for iso stage --------- Co-authored-by: Mariusz Kogen <k0gen@pm.me> Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
254
core/src/backup/target/cifs.rs
Normal file
254
core/src/backup/target/cifs.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use imbl_value::InternedString;
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use super::{BackupTarget, BackupTargetId};
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::DatabaseModel;
|
||||
use crate::disk::mount::filesystem::ReadOnly;
|
||||
use crate::disk::mount::filesystem::cifs::Cifs;
|
||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||
use crate::disk::util::{StartOsRecoveryInfo, recovery_info};
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::KeyVal;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct CifsTargets(pub BTreeMap<u32, Cifs>);
|
||||
impl CifsTargets {
|
||||
pub fn new() -> Self {
|
||||
Self(BTreeMap::new())
|
||||
}
|
||||
}
|
||||
impl Map for CifsTargets {
|
||||
type Key = u32;
|
||||
type Value = Cifs;
|
||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||
Self::key_string(key)
|
||||
}
|
||||
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
||||
Ok(InternedString::from_display(key))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CifsBackupTarget {
|
||||
hostname: String,
|
||||
path: PathBuf,
|
||||
username: String,
|
||||
mountable: bool,
|
||||
start_os: BTreeMap<String, StartOsRecoveryInfo>,
|
||||
}
|
||||
|
||||
pub fn cifs<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"add",
|
||||
from_fn_async(add)
|
||||
.no_display()
|
||||
.with_about("Add a new backup target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"update",
|
||||
from_fn_async(update)
|
||||
.no_display()
|
||||
.with_about("Update an existing backup target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
from_fn_async(remove)
|
||||
.no_display()
|
||||
.with_about("Remove an existing backup target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct AddParams {
|
||||
pub hostname: String,
|
||||
pub path: PathBuf,
|
||||
pub username: String,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn add(
|
||||
ctx: RpcContext,
|
||||
AddParams {
|
||||
hostname,
|
||||
path,
|
||||
username,
|
||||
password,
|
||||
}: AddParams,
|
||||
) -> Result<KeyVal<BackupTargetId, BackupTarget>, Error> {
|
||||
let cifs = Cifs {
|
||||
hostname,
|
||||
path: Path::new("/").join(path),
|
||||
username,
|
||||
password,
|
||||
};
|
||||
let guard = TmpMountGuard::mount(&cifs, ReadOnly).await?;
|
||||
let start_os = recovery_info(guard.path()).await?;
|
||||
guard.unmount().await?;
|
||||
let id = ctx
|
||||
.db
|
||||
.mutate(|db| {
|
||||
let id = db
|
||||
.as_private()
|
||||
.as_cifs()
|
||||
.keys()?
|
||||
.into_iter()
|
||||
.max()
|
||||
.map_or(0, |a| a + 1);
|
||||
db.as_private_mut().as_cifs_mut().insert(&id, &cifs)?;
|
||||
Ok(id)
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
Ok(KeyVal {
|
||||
key: BackupTargetId::Cifs { id },
|
||||
value: BackupTarget::Cifs(CifsBackupTarget {
|
||||
hostname: cifs.hostname,
|
||||
path: cifs.path,
|
||||
username: cifs.username,
|
||||
mountable: true,
|
||||
start_os,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct UpdateParams {
|
||||
pub id: BackupTargetId,
|
||||
pub hostname: String,
|
||||
pub path: PathBuf,
|
||||
pub username: String,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
ctx: RpcContext,
|
||||
UpdateParams {
|
||||
id,
|
||||
hostname,
|
||||
path,
|
||||
username,
|
||||
password,
|
||||
}: UpdateParams,
|
||||
) -> Result<KeyVal<BackupTargetId, BackupTarget>, Error> {
|
||||
let id = if let BackupTargetId::Cifs { id } = id {
|
||||
id
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
let cifs = Cifs {
|
||||
hostname,
|
||||
path: Path::new("/").join(path),
|
||||
username,
|
||||
password,
|
||||
};
|
||||
let guard = TmpMountGuard::mount(&cifs, ReadOnly).await?;
|
||||
let start_os = recovery_info(guard.path()).await?;
|
||||
guard.unmount().await?;
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_private_mut()
|
||||
.as_cifs_mut()
|
||||
.as_idx_mut(&id)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", BackupTargetId::Cifs { id }),
|
||||
ErrorKind::NotFound,
|
||||
)
|
||||
})?
|
||||
.ser(&cifs)
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
Ok(KeyVal {
|
||||
key: BackupTargetId::Cifs { id },
|
||||
value: BackupTarget::Cifs(CifsBackupTarget {
|
||||
hostname: cifs.hostname,
|
||||
path: cifs.path,
|
||||
username: cifs.username,
|
||||
mountable: true,
|
||||
start_os,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct RemoveParams {
|
||||
pub id: BackupTargetId,
|
||||
}
|
||||
|
||||
pub async fn remove(ctx: RpcContext, RemoveParams { id }: RemoveParams) -> Result<(), Error> {
|
||||
let id = if let BackupTargetId::Cifs { id } = id {
|
||||
id
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
ctx.db
|
||||
.mutate(|db| db.as_private_mut().as_cifs_mut().remove(&id))
|
||||
.await
|
||||
.result?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load(db: &DatabaseModel, id: u32) -> Result<Cifs, Error> {
|
||||
db.as_private()
|
||||
.as_cifs()
|
||||
.as_idx(&id)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
ErrorKind::NotFound,
|
||||
)
|
||||
})?
|
||||
.de()
|
||||
}
|
||||
|
||||
pub async fn list(db: &DatabaseModel) -> Result<Vec<(u32, CifsBackupTarget)>, Error> {
|
||||
let mut cifs = Vec::new();
|
||||
for (id, model) in db.as_private().as_cifs().as_entries()? {
|
||||
let mount_info = model.de()?;
|
||||
let start_os = async {
|
||||
let guard = TmpMountGuard::mount(&mount_info, ReadOnly).await?;
|
||||
let start_os = recovery_info(guard.path()).await?;
|
||||
guard.unmount().await?;
|
||||
Ok::<_, Error>(start_os)
|
||||
}
|
||||
.await;
|
||||
cifs.push((
|
||||
id,
|
||||
CifsBackupTarget {
|
||||
hostname: mount_info.hostname,
|
||||
path: mount_info.path,
|
||||
username: mount_info.username,
|
||||
mountable: start_os.is_ok(),
|
||||
start_os: start_os.ok().unwrap_or_default(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
Ok(cifs)
|
||||
}
|
||||
411
core/src/backup/target/mod.rs
Normal file
411
core/src/backup/target/mod.rs
Normal file
@@ -0,0 +1,411 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::Parser;
|
||||
use clap::builder::ValueParserFactory;
|
||||
use color_eyre::eyre::eyre;
|
||||
use digest::OutputSizeUser;
|
||||
use digest::generic_array::GenericArray;
|
||||
use exver::Version;
|
||||
use imbl_value::InternedString;
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use self::cifs::CifsBackupTarget;
|
||||
use crate::PackageId;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::DatabaseModel;
|
||||
use crate::disk::mount::backup::BackupMountGuard;
|
||||
use crate::disk::mount::filesystem::block_dev::BlockDev;
|
||||
use crate::disk::mount::filesystem::cifs::Cifs;
|
||||
use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite};
|
||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||
use crate::disk::util::PartitionInfo;
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::{
|
||||
HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable, serialize_display,
|
||||
};
|
||||
use crate::util::{FromStrParser, VersionString};
|
||||
|
||||
pub mod cifs;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum BackupTarget {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Disk {
|
||||
vendor: Option<String>,
|
||||
model: Option<String>,
|
||||
#[serde(flatten)]
|
||||
partition_info: PartitionInfo,
|
||||
},
|
||||
Cifs(CifsBackupTarget),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, TS)]
|
||||
#[ts(type = "string")]
|
||||
pub enum BackupTargetId {
|
||||
Disk { logicalname: PathBuf },
|
||||
Cifs { id: u32 },
|
||||
}
|
||||
impl BackupTargetId {
|
||||
pub fn load(self, db: &DatabaseModel) -> Result<BackupTargetFS, Error> {
|
||||
Ok(match self {
|
||||
BackupTargetId::Disk { logicalname } => {
|
||||
BackupTargetFS::Disk(BlockDev::new(logicalname))
|
||||
}
|
||||
BackupTargetId::Cifs { id } => BackupTargetFS::Cifs(cifs::load(db, id)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for BackupTargetId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BackupTargetId::Disk { logicalname } => write!(f, "disk-{}", logicalname.display()),
|
||||
BackupTargetId::Cifs { id } => write!(f, "cifs-{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for BackupTargetId {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.split_once('-') {
|
||||
Some(("disk", logicalname)) => Ok(BackupTargetId::Disk {
|
||||
logicalname: Path::new(logicalname).to_owned(),
|
||||
}),
|
||||
Some(("cifs", id)) => Ok(BackupTargetId::Cifs { id: id.parse()? }),
|
||||
_ => Err(Error::new(
|
||||
eyre!("Invalid Backup Target ID"),
|
||||
ErrorKind::InvalidBackupTargetId,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ValueParserFactory for BackupTargetId {
|
||||
type Parser = FromStrParser<Self>;
|
||||
fn value_parser() -> Self::Parser {
|
||||
FromStrParser::new()
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for BackupTargetId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserialize_from_str(deserializer)
|
||||
}
|
||||
}
|
||||
impl Serialize for BackupTargetId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serialize_display(self, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum BackupTargetFS {
|
||||
Disk(BlockDev<PathBuf>),
|
||||
Cifs(Cifs),
|
||||
}
|
||||
impl FileSystem for BackupTargetFS {
|
||||
async fn mount<P: AsRef<Path> + Send>(
|
||||
&self,
|
||||
mountpoint: P,
|
||||
mount_type: MountType,
|
||||
) -> Result<(), Error> {
|
||||
match self {
|
||||
BackupTargetFS::Disk(a) => a.mount(mountpoint, mount_type).await,
|
||||
BackupTargetFS::Cifs(a) => a.mount(mountpoint, mount_type).await,
|
||||
}
|
||||
}
|
||||
async fn source_hash(
|
||||
&self,
|
||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||
match self {
|
||||
BackupTargetFS::Disk(a) => a.source_hash().await,
|
||||
BackupTargetFS::Cifs(a) => a.source_hash().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[command(subcommands(cifs::cifs, list, info, mount, umount))]
|
||||
pub fn target<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"cifs",
|
||||
cifs::cifs::<C>().with_about("Add, remove, or update a backup target"),
|
||||
)
|
||||
.subcommand(
|
||||
"list",
|
||||
from_fn_async(list)
|
||||
.with_display_serializable()
|
||||
.with_about("List existing backup targets")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"info",
|
||||
from_fn_async(info)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn::<CliContext, _>(|params, info| {
|
||||
display_backup_info(params.params, info)
|
||||
})
|
||||
.with_about("Display package backup information")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"mount",
|
||||
from_fn_async(mount)
|
||||
.with_about("Mount backup target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"umount",
|
||||
from_fn_async(umount)
|
||||
.no_display()
|
||||
.with_about("Unmount backup target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
// #[command(display(display_serializable))]
|
||||
pub async fn list(ctx: RpcContext) -> Result<BTreeMap<BackupTargetId, BackupTarget>, Error> {
|
||||
let peek = ctx.db.peek().await;
|
||||
let (disks_res, cifs) = tokio::try_join!(
|
||||
crate::disk::util::list(&ctx.os_partitions),
|
||||
cifs::list(&peek),
|
||||
)?;
|
||||
Ok(disks_res
|
||||
.into_iter()
|
||||
.flat_map(|mut disk| {
|
||||
std::mem::take(&mut disk.partitions)
|
||||
.into_iter()
|
||||
.map(|part| {
|
||||
(
|
||||
BackupTargetId::Disk {
|
||||
logicalname: part.logicalname.clone(),
|
||||
},
|
||||
BackupTarget::Disk {
|
||||
vendor: disk.vendor.clone(),
|
||||
model: disk.model.clone(),
|
||||
partition_info: part,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.chain(
|
||||
cifs.into_iter()
|
||||
.map(|(id, cifs)| (BackupTargetId::Cifs { id }, BackupTarget::Cifs(cifs))),
|
||||
)
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BackupInfo {
|
||||
pub version: Version,
|
||||
pub timestamp: Option<DateTime<Utc>>,
|
||||
pub package_backups: BTreeMap<PackageId, PackageBackupInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PackageBackupInfo {
|
||||
pub title: InternedString,
|
||||
pub version: VersionString,
|
||||
pub os_version: Version,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) -> Result<(), Error> {
|
||||
use prettytable::*;
|
||||
|
||||
if let Some(format) = params.format {
|
||||
return display_serializable(format, info);
|
||||
}
|
||||
|
||||
let mut table = Table::new();
|
||||
table.add_row(row![bc =>
|
||||
"ID",
|
||||
"VERSION",
|
||||
"OS VERSION",
|
||||
"TIMESTAMP",
|
||||
]);
|
||||
table.add_row(row![
|
||||
"StartOS",
|
||||
&info.version.to_string(),
|
||||
&info.version.to_string(),
|
||||
&if let Some(ts) = &info.timestamp {
|
||||
ts.to_string()
|
||||
} else {
|
||||
"N/A".to_owned()
|
||||
},
|
||||
]);
|
||||
for (id, info) in info.package_backups {
|
||||
let row = row![
|
||||
&*id,
|
||||
info.version.as_str(),
|
||||
&info.os_version.to_string(),
|
||||
&info.timestamp.to_string(),
|
||||
];
|
||||
table.add_row(row);
|
||||
}
|
||||
table.print_tty(false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct InfoParams {
|
||||
target_id: BackupTargetId,
|
||||
server_id: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, password))]
|
||||
pub async fn info(
|
||||
ctx: RpcContext,
|
||||
InfoParams {
|
||||
target_id,
|
||||
server_id,
|
||||
password,
|
||||
}: InfoParams,
|
||||
) -> Result<BackupInfo, Error> {
|
||||
let guard = BackupMountGuard::mount(
|
||||
TmpMountGuard::mount(&target_id.load(&ctx.db.peek().await)?, ReadWrite).await?,
|
||||
&server_id,
|
||||
&password,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let res = guard.metadata.clone();
|
||||
|
||||
guard.unmount().await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref USER_MOUNTS: Mutex<BTreeMap<BackupTargetId, Result<BackupMountGuard<TmpMountGuard>, TmpMountGuard>>> =
|
||||
Mutex::new(BTreeMap::new());
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct MountParams {
|
||||
target_id: BackupTargetId,
|
||||
#[arg(long)]
|
||||
server_id: Option<String>,
|
||||
password: String, // TODO: rpassword
|
||||
#[arg(long)]
|
||||
allow_partial: bool,
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn mount(
|
||||
ctx: RpcContext,
|
||||
MountParams {
|
||||
target_id,
|
||||
server_id,
|
||||
password,
|
||||
allow_partial,
|
||||
}: MountParams,
|
||||
) -> Result<String, Error> {
|
||||
let server_id = if let Some(server_id) = server_id {
|
||||
server_id
|
||||
} else {
|
||||
ctx.db
|
||||
.peek()
|
||||
.await
|
||||
.into_public()
|
||||
.into_server_info()
|
||||
.into_id()
|
||||
.de()?
|
||||
};
|
||||
|
||||
let mut mounts = USER_MOUNTS.lock().await;
|
||||
|
||||
let existing = mounts.get(&target_id);
|
||||
|
||||
let base = match existing {
|
||||
Some(Ok(a)) => return Ok(a.path().display().to_string()),
|
||||
Some(Err(e)) => e.clone(),
|
||||
None => {
|
||||
TmpMountGuard::mount(&target_id.clone().load(&ctx.db.peek().await)?, ReadWrite).await?
|
||||
}
|
||||
};
|
||||
|
||||
let guard = match BackupMountGuard::mount(base.clone(), &server_id, &password).await {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
if allow_partial {
|
||||
mounts.insert(target_id, Err(base.clone()));
|
||||
let enc_key = BackupMountGuard::<TmpMountGuard>::load_metadata(
|
||||
base.path(),
|
||||
&server_id,
|
||||
&password,
|
||||
)
|
||||
.await
|
||||
.map(|(_, k)| k);
|
||||
return Err(e)
|
||||
.with_ctx(|e| (
|
||||
e.kind,
|
||||
format!(
|
||||
"\nThe base filesystem did successfully mount at {:?}\nWrapped Key: {:?}",
|
||||
base.path(),
|
||||
enc_key
|
||||
)
|
||||
));
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let res = guard.path().display().to_string();
|
||||
|
||||
mounts.insert(target_id, Ok(guard));
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct UmountParams {
|
||||
target_id: Option<BackupTargetId>,
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn umount(_: RpcContext, UmountParams { target_id }: UmountParams) -> Result<(), Error> {
|
||||
let mut mounts = USER_MOUNTS.lock().await; // TODO: move to context
|
||||
if let Some(target_id) = target_id {
|
||||
if let Some(existing) = mounts.remove(&target_id) {
|
||||
match existing {
|
||||
Ok(e) => e.unmount().await?,
|
||||
Err(e) => e.unmount().await?,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (_, existing) in std::mem::take(&mut *mounts) {
|
||||
match existing {
|
||||
Ok(e) => e.unmount().await?,
|
||||
Err(e) => e.unmount().await?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user