Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
837ce0a879 | ||
|
|
cdae1f0d06 | ||
|
|
96468dd931 | ||
|
|
a8949649a8 | ||
|
|
a3fc7e9990 | ||
|
|
c749842eab | ||
|
|
503497dcc7 | ||
|
|
516a822cd8 | ||
|
|
75eb8992a9 |
1
CHANGES
1
CHANGES
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
14
src/appdb.js
14
src/appdb.js
@@ -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 = [ ];
|
||||
|
||||
19
src/apps.js
19
src/apps.js
@@ -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';
|
||||
|
||||
@@ -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!` });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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}`));
|
||||
|
||||
@@ -22,7 +22,7 @@ task_id="$1"
|
||||
if [[ "${task_id}" == "all" ]]; then
|
||||
systemctl list-units --full --no-legend box-task-* # just to show who was running
|
||||
systemctl kill --signal=SIGTERM box-task-* || true
|
||||
systemctl reset-failed box-task-* || true
|
||||
systemctl reset-failed box-task-* 2>/dev/null || true
|
||||
systemctl stop box-task-* || true # because of remain-after-exit in Ubuntu 16 we have to deactivate the service
|
||||
else
|
||||
readonly service_name="box-task-${task_id}"
|
||||
|
||||
@@ -25,10 +25,8 @@ echo "Updating Cloudron with ${source_dir}"
|
||||
|
||||
readonly installer_path="${source_dir}/scripts/installer.sh"
|
||||
|
||||
echo "=> reset service ${UPDATER_SERVICE} status in case it failed"
|
||||
if systemctl reset-failed "${UPDATER_SERVICE}"; then
|
||||
echo "=> service has failed earlier"
|
||||
fi
|
||||
echo "=> reset service ${UPDATER_SERVICE} status (of previous update)"
|
||||
systemctl reset-failed "${UPDATER_SERVICE}" 2>/dev/null || true
|
||||
|
||||
# StandardError will follow StandardOutput in default inherit mode. https://www.freedesktop.org/software/systemd/man/systemd.exec.html
|
||||
echo "=> Run installer.sh as ${UPDATER_SERVICE}."
|
||||
|
||||
Reference in New Issue
Block a user