Files
cloudron-box/src/js/restore.js

406 lines
18 KiB
JavaScript
Raw Normal View History

2018-01-22 13:01:38 -08:00
'use strict';
/* global angular */
2018-01-22 13:01:38 -08:00
/* global tld */
/* global $ */
2018-01-22 13:01:38 -08:00
// create main application module
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ui.bootstrap']);
2018-01-22 13:01:38 -08:00
app.filter('zoneName', function () {
return function (domain) {
return tld.getDomain(domain);
};
});
app.controller('RestoreController', ['$scope', 'Client', function ($scope, Client) {
2018-01-22 13:01:38 -08:00
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
$scope.client = Client;
2018-01-22 13:01:38 -08:00
$scope.busy = false;
$scope.error = {};
$scope.message = ''; // progress
// variables here have to match the import config logic!
2018-01-22 13:01:38 -08:00
$scope.provider = '';
$scope.bucket = '';
$scope.prefix = '';
2021-01-07 19:41:39 +01:00
$scope.mountPoint = '';
2018-01-22 13:01:38 -08:00
$scope.accessKeyId = '';
$scope.secretAccessKey = '';
$scope.gcsKey = { keyFileName: '', content: '' };
$scope.region = '';
$scope.endpoint = '';
$scope.backupFolder = '';
$scope.backupId = '';
$scope.instanceId = '';
$scope.acceptSelfSignedCerts = false;
$scope.format = 'tgz';
2019-11-11 11:07:52 -08:00
$scope.advancedVisible = false;
2020-05-12 22:24:03 -07:00
$scope.password = '';
$scope.encrypted = false; // only used if a backup config contains that flag
$scope.setupToken = '';
2019-11-11 11:07:52 -08:00
$scope.sysinfo = {
provider: 'generic',
ip: '',
ifname: ''
};
$scope.sysinfoProvider = [
{ name: 'Public IP', value: 'generic' },
{ name: 'Static IP Address', value: 'fixed' },
{ name: 'Network Interface', value: 'network-interface' }
];
$scope.prettySysinfoProviderName = function (provider) {
switch (provider) {
case 'generic': return 'Public IP';
case 'fixed': return 'Static IP Address';
case 'network-interface': return 'Network Interface';
default: return 'Unknown';
}
};
2018-01-22 13:01:38 -08:00
// List is from http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
$scope.s3Regions = [
{ name: 'Asia Pacific (Mumbai)', value: 'ap-south-1' },
{ name: 'Asia Pacific (Osaka-Local)', value: 'ap-northeast-3' },
2018-01-22 13:01:38 -08:00
{ name: 'Asia Pacific (Seoul)', value: 'ap-northeast-2' },
{ name: 'Asia Pacific (Singapore)', value: 'ap-southeast-1' },
{ name: 'Asia Pacific (Sydney)', value: 'ap-southeast-2' },
{ name: 'Asia Pacific (Tokyo)', value: 'ap-northeast-1' },
{ name: 'Canada (Central)', value: 'ca-central-1' },
2020-12-02 18:20:58 +01:00
{ name: 'China (Beijing)', value: 'cn-north-1' },
{ name: 'China (Ningxia)', value: 'cn-northwest-1' },
2018-01-22 13:01:38 -08:00
{ name: 'EU (Frankfurt)', value: 'eu-central-1' },
{ name: 'EU (Ireland)', value: 'eu-west-1' },
{ name: 'EU (London)', value: 'eu-west-2' },
{ name: 'EU (Paris)', value: 'eu-west-3' },
{ name: 'EU (Stockholm)', value: 'eu-north-1' },
2018-01-22 13:01:38 -08:00
{ name: 'South America (São Paulo)', value: 'sa-east-1' },
{ name: 'US East (N. Virginia)', value: 'us-east-1' },
{ name: 'US East (Ohio)', value: 'us-east-2' },
{ name: 'US West (N. California)', value: 'us-west-1' },
{ name: 'US West (Oregon)', value: 'us-west-2' },
];
$scope.doSpacesRegions = [
{ name: 'AMS3', value: 'https://ams3.digitaloceanspaces.com' },
{ name: 'FRA1', value: 'https://fra1.digitaloceanspaces.com' },
2018-02-03 18:42:13 -08:00
{ name: 'NYC3', value: 'https://nyc3.digitaloceanspaces.com' },
2018-09-26 12:02:03 -07:00
{ name: 'SFO2', value: 'https://sfo2.digitaloceanspaces.com' },
2018-02-03 18:42:13 -08:00
{ name: 'SGP1', value: 'https://sgp1.digitaloceanspaces.com' }
2018-01-22 13:01:38 -08:00
];
$scope.exoscaleSosRegions = [
2019-06-21 10:44:24 -07:00
{ name: 'AT-VIE-1', value: 'https://sos-at-vie-1.exo.io' },
{ name: 'CH-DK-2', value: 'https://sos-ch-dk-2.exo.io' },
{ name: 'CH-GVA-2', value: 'https://sos-ch-gva-2.exo.io' },
{ name: 'DE-FRA-1', value: 'https://sos-de-fra-1.exo.io' },
];
2019-06-21 11:06:50 -07:00
// https://www.scaleway.com/docs/object-storage-feature/
$scope.scalewayRegions = [
{ name: 'FR-PAR', value: 'https://s3.fr-par.scw.cloud', region: 'fr-par' }, // default
{ name: 'NL-AMS', value: 'https://s3.nl-ams.scw.cloud', region: 'nl-ams' }
];
2020-02-26 09:08:34 -08:00
$scope.linodeRegions = [
{ name: 'Newark', value: 'us-east-1.linodeobjects.com', region: 'us-east-1' }, // default
2020-03-02 20:03:02 -08:00
{ name: 'Frankfurt', value: 'eu-central-1.linodeobjects.com', region: 'us-east-1' },
2020-09-02 19:35:02 -07:00
{ name: 'Singapore', value: 'ap-south-1.linodeobjects.com', region: 'us-east-1' },
2020-02-26 09:08:34 -08:00
];
2020-04-29 12:54:19 -07:00
$scope.ovhRegions = [
{ name: 'Beauharnois (BHS)', value: 'https://storage.bhs.cloud.ovh.net', region: 'bhs' }, // default
{ name: 'Frankfurt (DE)', value: 'https://storage.de.cloud.ovh.net', region: 'de' },
{ name: 'Gravelines (GRA)', value: 'https://storage.gra.cloud.ovh.net', region: 'gra' },
{ name: 'Strasbourg (SBG)', value: 'https://storage.sbg.cloud.ovh.net', region: 'sbg' },
{ name: 'London (UK)', value: 'https://storage.uk.cloud.ovh.net', region: 'uk' },
{ name: 'Sydney (SYD)', value: 'https://storage.syd.cloud.ovh.net', region: 'syd' },
{ name: 'Warsaw (WAW)', value: 'https://storage.waw.cloud.ovh.net', region: 'waw' },
2020-04-29 12:54:19 -07:00
];
// https://devops.ionos.com/api/s3/
$scope.ionosRegions = [
{ name: 'DE', value: 'https://s3-de-central.profitbricks.com', region: 's3-de-central' }, // default
];
2019-07-30 10:42:18 -07:00
$scope.wasabiRegions = [
{ name: 'EU Central 1', value: 'https://s3.eu-central-1.wasabisys.com' },
2020-05-19 14:52:40 +02:00
{ name: 'US East 1', value: 'https://s3.us-east-1.wasabisys.com' },
{ name: 'US East 2', value: 'https://s3.us-east-2.wasabisys.com ' },
2019-07-30 10:42:18 -07:00
{ name: 'US West 1', value: 'https://s3.us-west-1.wasabisys.com' }
];
2018-01-22 13:01:38 -08:00
$scope.storageProvider = [
{ name: 'Amazon S3', value: 's3' },
{ name: 'Backblaze B2 (S3 API)', value: 'backblaze-b2' },
2021-01-07 19:41:39 +01:00
{ name: 'CIFS Mount', value: 'cifs' },
2018-01-22 13:01:38 -08:00
{ name: 'DigitalOcean Spaces', value: 'digitalocean-spaces' },
{ name: 'Exoscale SOS', value: 'exoscale-sos' },
{ name: 'Filesystem', value: 'filesystem' },
{ name: 'Google Cloud Storage', value: 'gcs' },
2020-03-06 01:46:59 -08:00
{ name: 'Linode Object Storage', value: 'linode-objectstorage' },
2018-01-22 13:01:38 -08:00
{ name: 'Minio', value: 'minio' },
2021-01-07 19:41:39 +01:00
{ name: 'NFS Mount', value: 'nfs' },
2020-04-29 12:54:19 -07:00
{ name: 'OVH Object Storage', value: 'ovh-objectstorage' },
2018-01-22 13:01:38 -08:00
{ name: 'S3 API Compatible (v4)', value: 's3-v4-compat' },
2021-01-07 19:41:39 +01:00
{ name: 'Scaleway Object Storage', value: 'scaleway-objectstorage' },
{ name: 'SSHFS Mount', value: 'sshfs' },
2019-07-30 10:42:18 -07:00
{ name: 'Wasabi', value: 'wasabi' }
2018-01-22 13:01:38 -08:00
];
$scope.formats = [
{ name: 'Tarball (zipped)', value: 'tgz' },
{ name: 'rsync', value: 'rsync' }
];
$scope.s3like = function (provider) {
2019-07-30 10:42:18 -07:00
return provider === 's3' || provider === 'minio' || provider === 's3-v4-compat' || provider === 'exoscale-sos'
2020-02-26 09:08:34 -08:00
|| provider === 'digitalocean-spaces' || provider === 'wasabi' || provider === 'scaleway-objectstorage'
|| provider === 'linode-objectstorage' || provider === 'ovh-objectstorage' || provider === 'backblaze-b2'
|| provider === 'ionos-objectstorage';
2018-01-22 13:01:38 -08:00
};
2021-01-07 19:41:39 +01:00
$scope.mountlike = function (provider) {
return provider === 'sshfs' || provider === 'cifs' || provider === 'nfs';
};
2018-01-22 13:01:38 -08:00
$scope.restore = function () {
$scope.error = {};
$scope.busy = true;
var backupConfig = {
provider: $scope.provider,
format: $scope.format,
setupToken: $scope.setupToken
2018-01-22 13:01:38 -08:00
};
2020-05-12 22:24:03 -07:00
if ($scope.password) backupConfig.password = $scope.password;
2018-01-22 13:01:38 -08:00
// only set provider specific fields, this will clear them in the db
if ($scope.s3like(backupConfig.provider)) {
backupConfig.bucket = $scope.bucket;
backupConfig.prefix = $scope.prefix;
backupConfig.accessKeyId = $scope.accessKeyId;
backupConfig.secretAccessKey = $scope.secretAccessKey;
if ($scope.endpoint) backupConfig.endpoint = $scope.endpoint;
if (backupConfig.provider === 's3') {
if ($scope.region) backupConfig.region = $scope.region;
delete backupConfig.endpoint;
2018-01-22 13:01:38 -08:00
} else if (backupConfig.provider === 'minio' || backupConfig.provider === 's3-v4-compat') {
backupConfig.region = backupConfig.region || 'us-east-1';
2018-01-22 13:01:38 -08:00
backupConfig.acceptSelfSignedCerts = $scope.acceptSelfSignedCerts;
backupConfig.s3ForcePathStyle = true; // might want to expose this in the UI
2018-01-22 13:01:38 -08:00
} else if (backupConfig.provider === 'exoscale-sos') {
backupConfig.region = 'us-east-1';
backupConfig.signatureVersion = 'v4';
2019-07-30 10:42:18 -07:00
} else if (backupConfig.provider === 'wasabi') {
2020-05-19 14:52:40 +02:00
backupConfig.region = $scope.wasabiRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
2019-07-30 10:42:18 -07:00
backupConfig.signatureVersion = 'v4';
2019-06-21 11:06:50 -07:00
} else if (backupConfig.provider === 'scaleway-objectstorage') {
backupConfig.region = $scope.scalewayRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
backupConfig.signatureVersion = 'v4';
2020-02-26 09:08:34 -08:00
} else if (backupConfig.provider === 'linode-objectstorage') {
backupConfig.region = $scope.linodeRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
backupConfig.signatureVersion = 'v4';
2020-04-29 12:54:19 -07:00
} else if (backupConfig.provider === 'ovh-objectstorage') {
backupConfig.region = $scope.ovhRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
backupConfig.signatureVersion = 'v4';
} else if (backupConfig.provider === 'ionos-objectstorage') {
backupConfig.region = $scope.ionosRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
backupConfig.signatureVersion = 'v4';
2018-01-22 13:01:38 -08:00
} else if (backupConfig.provider === 'digitalocean-spaces') {
backupConfig.region = 'us-east-1';
}
} else if (backupConfig.provider === 'gcs') {
backupConfig.bucket = $scope.bucket;
backupConfig.prefix = $scope.prefix;
try {
var serviceAccountKey = JSON.parse($scope.gcsKey.content);
backupConfig.projectId = serviceAccountKey.project_id;
backupConfig.credentials = {
client_email: serviceAccountKey.client_email,
private_key: serviceAccountKey.private_key
};
if (!backupConfig.projectId || !backupConfig.credentials || !backupConfig.credentials.client_email || !backupConfig.credentials.private_key) {
throw 'fields_missing';
}
} catch (e) {
$scope.error.generic = 'Cannot parse Google Service Account Key: ' + e.message;
$scope.error.gcsKeyInput = true;
$scope.busy = false;
return;
}
2021-01-07 19:41:39 +01:00
} else if (backupConfig.provider === 'sshfs' || backupConfig.provider === 'cifs' || backupConfig.provider === 'nfs') {
backupConfig.mountPoint = $scope.mountPoint;
backupConfig.prefix = $scope.prefix;
2018-01-22 13:01:38 -08:00
} else if (backupConfig.provider === 'filesystem') {
backupConfig.backupFolder = $scope.backupFolder;
}
if ($scope.backupId.indexOf('/') === -1) {
$scope.error.generic = 'Backup id must include the directory path';
$scope.error.backupId = true;
$scope.busy = false;
return;
}
2019-11-11 16:21:27 -08:00
if ($scope.backupId.indexOf('box') === -1) {
$scope.error.generic = 'Backup id must contain "box"';
$scope.error.backupId = true;
$scope.busy = false;
return;
}
2018-01-22 13:01:38 -08:00
var version = $scope.backupId.match(/_v(\d+.\d+.\d+)/);
if (!version) {
$scope.error.generic = 'Backup id is missing version information';
$scope.error.backupId = true;
$scope.busy = false;
return;
}
2019-11-11 11:07:52 -08:00
var sysinfoConfig = {
provider: $scope.sysinfo.provider
};
if ($scope.sysinfo.provider === 'fixed') {
sysinfoConfig.ip = $scope.sysinfo.ip;
} else if ($scope.sysinfo.provider === 'network-interface') {
2019-11-11 16:06:29 -08:00
sysinfoConfig.ifname = $scope.sysinfo.ifname;
2019-11-11 11:07:52 -08:00
}
Client.restore(backupConfig, $scope.backupId.replace(/\.tar\.gz(\.enc)?$/, ''), version ? version[1] : '', sysinfoConfig, function (error) {
2018-01-22 13:01:38 -08:00
$scope.busy = false;
if (error) {
2018-09-10 10:36:32 -07:00
if (error.statusCode === 424) {
2018-01-22 13:01:38 -08:00
$scope.error.generic = error.message;
if (error.message.indexOf('AWS Access Key Id') !== -1) {
$scope.error.accessKeyId = true;
$scope.accessKeyId = '';
$scope.configureBackupForm.accessKeyId.$setPristine();
$('#inputConfigureBackupAccessKeyId').focus();
} else if (error.message.indexOf('not match the signature') !== -1 ) {
$scope.error.secretAccessKey = true;
$scope.secretAccessKey = '';
$scope.configureBackupForm.secretAccessKey.$setPristine();
$('#inputConfigureBackupSecretAccessKey').focus();
} else if (error.message.toLowerCase() === 'access denied') {
$scope.error.bucket = true;
$scope.bucket = '';
$scope.configureBackupForm.bucket.$setPristine();
$('#inputConfigureBackupBucket').focus();
} else if (error.message.indexOf('ECONNREFUSED') !== -1) {
$scope.error.generic = 'Unknown region';
$scope.error.region = true;
$scope.configureBackupForm.region.$setPristine();
$('#inputConfigureBackupDORegion').focus();
2018-01-22 13:01:38 -08:00
} else if (error.message.toLowerCase() === 'wrong region') {
$scope.error.generic = 'Wrong S3 Region';
$scope.error.region = true;
$scope.configureBackupForm.region.$setPristine();
$('#inputConfigureBackupS3Region').focus();
2018-01-22 13:01:38 -08:00
} else {
$('#inputConfigureBackupBucket').focus();
}
} else {
$scope.error.generic = error.message;
}
return;
}
waitForRestore();
});
2019-11-11 09:49:41 -08:00
};
2018-01-22 13:01:38 -08:00
function waitForRestore() {
$scope.busy = true;
Client.getStatus(function (error, status) {
2018-12-14 15:24:00 -08:00
if (!error && !status.restore.active) { // restore finished
if (status.restore.errorMessage) {
$scope.busy = false;
2018-12-14 15:24:00 -08:00
$scope.error.generic = status.restore.errorMessage;
} else { // restore worked, redirect to admin page
window.location.href = '/';
}
return;
2018-01-22 13:01:38 -08:00
}
2020-09-21 21:48:22 -07:00
if (!error) $scope.message = status.restore.message;
2018-01-22 13:01:38 -08:00
setTimeout(waitForRestore, 5000);
});
}
function readFileLocally(obj, file, fileName) {
return function (event) {
$scope.$apply(function () {
obj[file] = null;
obj[fileName] = event.target.files[0].name;
var reader = new FileReader();
reader.onload = function (result) {
if (!result.target || !result.target.result) return console.error('Unable to read local file');
obj[file] = result.target.result;
};
reader.readAsText(event.target.files[0]);
});
};
}
document.getElementById('gcsKeyFileInput').onchange = readFileLocally($scope.gcsKey, 'content', 'keyFileName');
document.getElementById('backupConfigFileInput').onchange = function (event) {
var reader = new FileReader();
reader.onload = function (result) {
if (!result.target || !result.target.result) return console.error('Unable to read backup config');
var backupConfig;
try {
backupConfig = JSON.parse(result.target.result);
} catch (e) {
console.error('Unable to parse backup config');
return;
}
$scope.$apply(function () {
// we assume property names match here, this does not yet work for gcs keys
Object.keys(backupConfig).forEach(function (k) {
if (k in $scope) $scope[k] = backupConfig[k];
});
});
};
reader.readAsText(event.target.files[0]);
};
function init() {
Client.getStatus(function (error, status) {
if (error) return Client.initError(error, init);
2018-01-22 13:01:38 -08:00
if (status.restore.active) return waitForRestore();
2018-01-22 13:01:38 -08:00
if (status.restore.errorMessage) $scope.error.generic = status.restore.errorMessage;
2018-08-05 23:30:22 -07:00
if (status.activated) {
window.location.href = '/';
return;
}
$scope.status = status;
$scope.instanceId = search.instanceId;
$scope.setupToken = search.setupToken;
$scope.initialized = true;
});
}
2018-01-22 13:01:38 -08:00
init();
2018-01-22 13:01:38 -08:00
}]);