diff --git a/dashboard/src/components/BackupProviderForm.vue b/dashboard/src/components/BackupProviderForm.vue
index 495e72ab5..7c07b38da 100644
--- a/dashboard/src/components/BackupProviderForm.vue
+++ b/dashboard/src/components/BackupProviderForm.vue
@@ -4,20 +4,26 @@ import { ref, onMounted, watch } from 'vue';
import { Button, InputGroup, SingleSelect, FormGroup, TextInput, Checkbox, PasswordInput, NumberInput } from 'pankow';
import { prettyBinarySize } from 'pankow/utils';
import { BACKUP_FORMATS, STORAGE_PROVIDERS, REGIONS_CONTABO, REGIONS_VULTR, REGIONS_IONOS, REGIONS_OVH, REGIONS_LINODE, REGIONS_SCALEWAY, REGIONS_EXOSCALE, REGIONS_DIGITALOCEAN, REGIONS_HETZNER, REGIONS_WASABI, REGIONS_S3 } from '../constants.js';
+import ProvisionModel from '../models/ProvisionModel.js';
import SystemModel from '../models/SystemModel.js';
import { mountlike, s3like } from '../utils.js';
const provider = defineModel('provider');
const providerConfig = defineModel('providerConfig');
-const formError = defineProps({
+const props = defineProps({
formError: {},
importOnly: {
type: Boolean,
default: false,
- }
+ },
+ provisioning: {
+ type: Boolean,
+ default: false,
+ },
});
const systemModel = SystemModel.create();
+const provisionModel = ProvisionModel.create();
const storageProviders = STORAGE_PROVIDERS.concat([
{ name: 'No-op (Only for testing)', value: 'noop' }
@@ -65,8 +71,13 @@ function onGcsKeyChange(event) {
gcsKeyFileName.value = event.target.files[0].name;
}
+
async function getBlockDevices() {
- const [error, result] = await systemModel.blockDevices();
+ let error, result;
+
+ if (props.provisioning) [error, result] = await provisionModel.blockDevices();
+ else [error, result] = await systemModel.blockDevices();
+
if (error) return console.error(error);
// amend label for UI
@@ -87,6 +98,11 @@ async function getBlockDevices() {
}
async function getMemory() {
+ if (props.provisioning) {
+ maxMemoryLimit.value = 4 * 1024 * 1024 * 1024;
+ return;
+ }
+
const [error, result] = await systemModel.memory();
if (error) return console.error(error);
diff --git a/dashboard/src/models/ProvisionModel.js b/dashboard/src/models/ProvisionModel.js
index 4239e6709..9f0ff12da 100644
--- a/dashboard/src/models/ProvisionModel.js
+++ b/dashboard/src/models/ProvisionModel.js
@@ -25,7 +25,7 @@ function create() {
}
if (error || result.status !== 200) return [error || result];
- return [null, result.body];
+ return [null, result.body.devices];
},
async createAdmin(data) {
let error, result;
diff --git a/dashboard/src/views/RestoreView.vue b/dashboard/src/views/RestoreView.vue
index 6f1c17118..cfdc25c87 100644
--- a/dashboard/src/views/RestoreView.vue
+++ b/dashboard/src/views/RestoreView.vue
@@ -2,7 +2,8 @@
import { ref, onMounted, useTemplateRef } from 'vue';
import { Spinner, Button, SingleSelect, FormGroup, TextInput, Checkbox } from 'pankow';
-import { redirectIfNeeded } from '../utils.js';
+import { REGIONS_CONTABO, REGIONS_VULTR, REGIONS_IONOS, REGIONS_OVH, REGIONS_LINODE, REGIONS_SCALEWAY, REGIONS_WASABI } from '../constants.js';
+import { redirectIfNeeded, mountlike, s3like } from '../utils.js';
import ProvisionModel from '../models/ProvisionModel.js';
import BackupProviderForm from '../components/BackupProviderForm.vue';
@@ -50,8 +51,6 @@ async function waitForRestore () {
return console.error(error);
}
- console.lo('restore', result.restore)
-
if (!result.restore.active) {
if (!result.adminFqdn || result.restore.errorMessage) { // restore reset or errored. start over
formError.value.dnsWait = result.restore.errorMessage;
@@ -76,48 +75,140 @@ async function onSubmit() {
formError.value = {};
if (remotePath.value.indexOf('/') === -1) {
- error.value.generic = 'Backup id must include the directory path';
- error.value.remotePath = true;
- busy.value = false;
- return;
+ error.value.generic = 'Backup id must include the directory path';
+ error.value.remotePath = true;
+ busy.value = false;
+ return;
}
if (remotePath.value.indexOf('box') === -1) {
- error.value.generic = 'Backup id must contain "box"';
- error.value.remotePath = true;
- busy.value = false;
- return;
+ error.value.generic = 'Backup id must contain "box"';
+ error.value.remotePath = true;
+ busy.value = false;
+ return;
}
const version = remotePath.value.match(/_v(\d+.\d+.\d+)/);
if (!version) {
- formError.value.generic = 'Backup id is missing version information';
- formError.value.remotePath = true;
- busy.value = false;
- return;
+ formError.value.generic = 'Backup id is missing version information';
+ formError.value.remotePath = true;
+ busy.value = false;
+ return;
}
const data = {
- backupConfig: {
- provider: provider.value,
- format: providerConfig.value.format,
- // TODO
- },
- remotePath: remotePath.value.replace(/\.tar\.gz(\.enc)?$/, ''),
- version: version ? version[1] : '',
- ipv4Config: {
- provider: ipv4Provider.value,
- ip: ipv4Address.value,
- ifname: ipv4Interface.value,
- },
- ipv6Config: {
- provider: ipv6Provider.value,
- ip: ipv6Address.value,
- ifname: ipv6Interface.value,
- },
- skipDnsSetup: skipDnsSetup.value,
+ backupConfig: {
+ provider: provider.value,
+ format: providerConfig.value.format,
+ },
+ remotePath: remotePath.value.replace(/\.tar\.gz(\.enc)?$/, ''),
+ version: version ? version[1] : '',
+ ipv4Config: {
+ provider: ipv4Provider.value,
+ ip: ipv4Address.value,
+ ifname: ipv4Interface.value,
+ },
+ ipv6Config: {
+ provider: ipv6Provider.value,
+ ip: ipv6Address.value,
+ ifname: ipv6Interface.value,
+ },
+ skipDnsSetup: skipDnsSetup.value,
};
+ if (providerConfig.value.encryptionPassword) {
+ data.backupConfig.encryptedFilenames = providerConfig.value.encryptedFilenames;
+ data.backupConfig.password = providerConfig.value.encryptionPassword;
+ }
+
+ if (s3like(provider.value)) {
+ data.backupConfig.endpoint = providerConfig.value.endpoint;
+ data.backupConfig.prefix = providerConfig.value.prefix;
+ data.backupConfig.bucket = providerConfig.value.bucket;
+ data.backupConfig.accessKeyId = providerConfig.value.accessKeyId;
+ data.backupConfig.secretAccessKey = providerConfig.value.secretAccessKey;
+
+ if (provider.value === 's3') {
+ data.backupConfig.region = providerConfig.value.region || undefined;
+ delete data.endpoint;
+ } else if (provider.value === 'minio' || provider.value === 's3-v4-compat') {
+ data.backupConfig.region = providerConfig.value.region || 'us-east-1';
+ data.backupConfig.acceptSelfSignedCerts = providerConfig.value.acceptSelfSignedCerts;
+ data.backupConfig.s3ForcePathStyle = true;
+ } else if (provider.value === 'exoscale-sos') {
+ data.backupConfig.region = 'us-east-1';
+ data.backupConfig.signatureVersion = 'v4';
+ } else if (provider.value === 'wasabi') {
+ data.backupConfig.region = REGIONS_WASABI.find(function (x) { return x.value === data.backupConfig.endpoint; }).region;
+ data.backupConfig.signatureVersion = 'v4';
+ } else if (provider.value === 'scaleway-objectstorage') {
+ data.backupConfig.region = REGIONS_SCALEWAY.find(function (x) { return x.value === data.backupConfig.endpoint; }).region;
+ data.backupConfig.signatureVersion = 'v4';
+ } else if (provider.value === 'linode-objectstorage') {
+ data.backupConfig.region = REGIONS_LINODE.find(function (x) { return x.value === data.backupConfig.endpoint; }).region;
+ data.backupConfig.signatureVersion = 'v4';
+ } else if (provider.value === 'ovh-objectstorage') {
+ data.backupConfig.region = REGIONS_OVH.find(function (x) { return x.value === data.backupConfig.endpoint; }).region;
+ data.backupConfig.signatureVersion = 'v4';
+ } else if (provider.value === 'ionos-objectstorage') {
+ data.backupConfig.region = REGIONS_IONOS.find(function (x) { return x.value === data.backupConfig.endpoint; }).region;
+ data.backupConfig.signatureVersion = 'v4';
+ } else if (provider.value === 'vultr-objectstorage') {
+ data.backupConfig.region = REGIONS_VULTR.find(function (x) { return x.value === data.backupConfig.endpoint; }).region;
+ data.backupConfig.signatureVersion = 'v4';
+ } else if (provider.value === 'contabo-objectstorage') {
+ data.backupConfig.region = REGIONS_CONTABO.find(function (x) { return x.value === data.backupConfig.endpoint; }).region;
+ data.backupConfig.signatureVersion = 'v4';
+ data.backupConfig.s3ForcePathStyle = true;
+ } else if (provider.value === 'upcloud-objectstorage') { // the UI sets region and endpoint
+ const m = /^.*\.(.*)\.upcloudobjects.com$/.exec(data.backupConfig.endpoint);
+ data.backupConfig.region = m ? m[1] : 'us-east-1'; // let it fail in validation phase if m is not valid
+ data.backupConfig.signatureVersion = 'v4';
+ } else if (provider.value === 'digitalocean-spaces') {
+ data.backupConfig.region = 'us-east-1';
+ } else if (provider.value === 'hetzner-objectstorage') {
+ data.backupConfig.region = 'us-east-1';
+ data.backupConfig.signatureVersion = 'v4';
+ }
+ } else if (mountlike(provider.value)) {
+ data.backupConfig.prefix = providerConfig.value.prefix;
+ data.backupConfig.noHardlinks = !providerConfig.value.useHardlinks;
+ data.backupConfig.mountOptions = {};
+
+ if (provider.value === 'cifs' || provider.value === 'sshfs' || provider.value === 'nfs') {
+ data.backupConfig.mountOptions.host = providerConfig.value.mountOptionHost;
+ data.backupConfig.mountOptions.remoteDir = providerConfig.value.mountOptionRemoteDir;
+
+ if (provider.value === 'cifs') {
+ data.backupConfig.mountOptions.username = providerConfig.value.mountOptionUsername;
+ data.backupConfig.mountOptions.password = providerConfig.value.mountOptionPassword;
+ data.backupConfig.mountOptions.seal = !!providerConfig.value.mountOptionSeal;
+ data.backupConfig.preserveAttributes = !!providerConfig.value.preserveAttributes;
+ } else if (provider.value === 'sshfs') {
+ data.backupConfig.mountOptions.user = providerConfig.value.mountOptionUser;
+ data.backupConfig.mountOptions.port = parseInt(providerConfig.value.mountOptionPort);
+ data.backupConfig.mountOptions.privateKey = providerConfig.value.mountOptionPrivateKey;
+ data.backupConfig.preserveAttributes = true;
+ }
+ } else if (provider.value === 'ext4' || provider.value === 'xfs' || provider.value === 'disk') {
+ data.backupConfig.mountOptions.diskPath = providerConfig.value.mountOptionDiskPath;
+ data.backupConfig.preserveAttributes = true;
+ } else if (provider.value === 'mountpoint') {
+ data.backupConfig.mountPoint = providerConfig.value.mountPoint;
+ data.backupConfig.chown = !!providerConfig.value.chown;
+ data.backupConfig.preserveAttributes = !!providerConfig.value.preserveAttributes;
+ }
+ } else if (provider.value === 'filesystem') {
+ data.backupConfig.backupFolder = providerConfig.value.backupFolder;
+ data.backupConfig.noHardlinks = !providerConfig.value.useHardlinks;
+ data.backupConfig.preserveAttributes = true;
+ } else if (provider.value === 'gcs') {
+ data.backupConfig.bucket = providerConfig.value.bucket;
+ data.backupConfig.prefix = providerConfig.value.prefix;
+ data.backupConfig.projectId = providerConfig.value.projectId;
+ data.backupConfig.credentials = providerConfig.value.credentials;
+ }
+
const [error] = await provisionModel.restore(data);
if (error) {
if (error.status === 424) {
@@ -185,11 +276,17 @@ function onUploadBackupConfig() {
}
onMounted(async () => {
- const [error, result] = await provisionModel.status();
+ let [error, result] = await provisionModel.status();
if (error) return console.error(error);
if (redirectIfNeeded(result, 'restore')) return; // redirected to some other view...
+ [error, result] = await provisionModel.detectIp();
+ if (error) return console.error(error);
+
+ ipv4Provider.value = result.ipv4 ? 'generic' : 'noop';
+ ipv6Provider.value = result.ipv6 ? 'generic' : 'noop';
+
ready.value = true;
});
@@ -231,7 +328,7 @@ onMounted(async () => {