setup flow mvp complete

This commit is contained in:
Aiden McClelland
2021-09-11 18:27:23 -06:00
committed by Aiden McClelland
parent c90b46da1e
commit bf701e2e28
12 changed files with 359 additions and 119 deletions

View File

@@ -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": {

View File

@@ -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");

View File

@@ -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!({

View File

@@ -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(),

View File

@@ -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 {

View File

@@ -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(())

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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",
}
}
}

View File

@@ -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(

View File

@@ -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(),
})
}

View File

@@ -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"])