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::path::Path;
use std::sync::Arc;
use embassy::context::rpc::RpcContextConfig; use embassy::context::rpc::RpcContextConfig;
use embassy::context::{DiagnosticContext, SetupContext}; use embassy::context::{DiagnosticContext, SetupContext};
@@ -72,16 +73,20 @@ async fn init(cfg_path: Option<&str>) -> Result<(), Error> {
}) })
.await .await
.with_kind(embassy::ErrorKind::Network)?; .with_kind(embassy::ErrorKind::Network)?;
let pool_name = ctx.zfs_pool_name.clone();
drop(ctx); drop(ctx);
embassy::disk::main::export(&*pool_name).await?; 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::load( embassy::disk::main::import(
tokio::fs::read_to_string("/embassy-os/disk.guid") // unique identifier for zfs pool - keeps track of the disk that goes with your embassy 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? .await?
.trim(), .trim(),
cfg.zfs_pool_name(),
cfg.datadir(), cfg.datadir(),
DEFAULT_PASSWORD, DEFAULT_PASSWORD,
) )
@@ -99,7 +104,11 @@ async fn init(cfg_path: Option<&str>) -> Result<(), Error> {
.invoke(embassy::ErrorKind::Journald) .invoke(embassy::ErrorKind::Journald)
.await?; .await?;
tracing::info!("Mounted Logs"); 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() { if tokio::fs::metadata(&tmp_docker).await.is_ok() {
tokio::fs::remove_dir_all(&tmp_docker).await?; 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") .arg("nginx")
.invoke(embassy::ErrorKind::Nginx) .invoke(embassy::ErrorKind::Nginx)
.await?; .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(); let mut shutdown_recv = ctx.shutdown.subscribe();
rpc_server!({ rpc_server!({
command: embassy::diagnostic_api, command: embassy::diagnostic_api,

View File

@@ -1,3 +1,4 @@
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
@@ -36,7 +37,16 @@ fn err_to_500(e: Error) -> Response<Body> {
#[instrument] #[instrument]
async fn inner_main(cfg_path: Option<&str>) -> Result<Option<Shutdown>, Error> { 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 mut shutdown_recv = rpc_ctx.shutdown.subscribe();
let sig_handler_ctx = rpc_ctx.clone(); let sig_handler_ctx = rpc_ctx.clone();
@@ -302,7 +312,21 @@ fn main() {
.arg("nginx") .arg("nginx")
.invoke(embassy::ErrorKind::Nginx) .invoke(embassy::ErrorKind::Nginx)
.await?; .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!({ rpc_server!({
command: embassy::diagnostic_api, command: embassy::diagnostic_api,
context: ctx.clone(), context: ctx.clone(),

View File

@@ -1,6 +1,6 @@
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
use std::ops::Deref; use std::ops::Deref;
use std::path::Path; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::yajrc::RpcError;
@@ -20,7 +20,7 @@ use crate::{Error, ResultExt};
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct DiagnosticContextConfig { pub struct DiagnosticContextConfig {
pub bind_rpc: Option<SocketAddr>, pub bind_rpc: Option<SocketAddr>,
pub zfs_pool_name: Option<String>, pub datadir: Option<PathBuf>,
} }
impl DiagnosticContextConfig { impl DiagnosticContextConfig {
#[instrument(skip(path))] #[instrument(skip(path))]
@@ -38,35 +38,40 @@ impl DiagnosticContextConfig {
Ok(Self::default()) Ok(Self::default())
} }
} }
pub fn zfs_pool_name(&self) -> &str { pub fn datadir(&self) -> &Path {
self.zfs_pool_name self.datadir
.as_ref() .as_deref()
.map(|s| s.as_str()) .unwrap_or_else(|| Path::new("/embassy-data"))
.unwrap_or("embassy-data")
} }
} }
pub struct DiagnosticContextSeed { pub struct DiagnosticContextSeed {
pub bind_rpc: SocketAddr, pub bind_rpc: SocketAddr,
pub datadir: PathBuf,
pub shutdown: Sender<Option<Shutdown>>, pub shutdown: Sender<Option<Shutdown>>,
pub error: Arc<RpcError>, pub error: Arc<RpcError>,
pub zfs_pool_name: Arc<String>, pub disk_guid: Option<Arc<String>>,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct DiagnosticContext(Arc<DiagnosticContextSeed>); pub struct DiagnosticContext(Arc<DiagnosticContextSeed>);
impl DiagnosticContext { impl DiagnosticContext {
#[instrument(skip(path))] #[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 cfg = DiagnosticContextConfig::load(path).await?;
let (shutdown, _) = tokio::sync::broadcast::channel(1); let (shutdown, _) = tokio::sync::broadcast::channel(1);
Ok(Self(Arc::new(DiagnosticContextSeed { Ok(Self(Arc::new(DiagnosticContextSeed {
bind_rpc: cfg.bind_rpc.unwrap_or(([127, 0, 0, 1], 5959).into()), bind_rpc: cfg.bind_rpc.unwrap_or(([127, 0, 0, 1], 5959).into()),
datadir: cfg.datadir().to_owned(),
shutdown, shutdown,
disk_guid,
error: Arc::new(error.into()), 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_control: Option<SocketAddr>,
pub tor_socks: Option<SocketAddr>, pub tor_socks: Option<SocketAddr>,
pub revision_cache_size: Option<usize>, pub revision_cache_size: Option<usize>,
pub zfs_pool_name: Option<String>,
pub datadir: Option<PathBuf>, pub datadir: Option<PathBuf>,
pub log_server: Option<Url>, pub log_server: Option<Url>,
} }
@@ -62,17 +61,10 @@ impl RpcContextConfig {
Ok(Self::default()) Ok(Self::default())
} }
} }
pub fn zfs_pool_name(&self) -> &str { pub fn datadir(&self) -> &Path {
self.zfs_pool_name
.as_ref()
.map(|s| s.as_str())
.unwrap_or("embassy-data")
}
pub fn datadir(&self) -> Cow<'_, Path> {
self.datadir self.datadir
.as_ref() .as_deref()
.map(|a| Cow::Borrowed(a.as_path())) .unwrap_or_else(|| Path::new("/embassy-data"))
.unwrap_or_else(|| Cow::Owned(Path::new("/").join(self.zfs_pool_name())))
} }
pub async fn db(&self, secret_store: &SqlitePool) -> Result<PatchDb, Error> { pub async fn db(&self, secret_store: &SqlitePool) -> Result<PatchDb, Error> {
let db_path = self.datadir().join("main").join("embassy.db"); let db_path = self.datadir().join("main").join("embassy.db");
@@ -115,7 +107,7 @@ pub struct RpcContextSeed {
pub bind_ws: SocketAddr, pub bind_ws: SocketAddr,
pub bind_static: SocketAddr, pub bind_static: SocketAddr,
pub datadir: PathBuf, pub datadir: PathBuf,
pub zfs_pool_name: Arc<String>, pub disk_guid: Arc<String>,
pub db: PatchDb, pub db: PatchDb,
pub secret_store: SqlitePool, pub secret_store: SqlitePool,
pub docker: Docker, pub docker: Docker,
@@ -136,7 +128,10 @@ pub struct RpcContextSeed {
pub struct RpcContext(Arc<RpcContextSeed>); pub struct RpcContext(Arc<RpcContextSeed>);
impl RpcContext { impl RpcContext {
#[instrument(skip(cfg_path))] #[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 base = RpcContextConfig::load(cfg_path).await?;
let log_epoch = Arc::new(AtomicU64::new(rand::random())); let log_epoch = Arc::new(AtomicU64::new(rand::random()));
let logger = EmbassyLogger::init(log_epoch.clone(), base.log_server.clone(), false); 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_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()), bind_static: base.bind_static.unwrap_or(([127, 0, 0, 1], 5961).into()),
datadir: base.datadir().to_path_buf(), datadir: base.datadir().to_path_buf(),
zfs_pool_name: Arc::new(base.zfs_pool_name().to_owned()), disk_guid,
db, db,
secret_store, secret_store,
docker, docker,

View File

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

View File

@@ -3,238 +3,273 @@ use std::path::Path;
use tokio::process::Command; use tokio::process::Command;
use tracing::instrument; use tracing::instrument;
use crate::disk::util::{mount, unmount};
use crate::util::Invoke; use crate::util::Invoke;
use crate::{Error, ResultExt}; use crate::{Error, ResultExt};
pub const PASSWORD_PATH: &'static str = "/etc/embassy/password"; pub const PASSWORD_PATH: &'static str = "/etc/embassy/password";
pub const DEFAULT_PASSWORD: &'static str = "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 // TODO: use IncorrectDisk / DiskNotAvailable / DiskCorrupted
#[instrument(skip(disks))] #[instrument(skip(disks, datadir, password))]
pub async fn create<I: IntoIterator<Item = P>, P: AsRef<Path>>( pub async fn create<I, P>(
pool_name: &str, disks: &I,
disks: I, datadir: impl AsRef<Path>,
password: &str, password: &str,
) -> Result<String, Error> { ) -> Result<String, Error>
let guid = create_pool(pool_name, disks).await?; where
create_fs(pool_name, password).await?; for<'a> &'a I: IntoIterator<Item = &'a P>,
export(pool_name).await?; P: AsRef<Path>,
{
let guid = create_pool(disks).await?;
create_all_fs(&guid, &datadir, password).await?;
export(&guid, datadir).await?;
Ok(guid) 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))] #[instrument(skip(disks))]
pub async fn create_pool<I: IntoIterator<Item = P>, P: AsRef<Path>>( pub async fn create_pool<I, P>(disks: &I) -> Result<String, Error>
pool_name: &str, where
disks: I, for<'a> &'a I: IntoIterator<Item = &'a P>,
) -> Result<String, Error> { P: AsRef<Path>,
let mut cmd = Command::new("zpool"); {
cmd.arg("create").arg("-f").arg(pool_name); 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 { for disk in disks {
cmd.arg(disk.as_ref()); cmd.arg(disk.as_ref());
} }
cmd.invoke(crate::ErrorKind::Zfs).await?; cmd.invoke(crate::ErrorKind::DiskManagement).await?;
Ok(String::from_utf8( Ok(guid)
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())
} }
#[instrument] #[derive(Debug, Clone, Copy)]
pub async fn create_fs(pool_name: &str, password: &str) -> Result<(), Error> { 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) tokio::fs::write(PASSWORD_PATH, password)
.await .await
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?; .with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
Command::new("zfs") let mut cmd = Command::new("lvcreate");
.arg("create") match size {
.arg("-o") FsSize::Gigabytes(a) => cmd.arg("-L").arg(format!("{}G", a)),
.arg("reservation=5G") FsSize::FreePercentage(a) => cmd.arg("-l").arg(format!("{}%FREE", a)),
.arg("-o") };
.arg("encryption=on") cmd.arg("-y")
.arg("-o") .arg("-n")
.arg("keylocation=file:///etc/embassy/password") .arg(name)
.arg("-o") .arg(guid)
.arg("keyformat=passphrase") .invoke(crate::ErrorKind::DiskManagement)
.arg(format!("{}/main", pool_name))
.invoke(crate::ErrorKind::Zfs)
.await?; .await?;
Command::new("zfs") Command::new("cryptsetup")
.arg("create") .arg("luksFormat")
.arg("-o") .arg(format!("--key-file={}", PASSWORD_PATH))
.arg("reservation=5G") .arg(format!("--keyfile-size={}", password.len()))
.arg(format!("{}/updates", pool_name)) .arg(Path::new("/dev").join(guid).join(name))
.invoke(crate::ErrorKind::Zfs) .invoke(crate::ErrorKind::DiskManagement)
.await?; .await?;
Command::new("zfs") Command::new("cryptsetup")
.arg("create") .arg("luksOpen")
.arg("-o") .arg(format!("--key-file={}", PASSWORD_PATH))
.arg("encryption=on") .arg(format!("--keyfile-size={}", password.len()))
.arg("-o") .arg(Path::new("/dev").join(guid).join(name))
.arg("keylocation=file:///etc/embassy/password") .arg(format!("{}_{}", guid, name))
.arg("-o") .invoke(crate::ErrorKind::DiskManagement)
.arg("keyformat=passphrase")
.arg(format!("{}/package-data", pool_name))
.invoke(crate::ErrorKind::Zfs)
.await?; .await?;
Command::new("zfs") if swap {
.arg("create") Command::new("mkswap")
.arg("-o") .arg("-f")
.arg("encryption=on") .arg(Path::new("/dev/mapper").join(format!("{}_{}", guid, name)))
.arg("-o") .invoke(crate::ErrorKind::DiskManagement)
.arg("keylocation=file:///etc/embassy/password") .await?;
.arg("-o") Command::new("swapon")
.arg("keyformat=passphrase") .arg(Path::new("/dev/mapper").join(format!("{}_{}", guid, name)))
.arg(format!("{}/tmp", pool_name)) .invoke(crate::ErrorKind::DiskManagement)
.invoke(crate::ErrorKind::Zfs) .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?; .await?;
}
tokio::fs::remove_file(PASSWORD_PATH) tokio::fs::remove_file(PASSWORD_PATH)
.await .await
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?; .with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
Ok(()) Ok(())
} }
#[instrument] #[instrument(skip(datadir, password))]
pub async fn create_swap(pool_name: &str) -> Result<(), Error> { pub async fn create_all_fs<P: AsRef<Path>>(
let pagesize = String::from_utf8( guid: &str,
Command::new("getconf") datadir: P,
.arg("PAGESIZE") password: &str,
.invoke(crate::ErrorKind::Zfs) ) -> Result<(), Error> {
.await?, create_fs(guid, &datadir, "main", MAIN_FS_SIZE, false, password).await?;
)?; create_fs(guid, &datadir, "swap", SWAP_SIZE, true, password).await?;
Command::new("zfs") create_fs(
.arg("create") guid,
.arg("-V8G") &datadir,
.arg("-b") "package-data",
.arg(pagesize) FsSize::FreePercentage(100),
.arg("-o") false,
.arg("logbias=throughput") password,
.arg("-o") )
.arg("sync=always") .await?;
.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)
.await?;
Ok(()) Ok(())
} }
#[instrument(skip(datadir))] #[instrument(skip(datadir))]
pub async fn mount<P: AsRef<Path>>( pub async fn unmount_fs<P: AsRef<Path>>(
pool_name: &str, guid: &str,
datadir: P, datadir: P,
name: &str,
swap: bool,
) -> Result<(), Error> {
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, password: &str,
) -> Result<(), Error> { ) -> 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)
.await?;
}
tokio::fs::write(PASSWORD_PATH, password) tokio::fs::write(PASSWORD_PATH, password)
.await .await
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?; .with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
Command::new("zfs") Command::new("cryptsetup")
.arg("load-key") .arg("luksOpen")
.arg(format!("{}/main", pool_name)) .arg(format!("--key-file={}", PASSWORD_PATH))
.invoke(crate::ErrorKind::Zfs) .arg(format!("--keyfile-size={}", password.len()))
.arg(Path::new("/dev").join(guid).join(name))
.arg(format!("{}_{}", guid, name))
.invoke(crate::ErrorKind::DiskManagement)
.await?; .await?;
Command::new("zfs") if swap {
.arg("load-key") Command::new("swapon")
.arg(format!("{}/package-data", pool_name)) .arg(Path::new("/dev/mapper").join(format!("{}_{}", guid, name)))
.invoke(crate::ErrorKind::Zfs) .invoke(crate::ErrorKind::DiskManagement)
.await?; .await?;
Command::new("zfs") } else {
.arg("load-key") mount(
.arg(format!("{}/tmp", pool_name)) Path::new("/dev/mapper").join(format!("{}_{}", guid, name)),
.invoke(crate::ErrorKind::Zfs) datadir.as_ref().join(name),
)
.await?; .await?;
}
tokio::fs::remove_file(PASSWORD_PATH) tokio::fs::remove_file(PASSWORD_PATH)
.await .await
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?; .with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
Command::new("zfs") Ok(())
.arg("mount") }
.arg(format!("{}/main", pool_name))
.invoke(crate::ErrorKind::Zfs) #[instrument(skip(datadir, password))]
.await?; pub async fn mount_all_fs<P: AsRef<Path>>(
Command::new("zfs") guid: &str,
.arg("mount") datadir: P,
.arg(format!("{}/package-data", pool_name)) password: &str,
.invoke(crate::ErrorKind::Zfs) ) -> Result<(), Error> {
.await?; mount_fs(guid, &datadir, "main", false, password).await?;
Command::new("zfs") mount_fs(guid, &datadir, "swap", true, password).await?;
.arg("mount") mount_fs(guid, &datadir, "package-data", false, password).await?;
.arg(format!("{}/tmp", pool_name))
.invoke(crate::ErrorKind::Zfs)
.await?;
Ok(()) Ok(())
} }

View File

@@ -62,7 +62,6 @@ const SYS_BLOCK_PATH: &'static str = "/sys/block";
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref PARTITION_REGEX: Regex = Regex::new("-part[0-9]+$").unwrap(); 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))] #[instrument(skip(path))]
@@ -150,22 +149,7 @@ pub async fn get_used<P: AsRef<Path>>(path: P) -> Result<usize, Error> {
#[instrument] #[instrument]
pub async fn list() -> Result<Vec<DiskInfo>, Error> { pub async fn list() -> Result<Vec<DiskInfo>, Error> {
let zpool_drives: BTreeSet<PathBuf> = match Command::new("zpool") let internal_drives: BTreeSet<PathBuf> = BTreeSet::new(); // todo!("parse pvscan");
.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 disks = tokio_stream::wrappers::ReadDirStream::new( let disks = tokio_stream::wrappers::ReadDirStream::new(
tokio::fs::read_dir(DISK_PATH) tokio::fs::read_dir(DISK_PATH)
.await .await
@@ -233,7 +217,7 @@ pub async fn list() -> Result<Vec<DiskInfo>, Error> {
tracing::warn!("Could not get capacity of {}: {}", disk.display(), e.source) tracing::warn!("Could not get capacity of {}: {}", disk.display(), e.source)
}) })
.unwrap_or_default(); .unwrap_or_default();
if zpool_drives.contains(&disk) { if internal_drives.contains(&disk) {
internal = true; internal = true;
} else { } else {
for part in parts { for part in parts {

View File

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

View File

@@ -142,25 +142,8 @@ pub async fn execute_inner(
)); ));
} }
let guid = let guid =
crate::disk::main::create(&ctx.zfs_pool_name, [embassy_logicalname], DEFAULT_PASSWORD) crate::disk::main::create(&[embassy_logicalname], &ctx.datadir, DEFAULT_PASSWORD).await?;
.await?; crate::disk::main::import(&guid, &ctx.datadir, 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?;
let password = argon2::hash_encoded( let password = argon2::hash_encoded(
embassy_password.as_bytes(), embassy_password.as_bytes(),
&rand::random::<[u8; 16]>()[..], &rand::random::<[u8; 16]>()[..],

View File

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

View File

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