Feature/backup fs (#2665)

* port 040 config, WIP

* update fixtures

* use taiga modal for backups too

* fix: update Taiga UI and refactor everything to work

* chore: package-lock

* fix interfaces and mocks for interfaces

* better mocks

* function to transform old spec to new

* delete unused fns

* delete unused FE config utils

* fix exports from sdk

* reorganize exports

* functions to translate config

* rename unionSelectKey and unionValueKey

* new backup fs

* update sdk types

* change types, include fuse module

* fix casing

* rework setup wiz

* rework UI

* only fuse3

* fix arm build

* misc fixes

* fix duplicate server select

* fix: fix throwing inside dialog

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
Co-authored-by: waterplea <alexander@inkin.ru>
Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
This commit is contained in:
Aiden McClelland
2024-07-11 11:32:46 -06:00
committed by GitHub
parent f2a02b392e
commit 87322744d4
67 changed files with 880 additions and 563 deletions

View File

@@ -164,7 +164,7 @@ pub async fn backup_all(
.decrypt(&ctx)?;
let password = password.decrypt(&ctx)?;
let ((fs, package_ids), status_guard) = (
let ((fs, package_ids, server_id), status_guard) = (
ctx.db
.mutate(|db| {
check_password_against_db(db, &password)?;
@@ -181,7 +181,11 @@ pub async fn backup_all(
.collect()
};
assure_backing_up(db, &package_ids)?;
Ok((fs, package_ids))
Ok((
fs,
package_ids,
db.as_public().as_server_info().as_id().de()?,
))
})
.await?,
BackupStatusGuard::new(ctx.db.clone()),
@@ -189,6 +193,7 @@ pub async fn backup_all(
let mut backup_guard = BackupMountGuard::mount(
TmpMountGuard::mount(&fs, ReadWrite).await?,
&server_id,
&old_password_decrypted,
)
.await?;
@@ -298,11 +303,11 @@ async fn perform_backup(
let ui = ctx.db.peek().await.into_public().into_ui().de()?;
let mut os_backup_file =
AtomicFile::new(backup_guard.path().join("os-backup.cbor"), None::<PathBuf>)
AtomicFile::new(backup_guard.path().join("os-backup.json"), None::<PathBuf>)
.await
.with_kind(ErrorKind::Filesystem)?;
os_backup_file
.write_all(&IoFormat::Cbor.to_vec(&OsBackup {
.write_all(&IoFormat::Json.to_vec(&OsBackup {
account: ctx.account.read().await.clone(),
ui,
})?)
@@ -325,22 +330,23 @@ async fn perform_backup(
dir_copy(luks_folder, &luks_folder_bak, None).await?;
}
let timestamp = Some(Utc::now());
let timestamp = Utc::now();
backup_guard.unencrypted_metadata.version = crate::version::Current::new().semver().into();
backup_guard.unencrypted_metadata.full = true;
backup_guard.unencrypted_metadata.hostname = ctx.account.read().await.hostname.clone();
backup_guard.unencrypted_metadata.timestamp = timestamp.clone();
backup_guard.metadata.version = crate::version::Current::new().semver().into();
backup_guard.metadata.timestamp = timestamp;
backup_guard.metadata.timestamp = Some(timestamp);
backup_guard.metadata.package_backups = package_backups;
backup_guard.save().await?;
backup_guard.save_and_unmount().await?;
ctx.db
.mutate(|v| {
v.as_public_mut()
.as_server_info_mut()
.as_last_backup_mut()
.ser(&timestamp)
.ser(&Some(timestamp))
})
.await?;

View File

@@ -44,9 +44,14 @@ pub async fn restore_packages_rpc(
password,
}: RestorePackageParams,
) -> Result<(), Error> {
let fs = target_id.load(&ctx.db.peek().await)?;
let backup_guard =
BackupMountGuard::mount(TmpMountGuard::mount(&fs, ReadWrite).await?, &password).await?;
let peek = ctx.db.peek().await;
let fs = target_id.load(&peek)?;
let backup_guard = BackupMountGuard::mount(
TmpMountGuard::mount(&fs, ReadWrite).await?,
&peek.as_public().as_server_info().as_id().de()?,
&password,
)
.await?;
let tasks = restore_packages(&ctx, backup_guard, ids).await?;
@@ -73,7 +78,8 @@ pub async fn recover_full_embassy(
disk_guid: Arc<String>,
start_os_password: String,
recovery_source: TmpMountGuard,
recovery_password: Option<String>,
server_id: &str,
recovery_password: &str,
SetupExecuteProgress {
init_phases,
restore_phase,
@@ -82,14 +88,11 @@ pub async fn recover_full_embassy(
) -> Result<(SetupResult, RpcContext), Error> {
let mut restore_phase = restore_phase.or_not_found("restore progress")?;
let backup_guard = BackupMountGuard::mount(
recovery_source,
recovery_password.as_deref().unwrap_or_default(),
)
.await?;
let backup_guard =
BackupMountGuard::mount(recovery_source, server_id, recovery_password).await?;
let os_backup_path = backup_guard.path().join("os-backup.cbor");
let mut os_backup: OsBackup = IoFormat::Cbor.from_slice(
let os_backup_path = backup_guard.path().join("os-backup.json");
let mut os_backup: OsBackup = IoFormat::Json.from_slice(
&tokio::fs::read(&os_backup_path)
.await
.with_ctx(|_| (ErrorKind::Filesystem, os_backup_path.display().to_string()))?,

View File

@@ -14,7 +14,7 @@ use crate::db::model::DatabaseModel;
use crate::disk::mount::filesystem::cifs::Cifs;
use crate::disk::mount::filesystem::ReadOnly;
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
use crate::disk::util::{recovery_info, EmbassyOsRecoveryInfo};
use crate::disk::util::{recovery_info, StartOsRecoveryInfo};
use crate::prelude::*;
use crate::util::serde::KeyVal;
@@ -43,7 +43,7 @@ pub struct CifsBackupTarget {
path: PathBuf,
username: String,
mountable: bool,
start_os: Option<EmbassyOsRecoveryInfo>,
start_os: BTreeMap<String, StartOsRecoveryInfo>,
}
pub fn cifs<C: Context>() -> ParentHandler<C> {
@@ -239,7 +239,7 @@ pub async fn list(db: &DatabaseModel) -> Result<Vec<(u32, CifsBackupTarget)>, Er
path: mount_info.path,
username: mount_info.username,
mountable: start_os.is_ok(),
start_os: start_os.ok().and_then(|a| a),
start_os: start_os.ok().unwrap_or_default(),
},
));
}

View File

@@ -157,6 +157,16 @@ pub fn target<C: Context>() -> ParentHandler<C> {
})
.with_call_remote::<CliContext>(),
)
.subcommand(
"mount",
from_fn_async(mount).with_call_remote::<CliContext>(),
)
.subcommand(
"umount",
from_fn_async(umount)
.no_display()
.with_call_remote::<CliContext>(),
)
}
// #[command(display(display_serializable))]
@@ -250,6 +260,7 @@ fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) {
#[command(rename_all = "kebab-case")]
pub struct InfoParams {
target_id: BackupTargetId,
server_id: String,
password: String,
}
@@ -258,11 +269,13 @@ 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?;
@@ -284,6 +297,7 @@ lazy_static::lazy_static! {
#[command(rename_all = "kebab-case")]
pub struct MountParams {
target_id: BackupTargetId,
server_id: String,
password: String,
}
@@ -292,6 +306,7 @@ pub async fn mount(
ctx: RpcContext,
MountParams {
target_id,
server_id,
password,
}: MountParams,
) -> Result<String, Error> {
@@ -303,6 +318,7 @@ pub async fn mount(
let guard = BackupMountGuard::mount(
TmpMountGuard::mount(&target_id.clone().load(&ctx.db.peek().await)?, ReadWrite).await?,
&server_id,
&password,
)
.await?;

View File

@@ -1,5 +1,7 @@
use std::path::{Path, PathBuf};
use itertools::Itertools;
use lazy_format::lazy_format;
use rpc_toolkit::{from_fn_async, CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize};
@@ -102,10 +104,18 @@ fn display_disk_info(params: WithIoFormat<Empty>, args: Vec<DiskInfo>) {
} else {
"N/A"
},
&if let Some(eos) = part.start_os.as_ref() {
eos.version.to_string()
} else {
&if part.start_os.is_empty() {
"N/A".to_owned()
} else if part.start_os.len() == 1 {
part.start_os
.first_key_value()
.map(|(_, info)| info.version.to_string())
.unwrap()
} else {
part.start_os
.iter()
.map(|(id, info)| lazy_format!("{} ({})", info.version, id))
.join(", ")
},
];
table.add_row(row);

View File

@@ -11,9 +11,10 @@ use super::filesystem::ecryptfs::EcryptFS;
use super::guard::{GenericMountGuard, TmpMountGuard};
use crate::auth::check_password;
use crate::backup::target::BackupInfo;
use crate::disk::mount::filesystem::backupfs::BackupFS;
use crate::disk::mount::filesystem::ReadWrite;
use crate::disk::mount::guard::SubPath;
use crate::disk::util::EmbassyOsRecoveryInfo;
use crate::disk::util::StartOsRecoveryInfo;
use crate::util::crypto::{decrypt_slice, encrypt_slice};
use crate::util::serde::IoFormat;
use crate::{Error, ErrorKind, ResultExt};
@@ -23,29 +24,27 @@ pub struct BackupMountGuard<G: GenericMountGuard> {
backup_disk_mount_guard: Option<G>,
encrypted_guard: Option<TmpMountGuard>,
enc_key: String,
pub unencrypted_metadata: EmbassyOsRecoveryInfo,
unencrypted_metadata_path: PathBuf,
pub unencrypted_metadata: StartOsRecoveryInfo,
pub metadata: BackupInfo,
}
impl<G: GenericMountGuard> BackupMountGuard<G> {
fn backup_disk_path(&self) -> &Path {
if let Some(guard) = &self.backup_disk_mount_guard {
guard.path()
} else {
unreachable!()
}
}
#[instrument(skip_all)]
pub async fn mount(backup_disk_mount_guard: G, password: &str) -> Result<Self, Error> {
pub async fn mount(
backup_disk_mount_guard: G,
server_id: &str,
password: &str,
) -> Result<Self, Error> {
let backup_disk_path = backup_disk_mount_guard.path();
let unencrypted_metadata_path =
backup_disk_path.join("EmbassyBackups/unencrypted-metadata.cbor");
let mut unencrypted_metadata: EmbassyOsRecoveryInfo =
let backup_dir = backup_disk_path.join("StartOSBackups").join(server_id);
let unencrypted_metadata_path = backup_dir.join("unencrypted-metadata.json");
let crypt_path = backup_dir.join("crypt");
let mut unencrypted_metadata: StartOsRecoveryInfo =
if tokio::fs::metadata(&unencrypted_metadata_path)
.await
.is_ok()
{
IoFormat::Cbor.from_slice(
IoFormat::Json.from_slice(
&tokio::fs::read(&unencrypted_metadata_path)
.await
.with_ctx(|_| {
@@ -56,6 +55,9 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
})?,
)?
} else {
if tokio::fs::metadata(&crypt_path).await.is_ok() {
tokio::fs::remove_dir_all(&crypt_path).await?;
}
Default::default()
};
let enc_key = if let (Some(hash), Some(wrapped_key)) = (
@@ -96,7 +98,6 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
));
}
let crypt_path = backup_disk_path.join("EmbassyBackups/crypt");
if tokio::fs::metadata(&crypt_path).await.is_err() {
tokio::fs::create_dir_all(&crypt_path).await.with_ctx(|_| {
(
@@ -106,11 +107,11 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
})?;
}
let encrypted_guard =
TmpMountGuard::mount(&EcryptFS::new(&crypt_path, &enc_key), ReadWrite).await?;
TmpMountGuard::mount(&BackupFS::new(&crypt_path, &enc_key), ReadWrite).await?;
let metadata_path = encrypted_guard.path().join("metadata.cbor");
let metadata_path = encrypted_guard.path().join("metadata.json");
let metadata: BackupInfo = if tokio::fs::metadata(&metadata_path).await.is_ok() {
IoFormat::Cbor.from_slice(&tokio::fs::read(&metadata_path).await.with_ctx(|_| {
IoFormat::Json.from_slice(&tokio::fs::read(&metadata_path).await.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
metadata_path.display().to_string(),
@@ -124,6 +125,7 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
backup_disk_mount_guard: Some(backup_disk_mount_guard),
encrypted_guard: Some(encrypted_guard),
enc_key,
unencrypted_metadata_path,
unencrypted_metadata,
metadata,
})
@@ -152,20 +154,17 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
#[instrument(skip_all)]
pub async fn save(&self) -> Result<(), Error> {
let metadata_path = self.path().join("metadata.cbor");
let backup_disk_path = self.backup_disk_path();
let metadata_path = self.path().join("metadata.json");
let mut file = AtomicFile::new(&metadata_path, None::<PathBuf>)
.await
.with_kind(ErrorKind::Filesystem)?;
file.write_all(&IoFormat::Cbor.to_vec(&self.metadata)?)
file.write_all(&IoFormat::Json.to_vec(&self.metadata)?)
.await?;
file.save().await.with_kind(ErrorKind::Filesystem)?;
let unencrypted_metadata_path =
backup_disk_path.join("EmbassyBackups/unencrypted-metadata.cbor");
let mut file = AtomicFile::new(&unencrypted_metadata_path, None::<PathBuf>)
let mut file = AtomicFile::new(&self.unencrypted_metadata_path, None::<PathBuf>)
.await
.with_kind(ErrorKind::Filesystem)?;
file.write_all(&IoFormat::Cbor.to_vec(&self.unencrypted_metadata)?)
file.write_all(&IoFormat::Json.to_vec(&self.unencrypted_metadata)?)
.await?;
file.save().await.with_kind(ErrorKind::Filesystem)?;
Ok(())

View File

@@ -0,0 +1,55 @@
use std::fmt::{self, Display};
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use digest::generic_array::GenericArray;
use digest::{Digest, OutputSizeUser};
use sha2::Sha256;
use super::FileSystem;
use crate::prelude::*;
pub struct BackupFS<DataDir: AsRef<Path>, Password: fmt::Display> {
data_dir: DataDir,
password: Password,
}
impl<DataDir: AsRef<Path>, Password: fmt::Display> BackupFS<DataDir, Password> {
pub fn new(data_dir: DataDir, password: Password) -> Self {
BackupFS { data_dir, password }
}
}
impl<DataDir: AsRef<Path> + Send + Sync, Password: fmt::Display + Send + Sync> FileSystem
for BackupFS<DataDir, Password>
{
fn mount_type(&self) -> Option<impl AsRef<str>> {
Some("backup-fs")
}
fn mount_options(&self) -> impl IntoIterator<Item = impl Display> {
[
format!("password={}", self.password),
format!("file-size-padding=0.05"),
]
}
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
Ok(Some(&self.data_dir))
}
async fn source_hash(
&self,
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
let mut sha = Sha256::new();
sha.update("BackupFS");
sha.update(
tokio::fs::canonicalize(self.data_dir.as_ref())
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
self.data_dir.as_ref().display().to_string(),
)
})?
.as_os_str()
.as_bytes(),
);
Ok(sha.finalize())
}
}

View File

@@ -1,6 +1,7 @@
use std::ffi::OsStr;
use std::fmt::{Display, Write};
use std::path::Path;
use std::time::Duration;
use digest::generic_array::GenericArray;
use digest::OutputSizeUser;
@@ -11,6 +12,7 @@ use tokio::process::Command;
use crate::prelude::*;
use crate::util::Invoke;
pub mod backupfs;
pub mod bind;
pub mod block_dev;
pub mod cifs;
@@ -71,6 +73,7 @@ pub(self) async fn default_mount_impl(
fs.pre_mount().await?;
tokio::fs::create_dir_all(mountpoint.as_ref()).await?;
Command::from(default_mount_command(fs, mountpoint, mount_type).await?)
.capture(false)
.invoke(ErrorKind::Filesystem)
.await?;

View File

@@ -1,6 +1,7 @@
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
use chrono::{DateTime, Utc};
use color_eyre::eyre::{self, eyre};
use futures::TryStreamExt;
use nom::bytes::complete::{tag, take_till1};
@@ -19,6 +20,7 @@ use super::mount::filesystem::ReadOnly;
use super::mount::guard::TmpMountGuard;
use crate::disk::mount::guard::GenericMountGuard;
use crate::disk::OsPartitionInfo;
use crate::hostname::Hostname;
use crate::util::serde::IoFormat;
use crate::util::Invoke;
use crate::{Error, ResultExt as _};
@@ -49,15 +51,16 @@ pub struct PartitionInfo {
pub label: Option<String>,
pub capacity: u64,
pub used: Option<u64>,
pub start_os: Option<EmbassyOsRecoveryInfo>,
pub start_os: BTreeMap<String, StartOsRecoveryInfo>,
pub guid: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EmbassyOsRecoveryInfo {
pub struct StartOsRecoveryInfo {
pub hostname: Hostname,
pub version: exver::Version,
pub full: bool,
pub timestamp: DateTime<Utc>,
pub password_hash: Option<String>,
pub wrapped_key: Option<String>,
}
@@ -223,29 +226,38 @@ pub async fn pvscan() -> Result<BTreeMap<PathBuf, Option<String>>, Error> {
pub async fn recovery_info(
mountpoint: impl AsRef<Path>,
) -> Result<Option<EmbassyOsRecoveryInfo>, Error> {
let backup_unencrypted_metadata_path = mountpoint
.as_ref()
.join("EmbassyBackups/unencrypted-metadata.cbor");
if tokio::fs::metadata(&backup_unencrypted_metadata_path)
.await
.is_ok()
{
return Ok(Some(
IoFormat::Cbor.from_slice(
&tokio::fs::read(&backup_unencrypted_metadata_path)
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
backup_unencrypted_metadata_path.display().to_string(),
)
})?,
)?,
));
) -> Result<BTreeMap<String, StartOsRecoveryInfo>, Error> {
let backup_root = mountpoint.as_ref().join("StartOSBackups");
let mut res = BTreeMap::new();
if tokio::fs::metadata(&backup_root).await.is_ok() {
let mut dir = tokio::fs::read_dir(&backup_root).await?;
while let Some(entry) = dir.next_entry().await? {
let server_id = entry.file_name().to_string_lossy().into_owned();
let backup_unencrypted_metadata_path = backup_root
.join(&server_id)
.join("unencrypted-metadata.json");
if tokio::fs::metadata(&backup_unencrypted_metadata_path)
.await
.is_ok()
{
res.insert(
server_id,
IoFormat::Json.from_slice(
&tokio::fs::read(&backup_unencrypted_metadata_path)
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
backup_unencrypted_metadata_path.display().to_string(),
)
})?,
)?,
);
}
}
}
Ok(None)
Ok(res)
}
#[instrument(skip_all)]
@@ -390,7 +402,7 @@ async fn disk_info(disk: PathBuf) -> DiskInfo {
}
async fn part_info(part: PathBuf) -> PartitionInfo {
let mut start_os = None;
let mut start_os = BTreeMap::new();
let label = get_label(&part)
.await
.map_err(|e| tracing::warn!("Could not get label of {}: {}", part.display(), e.source))
@@ -410,14 +422,13 @@ async fn part_info(part: PathBuf) -> PartitionInfo {
tracing::warn!("Could not get usage of {}: {}", part.display(), e.source)
})
.ok();
if let Some(recovery_info) = match recovery_info(mount_guard.path()).await {
Ok(a) => a,
match recovery_info(mount_guard.path()).await {
Ok(a) => {
start_os = a;
}
Err(e) => {
tracing::error!("Error fetching unencrypted backup metadata: {}", e);
None
}
} {
start_os = Some(recovery_info)
}
if let Err(e) = mount_guard.unmount().await {
tracing::error!("Error unmounting partition {}: {}", part.display(), e);

View File

@@ -4,7 +4,7 @@ use tracing::instrument;
use crate::util::Invoke;
use crate::{Error, ErrorKind};
#[derive(Clone, serde::Deserialize, serde::Serialize, Debug)]
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct Hostname(pub String);
lazy_static::lazy_static! {

View File

@@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
@@ -25,7 +26,7 @@ use crate::disk::main::DEFAULT_PASSWORD;
use crate::disk::mount::filesystem::cifs::Cifs;
use crate::disk::mount::filesystem::ReadWrite;
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
use crate::disk::util::{pvscan, recovery_info, DiskInfo, EmbassyOsRecoveryInfo};
use crate::disk::util::{pvscan, recovery_info, DiskInfo, StartOsRecoveryInfo};
use crate::disk::REPAIR_DISK_PATH;
use crate::init::{init, InitPhases, InitResult};
use crate::net::net_controller::PreInitNetController;
@@ -237,7 +238,7 @@ pub async fn verify_cifs(
username,
password,
}: VerifyCifsParams,
) -> Result<EmbassyOsRecoveryInfo, Error> {
) -> Result<BTreeMap<String, StartOsRecoveryInfo>, Error> {
let password: Option<String> = password.map(|x| x.decrypt(&ctx)).flatten();
let guard = TmpMountGuard::mount(
&Cifs {
@@ -251,15 +252,28 @@ pub async fn verify_cifs(
.await?;
let start_os = recovery_info(guard.path()).await?;
guard.unmount().await?;
start_os.ok_or_else(|| Error::new(eyre!("No Backup Found"), crate::ErrorKind::NotFound))
if start_os.is_empty() {
return Err(Error::new(
eyre!("No Backup Found"),
crate::ErrorKind::NotFound,
));
}
Ok(start_os)
}
#[derive(Debug, Deserialize, Serialize, TS)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum RecoverySource {
Migrate { guid: String },
Backup { target: BackupTargetFS },
#[serde(rename_all_fields = "camelCase")]
pub enum RecoverySource<Password> {
Migrate {
guid: String,
},
Backup {
target: BackupTargetFS,
password: Password,
server_id: String,
},
}
#[derive(Deserialize, Serialize, TS)]
@@ -268,8 +282,7 @@ pub enum RecoverySource {
pub struct SetupExecuteParams {
start_os_logicalname: PathBuf,
start_os_password: EncryptedWire,
recovery_source: Option<RecoverySource>,
recovery_password: Option<EncryptedWire>,
recovery_source: Option<RecoverySource<EncryptedWire>>,
}
// #[command(rpc_only)]
@@ -279,7 +292,6 @@ pub async fn execute(
start_os_logicalname,
start_os_password,
recovery_source,
recovery_password,
}: SetupExecuteParams,
) -> Result<SetupProgress, Error> {
let start_os_password = match start_os_password.decrypt(&ctx) {
@@ -291,29 +303,27 @@ pub async fn execute(
))
}
};
let recovery_password: Option<String> = match recovery_password {
Some(a) => match a.decrypt(&ctx) {
Some(a) => Some(a),
None => {
return Err(Error::new(
let recovery = match recovery_source {
Some(RecoverySource::Backup {
target,
password,
server_id,
}) => Some(RecoverySource::Backup {
target,
password: password.decrypt(&ctx).ok_or_else(|| {
Error::new(
color_eyre::eyre::eyre!("Couldn't decode recoveryPassword"),
crate::ErrorKind::Unknown,
))
}
},
)
})?,
server_id,
}),
Some(RecoverySource::Migrate { guid }) => Some(RecoverySource::Migrate { guid }),
None => None,
};
let setup_ctx = ctx.clone();
ctx.run_setup(|| {
execute_inner(
setup_ctx,
start_os_logicalname,
start_os_password,
recovery_source,
recovery_password,
)
})?;
ctx.run_setup(|| execute_inner(setup_ctx, start_os_logicalname, start_os_password, recovery))?;
Ok(ctx.progress().await)
}
@@ -348,12 +358,11 @@ pub async fn execute_inner(
ctx: SetupContext,
start_os_logicalname: PathBuf,
start_os_password: String,
recovery_source: Option<RecoverySource>,
recovery_password: Option<String>,
recovery_source: Option<RecoverySource<String>>,
) -> Result<(SetupResult, RpcContext), Error> {
let progress = &ctx.progress;
let mut disk_phase = progress.add_phase("Formatting data drive".into(), Some(10));
let restore_phase = match &recovery_source {
let restore_phase = match recovery_source.as_ref() {
Some(RecoverySource::Backup { .. }) => {
Some(progress.add_phase("Restoring backup".into(), Some(100)))
}
@@ -396,13 +405,18 @@ pub async fn execute_inner(
};
match recovery_source {
Some(RecoverySource::Backup { target }) => {
Some(RecoverySource::Backup {
target,
password,
server_id,
}) => {
recover(
&ctx,
guid,
start_os_password,
target,
recovery_password,
server_id,
password,
progress,
)
.await
@@ -448,7 +462,8 @@ async fn recover(
guid: Arc<String>,
start_os_password: String,
recovery_source: BackupTargetFS,
recovery_password: Option<String>,
server_id: String,
recovery_password: String,
progress: SetupExecuteProgress,
) -> Result<(SetupResult, RpcContext), Error> {
let recovery_source = TmpMountGuard::mount(&recovery_source, ReadWrite).await?;
@@ -457,7 +472,8 @@ async fn recover(
guid.clone(),
start_os_password,
recovery_source,
recovery_password,
&server_id,
&recovery_password,
progress,
)
.await

View File

@@ -107,64 +107,22 @@ pub fn serialize_display_opt<T: std::fmt::Display, S: Serializer>(
Option::<String>::serialize(&t.as_ref().map(|t| t.to_string()), serializer)
}
pub mod ed25519_pubkey {
use ed25519_dalek::VerifyingKey;
use serde::de::{Error, Unexpected, Visitor};
use serde::{Deserializer, Serializer};
pub fn serialize<S: Serializer>(
pubkey: &VerifyingKey,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&base32::encode(
base32::Alphabet::RFC4648 { padding: true },
pubkey.as_bytes(),
))
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<VerifyingKey, D::Error> {
struct PubkeyVisitor;
impl<'de> Visitor<'de> for PubkeyVisitor {
type Value = ed25519_dalek::VerifyingKey;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "an RFC4648 encoded string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
VerifyingKey::from_bytes(
&<[u8; 32]>::try_from(
base32::decode(base32::Alphabet::RFC4648 { padding: true }, v).ok_or(
Error::invalid_value(Unexpected::Str(v), &"an RFC4648 encoded string"),
)?,
)
.map_err(|e| Error::invalid_length(e.len(), &"32 bytes"))?,
)
.map_err(Error::custom)
}
}
deserializer.deserialize_str(PubkeyVisitor)
}
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum ValuePrimative {
pub enum ValuePrimitive {
Null,
Boolean(bool),
String(String),
Number(serde_json::Number),
}
impl<'de> serde::de::Deserialize<'de> for ValuePrimative {
impl<'de> serde::de::Deserialize<'de> for ValuePrimitive {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = ValuePrimative;
type Value = ValuePrimitive;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a JSON primative value")
}
@@ -172,37 +130,37 @@ impl<'de> serde::de::Deserialize<'de> for ValuePrimative {
where
E: serde::de::Error,
{
Ok(ValuePrimative::Null)
Ok(ValuePrimitive::Null)
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::Null)
Ok(ValuePrimitive::Null)
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::Boolean(v))
Ok(ValuePrimitive::Boolean(v))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::String(v.to_owned()))
Ok(ValuePrimitive::String(v.to_owned()))
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::String(v))
Ok(ValuePrimitive::String(v))
}
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::Number(
Ok(ValuePrimitive::Number(
serde_json::Number::from_f64(v as f64).ok_or_else(|| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Float(v as f64),
@@ -215,7 +173,7 @@ impl<'de> serde::de::Deserialize<'de> for ValuePrimative {
where
E: serde::de::Error,
{
Ok(ValuePrimative::Number(
Ok(ValuePrimitive::Number(
serde_json::Number::from_f64(v).ok_or_else(|| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Float(v),
@@ -228,49 +186,49 @@ impl<'de> serde::de::Deserialize<'de> for ValuePrimative {
where
E: serde::de::Error,
{
Ok(ValuePrimative::Number(v.into()))
Ok(ValuePrimitive::Number(v.into()))
}
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::Number(v.into()))
Ok(ValuePrimitive::Number(v.into()))
}
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::Number(v.into()))
Ok(ValuePrimitive::Number(v.into()))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::Number(v.into()))
Ok(ValuePrimitive::Number(v.into()))
}
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::Number(v.into()))
Ok(ValuePrimitive::Number(v.into()))
}
fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::Number(v.into()))
Ok(ValuePrimitive::Number(v.into()))
}
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::Number(v.into()))
Ok(ValuePrimitive::Number(v.into()))
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(ValuePrimative::Number(v.into()))
Ok(ValuePrimitive::Number(v.into()))
}
}
deserializer.deserialize_any(Visitor)