Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 291798f574 | |||
| b104843ae1 | |||
| dd062c656f | |||
| ae2eb718c6 | |||
| 7ac26bb653 | |||
| 41a726e8a7 | |||
| 4b69216548 | |||
| 99395ddf5a | |||
| 5f9fa5c352 | |||
| 9013331917 | |||
| 3a8f80477b | |||
| 813c680ed0 | |||
| a0eccd615f | |||
| 59be539ecd | |||
| a04740114c | |||
| 60b5d71c74 | |||
| 0a8b4b0c43 | |||
| ec21105c47 | |||
| 444258e7ee | |||
| e6fd05c2bd | |||
| 9fdcd452d0 | |||
| f39b9d5618 | |||
| 76e4c4919d | |||
| d1f159cdb4 | |||
| c63065e460 | |||
| 124c1d94a4 | |||
| e9161b726a | |||
| fd0d27b192 | |||
| 50064a40fe | |||
| c9bc5fc38e | |||
| 58f533fe50 | |||
| efcdffd8ff | |||
| 22793c3886 | |||
| 797ddbacc0 | |||
| e011962469 | |||
| b376ad9815 | |||
| 77248fe65c | |||
| 1dad115203 | |||
| 8812d58031 | |||
| fff7568f7e | |||
| ff6662579d | |||
| 0cf9fbd909 |
Generated
+27
-17
@@ -7,6 +7,28 @@
|
||||
"from": "https://registry.npmjs.org/async/-/async-1.2.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.2.1.tgz"
|
||||
},
|
||||
"aws-sdk": {
|
||||
"version": "2.1.46",
|
||||
"from": "aws-sdk@*",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1.46.tgz",
|
||||
"dependencies": {
|
||||
"sax": {
|
||||
"version": "0.5.3",
|
||||
"from": "sax@0.5.3",
|
||||
"resolved": "http://registry.npmjs.org/sax/-/sax-0.5.3.tgz"
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.2.8",
|
||||
"from": "xml2js@0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.2.8.tgz"
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "0.4.2",
|
||||
"from": "xmlbuilder@0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-0.4.2.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.13.1",
|
||||
"from": "https://registry.npmjs.org/body-parser/-/body-parser-1.13.1.tgz",
|
||||
@@ -106,22 +128,22 @@
|
||||
},
|
||||
"cloudron-manifestformat": {
|
||||
"version": "1.7.0",
|
||||
"from": "cloudron-manifestformat@1.7.0",
|
||||
"from": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-1.7.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-1.7.0.tgz",
|
||||
"dependencies": {
|
||||
"java-packagename-regex": {
|
||||
"version": "1.0.0",
|
||||
"from": "java-packagename-regex@>=1.0.0 <2.0.0",
|
||||
"from": "https://registry.npmjs.org/java-packagename-regex/-/java-packagename-regex-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/java-packagename-regex/-/java-packagename-regex-1.0.0.tgz"
|
||||
},
|
||||
"safetydance": {
|
||||
"version": "0.0.15",
|
||||
"from": "safetydance@0.0.15",
|
||||
"from": "http://registry.npmjs.org/safetydance/-/safetydance-0.0.15.tgz",
|
||||
"resolved": "http://registry.npmjs.org/safetydance/-/safetydance-0.0.15.tgz"
|
||||
},
|
||||
"tv4": {
|
||||
"version": "1.2.3",
|
||||
"from": "tv4@>=1.1.9 <2.0.0",
|
||||
"from": "https://registry.npmjs.org/tv4/-/tv4-1.2.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/tv4/-/tv4-1.2.3.tgz"
|
||||
}
|
||||
}
|
||||
@@ -1708,19 +1730,7 @@
|
||||
"bunyan": {
|
||||
"version": "0.22.1",
|
||||
"from": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.1.tgz",
|
||||
"dependencies": {
|
||||
"mv": {
|
||||
"version": "0.0.5",
|
||||
"from": "mv@0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/mv/-/mv-0.0.5.tgz"
|
||||
},
|
||||
"dtrace-provider": {
|
||||
"version": "0.2.8",
|
||||
"from": "dtrace-provider@0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz"
|
||||
}
|
||||
}
|
||||
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.1.tgz"
|
||||
},
|
||||
"nopt": {
|
||||
"version": "2.1.1",
|
||||
|
||||
+1
-1
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^1.2.1",
|
||||
"aws-sdk": "^2.1.46",
|
||||
"body-parser": "^1.13.1",
|
||||
"cloudron-manifestformat": "^1.7.0",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
@@ -68,7 +69,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "*",
|
||||
"aws-sdk": "^2.1.10",
|
||||
"bootstrap-sass": "^3.3.3",
|
||||
"del": "^1.1.1",
|
||||
"expect.js": "*",
|
||||
|
||||
@@ -16,6 +16,8 @@ arg_tls_key=""
|
||||
arg_token=""
|
||||
arg_version=""
|
||||
arg_web_server_origin=""
|
||||
arg_backup_key=""
|
||||
arg_aws=""
|
||||
|
||||
args=$(getopt -o "" -l "data:,retire" -n "$0" -- "$@")
|
||||
eval set -- "${args}"
|
||||
@@ -41,6 +43,12 @@ EOF
|
||||
arg_restore_key=$(echo "$2" | $json restoreKey)
|
||||
[[ "${arg_restore_key}" == "null" ]] && arg_restore_key=""
|
||||
|
||||
arg_backup_key=$(echo "$2" | $json backupKey)
|
||||
[[ "${arg_backup_key}" == "null" ]] && arg_backup_key=""
|
||||
|
||||
arg_aws=$(echo "$2" | $json aws)
|
||||
[[ "${arg_aws}" == "null" ]] && arg_aws=""
|
||||
|
||||
shift 2
|
||||
;;
|
||||
--) break;;
|
||||
|
||||
+4
-1
@@ -122,6 +122,7 @@ set_progress "65" "Creating cloudron.conf"
|
||||
sudo -u yellowtent -H bash <<EOF
|
||||
set -eu
|
||||
echo "Creating cloudron.conf"
|
||||
# note that arg_aws is a javascript object and intentionally unquoted below
|
||||
cat > "${CONFIG_DIR}/cloudron.conf" <<CONF_END
|
||||
{
|
||||
"version": "${arg_version}",
|
||||
@@ -138,7 +139,9 @@ cat > "${CONFIG_DIR}/cloudron.conf" <<CONF_END
|
||||
"password": "${mysql_root_password}",
|
||||
"port": 3306,
|
||||
"name": "box"
|
||||
}
|
||||
},
|
||||
"backupKey": "${arg_backup_key}",
|
||||
"aws": ${arg_aws}
|
||||
}
|
||||
CONF_END
|
||||
|
||||
|
||||
+3
-3
@@ -704,7 +704,7 @@ function backupApp(app, addonsToBackup, callback) {
|
||||
return callback(safe.error);
|
||||
}
|
||||
|
||||
backups.getBackupUrl(app, null, function (error, result) {
|
||||
backups.getBackupUrl(app, function (error, result) {
|
||||
if (error && error.reason === BackupsError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
@@ -713,7 +713,7 @@ function backupApp(app, addonsToBackup, callback) {
|
||||
async.series([
|
||||
ignoreError(shell.sudo.bind(null, 'mountSwap', [ BACKUP_SWAP_CMD, '--on' ])),
|
||||
addons.backupAddons.bind(null, app, addonsToBackup),
|
||||
shell.sudo.bind(null, 'backupApp', [ BACKUP_APP_CMD, app.id, result.url, result.backupKey ]),
|
||||
shell.sudo.bind(null, 'backupApp', [ BACKUP_APP_CMD, app.id, result.url, result.backupKey, result.sessionToken ]),
|
||||
ignoreError(shell.sudo.bind(null, 'unmountSwap', [ BACKUP_SWAP_CMD, '--off' ])),
|
||||
], function (error) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
@@ -760,7 +760,7 @@ function restoreApp(app, addonsToRestore, callback) {
|
||||
|
||||
debugApp(app, 'restoreApp: restoreUrl:%s', result.url);
|
||||
|
||||
shell.sudo('restoreApp', [ RESTORE_APP_CMD, app.id, result.url, result.backupKey ], function (error) {
|
||||
shell.sudo('restoreApp', [ RESTORE_APP_CMD, app.id, result.url, result.backupKey, result.sessionToken ], function (error) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
addons.restoreAddons(app, addonsToRestore, callback);
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/* jslint node:true */
|
||||
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
AWSError: AWSError,
|
||||
|
||||
getAWSCredentials: getAWSCredentials,
|
||||
|
||||
getSignedUploadUrl: getSignedUploadUrl,
|
||||
getSignedDownloadUrl: getSignedDownloadUrl
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
AWS = require('aws-sdk'),
|
||||
config = require('./config.js'),
|
||||
debug = require('debug')('box:aws'),
|
||||
superagent = require('superagent'),
|
||||
util = require('util');
|
||||
|
||||
// http://dustinsenos.com/articles/customErrorsInNode
|
||||
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
|
||||
function AWSError(reason, errorOrMessage) {
|
||||
assert.strictEqual(typeof reason, 'string');
|
||||
assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined');
|
||||
|
||||
Error.call(this);
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
|
||||
this.name = this.constructor.name;
|
||||
this.reason = reason;
|
||||
if (typeof errorOrMessage === 'undefined') {
|
||||
this.message = reason;
|
||||
} else if (typeof errorOrMessage === 'string') {
|
||||
this.message = errorOrMessage;
|
||||
} else {
|
||||
this.message = 'Internal error';
|
||||
this.nestedError = errorOrMessage;
|
||||
}
|
||||
}
|
||||
util.inherits(AWSError, Error);
|
||||
AWSError.INTERNAL_ERROR = 'Internal Error';
|
||||
AWSError.MISSING_CREDENTIALS = 'Missing AWS credentials';
|
||||
|
||||
function getAWSCredentials(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('getAWSCredentials()');
|
||||
|
||||
// CaaS
|
||||
if (config.token()) {
|
||||
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/awscredentials';
|
||||
superagent.get(url).query({ token: config.token() }).end(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
if (result.statusCode !== 201) return callback(new Error(result.text));
|
||||
if (!result.body || !result.body.credentials) return callback(new Error('Unexpected response'));
|
||||
|
||||
debug('getAWSCredentials()', result.body.credentials);
|
||||
|
||||
return callback(null, {
|
||||
accessKeyId: result.body.credentials.AccessKeyId,
|
||||
secretAccessKey: result.body.credentials.SecretAccessKey,
|
||||
sessionToken: result.body.credentials.SessionToken,
|
||||
region: 'us-east-1'
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (!config.aws().accessKeyId || !config.aws().secretAccessKey) return callback(new AWSError(AWSError.MISSING_CREDENTIALS));
|
||||
|
||||
callback(null, {
|
||||
accessKeyId: config.aws().accessKeyId,
|
||||
secretAccessKey: config.aws().secretAccessKey,
|
||||
region: 'us-east-1'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getSignedUploadUrl(filename, callback) {
|
||||
assert.strictEqual(typeof filename, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('getSignedUploadUrl()');
|
||||
|
||||
getAWSCredentials(function (error, credentials) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var s3 = new AWS.S3(credentials);
|
||||
|
||||
var params = {
|
||||
Bucket: config.aws().backupBucket,
|
||||
Key: config.aws().backupPrefix + '/' + filename,
|
||||
Expires: 60 * 30 /* 30 minutes */
|
||||
};
|
||||
|
||||
var url = s3.getSignedUrl('putObject', params);
|
||||
|
||||
callback(null, { url : url, sessionToken: credentials.sessionToken });
|
||||
});
|
||||
}
|
||||
|
||||
function getSignedDownloadUrl(filename, callback) {
|
||||
assert.strictEqual(typeof filename, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('getSignedDownloadUrl()');
|
||||
|
||||
getAWSCredentials(function (error, credentials) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var s3 = new AWS.S3(credentials);
|
||||
|
||||
var params = {
|
||||
Bucket: config.aws().backupBucket,
|
||||
Key: config.aws().backupPrefix + '/' + filename,
|
||||
Expires: 60 * 30 /* 30 minutes */
|
||||
};
|
||||
|
||||
var url = s3.getSignedUrl('getObject', params);
|
||||
|
||||
callback(null, { url: url, sessionToken: credentials.sessionToken });
|
||||
});
|
||||
}
|
||||
+31
-22
@@ -10,6 +10,7 @@ exports = module.exports = {
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
aws = require('./aws.js'),
|
||||
config = require('./config.js'),
|
||||
debug = require('debug')('box:backups'),
|
||||
superagent = require('superagent'),
|
||||
@@ -54,42 +55,50 @@ function getAllPaged(page, perPage, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function getBackupUrl(app, appBackupIds, callback) {
|
||||
function getBackupUrl(app, callback) {
|
||||
assert(!app || typeof app === 'object');
|
||||
assert(!appBackupIds || util.isArray(appBackupIds));
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/backupurl';
|
||||
var filename = '';
|
||||
if (app) {
|
||||
filename = util.format('appbackup_%s_%s-v%s.tar.gz', app.id, (new Date()).toISOString(), app.manifest.version);
|
||||
} else {
|
||||
filename = util.format('backup_%s-v%s.tar.gz', (new Date()).toISOString(), config.version());
|
||||
}
|
||||
|
||||
var data = {
|
||||
boxVersion: config.version(),
|
||||
appId: app ? app.id : null,
|
||||
appVersion: app ? app.manifest.version : null,
|
||||
appBackupIds: appBackupIds
|
||||
};
|
||||
aws.getSignedUploadUrl(filename, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
superagent.put(url).query({ token: config.token() }).send(data).end(function (error, result) {
|
||||
if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error));
|
||||
if (result.statusCode !== 201) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, result.text));
|
||||
if (!result.body || !result.body.url) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, 'Unexpected response'));
|
||||
var obj = {
|
||||
id: filename,
|
||||
url: result.url,
|
||||
sessionToken: result.sessionToken,
|
||||
backupKey: config.backupKey()
|
||||
};
|
||||
|
||||
return callback(null, result.body);
|
||||
debug('getBackupUrl: ', obj);
|
||||
|
||||
callback(null, obj);
|
||||
});
|
||||
}
|
||||
|
||||
// backupId is the s3 filename. appbackup_%s_%s-v%s.tar.gz
|
||||
function getRestoreUrl(backupId, callback) {
|
||||
assert.strictEqual(typeof backupId, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/restoreurl';
|
||||
aws.getSignedDownloadUrl(backupId, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
superagent.put(url).query({ token: config.token(), backupId: backupId }).end(function (error, result) {
|
||||
if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error));
|
||||
if (result.statusCode !== 201) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, result.text));
|
||||
if (!result.body || !result.body.url) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, 'Unexpected response'));
|
||||
var obj = {
|
||||
id: backupId,
|
||||
url: result.url,
|
||||
sessionToken: result.sessionToken,
|
||||
backupKey: config.backupKey()
|
||||
};
|
||||
|
||||
return callback(null, result.body);
|
||||
debug('getRestoreUrl: ', obj);
|
||||
|
||||
callback(null, obj);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
+20
-14
@@ -25,6 +25,7 @@ var apps = require('./apps.js'),
|
||||
AppsError = require('./apps.js').AppsError,
|
||||
assert = require('assert'),
|
||||
async = require('async'),
|
||||
aws = require('./aws.js'),
|
||||
backups = require('./backups.js'),
|
||||
BackupsError = require('./backups.js').BackupsError,
|
||||
clientdb = require('./clientdb.js'),
|
||||
@@ -46,7 +47,8 @@ var apps = require('./apps.js'),
|
||||
user = require('./user.js'),
|
||||
UserError = user.UserError,
|
||||
userdb = require('./userdb.js'),
|
||||
util = require('util');
|
||||
util = require('util'),
|
||||
webhooks = require('./webhooks.js');
|
||||
|
||||
var RELOAD_NGINX_CMD = path.join(__dirname, 'scripts/reloadnginx.sh'),
|
||||
REBOOT_CMD = path.join(__dirname, 'scripts/reboot.sh'),
|
||||
@@ -491,19 +493,20 @@ function doUpdate(boxUpdateInfo, callback) {
|
||||
var args = {
|
||||
sourceTarballUrl: result.body.url,
|
||||
|
||||
// IMPORTANT: if you change this, fix up argparser.sh as well. keep these sorted for readability
|
||||
// this data is opaque to the installer
|
||||
data: {
|
||||
apiServerOrigin: config.apiServerOrigin(),
|
||||
boxVersionsUrl: config.get('boxVersionsUrl'),
|
||||
fqdn: config.fqdn(),
|
||||
isCustomDomain: config.isCustomDomain(),
|
||||
restoreKey: null,
|
||||
restoreUrl: null,
|
||||
tlsKey: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), 'utf8'),
|
||||
tlsCert: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), 'utf8'),
|
||||
token: config.token(),
|
||||
version: boxUpdateInfo.version,
|
||||
webServerOrigin: config.webServerOrigin()
|
||||
apiServerOrigin: config.apiServerOrigin(),
|
||||
webServerOrigin: config.webServerOrigin(),
|
||||
fqdn: config.fqdn(),
|
||||
token: config.token(),
|
||||
tlsCert: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.cert'), 'utf8'),
|
||||
tlsKey: fs.readFileSync(path.join(paths.NGINX_CERT_DIR, 'host.key'), 'utf8'),
|
||||
isCustomDomain: config.isCustomDomain(),
|
||||
restoreUrl: null,
|
||||
restoreKey: null,
|
||||
aws: config.aws()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -560,7 +563,7 @@ function ensureBackup(callback) {
|
||||
function backupBoxWithAppBackupIds(appBackupIds, callback) {
|
||||
assert(util.isArray(appBackupIds));
|
||||
|
||||
backups.getBackupUrl(null /* app */, appBackupIds, function (error, result) {
|
||||
backups.getBackupUrl(null /* app */, function (error, result) {
|
||||
if (error && error.reason === BackupsError.EXTERNAL_ERROR) return callback(new CloudronError(CloudronError.EXTERNAL_ERROR, error.message));
|
||||
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
|
||||
|
||||
@@ -568,14 +571,17 @@ function backupBoxWithAppBackupIds(appBackupIds, callback) {
|
||||
|
||||
async.series([
|
||||
ignoreError(shell.sudo.bind(null, 'mountSwap', [ BACKUP_SWAP_CMD, '--on' ])),
|
||||
shell.sudo.bind(null, 'backupBox', [ BACKUP_BOX_CMD, result.url, result.backupKey ]),
|
||||
shell.sudo.bind(null, 'backupBox', [ BACKUP_BOX_CMD, result.url, result.backupKey, result.sessionToken ]),
|
||||
ignoreError(shell.sudo.bind(null, 'unmountSwap', [ BACKUP_SWAP_CMD, '--off' ])),
|
||||
], function (error) {
|
||||
if (error) return callback(new CloudronError(CloudronError.INTERNAL_ERROR, error));
|
||||
|
||||
debug('backup: successful');
|
||||
|
||||
callback(null, result.id);
|
||||
webhooks.backupDone(result.id, null /* app */, appBackupIds, function (error) {
|
||||
if (error) return callback(error);
|
||||
callback(null, result.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ exports = module.exports = {
|
||||
|
||||
isDev: isDev,
|
||||
|
||||
backupKey: backupKey,
|
||||
aws: aws,
|
||||
|
||||
// for testing resets to defaults
|
||||
_reset: initConfig
|
||||
};
|
||||
@@ -70,6 +73,13 @@ function initConfig() {
|
||||
data.webServerOrigin = null;
|
||||
data.internalPort = 3001;
|
||||
data.ldapPort = 3002;
|
||||
data.backupKey = 'backupKey';
|
||||
data.aws = {
|
||||
backupBucket: null,
|
||||
backupPrefix: null,
|
||||
accessKeyId: null, // selfhosting only
|
||||
secretAccessKey: null // selfhosting only
|
||||
};
|
||||
|
||||
if (exports.CLOUDRON) {
|
||||
data.port = 3000;
|
||||
@@ -172,3 +182,10 @@ function isDev() {
|
||||
return /dev/i.test(get('boxVersionsUrl'));
|
||||
}
|
||||
|
||||
function backupKey() {
|
||||
return get('backupKey');
|
||||
}
|
||||
|
||||
function aws() {
|
||||
return get('aws');
|
||||
}
|
||||
|
||||
@@ -119,8 +119,8 @@ describe('Backups API', function () {
|
||||
|
||||
it('succeeds', function (done) {
|
||||
var scope = nock(config.apiServerOrigin())
|
||||
.put('/api/v1/boxes/' + config.fqdn() + '/backupurl?token=APPSTORE_TOKEN', { boxVersion: '0.5.0', appId: null, appVersion: null, appBackupIds: [] })
|
||||
.reply(201, { id: 'someid', url: 'http://foobar', backupKey: 'somerestorekey' });
|
||||
.get('/api/v1/boxes/' + config.fqdn() + '/awscredentials?token=APPSTORE_TOKEN')
|
||||
.reply(201, { accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey', sessionToken: 'sessionToken' });
|
||||
|
||||
request.post(SERVER_URL + '/api/v1/backups')
|
||||
.query({ access_token: token })
|
||||
|
||||
@@ -450,8 +450,18 @@ describe('Cloudron', function () {
|
||||
});
|
||||
|
||||
it('fails when in wrong state', function (done) {
|
||||
var scope1 = nock(config.apiServerOrigin()).post('/api/v1/boxes/' + config.fqdn() + '/migrate?token=APPSTORE_TOKEN', { size: 'small', region: 'sfo', restoreKey: 'someid' }).reply(409, {});
|
||||
var scope2 = nock(config.apiServerOrigin()).put('/api/v1/boxes/' + config.fqdn() + '/backupurl?token=APPSTORE_TOKEN', { boxVersion: '0.5.0', appId: null, appVersion: null, appBackupIds: [] }).reply(201, { id: 'someid', url: 'http://foobar', backupKey: 'somerestorekey' });
|
||||
var scope2 = nock(config.apiServerOrigin())
|
||||
.get('/api/v1/boxes/' + config.fqdn() + '/awscredentials?token=APPSTORE_TOKEN')
|
||||
.reply(201, { credentials: { accessKeyId: 'accessKeyId', secretAccessKey: 'secretAccessKey', sessionToken: 'sessionToken' } });
|
||||
|
||||
var scope3 = nock(config.apiServerOrigin())
|
||||
.filteringRequestBody(function () { return false; })
|
||||
.post('/api/v1/boxes/' + config.fqdn() + '/backupDone?token=APPSTORE_TOKEN')
|
||||
.reply(200, { id: 'someid' });
|
||||
|
||||
var scope1 = nock(config.apiServerOrigin())
|
||||
.filteringRequestBody(function () { return false; })
|
||||
.post('/api/v1/boxes/' + config.fqdn() + '/migrate?token=APPSTORE_TOKEN', { }).reply(409, {});
|
||||
|
||||
injectShellMock();
|
||||
|
||||
@@ -463,7 +473,7 @@ describe('Cloudron', function () {
|
||||
expect(result.statusCode).to.equal(202);
|
||||
|
||||
function checkAppstoreServerCalled() {
|
||||
if (scope1.isDone() && scope2.isDone()) {
|
||||
if (scope1.isDone() && scope2.isDone() && scope3.isDone()) {
|
||||
restoreShellMock();
|
||||
return done();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ if [[ $# == 1 && "$1" == "--check" ]]; then
|
||||
fi
|
||||
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "Usage: backup.sh <appid> <url> <key>"
|
||||
echo "Usage: backupapp.sh <appid> <url> <key> [aws session token]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -22,6 +22,7 @@ readonly DATA_DIR="${HOME}/data"
|
||||
app_id="$1"
|
||||
backup_url="$2"
|
||||
backup_key="$3"
|
||||
session_token="$4"
|
||||
readonly now=$(date "+%Y-%m-%dT%H:%M:%S")
|
||||
readonly app_data_dir="${DATA_DIR}/${app_id}"
|
||||
readonly app_data_snapshot="${DATA_DIR}/snapshots/${app_id}-${now}"
|
||||
@@ -31,9 +32,17 @@ btrfs subvolume snapshot -r "${app_data_dir}" "${app_data_snapshot}"
|
||||
for try in `seq 1 5`; do
|
||||
echo "Uploading backup to ${backup_url} (try ${try})"
|
||||
error_log=$(mktemp)
|
||||
|
||||
headers=("-H" "Content-Type:")
|
||||
|
||||
# federated tokens in CaaS case need session token
|
||||
if [ ! -z "$session_token" ]; then
|
||||
headers=(${headers[@]} "-H" "x-amz-security-token: ${session_token}")
|
||||
fi
|
||||
|
||||
if tar -cvzf - -C "${app_data_snapshot}" . \
|
||||
| openssl aes-256-cbc -e -pass "pass:${backup_key}" \
|
||||
| curl --fail -H "Content-Type:" -X PUT --data-binary @- "${backup_url}" 2>"${error_log}"; then
|
||||
| curl --fail -X PUT "${headers[@]}" --data-binary @- "${backup_url}" 2>"${error_log}"; then
|
||||
break
|
||||
fi
|
||||
cat "${error_log}" && rm "${error_log}"
|
||||
|
||||
@@ -13,12 +13,13 @@ if [[ $# == 1 && "$1" == "--check" ]]; then
|
||||
fi
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: backupbox.sh <url> <key>"
|
||||
echo "Usage: backupbox.sh <url> <key> [aws session token]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
backup_url="$1"
|
||||
backup_key="$2"
|
||||
session_token="$3"
|
||||
now=$(date "+%Y-%m-%dT%H:%M:%S")
|
||||
BOX_DATA_DIR="${HOME}/data/box"
|
||||
box_snapshot_dir="${HOME}/data/snapshots/box-${now}"
|
||||
@@ -32,9 +33,17 @@ btrfs subvolume snapshot -r "${BOX_DATA_DIR}" "${box_snapshot_dir}"
|
||||
for try in `seq 1 5`; do
|
||||
echo "Uploading backup to ${backup_url} (try ${try})"
|
||||
error_log=$(mktemp)
|
||||
|
||||
headers=("-H" "Content-Type:")
|
||||
|
||||
# federated tokens in CaaS case need session token
|
||||
if [ ! -z "$session_token" ]; then
|
||||
headers=(${headers[@]} "-H" "x-amz-security-token: ${session_token}")
|
||||
fi
|
||||
|
||||
if tar -cvzf - -C "${box_snapshot_dir}" . \
|
||||
| openssl aes-256-cbc -e -pass "pass:${backup_key}" \
|
||||
| curl --fail -H "Content-Type:" -X PUT --data-binary @- "${backup_url}" 2>"${error_log}"; then
|
||||
| curl --fail -X PUT ${headers[@]} --data-binary @- "${backup_url}" 2>"${error_log}"; then
|
||||
break
|
||||
fi
|
||||
cat "${error_log}" && rm "${error_log}"
|
||||
|
||||
@@ -13,7 +13,7 @@ if [[ $# == 1 && "$1" == "--check" ]]; then
|
||||
fi
|
||||
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "Usage: restoreapp.sh <appid> <url> <key>"
|
||||
echo "Usage: restoreapp.sh <appid> <url> <key> [aws session token]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -23,6 +23,7 @@ readonly curl="curl --fail --connect-timeout 20 --retry 10 --retry-delay 2 --max
|
||||
app_id="$1"
|
||||
restore_url="$2"
|
||||
restore_key="$3"
|
||||
session_token="$4"
|
||||
|
||||
echo "Downloading backup: ${restore_url} and key: ${restore_key}"
|
||||
|
||||
@@ -30,7 +31,14 @@ for try in `seq 1 5`; do
|
||||
echo "Download backup from ${restore_url} (try ${try})"
|
||||
error_log=$(mktemp)
|
||||
|
||||
if $curl -L "${restore_url}" \
|
||||
headers=("") # empty element required (http://stackoverflow.com/questions/7577052/bash-empty-array-expansion-with-set-u)
|
||||
|
||||
# federated tokens in CaaS case need session token
|
||||
if [[ ! -z "${session_token}" ]]; then
|
||||
headers=(${headers[@]} "-H" "x-amz-security-token: ${session_token}")
|
||||
fi
|
||||
|
||||
if $curl -L "${headers[@]}" "${restore_url}" \
|
||||
| openssl aes-256-cbc -d -pass "pass:${restore_key}" \
|
||||
| tar -zxf - -C "${DATA_DIR}/${app_id}" 2>"${error_log}"; then
|
||||
chown -R yellowtent:yellowtent "${DATA_DIR}/${app_id}"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/* jslint node:true */
|
||||
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
backupDone: backupDone
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
config = require('./config.js'),
|
||||
debug = require('debug')('box:webhooks'),
|
||||
superagent = require('superagent'),
|
||||
util = require('util');
|
||||
|
||||
function backupDone(filename, app, appBackupIds, callback) {
|
||||
assert.strictEqual(typeof filename, 'string');
|
||||
assert(!app || typeof app === 'object');
|
||||
assert(!appBackupIds || util.isArray(appBackupIds));
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('backupDone():', filename);
|
||||
|
||||
// CaaS
|
||||
if (config.token()) {
|
||||
var url = config.apiServerOrigin() + '/api/v1/boxes/' + config.fqdn() + '/backupDone';
|
||||
var data = {
|
||||
boxVersion: config.version(),
|
||||
restoreKey: filename,
|
||||
appId: app ? app.id : null,
|
||||
appVersion: app ? app.manifest.version : null,
|
||||
appBackupIds: appBackupIds
|
||||
};
|
||||
|
||||
superagent.post(url).send(data).query({ token: config.token() }).end(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
if (result.statusCode !== 200) return callback(new Error(result.text));
|
||||
if (!result.body) return callback(new Error('Unexpected response'));
|
||||
|
||||
debug('backupDone()', filename);
|
||||
|
||||
return callback(null);
|
||||
});
|
||||
} else {
|
||||
// TODO call custom webhook
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
<title> Cloudron App Error </title>
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- external fonts and CSS -->
|
||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="//fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- jQuery-->
|
||||
<script src="3rdparty/js/jquery.min.js"></script>
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
<title> Cloudron Error </title>
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- external fonts and CSS -->
|
||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="//fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- jQuery-->
|
||||
<script src="3rdparty/js/jquery.min.js"></script>
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
<link href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet">
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="//fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet" type="text/css">
|
||||
@@ -48,9 +51,6 @@
|
||||
<!-- Main Application -->
|
||||
<script src="js/index.js"></script>
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
<title> Cloudron </title>
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- external fonts and CSS -->
|
||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="//fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- jQuery-->
|
||||
<script src="3rdparty/js/jquery.min.js"></script>
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
<title> Cloudron Setup </title>
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet">
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="//fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet" type="text/css">
|
||||
@@ -31,9 +34,6 @@
|
||||
<!-- Setup Application -->
|
||||
<script src="js/setup.js"></script>
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<body class="setup" ng-app="Application" ng-controller="SetupController">
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
<title> Cloudron Update </title>
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet">
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="//fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet" type="text/css">
|
||||
@@ -23,9 +26,6 @@
|
||||
<!-- Update Application -->
|
||||
<script src="js/update.js"></script>
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link href="theme.css" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<body ng-app="Application" ng-controller="Controller" style="background-color: #7F7F7F">
|
||||
|
||||
Reference in New Issue
Block a user