mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
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:
6
.github/workflows/backend.yaml
vendored
6
.github/workflows/backend.yaml
vendored
@@ -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
2
backend/Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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))]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -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>> {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
backend/src/version/v0_3_2.rs
Normal file
61
backend/src/version/v0_3_2.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user