From 60a48d7af2daa5cdb50d69245ff13770cd7a1736 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Tue, 14 Dec 2021 17:09:00 -0700 Subject: [PATCH] allow mdns cifs shares (#933) * allow mdns cifs shares * regex and messaging for cifs * Update install-wizard.component.ts * refactor * always include leading slash when saving path * add avahi-utils Co-authored-by: Matt Hill Co-authored-by: Matt Hill --- appmgr/src/backup/target/cifs.rs | 6 ++--- appmgr/src/disk/mount/filesystem/cifs.rs | 27 ++++++++++++------- appmgr/src/net/mdns.rs | 24 +++++++++++++++++ build/initialization.sh | 1 + .../modals/cifs-modal/cifs-modal.page.html | 5 ++-- .../app/modals/cifs-modal/cifs-modal.page.ts | 5 ---- .../src/app/pages/recover/recover.page.html | 4 +-- .../backup-drives.component.html | 4 +-- .../backup-drives/backup-drives.component.ts | 8 +++--- .../install-wizard/prebaked-wizards.ts | 3 ++- .../components/status/status.component.html | 8 +++--- .../marketplace-list.page.html | 11 +++++--- .../marketplace-show.page.html | 11 +++++--- .../server-routes/ssh-keys/ssh-keys.page.ts | 3 --- ui/src/app/util/package-loading-progress.ts | 2 -- ui/src/global.scss | 25 +++++++++++++++++ 16 files changed, 104 insertions(+), 43 deletions(-) diff --git a/appmgr/src/backup/target/cifs.rs b/appmgr/src/backup/target/cifs.rs index d94ec6071..04dd25059 100644 --- a/appmgr/src/backup/target/cifs.rs +++ b/appmgr/src/backup/target/cifs.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use color_eyre::eyre::eyre; use futures::TryStreamExt; @@ -47,7 +47,7 @@ pub async fn add( let guard = TmpMountGuard::mount(&cifs).await?; let embassy_os = recovery_info(&guard).await?; guard.unmount().await?; - let path_string = cifs.path.display().to_string(); + let path_string = Path::new("/").join(&cifs.path).display().to_string(); let id: u32 = sqlx::query!( "INSERT INTO cifs_shares (hostname, path, username, password) VALUES (?, ?, ?, ?) RETURNING id AS \"id: u32\"", cifs.hostname, @@ -95,7 +95,7 @@ pub async fn update( let guard = TmpMountGuard::mount(&cifs).await?; let embassy_os = recovery_info(&guard).await?; guard.unmount().await?; - let path_string = cifs.path.display().to_string(); + let path_string = Path::new("/").join(&cifs.path).display().to_string(); if sqlx::query!( "UPDATE cifs_shares SET hostname = ?, path = ?, username = ?, password = ? WHERE id = ?", cifs.hostname, diff --git a/appmgr/src/disk/mount/filesystem/cifs.rs b/appmgr/src/disk/mount/filesystem/cifs.rs index c7004617b..d1929f5fa 100644 --- a/appmgr/src/disk/mount/filesystem/cifs.rs +++ b/appmgr/src/disk/mount/filesystem/cifs.rs @@ -3,6 +3,7 @@ use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use async_trait::async_trait; +use color_eyre::eyre::eyre; use digest::generic_array::GenericArray; use digest::Digest; use serde::{Deserialize, Serialize}; @@ -12,6 +13,7 @@ use tracing::instrument; use super::FileSystem; use crate::disk::mount::guard::TmpMountGuard; +use crate::net::mdns::resolve_mdns; use crate::util::Invoke; use crate::Error; @@ -24,16 +26,21 @@ pub async fn mount_cifs( mountpoint: impl AsRef, ) -> Result<(), Error> { tokio::fs::create_dir_all(mountpoint.as_ref()).await?; - let ip: IpAddr = String::from_utf8( - Command::new("nmblookup") - .arg(hostname) - .invoke(crate::ErrorKind::Network) - .await?, - )? - .split(" ") - .next() - .unwrap() - .parse()?; + let ip: IpAddr = if hostname.ends_with(".local") { + resolve_mdns(hostname).await? + } else { + String::from_utf8( + Command::new("nmblookup") + .arg(hostname) + .invoke(crate::ErrorKind::Network) + .await?, + )? + .split(" ") + .next() + .unwrap() + .trim() + .parse()? + }; let absolute_path = Path::new("/").join(path.as_ref()); Command::new("mount") .arg("-t") diff --git a/appmgr/src/net/mdns.rs b/appmgr/src/net/mdns.rs index 0f52d8add..8f60055f8 100644 --- a/appmgr/src/net/mdns.rs +++ b/appmgr/src/net/mdns.rs @@ -1,16 +1,40 @@ use std::collections::BTreeMap; +use std::net::IpAddr; use avahi_sys::{ self, avahi_client_errno, avahi_entry_group_add_service, avahi_entry_group_commit, avahi_entry_group_free, avahi_entry_group_reset, avahi_free, avahi_strerror, AvahiClient, AvahiEntryGroup, }; +use color_eyre::eyre::eyre; use libc::c_void; +use tokio::process::Command; use tokio::sync::Mutex; use torut::onion::TorSecretKeyV3; use super::interface::InterfaceId; use crate::s9pk::manifest::PackageId; +use crate::util::Invoke; +use crate::Error; + +pub async fn resolve_mdns(hostname: &str) -> Result { + Ok(String::from_utf8( + Command::new("avahi-resolve-host-name") + .arg(hostname) + .invoke(crate::ErrorKind::Network) + .await?, + )? + .split_once("\t") + .ok_or_else(|| { + Error::new( + eyre!("Failed to resolve hostname: {}", hostname), + crate::ErrorKind::Network, + ) + })? + .1 + .trim() + .parse()?) +} pub struct MdnsController(Mutex); impl MdnsController { diff --git a/build/initialization.sh b/build/initialization.sh index aa5cfd16e..0f75ac9d2 100755 --- a/build/initialization.sh +++ b/build/initialization.sh @@ -15,6 +15,7 @@ apt-get install -y \ nginx \ libavahi-client3 \ avahi-daemon \ + avahi-utils \ iotop \ bmon \ exfat-utils \ diff --git a/setup-wizard/src/app/modals/cifs-modal/cifs-modal.page.html b/setup-wizard/src/app/modals/cifs-modal/cifs-modal.page.html index 7b1f37860..6f9728ac2 100644 --- a/setup-wizard/src/app/modals/cifs-modal/cifs-modal.page.html +++ b/setup-wizard/src/app/modals/cifs-modal/cifs-modal.page.html @@ -16,11 +16,12 @@ [(ngModel)]="cifs.hostname" name="hostname" #hostname="ngModel" - placeholder="e.g. My Computer, Bob's Laptop" + placeholder="e.g. 'My Computer' OR 'my-computer.local'" + pattern="^[a-zA-Z0-9._-]+( [a-zA-Z0-9]+)*$" >

- Hostname is required + Hostname is required. e.g. 'My Computer' OR 'my-computer.local'

Path *

diff --git a/setup-wizard/src/app/modals/cifs-modal/cifs-modal.page.ts b/setup-wizard/src/app/modals/cifs-modal/cifs-modal.page.ts index 44ca3dde5..b8bc93731 100644 --- a/setup-wizard/src/app/modals/cifs-modal/cifs-modal.page.ts +++ b/setup-wizard/src/app/modals/cifs-modal/cifs-modal.page.ts @@ -77,8 +77,3 @@ export class CifsModal { alert.present() } } - -interface MappedCifs { - hasValidBackup: boolean - cifs: CifsBackupTarget -} diff --git a/setup-wizard/src/app/pages/recover/recover.page.html b/setup-wizard/src/app/pages/recover/recover.page.html index adca7a62b..d7b5680fd 100644 --- a/setup-wizard/src/app/pages/recover/recover.page.html +++ b/setup-wizard/src/app/pages/recover/recover.page.html @@ -24,7 +24,7 @@

Using a shared folder is the recommended way to recover from backup, since it works with all Embassy hardware configurations. - To restore from a shared folder, please follow the instructions. + To restore from a shared folder, please follow the instructions.

@@ -42,7 +42,7 @@

Warning! Plugging in more than one physical drive to Embassy can lead to power failure and data corruption. - To restore from a physical drive, please follow the instructions. + To restore from a physical drive, please follow the instructions.

diff --git a/ui/src/app/components/backup-drives/backup-drives.component.html b/ui/src/app/components/backup-drives/backup-drives.component.html index 23a23d43d..0247510d2 100644 --- a/ui/src/app/components/backup-drives/backup-drives.component.html +++ b/ui/src/app/components/backup-drives/backup-drives.component.html @@ -19,7 +19,7 @@

- Shared folders are the recommended way to create Embassy backups. + Shared folders are the recommended way to create Embassy backups. View the Instructions

@@ -59,7 +59,7 @@

- To backup to a physical drive, please follow the instructions. + To backup to a physical drive, please follow the instructions.

diff --git a/ui/src/app/components/backup-drives/backup-drives.component.ts b/ui/src/app/components/backup-drives/backup-drives.component.ts index fa98a73a8..09e1a9d41 100644 --- a/ui/src/app/components/backup-drives/backup-drives.component.ts +++ b/ui/src/app/components/backup-drives/backup-drives.component.ts @@ -238,8 +238,10 @@ const CifsSpec: ConfigSpec = { hostname: { type: 'string', name: 'Hostname', - description: 'The local URL of the shared folder.', - placeholder: `e.g. My Computer, Bob's Laptop`, + description: 'The hostname of your target device on the Local Area Network.', + placeholder: `e.g. 'My Computer' OR 'my-computer.local'`, + pattern: '^[a-zA-Z0-9._-]+( [a-zA-Z0-9]+)*$', + 'pattern-description': `Must be a valid hostname. e.g. 'My Computer' OR 'my-computer.local'`, nullable: false, masked: false, copyable: false, @@ -247,7 +249,7 @@ const CifsSpec: ConfigSpec = { path: { type: 'string', name: 'Path', - description: 'The path to the shared folder on the target device.', + description: 'The directory path to the shared folder on your target device.', placeholder: 'e.g. /Desktop/my-folder', nullable: false, masked: false, diff --git a/ui/src/app/components/install-wizard/prebaked-wizards.ts b/ui/src/app/components/install-wizard/prebaked-wizards.ts index b6fe4faec..7c0c5fd22 100644 --- a/ui/src/app/components/install-wizard/prebaked-wizards.ts +++ b/ui/src/app/components/install-wizard/prebaked-wizards.ts @@ -277,7 +277,8 @@ export class WizardBaker { whileLoading: { }, afterLoading: { text: 'Cancel' }, }, - next: 'Stop Service' }, + next: 'Stop Service', + }, }, { slide: { diff --git a/ui/src/app/components/status/status.component.html b/ui/src/app/components/status/status.component.html index f1e4a8d6c..41262c8f7 100644 --- a/ui/src/app/components/status/status.component.html +++ b/ui/src/app/components/status/status.component.html @@ -6,14 +6,16 @@ > {{ disconnected ? 'Unknown' : rendering.display }} - ... + - Installing... {{ installProgress }}% + Installing + {{ installProgress }}% - Finalizing install. This could take a minute... + Finalizing install. This could take a minute + diff --git a/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html b/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html index 0d5d1a4bd..f6f5ec387 100644 --- a/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html +++ b/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html @@ -109,13 +109,16 @@ Update Available

- - {{ (localPkg['install-progress'] | installState).display }} + + Installing + {{ progress.totalProgress < 99 ? progress.totalProgress + '%' : 'finalizing' }}

- {{ localPkg.state | titlecase }} - + + Removing + +

diff --git a/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html b/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html index 9aeb5d347..019633289 100644 --- a/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html +++ b/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html @@ -32,12 +32,17 @@

- {{ (localPkg['install-progress'] | installState).display }} + + Installing + {{ progress.totalProgress < 99 ? progress.totalProgress + '%' : 'finalizing' }} +

- {{ localPkg.state | titlecase }} - + + Removing + +

diff --git a/ui/src/app/pages/server-routes/ssh-keys/ssh-keys.page.ts b/ui/src/app/pages/server-routes/ssh-keys/ssh-keys.page.ts index e79b8560c..f91dcbbcf 100644 --- a/ui/src/app/pages/server-routes/ssh-keys/ssh-keys.page.ts +++ b/ui/src/app/pages/server-routes/ssh-keys/ssh-keys.page.ts @@ -119,9 +119,6 @@ const sshSpec = { name: 'SSH Key', description: 'Enter the SSH public key of you would like to authorize for root access to your Embassy.', nullable: false, - // @TODO regex for SSH Key - // pattern: '', - 'pattern-description': 'Must be a valid SSH key', masked: false, copyable: false, } diff --git a/ui/src/app/util/package-loading-progress.ts b/ui/src/app/util/package-loading-progress.ts index 2a7d08133..58d97b553 100644 --- a/ui/src/app/util/package-loading-progress.ts +++ b/ui/src/app/util/package-loading-progress.ts @@ -40,7 +40,6 @@ export function packageLoadingProgress ( validateProgress: Math.floor((100 * validated) / size), unpackProgress: Math.floor((100 * unpacked) / size), isComplete: downloadComplete && validationComplete && unpackComplete, - display: totalProgress > 98 ? 'Finalizing...' : `Installing... ${totalProgress}%`, } } @@ -50,5 +49,4 @@ export interface ProgressData { validateProgress: number unpackProgress: number isComplete: boolean - display: string } diff --git a/ui/src/global.scss b/ui/src/global.scss index 113785f3f..fd2bdbe61 100644 --- a/ui/src/global.scss +++ b/ui/src/global.scss @@ -311,3 +311,28 @@ ion-loading { h2 { line-height: unset; } + +.loading-dots:after { + content: "..."; + overflow: hidden; + display: inline-block; + vertical-align: bottom; + animation: ellipsis-dot 1s infinite .3s; + animation-fill-mode: forwards; + width: 1em; +} + +@keyframes ellipsis-dot { + 25% { + content: ""; + } + 50% { + content: "."; + } + 75% { + content: ".."; + } + 100% { + content: "..."; + } +}