Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c95778178f | |||
| 04870313b7 | |||
| 6ca040149c | |||
| e487b9d46b | |||
| 1375e16ad2 | |||
| 312f1f0085 | |||
| 721900fc47 | |||
| 2d815a92a3 | |||
| 1c192b7c11 | |||
| 4a887336bc | |||
| 8f6521f942 | |||
| fbdfaa4dc7 | |||
| bf4290db3e | |||
| 94ad633128 | |||
| c552917991 | |||
| a7ee8c853e | |||
| 29e4879451 | |||
| 8b92344808 | |||
| 0877cec2e6 | |||
| b1ca577be7 | |||
| 9b484f5ac9 | |||
| b6a9fd81da | |||
| f19113f88e | |||
| 3837bee51f | |||
| 89c3296632 | |||
| db55f0696e | |||
| 03d4ae9058 | |||
| f8b41b703c | |||
| 2a989e455c | |||
| cd24decca0 |
+8
-8
@@ -3,15 +3,15 @@
|
||||
# If you change the infra version, be sure to put a warning
|
||||
# in the change log
|
||||
|
||||
INFRA_VERSION=10
|
||||
INFRA_VERSION=12
|
||||
|
||||
# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
# These constants are used in the installer script as well
|
||||
BASE_IMAGE=cloudron/base:0.3.3
|
||||
MYSQL_IMAGE=cloudron/mysql:0.3.3
|
||||
POSTGRESQL_IMAGE=cloudron/postgresql:0.3.2
|
||||
MONGODB_IMAGE=cloudron/mongodb:0.3.2
|
||||
REDIS_IMAGE=cloudron/redis:0.3.2 # if you change this, fix src/addons.js as well
|
||||
MAIL_IMAGE=cloudron/mail:0.3.2
|
||||
GRAPHITE_IMAGE=cloudron/graphite:0.3.4
|
||||
BASE_IMAGE=cloudron/base:0.5.1
|
||||
MYSQL_IMAGE=cloudron/mysql:0.5.0
|
||||
POSTGRESQL_IMAGE=cloudron/postgresql:0.5.0
|
||||
MONGODB_IMAGE=cloudron/mongodb:0.5.0
|
||||
REDIS_IMAGE=cloudron/redis:0.5.0 # if you change this, fix src/addons.js as well
|
||||
MAIL_IMAGE=cloudron/mail:0.5.0
|
||||
GRAPHITE_IMAGE=cloudron/graphite:0.4.0
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ graphite_container_id=$(docker run --restart=always -d --name="graphite" \
|
||||
-p 127.0.0.1:2004:2004 \
|
||||
-p 127.0.0.1:8000:8000 \
|
||||
-v "${DATA_DIR}/graphite:/app/data" \
|
||||
--read-only -v /tmp -v /run -v /var/log \
|
||||
"${GRAPHITE_IMAGE}")
|
||||
echo "Graphite container id: ${graphite_container_id}"
|
||||
|
||||
@@ -45,6 +46,7 @@ mail_container_id=$(docker run --restart=always -d --name="mail" \
|
||||
-h "${arg_fqdn}" \
|
||||
-e "DOMAIN_NAME=${arg_fqdn}" \
|
||||
-v "${DATA_DIR}/box/mail:/app/data" \
|
||||
--read-only -v /tmp -v /run -v /var/log \
|
||||
"${MAIL_IMAGE}")
|
||||
echo "Mail container id: ${mail_container_id}"
|
||||
|
||||
@@ -61,6 +63,7 @@ mysql_container_id=$(docker run --restart=always -d --name="mysql" \
|
||||
-h "${arg_fqdn}" \
|
||||
-v "${DATA_DIR}/mysql:/var/lib/mysql" \
|
||||
-v "${DATA_DIR}/addons/mysql_vars.sh:/etc/mysql/mysql_vars.sh:ro" \
|
||||
--read-only -v /tmp -v /run -v /var/log \
|
||||
"${MYSQL_IMAGE}")
|
||||
echo "MySQL container id: ${mysql_container_id}"
|
||||
|
||||
@@ -75,6 +78,7 @@ postgresql_container_id=$(docker run --restart=always -d --name="postgresql" \
|
||||
-h "${arg_fqdn}" \
|
||||
-v "${DATA_DIR}/postgresql:/var/lib/postgresql" \
|
||||
-v "${DATA_DIR}/addons/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh:ro" \
|
||||
--read-only -v /tmp -v /run -v /var/log \
|
||||
"${POSTGRESQL_IMAGE}")
|
||||
echo "PostgreSQL container id: ${postgresql_container_id}"
|
||||
|
||||
@@ -89,6 +93,7 @@ mongodb_container_id=$(docker run --restart=always -d --name="mongodb" \
|
||||
-h "${arg_fqdn}" \
|
||||
-v "${DATA_DIR}/mongodb:/var/lib/mongodb" \
|
||||
-v "${DATA_DIR}/addons/mongodb_vars.sh:/etc/mongodb_vars.sh:ro" \
|
||||
--read-only -v /tmp -v /run -v /var/log \
|
||||
"${MONGODB_IMAGE}")
|
||||
echo "Mongodb container id: ${mongodb_container_id}"
|
||||
|
||||
|
||||
+73
-84
@@ -11,8 +11,8 @@ exports = module.exports = {
|
||||
getBindsSync: getBindsSync,
|
||||
|
||||
// exported for testing
|
||||
_allocateOAuthCredentials: allocateOAuthCredentials,
|
||||
_removeOAuthCredentials: removeOAuthCredentials
|
||||
_setupOauth: setupOauth,
|
||||
_teardownOauth: teardownOauth
|
||||
};
|
||||
|
||||
var appdb = require('./appdb.js'),
|
||||
@@ -35,27 +35,20 @@ var appdb = require('./appdb.js'),
|
||||
safe = require('safetydance'),
|
||||
shell = require('./shell.js'),
|
||||
spawn = child_process.spawn,
|
||||
tokendb = require('./tokendb.js'),
|
||||
util = require('util'),
|
||||
uuid = require('node-uuid'),
|
||||
vbox = require('./vbox.js');
|
||||
|
||||
var NOOP = function (app, callback) { return callback(); };
|
||||
var NOOP = function (app, options, callback) { return callback(); };
|
||||
|
||||
// setup can be called multiple times for the same app (configure crash restart) and existing data must not be lost
|
||||
// teardown is destructive. app data stored with the addon is lost
|
||||
var KNOWN_ADDONS = {
|
||||
oauth: {
|
||||
setup: allocateOAuthCredentials,
|
||||
teardown: removeOAuthCredentials,
|
||||
setup: setupOauth,
|
||||
teardown: teardownOauth,
|
||||
backup: NOOP,
|
||||
restore: allocateOAuthCredentials
|
||||
},
|
||||
token: {
|
||||
setup: allocateAccessToken,
|
||||
teardown: removeAccessToken,
|
||||
backup: NOOP,
|
||||
restore: allocateAccessToken
|
||||
restore: setupOauth
|
||||
},
|
||||
ldap: {
|
||||
setup: setupLdap,
|
||||
@@ -128,9 +121,9 @@ function setupAddons(app, addons, callback) {
|
||||
async.eachSeries(Object.keys(addons), function iterator(addon, iteratorCallback) {
|
||||
if (!(addon in KNOWN_ADDONS)) return iteratorCallback(new Error('No such addon:' + addon));
|
||||
|
||||
debugApp(app, 'Setting up addon %s', addon);
|
||||
debugApp(app, 'Setting up addon %s with options %j', addon, addons[addon]);
|
||||
|
||||
KNOWN_ADDONS[addon].setup(app, iteratorCallback);
|
||||
KNOWN_ADDONS[addon].setup(app, addons[addon], iteratorCallback);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
@@ -146,9 +139,9 @@ function teardownAddons(app, addons, callback) {
|
||||
async.eachSeries(Object.keys(addons), function iterator(addon, iteratorCallback) {
|
||||
if (!(addon in KNOWN_ADDONS)) return iteratorCallback(new Error('No such addon:' + addon));
|
||||
|
||||
debugApp(app, 'Tearing down addon %s', addon);
|
||||
debugApp(app, 'Tearing down addon %s with options %j', addon, addons[addon]);
|
||||
|
||||
KNOWN_ADDONS[addon].teardown(app, iteratorCallback);
|
||||
KNOWN_ADDONS[addon].teardown(app, addons[addon], iteratorCallback);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
@@ -166,7 +159,7 @@ function backupAddons(app, addons, callback) {
|
||||
async.eachSeries(Object.keys(addons), function iterator (addon, iteratorCallback) {
|
||||
if (!(addon in KNOWN_ADDONS)) return iteratorCallback(new Error('No such addon:' + addon));
|
||||
|
||||
KNOWN_ADDONS[addon].backup(app, iteratorCallback);
|
||||
KNOWN_ADDONS[addon].backup(app, addons[addon], iteratorCallback);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
@@ -184,7 +177,7 @@ function restoreAddons(app, addons, callback) {
|
||||
async.eachSeries(Object.keys(addons), function iterator (addon, iteratorCallback) {
|
||||
if (!(addon in KNOWN_ADDONS)) return iteratorCallback(new Error('No such addon:' + addon));
|
||||
|
||||
KNOWN_ADDONS[addon].restore(app, iteratorCallback);
|
||||
KNOWN_ADDONS[addon].restore(app, addons[addon], iteratorCallback);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
@@ -236,8 +229,9 @@ function getBindsSync(app, addons) {
|
||||
return binds;
|
||||
}
|
||||
|
||||
function allocateOAuthCredentials(app, callback) {
|
||||
function setupOauth(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var appId = app.id;
|
||||
@@ -246,7 +240,7 @@ function allocateOAuthCredentials(app, callback) {
|
||||
var redirectURI = 'https://' + config.appFqdn(app.location);
|
||||
var scope = 'profile,roleUser';
|
||||
|
||||
debugApp(app, 'allocateOAuthCredentials: id:%s clientSecret:%s', id, clientSecret);
|
||||
debugApp(app, 'setupOauth: id:%s clientSecret:%s', id, clientSecret);
|
||||
|
||||
clientdb.delByAppId('addon-' + appId, function (error) { // remove existing creds
|
||||
if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(error);
|
||||
@@ -267,11 +261,12 @@ function allocateOAuthCredentials(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function removeOAuthCredentials(app, callback) {
|
||||
function teardownOauth(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debugApp(app, 'removeOAuthCredentials');
|
||||
debugApp(app, 'teardownOauth');
|
||||
|
||||
clientdb.delByAppId('addon-' + app.id, function (error) {
|
||||
if (error && error.reason !== DatabaseError.NOT_FOUND) console.error(error);
|
||||
@@ -280,8 +275,9 @@ function removeOAuthCredentials(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function setupLdap(app, callback) {
|
||||
function setupLdap(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var env = [
|
||||
@@ -299,8 +295,9 @@ function setupLdap(app, callback) {
|
||||
appdb.setAddonConfig(app.id, 'ldap', env, callback);
|
||||
}
|
||||
|
||||
function teardownLdap(app, callback) {
|
||||
function teardownLdap(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debugApp(app, 'Tearing down LDAP');
|
||||
@@ -308,8 +305,9 @@ function teardownLdap(app, callback) {
|
||||
appdb.unsetAddonConfig(app.id, 'ldap', callback);
|
||||
}
|
||||
|
||||
function setupSendMail(app, callback) {
|
||||
function setupSendMail(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var env = [
|
||||
@@ -324,8 +322,9 @@ function setupSendMail(app, callback) {
|
||||
appdb.setAddonConfig(app.id, 'sendmail', env, callback);
|
||||
}
|
||||
|
||||
function teardownSendMail(app, callback) {
|
||||
function teardownSendMail(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debugApp(app, 'Tearing down sendmail');
|
||||
@@ -333,8 +332,9 @@ function teardownSendMail(app, callback) {
|
||||
appdb.unsetAddonConfig(app.id, 'sendmail', callback);
|
||||
}
|
||||
|
||||
function setupMySql(app, callback) {
|
||||
function setupMySql(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debugApp(app, 'Setting up mysql');
|
||||
@@ -367,7 +367,11 @@ function setupMySql(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function teardownMySql(app, callback) {
|
||||
function teardownMySql(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var container = docker.getContainer('mysql');
|
||||
var cmd = [ '/addons/mysql/service.sh', 'remove', app.id ];
|
||||
|
||||
@@ -389,7 +393,7 @@ function teardownMySql(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function backupMySql(app, callback) {
|
||||
function backupMySql(app, options, callback) {
|
||||
debugApp(app, 'Backing up mysql');
|
||||
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
@@ -408,10 +412,10 @@ function backupMySql(app, callback) {
|
||||
cp.stderr.pipe(process.stderr);
|
||||
}
|
||||
|
||||
function restoreMySql(app, callback) {
|
||||
function restoreMySql(app, options, callback) {
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
|
||||
setupMySql(app, function (error) {
|
||||
setupMySql(app, options, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debugApp(app, 'restoreMySql');
|
||||
@@ -433,8 +437,9 @@ function restoreMySql(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function setupPostgreSql(app, callback) {
|
||||
function setupPostgreSql(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debugApp(app, 'Setting up postgresql');
|
||||
@@ -467,7 +472,11 @@ function setupPostgreSql(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function teardownPostgreSql(app, callback) {
|
||||
function teardownPostgreSql(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var container = docker.getContainer('postgresql');
|
||||
var cmd = [ '/addons/postgresql/service.sh', 'remove', app.id ];
|
||||
|
||||
@@ -489,7 +498,7 @@ function teardownPostgreSql(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function backupPostgreSql(app, callback) {
|
||||
function backupPostgreSql(app, options, callback) {
|
||||
debugApp(app, 'Backing up postgresql');
|
||||
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
@@ -508,10 +517,10 @@ function backupPostgreSql(app, callback) {
|
||||
cp.stderr.pipe(process.stderr);
|
||||
}
|
||||
|
||||
function restorePostgreSql(app, callback) {
|
||||
function restorePostgreSql(app, options, callback) {
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
|
||||
setupPostgreSql(app, function (error) {
|
||||
setupPostgreSql(app, options, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debugApp(app, 'restorePostgreSql');
|
||||
@@ -533,8 +542,9 @@ function restorePostgreSql(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function setupMongoDb(app, callback) {
|
||||
function setupMongoDb(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debugApp(app, 'Setting up mongodb');
|
||||
@@ -567,7 +577,11 @@ function setupMongoDb(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function teardownMongoDb(app, callback) {
|
||||
function teardownMongoDb(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var container = docker.getContainer('mongodb');
|
||||
var cmd = [ '/addons/mongodb/service.sh', 'remove', app.id ];
|
||||
|
||||
@@ -589,7 +603,7 @@ function teardownMongoDb(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function backupMongoDb(app, callback) {
|
||||
function backupMongoDb(app, options, callback) {
|
||||
debugApp(app, 'Backing up mongodb');
|
||||
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
@@ -608,10 +622,10 @@ function backupMongoDb(app, callback) {
|
||||
cp.stderr.pipe(process.stderr);
|
||||
}
|
||||
|
||||
function restoreMongoDb(app, callback) {
|
||||
function restoreMongoDb(app, options, callback) {
|
||||
callback = once(callback); // ChildProcess exit may or may not be called after error
|
||||
|
||||
setupMongoDb(app, function (error) {
|
||||
setupMongoDb(app, options, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debugApp(app, 'restoreMongoDb');
|
||||
@@ -651,7 +665,11 @@ function forwardRedisPort(appId, callback) {
|
||||
}
|
||||
|
||||
// Ensures that app's addon redis container is running. Can be called when named container already exists/running
|
||||
function setupRedis(app, callback) {
|
||||
function setupRedis(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var redisPassword = generatePassword(64, false /* memorable */);
|
||||
var redisVarsFile = path.join(paths.ADDON_CONFIG_DIR, 'redis-' + app.id + '_vars.sh');
|
||||
var redisDataDir = path.join(paths.DATA_DIR, app.id + '/redis');
|
||||
@@ -666,9 +684,13 @@ function setupRedis(app, callback) {
|
||||
name: 'redis-' + app.id,
|
||||
Hostname: config.appFqdn(app.location),
|
||||
Tty: true,
|
||||
Image: 'cloudron/redis:0.3.2', // if you change this, fix setup/INFRA_VERSION as well
|
||||
Image: 'cloudron/redis:0.5.0', // if you change this, fix setup/INFRA_VERSION as well
|
||||
Cmd: null,
|
||||
Volumes: {},
|
||||
Volumes: {
|
||||
'/tmp': {},
|
||||
'/run': {},
|
||||
'/var/log': {}
|
||||
},
|
||||
VolumesFrom: []
|
||||
};
|
||||
|
||||
@@ -686,6 +708,7 @@ function setupRedis(app, callback) {
|
||||
PortBindings: {
|
||||
'6379/tcp': [{ HostPort: '0', HostIp: isMac ? '0.0.0.0' : '127.0.0.1' }]
|
||||
},
|
||||
ReadonlyRootfs: true,
|
||||
RestartPolicy: {
|
||||
'Name': 'always',
|
||||
'MaximumRetryCount': 0
|
||||
@@ -717,7 +740,11 @@ function setupRedis(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function teardownRedis(app, callback) {
|
||||
function teardownRedis(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var container = docker.getContainer('redis-' + app.id);
|
||||
|
||||
var removeOptions = {
|
||||
@@ -739,41 +766,3 @@ function teardownRedis(app, callback) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function allocateAccessToken(app, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var token = tokendb.generateToken();
|
||||
var expiresAt = Number.MAX_SAFE_INTEGER; // basically never expire
|
||||
var scopes = 'profile,users'; // TODO This should be put into the manifest and the user should know those
|
||||
var clientId = ''; // meaningless for apps so far
|
||||
|
||||
tokendb.delByIdentifier(tokendb.PREFIX_APP + app.id, function (error) {
|
||||
if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(error);
|
||||
|
||||
tokendb.add(token, tokendb.PREFIX_APP + app.id, clientId, expiresAt, scopes, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var env = [
|
||||
'CLOUDRON_TOKEN=' + token
|
||||
];
|
||||
|
||||
debugApp(app, 'Setting token addon config to %j', env);
|
||||
|
||||
appdb.setAddonConfig(appId, 'token', env, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function removeAccessToken(app, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
tokendb.delByIdentifier(tokendb.PREFIX_APP + app.id, function (error) {
|
||||
if (error && error.reason !== DatabaseError.NOT_FOUND) console.error(error);
|
||||
|
||||
appdb.unsetAddonConfig(app.id, 'token', callback);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ function setHealth(app, health, callback) {
|
||||
|
||||
debugApp(app, 'marking as unhealthy since not seen for more than %s minutes', UNHEALTHY_THRESHOLD/(60 * 1000));
|
||||
|
||||
mailer.appDied(app);
|
||||
if (app.appStoreId !== '') mailer.appDied(app); // do not send mails for dev apps
|
||||
gHealthInfo[app.id].emailSent = true;
|
||||
} else {
|
||||
debugApp(app, 'waiting for sometime to update the app health');
|
||||
@@ -147,7 +147,9 @@ function processDockerEvents() {
|
||||
if (app) context = context + '\n\n' + JSON.stringify(app, null, 4) + '\n';
|
||||
|
||||
debug('OOM Context: %s', context);
|
||||
mailer.sendCrashNotification(program, context); // app can be null if it's an addon crash
|
||||
|
||||
// do not send mails for dev apps
|
||||
if (app.appStoreId !== '') mailer.sendCrashNotification(program, context); // app can be null if it's an addon crash
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -818,4 +818,3 @@ function restoreApp(app, addonsToRestore, callback) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+36
-18
@@ -51,6 +51,7 @@ var addons = require('./addons.js'),
|
||||
path = require('path'),
|
||||
paths = require('./paths.js'),
|
||||
safe = require('safetydance'),
|
||||
semver = require('semver'),
|
||||
shell = require('./shell.js'),
|
||||
SubdomainError = require('./subdomainerror.js'),
|
||||
subdomains = require('./subdomains.js'),
|
||||
@@ -79,6 +80,14 @@ function debugApp(app, args) {
|
||||
debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
|
||||
}
|
||||
|
||||
function targetBoxVersion(manifest) {
|
||||
if ('targetBoxVersion' in manifest) return manifest.targetBoxVersion;
|
||||
|
||||
if ('minBoxVersion' in manifest) return manifest.minBoxVersion;
|
||||
|
||||
return '0.0.1';
|
||||
}
|
||||
|
||||
// We expect conflicts to not happen despite closing the port (parallel app installs, app update does not reconfigure nginx etc)
|
||||
// https://tools.ietf.org/html/rfc6056#section-3.5 says linux uses random ephemeral port allocation
|
||||
function getFreePort(callback) {
|
||||
@@ -132,11 +141,9 @@ function unconfigureNginx(app, callback) {
|
||||
vbox.unforwardFromHostToVirtualBox(app.id + '-http');
|
||||
}
|
||||
|
||||
function downloadImage(app, callback) {
|
||||
debugApp(app, 'downloadImage %s', app.manifest.dockerImage);
|
||||
|
||||
function pullImage(app, callback) {
|
||||
docker.pull(app.manifest.dockerImage, function (err, stream) {
|
||||
if (err) return callback(new Error('Error connecting to docker'));
|
||||
if (err) return callback(new Error('Error connecting to docker. statusCode: %s' + err.statusCode));
|
||||
|
||||
// https://github.com/dotcloud/docker/issues/1074 says each status message
|
||||
// is emitted as a chunk
|
||||
@@ -158,25 +165,30 @@ function downloadImage(app, callback) {
|
||||
var image = docker.getImage(app.manifest.dockerImage);
|
||||
|
||||
image.inspect(function (err, data) {
|
||||
if (err) {
|
||||
return callback(new Error('Error inspecting image:' + err.message));
|
||||
}
|
||||
|
||||
if (!data || !data.Config) {
|
||||
return callback(new Error('Missing Config in image:' + JSON.stringify(data, null, 4)));
|
||||
}
|
||||
|
||||
if (!data.Config.Entrypoint && !data.Config.Cmd) {
|
||||
return callback(new Error('Only images with entry point are allowed'));
|
||||
}
|
||||
if (err) return callback(new Error('Error inspecting image:' + err.message));
|
||||
if (!data || !data.Config) return callback(new Error('Missing Config in image:' + JSON.stringify(data, null, 4)));
|
||||
if (!data.Config.Entrypoint && !data.Config.Cmd) return callback(new Error('Only images with entry point are allowed'));
|
||||
|
||||
debugApp(app, 'This image exposes ports: %j', data.Config.ExposedPorts);
|
||||
return callback(null);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function downloadImage(app, callback) {
|
||||
debugApp(app, 'downloadImage %s', app.manifest.dockerImage);
|
||||
|
||||
var attempt = 1;
|
||||
|
||||
async.retry({ times: 5, interval: 15000 }, function (retryCallback) {
|
||||
debugApp(app, 'Downloading image. attempt: %s', attempt++);
|
||||
|
||||
pullImage(app, retryCallback);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function createContainer(app, callback) {
|
||||
appdb.getPortBindings(app.id, function (error, portBindings) {
|
||||
if (error) return callback(error);
|
||||
@@ -210,7 +222,12 @@ function createContainer(app, callback) {
|
||||
Image: app.manifest.dockerImage,
|
||||
Cmd: null,
|
||||
Env: env.concat(addonEnv),
|
||||
ExposedPorts: exposedPorts
|
||||
ExposedPorts: exposedPorts,
|
||||
Volumes: { // see also ReadonlyRootfs
|
||||
'/tmp': {},
|
||||
'/run': {},
|
||||
'/var/log': {}
|
||||
}
|
||||
};
|
||||
|
||||
debugApp(app, 'Creating container for %s', app.manifest.dockerImage);
|
||||
@@ -231,7 +248,7 @@ function deleteContainer(app, callback) {
|
||||
|
||||
var removeOptions = {
|
||||
force: true, // kill container if it's running
|
||||
v: false // removes volumes associated with the container
|
||||
v: true // removes volumes associated with the container (but not host mounts)
|
||||
};
|
||||
|
||||
container.remove(removeOptions, function (error) {
|
||||
@@ -347,6 +364,7 @@ function startContainer(app, callback) {
|
||||
MemorySwap: memoryLimit, // Memory + Swap
|
||||
PortBindings: dockerPortBindings,
|
||||
PublishAllPorts: false,
|
||||
ReadonlyRootfs: semver.gte(targetBoxVersion(app.manifest), '0.0.66'), // see also Volumes in startContainer
|
||||
Links: addons.getLinksSync(app, app.manifest.addons),
|
||||
RestartPolicy: {
|
||||
"Name": "always",
|
||||
|
||||
+4
-2
@@ -22,7 +22,9 @@ function addSubdomain(zoneName, subdomain, type, value, callback) {
|
||||
assert.strictEqual(typeof value, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('addSubdomain: ' + subdomain + ' for domain ' + zoneName + ' with value ' + value);
|
||||
var fqdn = subdomain !== '' && type === 'TXT' ? subdomain + '.' + config.fqdn() : config.appFqdn(subdomain);
|
||||
|
||||
debug('addSubdomain: zoneName: %s subdomain: %s type: %s value: %s fqdn: %s', zoneName, subdomain, type, value, fqdn);
|
||||
|
||||
var data = {
|
||||
type: type,
|
||||
@@ -30,7 +32,7 @@ function addSubdomain(zoneName, subdomain, type, value, callback) {
|
||||
};
|
||||
|
||||
superagent
|
||||
.post(config.apiServerOrigin() + '/api/v1/domains/' + config.appFqdn(subdomain))
|
||||
.post(config.apiServerOrigin() + '/api/v1/domains/' + fqdn)
|
||||
.query({ token: config.token() })
|
||||
.send(data)
|
||||
.end(function (error, result) {
|
||||
|
||||
+11
-18
@@ -19,7 +19,8 @@ exports = module.exports = {
|
||||
reboot: reboot,
|
||||
migrate: migrate,
|
||||
backup: backup,
|
||||
ensureBackup: ensureBackup};
|
||||
ensureBackup: ensureBackup
|
||||
};
|
||||
|
||||
var apps = require('./apps.js'),
|
||||
AppsError = require('./apps.js').AppsError,
|
||||
@@ -323,31 +324,23 @@ function addDnsRecords() {
|
||||
function checkIfInSync() {
|
||||
debug('addDnsRecords: Check if admin DNS record is in sync.');
|
||||
|
||||
var allDone = true;
|
||||
|
||||
async.each(changeIds, function (changeId, callback) {
|
||||
async.eachSeries(changeIds, function (changeId, callback) {
|
||||
subdomains.status(changeId, function (error, result) {
|
||||
if (error) return callback(new Error('Failed to check if admin DNS record is in sync.', error));
|
||||
|
||||
if (result !== 'done') allDone = false;
|
||||
if (result !== 'done') return callback(new Error(changeId + ' is not in sync. result:' + result));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}, function (error) {
|
||||
if (error) console.error(error);
|
||||
|
||||
// retry if needed
|
||||
if (error || !allDone) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
gAddDnsRecordsTimerId = setTimeout(checkIfInSync, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
config.set('dnsInSync', true);
|
||||
|
||||
// send heartbeat after the dns records are done
|
||||
sendHeartbeat();
|
||||
|
||||
debug('addDnsRecords: done');
|
||||
config.set('dnsInSync', true);
|
||||
sendHeartbeat(); // send heartbeat after the dns records are done
|
||||
});
|
||||
}
|
||||
|
||||
@@ -473,7 +466,7 @@ function doUpgrade(boxUpdateInfo, callback) {
|
||||
.send({ version: boxUpdateInfo.version })
|
||||
.end(function (error, result) {
|
||||
if (error) return upgradeError(new Error('Error making upgrade request: ' + error));
|
||||
if (result.status !== 202) return upgradeError(new Error('Server not ready to upgrade: ' + result.body));
|
||||
if (result.status !== 202) return upgradeError(new Error(util.format('Server not ready to upgrade. statusCode: %s body: %j', result.status, result.body)));
|
||||
|
||||
progress.set(progress.UPDATE, 10, 'Updating base system');
|
||||
|
||||
@@ -503,7 +496,7 @@ function doUpdate(boxUpdateInfo, callback) {
|
||||
.end(function (error, result) {
|
||||
if (error) return updateError(new Error('Error fetching sourceTarballUrl: ' + error));
|
||||
if (result.status !== 200) return updateError(new Error('Error fetching sourceTarballUrl status: ' + result.status));
|
||||
if (!safe.query(result, 'body.url')) return updateError(new Error('Error fetching sourceTarballUrl response: ' + result.body));
|
||||
if (!safe.query(result, 'body.url')) return updateError(new Error('Error fetching sourceTarballUrl response: ' + JSON.stringify(result.body)));
|
||||
|
||||
// NOTE: the args here are tied to the installer revision, box code and appstore provisioning logic
|
||||
var args = {
|
||||
@@ -531,7 +524,7 @@ function doUpdate(boxUpdateInfo, callback) {
|
||||
|
||||
superagent.post(INSTALLER_UPDATE_URL).send(args).end(function (error, result) {
|
||||
if (error) return updateError(error);
|
||||
if (result.status !== 202) return updateError(new Error('Error initiating update: ' + result.body));
|
||||
if (result.status !== 202) return updateError(new Error('Error initiating update: ' + JSON.stringify(result.body)));
|
||||
|
||||
progress.set(progress.UPDATE, 10, 'Updating cloudron software');
|
||||
|
||||
|
||||
@@ -40,12 +40,17 @@ var appdb = require('../../appdb.js'),
|
||||
|
||||
var SERVER_URL = 'http://localhost:' + config.get('port');
|
||||
|
||||
// Test image information
|
||||
var TEST_IMAGE_REPO = 'cloudron/test';
|
||||
var TEST_IMAGE_TAG = '2.0.1';
|
||||
var TEST_IMAGE_ID = 'f0c6f6fe356b1bb35408d2fafc5cca679ee66125d018082f6695a90a3e5f9ce0';
|
||||
|
||||
var APP_STORE_ID = 'test', APP_ID;
|
||||
var APP_LOCATION = 'appslocation';
|
||||
var APP_LOCATION_2 = 'appslocationtwo';
|
||||
var APP_LOCATION_NEW = 'appslocationnew';
|
||||
var APP_MANIFEST = JSON.parse(fs.readFileSync(__dirname + '/../../../../test-app/CloudronManifest.json', 'utf8'));
|
||||
APP_MANIFEST.dockerImage = 'girish/test:0.2.0';
|
||||
APP_MANIFEST.dockerImage = TEST_IMAGE_REPO + ':' + TEST_IMAGE_TAG;
|
||||
var USERNAME = 'admin', PASSWORD = 'password', EMAIL ='admin@me.com';
|
||||
var USERNAME_1 = 'user', PASSWORD_1 = 'password', EMAIL_1 ='user@me.com';
|
||||
var token = null; // authentication token
|
||||
@@ -162,7 +167,7 @@ describe('App API', function () {
|
||||
|
||||
before(function (done) {
|
||||
dockerProxy = startDockerProxy(function interceptor(req, res) {
|
||||
if (req.method === 'DELETE' && req.url === '/images/c7ddfc8fb7cd8a14d4d70153a199ff0c6e9b709807aeec5a7b799d60618731d1?force=true&noprune=false') {
|
||||
if (req.method === 'DELETE' && req.url === '/images/' + TEST_IMAGE_ID + '?force=true&noprune=false') {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return true;
|
||||
@@ -510,12 +515,12 @@ describe('App installation', function () {
|
||||
async.series([
|
||||
function (callback) {
|
||||
dockerProxy = startDockerProxy(function interceptor(req, res) {
|
||||
if (req.method === 'POST' && req.url === '/images/create?fromImage=girish%2Ftest&tag=0.2.0') {
|
||||
if (req.method === 'POST' && req.url === '/images/create?fromImage=' + encodeURIComponent(TEST_IMAGE_REPO) + '&tag=' + TEST_IMAGE_TAG) {
|
||||
imageCreated = true;
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return true;
|
||||
} else if (req.method === 'DELETE' && req.url === '/images/c7ddfc8fb7cd8a14d4d70153a199ff0c6e9b709807aeec5a7b799d60618731d1?force=true&noprune=false') {
|
||||
} else if (req.method === 'DELETE' && req.url === '/images/' + TEST_IMAGE_ID + '?force=true&noprune=false') {
|
||||
imageDeleted = true;
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
@@ -735,7 +740,7 @@ describe('App installation', function () {
|
||||
child_process.exec('docker exec ' + appContainer.id + ' ' + cmd, { timeout: 5000 }, function (error, stdout, stderr) {
|
||||
expect(!error).to.be.ok();
|
||||
expect(stdout.length).to.be(0);
|
||||
expect(stderr.length).to.be(0);
|
||||
// expect(stderr.length).to.be(0); // "Warning: Using a password on the command line interface can be insecure."
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -959,12 +964,12 @@ describe('App installation - port bindings', function () {
|
||||
async.series([
|
||||
function (callback) {
|
||||
dockerProxy = startDockerProxy(function interceptor(req, res) {
|
||||
if (req.method === 'POST' && req.url === '/images/create?fromImage=girish%2Ftest&tag=0.2.0') {
|
||||
if (req.method === 'POST' && req.url === '/images/create?fromImage=' + encodeURIComponent(TEST_IMAGE_REPO) + '&tag=' + TEST_IMAGE_TAG) {
|
||||
imageCreated = true;
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return true;
|
||||
} else if (req.method === 'DELETE' && req.url === '/images/c7ddfc8fb7cd8a14d4d70153a199ff0c6e9b709807aeec5a7b799d60618731d1?force=true&noprune=false') {
|
||||
} else if (req.method === 'DELETE' && req.url === '/images/' + TEST_IMAGE_ID + '?force=true&noprune=false') {
|
||||
imageDeleted = true;
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
|
||||
@@ -14,7 +14,7 @@ root_password=secret
|
||||
start_postgresql() {
|
||||
postgresql_vars="POSTGRESQL_ROOT_PASSWORD=${root_password}; POSTGRESQL_ROOT_HOST=172.17.0.0/255.255.0.0"
|
||||
|
||||
if which boot2docker >/dev/null; then
|
||||
if which boot2docker >/dev/null 2>&1; then
|
||||
boot2docker ssh "sudo rm -rf /tmp/postgresql_vars.sh"
|
||||
boot2docker ssh "echo \"${postgresql_vars}\" > /tmp/postgresql_vars.sh"
|
||||
else
|
||||
@@ -24,13 +24,15 @@ start_postgresql() {
|
||||
|
||||
docker rm -f postgresql 2>/dev/null 1>&2 || true
|
||||
|
||||
docker run -dtP --name=postgresql -v "${postgresqldatadir}:/var/lib/postgresql" -v /tmp/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh "${POSTGRESQL_IMAGE}" >/dev/null
|
||||
docker run -dtP --name=postgresql -v "${postgresqldatadir}:/var/lib/postgresql" \
|
||||
--read-only -v /tmp -v /run -v /var/log \
|
||||
-v /tmp/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh "${POSTGRESQL_IMAGE}" >/dev/null
|
||||
}
|
||||
|
||||
start_mysql() {
|
||||
local mysql_vars="MYSQL_ROOT_PASSWORD=${root_password}; MYSQL_ROOT_HOST=172.17.0.0/255.255.0.0"
|
||||
|
||||
if which boot2docker >/dev/null; then
|
||||
if which boot2docker >/dev/null 2>&1; then
|
||||
boot2docker ssh "sudo rm -rf /tmp/mysql_vars.sh"
|
||||
boot2docker ssh "echo \"${mysql_vars}\" > /tmp/mysql_vars.sh"
|
||||
else
|
||||
@@ -40,13 +42,15 @@ start_mysql() {
|
||||
|
||||
docker rm -f mysql 2>/dev/null 1>&2 || true
|
||||
|
||||
docker run -dP --name=mysql -v "${mysqldatadir}:/var/lib/mysql" -v /tmp/mysql_vars.sh:/etc/mysql/mysql_vars.sh "${MYSQL_IMAGE}" >/dev/null
|
||||
docker run -dP --name=mysql -v "${mysqldatadir}:/var/lib/mysql" \
|
||||
--read-only -v /tmp -v /run -v /var/log \
|
||||
-v /tmp/mysql_vars.sh:/etc/mysql/mysql_vars.sh "${MYSQL_IMAGE}" >/dev/null
|
||||
}
|
||||
|
||||
start_mongodb() {
|
||||
local mongodb_vars="MONGODB_ROOT_PASSWORD=${root_password}"
|
||||
|
||||
if which boot2docker >/dev/null; then
|
||||
if which boot2docker >/dev/null 2>&1; then
|
||||
boot2docker ssh "sudo rm -rf /tmp/mongodb_vars.sh"
|
||||
boot2docker ssh "echo \"${mongodb_vars}\" > /tmp/mongodb_vars.sh"
|
||||
else
|
||||
@@ -56,7 +60,9 @@ start_mongodb() {
|
||||
|
||||
docker rm -f mongodb 2>/dev/null 1>&2 || true
|
||||
|
||||
docker run -dP --name=mongodb -v "${mongodbdatadir}:/var/lib/mongodb" -v /tmp/mongodb_vars.sh:/etc/mongodb_vars.sh "${MONGODB_IMAGE}" >/dev/null
|
||||
docker run -dP --name=mongodb -v "${mongodbdatadir}:/var/lib/mongodb" \
|
||||
--read-only -v /tmp -v /run -v /var/log \
|
||||
-v /tmp/mongodb_vars.sh:/etc/mongodb_vars.sh "${MONGODB_IMAGE}" >/dev/null
|
||||
}
|
||||
|
||||
start_mysql
|
||||
|
||||
@@ -48,6 +48,8 @@ function uninitialize(callback) {
|
||||
stopAppTask(appId);
|
||||
}
|
||||
|
||||
locker.removeListener('unlocked', startNextTask);
|
||||
|
||||
callback(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ var MANIFEST = {
|
||||
"contactEmail": "support@cloudron.io",
|
||||
"version": "0.1.0",
|
||||
"manifestVersion": 1,
|
||||
"dockerImage": "girish/test:0.2.0",
|
||||
"dockerImage": "cloudron/test:2.0.0",
|
||||
"healthCheckPath": "/",
|
||||
"httpPort": 7777,
|
||||
"tcpPorts": {
|
||||
@@ -136,21 +136,21 @@ describe('apptask', function () {
|
||||
});
|
||||
|
||||
it('allocate OAuth credentials', function (done) {
|
||||
addons._allocateOAuthCredentials(APP, function (error) {
|
||||
addons._setupOauth(APP, {}, function (error) {
|
||||
expect(error).to.be(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('remove OAuth credentials', function (done) {
|
||||
addons._removeOAuthCredentials(APP, function (error) {
|
||||
addons._teardownOauth(APP, {}, function (error) {
|
||||
expect(error).to.be(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('remove OAuth credentials twice succeeds', function (done) {
|
||||
addons._removeOAuthCredentials(APP, function (error) {
|
||||
addons._teardownOauth(APP, {}, function (error) {
|
||||
expect(!error).to.be.ok();
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -34,8 +34,8 @@ for script in "${scripts[@]}"; do
|
||||
fi
|
||||
done
|
||||
|
||||
if ! docker inspect girish/test:0.2.0 >/dev/null 2>/dev/null; then
|
||||
echo "docker pull girish/test:0.2.0 for tests to run"
|
||||
if ! docker inspect cloudron/test:2.0.1 >/dev/null 2>/dev/null; then
|
||||
echo "docker pull cloudron/test:2.0.1 for tests to run"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -616,6 +616,9 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
|
||||
var state = Math.floor((1 + Math.random()) * 0x1000000000000).toString(16).substring(1);
|
||||
window.localStorage.oauth2State = state;
|
||||
|
||||
// stash for further use in login_callback
|
||||
window.localStorage.returnTo = '/' + window.location.hash;
|
||||
|
||||
window.location.href = this.apiOrigin + '/api/v1/oauth/dialog/authorize?response_type=token&client_id=' + this._clientId + '&redirect_uri=' + callbackURL + '&scope=' + scope + '&state=' + state;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,11 @@
|
||||
// clear oauth2 state
|
||||
delete window.localStorage.oauth2State;
|
||||
|
||||
window.location.href = '/';
|
||||
var returnTo = window.localStorage.returnTo;
|
||||
delete window.localStorage.returnTo;
|
||||
|
||||
if (returnTo) window.location.href = returnTo;
|
||||
else window.location.href = '/';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user