switch to LVM + LUKS + ext4 (#747)

* code complete

* misc fixes

* magic
This commit is contained in:
Aiden McClelland
2021-11-01 14:00:53 -06:00
parent c65f019ffc
commit 2a0425e968
12 changed files with 325 additions and 282 deletions

View File

@@ -1,4 +1,5 @@
use std::path::Path;
use std::sync::Arc;
use embassy::context::rpc::RpcContextConfig;
use embassy::context::{DiagnosticContext, SetupContext};
@@ -72,16 +73,20 @@ async fn init(cfg_path: Option<&str>) -> Result<(), Error> {
})
.await
.with_kind(embassy::ErrorKind::Network)?;
let pool_name = ctx.zfs_pool_name.clone();
drop(ctx);
embassy::disk::main::export(&*pool_name).await?;
}
embassy::disk::main::load(
tokio::fs::read_to_string("/embassy-os/disk.guid") // unique identifier for zfs pool - keeps track of the disk that goes with your embassy
embassy::disk::main::export(
tokio::fs::read_to_string("/embassy-os/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
.await?
.trim(),
cfg.datadir(),
)
.await?;
}
embassy::disk::main::import(
tokio::fs::read_to_string("/embassy-os/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
.await?
.trim(),
cfg.zfs_pool_name(),
cfg.datadir(),
DEFAULT_PASSWORD,
)
@@ -99,7 +104,11 @@ async fn init(cfg_path: Option<&str>) -> Result<(), Error> {
.invoke(embassy::ErrorKind::Journald)
.await?;
tracing::info!("Mounted Logs");
let tmp_docker = cfg.datadir().join("tmp").join("docker");
let tmp_dir = cfg.datadir().join("package-data/tmp");
if tokio::fs::metadata(&tmp_dir).await.is_err() {
tokio::fs::create_dir_all(&tmp_dir).await?;
}
let tmp_docker = cfg.datadir().join("package-data/tmp/docker");
if tokio::fs::metadata(&tmp_docker).await.is_ok() {
tokio::fs::remove_dir_all(&tmp_docker).await?;
}
@@ -210,7 +219,21 @@ async fn inner_main(cfg_path: Option<&str>) -> Result<Option<Shutdown>, Error> {
.arg("nginx")
.invoke(embassy::ErrorKind::Nginx)
.await?;
let ctx = DiagnosticContext::init(cfg_path, e).await?;
let ctx = DiagnosticContext::init(
cfg_path,
if tokio::fs::metadata("/embassy-os/disk.guid").await.is_ok() {
Some(Arc::new(
tokio::fs::read_to_string("/embassy-os/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
.await?
.trim()
.to_owned(),
))
} else {
None
},
e,
)
.await?;
let mut shutdown_recv = ctx.shutdown.subscribe();
rpc_server!({
command: embassy::diagnostic_api,

View File

@@ -1,3 +1,4 @@
use std::sync::Arc;
use std::time::Duration;
use color_eyre::eyre::eyre;
@@ -36,7 +37,16 @@ fn err_to_500(e: Error) -> Response<Body> {
#[instrument]
async fn inner_main(cfg_path: Option<&str>) -> Result<Option<Shutdown>, Error> {
let rpc_ctx = RpcContext::init(cfg_path).await?;
let rpc_ctx = RpcContext::init(
cfg_path,
Arc::new(
tokio::fs::read_to_string("/embassy-os/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
.await?
.trim()
.to_owned(),
),
)
.await?;
let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
let sig_handler_ctx = rpc_ctx.clone();
@@ -302,7 +312,21 @@ fn main() {
.arg("nginx")
.invoke(embassy::ErrorKind::Nginx)
.await?;
let ctx = DiagnosticContext::init(cfg_path, e).await?;
let ctx = DiagnosticContext::init(
cfg_path,
if tokio::fs::metadata("/embassy-os/disk.guid").await.is_ok() {
Some(Arc::new(
tokio::fs::read_to_string("/embassy-os/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
.await?
.trim()
.to_owned(),
))
} else {
None
},
e,
)
.await?;
rpc_server!({
command: embassy::diagnostic_api,
context: ctx.clone(),

View File

@@ -1,6 +1,6 @@
use std::net::{IpAddr, SocketAddr};
use std::ops::Deref;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use rpc_toolkit::yajrc::RpcError;
@@ -20,7 +20,7 @@ use crate::{Error, ResultExt};
#[serde(rename_all = "kebab-case")]
pub struct DiagnosticContextConfig {
pub bind_rpc: Option<SocketAddr>,
pub zfs_pool_name: Option<String>,
pub datadir: Option<PathBuf>,
}
impl DiagnosticContextConfig {
#[instrument(skip(path))]
@@ -38,35 +38,40 @@ impl DiagnosticContextConfig {
Ok(Self::default())
}
}
pub fn zfs_pool_name(&self) -> &str {
self.zfs_pool_name
.as_ref()
.map(|s| s.as_str())
.unwrap_or("embassy-data")
pub fn datadir(&self) -> &Path {
self.datadir
.as_deref()
.unwrap_or_else(|| Path::new("/embassy-data"))
}
}
pub struct DiagnosticContextSeed {
pub bind_rpc: SocketAddr,
pub datadir: PathBuf,
pub shutdown: Sender<Option<Shutdown>>,
pub error: Arc<RpcError>,
pub zfs_pool_name: Arc<String>,
pub disk_guid: Option<Arc<String>>,
}
#[derive(Clone)]
pub struct DiagnosticContext(Arc<DiagnosticContextSeed>);
impl DiagnosticContext {
#[instrument(skip(path))]
pub async fn init<P: AsRef<Path>>(path: Option<P>, error: Error) -> Result<Self, Error> {
pub async fn init<P: AsRef<Path>>(
path: Option<P>,
disk_guid: Option<Arc<String>>,
error: Error,
) -> Result<Self, Error> {
let cfg = DiagnosticContextConfig::load(path).await?;
let (shutdown, _) = tokio::sync::broadcast::channel(1);
Ok(Self(Arc::new(DiagnosticContextSeed {
bind_rpc: cfg.bind_rpc.unwrap_or(([127, 0, 0, 1], 5959).into()),
datadir: cfg.datadir().to_owned(),
shutdown,
disk_guid,
error: Arc::new(error.into()),
zfs_pool_name: Arc::new(cfg.zfs_pool_name().to_owned()),
})))
}
}

View File

@@ -43,7 +43,6 @@ pub struct RpcContextConfig {
pub tor_control: Option<SocketAddr>,
pub tor_socks: Option<SocketAddr>,
pub revision_cache_size: Option<usize>,
pub zfs_pool_name: Option<String>,
pub datadir: Option<PathBuf>,
pub log_server: Option<Url>,
}
@@ -62,17 +61,10 @@ impl RpcContextConfig {
Ok(Self::default())
}
}
pub fn zfs_pool_name(&self) -> &str {
self.zfs_pool_name
.as_ref()
.map(|s| s.as_str())
.unwrap_or("embassy-data")
}
pub fn datadir(&self) -> Cow<'_, Path> {
pub fn datadir(&self) -> &Path {
self.datadir
.as_ref()
.map(|a| Cow::Borrowed(a.as_path()))
.unwrap_or_else(|| Cow::Owned(Path::new("/").join(self.zfs_pool_name())))
.as_deref()
.unwrap_or_else(|| Path::new("/embassy-data"))
}
pub async fn db(&self, secret_store: &SqlitePool) -> Result<PatchDb, Error> {
let db_path = self.datadir().join("main").join("embassy.db");
@@ -115,7 +107,7 @@ pub struct RpcContextSeed {
pub bind_ws: SocketAddr,
pub bind_static: SocketAddr,
pub datadir: PathBuf,
pub zfs_pool_name: Arc<String>,
pub disk_guid: Arc<String>,
pub db: PatchDb,
pub secret_store: SqlitePool,
pub docker: Docker,
@@ -136,7 +128,10 @@ pub struct RpcContextSeed {
pub struct RpcContext(Arc<RpcContextSeed>);
impl RpcContext {
#[instrument(skip(cfg_path))]
pub async fn init<P: AsRef<Path>>(cfg_path: Option<P>) -> Result<Self, Error> {
pub async fn init<P: AsRef<Path>>(
cfg_path: Option<P>,
disk_guid: Arc<String>,
) -> Result<Self, Error> {
let base = RpcContextConfig::load(cfg_path).await?;
let log_epoch = Arc::new(AtomicU64::new(rand::random()));
let logger = EmbassyLogger::init(log_epoch.clone(), base.log_server.clone(), false);
@@ -167,7 +162,7 @@ impl RpcContext {
bind_ws: base.bind_ws.unwrap_or(([127, 0, 0, 1], 5960).into()),
bind_static: base.bind_static.unwrap_or(([127, 0, 0, 1], 5961).into()),
datadir: base.datadir().to_path_buf(),
zfs_pool_name: Arc::new(base.zfs_pool_name().to_owned()),
disk_guid,
db,
secret_store,
docker,

View File

@@ -30,7 +30,6 @@ use crate::{Error, ResultExt};
#[serde(rename_all = "kebab-case")]
pub struct SetupContextConfig {
pub bind_rpc: Option<SocketAddr>,
pub zfs_pool_name: Option<String>,
pub datadir: Option<PathBuf>,
}
impl SetupContextConfig {
@@ -49,17 +48,10 @@ impl SetupContextConfig {
Ok(Self::default())
}
}
pub fn zfs_pool_name(&self) -> &str {
self.zfs_pool_name
.as_ref()
.map(|s| s.as_str())
.unwrap_or("embassy-data")
}
pub fn datadir(&self) -> Cow<'_, Path> {
pub fn datadir(&self) -> &Path {
self.datadir
.as_ref()
.map(|a| Cow::Borrowed(a.as_path()))
.unwrap_or_else(|| Cow::Owned(Path::new("/").join(self.zfs_pool_name())))
.as_deref()
.unwrap_or_else(|| Path::new("/embassy-data"))
}
}
@@ -67,7 +59,6 @@ pub struct SetupContextSeed {
pub bind_rpc: SocketAddr,
pub shutdown: Sender<()>,
pub datadir: PathBuf,
pub zfs_pool_name: Arc<String>,
pub selected_v2_drive: RwLock<Option<PathBuf>>,
pub cached_product_key: RwLock<Option<Arc<String>>>,
pub recovery_status: RwLock<Option<Result<RecoveryStatus, RpcError>>>,
@@ -80,13 +71,11 @@ impl SetupContext {
pub async fn init<P: AsRef<Path>>(path: Option<P>) -> Result<Self, Error> {
let cfg = SetupContextConfig::load(path).await?;
let (shutdown, _) = tokio::sync::broadcast::channel(1);
let datadir = cfg.datadir().into_owned();
let zfs_pool_name = Arc::new(cfg.zfs_pool_name().to_owned());
let datadir = cfg.datadir().to_owned();
Ok(Self(Arc::new(SetupContextSeed {
bind_rpc: cfg.bind_rpc.unwrap_or(([127, 0, 0, 1], 5959).into()),
shutdown,
datadir,
zfs_pool_name,
selected_v2_drive: RwLock::new(None),
cached_product_key: RwLock::new(None),
recovery_status: RwLock::new(None),

View File

@@ -47,7 +47,8 @@ pub fn exit(#[context] ctx: DiagnosticContext) -> Result<(), Error> {
pub fn restart(#[context] ctx: DiagnosticContext) -> Result<(), Error> {
ctx.shutdown
.send(Some(Shutdown {
zfs_pool: ctx.zfs_pool_name.clone(),
datadir: ctx.datadir.clone(),
disk_guid: ctx.disk_guid.clone(),
db_handle: None,
restart: true,
}))

View File

@@ -3,238 +3,273 @@ use std::path::Path;
use tokio::process::Command;
use tracing::instrument;
use crate::disk::util::{mount, unmount};
use crate::util::Invoke;
use crate::{Error, ResultExt};
pub const PASSWORD_PATH: &'static str = "/etc/embassy/password";
pub const DEFAULT_PASSWORD: &'static str = "password";
pub const MAIN_FS_SIZE: FsSize = FsSize::Gigabytes(8);
pub const SWAP_SIZE: FsSize = FsSize::Gigabytes(8);
// TODO: use IncorrectDisk / DiskNotAvailable / DiskCorrupted
#[instrument(skip(disks))]
pub async fn create<I: IntoIterator<Item = P>, P: AsRef<Path>>(
pool_name: &str,
disks: I,
#[instrument(skip(disks, datadir, password))]
pub async fn create<I, P>(
disks: &I,
datadir: impl AsRef<Path>,
password: &str,
) -> Result<String, Error> {
let guid = create_pool(pool_name, disks).await?;
create_fs(pool_name, password).await?;
export(pool_name).await?;
) -> Result<String, Error>
where
for<'a> &'a I: IntoIterator<Item = &'a P>,
P: AsRef<Path>,
{
let guid = create_pool(disks).await?;
create_all_fs(&guid, &datadir, password).await?;
export(&guid, datadir).await?;
Ok(guid)
}
#[instrument(skip(datadir))]
pub async fn load<P: AsRef<Path>>(
guid: &str,
pool_name: &str,
datadir: P,
password: &str,
) -> Result<(), Error> {
import(guid).await?;
mount(pool_name, datadir, password).await?;
Ok(())
}
#[instrument(skip(disks))]
pub async fn create_pool<I: IntoIterator<Item = P>, P: AsRef<Path>>(
pool_name: &str,
disks: I,
) -> Result<String, Error> {
let mut cmd = Command::new("zpool");
cmd.arg("create").arg("-f").arg(pool_name);
pub async fn create_pool<I, P>(disks: &I) -> Result<String, Error>
where
for<'a> &'a I: IntoIterator<Item = &'a P>,
P: AsRef<Path>,
{
for disk in disks {
tokio::fs::write(disk.as_ref(), &[0; 2048]).await?; // wipe partition table and lvm2 metadata
Command::new("pvcreate")
.arg("-yff")
.arg(disk.as_ref())
.invoke(crate::ErrorKind::DiskManagement)
.await?;
}
let guid = format!(
"EMBASSY_{}",
base32::encode(
base32::Alphabet::RFC4648 { padding: false },
&rand::random::<[u8; 32]>(),
)
);
let mut cmd = Command::new("vgcreate");
cmd.arg("-y").arg(&guid);
for disk in disks {
cmd.arg(disk.as_ref());
}
cmd.invoke(crate::ErrorKind::Zfs).await?;
Ok(String::from_utf8(
Command::new("zpool")
.arg("get")
.arg("-H")
.arg("-ovalue")
.arg("guid")
.arg(pool_name)
.invoke(crate::ErrorKind::Zfs)
.await?,
)?
.trim() // this allocates but fuck it
.to_owned())
cmd.invoke(crate::ErrorKind::DiskManagement).await?;
Ok(guid)
}
#[instrument]
pub async fn create_fs(pool_name: &str, password: &str) -> Result<(), Error> {
#[derive(Debug, Clone, Copy)]
pub enum FsSize {
Gigabytes(usize),
FreePercentage(usize),
}
#[instrument(skip(datadir, password))]
pub async fn create_fs<P: AsRef<Path>>(
guid: &str,
datadir: P,
name: &str,
size: FsSize,
swap: bool,
password: &str,
) -> Result<(), Error> {
tokio::fs::write(PASSWORD_PATH, password)
.await
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
Command::new("zfs")
.arg("create")
.arg("-o")
.arg("reservation=5G")
.arg("-o")
.arg("encryption=on")
.arg("-o")
.arg("keylocation=file:///etc/embassy/password")
.arg("-o")
.arg("keyformat=passphrase")
.arg(format!("{}/main", pool_name))
.invoke(crate::ErrorKind::Zfs)
let mut cmd = Command::new("lvcreate");
match size {
FsSize::Gigabytes(a) => cmd.arg("-L").arg(format!("{}G", a)),
FsSize::FreePercentage(a) => cmd.arg("-l").arg(format!("{}%FREE", a)),
};
cmd.arg("-y")
.arg("-n")
.arg(name)
.arg(guid)
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("zfs")
.arg("create")
.arg("-o")
.arg("reservation=5G")
.arg(format!("{}/updates", pool_name))
.invoke(crate::ErrorKind::Zfs)
Command::new("cryptsetup")
.arg("luksFormat")
.arg(format!("--key-file={}", PASSWORD_PATH))
.arg(format!("--keyfile-size={}", password.len()))
.arg(Path::new("/dev").join(guid).join(name))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("zfs")
.arg("create")
.arg("-o")
.arg("encryption=on")
.arg("-o")
.arg("keylocation=file:///etc/embassy/password")
.arg("-o")
.arg("keyformat=passphrase")
.arg(format!("{}/package-data", pool_name))
.invoke(crate::ErrorKind::Zfs)
Command::new("cryptsetup")
.arg("luksOpen")
.arg(format!("--key-file={}", PASSWORD_PATH))
.arg(format!("--keyfile-size={}", password.len()))
.arg(Path::new("/dev").join(guid).join(name))
.arg(format!("{}_{}", guid, name))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("zfs")
.arg("create")
.arg("-o")
.arg("encryption=on")
.arg("-o")
.arg("keylocation=file:///etc/embassy/password")
.arg("-o")
.arg("keyformat=passphrase")
.arg(format!("{}/tmp", pool_name))
.invoke(crate::ErrorKind::Zfs)
if swap {
Command::new("mkswap")
.arg("-f")
.arg(Path::new("/dev/mapper").join(format!("{}_{}", guid, name)))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("swapon")
.arg(Path::new("/dev/mapper").join(format!("{}_{}", guid, name)))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
} else {
Command::new("mkfs.ext4")
.arg(Path::new("/dev/mapper").join(format!("{}_{}", guid, name)))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
mount(
Path::new("/dev/mapper").join(format!("{}_{}", guid, name)),
datadir.as_ref().join(name),
)
.await?;
}
tokio::fs::remove_file(PASSWORD_PATH)
.await
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
Ok(())
}
#[instrument]
pub async fn create_swap(pool_name: &str) -> Result<(), Error> {
let pagesize = String::from_utf8(
Command::new("getconf")
.arg("PAGESIZE")
.invoke(crate::ErrorKind::Zfs)
.await?,
)?;
Command::new("zfs")
.arg("create")
.arg("-V8G")
.arg("-b")
.arg(pagesize)
.arg("-o")
.arg("logbias=throughput")
.arg("-o")
.arg("sync=always")
.arg("-o")
.arg("primarycache=metadata")
.arg("-o")
.arg("com.sun:auto-snapshot=false")
.invoke(crate::ErrorKind::Zfs)
.await?;
Command::new("mkswap")
.arg("-f")
.arg(Path::new("/dev/zvol").join(pool_name).join("swap"))
.invoke(crate::ErrorKind::Zfs)
.await?;
Ok(())
}
#[instrument]
pub async fn use_swap(pool_name: &str) -> Result<(), Error> {
Command::new("swapon")
.arg(Path::new("/dev/zvol").join(pool_name).join("swap"))
.invoke(crate::ErrorKind::Zfs)
.await?;
Ok(())
}
#[instrument]
pub async fn export(pool_name: &str) -> Result<(), Error> {
Command::new("zpool")
.arg("export")
.arg(pool_name)
.invoke(crate::ErrorKind::Zfs)
.await?;
Ok(())
}
#[instrument]
pub async fn import(guid: &str) -> Result<(), Error> {
Command::new("zpool")
.arg("import")
.arg("-f")
.arg(guid)
.invoke(crate::ErrorKind::Zfs)
#[instrument(skip(datadir, password))]
pub async fn create_all_fs<P: AsRef<Path>>(
guid: &str,
datadir: P,
password: &str,
) -> Result<(), Error> {
create_fs(guid, &datadir, "main", MAIN_FS_SIZE, false, password).await?;
create_fs(guid, &datadir, "swap", SWAP_SIZE, true, password).await?;
create_fs(
guid,
&datadir,
"package-data",
FsSize::FreePercentage(100),
false,
password,
)
.await?;
Ok(())
}
#[instrument(skip(datadir))]
pub async fn mount<P: AsRef<Path>>(
pool_name: &str,
pub async fn unmount_fs<P: AsRef<Path>>(
guid: &str,
datadir: P,
password: &str,
name: &str,
swap: bool,
) -> Result<(), Error> {
let mountpoint = String::from_utf8(
Command::new("zfs")
.arg("get")
.arg("-H")
.arg("-ovalue")
.arg("mountpoint")
.arg(pool_name)
.invoke(crate::ErrorKind::Zfs)
.await?,
)?;
if Path::new(mountpoint.trim()) != datadir.as_ref() {
Command::new("zfs")
.arg("set")
.arg(format!("mountpoint={}", datadir.as_ref().display()))
.arg(pool_name)
.invoke(crate::ErrorKind::Zfs)
if swap {
Command::new("swapoff")
.arg(Path::new("/dev/mapper").join(format!("{}_{}", guid, name)))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
} else {
unmount(datadir.as_ref().join(name)).await?;
}
Command::new("cryptsetup")
.arg("luksClose")
.arg(format!("{}_{}", guid, name))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Ok(())
}
#[instrument(skip(datadir))]
pub async fn unmount_all_fs<P: AsRef<Path>>(guid: &str, datadir: P) -> Result<(), Error> {
unmount_fs(guid, &datadir, "main", false).await?;
unmount_fs(guid, &datadir, "swap", true).await?;
unmount_fs(guid, &datadir, "package-data", false).await?;
Ok(())
}
#[instrument(skip(datadir))]
pub async fn export<P: AsRef<Path>>(guid: &str, datadir: P) -> Result<(), Error> {
unmount_all_fs(guid, datadir).await?;
Command::new("vgchange")
.arg("-an")
.arg(guid)
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("vgexport")
.arg(guid)
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Ok(())
}
#[instrument(skip(datadir, password))]
pub async fn import<P: AsRef<Path>>(guid: &str, datadir: P, password: &str) -> Result<(), Error> {
match Command::new("vgimport")
.arg(guid)
.invoke(crate::ErrorKind::DiskManagement)
.await
{
Ok(_) => Ok(()),
Err(e)
if format!("{}", e.source).trim()
== format!("Volume group \"{}\" is not exported", guid) =>
{
Ok(())
}
Err(e) => Err(e),
}?;
Command::new("vgchange")
.arg("-ay")
.arg(guid)
.invoke(crate::ErrorKind::DiskManagement)
.await?;
mount_all_fs(guid, datadir, password).await?;
Ok(())
}
#[instrument(skip(datadir, password))]
pub async fn mount_fs<P: AsRef<Path>>(
guid: &str,
datadir: P,
name: &str,
swap: bool,
password: &str,
) -> Result<(), Error> {
tokio::fs::write(PASSWORD_PATH, password)
.await
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
Command::new("zfs")
.arg("load-key")
.arg(format!("{}/main", pool_name))
.invoke(crate::ErrorKind::Zfs)
Command::new("cryptsetup")
.arg("luksOpen")
.arg(format!("--key-file={}", PASSWORD_PATH))
.arg(format!("--keyfile-size={}", password.len()))
.arg(Path::new("/dev").join(guid).join(name))
.arg(format!("{}_{}", guid, name))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("zfs")
.arg("load-key")
.arg(format!("{}/package-data", pool_name))
.invoke(crate::ErrorKind::Zfs)
if swap {
Command::new("swapon")
.arg(Path::new("/dev/mapper").join(format!("{}_{}", guid, name)))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("zfs")
.arg("load-key")
.arg(format!("{}/tmp", pool_name))
.invoke(crate::ErrorKind::Zfs)
} else {
mount(
Path::new("/dev/mapper").join(format!("{}_{}", guid, name)),
datadir.as_ref().join(name),
)
.await?;
}
tokio::fs::remove_file(PASSWORD_PATH)
.await
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
Command::new("zfs")
.arg("mount")
.arg(format!("{}/main", pool_name))
.invoke(crate::ErrorKind::Zfs)
.await?;
Command::new("zfs")
.arg("mount")
.arg(format!("{}/package-data", pool_name))
.invoke(crate::ErrorKind::Zfs)
.await?;
Command::new("zfs")
.arg("mount")
.arg(format!("{}/tmp", pool_name))
.invoke(crate::ErrorKind::Zfs)
.await?;
Ok(())
}
#[instrument(skip(datadir, password))]
pub async fn mount_all_fs<P: AsRef<Path>>(
guid: &str,
datadir: P,
password: &str,
) -> Result<(), Error> {
mount_fs(guid, &datadir, "main", false, password).await?;
mount_fs(guid, &datadir, "swap", true, password).await?;
mount_fs(guid, &datadir, "package-data", false, password).await?;
Ok(())
}

View File

@@ -62,7 +62,6 @@ const SYS_BLOCK_PATH: &'static str = "/sys/block";
lazy_static::lazy_static! {
static ref PARTITION_REGEX: Regex = Regex::new("-part[0-9]+$").unwrap();
static ref ZPOOL_REGEX: Regex = Regex::new("^\\s+([a-z0-9]+)\\s+ONLINE").unwrap();
}
#[instrument(skip(path))]
@@ -150,22 +149,7 @@ pub async fn get_used<P: AsRef<Path>>(path: P) -> Result<usize, Error> {
#[instrument]
pub async fn list() -> Result<Vec<DiskInfo>, Error> {
let zpool_drives: BTreeSet<PathBuf> = match Command::new("zpool")
.arg("status")
.invoke(crate::ErrorKind::Zfs)
.await
{
Ok(v) => String::from_utf8(v)?
.lines()
.filter_map(|l| ZPOOL_REGEX.captures(l))
.filter_map(|c| c.get(1))
.map(|d| Path::new("/dev").join(d.as_str()))
.collect(),
Err(e) => {
tracing::warn!("`zpool status` returned error: {}", e);
BTreeSet::new()
}
};
let internal_drives: BTreeSet<PathBuf> = BTreeSet::new(); // todo!("parse pvscan");
let disks = tokio_stream::wrappers::ReadDirStream::new(
tokio::fs::read_dir(DISK_PATH)
.await
@@ -233,7 +217,7 @@ pub async fn list() -> Result<Vec<DiskInfo>, Error> {
tracing::warn!("Could not get capacity of {}: {}", disk.display(), e.source)
})
.unwrap_or_default();
if zpool_drives.contains(&disk) {
if internal_drives.contains(&disk) {
internal = true;
} else {
for part in parts {

View File

@@ -53,7 +53,7 @@ pub enum ErrorKind {
ParseSysInfo = 45,
Wifi = 46,
Journald = 47,
Zfs = 48,
DiskManagement = 48,
OpenSsl = 49,
PasswordHashGeneration = 50,
DiagnosticMode = 51,
@@ -111,7 +111,7 @@ impl ErrorKind {
ParseSysInfo => "System Info Parsing Error",
Wifi => "WiFi Internal Error",
Journald => "Journald Error",
Zfs => "ZFS Error",
DiskManagement => "Disk Management Error",
OpenSsl => "OpenSSL Internal Error",
PasswordHashGeneration => "Password Hash Generation Error",
DiagnosticMode => "Embassy is in Diagnostic Mode",

View File

@@ -142,25 +142,8 @@ pub async fn execute_inner(
));
}
let guid =
crate::disk::main::create(&ctx.zfs_pool_name, [embassy_logicalname], DEFAULT_PASSWORD)
.await?;
let search_string = format!("id: {}", guid);
let mut ctr = 0;
while {
ctr += 1;
ctr < 30 // 30s timeout
} && !String::from_utf8(
Command::new("zpool")
.arg("import")
.invoke(crate::ErrorKind::Zfs)
.await?,
)?
.lines()
.any(|line| line.trim() == &search_string)
{
tokio::time::sleep(Duration::from_secs(1)).await;
}
crate::disk::main::load(&guid, &ctx.zfs_pool_name, &ctx.datadir, DEFAULT_PASSWORD).await?;
crate::disk::main::create(&[embassy_logicalname], &ctx.datadir, DEFAULT_PASSWORD).await?;
crate::disk::main::import(&guid, &ctx.datadir, DEFAULT_PASSWORD).await?;
let password = argon2::hash_encoded(
embassy_password.as_bytes(),
&rand::random::<[u8; 16]>()[..],

View File

@@ -1,3 +1,4 @@
use std::path::PathBuf;
use std::sync::Arc;
use patch_db::{LockType, PatchDbHandle};
@@ -11,7 +12,8 @@ use crate::Error;
#[derive(Debug, Clone)]
pub struct Shutdown {
pub zfs_pool: Arc<String>,
pub datadir: PathBuf,
pub disk_guid: Option<Arc<String>>,
pub restart: bool,
pub db_handle: Option<Arc<PatchDbHandle>>,
}
@@ -45,10 +47,12 @@ impl Shutdown {
tracing::error!("Error Stopping Docker: {}", e);
tracing::debug!("{:?}", e);
}
if let Err(e) = export(&*self.zfs_pool).await {
tracing::error!("Error Exporting ZFS Pool: {}", e);
if let Some(guid) = &self.disk_guid {
if let Err(e) = export(guid, &self.datadir).await {
tracing::error!("Error Exporting Volume Group: {}", e);
tracing::debug!("{:?}", e);
}
}
if let Err(e) = MARIO_DEATH.play().await {
tracing::error!("Error Playing Shutdown Song: {}", e);
tracing::debug!("{:?}", e);
@@ -76,7 +80,8 @@ pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> {
.await;
ctx.shutdown
.send(Some(Shutdown {
zfs_pool: ctx.zfs_pool_name.clone(),
datadir: ctx.datadir.clone(),
disk_guid: Some(ctx.disk_guid.clone()),
restart: false,
db_handle: Some(Arc::new(db)),
}))
@@ -93,7 +98,8 @@ pub async fn restart(#[context] ctx: RpcContext) -> Result<(), Error> {
.await;
ctx.shutdown
.send(Some(Shutdown {
zfs_pool: ctx.zfs_pool_name.clone(),
datadir: ctx.datadir.clone(),
disk_guid: Some(ctx.disk_guid.clone()),
restart: true,
db_handle: Some(Arc::new(db)),
}))

View File

@@ -14,7 +14,6 @@ apt install -y \
avahi-daemon \
iotop \
bmon \
zfsutils-linux \
exfat-utils \
sqlite3 \
wireless-tools \
@@ -37,7 +36,6 @@ touch /root/.docker/config.json
docker run --privileged --rm tonistiigi/binfmt --install all
docker network create -d bridge --subnet 172.18.0.1/16 start9 || true
echo '{ "storage-driver": "zfs" }' > /etc/docker/daemon.json
mkdir -p /etc/embassy
hostnamectl set-hostname "embassy"
systemctl enable embassyd.service embassy-init.service