run update as a task, so it is cancelable
This commit is contained in:
+1
-1
@@ -58,7 +58,7 @@ mkdir -p "${PLATFORM_DATA_DIR}/collectd/collectd.conf.d"
|
||||
mkdir -p "${PLATFORM_DATA_DIR}/logrotate.d"
|
||||
mkdir -p "${PLATFORM_DATA_DIR}/acme"
|
||||
mkdir -p "${PLATFORM_DATA_DIR}/backup"
|
||||
mkdir -p "${PLATFORM_DATA_DIR}/logs/backup"
|
||||
mkdir -p "${PLATFORM_DATA_DIR}/logs/backup" "${PLATFORM_DATA_DIR}/logs/updater"
|
||||
mkdir -p "${PLATFORM_DATA_DIR}/update"
|
||||
|
||||
mkdir -p "${BOX_DATA_DIR}/appicons"
|
||||
|
||||
@@ -35,4 +35,5 @@ exports = module.exports = {
|
||||
LOG_DIR: path.join(config.baseDir(), 'platformdata/logs'),
|
||||
// this pattern is for the cloudron logs API route to work
|
||||
BACKUP_LOG_FILE: path.join(config.baseDir(), 'platformdata/logs/backup/app.log'),
|
||||
UPDATER_LOG_FILE: path.join(config.baseDir(), 'platformdata/logs/updater/app.log')
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ fi
|
||||
|
||||
readonly UPDATER_SERVICE="cloudron-updater"
|
||||
readonly DATETIME=`date '+%Y-%m-%d_%H-%M-%S'`
|
||||
readonly LOG_FILE="/var/log/cloudron-updater-${DATETIME}.log"
|
||||
readonly LOG_FILE="/home/yellowtent/platformdata/logs/updater/cloudron-updater-${DATETIME}.log"
|
||||
|
||||
if [[ $# == 1 && "$1" == "--check" ]]; then
|
||||
echo "OK"
|
||||
|
||||
+23
-9
@@ -3,7 +3,6 @@
|
||||
exports = module.exports = {
|
||||
update: update,
|
||||
get: get,
|
||||
clear: clear,
|
||||
|
||||
startTask: startTask,
|
||||
stopTask: stopTask,
|
||||
@@ -25,18 +24,27 @@ let assert = require('assert'),
|
||||
paths = require('./paths.js'),
|
||||
safe = require('safetydance'),
|
||||
taskdb = require('./taskdb.js'),
|
||||
util = require('util');
|
||||
util = require('util'),
|
||||
_ = require('underscore');
|
||||
|
||||
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
|
||||
const TASKS = {
|
||||
'backup': {
|
||||
backup: {
|
||||
lock: locker.OP_FULL_BACKUP,
|
||||
logFile: paths.BACKUP_LOG_FILE,
|
||||
program: __dirname + '/tasks/backuptask.js',
|
||||
onFailure: mailer.backupFailed,
|
||||
startEventId: eventlog.ACTION_BACKUP_START,
|
||||
finishEventId: eventlog.ACTION_BACKUP_FINISH
|
||||
},
|
||||
update: {
|
||||
lock: locker.OP_BOX_UPDATE,
|
||||
logFile: paths.UPDATER_LOG_FILE,
|
||||
program: __dirname + '/tasks/updatertask.js',
|
||||
onFailure: NOOP_CALLBACK,
|
||||
startEventId: eventlog.ACTION_UPDATE,
|
||||
finishEventId: eventlog.ACTION_UPDATE
|
||||
}
|
||||
};
|
||||
|
||||
@@ -93,11 +101,12 @@ function get(id, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function clear(id, callback) {
|
||||
function clear(id, args, callback) {
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
assert(args && typeof args === 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
update(id, { percent: 0, message: 'Starting', result: '', errorMessage: '', args: {} }, callback);
|
||||
update(id, { percent: 0, message: 'Starting', result: '', errorMessage: '', args: args }, callback);
|
||||
}
|
||||
|
||||
function startTask(id, args, auditSource, callback) {
|
||||
@@ -124,17 +133,22 @@ function startTask(id, args, auditSource, callback) {
|
||||
// when parent process dies, this process is killed because KillMode=control-group in systemd unit file
|
||||
assert(!gTasks[id], 'Task is already running');
|
||||
|
||||
clear(id, NOOP_CALLBACK);
|
||||
clear(id, args, NOOP_CALLBACK);
|
||||
eventlog.add(taskInfo.startEventId, auditSource, args);
|
||||
|
||||
gTasks[id] = child_process.fork(taskInfo.program, [], { stdio: [ 'pipe', fd, fd, 'ipc' ]});
|
||||
gTasks[id] = child_process.fork(taskInfo.program, [], { stdio: [ 'pipe', fd, fd, 'ipc' ]}); // fork requires ipc
|
||||
gTasks[id].once('exit', function (code, signal) {
|
||||
debug(`startTask: ${id} completed with code ${code} and signal ${signal}`);
|
||||
|
||||
get(id, function (error, progress) {
|
||||
if (!error && progress.errorMessage) error = new Error(progress.errorMessage);
|
||||
if (!error && progress.percent !== 100) { // task crashed or was killed by us (code 50)
|
||||
error = code === 0 ? new Error(`${id} task stopped`) : new Error(`${id} task crashed with code ${code} and signal ${signal}`);
|
||||
update(id, { percent: 100, errorMessage: error.message }, NOOP_CALLBACK);
|
||||
} else if (!error && progress.errorMessage) {
|
||||
error = new Error(progress.errorMessage);
|
||||
}
|
||||
|
||||
eventlog.add(taskInfo.finishEventId, auditSource, { errorMessage: error ? error.message : null, backupId: progress ? progress.result : null });
|
||||
eventlog.add(taskInfo.finishEventId, auditSource, _.extend({ errorMessage: error ? error.message : null }, progress ? progress.result : {}));
|
||||
|
||||
locker.unlock(taskInfo.lock);
|
||||
|
||||
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
require('supererror')({ splatchError: true });
|
||||
|
||||
let database = require('../database.js'),
|
||||
debug = require('debug')('box:updatertask'),
|
||||
tasks = require('../tasks.js'),
|
||||
updater = require('../updater.js');
|
||||
|
||||
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
|
||||
process.on('SIGTERM', function () {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
function exit(error) {
|
||||
if (!error) process.exit(0);
|
||||
|
||||
debug(error);
|
||||
process.exit(50);
|
||||
}
|
||||
|
||||
// Main process starts here
|
||||
debug('Staring update');
|
||||
database.initialize(function (error) {
|
||||
if (error) return exit(error);
|
||||
|
||||
tasks.get(tasks.TASK_UPDATE, function (error, result) {
|
||||
if (error) return exit(error);
|
||||
if (!result.args.boxUpdateInfo) return exit(new Error('Invalid args:' + JSON.stringify(result)));
|
||||
|
||||
updater.update(result.args.boxUpdateInfo, (progress) => tasks.update(tasks.TASK_UPDATE, progress, NOOP_CALLBACK), function (updateError) {
|
||||
const progress = { percent: 100, errorMessage: updateError ? updateError.message : '' };
|
||||
|
||||
tasks.update(tasks.TASK_UPDATE, progress, () => exit(updateError));
|
||||
});
|
||||
});
|
||||
});
|
||||
+13
-41
@@ -2,6 +2,7 @@
|
||||
|
||||
exports = module.exports = {
|
||||
updateToLatest: updateToLatest,
|
||||
update: update,
|
||||
|
||||
UpdaterError: UpdaterError
|
||||
};
|
||||
@@ -13,8 +14,6 @@ var assert = require('assert'),
|
||||
config = require('./config.js'),
|
||||
crypto = require('crypto'),
|
||||
debug = require('debug')('box:updater'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
locker = require('./locker.js'),
|
||||
mkdirp = require('mkdirp'),
|
||||
os = require('os'),
|
||||
path = require('path'),
|
||||
@@ -27,7 +26,6 @@ var assert = require('assert'),
|
||||
|
||||
const RELEASES_PUBLIC_KEY = path.join(__dirname, 'releases.gpg');
|
||||
const UPDATE_CMD = path.join(__dirname, 'scripts/update.sh');
|
||||
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
|
||||
function UpdaterError(reason, errorOrMessage) {
|
||||
assert.strictEqual(typeof reason, 'string');
|
||||
@@ -152,30 +150,28 @@ function downloadAndVerifyRelease(updateInfo, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function doUpdate(boxUpdateInfo, callback) {
|
||||
function update(boxUpdateInfo, progressCallback, callback) {
|
||||
assert(boxUpdateInfo && typeof boxUpdateInfo === 'object');
|
||||
assert.strictEqual(typeof progressCallback, 'function');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
function updateError(e) {
|
||||
tasks.update(tasks.TASK_UPDATE, { percent: -1, errorMessage: e.message }, NOOP_CALLBACK);
|
||||
callback(e);
|
||||
}
|
||||
|
||||
tasks.update(tasks.TASK_UPDATE, { percent: 5, message: 'Downloading and verifying release' }, NOOP_CALLBACK);
|
||||
progressCallback({ percent: 5, message: 'Downloading and verifying release' });
|
||||
|
||||
downloadAndVerifyRelease(boxUpdateInfo, function (error, packageInfo) {
|
||||
if (error) return updateError(error);
|
||||
if (error) return callback(error);
|
||||
|
||||
tasks.update(tasks.TASK_UPDATE, { percent: 10, message: 'Backing up' }, NOOP_CALLBACK);
|
||||
progressCallback({ percent: 10, message: 'Backing up' });
|
||||
|
||||
backups.backupBoxAndApps((progress) => tasks.update(tasks.TASK_MIGRATE, { percent: 10+progress.percent*70/100, message: progress.message }, NOOP_CALLBACK), function (error) {
|
||||
if (error) return updateError(error);
|
||||
backups.backupBoxAndApps((progress) => progressCallback({ percent: 10+progress.percent*70/100, message: progress.message }), function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debug('updating box %s', boxUpdateInfo.sourceTarballUrl);
|
||||
|
||||
tasks.update(tasks.TASK_UPDATE, { percent: 70, message: 'Installing update' }, NOOP_CALLBACK);
|
||||
progressCallback({ percent: 70, message: 'Installing update' });
|
||||
|
||||
// run installer.sh from new box code as a separate service
|
||||
shell.sudo('update', [ UPDATE_CMD, packageInfo.file ], {}, function (error) {
|
||||
if (error) return updateError(error);
|
||||
if (error) return callback(error);
|
||||
|
||||
// Do not add any code here. The installer script will stop the box code any instant
|
||||
});
|
||||
@@ -183,30 +179,6 @@ function doUpdate(boxUpdateInfo, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function update(boxUpdateInfo, auditSource, callback) {
|
||||
assert(boxUpdateInfo && typeof boxUpdateInfo === 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var error = locker.lock(locker.OP_BOX_UPDATE);
|
||||
if (error) return callback(new UpdaterError(UpdaterError.BAD_STATE, error.message));
|
||||
|
||||
eventlog.add(eventlog.ACTION_UPDATE, auditSource, { boxUpdateInfo: boxUpdateInfo });
|
||||
|
||||
// ensure tools can 'wait' on progress
|
||||
tasks.update(tasks.TASK_UPDATE, { percent: 0, message: 'Starting' }, NOOP_CALLBACK);
|
||||
|
||||
debug('Starting update');
|
||||
doUpdate(boxUpdateInfo, function (error) {
|
||||
if (error) {
|
||||
debug('Update failed with error:', error);
|
||||
locker.unlock(locker.OP_BOX_UPDATE);
|
||||
}
|
||||
});
|
||||
|
||||
callback(null);
|
||||
}
|
||||
|
||||
function updateToLatest(auditSource, callback) {
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
@@ -215,5 +187,5 @@ function updateToLatest(auditSource, callback) {
|
||||
if (!boxUpdateInfo) return callback(new UpdaterError(UpdaterError.ALREADY_UPTODATE, 'No update available'));
|
||||
if (!boxUpdateInfo.sourceTarballUrl) return callback(new UpdaterError(UpdaterError.BAD_STATE, 'No automatic update available'));
|
||||
|
||||
update(boxUpdateInfo, auditSource, callback);
|
||||
tasks.startTask(tasks.TASK_UPDATE, { boxUpdateInfo }, auditSource, callback);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user