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