mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
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:
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -15,6 +15,7 @@ apt-get install -y \
|
||||
nginx \
|
||||
libavahi-client3 \
|
||||
avahi-daemon \
|
||||
avahi-utils \
|
||||
iotop \
|
||||
bmon \
|
||||
exfat-utils \
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -77,8 +77,3 @@ export class CifsModal {
|
||||
alert.present()
|
||||
}
|
||||
}
|
||||
|
||||
interface MappedCifs {
|
||||
hasValidBackup: boolean
|
||||
cifs: CifsBackupTarget
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -277,7 +277,8 @@ export class WizardBaker {
|
||||
whileLoading: { },
|
||||
afterLoading: { text: 'Cancel' },
|
||||
},
|
||||
next: 'Stop Service' },
|
||||
next: 'Stop Service',
|
||||
},
|
||||
},
|
||||
{
|
||||
slide: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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: "...";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user