mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
refactor setup wizard (#1937)
* refactor setup backend * rework setup wizard according to new scheme * fix bug with partitions in SW and warning message in IW * treat localhost as LAN for launching services * misc backend fixes Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
@@ -47,27 +47,24 @@ export class HomePage {
|
||||
}
|
||||
|
||||
async tryInstall(overwrite: boolean) {
|
||||
if (!this.selectedDisk) return
|
||||
|
||||
const { logicalname, guid } = this.selectedDisk
|
||||
|
||||
const hasEmbassyData = !!guid
|
||||
|
||||
if (hasEmbassyData && !overwrite) {
|
||||
return this.install(logicalname, overwrite)
|
||||
if (overwrite) {
|
||||
return this.presentAlertDanger()
|
||||
}
|
||||
|
||||
await this.presentAlertDanger(logicalname, hasEmbassyData)
|
||||
this.install(false)
|
||||
}
|
||||
|
||||
private async install(logicalname: string, overwrite: boolean) {
|
||||
private async install(overwrite: boolean) {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Installing embassyOS...',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.api.install({ logicalname, overwrite })
|
||||
await this.api.install({
|
||||
logicalname: this.selectedDisk!.logicalname,
|
||||
overwrite,
|
||||
})
|
||||
this.presentAlertReboot()
|
||||
} catch (e: any) {
|
||||
this.error = e.message
|
||||
@@ -76,17 +73,14 @@ export class HomePage {
|
||||
}
|
||||
}
|
||||
|
||||
private async presentAlertDanger(
|
||||
logicalname: string,
|
||||
hasEmbassyData: boolean,
|
||||
) {
|
||||
const message = hasEmbassyData
|
||||
? 'This action COMPLETELY erases your existing Embassy data'
|
||||
: `This action COMPLETELY erases the disk ${logicalname} and installs embassyOS`
|
||||
private async presentAlertDanger() {
|
||||
const { vendor, model } = this.selectedDisk!
|
||||
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Warning',
|
||||
message,
|
||||
message: `This action will COMPLETELY erase the disk ${
|
||||
vendor || 'Unknown Vendor'
|
||||
} - ${model || 'Unknown Model'} and install embassyOS in its place`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
@@ -95,7 +89,7 @@ export class HomePage {
|
||||
{
|
||||
text: 'Continue',
|
||||
handler: () => {
|
||||
this.install(logicalname, true)
|
||||
this.install(true)
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -17,8 +17,14 @@ export class AppComponent {
|
||||
|
||||
async ngOnInit() {
|
||||
try {
|
||||
const { migrating } = await this.apiService.getStatus()
|
||||
await this.navCtrl.navigateForward(migrating ? '/loading' : '/home')
|
||||
const inProgress = await this.apiService.getStatus()
|
||||
|
||||
let route = '/home'
|
||||
if (inProgress) {
|
||||
route = inProgress.complete ? '/success' : '/loading'
|
||||
}
|
||||
|
||||
await this.navCtrl.navigateForward(route)
|
||||
} catch (e: any) {
|
||||
this.errorToastService.present(e)
|
||||
}
|
||||
|
||||
@@ -38,8 +38,7 @@ export class AttachPage {
|
||||
|
||||
async getDrives() {
|
||||
try {
|
||||
const drives = await this.apiService.getDrives()
|
||||
this.drives = drives.filter(d => d.partitions.length)
|
||||
this.drives = await this.apiService.getDrives()
|
||||
} catch (e: any) {
|
||||
this.errToastService.present(e)
|
||||
} finally {
|
||||
@@ -61,13 +60,11 @@ export class AttachPage {
|
||||
}
|
||||
|
||||
private async attachDrive(guid: string, password: string) {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Attaching Drive',
|
||||
})
|
||||
const loader = await this.loadingCtrl.create()
|
||||
await loader.present()
|
||||
try {
|
||||
await this.stateService.importDrive(guid, password)
|
||||
await this.navCtrl.navigateForward(`/success`)
|
||||
await this.navCtrl.navigateForward(`/loading`)
|
||||
} catch (e: any) {
|
||||
this.errToastService.present(e)
|
||||
} finally {
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
import { DiskInfo, ErrorToastService, GuidPipe } from '@start9labs/shared'
|
||||
import { StateService } from 'src/app/services/state.service'
|
||||
import { PasswordPage } from '../../modals/password/password.page'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
|
||||
@Component({
|
||||
selector: 'app-embassy',
|
||||
@@ -34,7 +33,6 @@ export class EmbassyPage {
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errorToastService: ErrorToastService,
|
||||
private readonly guidPipe: GuidPipe,
|
||||
private route: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -133,21 +131,12 @@ export class EmbassyPage {
|
||||
logicalname: string,
|
||||
password: string,
|
||||
): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Initializing data drive. This could take a while...',
|
||||
})
|
||||
|
||||
const loader = await this.loadingCtrl.create()
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.stateService.setupEmbassy(logicalname, password)
|
||||
if (!!this.stateService.recoverySource) {
|
||||
await this.navCtrl.navigateForward(`/loading`, {
|
||||
queryParams: { action: this.route.snapshot.paramMap.get('action') },
|
||||
})
|
||||
} else {
|
||||
await this.navCtrl.navigateForward(`/success`)
|
||||
}
|
||||
await this.navCtrl.navigateForward(`/loading`)
|
||||
} catch (e: any) {
|
||||
this.errorToastService.present({
|
||||
message: `${e.message}\n\nRestart Embassy to try again.`,
|
||||
|
||||
@@ -8,11 +8,8 @@
|
||||
|
||||
<ion-card color="dark">
|
||||
<ion-card-header>
|
||||
<ion-card-title style="font-size: 40px">
|
||||
<span *ngIf="incomingAction === 'transfer'">Transferring</span>
|
||||
<span *ngIf="incomingAction === 'recover'">Recovering</span>
|
||||
</ion-card-title>
|
||||
<ion-card-subtitle
|
||||
<ion-card-title>Initializing Embassy</ion-card-title>
|
||||
<ion-card-subtitle *ngIf="stateService.dataProgress"
|
||||
>Progress: {{ (stateService.dataProgress * 100).toFixed(0)
|
||||
}}%</ion-card-subtitle
|
||||
>
|
||||
@@ -27,8 +24,10 @@
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 40px;
|
||||
"
|
||||
[type]="stateService.dataProgress ? 'determinate' : 'indeterminate'"
|
||||
[value]="stateService.dataProgress"
|
||||
></ion-progress-bar>
|
||||
<p>Setting up your Embassy. This can take a while.</p>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ion-card-title {
|
||||
font-size: 42px;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { NavController } from '@ionic/angular'
|
||||
import { StateService } from 'src/app/services/state.service'
|
||||
|
||||
@@ -9,16 +8,12 @@ import { StateService } from 'src/app/services/state.service'
|
||||
styleUrls: ['loading.page.scss'],
|
||||
})
|
||||
export class LoadingPage {
|
||||
incomingAction!: string
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
private navCtrl: NavController,
|
||||
private route: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.incomingAction = this.route.snapshot.paramMap.get('action')!
|
||||
this.stateService.pollDataTransferProgress()
|
||||
const progSub = this.stateService.dataCompletionSubject.subscribe(
|
||||
async complete => {
|
||||
|
||||
@@ -118,9 +118,7 @@ export class RecoverPage {
|
||||
},
|
||||
}
|
||||
this.stateService.recoveryPassword = password
|
||||
this.navCtrl.navigateForward(`/embassy`, {
|
||||
queryParams: { action: 'recover' },
|
||||
})
|
||||
this.navCtrl.navigateForward(`/embassy`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,245 +2,267 @@
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<ion-card color="dark">
|
||||
<ion-card-header class="ion-text-center" color="success">
|
||||
<ion-icon
|
||||
style="font-size: 80px"
|
||||
name="checkmark-circle-outline"
|
||||
></ion-icon>
|
||||
<ion-card-title>Setup Complete</ion-card-title>
|
||||
<ion-card-subtitle
|
||||
><b
|
||||
>You have successfully claimed your Embassy!</b
|
||||
></ion-card-subtitle
|
||||
>
|
||||
<br />
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<br />
|
||||
<br />
|
||||
<h2 *ngIf="recoverySource" class="ion-padding-bottom">
|
||||
<span *ngIf="recoverySource.type === 'backup'"
|
||||
>You can now safely unplug your backup drive.</span
|
||||
<!-- kiosk mode -->
|
||||
<ng-container *ngIf="isKiosk; else notKiosk">
|
||||
<ion-card color="dark">
|
||||
<ion-card-header class="ion-text-center" color="success">
|
||||
<ion-icon
|
||||
style="font-size: 80px"
|
||||
name="checkmark-circle-outline"
|
||||
></ion-icon>
|
||||
<ion-card-title>Setup Complete</ion-card-title>
|
||||
<ion-card-subtitle
|
||||
><b>You will be redirected momentarily</b></ion-card-subtitle
|
||||
>
|
||||
<span *ngIf="recoverySource.type === 'migrate'"
|
||||
>You can now safely unplug your old drive.</span
|
||||
>
|
||||
</h2>
|
||||
<h2 style="font-weight: bold">
|
||||
Access your Embassy using the methods below. You should
|
||||
<a (click)="download()" class="inline">
|
||||
download this page <ion-icon name="download-outline"></ion-icon>
|
||||
</a>
|
||||
for your records.
|
||||
</h2>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<!-- LAN Instructions -->
|
||||
<h1><b>From Home (LAN)</b></h1>
|
||||
|
||||
<div class="ion-padding ion-text-start">
|
||||
<p>
|
||||
Visit the address below when you are connected to the same WiFi
|
||||
or Local Area Network (LAN) as your Embassy:
|
||||
</p>
|
||||
|
||||
<br />
|
||||
</ion-card-header>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
|
||||
<p>
|
||||
<b>Note:</b> embassy.local was for setup purposes only, it will
|
||||
no longer work.
|
||||
</p>
|
||||
|
||||
<ion-item
|
||||
lines="none"
|
||||
color="dark"
|
||||
class="ion-padding-top ion-padding-bottom"
|
||||
<!-- not kiosk -->
|
||||
<ng-template #notKiosk>
|
||||
<ion-card color="dark">
|
||||
<ion-card-header class="ion-text-center" color="success">
|
||||
<ion-icon
|
||||
style="font-size: 80px"
|
||||
name="checkmark-circle-outline"
|
||||
></ion-icon>
|
||||
<ion-card-title>Setup Complete</ion-card-title>
|
||||
<ion-card-subtitle
|
||||
><b>See below for next steps</b></ion-card-subtitle
|
||||
>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<code
|
||||
><ion-text color="light"
|
||||
><b>{{ lanAddress }}</b></ion-text
|
||||
></code
|
||||
>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
color="light"
|
||||
fill="clear"
|
||||
[href]="lanAddress"
|
||||
target="_blank"
|
||||
<br />
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<br />
|
||||
<br />
|
||||
<h2 *ngIf="recoverySource" class="ion-padding-bottom">
|
||||
<span *ngIf="recoverySource.type === 'backup'"
|
||||
>You can now safely unplug your backup drive.</span
|
||||
>
|
||||
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button
|
||||
color="light"
|
||||
fill="clear"
|
||||
(click)="copy(lanAddress)"
|
||||
<span *ngIf="recoverySource.type === 'migrate'"
|
||||
>You can now safely unplug your old drive.</span
|
||||
>
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<p>
|
||||
<b>Important!</b>
|
||||
Your browser will warn you that the website is untrusted. You
|
||||
can bypass this warning on most browsers. The warning will go
|
||||
away after you
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="inline"
|
||||
>
|
||||
follow the instructions
|
||||
<ion-icon name="open-outline"></ion-icon>
|
||||
</h2>
|
||||
<h2 style="font-weight: bold">
|
||||
Access your Embassy using the methods below. You should
|
||||
<a (click)="download()" class="inline">
|
||||
download this page
|
||||
<ion-icon name="download-outline"></ion-icon>
|
||||
</a>
|
||||
to download and trust your Embassy's Root Certificate Authority.
|
||||
</p>
|
||||
for your records.
|
||||
</h2>
|
||||
|
||||
<ion-button style="margin-top: 24px" (click)="installCert()">
|
||||
Download Root CA
|
||||
<ion-icon slot="end" name="download-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="line"></div>
|
||||
<!-- LAN Instructions -->
|
||||
<h1><b>From Home (LAN)</b></h1>
|
||||
|
||||
<!-- Tor Instructions -->
|
||||
<h1><b>On The Go (Tor)</b></h1>
|
||||
<div class="ion-padding ion-text-start">
|
||||
<p>
|
||||
Visit the address below when you are connected to the same
|
||||
WiFi or Local Area Network (LAN) as your Embassy:
|
||||
</p>
|
||||
|
||||
<div class="ion-padding ion-text-start">
|
||||
<p>Visit the address below when you are away from home:</p>
|
||||
<br />
|
||||
|
||||
<ion-item
|
||||
lines="none"
|
||||
color="dark"
|
||||
class="ion-padding-top ion-padding-bottom"
|
||||
>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<code
|
||||
><ion-text color="light"
|
||||
><b>{{ torAddress }}</b></ion-text
|
||||
></code
|
||||
<p>
|
||||
<b>Note:</b> embassy.local was for setup purposes only, it
|
||||
will no longer work.
|
||||
</p>
|
||||
|
||||
<ion-item
|
||||
lines="none"
|
||||
color="dark"
|
||||
class="ion-padding-top ion-padding-bottom"
|
||||
>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<code
|
||||
><ion-text color="light"
|
||||
><b>{{ lanAddress }}</b></ion-text
|
||||
></code
|
||||
>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
color="light"
|
||||
fill="clear"
|
||||
[href]="lanAddress"
|
||||
target="_blank"
|
||||
>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
color="light"
|
||||
fill="clear"
|
||||
(click)="copy(torAddress)"
|
||||
>
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button
|
||||
color="light"
|
||||
fill="clear"
|
||||
(click)="copy(lanAddress)"
|
||||
>
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<p>
|
||||
<b>Important!</b>
|
||||
This address will only work from a
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-tor"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="inline"
|
||||
>
|
||||
Tor-enabled browser
|
||||
<ion-icon name="open-outline"></ion-icon> </a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</ion-card-content>
|
||||
<div id="bottom-div"></div>
|
||||
</ion-card>
|
||||
<p>
|
||||
<b>Important!</b>
|
||||
Your browser will warn you that the website is untrusted. You
|
||||
can bypass this warning on most browsers. The warning will go
|
||||
away after you
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="inline"
|
||||
>
|
||||
follow the instructions
|
||||
<ion-icon name="open-outline"></ion-icon>
|
||||
</a>
|
||||
to download and trust your Embassy's Root Certificate
|
||||
Authority.
|
||||
</p>
|
||||
|
||||
<!-- scroll down -->
|
||||
<div
|
||||
[ngStyle]="{
|
||||
position: 'fixed',
|
||||
bottom: isOnBottom ? '-42px' : '24px',
|
||||
transition: 'bottom 0.15s ease-out 0s',
|
||||
right: '50%',
|
||||
width: '120px',
|
||||
'margin-right': '-60px',
|
||||
'z-index': '1000'
|
||||
}"
|
||||
>
|
||||
<ion-button color="warning" (click)="scrollToBottom()">
|
||||
More
|
||||
<ion-icon slot="end" name="chevron-down"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<!-- cert elem -->
|
||||
<a hidden id="install-cert" download="embassy.crt"></a>
|
||||
|
||||
<!-- download elem -->
|
||||
<div hidden id="downloadable">
|
||||
<div style="padding: 0 24px; font-family: Courier">
|
||||
<h1>Embassy Info</h1>
|
||||
|
||||
<section style="padding: 16px; border: solid 1px">
|
||||
<h2>Tor Info</h2>
|
||||
<p>
|
||||
To use your Embassy over Tor, visit its unique Tor address from
|
||||
any Tor-enabled browser.
|
||||
</p>
|
||||
<p>
|
||||
For more detailed instructions, click
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-tor"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><b>here</b></a
|
||||
>.
|
||||
</p>
|
||||
<p><b>Tor Address: </b><code id="tor-addr"></code></p>
|
||||
</section>
|
||||
|
||||
<section style="padding: 16px; border: solid 1px; border-top: none">
|
||||
<h2>LAN Info</h2>
|
||||
<p>To use your Embassy locally, you must:</p>
|
||||
<ol>
|
||||
<li>
|
||||
Currently be connected to the same Local Area Network (LAN) as
|
||||
your Embassy.
|
||||
</li>
|
||||
<li>Download your Embassy's Root Certificate Authority.</li>
|
||||
<li>
|
||||
Trust your Embassy's Root CA on <i>both</i> your
|
||||
computer/phone and in your browser settings.
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
For step-by-step instructions, click
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><b>here</b></a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<div style="margin: 42px 0">
|
||||
<a
|
||||
id="cert"
|
||||
download="embassy.crt"
|
||||
style="
|
||||
background: #25272b;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
"
|
||||
>
|
||||
<ion-button style="margin-top: 24px" (click)="installCert()">
|
||||
Download Root CA
|
||||
</a>
|
||||
<ion-icon slot="end" name="download-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<p><b>LAN Address: </b><code id="lan-addr"></code></p>
|
||||
</section>
|
||||
<div class="line"></div>
|
||||
|
||||
<!-- Tor Instructions -->
|
||||
<h1><b>On The Go (Tor)</b></h1>
|
||||
|
||||
<div class="ion-padding ion-text-start">
|
||||
<p>Visit the address below when you are away from home:</p>
|
||||
|
||||
<ion-item
|
||||
lines="none"
|
||||
color="dark"
|
||||
class="ion-padding-top ion-padding-bottom"
|
||||
>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<code
|
||||
><ion-text color="light"
|
||||
><b>{{ torAddress }}</b></ion-text
|
||||
></code
|
||||
>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
color="light"
|
||||
fill="clear"
|
||||
(click)="copy(torAddress)"
|
||||
>
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<p>
|
||||
<b>Important!</b>
|
||||
This address will only work from a
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-tor"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="inline"
|
||||
>
|
||||
Tor-enabled browser
|
||||
<ion-icon name="open-outline"></ion-icon> </a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</ion-card-content>
|
||||
<div id="bottom-div"></div>
|
||||
</ion-card>
|
||||
|
||||
<!-- scroll down -->
|
||||
<div
|
||||
[ngStyle]="{
|
||||
position: 'fixed',
|
||||
bottom: isOnBottom ? '-42px' : '24px',
|
||||
transition: 'bottom 0.15s ease-out 0s',
|
||||
right: '50%',
|
||||
width: '120px',
|
||||
'margin-right': '-60px',
|
||||
'z-index': '1000'
|
||||
}"
|
||||
>
|
||||
<ion-button color="warning" (click)="scrollToBottom()">
|
||||
More
|
||||
<ion-icon slot="end" name="chevron-down"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- cert elem -->
|
||||
<a hidden id="install-cert" download="embassy.crt"></a>
|
||||
|
||||
<!-- download elem -->
|
||||
<div hidden id="downloadable">
|
||||
<div style="padding: 0 24px; font-family: Courier">
|
||||
<h1>Embassy Info</h1>
|
||||
|
||||
<section style="padding: 16px; border: solid 1px">
|
||||
<h2>Tor Info</h2>
|
||||
<p>
|
||||
To use your Embassy over Tor, visit its unique Tor address
|
||||
from any Tor-enabled browser.
|
||||
</p>
|
||||
<p>
|
||||
For more detailed instructions, click
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-tor"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><b>here</b></a
|
||||
>.
|
||||
</p>
|
||||
<p><b>Tor Address: </b><code id="tor-addr"></code></p>
|
||||
</section>
|
||||
|
||||
<section
|
||||
style="padding: 16px; border: solid 1px; border-top: none"
|
||||
>
|
||||
<h2>LAN Info</h2>
|
||||
<p>To use your Embassy locally, you must:</p>
|
||||
<ol>
|
||||
<li>
|
||||
Currently be connected to the same Local Area Network (LAN)
|
||||
as your Embassy.
|
||||
</li>
|
||||
<li>Download your Embassy's Root Certificate Authority.</li>
|
||||
<li>
|
||||
Trust your Embassy's Root CA on <i>both</i> your
|
||||
computer/phone and in your browser settings.
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
For step-by-step instructions, click
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><b>here</b></a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<div style="margin: 42px 0">
|
||||
<a
|
||||
id="cert"
|
||||
download="embassy.crt"
|
||||
style="
|
||||
background: #25272b;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
"
|
||||
>
|
||||
Download Root CA
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p><b>LAN Address: </b><code id="lan-addr"></code></p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
DownloadHTMLService,
|
||||
ErrorToastService,
|
||||
} from '@start9labs/shared'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
import { StateService } from 'src/app/services/state.service'
|
||||
|
||||
@Component({
|
||||
@@ -26,6 +27,10 @@ export class SuccessPage {
|
||||
|
||||
@Output() onDownload = new EventEmitter()
|
||||
|
||||
torAddress = ''
|
||||
lanAddress = ''
|
||||
cert = ''
|
||||
|
||||
isOnBottom = true
|
||||
|
||||
constructor(
|
||||
@@ -33,6 +38,7 @@ export class SuccessPage {
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly errCtrl: ErrorToastService,
|
||||
private readonly stateService: StateService,
|
||||
private api: ApiService,
|
||||
private readonly downloadHtml: DownloadHTMLService,
|
||||
) {}
|
||||
|
||||
@@ -40,27 +46,30 @@ export class SuccessPage {
|
||||
return this.stateService.recoverySource
|
||||
}
|
||||
|
||||
get torAddress() {
|
||||
return this.stateService.torAddress
|
||||
}
|
||||
|
||||
get lanAddress() {
|
||||
return this.stateService.lanAddress
|
||||
get isKiosk() {
|
||||
return ['localhost', '127.0.0.1'].includes(this.document.location.hostname)
|
||||
}
|
||||
|
||||
async ngAfterViewInit() {
|
||||
setTimeout(() => this.checkBottom(), 42)
|
||||
|
||||
try {
|
||||
await this.stateService.completeEmbassy()
|
||||
this.document
|
||||
.getElementById('install-cert')
|
||||
?.setAttribute(
|
||||
'href',
|
||||
'data:application/x-x509-ca-cert;base64,' +
|
||||
encodeURIComponent(this.stateService.cert),
|
||||
)
|
||||
this.download()
|
||||
const ret = await this.api.complete()
|
||||
if (!this.isKiosk) {
|
||||
setTimeout(() => this.checkBottom(), 42)
|
||||
|
||||
this.torAddress = ret['tor-address']
|
||||
this.lanAddress = ret['lan-address']
|
||||
this.cert = ret['root-ca']
|
||||
|
||||
this.document
|
||||
.getElementById('install-cert')
|
||||
?.setAttribute(
|
||||
'href',
|
||||
'data:application/x-x509-ca-cert;base64,' +
|
||||
encodeURIComponent(this.cert),
|
||||
)
|
||||
this.download()
|
||||
}
|
||||
await this.api.exit()
|
||||
} catch (e: any) {
|
||||
await this.errCtrl.present(e)
|
||||
}
|
||||
@@ -88,15 +97,15 @@ export class SuccessPage {
|
||||
const torAddress = this.document.getElementById('tor-addr')
|
||||
const lanAddress = this.document.getElementById('lan-addr')
|
||||
|
||||
if (torAddress) torAddress.innerHTML = this.stateService.torAddress
|
||||
if (lanAddress) lanAddress.innerHTML = this.stateService.lanAddress
|
||||
if (torAddress) torAddress.innerHTML = this.torAddress
|
||||
if (lanAddress) lanAddress.innerHTML = this.lanAddress
|
||||
|
||||
this.document
|
||||
.getElementById('cert')
|
||||
?.setAttribute(
|
||||
'href',
|
||||
'data:application/x-x509-ca-cert;base64,' +
|
||||
encodeURIComponent(this.stateService.cert),
|
||||
encodeURIComponent(this.cert),
|
||||
)
|
||||
let html = this.document.getElementById('downloadable')?.innerHTML || ''
|
||||
this.downloadHtml.download('embassy-info.html', html)
|
||||
|
||||
@@ -32,8 +32,7 @@ export class TransferPage {
|
||||
|
||||
async getDrives() {
|
||||
try {
|
||||
const drives = await this.apiService.getDrives()
|
||||
this.drives = drives.filter(d => d.partitions.length)
|
||||
this.drives = await this.apiService.getDrives()
|
||||
} catch (e: any) {
|
||||
this.errToastService.present(e)
|
||||
} finally {
|
||||
@@ -58,9 +57,7 @@ export class TransferPage {
|
||||
type: 'migrate',
|
||||
guid,
|
||||
}
|
||||
this.navCtrl.navigateForward(`/embassy`, {
|
||||
queryParams: { action: 'transfer' },
|
||||
})
|
||||
this.navCtrl.navigateForward(`/embassy`)
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -3,14 +3,14 @@ import { DiskListResponse, EmbassyOSDiskInfo } from '@start9labs/shared'
|
||||
export abstract class ApiService {
|
||||
pubkey?: jose.JWK.Key
|
||||
|
||||
abstract getStatus(): Promise<GetStatusRes> // setup.status
|
||||
abstract getStatus(): Promise<StatusRes> // setup.status
|
||||
abstract getPubKey(): Promise<void> // setup.get-pubkey
|
||||
abstract getDrives(): Promise<DiskListResponse> // setup.disk.list
|
||||
abstract getRecoveryStatus(): Promise<RecoveryStatusRes> // setup.recovery.status
|
||||
abstract verifyCifs(cifs: CifsRecoverySource): Promise<EmbassyOSDiskInfo> // setup.cifs.verify
|
||||
abstract importDrive(importInfo: ImportDriveReq): Promise<SetupEmbassyRes> // setup.attach
|
||||
abstract setupEmbassy(setupInfo: SetupEmbassyReq): Promise<SetupEmbassyRes> // setup.execute
|
||||
abstract setupComplete(): Promise<SetupEmbassyRes> // setup.complete
|
||||
abstract attach(importInfo: AttachReq): Promise<void> // setup.attach
|
||||
abstract execute(setupInfo: ExecuteReq): Promise<void> // setup.execute
|
||||
abstract complete(): Promise<CompleteRes> // setup.complete
|
||||
abstract exit(): Promise<void> // setup.exit
|
||||
|
||||
async encrypt(toEncrypt: string): Promise<Encrypted> {
|
||||
if (!this.pubkey) throw new Error('No pubkey found!')
|
||||
@@ -27,23 +27,25 @@ type Encrypted = {
|
||||
encrypted: string
|
||||
}
|
||||
|
||||
export type GetStatusRes = {
|
||||
migrating: boolean
|
||||
}
|
||||
export type StatusRes = {
|
||||
'bytes-transferred': number
|
||||
'total-bytes': number
|
||||
complete: boolean
|
||||
} | null
|
||||
|
||||
export type ImportDriveReq = {
|
||||
export type AttachReq = {
|
||||
guid: string
|
||||
'embassy-password': Encrypted
|
||||
}
|
||||
|
||||
export type SetupEmbassyReq = {
|
||||
export type ExecuteReq = {
|
||||
'embassy-logicalname': string
|
||||
'embassy-password': Encrypted
|
||||
'recovery-source': RecoverySource | null
|
||||
'recovery-password': Encrypted | null
|
||||
}
|
||||
|
||||
export type SetupEmbassyRes = {
|
||||
export type CompleteRes = {
|
||||
'tor-address': string
|
||||
'lan-address': string
|
||||
'root-ca': string
|
||||
@@ -90,9 +92,3 @@ export type CifsRecoverySource = {
|
||||
username: string
|
||||
password: Encrypted | null
|
||||
}
|
||||
|
||||
export type RecoveryStatusRes = {
|
||||
'bytes-transferred': number
|
||||
'total-bytes': number
|
||||
complete: boolean
|
||||
}
|
||||
|
||||
@@ -12,11 +12,10 @@ import {
|
||||
ApiService,
|
||||
CifsRecoverySource,
|
||||
DiskRecoverySource,
|
||||
GetStatusRes,
|
||||
ImportDriveReq,
|
||||
RecoveryStatusRes,
|
||||
SetupEmbassyReq,
|
||||
SetupEmbassyRes,
|
||||
StatusRes,
|
||||
AttachReq,
|
||||
ExecuteReq,
|
||||
CompleteRes,
|
||||
} from './api.service'
|
||||
import * as jose from 'node-jose'
|
||||
|
||||
@@ -29,7 +28,7 @@ export class LiveApiService extends ApiService {
|
||||
}
|
||||
|
||||
async getStatus() {
|
||||
return this.rpcRequest<GetStatusRes>({
|
||||
return this.rpcRequest<StatusRes>({
|
||||
method: 'setup.status',
|
||||
params: {},
|
||||
})
|
||||
@@ -58,13 +57,6 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async getRecoveryStatus() {
|
||||
return this.rpcRequest<RecoveryStatusRes>({
|
||||
method: 'setup.recovery.status',
|
||||
params: {},
|
||||
})
|
||||
}
|
||||
|
||||
async verifyCifs(source: CifsRecoverySource) {
|
||||
source.path = source.path.replace('/\\/g', '/')
|
||||
return this.rpcRequest<EmbassyOSDiskInfo>({
|
||||
@@ -73,19 +65,14 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async importDrive(params: ImportDriveReq) {
|
||||
const res = await this.rpcRequest<SetupEmbassyRes>({
|
||||
async attach(params: AttachReq) {
|
||||
await this.rpcRequest<void>({
|
||||
method: 'setup.attach',
|
||||
params,
|
||||
})
|
||||
|
||||
return {
|
||||
...res,
|
||||
'root-ca': encodeBase64(res['root-ca']),
|
||||
}
|
||||
}
|
||||
|
||||
async setupEmbassy(setupInfo: SetupEmbassyReq) {
|
||||
async execute(setupInfo: ExecuteReq) {
|
||||
if (setupInfo['recovery-source']?.type === 'backup') {
|
||||
if (isCifsSource(setupInfo['recovery-source'].target)) {
|
||||
setupInfo['recovery-source'].target.path = setupInfo[
|
||||
@@ -94,19 +81,14 @@ export class LiveApiService extends ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
const res = await this.rpcRequest<SetupEmbassyRes>({
|
||||
await this.rpcRequest<void>({
|
||||
method: 'setup.execute',
|
||||
params: setupInfo,
|
||||
})
|
||||
|
||||
return {
|
||||
...res,
|
||||
'root-ca': encodeBase64(res['root-ca']),
|
||||
}
|
||||
}
|
||||
|
||||
async setupComplete() {
|
||||
const res = await this.rpcRequest<SetupEmbassyRes>({
|
||||
async complete() {
|
||||
const res = await this.rpcRequest<CompleteRes>({
|
||||
method: 'setup.complete',
|
||||
params: {},
|
||||
})
|
||||
@@ -117,6 +99,13 @@ export class LiveApiService extends ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
async exit() {
|
||||
await this.rpcRequest<void>({
|
||||
method: 'setup.exit',
|
||||
params: {},
|
||||
})
|
||||
}
|
||||
|
||||
private async rpcRequest<T>(opts: RPCOptions): Promise<T> {
|
||||
const res = await this.http.rpcRequest<T>(opts)
|
||||
|
||||
|
||||
@@ -3,21 +3,36 @@ import { encodeBase64, pauseFor } from '@start9labs/shared'
|
||||
import {
|
||||
ApiService,
|
||||
CifsRecoverySource,
|
||||
ImportDriveReq,
|
||||
SetupEmbassyReq,
|
||||
AttachReq,
|
||||
ExecuteReq,
|
||||
CompleteRes,
|
||||
} from './api.service'
|
||||
import * as jose from 'node-jose'
|
||||
|
||||
let tries = 0
|
||||
let tries: number
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MockApiService extends ApiService {
|
||||
async getStatus() {
|
||||
const restoreOrMigrate = true
|
||||
const total = 4
|
||||
|
||||
await pauseFor(1000)
|
||||
|
||||
if (tries === undefined) {
|
||||
tries = 0
|
||||
return null
|
||||
}
|
||||
|
||||
tries++
|
||||
const progress = tries - 1
|
||||
|
||||
return {
|
||||
migrating: false,
|
||||
'bytes-transferred': restoreOrMigrate ? progress : 0,
|
||||
'total-bytes': restoreOrMigrate ? total : 0,
|
||||
complete: progress === total,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,15 +127,6 @@ export class MockApiService extends ApiService {
|
||||
]
|
||||
}
|
||||
|
||||
async getRecoveryStatus() {
|
||||
tries = Math.min(tries + 1, 4)
|
||||
return {
|
||||
'bytes-transferred': tries,
|
||||
'total-bytes': 4,
|
||||
complete: tries === 4,
|
||||
}
|
||||
}
|
||||
|
||||
async verifyCifs(params: CifsRecoverySource) {
|
||||
await pauseFor(1000)
|
||||
return {
|
||||
@@ -132,19 +138,25 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
async importDrive(params: ImportDriveReq) {
|
||||
await pauseFor(3000)
|
||||
return setupRes
|
||||
}
|
||||
|
||||
async setupEmbassy(setupInfo: SetupEmbassyReq) {
|
||||
await pauseFor(3000)
|
||||
return setupRes
|
||||
}
|
||||
|
||||
async setupComplete() {
|
||||
async attach(params: AttachReq) {
|
||||
await pauseFor(1000)
|
||||
}
|
||||
|
||||
async execute(setupInfo: ExecuteReq) {
|
||||
await pauseFor(1000)
|
||||
}
|
||||
|
||||
async complete(): Promise<CompleteRes> {
|
||||
await pauseFor(1000)
|
||||
return {
|
||||
'tor-address': 'http://asdafsadasdasasdasdfasdfasdf.onion',
|
||||
'lan-address': 'https://embassy-abcdefgh.local',
|
||||
'root-ca': encodeBase64(rootCA),
|
||||
}
|
||||
}
|
||||
|
||||
async exit() {
|
||||
await pauseFor(1000)
|
||||
return setupRes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,9 +182,3 @@ Rf3ZOPm9QP92YpWyYDkfAU04xdDo1vR0MYjKPkl4LjRqSU/tcCJnPMbJiwq+bWpX
|
||||
2WJoEBXB/p15Kn6JxjI0ze2SnSI48JZ8it4fvxrhOo0VoLNIuCuNXJOwU17Rdl1W
|
||||
YJidaq7je6k18AdgPA0Kh8y1XtfUH3fTaVw4
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
const setupRes = {
|
||||
'tor-address': 'http://asdafsadasdasasdasdfasdfasdf.onion',
|
||||
'lan-address': 'https://embassy-abcdefgh.local',
|
||||
'root-ca': encodeBase64(rootCA),
|
||||
}
|
||||
|
||||
@@ -7,9 +7,6 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StateService {
|
||||
polling = false
|
||||
embassyLoaded = false
|
||||
|
||||
recoverySource?: RecoverySource
|
||||
recoveryPassword?: string
|
||||
|
||||
@@ -21,17 +18,12 @@ export class StateService {
|
||||
dataProgress = 0
|
||||
dataCompletionSubject = new BehaviorSubject(false)
|
||||
|
||||
torAddress = ''
|
||||
lanAddress = ''
|
||||
cert = ''
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly errorToastService: ErrorToastService,
|
||||
) {}
|
||||
|
||||
async pollDataTransferProgress() {
|
||||
this.polling = true
|
||||
await pauseFor(500)
|
||||
|
||||
if (this.dataTransferProgress?.complete) {
|
||||
@@ -39,15 +31,10 @@ export class StateService {
|
||||
return
|
||||
}
|
||||
|
||||
let progress
|
||||
try {
|
||||
progress = await this.api.getRecoveryStatus()
|
||||
} catch (e: any) {
|
||||
this.errorToastService.present({
|
||||
message: `${e.message}\n\nRestart Embassy to try again.`,
|
||||
})
|
||||
}
|
||||
if (progress) {
|
||||
const progress = await this.api.getStatus()
|
||||
if (!progress) return
|
||||
|
||||
this.dataTransferProgress = {
|
||||
bytesTransferred: progress['bytes-transferred'],
|
||||
totalBytes: progress['total-bytes'],
|
||||
@@ -58,25 +45,26 @@ export class StateService {
|
||||
this.dataTransferProgress.bytesTransferred /
|
||||
this.dataTransferProgress.totalBytes
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.errorToastService.present({
|
||||
message: `${e.message}\n\nRestart Embassy to try again.`,
|
||||
})
|
||||
}
|
||||
setTimeout(() => this.pollDataTransferProgress(), 0) // prevent call stack from growing
|
||||
}
|
||||
|
||||
async importDrive(guid: string, password: string): Promise<void> {
|
||||
const ret = await this.api.importDrive({
|
||||
await this.api.attach({
|
||||
guid,
|
||||
'embassy-password': await this.api.encrypt(password),
|
||||
})
|
||||
this.torAddress = ret['tor-address']
|
||||
this.lanAddress = ret['lan-address']
|
||||
this.cert = ret['root-ca']
|
||||
}
|
||||
|
||||
async setupEmbassy(
|
||||
storageLogicalname: string,
|
||||
password: string,
|
||||
): Promise<void> {
|
||||
const ret = await this.api.setupEmbassy({
|
||||
await this.api.execute({
|
||||
'embassy-logicalname': storageLogicalname,
|
||||
'embassy-password': await this.api.encrypt(password),
|
||||
'recovery-source': this.recoverySource || null,
|
||||
@@ -84,15 +72,5 @@ export class StateService {
|
||||
? await this.api.encrypt(this.recoveryPassword)
|
||||
: null,
|
||||
})
|
||||
this.torAddress = ret['tor-address']
|
||||
this.lanAddress = ret['lan-address']
|
||||
this.cert = ret['root-ca']
|
||||
}
|
||||
|
||||
async completeEmbassy(): Promise<void> {
|
||||
const ret = await this.api.setupComplete()
|
||||
this.torAddress = ret['tor-address']
|
||||
this.lanAddress = ret['lan-address']
|
||||
this.cert = ret['root-ca']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { DOCUMENT } from '@angular/common'
|
||||
import { Inject, Injectable } from '@angular/core'
|
||||
import { WorkspaceConfig } from '@start9labs/shared'
|
||||
import {
|
||||
InterfaceDef,
|
||||
@@ -18,7 +19,9 @@ const {
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ConfigService {
|
||||
origin = removePort(removeProtocol(window.origin))
|
||||
constructor(@Inject(DOCUMENT) private readonly document: Document) {}
|
||||
|
||||
hostname = this.document.location.hostname
|
||||
version = require('../../../../../package.json').version as string
|
||||
useMocks = useMocks
|
||||
mocks = mocks
|
||||
@@ -31,13 +34,15 @@ export class ConfigService {
|
||||
|
||||
isTor(): boolean {
|
||||
return (
|
||||
(useMocks && mocks.maskAs === 'tor') || this.origin.endsWith('.onion')
|
||||
this.hostname.endsWith('.onion') || (useMocks && mocks.maskAs === 'tor')
|
||||
)
|
||||
}
|
||||
|
||||
isLan(): boolean {
|
||||
return (
|
||||
(useMocks && mocks.maskAs === 'lan') || this.origin.endsWith('.local')
|
||||
this.hostname === 'localhost' ||
|
||||
this.hostname.endsWith('.local') ||
|
||||
(useMocks && mocks.maskAs === 'lan')
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user