Feature/consolidate setup (#3092)

* start consolidating

* add start-cli flash-os

* combine install and setup and refactor all

* use http

* undo mock

* fix translation

* translations

* use dialogservice wrapper

* better ST messaging on setup

* only warn on update if breakages (#3097)

* finish setup wizard and ui language-keyboard feature

* fix typo

* wip: localization

* remove start-tunnel readme

* switch to posix strings for language internal

* revert mock

* translate backend strings

* fix missing about text

* help text for args

* feat: add "Add new gateway" option (#3098)

* feat: add "Add new gateway" option

* Update web/projects/ui/src/app/routes/portal/components/form/controls/select.component.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* add translation

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Matt Hill <mattnine@protonmail.com>

* fix dns selection

* keyboard keymap also

* ability to shutdown after install

* revert mock

* working setup flow + manifest localization

* (mostly) redundant localization on frontend

* version bump

* omit live medium from disk list and better space management

* ignore missing package archive on 035 migration

* fix device migration

* add i18n helper to sdk

* fix install over 0.3.5.1

* fix grub config

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
Co-authored-by: Alex Inkin <alexander@inkin.ru>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Aiden McClelland
2026-01-27 14:44:41 -08:00
committed by GitHub
parent 99871805bd
commit c65db31fd9
251 changed files with 12163 additions and 3966 deletions

View File

@@ -1,50 +1,65 @@
import * as jose from 'node-jose'
import {
DiskInfo,
DiskListResponse,
FollowLogsRes,
PartitionInfo,
FullKeyboard,
SetLanguageParams,
StartOSDiskInfo,
} from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
import { Observable } from 'rxjs'
import {
SetupStatusRes,
InstallOsParams,
InstallOsRes,
AttachParams,
SetupExecuteParams,
SetupCompleteRes,
EchoReq,
} from '../types'
export abstract class ApiService {
pubkey?: jose.JWK.Key
abstract getStatus(): Promise<T.SetupStatusRes | null> // setup.status
// echo
abstract echo(params: EchoReq, url: string): Promise<string>
// Status & Setup
abstract getStatus(): Promise<SetupStatusRes> // setup.status
abstract getPubKey(): Promise<void> // setup.get-pubkey
abstract getDrives(): Promise<DiskListResponse> // setup.disk.list
abstract setKeyboard(params: FullKeyboard): Promise<null> // setup.set-keyboard
abstract setLanguage(params: SetLanguageParams): Promise<null> // setup.set-language
// Install
abstract getDisks(): Promise<DiskInfo[]> // setup.disk.list
abstract installOs(params: InstallOsParams): Promise<InstallOsRes> // setup.install-os
// Setup execution
abstract attach(params: AttachParams): Promise<T.SetupProgress> // setup.attach
abstract execute(params: SetupExecuteParams): Promise<T.SetupProgress> // setup.execute
// Recovery helpers
abstract verifyCifs(
cifs: T.VerifyCifsParams,
): Promise<Record<string, StartOSDiskInfo>> // setup.cifs.verify
abstract attach(importInfo: T.AttachParams): Promise<T.SetupProgress> // setup.attach
abstract execute(setupInfo: T.SetupExecuteParams): Promise<T.SetupProgress> // setup.execute
abstract complete(): Promise<T.SetupResult> // setup.complete
// Completion
abstract complete(): Promise<SetupCompleteRes> // setup.complete
abstract exit(): Promise<void> // setup.exit
abstract shutdown(): Promise<void> // setup.shutdown
// Logs & Progress
abstract initFollowLogs(): Promise<FollowLogsRes> // setup.logs.follow
abstract restart(): Promise<void> // setup.restart
abstract openWebsocket$<T>(guid: string): Observable<T>
// Restart (for error recovery)
abstract restart(): Promise<void> // setup.restart
async encrypt(toEncrypt: string): Promise<T.EncryptedWire> {
if (!this.pubkey) throw new Error('No pubkey found!')
const encrypted = await jose.JWE.createEncrypt(this.pubkey!)
.update(toEncrypt)
.final()
return {
encrypted,
}
return { encrypted }
}
}
export type WebsocketConfig<T> = Omit<WebSocketSubjectConfig<T>, 'url'>
export type StartOSDiskInfoWithId = StartOSDiskInfo & {
id: string
}
export type StartOSDiskInfoFull = StartOSDiskInfoWithId & {
partition: PartitionInfo
drive: DiskInfo
}

View File

@@ -1,12 +1,14 @@
import { Inject, Injectable, DOCUMENT } from '@angular/core'
import {
DiskListResponse,
DiskInfo,
encodeBase64,
FollowLogsRes,
FullKeyboard,
HttpService,
isRpcError,
RpcError,
RPCOptions,
SetLanguageParams,
StartOSDiskInfo,
} from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
@@ -14,6 +16,15 @@ import * as jose from 'node-jose'
import { Observable } from 'rxjs'
import { webSocket } from 'rxjs/webSocket'
import { ApiService } from './api.service'
import {
SetupStatusRes,
InstallOsParams,
InstallOsRes,
AttachParams,
SetupExecuteParams,
SetupCompleteRes,
EchoReq,
} from '../types'
@Injectable({
providedIn: 'root',
@@ -36,39 +47,54 @@ export class LiveApiService extends ApiService {
})
}
async getStatus(): Promise<T.SetupStatusRes | null> {
return this.rpcRequest<T.SetupStatusRes | null>({
async echo(params: EchoReq, url: string): Promise<string> {
return this.rpcRequest({ method: 'echo', params }, url)
}
async getStatus() {
return this.rpcRequest<SetupStatusRes>({
method: 'setup.status',
params: {},
})
}
/**
* We want to update the pubkey, which means that we will call in clearnet the
* getPubKey, and all the information is never in the clear, and only public
* information is sent across the network. We don't want to expose that we do
* this wil all public/private key, which means that there is no information loss
* through the network.
*/
async getPubKey(): Promise<void> {
async getPubKey() {
const response: jose.JWK.Key = await this.rpcRequest({
method: 'setup.get-pubkey',
params: {},
})
this.pubkey = response
}
async getDrives(): Promise<DiskListResponse> {
return this.rpcRequest<DiskListResponse>({
async setKeyboard(params: FullKeyboard): Promise<null> {
return this.rpcRequest({
method: 'setup.set-keyboard',
params,
})
}
async setLanguage(params: SetLanguageParams): Promise<null> {
return this.rpcRequest({
method: 'setup.set-language',
params,
})
}
async getDisks() {
return this.rpcRequest<DiskInfo[]>({
method: 'setup.disk.list',
params: {},
})
}
async verifyCifs(
source: T.VerifyCifsParams,
): Promise<Record<string, StartOSDiskInfo>> {
async installOs(params: InstallOsParams) {
return this.rpcRequest<InstallOsRes>({
method: 'setup.install-os',
params,
})
}
async verifyCifs(source: T.VerifyCifsParams) {
source.path = source.path.replace('/\\/g', '/')
return this.rpcRequest<Record<string, StartOSDiskInfo>>({
method: 'setup.cifs.verify',
@@ -76,33 +102,36 @@ export class LiveApiService extends ApiService {
})
}
async attach(params: T.AttachParams): Promise<T.SetupProgress> {
async attach(params: AttachParams) {
return this.rpcRequest<T.SetupProgress>({
method: 'setup.attach',
params,
})
}
async execute(setupInfo: T.SetupExecuteParams): Promise<T.SetupProgress> {
if (setupInfo.recoverySource?.type === 'backup') {
if (isCifsSource(setupInfo.recoverySource.target)) {
setupInfo.recoverySource.target.path =
setupInfo.recoverySource.target.path.replace('/\\/g', '/')
async execute(params: SetupExecuteParams) {
if (params.recoverySource?.type === 'backup') {
const target = params.recoverySource.target
if (target.type === 'cifs') {
target.path = target.path.replace('/\\/g', '/')
}
}
return this.rpcRequest<T.SetupProgress>({
method: 'setup.execute',
params: setupInfo,
params,
})
}
async initFollowLogs(): Promise<FollowLogsRes> {
return this.rpcRequest({ method: 'setup.logs.follow', params: {} })
async initFollowLogs() {
return this.rpcRequest<FollowLogsRes>({
method: 'setup.logs.follow',
params: {},
})
}
async complete(): Promise<T.SetupResult> {
const res = await this.rpcRequest<T.SetupResult>({
async complete() {
const res = await this.rpcRequest<SetupCompleteRes>({
method: 'setup.complete',
params: {},
})
@@ -113,23 +142,29 @@ export class LiveApiService extends ApiService {
}
}
async exit(): Promise<void> {
async exit() {
await this.rpcRequest<void>({
method: 'setup.exit',
params: {},
})
}
async restart(): Promise<void> {
async shutdown() {
await this.rpcRequest<void>({
method: 'setup.shutdown',
params: {},
})
}
async restart() {
await this.rpcRequest<void>({
method: 'setup.restart',
params: {},
})
}
private async rpcRequest<T>(opts: RPCOptions): Promise<T> {
const res = await this.http.rpcRequest<T>(opts)
private async rpcRequest<T>(opts: RPCOptions, url?: string): Promise<T> {
const res = await this.http.rpcRequest<T>(opts, url)
const rpcRes = res.body
if (isRpcError(rpcRes)) {
@@ -139,9 +174,3 @@ export class LiveApiService extends ApiService {
return rpcRes.result
}
}
function isCifsSource(
source: T.BackupTargetFS | null,
): source is T.Cifs & { type: 'cifs' } {
return !!(source as T.Cifs)?.hostname
}

View File

@@ -1,111 +1,33 @@
import { Injectable } from '@angular/core'
import {
DiskListResponse,
DiskInfo,
encodeBase64,
FollowLogsRes,
FullKeyboard,
pauseFor,
SetLanguageParams,
StartOSDiskInfo,
} from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import * as jose from 'node-jose'
import { first, interval, map, Observable } from 'rxjs'
import { interval, map, Observable } from 'rxjs'
import { ApiService } from './api.service'
import {
SetupStatusRes,
InstallOsParams,
InstallOsRes,
AttachParams,
SetupExecuteParams,
SetupCompleteRes,
EchoReq,
} from '../types'
@Injectable({
providedIn: 'root',
})
export class MockApiService extends ApiService {
// fullProgress$(): Observable<T.FullProgress> {
// const phases = [
// {
// name: 'Preparing Data',
// progress: null,
// },
// {
// name: 'Transferring Data',
// progress: null,
// },
// {
// name: 'Finalizing Setup',
// progress: null,
// },
// ]
// return from(phases).pipe(
// switchScan((acc, val, i) => {}, { overall: null, phases }),
// )
// }
// namedProgress$(namedProgress: T.NamedProgress): Observable<T.NamedProgress> {
// return of(namedProgress).pipe(startWith(namedProgress))
// }
// progress$(progress: T.Progress): Observable<T.Progress> {}
// websocket
// oldMockProgress$(): Promise<T.FullProgress> {
// const numPhases = PROGRESS.phases.length
// return of(PROGRESS).pipe(
// switchMap(full =>
// from(PROGRESS.phases).pipe(
// mergeScan((full, phase, i) => {
// if (
// !phase.progress ||
// typeof phase.progress !== 'object' ||
// !phase.progress.total
// ) {
// full.phases[i].progress = true
// if (
// full.overall &&
// typeof full.overall === 'object' &&
// full.overall.total
// ) {
// const step = full.overall.total / numPhases
// full.overall.done += step
// }
// return of(full).pipe(delay(2000))
// } else {
// const total = phase.progress.total
// const step = total / 4
// let done = phase.progress.done
// return interval(1000).pipe(
// takeWhile(() => done < total),
// map(() => {
// done += step
// console.error(done)
// if (
// full.overall &&
// typeof full.overall === 'object' &&
// full.overall.total
// ) {
// const step = full.overall.total / numPhases / 4
// full.overall.done += step
// }
// if (done === total) {
// full.phases[i].progress = true
// if (i === numPhases - 1) {
// full.overall = true
// }
// }
// return full
// }),
// )
// }
// }, full),
// ),
// ),
// )
// }
private statusIndex = 0
private installCompleted = false
openWebsocket$<T>(guid: string): Observable<T> {
if (guid === 'logs-guid') {
@@ -117,24 +39,13 @@ export class MockApiService extends ApiService {
})),
) as Observable<T>
} else if (guid === 'progress-guid') {
// @TODO Matt mock progress
return interval(1000).pipe(
first(),
map(() => ({
overall: true,
phases: [
{
name: 'Preparing Data',
progress: true,
},
{
name: 'Transferring Data',
progress: true,
},
{
name: 'Finalizing Setup',
progress: true,
},
{ name: 'Preparing Data', progress: true },
{ name: 'Transferring Data', progress: true },
{ name: 'Finalizing Setup', progress: true },
],
})),
) as Observable<T>
@@ -143,40 +54,44 @@ export class MockApiService extends ApiService {
}
}
private statusIndex = 0
async getStatus(): Promise<T.SetupStatusRes | null> {
await pauseFor(1000)
async echo(params: EchoReq, url: string): Promise<string> {
if (url) {
const num = Math.floor(Math.random() * 10) + 1
if (num > 8) return params.message
throw new Error()
}
await pauseFor(500)
return params.message
}
async getStatus(): Promise<SetupStatusRes> {
await pauseFor(500)
this.statusIndex++
switch (this.statusIndex) {
case 2:
return {
status: 'running',
progress: PROGRESS,
guid: 'progress-guid',
}
case 3:
return {
status: 'complete',
torAddresses: ['https://asdafsadasdasasdasdfasdfasdf.onion'],
hostname: 'adjective-noun',
lanAddress: 'https://adjective-noun.local',
rootCa: encodeBase64(rootCA),
}
default:
return null
if (this.statusIndex === 1) {
return { status: 'needs-install', keyboard: null }
// return {
// status: 'incomplete',
// attach: false,
// guid: 'mock-data-guid',
// keyboard: null,
// }
}
if (this.statusIndex > 3) {
return { status: 'complete' }
}
return {
status: 'running',
progress: PROGRESS,
guid: 'progress-guid',
}
}
async getPubKey(): Promise<void> {
await pauseFor(1000)
// randomly generated
// const keystore = jose.JWK.createKeyStore()
// this.pubkey = await keystore.generate('EC', 'P-256')
// generated from backend
await pauseFor(300)
this.pubkey = await jose.JWK.asKey({
kty: 'EC',
crv: 'P-256',
@@ -185,88 +100,28 @@ export class MockApiService extends ApiService {
})
}
async getDrives(): Promise<DiskListResponse> {
await pauseFor(1000)
return [
{
logicalname: '/dev/nvme0n1p3',
vendor: 'Unknown Vendor',
model: 'Samsung SSD - 970 EVO Plus 2TB',
partitions: [
{
logicalname: 'pabcd',
label: null,
capacity: 1979120929996,
used: null,
startOs: {
'1234-5678-9876-5432': {
hostname: 'adjective-noun',
version: '0.2.17',
timestamp: new Date().toISOString(),
passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
wrappedKey: null,
},
},
guid: null,
},
],
capacity: 1979120929996,
guid: 'uuid-uuid-uuid-uuid',
},
{
logicalname: 'dcba',
vendor: 'CT1000MX',
model: '500SSD1',
partitions: [
{
logicalname: 'pbcba',
label: null,
capacity: 73264762332,
used: null,
startOs: {
'1234-5678-9876-5432': {
hostname: 'adjective-noun',
version: '0.2.17',
timestamp: new Date().toISOString(),
passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
wrappedKey: null,
},
},
guid: null,
},
],
capacity: 1000190509056,
guid: null,
},
{
logicalname: '/dev/sda',
vendor: 'ASMT',
model: '2115',
partitions: [
{
logicalname: 'pbcba',
label: null,
capacity: 73264762332,
used: null,
startOs: {
'1234-5678-9876-5432': {
hostname: 'adjective-noun',
version: '0.2.17',
timestamp: new Date().toISOString(),
passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
wrappedKey: null,
},
},
guid: 'guid-guid-guid-guid',
},
],
capacity: 1000190509,
guid: null,
},
]
async setKeyboard(_params: FullKeyboard): Promise<null> {
await pauseFor(300)
return null
}
async setLanguage(params: SetLanguageParams): Promise<null> {
await pauseFor(300)
return null
}
async getDisks(): Promise<DiskInfo[]> {
await pauseFor(500)
return MOCK_DISKS
}
async installOs(params: InstallOsParams): Promise<InstallOsRes> {
await pauseFor(2000)
this.installCompleted = true
return {
guid: 'mock-data-guid',
attach: !params.dataDrive.wipe,
}
}
async verifyCifs(
@@ -282,21 +137,29 @@ export class MockApiService extends ApiService {
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
wrappedKey: '',
},
'9876-5432-1234-5671': {
hostname: 'adjective-noun',
version: '0.3.6',
timestamp: new Date().toISOString(),
passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
wrappedKey: '',
},
}
}
async attach(params: T.AttachParams): Promise<T.SetupProgress> {
async attach(params: AttachParams): Promise<T.SetupProgress> {
await pauseFor(1000)
this.statusIndex = 1 // Jump to running state
return {
progress: PROGRESS,
guid: 'progress-guid',
}
}
async execute(setupInfo: T.SetupExecuteParams): Promise<T.SetupProgress> {
async execute(params: SetupExecuteParams): Promise<T.SetupProgress> {
await pauseFor(1000)
this.statusIndex = 1 // Jump to running state
return {
progress: PROGRESS,
guid: 'progress-guid',
@@ -304,33 +167,113 @@ export class MockApiService extends ApiService {
}
async initFollowLogs(): Promise<FollowLogsRes> {
await pauseFor(1000)
await pauseFor(500)
return {
startCursor: 'fakestartcursor',
guid: 'logs-guid',
}
}
async complete(): Promise<T.SetupResult> {
await pauseFor(1000)
async complete(): Promise<SetupCompleteRes> {
await pauseFor(500)
return {
torAddresses: ['https://asdafsadasdasasdasdfasdfasdf.onion'],
hostname: 'adjective-noun',
lanAddress: 'https://adjective-noun.local',
rootCa: encodeBase64(rootCA),
rootCa: encodeBase64(ROOT_CA),
needsRestart: this.installCompleted,
}
}
async exit(): Promise<void> {
await pauseFor(1000)
await pauseFor(500)
}
async shutdown(): Promise<void> {
await pauseFor(500)
}
async restart(): Promise<void> {
await pauseFor(1000)
await pauseFor(500)
}
}
const rootCA = `-----BEGIN CERTIFICATE-----
const MOCK_DISKS: DiskInfo[] = [
{
logicalname: '/dev/sda',
vendor: 'Samsung',
model: 'SSD 970 EVO Plus',
partitions: [
{
logicalname: '/dev/sda1',
label: null,
capacity: 500000000000,
used: null,
startOs: {},
guid: null,
},
],
capacity: 500000000000,
guid: null,
},
{
logicalname: '/dev/sdb',
vendor: 'Crucial',
model: 'MX500',
partitions: [
{
logicalname: '/dev/sdb1',
label: null,
capacity: 1000000000000,
used: null,
startOs: {
'1234-5678-9876-5432': {
hostname: 'existing-server',
version: '0.3.6',
timestamp: new Date().toISOString(),
passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
wrappedKey: null,
},
},
guid: 'existing-guid',
},
],
capacity: 1000000000000,
guid: 'existing-guid',
},
{
logicalname: '/dev/sdc',
vendor: 'WD',
model: 'Blue SN570',
partitions: [
{
logicalname: '/dev/sdc1',
label: 'Backup',
capacity: 2000000000000,
used: 500000000000,
startOs: {
'backup-server-id': {
hostname: 'backup-server',
version: '0.3.5',
timestamp: new Date(Date.now() - 86400000).toISOString(),
passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
wrappedKey: '',
},
},
guid: null,
},
],
capacity: 2000000000000,
guid: null,
},
]
const PROGRESS: T.FullProgress = {
overall: null,
phases: [],
}
const ROOT_CA = `-----BEGIN CERTIFICATE-----
MIIDpzCCAo+gAwIBAgIRAIIuOarlQETlUQEOZJGZYdIwDQYJKoZIhvcNAQELBQAw
bTELMAkGA1UEBhMCVVMxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEOMAwGA1UECwwF
U2FsZXMxCzAJBgNVBAgMAldBMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20xEDAO
@@ -352,8 +295,3 @@ Rf3ZOPm9QP92YpWyYDkfAU04xdDo1vR0MYjKPkl4LjRqSU/tcCJnPMbJiwq+bWpX
2WJoEBXB/p15Kn6JxjI0ze2SnSI48JZ8it4fvxrhOo0VoLNIuCuNXJOwU17Rdl1W
YJidaq7je6k18AdgPA0Kh8y1XtfUH3fTaVw4
-----END CERTIFICATE-----`
const PROGRESS = {
overall: null,
phases: [],
}

View File

@@ -1,6 +1,28 @@
import { inject, Injectable } from '@angular/core'
import { ApiService } from './api.service'
import { T } from '@start9labs/start-sdk'
import { ApiService } from './api.service'
export type SetupType = 'fresh' | 'restore' | 'attach' | 'transfer'
export type RecoverySource =
| {
type: 'migrate'
guid: string
}
| {
type: 'backup'
target:
| { type: 'disk'; logicalname: string }
| {
type: 'cifs'
hostname: string
path: string
username: string
password: string | null
}
serverId: string
password: string // plaintext, will be encrypted before sending
}
@Injectable({
providedIn: 'root',
@@ -8,34 +30,68 @@ import { T } from '@start9labs/start-sdk'
export class StateService {
private readonly api = inject(ApiService)
kiosk?: boolean
setupType?: 'fresh' | 'restore' | 'attach' | 'transfer'
recoverySource?: T.RecoverySource<string>
// Determined at app init
kiosk = false
async importDrive(guid: string, password: string): Promise<void> {
// Set during install flow, or loaded from status response
language = ''
keyboard = ''
// From install response or status response (incomplete)
dataDriveGuid = ''
attach = false
// Set during setup flow
setupType?: SetupType
recoverySource?: RecoverySource
/**
* Called for attach flow (existing data drive)
*/
async attachDrive(password: string | null): Promise<void> {
await this.api.attach({
guid,
startOsPassword: await this.api.encrypt(password),
kiosk: this.kiosk,
guid: this.dataDriveGuid,
password: password ? await this.api.encrypt(password) : null,
})
}
async setupEmbassy(
storageLogicalname: string,
password: string,
): Promise<void> {
/**
* Called for fresh, restore, and transfer flows
* password is required for fresh, optional for restore/transfer
*/
async executeSetup(password: string | null): Promise<void> {
let recoverySource: T.RecoverySource<T.EncryptedWire> | null = null
if (this.recoverySource) {
if (this.recoverySource.type === 'migrate') {
recoverySource = this.recoverySource
} else {
// backup type - need to encrypt the backup password
recoverySource = {
type: 'backup',
target: this.recoverySource.target,
serverId: this.recoverySource.serverId,
password: await this.api.encrypt(this.recoverySource.password),
}
}
}
await this.api.execute({
startOsLogicalname: storageLogicalname,
startOsPassword: await this.api.encrypt(password),
recoverySource: this.recoverySource
? this.recoverySource.type === 'migrate'
? this.recoverySource
: {
...this.recoverySource,
password: await this.api.encrypt(this.recoverySource.password),
}
: null,
kiosk: this.kiosk,
guid: this.dataDriveGuid,
password: password ? await this.api.encrypt(password) : null,
recoverySource,
})
}
/**
* Reset state for a fresh start
*/
reset(): void {
this.language = ''
this.keyboard = ''
this.dataDriveGuid = ''
this.attach = false
this.setupType = undefined
this.recoverySource = undefined
}
}