mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
setup flow mvp complete
This commit is contained in:
committed by
Aiden McClelland
parent
c90b46da1e
commit
bf701e2e28
@@ -142,6 +142,16 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"70a100abc8ca04ffc559ca16460d4fd4c65aa34952ade2681491f0b44dc1aaa1": {
|
||||
"query": "INSERT OR REPLACE INTO account (id, password, tor_key) VALUES (?, ?, ?)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"8595651866e7db772260bd79e19d55b7271fd795b82a99821c935a9237c1aa16": {
|
||||
"query": "SELECT interface, key FROM tor WHERE package = ?",
|
||||
"describe": {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use embassy::context::rpc::RpcContextConfig;
|
||||
use embassy::context::{RecoveryContext, SetupContext};
|
||||
use embassy::disk::main::DEFAULT_PASSWORD;
|
||||
use embassy::hostname::get_product_key;
|
||||
use embassy::middleware::encrypt::encrypt;
|
||||
use embassy::util::Invoke;
|
||||
@@ -20,11 +21,12 @@ async fn init(cfg_path: Option<&str>) -> Result<(), Error> {
|
||||
embassy::disk::util::mount("LABEL=EMBASSY", "/embassy-os").await?;
|
||||
if tokio::fs::metadata("/embassy-os/disk.guid").await.is_ok() {
|
||||
embassy::disk::main::load(
|
||||
&cfg,
|
||||
tokio::fs::read_to_string("/embassy-os/disk.guid")
|
||||
.await?
|
||||
.trim(),
|
||||
"password",
|
||||
cfg.zfs_pool_name(),
|
||||
cfg.datadir(),
|
||||
DEFAULT_PASSWORD,
|
||||
)
|
||||
.await?;
|
||||
log::info!("Loaded Disk");
|
||||
|
||||
@@ -62,20 +62,6 @@ async fn inner_main(cfg_path: Option<&str>) -> Result<Option<Shutdown>, Error> {
|
||||
.expect("send shutdown signal");
|
||||
});
|
||||
|
||||
if !rpc_ctx.db.exists(&<JsonPointer>::default()).await? {
|
||||
rpc_ctx
|
||||
.db
|
||||
.put(
|
||||
&<JsonPointer>::default(),
|
||||
&Database::init(
|
||||
get_id().await?,
|
||||
&get_hostname().await?,
|
||||
&os_key(&mut rpc_ctx.secret_store.acquire().await?).await?,
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let auth = auth(rpc_ctx.clone());
|
||||
let ctx = rpc_ctx.clone();
|
||||
let server = rpc_server!({
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::sync::atomic::{AtomicU64, AtomicUsize};
|
||||
use std::sync::Arc;
|
||||
|
||||
use bollard::Docker;
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::{PatchDb, Revision};
|
||||
use reqwest::Url;
|
||||
use rpc_toolkit::url::Host;
|
||||
@@ -18,7 +19,10 @@ use tokio::fs::File;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::db::model::Database;
|
||||
use crate::hostname::{get_hostname, get_id};
|
||||
use crate::manager::ManagerMap;
|
||||
use crate::net::tor::os_key;
|
||||
use crate::net::NetController;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::util::{from_toml_async_reader, AsyncFileExt};
|
||||
@@ -61,11 +65,24 @@ impl RpcContextConfig {
|
||||
.map(|a| Cow::Borrowed(a.as_path()))
|
||||
.unwrap_or_else(|| Cow::Owned(Path::new("/").join(self.zfs_pool_name())))
|
||||
}
|
||||
pub async fn db(&self) -> Result<PatchDb, Error> {
|
||||
pub async fn db(&self, secret_store: &SqlitePool) -> Result<PatchDb, Error> {
|
||||
let db_path = self.datadir().join("main").join("embassy.db");
|
||||
PatchDb::open(&db_path)
|
||||
let db = PatchDb::open(&db_path)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, db_path.display().to_string()))
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, db_path.display().to_string()))?;
|
||||
if !db.exists(&<JsonPointer>::default()).await? {
|
||||
db.put(
|
||||
&<JsonPointer>::default(),
|
||||
&Database::init(
|
||||
get_id().await?,
|
||||
&get_hostname().await?,
|
||||
&os_key(&mut secret_store.acquire().await?).await?,
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(db)
|
||||
}
|
||||
pub async fn secret_store(&self) -> Result<SqlitePool, Error> {
|
||||
let secret_store_url = format!(
|
||||
@@ -108,8 +125,8 @@ impl RpcContext {
|
||||
pub async fn init<P: AsRef<Path>>(cfg_path: Option<P>) -> Result<Self, Error> {
|
||||
let base = RpcContextConfig::load(cfg_path).await?;
|
||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||
let db = base.db().await?;
|
||||
let secret_store = base.secret_store().await?;
|
||||
let db = base.db(&secret_store).await?;
|
||||
let docker = Docker::connect_with_unix_defaults()?;
|
||||
let net_controller = NetController::init(
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
use std::borrow::Cow;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::PatchDb;
|
||||
use rpc_toolkit::Context;
|
||||
use serde::Deserialize;
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
use sqlx::{Sqlite, SqlitePool};
|
||||
use tokio::fs::File;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
use url::Host;
|
||||
|
||||
use crate::db::model::Database;
|
||||
use crate::hostname::{get_hostname, get_id};
|
||||
use crate::net::tor::os_key;
|
||||
use crate::util::{from_toml_async_reader, AsyncFileExt};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
@@ -16,6 +24,8 @@ 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 {
|
||||
pub async fn load<P: AsRef<Path>>(path: Option<P>) -> Result<Self, Error> {
|
||||
@@ -32,11 +42,25 @@ 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> {
|
||||
self.datadir
|
||||
.as_ref()
|
||||
.map(|a| Cow::Borrowed(a.as_path()))
|
||||
.unwrap_or_else(|| Cow::Owned(Path::new("/").join(self.zfs_pool_name())))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetupContextSeed {
|
||||
pub bind_rpc: SocketAddr,
|
||||
pub shutdown: Sender<()>,
|
||||
pub datadir: PathBuf,
|
||||
pub zfs_pool_name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -45,11 +69,49 @@ 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 = cfg.zfs_pool_name().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,
|
||||
})))
|
||||
}
|
||||
pub async fn db(&self, secret_store: &SqlitePool) -> Result<PatchDb, Error> {
|
||||
let db_path = self.datadir.join("main").join("embassy.db");
|
||||
let db = PatchDb::open(&db_path)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, db_path.display().to_string()))?;
|
||||
if !db.exists(&<JsonPointer>::default()).await? {
|
||||
db.put(
|
||||
&<JsonPointer>::default(),
|
||||
&Database::init(
|
||||
get_id().await?,
|
||||
&get_hostname().await?,
|
||||
&os_key(&mut secret_store.acquire().await?).await?,
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(db)
|
||||
}
|
||||
pub async fn secret_store(&self) -> Result<SqlitePool, Error> {
|
||||
let secret_store_url = format!(
|
||||
"sqlite://{}",
|
||||
self.datadir.join("main").join("secrets.db").display()
|
||||
);
|
||||
if !Sqlite::database_exists(&secret_store_url).await? {
|
||||
Sqlite::create_database(&secret_store_url).await?;
|
||||
}
|
||||
let secret_store = SqlitePool::connect(&secret_store_url).await?;
|
||||
sqlx::migrate!()
|
||||
.run(&secret_store)
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Database)?;
|
||||
Ok(secret_store)
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for SetupContext {
|
||||
|
||||
@@ -3,49 +3,57 @@ use std::path::Path;
|
||||
use anyhow::anyhow;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::context::rpc::RpcContextConfig;
|
||||
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 async fn create(
|
||||
cfg: &RpcContextConfig,
|
||||
disks: &[&str],
|
||||
pub async fn create<I: IntoIterator<Item = P>, P: AsRef<Path>>(
|
||||
pool_name: &str,
|
||||
disks: I,
|
||||
password: &str,
|
||||
) -> Result<String, Error> {
|
||||
let guid = create_pool(cfg, disks).await?;
|
||||
create_fs(cfg, password).await?;
|
||||
export(cfg).await?;
|
||||
let guid = create_pool(pool_name, disks).await?;
|
||||
create_fs(pool_name, password).await?;
|
||||
export(pool_name).await?;
|
||||
Ok(guid)
|
||||
}
|
||||
|
||||
pub async fn load(cfg: &RpcContextConfig, guid: &str, password: &str) -> Result<(), Error> {
|
||||
pub async fn load<P: AsRef<Path>>(
|
||||
guid: &str,
|
||||
pool_name: &str,
|
||||
datadir: P,
|
||||
password: &str,
|
||||
) -> Result<(), Error> {
|
||||
import(guid).await?;
|
||||
mount(cfg, password).await?;
|
||||
mount(pool_name, datadir, password).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_pool(cfg: &RpcContextConfig, disks: &[&str]) -> Result<String, Error> {
|
||||
Command::new("zpool")
|
||||
.arg("create")
|
||||
.arg(cfg.zfs_pool_name())
|
||||
.args(disks)
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
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(pool_name);
|
||||
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(cfg.zfs_pool_name())
|
||||
.arg(pool_name)
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?,
|
||||
)?)
|
||||
}
|
||||
|
||||
pub async fn create_fs(cfg: &RpcContextConfig, password: &str) -> Result<(), Error> {
|
||||
pub async fn create_fs(pool_name: &str, password: &str) -> Result<(), Error> {
|
||||
tokio::fs::write(PASSWORD_PATH, password)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
|
||||
@@ -59,14 +67,14 @@ pub async fn create_fs(cfg: &RpcContextConfig, password: &str) -> Result<(), Err
|
||||
.arg("keylocation=file:///etc/embassy/password")
|
||||
.arg("-o")
|
||||
.arg("keyformat=passphrase")
|
||||
.arg(format!("{}/main", cfg.zfs_pool_name()))
|
||||
.arg(format!("{}/main", pool_name))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Command::new("zfs")
|
||||
.arg("create")
|
||||
.arg("-o")
|
||||
.arg("reservation=5G")
|
||||
.arg(format!("{}/updates", cfg.zfs_pool_name()))
|
||||
.arg(format!("{}/updates", pool_name))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Command::new("zfs")
|
||||
@@ -77,7 +85,7 @@ pub async fn create_fs(cfg: &RpcContextConfig, password: &str) -> Result<(), Err
|
||||
.arg("keylocation=file:///etc/embassy/password")
|
||||
.arg("-o")
|
||||
.arg("keyformat=passphrase")
|
||||
.arg(format!("{}/package-data", cfg.zfs_pool_name()))
|
||||
.arg(format!("{}/package-data", pool_name))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Command::new("zfs")
|
||||
@@ -88,7 +96,7 @@ pub async fn create_fs(cfg: &RpcContextConfig, password: &str) -> Result<(), Err
|
||||
.arg("keylocation=file:///etc/embassy/password")
|
||||
.arg("-o")
|
||||
.arg("keyformat=passphrase")
|
||||
.arg(format!("{}/tmp", cfg.zfs_pool_name()))
|
||||
.arg(format!("{}/tmp", pool_name))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
tokio::fs::remove_file(PASSWORD_PATH)
|
||||
@@ -97,7 +105,7 @@ pub async fn create_fs(cfg: &RpcContextConfig, password: &str) -> Result<(), Err
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_swap(cfg: &RpcContextConfig) -> Result<(), Error> {
|
||||
pub async fn create_swap(pool_name: &str) -> Result<(), Error> {
|
||||
let pagesize = String::from_utf8(
|
||||
Command::new("getconf")
|
||||
.arg("PAGESIZE")
|
||||
@@ -121,32 +129,24 @@ pub async fn create_swap(cfg: &RpcContextConfig) -> Result<(), Error> {
|
||||
.await?;
|
||||
Command::new("mkswap")
|
||||
.arg("-f")
|
||||
.arg(
|
||||
Path::new("/dev/zvol")
|
||||
.join(cfg.zfs_pool_name())
|
||||
.join("swap"),
|
||||
)
|
||||
.arg(Path::new("/dev/zvol").join(pool_name).join("swap"))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn use_swap(cfg: &RpcContextConfig) -> Result<(), Error> {
|
||||
pub async fn use_swap(pool_name: &str) -> Result<(), Error> {
|
||||
Command::new("swapon")
|
||||
.arg(
|
||||
Path::new("/dev/zvol")
|
||||
.join(cfg.zfs_pool_name())
|
||||
.join("swap"),
|
||||
)
|
||||
.arg(Path::new("/dev/zvol").join(pool_name).join("swap"))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn export(cfg: &RpcContextConfig) -> Result<(), Error> {
|
||||
pub async fn export(pool_name: &str) -> Result<(), Error> {
|
||||
Command::new("zpool")
|
||||
.arg("export")
|
||||
.arg(cfg.zfs_pool_name())
|
||||
.arg(pool_name)
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -178,22 +178,26 @@ pub async fn import(guid: &str) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn mount(cfg: &RpcContextConfig, password: &str) -> Result<(), Error> {
|
||||
pub async fn mount<P: AsRef<Path>>(
|
||||
pool_name: &str,
|
||||
datadir: P,
|
||||
password: &str,
|
||||
) -> Result<(), Error> {
|
||||
let mountpoint = String::from_utf8(
|
||||
Command::new("zfs")
|
||||
.arg("get")
|
||||
.arg("-H")
|
||||
.arg("-ovalue")
|
||||
.arg("mountpoint")
|
||||
.arg(cfg.zfs_pool_name())
|
||||
.arg(pool_name)
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?,
|
||||
)?;
|
||||
if Path::new(mountpoint.trim()) != &cfg.datadir() {
|
||||
if Path::new(mountpoint.trim()) != datadir.as_ref() {
|
||||
Command::new("zfs")
|
||||
.arg("set")
|
||||
.arg(format!("mountpoint={}", cfg.datadir().display()))
|
||||
.arg(cfg.zfs_pool_name())
|
||||
.arg(format!("mountpoint={}", datadir.as_ref().display()))
|
||||
.arg(pool_name)
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
}
|
||||
@@ -203,17 +207,17 @@ pub async fn mount(cfg: &RpcContextConfig, password: &str) -> Result<(), Error>
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
|
||||
Command::new("zfs")
|
||||
.arg("load-key")
|
||||
.arg(format!("{}/main", cfg.zfs_pool_name()))
|
||||
.arg(format!("{}/main", pool_name))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Command::new("zfs")
|
||||
.arg("load-key")
|
||||
.arg(format!("{}/package-data", cfg.zfs_pool_name()))
|
||||
.arg(format!("{}/package-data", pool_name))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Command::new("zfs")
|
||||
.arg("load-key")
|
||||
.arg(format!("{}/tmp", cfg.zfs_pool_name()))
|
||||
.arg(format!("{}/tmp", pool_name))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
tokio::fs::remove_file(PASSWORD_PATH)
|
||||
@@ -222,17 +226,17 @@ pub async fn mount(cfg: &RpcContextConfig, password: &str) -> Result<(), Error>
|
||||
|
||||
Command::new("zfs")
|
||||
.arg("mount")
|
||||
.arg(format!("{}/main", cfg.zfs_pool_name()))
|
||||
.arg(format!("{}/main", pool_name))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Command::new("zfs")
|
||||
.arg("mount")
|
||||
.arg(format!("{}/package-data", cfg.zfs_pool_name()))
|
||||
.arg(format!("{}/package-data", pool_name))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Command::new("zfs")
|
||||
.arg("mount")
|
||||
.arg(format!("{}/tmp", cfg.zfs_pool_name()))
|
||||
.arg(format!("{}/tmp", pool_name))
|
||||
.invoke(crate::ErrorKind::Zfs)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,2 +1,77 @@
|
||||
use clap::ArgMatches;
|
||||
use rpc_toolkit::command;
|
||||
|
||||
use self::util::DiskInfo;
|
||||
use crate::util::{display_serializable, IoFormat};
|
||||
use crate::Error;
|
||||
|
||||
pub mod main;
|
||||
pub mod util;
|
||||
|
||||
#[command(subcommands(list))]
|
||||
pub fn disk() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_disk_info(info: Vec<DiskInfo>, matches: &ArgMatches<'_>) {
|
||||
use prettytable::*;
|
||||
|
||||
if matches.is_present("format") {
|
||||
return display_serializable(info, matches);
|
||||
}
|
||||
|
||||
let mut table = Table::new();
|
||||
table.add_row(row![bc =>
|
||||
"LOGICALNAME",
|
||||
"LABEL",
|
||||
"CAPACITY",
|
||||
"USED",
|
||||
"EMBASSY OS VERSION"
|
||||
]);
|
||||
for disk in info {
|
||||
let row = row![
|
||||
disk.logicalname.display(),
|
||||
"N/A",
|
||||
&format!("{:.2} GiB", disk.capacity as f64 / 1024.0 / 1024.0 / 1024.0),
|
||||
"N/A",
|
||||
if let Some(eos_info) = disk.embassy_os.as_ref() {
|
||||
eos_info.version.as_str()
|
||||
} else {
|
||||
"N/A"
|
||||
}
|
||||
];
|
||||
table.add_row(row);
|
||||
for part in disk.partitions {
|
||||
let row = row![
|
||||
part.logicalname.display(),
|
||||
if let Some(label) = part.label.as_ref() {
|
||||
label
|
||||
} else {
|
||||
"N/A"
|
||||
},
|
||||
part.capacity,
|
||||
if let Some(used) = part
|
||||
.used
|
||||
.map(|u| format!("{:.2} GiB", u as f64 / 1024.0 / 1024.0 / 1024.0))
|
||||
.as_ref()
|
||||
{
|
||||
used
|
||||
} else {
|
||||
"N/A"
|
||||
},
|
||||
"N/A",
|
||||
];
|
||||
table.add_row(row);
|
||||
}
|
||||
}
|
||||
table.print_tty(false);
|
||||
}
|
||||
|
||||
#[command(display(display_disk_info))]
|
||||
pub async fn list(
|
||||
#[allow(unused_variables)]
|
||||
#[arg]
|
||||
format: Option<IoFormat>,
|
||||
) -> Result<Vec<DiskInfo>, Error> {
|
||||
crate::disk::util::list().await
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use futures::future::try_join_all;
|
||||
use futures::TryStreamExt;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use regex::Regex;
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::util::{Invoke, Version};
|
||||
use crate::util::{from_yaml_async_reader, GeneralGuard, Invoke, Version};
|
||||
use crate::{Error, ResultExt as _};
|
||||
|
||||
pub const TMP_MOUNTPOINT: &'static str = "/media/embassy-os";
|
||||
@@ -18,40 +17,78 @@ pub const TMP_MOUNTPOINT: &'static str = "/media/embassy-os";
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct DiskInfo {
|
||||
logicalname: PathBuf,
|
||||
partitions: Vec<PartitionInfo>,
|
||||
capacity: usize,
|
||||
embassy_os: Option<PartitionInfo>,
|
||||
pub logicalname: PathBuf,
|
||||
pub vendor: Option<String>,
|
||||
pub model: Option<String>,
|
||||
pub partitions: Vec<PartitionInfo>,
|
||||
pub capacity: usize,
|
||||
pub embassy_os: Option<EmbassyOsDiskInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct PartitionInfo {
|
||||
logicalname: PathBuf,
|
||||
label: Option<String>,
|
||||
capacity: usize,
|
||||
used: Option<usize>,
|
||||
pub logicalname: PathBuf,
|
||||
pub label: Option<String>,
|
||||
pub capacity: usize,
|
||||
pub used: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct EmbassyOsDiskInfo {
|
||||
version: Version,
|
||||
name: String,
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
const DISK_PATH: &'static str = "/dev/disk/by-path";
|
||||
const SYS_BLOCK_PATH: &'static str = "/sys/block";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref PARTITION_REGEX: Regex = Regex::new("-part[0-9]+$").unwrap();
|
||||
}
|
||||
|
||||
pub async fn get_vendor<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error> {
|
||||
let vendor = tokio::fs::read_to_string(
|
||||
Path::new(SYS_BLOCK_PATH)
|
||||
.join(path.as_ref().strip_prefix("/dev").map_err(|_| {
|
||||
Error::new(
|
||||
anyhow!("not a canonical block device"),
|
||||
crate::ErrorKind::BlockDevice,
|
||||
)
|
||||
})?)
|
||||
.join("device")
|
||||
.join("vendor"),
|
||||
)
|
||||
.await?;
|
||||
Ok(if vendor.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(vendor)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_model<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error> {
|
||||
let model = tokio::fs::read_to_string(
|
||||
Path::new(SYS_BLOCK_PATH)
|
||||
.join(path.as_ref().strip_prefix("/dev").map_err(|_| {
|
||||
Error::new(
|
||||
anyhow!("not a canonical block device"),
|
||||
crate::ErrorKind::BlockDevice,
|
||||
)
|
||||
})?)
|
||||
.join("device")
|
||||
.join("model"),
|
||||
)
|
||||
.await?;
|
||||
Ok(if model.is_empty() { None } else { Some(model) })
|
||||
}
|
||||
|
||||
pub async fn get_capacity<P: AsRef<Path>>(path: P) -> Result<usize, Error> {
|
||||
Ok(String::from_utf8(
|
||||
Command::new("blockdev")
|
||||
Command::new("BlockDevice")
|
||||
.arg("--getsize64")
|
||||
.arg(path.as_ref())
|
||||
.invoke(crate::ErrorKind::BlockDev)
|
||||
.invoke(crate::ErrorKind::BlockDevice)
|
||||
.await?,
|
||||
)?
|
||||
.parse()?)
|
||||
@@ -63,7 +100,7 @@ pub async fn get_label<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error>
|
||||
.arg("-no")
|
||||
.arg("label")
|
||||
.arg(path.as_ref())
|
||||
.invoke(crate::ErrorKind::BlockDev) // TODO: error kind
|
||||
.invoke(crate::ErrorKind::BlockDevice)
|
||||
.await?,
|
||||
)?;
|
||||
Ok(if label.is_empty() { None } else { Some(label) })
|
||||
@@ -74,7 +111,7 @@ pub async fn get_used<P: AsRef<Path>>(path: P) -> Result<usize, Error> {
|
||||
Command::new("df")
|
||||
.arg("--output=used")
|
||||
.arg(path.as_ref())
|
||||
.invoke(crate::ErrorKind::Unknown)
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?,
|
||||
)?
|
||||
.lines()
|
||||
@@ -129,6 +166,14 @@ pub async fn list() -> Result<Vec<DiskInfo>, Error> {
|
||||
let mut res = Vec::with_capacity(disks.len());
|
||||
for (disk, parts) in disks {
|
||||
let mut partitions = Vec::with_capacity(parts.len());
|
||||
let vendor = get_vendor(&disk)
|
||||
.await
|
||||
.map_err(|e| log::warn!("Could not get vendor of {}: {}", disk.display(), e.source))
|
||||
.unwrap_or_default();
|
||||
let model = get_model(&disk)
|
||||
.await
|
||||
.map_err(|e| log::warn!("Could not get model of {}: {}", disk.display(), e.source))
|
||||
.unwrap_or_default();
|
||||
let capacity = get_capacity(&disk)
|
||||
.await
|
||||
.map_err(|e| log::warn!("Could not get capacity of {}: {}", disk.display(), e.source))
|
||||
@@ -148,14 +193,29 @@ pub async fn list() -> Result<Vec<DiskInfo>, Error> {
|
||||
if let Err(e) = mount(&part, &tmp_mountpoint).await {
|
||||
log::warn!("Could not collect usage information: {}", e.source)
|
||||
} else {
|
||||
let mount_guard = GeneralGuard::new(|| {
|
||||
let path = tmp_mountpoint.clone();
|
||||
tokio::spawn(unmount(path))
|
||||
});
|
||||
used = get_used(&tmp_mountpoint)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::warn!("Could not get usage of {}: {}", part.display(), e.source)
|
||||
})
|
||||
.ok();
|
||||
// todo!("check embassy-os");
|
||||
unmount(&tmp_mountpoint).await?; // TODO: mount guard
|
||||
if label.as_deref() == Some("rootfs") {
|
||||
let version_path = tmp_mountpoint.join("root").join("appmgr").join("version");
|
||||
if tokio::fs::metadata(&version_path).await.is_ok() {
|
||||
embassy_os = Some(EmbassyOsDiskInfo {
|
||||
version: from_yaml_async_reader(File::open(&version_path).await?)
|
||||
.await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
mount_guard
|
||||
.drop()
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Unknown)??;
|
||||
}
|
||||
|
||||
partitions.push(PartitionInfo {
|
||||
@@ -167,6 +227,8 @@ pub async fn list() -> Result<Vec<DiskInfo>, Error> {
|
||||
}
|
||||
res.push(DiskInfo {
|
||||
logicalname: disk,
|
||||
vendor,
|
||||
model,
|
||||
partitions,
|
||||
capacity,
|
||||
embassy_os,
|
||||
|
||||
@@ -12,7 +12,7 @@ pub enum ErrorKind {
|
||||
ConfigSpecViolation = 4,
|
||||
ConfigRulesViolation = 5,
|
||||
NotFound = 6,
|
||||
InvalidPassword = 7,
|
||||
InvalidPassword = 7, // REMOVE
|
||||
VersionIncompatible = 8,
|
||||
Network = 9,
|
||||
Registry = 10,
|
||||
@@ -20,13 +20,13 @@ pub enum ErrorKind {
|
||||
Deserialization = 12,
|
||||
Utf8 = 13,
|
||||
ParseVersion = 14,
|
||||
Duplicity = 15,
|
||||
Duplicity = 15, // REMOVE
|
||||
Nginx = 16,
|
||||
Dependency = 17,
|
||||
ParseS9pk = 18,
|
||||
ParseUrl = 19,
|
||||
GParted = 20,
|
||||
BlockDev = 21,
|
||||
GParted = 20, // REMOVE
|
||||
BlockDevice = 21,
|
||||
InvalidOnionAddress = 22,
|
||||
Pack = 23,
|
||||
ValidateS9pk = 24,
|
||||
@@ -54,6 +54,7 @@ pub enum ErrorKind {
|
||||
Wifi = 46,
|
||||
Journald = 47,
|
||||
Zfs = 48,
|
||||
PasswordHashGeneration = 49,
|
||||
}
|
||||
impl ErrorKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
@@ -79,7 +80,7 @@ impl ErrorKind {
|
||||
ParseS9pk => "S9PK Parsing Error",
|
||||
ParseUrl => "URL Parsing Error",
|
||||
GParted => "GNU Parted Error",
|
||||
BlockDev => "BlockDev Error",
|
||||
BlockDevice => "Block Device Error",
|
||||
InvalidOnionAddress => "Invalid Onion Address",
|
||||
Pack => "Pack Error",
|
||||
ValidateS9pk => "S9PK Validation Error",
|
||||
@@ -107,6 +108,7 @@ impl ErrorKind {
|
||||
Wifi => "WiFi Internal Error",
|
||||
Journald => "Journald Error",
|
||||
Zfs => "ZFS Error",
|
||||
PasswordHashGeneration => "Password Hash Generation Error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,8 +165,8 @@ pub fn encrypt<M: Metadata>(key: Arc<String>) -> DynMiddleware<M> {
|
||||
async move {
|
||||
if !encrypted
|
||||
&& metadata
|
||||
.get(&rpc_req.method.as_str(), "encrypted")
|
||||
.unwrap_or_default()
|
||||
.get(&rpc_req.method.as_str(), "authenticated")
|
||||
.unwrap_or(true)
|
||||
{
|
||||
let (res_parts, _) = Response::new(()).into_parts();
|
||||
Ok(Err(to_response(
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
|
||||
use crate::util::Version;
|
||||
use crate::Error;
|
||||
use crate::context::SetupContext;
|
||||
use crate::disk::disk;
|
||||
use crate::disk::main::DEFAULT_PASSWORD;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[command(subcommands(status, disk))]
|
||||
pub fn setup() -> Result<(), Error> {
|
||||
@@ -16,7 +21,7 @@ pub struct StatusRes {
|
||||
tor_address: Option<String>,
|
||||
}
|
||||
|
||||
#[command(rpc_only, metadata(encrypted = true))]
|
||||
#[command(rpc_only)]
|
||||
pub fn status() -> Result<StatusRes, Error> {
|
||||
// TODO
|
||||
Ok(StatusRes {
|
||||
@@ -25,29 +30,44 @@ pub fn status() -> Result<StatusRes, Error> {
|
||||
})
|
||||
}
|
||||
|
||||
#[command(subcommands(list))]
|
||||
pub fn disk() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct DiskInfo {
|
||||
logicalname: String,
|
||||
labels: Vec<String>,
|
||||
capacity: usize,
|
||||
used: Option<usize>,
|
||||
recovery: Option<RecoveryInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct RecoveryInfo {
|
||||
version: Version,
|
||||
name: String,
|
||||
pub struct SetupResult {
|
||||
tor_address: String,
|
||||
}
|
||||
|
||||
#[command(rpc_only)]
|
||||
pub fn list() -> Result<Vec<DiskInfo>, Error> {
|
||||
todo!()
|
||||
pub async fn execute(
|
||||
#[context] ctx: SetupContext,
|
||||
#[arg(rename = "embassy-logicalname")] embassy_logicalname: PathBuf,
|
||||
#[arg(rename = "embassy-password")] embassy_password: String,
|
||||
) -> Result<SetupResult, Error> {
|
||||
let guid =
|
||||
crate::disk::main::create(&ctx.zfs_pool_name, [embassy_logicalname], DEFAULT_PASSWORD)
|
||||
.await?;
|
||||
tokio::fs::write("/embassy-os/disk.guid", &guid)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, "/embassy-os/disk.guid"))?;
|
||||
crate::disk::main::load(&guid, &ctx.zfs_pool_name, &ctx.datadir, DEFAULT_PASSWORD).await?;
|
||||
let password = argon2::hash_encoded(
|
||||
embassy_password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::default(),
|
||||
)
|
||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)?;
|
||||
let tor_key = TorSecretKeyV3::generate();
|
||||
let key_vec = tor_key.as_bytes().to_vec();
|
||||
let sqlite_pool = ctx.secret_store().await?;
|
||||
sqlx::query!(
|
||||
"INSERT OR REPLACE INTO account (id, password, tor_key) VALUES (?, ?, ?)",
|
||||
0,
|
||||
password,
|
||||
key_vec,
|
||||
)
|
||||
.execute(&mut sqlite_pool.acquire().await?)
|
||||
.await?;
|
||||
|
||||
Ok(SetupResult {
|
||||
tor_address: tor_key.public().get_onion_address().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ pub async fn self_update(requirement: emver::VersionRange) -> Result<(), Error>
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[command(rename = "git-info", local)]
|
||||
#[command(rename = "git-info", local, metadata(authenticated = false))]
|
||||
pub fn git_info() -> Result<String, Error> {
|
||||
Ok(
|
||||
git_version::git_version!(args = ["--always", "--abbrev=40", "--dirty=-modified"])
|
||||
|
||||
Reference in New Issue
Block a user