diff --git a/appmgr/sqlx-data.json b/appmgr/sqlx-data.json index a3e5bb9ab..9f207d592 100644 --- a/appmgr/sqlx-data.json +++ b/appmgr/sqlx-data.json @@ -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": { diff --git a/appmgr/src/bin/embassy-init.rs b/appmgr/src/bin/embassy-init.rs index 433139e47..559766107 100644 --- a/appmgr/src/bin/embassy-init.rs +++ b/appmgr/src/bin/embassy-init.rs @@ -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"); diff --git a/appmgr/src/bin/embassyd.rs b/appmgr/src/bin/embassyd.rs index aebdd8662..e80555c4e 100644 --- a/appmgr/src/bin/embassyd.rs +++ b/appmgr/src/bin/embassyd.rs @@ -62,20 +62,6 @@ async fn inner_main(cfg_path: Option<&str>) -> Result, Error> { .expect("send shutdown signal"); }); - if !rpc_ctx.db.exists(&::default()).await? { - rpc_ctx - .db - .put( - &::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!({ diff --git a/appmgr/src/context/rpc.rs b/appmgr/src/context/rpc.rs index 1bf72af0f..b47c952ca 100644 --- a/appmgr/src/context/rpc.rs +++ b/appmgr/src/context/rpc.rs @@ -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 { + pub async fn db(&self, secret_store: &SqlitePool) -> Result { 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(&::default()).await? { + db.put( + &::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 { let secret_store_url = format!( @@ -108,8 +125,8 @@ impl RpcContext { pub async fn init>(cfg_path: Option

) -> Result { 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(), diff --git a/appmgr/src/context/setup.rs b/appmgr/src/context/setup.rs index a0d93c2b6..205212433 100644 --- a/appmgr/src/context/setup.rs +++ b/appmgr/src/context/setup.rs @@ -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, + pub zfs_pool_name: Option, + pub datadir: Option, } impl SetupContextConfig { pub async fn load>(path: Option

) -> Result { @@ -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>(path: Option

) -> Result { 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 { + 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(&::default()).await? { + db.put( + &::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 { + 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 { diff --git a/appmgr/src/disk/main.rs b/appmgr/src/disk/main.rs index 337b2dcb0..96bc02053 100644 --- a/appmgr/src/disk/main.rs +++ b/appmgr/src/disk/main.rs @@ -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, P: AsRef>( + pool_name: &str, + disks: I, password: &str, ) -> Result { - 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>( + 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 { - Command::new("zpool") - .arg("create") - .arg(cfg.zfs_pool_name()) - .args(disks) - .invoke(crate::ErrorKind::Zfs) - .await?; +pub async fn create_pool, P: AsRef>( + pool_name: &str, + disks: I, +) -> Result { + 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>( + 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(()) diff --git a/appmgr/src/disk/mod.rs b/appmgr/src/disk/mod.rs index ad7722d56..2465361ea 100644 --- a/appmgr/src/disk/mod.rs +++ b/appmgr/src/disk/mod.rs @@ -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, 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, +) -> Result, Error> { + crate::disk::util::list().await +} diff --git a/appmgr/src/disk/util.rs b/appmgr/src/disk/util.rs index 6a97b5d28..29a684056 100644 --- a/appmgr/src/disk/util.rs +++ b/appmgr/src/disk/util.rs @@ -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, - capacity: usize, - embassy_os: Option, + pub logicalname: PathBuf, + pub vendor: Option, + pub model: Option, + pub partitions: Vec, + pub capacity: usize, + pub embassy_os: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct PartitionInfo { - logicalname: PathBuf, - label: Option, - capacity: usize, - used: Option, + pub logicalname: PathBuf, + pub label: Option, + pub capacity: usize, + pub used: Option, } #[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>(path: P) -> Result, 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>(path: P) -> Result, 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>(path: P) -> Result { 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>(path: P) -> Result, 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>(path: P) -> Result { 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, 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, 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, Error> { } res.push(DiskInfo { logicalname: disk, + vendor, + model, partitions, capacity, embassy_os, diff --git a/appmgr/src/error.rs b/appmgr/src/error.rs index cbf77bf90..c597be535 100644 --- a/appmgr/src/error.rs +++ b/appmgr/src/error.rs @@ -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", } } } diff --git a/appmgr/src/middleware/encrypt.rs b/appmgr/src/middleware/encrypt.rs index 935a0a53c..b18f57e30 100644 --- a/appmgr/src/middleware/encrypt.rs +++ b/appmgr/src/middleware/encrypt.rs @@ -165,8 +165,8 @@ pub fn encrypt(key: Arc) -> DynMiddleware { 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( diff --git a/appmgr/src/setup.rs b/appmgr/src/setup.rs index a8249befb..1cc4d36f5 100644 --- a/appmgr/src/setup.rs +++ b/appmgr/src/setup.rs @@ -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, } -#[command(rpc_only, metadata(encrypted = true))] +#[command(rpc_only)] pub fn status() -> Result { // TODO Ok(StatusRes { @@ -25,29 +30,44 @@ pub fn status() -> Result { }) } -#[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, - capacity: usize, - used: Option, - recovery: Option, -} - -#[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, Error> { - todo!() +pub async fn execute( + #[context] ctx: SetupContext, + #[arg(rename = "embassy-logicalname")] embassy_logicalname: PathBuf, + #[arg(rename = "embassy-password")] embassy_password: String, +) -> Result { + 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(), + }) } diff --git a/appmgr/src/version/mod.rs b/appmgr/src/version/mod.rs index 378297921..4f495b515 100644 --- a/appmgr/src/version/mod.rs +++ b/appmgr/src/version/mod.rs @@ -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 { Ok( git_version::git_version!(args = ["--always", "--abbrev=40", "--dirty=-modified"])