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 }}%
= 99">
- 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: "...";
+ }
+}