0.3.2 final cleanup (#1782)

* bump version with stubbed release notes

* increase BE timeout

* 032 release notes

* hide developer menu for now

* remove unused sub/import

* remoce reconnect from disks res in setup wiz

* remove quirks

* flatten drives response

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
Aiden McClelland
2022-09-08 16:14:42 -06:00
committed by GitHub
parent 5442459b2d
commit b9ce2bf2dc
32 changed files with 154 additions and 395 deletions

View File

@@ -75,7 +75,7 @@ jobs:
- target: aarch64 - target: aarch64
snapshot_download: arm_js_snapshot snapshot_download: arm_js_snapshot
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 60 timeout-minutes: 120
needs: build_libs needs: build_libs
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -219,7 +219,7 @@ jobs:
- name: Run tests - name: Run tests
run: | run: |
${CARGO_HOME:-~/.cargo}/bin/cargo-nextest nextest run --no-fail-fast --archive-file nextest-archive-${{ matrix.target }}.tar.zst \ ${CARGO_HOME:-~/.cargo}/bin/cargo-nextest nextest run --no-fail-fast --archive-file nextest-archive-${{ matrix.target }}.tar.zst \
--filter-expr 'not (test(system::test_get_temp) | test(net::tor::test) | test(system::test_get_disk_usage) | test(net::ssl::certificate_details_persist) | test(net::ssl::ca_details_persist) | test(logs::test_logs))' --filter-expr 'not (test(system::test_get_temp) | test(net::tor::test) | test(system::test_get_disk_usage) | test(net::ssl::certificate_details_persist) | test(net::ssl::ca_details_persist))'
if: ${{ matrix.target == 'x86_64' }} if: ${{ matrix.target == 'x86_64' }}
- name: Run tests - name: Run tests
@@ -236,5 +236,5 @@ jobs:
mkdir -p ~/.cargo/bin && mkdir -p ~/.cargo/bin &&
tar -zxvf nextest-aarch64.tar.gz -C ${CARGO_HOME:-~/.cargo}/bin && tar -zxvf nextest-aarch64.tar.gz -C ${CARGO_HOME:-~/.cargo}/bin &&
${CARGO_HOME:-~/.cargo}/bin/cargo-nextest nextest run --archive-file nextest-archive-${{ matrix.target }}.tar.zst \ ${CARGO_HOME:-~/.cargo}/bin/cargo-nextest nextest run --archive-file nextest-archive-${{ matrix.target }}.tar.zst \
--filter-expr "not (test(system::test_get_temp) | test(net::tor::test) | test(system::test_get_disk_usage) | test(net::ssl::certificate_details_persist) | test(net::ssl::ca_details_persist) | test(logs::test_logs))"' --filter-expr "not (test(system::test_get_temp) | test(net::tor::test) | test(system::test_get_disk_usage) | test(net::ssl::certificate_details_persist) | test(net::ssl::ca_details_persist))"'
if: ${{ matrix.target == 'aarch64' }} if: ${{ matrix.target == 'aarch64' }}

2
backend/Cargo.lock generated
View File

@@ -1122,7 +1122,7 @@ dependencies = [
[[package]] [[package]]
name = "embassy-os" name = "embassy-os"
version = "0.3.1-rev.1" version = "0.3.2"
dependencies = [ dependencies = [
"aes", "aes",
"async-stream", "async-stream",

View File

@@ -14,7 +14,7 @@ keywords = [
name = "embassy-os" name = "embassy-os"
readme = "README.md" readme = "README.md"
repository = "https://github.com/Start9Labs/embassy-os" repository = "https://github.com/Start9Labs/embassy-os"
version = "0.3.1-rev.1" version = "0.3.2"
[lib] [lib]
name = "embassy" name = "embassy"

View File

@@ -1,6 +1,5 @@
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc;
use chrono::Utc; use chrono::Utc;
use clap::ArgMatches; use clap::ArgMatches;
@@ -8,7 +7,7 @@ use color_eyre::eyre::eyre;
use helpers::AtomicFile; use helpers::AtomicFile;
use openssl::pkey::{PKey, Private}; use openssl::pkey::{PKey, Private};
use openssl::x509::X509; use openssl::x509::X509;
use patch_db::{DbHandle, LockType, PatchDbHandle, Revision}; use patch_db::{DbHandle, LockType, PatchDbHandle};
use rpc_toolkit::command; use rpc_toolkit::command;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;

View File

@@ -9,7 +9,7 @@ use color_eyre::eyre::eyre;
use futures::future::BoxFuture; use futures::future::BoxFuture;
use futures::FutureExt; use futures::FutureExt;
use openssl::x509::X509; use openssl::x509::X509;
use patch_db::{DbHandle, PatchDbHandle, Revision}; use patch_db::{DbHandle, PatchDbHandle};
use rpc_toolkit::command; use rpc_toolkit::command;
use tokio::fs::File; use tokio::fs::File;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
@@ -57,8 +57,7 @@ pub async fn restore_packages_rpc(
let backup_guard = let backup_guard =
BackupMountGuard::mount(TmpMountGuard::mount(&fs, ReadOnly).await?, &password).await?; BackupMountGuard::mount(TmpMountGuard::mount(&fs, ReadOnly).await?, &password).await?;
let (revision, backup_guard, tasks, _) = let (backup_guard, tasks, _) = restore_packages(&ctx, &mut db, backup_guard, ids).await?;
restore_packages(&ctx, &mut db, backup_guard, ids).await?;
tokio::spawn(async move { tokio::spawn(async move {
let res = futures::future::join_all(tasks).await; let res = futures::future::join_all(tasks).await;
@@ -246,7 +245,7 @@ pub async fn recover_full_embassy(
.keys() .keys()
.cloned() .cloned()
.collect(); .collect();
let (_, backup_guard, tasks, progress_info) = restore_packages( let (backup_guard, tasks, progress_info) = restore_packages(
&rpc_ctx, &rpc_ctx,
&mut db, &mut db,
backup_guard, backup_guard,
@@ -304,14 +303,13 @@ async fn restore_packages(
ids: Vec<PackageId>, ids: Vec<PackageId>,
) -> Result< ) -> Result<
( (
Option<Arc<Revision>>,
BackupMountGuard<TmpMountGuard>, BackupMountGuard<TmpMountGuard>,
Vec<JoinHandle<(Result<(), Error>, PackageId)>>, Vec<JoinHandle<(Result<(), Error>, PackageId)>>,
ProgressInfo, ProgressInfo,
), ),
Error, Error,
> { > {
let (revision, guards) = assure_restoring(ctx, db, ids, &backup_guard).await?; let guards = assure_restoring(ctx, db, ids, &backup_guard).await?;
let mut progress_info = ProgressInfo::default(); let mut progress_info = ProgressInfo::default();
@@ -339,7 +337,7 @@ async fn restore_packages(
)); ));
} }
Ok((revision, backup_guard, tasks, progress_info)) Ok((backup_guard, tasks, progress_info))
} }
#[instrument(skip(ctx, db, backup_guard))] #[instrument(skip(ctx, db, backup_guard))]
@@ -348,13 +346,7 @@ async fn assure_restoring(
db: &mut PatchDbHandle, db: &mut PatchDbHandle,
ids: Vec<PackageId>, ids: Vec<PackageId>,
backup_guard: &BackupMountGuard<TmpMountGuard>, backup_guard: &BackupMountGuard<TmpMountGuard>,
) -> Result< ) -> Result<Vec<(Manifest, PackageBackupMountGuard)>, Error> {
(
Option<Arc<Revision>>,
Vec<(Manifest, PackageBackupMountGuard)>,
),
Error,
> {
let mut tx = db.begin().await?; let mut tx = db.begin().await?;
let mut guards = Vec::with_capacity(ids.len()); let mut guards = Vec::with_capacity(ids.len());
@@ -414,7 +406,8 @@ async fn assure_restoring(
guards.push((manifest, guard)); guards.push((manifest, guard));
} }
Ok((tx.commit().await?, guards)) tx.commit().await?;
Ok(guards)
} }
#[instrument(skip(ctx, guard))] #[instrument(skip(ctx, guard))]

View File

@@ -134,7 +134,6 @@ pub fn target() -> Result<(), Error> {
Ok(()) Ok(())
} }
// TODO: incorporate reconnect into this response as well
#[command(display(display_serializable))] #[command(display(display_serializable))]
pub async fn list( pub async fn list(
#[context] ctx: RpcContext, #[context] ctx: RpcContext,
@@ -143,7 +142,6 @@ pub async fn list(
let (disks_res, cifs) = let (disks_res, cifs) =
tokio::try_join!(crate::disk::util::list(), cifs::list(&mut sql_handle),)?; tokio::try_join!(crate::disk::util::list(), cifs::list(&mut sql_handle),)?;
Ok(disks_res Ok(disks_res
.disks
.into_iter() .into_iter()
.flat_map(|mut disk| { .flat_map(|mut disk| {
std::mem::take(&mut disk.partitions) std::mem::take(&mut disk.partitions)

View File

@@ -77,7 +77,7 @@ async fn subscribe_to_session_kill(
async fn deal_with_messages( async fn deal_with_messages(
_has_valid_authentication: HasValidSession, _has_valid_authentication: HasValidSession,
mut kill: oneshot::Receiver<()>, mut kill: oneshot::Receiver<()>,
mut sub: patch_db::Subscriber, sub: patch_db::Subscriber,
mut stream: WebSocketStream<Upgraded>, mut stream: WebSocketStream<Upgraded>,
) -> Result<(), Error> { ) -> Result<(), Error> {
loop { loop {

View File

@@ -1,7 +1,7 @@
use clap::ArgMatches; use clap::ArgMatches;
use rpc_toolkit::command; use rpc_toolkit::command;
use self::util::DiskListResponse; use crate::disk::util::DiskInfo;
use crate::util::display_none; use crate::util::display_none;
use crate::util::serde::{display_serializable, IoFormat}; use crate::util::serde::{display_serializable, IoFormat};
use crate::Error; use crate::Error;
@@ -9,7 +9,6 @@ use crate::Error;
pub mod fsck; pub mod fsck;
pub mod main; pub mod main;
pub mod mount; pub mod mount;
pub mod quirks;
pub mod util; pub mod util;
pub const BOOT_RW_PATH: &str = "/media/boot-rw"; pub const BOOT_RW_PATH: &str = "/media/boot-rw";
@@ -20,7 +19,7 @@ pub fn disk() -> Result<(), Error> {
Ok(()) Ok(())
} }
fn display_disk_info(info: DiskListResponse, matches: &ArgMatches) { fn display_disk_info(info: Vec<DiskInfo>, matches: &ArgMatches) {
use prettytable::*; use prettytable::*;
if matches.is_present("format") { if matches.is_present("format") {
@@ -35,7 +34,7 @@ fn display_disk_info(info: DiskListResponse, matches: &ArgMatches) {
"USED", "USED",
"EMBASSY OS VERSION" "EMBASSY OS VERSION"
]); ]);
for disk in info.disks { for disk in info {
let row = row![ let row = row![
disk.logicalname.display(), disk.logicalname.display(),
"N/A", "N/A",
@@ -79,7 +78,7 @@ pub async fn list(
#[allow(unused_variables)] #[allow(unused_variables)]
#[arg] #[arg]
format: Option<IoFormat>, format: Option<IoFormat>,
) -> Result<DiskListResponse, Error> { ) -> Result<Vec<DiskInfo>, Error> {
crate::disk::util::list().await crate::disk::util::list().await
} }

View File

@@ -1,172 +0,0 @@
use std::collections::BTreeSet;
use std::num::ParseIntError;
use std::path::{Path, PathBuf};
use color_eyre::eyre::eyre;
use helpers::AtomicFile;
use tokio::io::AsyncWriteExt;
use tracing::instrument;
use super::BOOT_RW_PATH;
use crate::{Error, ErrorKind, ResultExt};
pub const QUIRK_PATH: &'static str = "/sys/module/usb_storage/parameters/quirks";
pub const WHITELIST: [(VendorId, ProductId); 5] = [
(VendorId(0x1d6b), ProductId(0x0002)), // root hub usb2
(VendorId(0x1d6b), ProductId(0x0003)), // root hub usb3
(VendorId(0x2109), ProductId(0x3431)),
(VendorId(0x1058), ProductId(0x262f)), // western digital black HDD
(VendorId(0x04e8), ProductId(0x4001)), // Samsung T7
];
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct VendorId(u16);
impl std::str::FromStr for VendorId {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
u16::from_str_radix(s.trim(), 16).map(VendorId)
}
}
impl std::fmt::Display for VendorId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:04x}", self.0)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ProductId(u16);
impl std::str::FromStr for ProductId {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
u16::from_str_radix(s.trim(), 16).map(ProductId)
}
}
impl std::fmt::Display for ProductId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:04x}", self.0)
}
}
#[derive(Clone, Debug)]
pub struct Quirks(BTreeSet<(VendorId, ProductId)>);
impl Quirks {
pub fn add(&mut self, vendor: VendorId, product: ProductId) {
self.0.insert((vendor, product));
}
pub fn remove(&mut self, vendor: VendorId, product: ProductId) {
self.0.remove(&(vendor, product));
}
pub fn contains(&self, vendor: VendorId, product: ProductId) -> bool {
self.0.contains(&(vendor, product))
}
}
impl std::fmt::Display for Quirks {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut comma = false;
for (vendor, product) in &self.0 {
if comma {
write!(f, ",")?;
} else {
comma = true;
}
write!(f, "{}:{}:u", vendor, product)?;
}
Ok(())
}
}
impl std::str::FromStr for Quirks {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let mut quirks = BTreeSet::new();
for item in s.split(",") {
if let [vendor, product, "u"] = item.splitn(3, ":").collect::<Vec<_>>().as_slice() {
quirks.insert((vendor.parse()?, product.parse()?));
} else {
return Err(Error::new(
eyre!("Invalid quirk: `{}`", item),
crate::ErrorKind::DiskManagement,
));
}
}
Ok(Quirks(quirks))
}
}
#[instrument]
pub async fn update_quirks(quirks: &mut Quirks) -> Result<Vec<String>, Error> {
let mut usb_devices = tokio::fs::read_dir("/sys/bus/usb/devices/").await?;
let mut to_reconnect = Vec::new();
while let Some(usb_device) = usb_devices.next_entry().await? {
if tokio::fs::metadata(usb_device.path().join("idVendor"))
.await
.is_err()
{
continue;
}
let vendor = tokio::fs::read_to_string(usb_device.path().join("idVendor"))
.await?
.parse()?;
let product = tokio::fs::read_to_string(usb_device.path().join("idProduct"))
.await?
.parse()?;
if WHITELIST.contains(&(vendor, product)) {
quirks.remove(vendor, product);
continue;
}
if quirks.contains(vendor, product) {
continue;
}
quirks.add(vendor, product);
{
// write quirks to sysfs
let mut quirk_file = tokio::fs::File::create(QUIRK_PATH).await?;
quirk_file.write_all(quirks.to_string().as_bytes()).await?;
quirk_file.sync_all().await?;
drop(quirk_file);
}
disconnect_usb(usb_device.path()).await?;
let (vendor_name, product_name) = tokio::try_join!(
tokio::fs::read_to_string(usb_device.path().join("manufacturer")),
tokio::fs::read_to_string(usb_device.path().join("product")),
)?;
to_reconnect.push(format!("{} {}", vendor_name, product_name));
}
Ok(to_reconnect)
}
#[instrument(skip(usb_device_path))]
pub async fn disconnect_usb(usb_device_path: impl AsRef<Path>) -> Result<(), Error> {
let authorized_path = usb_device_path.as_ref().join("bConfigurationValue");
let mut authorized_file = tokio::fs::File::create(&authorized_path).await?;
authorized_file.write_all(b"0").await?;
authorized_file.sync_all().await?;
drop(authorized_file);
Ok(())
}
#[instrument]
pub async fn fetch_quirks() -> Result<Quirks, Error> {
Ok(tokio::fs::read_to_string(QUIRK_PATH).await?.parse()?)
}
#[instrument]
pub async fn save_quirks(quirks: &Quirks) -> Result<(), Error> {
let orig_path = Path::new(BOOT_RW_PATH).join("cmdline.txt.orig");
let target_path = Path::new(BOOT_RW_PATH).join("cmdline.txt");
if tokio::fs::metadata(&orig_path).await.is_err() {
tokio::fs::copy(&target_path, &orig_path).await?;
}
let cmdline = tokio::fs::read_to_string(&orig_path).await?;
let mut target = AtomicFile::new(&target_path, None::<PathBuf>)
.await
.with_kind(ErrorKind::Filesystem)?;
target
.write_all(format!("usb-storage.quirks={} {}", quirks, cmdline).as_bytes())
.await?;
target.save().await.with_kind(ErrorKind::Filesystem)?;
Ok(())
}

View File

@@ -19,19 +19,11 @@ use tracing::instrument;
use super::mount::filesystem::block_dev::BlockDev; use super::mount::filesystem::block_dev::BlockDev;
use super::mount::filesystem::ReadOnly; use super::mount::filesystem::ReadOnly;
use super::mount::guard::TmpMountGuard; use super::mount::guard::TmpMountGuard;
use super::quirks::{fetch_quirks, save_quirks, update_quirks};
use crate::util::io::from_yaml_async_reader; use crate::util::io::from_yaml_async_reader;
use crate::util::serde::IoFormat; use crate::util::serde::IoFormat;
use crate::util::{Invoke, Version}; use crate::util::{Invoke, Version};
use crate::{Error, ResultExt as _}; use crate::{Error, ResultExt as _};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct DiskListResponse {
pub disks: Vec<DiskInfo>,
pub reconnect: Vec<String>,
}
#[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 {
@@ -240,10 +232,7 @@ pub async fn recovery_info(
} }
#[instrument] #[instrument]
pub async fn list() -> Result<DiskListResponse, Error> { pub async fn list() -> Result<Vec<DiskInfo>, Error> {
let mut quirks = fetch_quirks().await?;
let reconnect = update_quirks(&mut quirks).await?;
save_quirks(&mut quirks).await?;
let disk_guids = pvscan().await?; let disk_guids = pvscan().await?;
let disks = tokio_stream::wrappers::ReadDirStream::new( let disks = tokio_stream::wrappers::ReadDirStream::new(
tokio::fs::read_dir(DISK_PATH) tokio::fs::read_dir(DISK_PATH)
@@ -374,10 +363,7 @@ pub async fn list() -> Result<DiskListResponse, Error> {
}) })
} }
Ok(DiskListResponse { Ok(res)
disks: res,
reconnect,
})
} }
fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap<PathBuf, Option<String>> { fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap<PathBuf, Option<String>> {

View File

@@ -508,26 +508,26 @@ pub async fn follow_logs(
// println!("{}", serialized); // println!("{}", serialized);
// } // }
#[tokio::test] // #[tokio::test]
pub async fn test_logs() { // pub async fn test_logs() {
let mut cmd = Command::new("journalctl"); // let mut cmd = Command::new("journalctl");
cmd.kill_on_drop(true); // cmd.kill_on_drop(true);
cmd.arg("-f"); // cmd.arg("-f");
cmd.arg("CONTAINER_NAME=hello-world.embassy"); // cmd.arg("CONTAINER_NAME=hello-world.embassy");
let mut child = cmd.stdout(Stdio::piped()).spawn().unwrap(); // let mut child = cmd.stdout(Stdio::piped()).spawn().unwrap();
let out = BufReader::new( // let out = BufReader::new(
child // child
.stdout // .stdout
.take() // .take()
.ok_or_else(|| Error::new(eyre!("No stdout available"), crate::ErrorKind::Journald)) // .ok_or_else(|| Error::new(eyre!("No stdout available"), crate::ErrorKind::Journald))
.unwrap(), // .unwrap(),
); // );
let mut journalctl_entries = LinesStream::new(out.lines()); // let mut journalctl_entries = LinesStream::new(out.lines());
while let Some(line) = journalctl_entries.try_next().await.unwrap() { // while let Some(line) = journalctl_entries.try_next().await.unwrap() {
dbg!(line); // dbg!(line);
} // }
} // }

View File

@@ -94,7 +94,6 @@ impl MdnsControllerInner {
} }
self.sync(); self.sync();
} }
fn free(&self) {}
} }
fn log_str_error(action: &str, e: i32) { fn log_str_error(action: &str, e: i32) {

View File

@@ -36,7 +36,7 @@ use crate::disk::mount::filesystem::block_dev::BlockDev;
use crate::disk::mount::filesystem::cifs::Cifs; use crate::disk::mount::filesystem::cifs::Cifs;
use crate::disk::mount::filesystem::ReadOnly; use crate::disk::mount::filesystem::ReadOnly;
use crate::disk::mount::guard::TmpMountGuard; use crate::disk::mount::guard::TmpMountGuard;
use crate::disk::util::{pvscan, recovery_info, DiskListResponse, EmbassyOsRecoveryInfo}; use crate::disk::util::{pvscan, recovery_info, DiskInfo, EmbassyOsRecoveryInfo};
use crate::disk::REPAIR_DISK_PATH; use crate::disk::REPAIR_DISK_PATH;
use crate::hostname::{get_hostname, Hostname}; use crate::hostname::{get_hostname, Hostname};
use crate::id::Id; use crate::id::Id;
@@ -87,7 +87,7 @@ pub fn disk() -> Result<(), Error> {
} }
#[command(rename = "list", rpc_only, metadata(authenticated = false))] #[command(rename = "list", rpc_only, metadata(authenticated = false))]
pub async fn list_disks() -> Result<DiskListResponse, Error> { pub async fn list_disks() -> Result<Vec<DiskInfo>, Error> {
crate::disk::list(None).await crate::disk::list(None).await
} }

View File

@@ -15,8 +15,9 @@ mod v0_3_0_3;
mod v0_3_1; mod v0_3_1;
mod v0_3_1_1; mod v0_3_1_1;
mod v0_3_1_2; mod v0_3_1_2;
mod v0_3_2;
pub type Current = v0_3_1_2::Version; pub type Current = v0_3_2::Version;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(untagged)] #[serde(untagged)]
@@ -28,6 +29,7 @@ enum Version {
V0_3_1(Wrapper<v0_3_1::Version>), V0_3_1(Wrapper<v0_3_1::Version>),
V0_3_1_1(Wrapper<v0_3_1_1::Version>), V0_3_1_1(Wrapper<v0_3_1_1::Version>),
V0_3_1_2(Wrapper<v0_3_1_2::Version>), V0_3_1_2(Wrapper<v0_3_1_2::Version>),
V0_3_2(Wrapper<v0_3_2::Version>),
Other(emver::Version), Other(emver::Version),
} }
@@ -50,6 +52,7 @@ impl Version {
Version::V0_3_1(Wrapper(x)) => x.semver(), Version::V0_3_1(Wrapper(x)) => x.semver(),
Version::V0_3_1_1(Wrapper(x)) => x.semver(), Version::V0_3_1_1(Wrapper(x)) => x.semver(),
Version::V0_3_1_2(Wrapper(x)) => x.semver(), Version::V0_3_1_2(Wrapper(x)) => x.semver(),
Version::V0_3_2(Wrapper(x)) => x.semver(),
Version::Other(x) => x.clone(), Version::Other(x) => x.clone(),
} }
} }
@@ -183,6 +186,7 @@ pub async fn init<Db: DbHandle>(
Version::V0_3_1(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, Version::V0_3_1(v) => v.0.migrate_to(&Current::new(), db, receipts).await?,
Version::V0_3_1_1(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, Version::V0_3_1_1(v) => v.0.migrate_to(&Current::new(), db, receipts).await?,
Version::V0_3_1_2(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, Version::V0_3_1_2(v) => v.0.migrate_to(&Current::new(), db, receipts).await?,
Version::V0_3_2(v) => v.0.migrate_to(&Current::new(), db, receipts).await?,
Version::Other(_) => { Version::Other(_) => {
return Err(Error::new( return Err(Error::new(
eyre!("Cannot downgrade"), eyre!("Cannot downgrade"),
@@ -221,6 +225,8 @@ mod tests {
Just(Version::V0_3_0_3(Wrapper(v0_3_0_3::Version::new()))), Just(Version::V0_3_0_3(Wrapper(v0_3_0_3::Version::new()))),
Just(Version::V0_3_1(Wrapper(v0_3_1::Version::new()))), Just(Version::V0_3_1(Wrapper(v0_3_1::Version::new()))),
Just(Version::V0_3_1_1(Wrapper(v0_3_1_1::Version::new()))), Just(Version::V0_3_1_1(Wrapper(v0_3_1_1::Version::new()))),
Just(Version::V0_3_1_2(Wrapper(v0_3_1_2::Version::new()))),
Just(Version::V0_3_2(Wrapper(v0_3_2::Version::new()))),
em_version().prop_map(Version::Other), em_version().prop_map(Version::Other),
] ]
} }

View File

@@ -4,7 +4,6 @@ use emver::VersionRange;
use tokio::process::Command; use tokio::process::Command;
use super::*; use super::*;
use crate::disk::quirks::{fetch_quirks, save_quirks, update_quirks};
use crate::disk::BOOT_RW_PATH; use crate::disk::BOOT_RW_PATH;
use crate::update::query_mounted_label; use crate::update::query_mounted_label;
use crate::util::Invoke; use crate::util::Invoke;
@@ -36,9 +35,6 @@ impl VersionT for Version {
.arg(Path::new(BOOT_RW_PATH).join("cmdline.txt.orig")) .arg(Path::new(BOOT_RW_PATH).join("cmdline.txt.orig"))
.invoke(crate::ErrorKind::Filesystem) .invoke(crate::ErrorKind::Filesystem)
.await?; .await?;
let mut q = fetch_quirks().await?;
update_quirks(&mut q).await?;
save_quirks(&q).await?;
Ok(()) Ok(())
} }
async fn down<Db: DbHandle>(&self, _db: &mut Db) -> Result<(), Error> { async fn down<Db: DbHandle>(&self, _db: &mut Db) -> Result<(), Error> {

View File

@@ -1,7 +1,5 @@
use emver::VersionRange; use emver::VersionRange;
use crate::hostname::{generate_id, get_hostname, sync_hostname};
use super::v0_3_0::V0_3_0_COMPAT; use super::v0_3_0::V0_3_0_COMPAT;
use super::*; use super::*;
@@ -21,41 +19,10 @@ impl VersionT for Version {
fn compat(&self) -> &'static VersionRange { fn compat(&self) -> &'static VersionRange {
&*V0_3_0_COMPAT &*V0_3_0_COMPAT
} }
async fn up<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> { async fn up<Db: DbHandle>(&self, _db: &mut Db) -> Result<(), Error> {
let hostname = get_hostname(db).await?;
crate::db::DatabaseModel::new()
.server_info()
.hostname()
.put(db, &Some(hostname.0))
.await?;
crate::db::DatabaseModel::new()
.server_info()
.id()
.put(db, &generate_id())
.await?;
sync_hostname(db).await?;
let mut ui = crate::db::DatabaseModel::new()
.ui()
.get(db, false)
.await?
.clone();
if let serde_json::Value::Object(ref mut ui) = ui {
ui.insert("ack-instructions".to_string(), serde_json::json!({}));
}
crate::db::DatabaseModel::new().ui().put(db, &ui).await?;
Ok(()) Ok(())
} }
async fn down<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> { async fn down<Db: DbHandle>(&self, _db: &mut Db) -> Result<(), Error> {
let mut ui = crate::db::DatabaseModel::new()
.ui()
.get(db, false)
.await?
.clone();
if let serde_json::Value::Object(ref mut ui) = ui {
ui.remove("ack-instructions");
}
crate::db::DatabaseModel::new().ui().put(db, &ui).await?;
Ok(()) Ok(())
} }
} }

View File

@@ -0,0 +1,61 @@
use emver::VersionRange;
use crate::hostname::{generate_id, get_hostname, sync_hostname};
use super::v0_3_0::V0_3_0_COMPAT;
use super::*;
const V0_3_2: emver::Version = emver::Version::new(0, 3, 2, 0);
#[derive(Clone, Debug)]
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_3_1_2::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> emver::Version {
V0_3_2
}
fn compat(&self) -> &'static VersionRange {
&*V0_3_0_COMPAT
}
async fn up<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
let hostname = get_hostname(db).await?;
crate::db::DatabaseModel::new()
.server_info()
.hostname()
.put(db, &Some(hostname.0))
.await?;
crate::db::DatabaseModel::new()
.server_info()
.id()
.put(db, &generate_id())
.await?;
sync_hostname(db).await?;
let mut ui = crate::db::DatabaseModel::new()
.ui()
.get(db, false)
.await?
.clone();
if let serde_json::Value::Object(ref mut ui) = ui {
ui.insert("ack-instructions".to_string(), serde_json::json!({}));
}
crate::db::DatabaseModel::new().ui().put(db, &ui).await?;
Ok(())
}
async fn down<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
let mut ui = crate::db::DatabaseModel::new()
.ui()
.get(db, false)
.await?
.clone();
if let serde_json::Value::Object(ref mut ui) = ui {
ui.remove("ack-instructions");
}
crate::db::DatabaseModel::new().ui().put(db, &ui).await?;
Ok(())
}
}

View File

@@ -1,12 +1,12 @@
{ {
"name": "embassy-os", "name": "embassy-os",
"version": "0.3.1.1", "version": "0.3.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "embassy-os", "name": "embassy-os",
"version": "0.3.1.1", "version": "0.3.2",
"dependencies": { "dependencies": {
"@angular/animations": "^14.1.0", "@angular/animations": "^14.1.0",
"@angular/common": "^14.1.0", "@angular/common": "^14.1.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "embassy-os", "name": "embassy-os",
"version": "0.3.1.1", "version": "0.3.2",
"author": "Start9 Labs, Inc", "author": "Start9 Labs, Inc",
"homepage": "https://start9.com/", "homepage": "https://start9.com/",
"scripts": { "scripts": {

View File

@@ -49,7 +49,7 @@ export class EmbassyPage {
async getDrives() { async getDrives() {
this.loading = true this.loading = true
try { try {
const { disks, reconnect } = await this.apiService.getDrives() const disks = await this.apiService.getDrives()
this.storageDrives = disks.filter( this.storageDrives = disks.filter(
d => d =>
!d.partitions !d.partitions
@@ -59,15 +59,6 @@ export class EmbassyPage {
?.logicalname, ?.logicalname,
), ),
) )
if (!this.storageDrives.length && reconnect.length) {
const list = `<ul>${reconnect.map(recon => `<li>${recon}</li>`)}</ul>`
const alert = await this.alertCtrl.create({
header: 'Warning',
message: `One or more devices you connected had to be reconfigured to support the current hardware platform. Please unplug and replug the following device(s), then refresh the page:<br> ${list}`,
buttons: ['OK'],
})
await alert.present()
}
} catch (e: any) { } catch (e: any) {
this.errorToastService.present(e) this.errorToastService.present(e)
} finally { } finally {

View File

@@ -39,7 +39,7 @@ export class HomePage {
async ngOnInit() { async ngOnInit() {
try { try {
this.encrypted.secret = await this.unencrypted.getSecret() this.encrypted.secret = await this.unencrypted.getSecret()
const { disks } = await this.unencrypted.getDrives() const disks = await this.unencrypted.getDrives()
this.guid = disks.find(d => !!d.guid)?.guid this.guid = disks.find(d => !!d.guid)?.guid
} catch (e: any) { } catch (e: any) {
this.error = true this.error = true

View File

@@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { AlertController, ModalController, NavController } from '@ionic/angular' import { ModalController, NavController } from '@ionic/angular'
import { CifsModal } from 'src/app/modals/cifs-modal/cifs-modal.page' import { CifsModal } from 'src/app/modals/cifs-modal/cifs-modal.page'
import { ApiService, DiskBackupTarget } from 'src/app/services/api/api.service' import { ApiService, DiskBackupTarget } from 'src/app/services/api/api.service'
import { ErrorToastService } from '@start9labs/shared' import { ErrorToastService } from '@start9labs/shared'
@@ -20,7 +20,6 @@ export class RecoverPage {
private readonly navCtrl: NavController, private readonly navCtrl: NavController,
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,
private readonly modalController: ModalController, private readonly modalController: ModalController,
private readonly alertCtrl: AlertController,
private readonly errToastService: ErrorToastService, private readonly errToastService: ErrorToastService,
private readonly stateService: StateService, private readonly stateService: StateService,
) {} ) {}
@@ -41,7 +40,7 @@ export class RecoverPage {
async getDrives() { async getDrives() {
this.mappedDrives = [] this.mappedDrives = []
try { try {
const { disks, reconnect } = await this.apiService.getDrives() const disks = await this.apiService.getDrives()
disks disks
.filter(d => d.partitions.length) .filter(d => d.partitions.length)
.forEach(d => { .forEach(d => {
@@ -62,21 +61,6 @@ export class RecoverPage {
}) })
}) })
}) })
if (!this.mappedDrives.length && reconnect.length) {
const list = `<ul>${reconnect.map(recon => `<li>${recon}</li>`)}</ul>`
const alert = await this.alertCtrl.create({
header: 'Warning',
message: `One or more devices you connected had to be reconfigured to support the current hardware platform. Please unplug and replug the following device(s), then refresh the page:<br> ${list}`,
buttons: [
{
role: 'cancel',
text: 'OK',
},
],
})
await alert.present()
}
} catch (e: any) { } catch (e: any) {
this.errToastService.present(e) this.errToastService.present(e)
} finally { } finally {

View File

@@ -42,10 +42,7 @@ export type EmbassyOSRecoveryInfo = {
'wrapped-key': string | null 'wrapped-key': string | null
} }
export type DiskListResponse = { export type DiskListResponse = DiskInfo[]
disks: DiskInfo[]
reconnect: string[]
}
export type DiskBackupTarget = { export type DiskBackupTarget = {
vendor: string | null vendor: string | null

View File

@@ -41,7 +41,7 @@ export class LiveApiService implements ApiService {
/** /**
* We want to update the secret, which means that we will call in clearnet the * We want to update the secret, which means that we will call in clearnet the
* getSecret, and all the information is never in the clear, and only public * getSecret, and all the information is never in the clear, and only public
* information is sent across the network. We don't want to expose that we do * information is sent across the network. We don't want to expose that we do
* this wil all public/private key, which means that there is no information loss * this wil all public/private key, which means that there is no information loss
* through the network. * through the network.
*/ */

View File

@@ -37,32 +37,29 @@ export class MockApiService implements ApiService {
async getDrives() { async getDrives() {
await pauseFor(1000) await pauseFor(1000)
return { return [
disks: [ {
{ logicalname: 'abcd',
logicalname: 'abcd', vendor: 'Samsung',
vendor: 'Samsung', model: 'T5',
model: 'T5', partitions: [
partitions: [ {
{ logicalname: 'pabcd',
logicalname: 'pabcd', label: null,
label: null, capacity: 73264762332,
capacity: 73264762332, used: null,
used: null, 'embassy-os': {
'embassy-os': { version: '0.2.17',
version: '0.2.17', full: true,
full: true, 'password-hash': null,
'password-hash': null, 'wrapped-key': null,
'wrapped-key': null,
},
}, },
], },
capacity: 123456789123, ],
guid: 'uuid-uuid-uuid-uuid', capacity: 123456789123,
}, guid: 'uuid-uuid-uuid-uuid',
], },
reconnect: [], ]
}
} }
async set02XDrive() { async set02XDrive() {

View File

@@ -3,9 +3,8 @@
</a> </a>
<div class="divider"></div> <div class="divider"></div>
<ion-item-group class="menu"> <ion-item-group class="menu">
<ion-menu-toggle *ngFor="let page of pages; let i = index" auto-hide="false"> <ion-menu-toggle *ngFor="let page of pages" auto-hide="false">
<ion-item <ion-item
*ngIf="page.url !== '/developer' || (showDevTools$ | async)"
button button
class="link" class="link"
color="transparent" color="transparent"

View File

@@ -1,5 +1,4 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
import { LocalStorageService } from '../../services/local-storage.service'
import { EOSService } from '../../services/eos.service' import { EOSService } from '../../services/eos.service'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
@@ -37,11 +36,6 @@ export class MenuComponent {
url: '/notifications', url: '/notifications',
icon: 'notifications-outline', icon: 'notifications-outline',
}, },
{
title: 'Developer Tools',
url: '/developer',
icon: 'hammer-outline',
},
] ]
readonly notificationCount$ = this.patch.watch$( readonly notificationCount$ = this.patch.watch$(
@@ -53,8 +47,6 @@ export class MenuComponent {
readonly showEOSUpdate$ = this.eosService.showUpdate$ readonly showEOSUpdate$ = this.eosService.showUpdate$
readonly showDevTools$ = this.localStorageService.showDevTools$
readonly updateCount$: Observable<number> = this.marketplaceService readonly updateCount$: Observable<number> = this.marketplaceService
.getUpdates() .getUpdates()
.pipe(map(pkgs => pkgs.length)) .pipe(map(pkgs => pkgs.length))
@@ -63,7 +55,6 @@ export class MenuComponent {
constructor( constructor(
private readonly patch: PatchDB<DataModel>, private readonly patch: PatchDB<DataModel>,
private readonly localStorageService: LocalStorageService,
private readonly eosService: EOSService, private readonly eosService: EOSService,
@Inject(AbstractMarketplaceService) @Inject(AbstractMarketplaceService)
private readonly marketplaceService: MarketplaceService, private readonly marketplaceService: MarketplaceService,

View File

@@ -11,11 +11,11 @@
<ion-content class="ion-padding"> <ion-content class="ion-padding">
<h2>This release</h2> <h2>This release</h2>
<h4>0.3.1~1</h4> <h4>0.3.2</h4>
<p class="note-padding"> <p class="note-padding">
View the complete View the complete
<a <a
href="https://github.com/Start9Labs/embassy-os/releases/tag/v0.3.1.1" href="https://github.com/Start9Labs/embassy-os/releases/tag/v0.3.2"
target="_blank" target="_blank"
noreferrer noreferrer
>release notes</a >release notes</a
@@ -24,38 +24,12 @@
</p> </p>
<h6>Highlights</h6> <h6>Highlights</h6>
<ul class="spaced-list"> <ul class="spaced-list">
<li>Multiple bug fixes.</li> <li>Autoscrolling for logs</li>
</ul> <li>Improved connectivity between browser and Embassy</li>
<li>Switch to Postgres for EOS database for better performance</li>
<br /> <li>Multiple bug fixes and under-the-hood improvements</li>
<h2>Previous releases in this series</h2> <li>Various UI/UX enhancements</li>
<h4>0.3.1</h4> <li>Removal of product keys</li>
<p class="note-padding">
View the complete
<a
href="https://github.com/Start9Labs/embassy-os/releases/tag/v0.3.1"
target="_blank"
noreferrer
>release notes</a
>
for more details.
</p>
<h6>Highlights</h6>
<ul class="spaced-list">
<li>
Drag and drop installs. Install a service simply by dragging it into the
browser
</li>
<li>
Password reset flow. Requires physical access to the device for security
purposes
</li>
<li>Selective backups. Only back up the services you choose</li>
<li>New button to restart a service</li>
<li>New button to log out all sessions</li>
<li>New button to copy/paste service and OS logs</li>
<li>Significant speedup for service configuration and property viewing</li>
<li>Multiple bugfixes and performance improvements</li>
</ul> </ul>
<div class="ion-text-center ion-padding"> <div class="ion-text-center ion-padding">
<ion-button <ion-button

View File

@@ -538,7 +538,7 @@ export class ServerShowPage {
this.clicks++ this.clicks++
if (this.clicks >= 5) { if (this.clicks >= 5) {
this.clicks = 0 this.clicks = 0
const newVal = await this.localStorageService.toggleShowDiskRepair() await this.localStorageService.toggleShowDiskRepair()
} }
setTimeout(() => { setTimeout(() => {
this.clicks = Math.max(this.clicks - 1, 0) this.clicks = Math.max(this.clicks - 1, 0)

View File

@@ -338,7 +338,6 @@ export module Mock {
}, },
}, },
}, },
permissions: {},
dependencies: {}, dependencies: {},
} }
@@ -472,7 +471,6 @@ export module Mock {
'input-spec': null, 'input-spec': null,
}, },
}, },
permissions: {},
dependencies: { dependencies: {
bitcoind: { bitcoind: {
version: '=0.21.0', version: '=0.21.0',
@@ -587,7 +585,6 @@ export module Mock {
}, },
migrations: null, migrations: null,
actions: {}, actions: {},
permissions: {},
dependencies: { dependencies: {
bitcoind: { bitcoind: {
version: '>=0.20.0', version: '>=0.20.0',

View File

@@ -27,7 +27,7 @@ export const mockPatchData: DataModel = {
}, },
'server-info': { 'server-info': {
id: 'abcdefgh', id: 'abcdefgh',
version: '0.3.1.1', version: '0.3.2',
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(), 'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
'lan-address': 'https://embassy-abcdefgh.local', 'lan-address': 'https://embassy-abcdefgh.local',
'tor-address': 'http://myveryownspecialtoraddress.onion', 'tor-address': 'http://myveryownspecialtoraddress.onion',
@@ -381,7 +381,6 @@ export const mockPatchData: DataModel = {
}, },
}, },
}, },
permissions: {},
dependencies: {}, dependencies: {},
}, },
installed: { installed: {
@@ -578,7 +577,6 @@ export const mockPatchData: DataModel = {
'input-spec': null, 'input-spec': null,
}, },
}, },
permissions: {},
dependencies: { dependencies: {
bitcoind: { bitcoind: {
version: '=0.21.0', version: '=0.21.0',

View File

@@ -147,7 +147,6 @@ export interface Manifest extends MarketplaceManifest<DependencyConfig | null> {
backup: BackupActions backup: BackupActions
migrations: Migrations | null migrations: Migrations | null
actions: Record<string, Action> actions: Record<string, Action>
permissions: any // @TODO 0.3.1
} }
export interface DependencyConfig { export interface DependencyConfig {