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 <matthewonthemoon@gmail.com>
Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
This commit is contained in:
Aiden McClelland
2021-12-14 17:09:00 -07:00
parent 8869361eec
commit 60a48d7af2
16 changed files with 104 additions and 43 deletions

View File

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

View File

@@ -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<Path>,
) -> 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")

View File

@@ -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<IpAddr, Error> {
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<MdnsControllerInner>);
impl MdnsController {

View File

@@ -15,6 +15,7 @@ apt-get install -y \
nginx \
libavahi-client3 \
avahi-daemon \
avahi-utils \
iotop \
bmon \
exfat-utils \

View File

@@ -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]+)*$"
></ion-input>
</ion-item>
<p [hidden]="hostname.valid || hostname.pristine">
<ion-text color="danger">Hostname is required</ion-text>
<ion-text color="danger">Hostname is required. e.g. 'My Computer' OR 'my-computer.local'</ion-text>
</p>
<p>Path *</p>

View File

@@ -77,8 +77,3 @@ export class CifsModal {
alert.present()
}
}
interface MappedCifs {
hasValidBackup: boolean
cifs: CifsBackupTarget
}

View File

@@ -24,7 +24,7 @@
</h2>
<p class="ion-padding-bottom">
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 <a href="https://docs.start9.com/user-manual/general/backups.html" target="blank" noreferrer>instructions</a>.
To restore from a shared folder, please follow the <a href="https://docs.start9.com/user-manual/general/backups.html#shared-network-folder" target="_blank" noreferrer>instructions</a>.
</p>
<!-- connect -->
@@ -42,7 +42,7 @@
</h2>
<p class="ion-padding-bottom">
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 <a href="https://docs.start9.com/user-manual/general/backups.html" target="blank" noreferrer>instructions</a>.
To restore from a physical drive, please follow the <a href="https://docs.start9.com/user-manual/general/backups.html#physical-drive" target="_blank" noreferrer>instructions</a>.
</p>
<ng-container *ngFor="let mapped of mappedDrives">

View File

@@ -19,7 +19,7 @@
<ion-item>
<ion-label>
<h2>
Shared folders are the recommended way to create Embassy backups.
Shared folders are the recommended way to create Embassy backups. View the <a href="https://docs.start9.com/user-manual/general/backups.html#shared-network-folder" target="_blank" noreferrer>Instructions</a>
</h2>
</ion-label>
</ion-item>
@@ -59,7 +59,7 @@
</h2>
<br />
<h2>
To backup to a physical drive, please follow the <a href="https://docs.start9.com/user-manual/general/backups.html" target="blank" noreferrer>instructions</a>.
To backup to a physical drive, please follow the <a href="https://docs.start9.com/user-manual/general/backups.html#physical-drive" target="_blank" noreferrer>instructions</a>.
</h2>
</ion-label>
</ion-item>

View File

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

View File

@@ -277,7 +277,8 @@ export class WizardBaker {
whileLoading: { },
afterLoading: { text: 'Cancel' },
},
next: 'Stop Service' },
next: 'Stop Service',
},
},
{
slide: {

View File

@@ -6,14 +6,16 @@
>
<span *ngIf= "!installProgress">
{{ disconnected ? 'Unknown' : rendering.display }}
<span *ngIf="rendering.showDots">...</span>
<span *ngIf="rendering.showDots" class="loading-dots"></span>
</span>
<span *ngIf="installProgress">
<span *ngIf="installProgress < 99">
Installing... {{ installProgress }}%
Installing
<span class="loading-dots"></span>{{ installProgress }}%
</span>
<span *ngIf="installProgress >= 99">
Finalizing install. This could take a minute...
Finalizing install. This could take a minute
<span class="loading-dots"></span>
</span>
</span>

View File

@@ -109,13 +109,16 @@
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1" color="warning">Update Available</ion-text>
</p>
<p *ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state">
<ion-text color="primary">
<span>{{ (localPkg['install-progress'] | installState).display }}</span>
<ion-text color="primary" *ngIf="(localPkg['install-progress'] | installState) as progress">
Installing
<span class="loading-dots"></span>{{ progress.totalProgress < 99 ? progress.totalProgress + '%' : 'finalizing' }}
</ion-text>
</p>
<p *ngIf="localPkg.state === PackageState.Removing">
<ion-text color="warning">{{ localPkg.state | titlecase }}</ion-text>
<ion-spinner name="dots" color="warning"></ion-spinner>
<ion-text color="danger">
Removing
<span class="loading-dots"></span>
</ion-text>
</p>
</ng-container>
<ng-template #none>

View File

@@ -32,12 +32,17 @@
</p>
<!-- installing, updating -->
<p *ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state">
<ion-text color="primary">{{ (localPkg['install-progress'] | installState).display }}</ion-text>
<ion-text color="primary" *ngIf="(localPkg['install-progress'] | installState) as progress">
Installing
<span class="loading-dots"></span>{{ progress.totalProgress < 99 ? progress.totalProgress + '%' : 'finalizing' }}
</ion-text>
</p>
<!-- removing -->
<p *ngIf="localPkg.state === PackageState.Removing">
<ion-text color="warning">{{ localPkg.state | titlecase }}</ion-text>
<ion-spinner name="dots" color="warning"></ion-spinner>
<ion-text color="danger">
Removing
<span class="loading-dots"></span>
</ion-text>
</p>
</ng-template>
</div>

View File

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

View File

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

View File

@@ -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: "...";
}
}