Compare commits

...

8 Commits
7.4 ... v5.6.2

Author SHA1 Message Date
Girish Ramakrishnan
cdae1f0d06 restore: disable IP based api calls after all activation tasks
the restore code relies on the status call to get the domain to
redirect. if the IP/v1/cloudron/status does not respond, it will
fail the redirection.

(cherry picked from commit fa8a6bfc8c814b20c8bc04b629732a51e4edcf1f)
2020-10-07 10:57:28 -07:00
Johannes Zellner
96468dd931 Limit log files to last 1000 lines
(cherry picked from commit 645c1b9151)
2020-10-07 17:49:56 +02:00
Johannes Zellner
a8949649a8 For app tickets, send the log files along
(cherry picked from commit 678fca6704)
2020-10-06 13:04:27 -07:00
Johannes Zellner
a3fc7e9990 Support SSH remote enabling on ticket submission
(cherry picked from commit b74fae3762)
2020-10-06 13:04:21 -07:00
Johannes Zellner
c749842eab Add enableSshSupport option to support tickets
(cherry picked from commit 2817ea833a)
2020-10-06 13:04:14 -07:00
Girish Ramakrishnan
503497dcc7 add changes
(cherry picked from commit b7ed6d8463)
2020-10-05 21:32:44 -07:00
Girish Ramakrishnan
516a822cd8 locations (primary, secondary) of an app must be updated together
do the delete first to clear out all the domains. this way, you can
move primary to redirect in a single shot.

(cherry picked from commit 005c33dbb5)
2020-10-05 16:17:09 -07:00
Girish Ramakrishnan
75eb8992a9 Disable focal for now 2020-10-05 12:46:26 -07:00
7 changed files with 75 additions and 29 deletions

View File

@@ -2112,6 +2112,7 @@
* postgresql: set collation order explicity when creating database to C.UTF-8 (for confluence)
* rsync: fix error while goes missing when syncing
* Pre-select app domain by default in the redirection drop down
* robots: preseve leading and trailing whitespaces/newlines
[6.0.0]
* Focal support

View File

@@ -80,8 +80,8 @@ fi
# Only --help works with mismatched ubuntu
ubuntu_version=$(lsb_release -rs)
if [[ "${ubuntu_version}" != "16.04" && "${ubuntu_version}" != "18.04" && "${ubuntu_version}" != "20.04" ]]; then
echo "Cloudron requires Ubuntu 16.04, 18.04 or 20.04" > /dev/stderr
if [[ "${ubuntu_version}" != "16.04" && "${ubuntu_version}" != "18.04" ]]; then
echo "Cloudron requires Ubuntu 16.04 or 18.04" > /dev/stderr
exit 1
fi

View File

@@ -423,14 +423,14 @@ function updateWithConstraints(id, app, constraints, callback) {
}
if ('location' in app && 'domain' in app) { // must be updated together as they are unique together
queries.push({ query: 'UPDATE subdomains SET subdomain = ?, domain = ? WHERE appId = ? AND type = ?', args: [ app.location, app.domain, id, exports.SUBDOMAIN_TYPE_PRIMARY ]});
}
queries.push({ query: 'DELETE FROM subdomains WHERE appId = ?', args: [ id ]}); // all locations of an app must be updated together
queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, app.domain, app.location, exports.SUBDOMAIN_TYPE_PRIMARY ]});
if ('alternateDomains' in app) {
queries.push({ query: 'DELETE FROM subdomains WHERE appId = ? AND type = ?', args: [ id, exports.SUBDOMAIN_TYPE_REDIRECT ]});
app.alternateDomains.forEach(function (d) {
queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, exports.SUBDOMAIN_TYPE_REDIRECT ]});
});
if ('alternateDomains' in app) {
app.alternateDomains.forEach(function (d) {
queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, exports.SUBDOMAIN_TYPE_REDIRECT ]});
});
}
}
var fields = [ ], values = [ ];

View File

@@ -41,6 +41,7 @@ exports = module.exports = {
backup: backup,
listBackups: listBackups,
getLocalLogfilePaths: getLocalLogfilePaths,
getLogs: getLogs,
start: start,
@@ -1365,6 +1366,19 @@ function update(app, data, auditSource, callback) {
});
}
function getLocalLogfilePaths(app) {
assert.strictEqual(typeof app, 'object');
const appId = app.id;
var filePaths = [];
filePaths.push(path.join(paths.LOG_DIR, appId, 'apptask.log'));
filePaths.push(path.join(paths.LOG_DIR, appId, 'app.log'));
if (app.manifest.addons && app.manifest.addons.redis) filePaths.push(path.join(paths.LOG_DIR, `redis-${appId}/app.log`));
return filePaths;
}
function getLogs(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert(options && typeof options === 'object');
@@ -1384,11 +1398,8 @@ function getLogs(app, options, callback) {
var args = [ '--lines=' + lines ];
if (follow) args.push('--follow', '--retry', '--quiet'); // same as -F. to make it work if file doesn't exist, --quiet to not output file headers, which are no logs
args.push(path.join(paths.LOG_DIR, appId, 'apptask.log'));
args.push(path.join(paths.LOG_DIR, appId, 'app.log'));
if (app.manifest.addons && app.manifest.addons.redis) args.push(path.join(paths.LOG_DIR, `redis-${appId}/app.log`));
var cp = spawn('/usr/bin/tail', args);
var cp = spawn('/usr/bin/tail', args.concat(getLocalLogfilePaths(app)));
var transformStream = split(function mapper(line) {
if (format !== 'json') return line + '\n';

View File

@@ -32,11 +32,13 @@ var apps = require('./apps.js'),
constants = require('./constants.js'),
debug = require('debug')('box:appstore'),
eventlog = require('./eventlog.js'),
path = require('path'),
paths = require('./paths.js'),
safe = require('safetydance'),
semver = require('semver'),
settings = require('./settings.js'),
superagent = require('superagent'),
support = require('./support.js'),
util = require('util');
// These are the default options and will be adjusted once a subscription state is obtained
@@ -406,26 +408,55 @@ function createTicket(info, auditSource, callback) {
apps.get(info.appId, callback);
}
function enableSshIfNeeded(callback) {
if (!info.enableSshSupport) return callback();
support.enableRemoteSupport(true, auditSource, function (error) {
// ensure we can at least get the ticket through
if (error) console.error('Unable to enable SSH support.', error);
callback();
});
}
getCloudronToken(function (error, token) {
if (error) return callback(error);
collectAppInfoIfNeeded(function (error, result) {
enableSshIfNeeded(function (error) {
if (error) return callback(error);
if (result) info.app = result;
let url = settings.apiServerOrigin() + '/api/v1/ticket';
collectAppInfoIfNeeded(function (error, app) {
if (error) return callback(error);
if (app) info.app = app;
info.supportEmail = constants.SUPPORT_EMAIL; // destination address for tickets
info.supportEmail = constants.SUPPORT_EMAIL; // destination address for tickets
superagent.post(url).query({ accessToken: token }).send(info).timeout(10 * 1000).end(function (error, result) {
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
var req = superagent.post(`${settings.apiServerOrigin()}/api/v1/ticket`)
.query({ accessToken: token })
.timeout(20 * 1000);
eventlog.add(eventlog.ACTION_SUPPORT_TICKET, auditSource, info);
// either send as JSON through body or as multipart, depending on attachments
if (info.app) {
req.field('infoJSON', JSON.stringify(info));
callback(null, { message: `An email for sent to ${constants.SUPPORT_EMAIL}. We will get back shortly!` });
apps.getLocalLogfilePaths(info.app).forEach(function (filePath) {
var logs = safe.child_process.execSync(`tail --lines=1000 ${filePath}`);
if (logs) req.attach(path.basename(filePath), logs, path.basename(filePath));
});
} else {
req.send(info);
}
req.end(function (error, result) {
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
eventlog.add(eventlog.ACTION_SUPPORT_TICKET, auditSource, info);
callback(null, { message: `An email for sent to ${constants.SUPPORT_EMAIL}. We will get back shortly!` });
});
});
});
});

View File

@@ -78,15 +78,17 @@ function onActivated(callback) {
// 1. mail bounces can now be sent to the cloudron owner
// 2. the restore code path can run without sudo (since mail/ is non-root)
async.series([
(done) => reverseProxy.writeDefaultConfig({ activated :true }, done), // update IP based nginx config once user is created
platform.start,
cron.startJobs,
function checkBackupConfiguration(callback) {
function checkBackupConfiguration(done) {
backups.checkConfiguration(function (error, message) {
if (error) return callback(error);
notifications.alert(notifications.ALERT_BACKUP_CONFIG, 'Backup configuration is unsafe', message, callback);
if (error) return done(error);
notifications.alert(notifications.ALERT_BACKUP_CONFIG, 'Backup configuration is unsafe', message, done);
});
}
},
// disable responding to api calls via IP to not leak domain info. this is carefully placed as the last item, so it buys
// the UI some time to query the dashboard domain in the restore code path
(done) => setTimeout(() => reverseProxy.writeDefaultConfig({ activated :true }, done), 30000)
], callback);
}

View File

@@ -41,6 +41,7 @@ function createTicket(req, res, next) {
if (typeof req.body.description !== 'string' || !req.body.description) return next(new HttpError(400, 'description must be string'));
if (req.body.appId && typeof req.body.appId !== 'string') return next(new HttpError(400, 'appId must be string'));
if (req.body.altEmail && typeof req.body.altEmail !== 'string') return next(new HttpError(400, 'altEmail must be string'));
if (req.body.enableSshSupport && typeof req.body.enableSshSupport !== 'boolean') return next(new HttpError(400, 'enableSshSupport must be a boolean'));
settings.getSupportConfig(function (error, supportConfig) {
if (error) return next(new HttpError(503, `Error getting support config: ${error.message}`));