mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
switch to LVM + LUKS + ext4 (#747)
* code complete * misc fixes * magic
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()),
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
}))
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]>()[..],
|
||||
|
||||
@@ -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)),
|
||||
}))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user