Feature/cloud backups (#889)

* cifs for cloud backups on lan

* password spelling fix

* fix spelling and fix rpc method

* fix other methods

* remove old code and rename method

* add support for cifs backup targets

wip

cifs api

simplify idiom

add doc comment

wip

wip

should work™

* add password hash to server info

* fix type

* fix types for cifs

* minor fixes for cifs feature

* fix rpc structure

* fix copy, address some TODOs

* add subcommand

* backup path and navigation

* wizard edits

* rebased success page

* wiz conflicts resolved

* current change actually

* only unsub if done

* no fileter if necessary

* fix copy for cifs old password

* setup complete (#913)

* setup complete

* minor fixes

* setup.complete

* complete bool

* setup-wizard: complete boolean

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
Co-authored-by: Drew Ansbacher <drew.ansbacher@spiredigital.com>
Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
This commit is contained in:
Aiden McClelland
2021-12-07 11:51:04 -07:00
parent 6ee0bf8636
commit e6fb74a800
140 changed files with 3968 additions and 2399 deletions

View File

@@ -975,50 +975,54 @@ export module Mock {
'signal-strength': 50,
}
export const Drives: RR.GetDrivesRes = [
{
logicalname: '/dev/sda',
model: null,
vendor: 'SSK',
partitions: [
{
logicalname: 'sdba1',
label: 'Matt Stuff',
capacity: 1000000000000,
used: 0,
'embassy-os': null,
},
],
capacity: 1000000000000,
guid: 'asdfasdf',
export const BackupTargets: RR.GetBackupTargetsRes = {
'hsbdjhasbasda': {
type: 'cifs',
hostname: 'smb://192.169.10.0',
path: '/Desktop/embassy-backups',
username: 'TestUser',
mountable: false,
'embassy-os': {
version: '0.3.0',
full: true,
'password-hash': '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNK',
'wrapped-key': '',
},
},
{
logicalname: '/dev/sdb',
model: 'JMS567 SATA 6Gb/s bridge',
vendor: 'Samsung',
partitions: [
{
logicalname: 'sdba1',
label: 'Partition 1',
capacity: 1000000000,
used: 1000000000,
'embassy-os': {
version: '0.3.0',
full: true,
},
},
{
logicalname: 'sdba2',
label: 'Partition 2',
capacity: 900000000,
used: 300000000,
'embassy-os': null,
},
],
capacity: 10000000000,
guid: null,
// 'ftcvewdnkemfksdm': {
// type: 'disk',
// logicalname: 'sdba1',
// label: 'Matt Stuff',
// capacity: 1000000000000,
// used: 0,
// model: 'Evo SATA 2.5',
// vendor: 'Samsung',
// 'embassy-os': null,
// },
'csgashbdjkasnd': {
type: 'cifs',
hostname: 'smb://192.169.10.0',
path: '/Desktop/embassy-backups-2',
username: 'TestUser',
mountable: true,
'embassy-os': null,
},
]
// 'powjefhjbnwhdva': {
// type: 'disk',
// logicalname: 'sdba1',
// label: 'Another Drive',
// capacity: 2000000000000,
// used: 100000000000,
// model: null,
// vendor: 'SSK',
// 'embassy-os': {
// version: '0.3.0',
// full: true,
// 'password-hash': '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
// 'wrapped-key': '',
// },
// },
}
export const BackupInfo: RR.GetBackupInfoRes = {
version: '0.3.0',

View File

@@ -118,17 +118,33 @@ export module RR {
// backup
export type CreateBackupReq = WithExpire<{ logicalname: string, password: string }> // backup.create
export type CreateBackupRes = WithRevision<null>
export type GetBackupTargetsReq = { } // backup.target.list
export type GetBackupTargetsRes = { [id: string]: BackupTarget }
// drive
export type AddBackupTargetReq = { // backup.target.cifs.add
hostname: string
path: string
username: string
password: string | null
}
export type AddBackupTargetRes = { [id: string]: CifsBackupTarget }
export type GetDrivesReq = { } // disk.list
export type GetDrivesRes = DriveInfo[]
export type UpdateBackupTargetReq = AddBackupTargetReq & { id: string } // backup.target.cifs.update
export type UpdateBackupTargetRes = AddBackupTargetRes
export type GetBackupInfoReq = { logicalname: string, password: string } // disk.backup-info
export type RemoveBackupTargetReq = { id: string } // backup.target.cifs.remove
export type RemoveBackupTargetRes = null
export type GetBackupInfoReq = { 'target-id': string, password: string } // backup.target.info
export type GetBackupInfoRes = BackupInfo
export type CreateBackupReq = WithExpire<{ // backup.create
'target-id': string
'old-password': string | null
password: string
}>
export type CreateBackupRes = WithRevision<null>
// package
export type GetPackagePropertiesReq = { id: string } // package.properties
@@ -157,7 +173,12 @@ export module RR {
export type SetPackageConfigReq = WithExpire<DrySetPackageConfigReq> // package.config.set
export type SetPackageConfigRes = WithRevision<null>
export type RestorePackagesReq = WithExpire<{ ids: string[], logicalname: string, password: string }> // package.backup.restore
export type RestorePackagesReq = WithExpire<{ // package.backup.restore
ids: string[]
'target-id': string
'old-password': string | null,
password: string
}>
export type RestorePackagesRes = WithRevision<null>
export type ExecutePackageActionReq = { id: string, 'action-id': string, input?: object } // package.action
@@ -200,8 +221,8 @@ export module RR {
export type GetMarketplacePackagesReq = {
ids?: { id: string, version: string }[]
// iff !id
'eos-version-compat': string
// iff !ids
category?: string
query?: string
page?: string
@@ -294,7 +315,51 @@ export interface SessionMetadata {
export type PlatformType = 'cli' | 'ios' | 'ipad' | 'iphone' | 'android' | 'phablet' | 'tablet' | 'cordova' | 'capacitor' | 'electron' | 'pwa' | 'mobile' | 'mobileweb' | 'desktop' | 'hybrid'
export interface DriveInfo {
export type BackupTarget = DiskBackupTarget | CifsBackupTarget
export interface EmbassyOSRecoveryInfo {
version: string
full: boolean
'password-hash': string | null
'wrapped-key': string | null
}
export interface DiskBackupTarget {
type: 'disk'
vendor: string | null
model: string | null
logicalname: string | null
label: string | null
capacity: number
used: number | null
'embassy-os': EmbassyOSRecoveryInfo | null
}
export interface CifsBackupTarget {
type: 'cifs'
hostname: string
path: string
username: string
mountable: boolean
'embassy-os': EmbassyOSRecoveryInfo | null
}
export type RecoverySource = DiskRecoverySource | CifsRecoverySource
export interface DiskRecoverySource {
type: 'disk'
logicalname: string // partition logicalname
}
export interface CifsRecoverySource {
type: 'cifs'
hostname: string
path: string
username: string
password: string
}
export interface DiskInfo {
logicalname: string
vendor: string | null
model: string | null
@@ -308,10 +373,10 @@ export interface PartitionInfo {
label: string | null
capacity: number
used: number | null
'embassy-os': EmbassyOsDriveInfo | null
'embassy-os': EmbassyOsDiskInfo | null
}
export interface EmbassyOsDriveInfo {
export interface EmbassyOsDiskInfo {
version: string
full: boolean
}

View File

@@ -121,17 +121,21 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
// backup
abstract getBackupTargets (params: RR.GetBackupTargetsReq): Promise<RR.GetBackupTargetsRes>
abstract addBackupTarget (params: RR.AddBackupTargetReq): Promise<RR.AddBackupTargetRes>
abstract updateBackupTarget (params: RR.UpdateBackupTargetReq): Promise<RR.UpdateBackupTargetRes>
abstract removeBackupTarget (params: RR.RemoveBackupTargetReq): Promise<RR.RemoveBackupTargetRes>
abstract getBackupInfo (params: RR.GetBackupInfoReq): Promise<RR.GetBackupInfoRes>
protected abstract createBackupRaw (params: RR.CreateBackupReq): Promise<RR.CreateBackupRes>
createBackup = (params: RR.CreateBackupReq) => this.syncResponse(
() => this.createBackupRaw(params),
)()
// drive
abstract getDrives (params: RR.GetDrivesReq): Promise<RR.GetDrivesRes>
abstract getBackupInfo (params: RR.GetBackupInfoReq): Promise<RR.GetBackupInfoRes>
// package
abstract getPackageProperties (params: RR.GetPackagePropertiesReq): Promise<RR.GetPackagePropertiesRes<any>['data']>

View File

@@ -90,7 +90,7 @@ export class LiveApiService extends ApiService {
})
}
async getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise <RR.GetMarketplaceDataRes> {
async getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes> {
return this.http.httpRequest({
method: Method.GET,
url: '/marketplace/package/data',
@@ -98,7 +98,7 @@ export class LiveApiService extends ApiService {
})
}
async getMarketplacePkgs (params: RR.GetMarketplacePackagesReq): Promise <RR.GetMarketplacePackagesRes> {
async getMarketplacePkgs (params: RR.GetMarketplacePackagesReq): Promise<RR.GetMarketplacePackagesRes> {
if (params.query) params.category = undefined
return this.http.httpRequest({
method: Method.GET,
@@ -110,7 +110,7 @@ export class LiveApiService extends ApiService {
})
}
async getReleaseNotes (params: RR.GetReleaseNotesReq): Promise <RR.GetReleaseNotesRes> {
async getReleaseNotes (params: RR.GetReleaseNotesReq): Promise<RR.GetReleaseNotesRes> {
return this.http.httpRequest({
method: Method.GET,
url: '/marketplace/package/release-notes',
@@ -118,7 +118,7 @@ export class LiveApiService extends ApiService {
})
}
async getLatestVersion (params: RR.GetLatestVersionReq): Promise <RR.GetLatestVersionRes> {
async getLatestVersion (params: RR.GetLatestVersionReq): Promise<RR.GetLatestVersionRes> {
return this.http.httpRequest({
method: Method.GET,
url: '/marketplace/latest-version',
@@ -137,138 +137,149 @@ export class LiveApiService extends ApiService {
// notification
async getNotificationsRaw (params: RR.GetNotificationsReq): Promise <RR.GetNotificationsRes> {
async getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes> {
return this.http.rpcRequest({ method: 'notification.list', params })
}
async deleteNotification (params: RR.DeleteNotificationReq): Promise <RR.DeleteNotificationRes> {
async deleteNotification (params: RR.DeleteNotificationReq): Promise<RR.DeleteNotificationRes> {
return this.http.rpcRequest({ method: 'notification.delete', params })
}
async deleteAllNotifications (params: RR.DeleteAllNotificationsReq): Promise <RR.DeleteAllNotificationsRes> {
async deleteAllNotifications (params: RR.DeleteAllNotificationsReq): Promise<RR.DeleteAllNotificationsRes> {
return this.http.rpcRequest({ method: 'notification.delete-before', params })
}
// wifi
async getWifi (params: RR.GetWifiReq, timeout?: number): Promise <RR.GetWifiRes> {
async getWifi (params: RR.GetWifiReq, timeout?: number): Promise<RR.GetWifiRes> {
return this.http.rpcRequest({ method: 'wifi.get', params, timeout })
}
async setWifiCountry (params: RR.SetWifiCountryReq): Promise <RR.SetWifiCountryRes> {
async setWifiCountry (params: RR.SetWifiCountryReq): Promise<RR.SetWifiCountryRes> {
return this.http.rpcRequest({ method: 'wifi.country.set', params })
}
async addWifi (params: RR.AddWifiReq): Promise <RR.AddWifiRes> {
async addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
return this.http.rpcRequest({ method: 'wifi.add', params })
}
async connectWifi (params: RR.ConnectWifiReq): Promise <RR.ConnectWifiRes> {
async connectWifi (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
return this.http.rpcRequest({ method: 'wifi.connect', params })
}
async deleteWifi (params: RR.DeleteWifiReq): Promise <RR.DeleteWifiRes> {
async deleteWifi (params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
return this.http.rpcRequest({ method: 'wifi.delete', params })
}
// ssh
async getSshKeys (params: RR.GetSSHKeysReq): Promise <RR.GetSSHKeysRes> {
async getSshKeys (params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
return this.http.rpcRequest({ method: 'ssh.list', params })
}
async addSshKey (params: RR.AddSSHKeyReq): Promise <RR.AddSSHKeyRes> {
async addSshKey (params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes> {
return this.http.rpcRequest({ method: 'ssh.add', params })
}
async deleteSshKey (params: RR.DeleteSSHKeyReq): Promise <RR.DeleteSSHKeyRes> {
async deleteSshKey (params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes> {
return this.http.rpcRequest({ method: 'ssh.delete', params })
}
// backup
async getBackupTargets (params: RR.GetBackupTargetsReq): Promise<RR.GetBackupTargetsRes> {
return this.http.rpcRequest({ method: 'backup.target.list', params })
}
async addBackupTarget (params: RR.AddBackupTargetReq): Promise<RR.AddBackupTargetRes> {
params.path = params.path.replace('/\\/g', '/')
return this.http.rpcRequest({ method: 'backup.target.cifs.add', params })
}
async updateBackupTarget (params: RR.UpdateBackupTargetReq): Promise<RR.UpdateBackupTargetRes> {
return this.http.rpcRequest({ method: 'backup.target.cifs.update', params })
}
async removeBackupTarget (params: RR.RemoveBackupTargetReq): Promise<RR.RemoveBackupTargetRes> {
return this.http.rpcRequest({ method: 'backup.target.cifs.remove', params })
}
async getBackupInfo (params: RR.GetBackupInfoReq): Promise<RR.GetBackupInfoRes> {
return this.http.rpcRequest({ method: 'backup.target.info', params })
}
async createBackupRaw (params: RR.CreateBackupReq): Promise <RR.CreateBackupRes> {
return this.http.rpcRequest({ method: 'backup.create', params })
}
// drives
getDrives (params: RR.GetDrivesReq): Promise <RR.GetDrivesRes> {
return this.http.rpcRequest({ method: 'disk.list', params })
}
getBackupInfo (params: RR.GetBackupInfoReq): Promise <RR.GetBackupInfoRes> {
return this.http.rpcRequest({ method: 'disk.backup-info', params })
}
// package
async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise <RR.GetPackagePropertiesRes < any > ['data'] > {
async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise<RR.GetPackagePropertiesRes < any > ['data'] > {
return this.http.rpcRequest({ method: 'package.properties', params })
.then(parsePropertiesPermissive)
}
async getPackageLogs (params: RR.GetPackageLogsReq): Promise <RR.GetPackageLogsRes> {
async getPackageLogs (params: RR.GetPackageLogsReq): Promise<RR.GetPackageLogsRes> {
return this.http.rpcRequest( { method: 'package.logs', params })
}
async getPkgMetrics (params: RR.GetPackageMetricsReq): Promise <RR.GetPackageMetricsRes> {
async getPkgMetrics (params: RR.GetPackageMetricsReq): Promise<RR.GetPackageMetricsRes> {
return this.http.rpcRequest({ method: 'package.metrics', params })
}
async installPackageRaw (params: RR.InstallPackageReq): Promise <RR.InstallPackageRes> {
async installPackageRaw (params: RR.InstallPackageReq): Promise<RR.InstallPackageRes> {
return this.http.rpcRequest({ method: 'package.install', params })
}
async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise <RR.DryUpdatePackageRes> {
async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes> {
return this.http.rpcRequest({ method: 'package.update.dry', params })
}
async getPackageConfig (params: RR.GetPackageConfigReq): Promise <RR.GetPackageConfigRes> {
async getPackageConfig (params: RR.GetPackageConfigReq): Promise<RR.GetPackageConfigRes> {
return this.http.rpcRequest({ method: 'package.config.get', params })
}
async drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise <RR.DrySetPackageConfigRes> {
async drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise<RR.DrySetPackageConfigRes> {
return this.http.rpcRequest({ method: 'package.config.set.dry', params })
}
async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise <RR.SetPackageConfigRes> {
async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise<RR.SetPackageConfigRes> {
return this.http.rpcRequest({ method: 'package.config.set', params })
}
async restorePackagesRaw (params: RR.RestorePackagesReq): Promise <RR.RestorePackagesRes> {
async restorePackagesRaw (params: RR.RestorePackagesReq): Promise<RR.RestorePackagesRes> {
return this.http.rpcRequest({ method: 'package.backup.restore', params })
}
async executePackageAction (params: RR.ExecutePackageActionReq): Promise <RR.ExecutePackageActionRes> {
async executePackageAction (params: RR.ExecutePackageActionReq): Promise<RR.ExecutePackageActionRes> {
return this.http.rpcRequest({ method: 'package.action', params })
}
async startPackageRaw (params: RR.StartPackageReq): Promise <RR.StartPackageRes> {
async startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
return this.http.rpcRequest({ method: 'package.start', params })
}
async dryStopPackage (params: RR.DryStopPackageReq): Promise <RR.DryStopPackageRes> {
async dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes> {
return this.http.rpcRequest({ method: 'package.stop.dry', params })
}
async stopPackageRaw (params: RR.StopPackageReq): Promise <RR.StopPackageRes> {
async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
return this.http.rpcRequest({ method: 'package.stop', params })
}
async dryUninstallPackage (params: RR.DryUninstallPackageReq): Promise <RR.DryUninstallPackageRes> {
async dryUninstallPackage (params: RR.DryUninstallPackageReq): Promise<RR.DryUninstallPackageRes> {
return this.http.rpcRequest({ method: 'package.uninstall.dry', params })
}
async deleteRecoveredPackageRaw (params: RR.DeleteRecoveredPackageReq): Promise <RR.DeleteRecoveredPackageRes> {
async deleteRecoveredPackageRaw (params: RR.DeleteRecoveredPackageReq): Promise<RR.DeleteRecoveredPackageRes> {
return this.http.rpcRequest({ method: 'package.delete-recovered', params })
}
async uninstallPackageRaw (params: RR.UninstallPackageReq): Promise <RR.UninstallPackageRes> {
async uninstallPackageRaw (params: RR.UninstallPackageReq): Promise<RR.UninstallPackageRes> {
return this.http.rpcRequest({ method: 'package.uninstall', params })
}
async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise <RR.DryConfigureDependencyRes> {
async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise<RR.DryConfigureDependencyRes> {
return this.http.rpcRequest({ method: 'package.dependency.configure.dry', params })
}
}

View File

@@ -3,7 +3,7 @@ import { pauseFor } from '../../util/misc.util'
import { ApiService } from './embassy-api.service'
import { PatchOp, Update, Operation, RemoveOperation } from 'patch-db-client'
import { DataModel, DependencyErrorType, InstallProgress, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
import { Log, RR, WithRevision } from './api.types'
import { CifsBackupTarget, Log, RR, WithRevision } from './api.types'
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { Mock } from './api.fixures'
import markdown from 'raw-loader!src/assets/markdown/md-sample.md'
@@ -277,6 +277,49 @@ export class MockApiService extends ApiService {
// backup
async getBackupTargets (params: RR.GetBackupTargetsReq): Promise<RR.GetBackupTargetsRes> {
await pauseFor(2000)
return Mock.BackupTargets
}
async addBackupTarget (params: RR.AddBackupTargetReq): Promise<RR.AddBackupTargetRes> {
await pauseFor(2000)
const { hostname, path, username } = params
return {
'latfgvwdbhjsndmk': {
type: 'cifs',
hostname,
path: path.replace(/\\/g, '/'),
username,
mountable: true,
'embassy-os': null,
},
}
}
async updateBackupTarget (params: RR.UpdateBackupTargetReq): Promise<RR.UpdateBackupTargetRes> {
await pauseFor(2000)
const { id, hostname, path, username } = params
return {
[id]: {
...Mock.BackupTargets[id] as CifsBackupTarget,
hostname,
path,
username,
},
}
}
async removeBackupTarget (params: RR.RemoveBackupTargetReq): Promise<RR.RemoveBackupTargetRes> {
await pauseFor(2000)
return null
}
async getBackupInfo (params: RR.GetBackupInfoReq): Promise<RR.GetBackupInfoRes> {
await pauseFor(2000)
return Mock.BackupInfo
}
async createBackupRaw (params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> {
await pauseFor(2000)
const path = '/server-info/status'
@@ -324,18 +367,6 @@ export class MockApiService extends ApiService {
return this.withRevision(originalPatch)
}
// drives
async getDrives (params: RR.GetDrivesReq): Promise<RR.GetDrivesRes> {
await pauseFor(2000)
return Mock.Drives
}
async getBackupInfo (params: RR.GetBackupInfoReq): Promise<RR.GetBackupInfoRes> {
await pauseFor(2000)
return Mock.BackupInfo
}
// package
async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise<RR.GetPackagePropertiesRes<any>['data']> {

View File

@@ -19,7 +19,7 @@ export const mockPatchData: DataModel = {
'package-marketplace': null,
'share-stats': false,
'unread-notification-count': 4,
// 'password-hash': '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'password-hash': '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'eos-version-compat': '>=0.3.0',
},
'recovered-packages': {