Files
cloudron-box/src/taskworker.js
2024-11-01 16:15:32 +01:00

120 lines
4.4 KiB
JavaScript
Executable File

#!/usr/bin/env node
'use strict';
const apptask = require('./apptask.js'),
backupCleaner = require('./backupcleaner.js'),
backuptask = require('./backuptask.js'),
dashboard = require('./dashboard.js'),
database = require('./database.js'),
dns = require('./dns.js'),
dyndns = require('./dyndns.js'),
externalLdap = require('./externalldap.js'),
fs = require('fs'),
mailServer = require('./mailserver.js'),
net = require('net'),
reverseProxy = require('./reverseproxy.js'),
safe = require('safetydance'),
system = require('./system.js'),
tasks = require('./tasks.js'),
updater = require('./updater.js');
const TASKS = { // indexed by task type
app: apptask.run,
backup: backuptask.fullBackup,
update: updater.update,
checkCerts: reverseProxy.checkCerts,
prepareDashboardLocation: dashboard.prepareLocation,
cleanBackups: backupCleaner.run,
syncExternalLdap: externalLdap.sync,
changeMailLocation: mailServer.changeLocation,
syncDnsRecords: dns.syncDnsRecords,
syncDyndns: dyndns.sync,
updateDiskUsage: system.updateDiskUsage,
_identity: async (arg, progressCallback) => { progressCallback({}); return arg; },
_error: async (arg, progressCallback) => { progressCallback({}); throw new Error(`Failed for arg: ${arg}`); },
_crash: (arg) => { throw new Error(`Crashing for arg: ${arg}`); }, // the test looks for this debug string in the log file
_sleep: async (arg) => setTimeout(process.exit, arg)
};
if (process.argv.length !== 4) {
console.error('Pass the taskid and logfile as argument');
process.exit(1);
}
const taskId = process.argv[2];
const logFile = process.argv[3];
let logFd = null;
async function setupLogging() {
logFd = fs.openSync(logFile, 'a');
// we used to write using a stream before but it caches internally and there is no way to flush it when things crash
process.stdout.write = process.stderr.write = function (...args) {
const callback = typeof args[args.length-1] === 'function' ? args.pop() : function () {}; // callback is required for fs.write
fs.write.apply(fs, [logFd, ...args, callback]);
};
process.stdout.logFile = logFile; // used by update task
}
// happy eyeballs workaround. see box.js for detailed note
async function setupNetworking() {
net.setDefaultAutoSelectFamilyAttemptTimeout(2500);
}
// this is also used as the 'uncaughtException' handler which can only have synchronous functions
function exitSync(status) {
if (status.error) fs.write(logFd, status.error.stack + '\n', function () {});
fs.fsyncSync(logFd);
fs.closeSync(logFd);
process.exit(status.code);
}
// Main process starts here
const startTime = new Date();
async function main() {
try {
await setupLogging();
await setupNetworking();
await database.initialize();
} catch (initError) {
console.error(initError);
return process.exit(50);
}
const debug = require('debug')('box:taskworker'); // require this here so that logging handler is already setup
process.on('SIGTERM', () => exitSync({ code: 0 })); // sent as timeout notification
// ensure we log task crashes with the task logs. neither console.log nor debug are sync for some reason
process.on('uncaughtException', (error) => exitSync({ error, code: 1 }));
debug(`Starting task ${taskId}. Logs are at ${logFile}`);
const [getError, task] = await safe(tasks.get(taskId));
if (getError) return exitSync({ error: getError, code: 50 });
if (!task) return exitSync({ error: new Error(`Task ${taskId} not found`), code: 50 });
async function progressCallback(progress) {
await safe(tasks.update(taskId, progress), { debug });
}
try {
const [runError, result] = await safe(TASKS[task.type].apply(null, task.args.concat(progressCallback)));
const progress = {
result: result || null,
error: runError ? JSON.parse(JSON.stringify(runError, Object.getOwnPropertyNames(runError))) : null
};
debug(`Task took ${(new Date() - startTime)/1000} seconds`);
await safe(tasks.setCompleted(taskId, progress));
exitSync({ error: runError, code: runError ? 50 : 0 });
} catch (error) {
exitSync({ error, code: 1 }); // do not call setCompleted() intentionally. the task code must be resilient enough to handle it
}
}
main();