mirror of https://github.com/xwiki-labs/cryptpad
implement persistence of config changes from admin panel
This commit is contained in:
parent
54b32690b2
commit
7aa7d5978f
|
@ -4,6 +4,7 @@ const nThen = require("nthen");
|
|||
const getFolderSize = require("get-folder-size");
|
||||
const Util = require("../common-util");
|
||||
const Ulimit = require("ulimit");
|
||||
const Decrees = require("../decrees");
|
||||
|
||||
var Fs = require("fs");
|
||||
|
||||
|
@ -175,6 +176,8 @@ var restoreArchivedDocument = function (Env, Server, cb) {
|
|||
};
|
||||
|
||||
// CryptPad_AsyncStore.rpc.send('ADMIN', ['SET_DEFAULT_STORAGE_LIMIT', 1024 * 1024 * 1024 /* 1GB */], console.log)
|
||||
// XXX make this a decree
|
||||
// XXX expose this via the admin panel (with UI)
|
||||
var setDefaultStorageLimit = function (Env, Server, cb, data) {
|
||||
var value = Array.isArray(data) && data[1];
|
||||
if (typeof(value) !== 'number' || value <= 0) { return void cb('EINVAL'); }
|
||||
|
@ -190,6 +193,56 @@ var setDefaultStorageLimit = function (Env, Server, cb, data) {
|
|||
cb(void 0, change);
|
||||
};
|
||||
|
||||
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['RESTRICT_REGISTRATION', [true]]], console.log)
|
||||
var adminDecree = function (Env, Server, cb, data, safeKey) {
|
||||
var value = data[1];
|
||||
if (!Array.isArray(value)) { return void cb('INVALID_DECREE'); }
|
||||
|
||||
var command = value[0];
|
||||
var args = value[1];
|
||||
var unsafeKey = Util.unescapeKeyCharacters(safeKey);
|
||||
|
||||
/*
|
||||
|
||||
The admin should have sent a command to be run:
|
||||
|
||||
the server adds two pieces of information to the supplied decree:
|
||||
|
||||
* the unsafeKey of the admin who uploaded it
|
||||
* the current time
|
||||
|
||||
1. test the command to see if it's valid and will result in a change
|
||||
2. if so, apply it and write it to the log for persistence
|
||||
3. respond to the admin with an error or nothing
|
||||
|
||||
*/
|
||||
|
||||
var decree = [command, args, unsafeKey, +new Date()];
|
||||
var changed;
|
||||
try {
|
||||
changed = Decrees.handleCommand(Env, decree) || false;
|
||||
} catch (err) {
|
||||
return void cb(err);
|
||||
}
|
||||
|
||||
if (!changed) { return void cb(); }
|
||||
Decrees.write(Env, decree, cb);
|
||||
};
|
||||
|
||||
// CryptPad_AsyncStore.rpc.send('ADMIN', ['INSTANCE_STATUS], console.log)
|
||||
var instanceStatus = function (Env, Server, cb) {
|
||||
cb(void 0, {
|
||||
restrictRegistration: Boolean(Env.restrictRegistration),
|
||||
launchTime: Env.launchTime,
|
||||
currentTime: +new Date(),
|
||||
|
||||
accountRetentionTime: Env.accountRetentionTime,
|
||||
archiveRetentionTime: Env.archiveRetentionTime,
|
||||
|
||||
defaultStorageLimit: Env.defaultStorageLimit,
|
||||
});
|
||||
};
|
||||
|
||||
var commands = {
|
||||
ACTIVE_SESSIONS: getActiveSessions,
|
||||
ACTIVE_PADS: getActiveChannelCount,
|
||||
|
@ -204,6 +257,9 @@ var commands = {
|
|||
|
||||
ARCHIVE_DOCUMENT: archiveDocument,
|
||||
RESTORE_ARCHIVED_DOCUMENT: restoreArchivedDocument,
|
||||
|
||||
ADMIN_DECREE: adminDecree,
|
||||
INSTANCE_STATUS: instanceStatus,
|
||||
};
|
||||
|
||||
Admin.command = function (Env, safeKey, data, _cb, Server) {
|
||||
|
@ -218,10 +274,9 @@ Admin.command = function (Env, safeKey, data, _cb, Server) {
|
|||
var command = commands[data[0]];
|
||||
|
||||
if (typeof(command) === 'function') {
|
||||
return void command(Env, Server, cb, data);
|
||||
return void command(Env, Server, cb, data, safeKey);
|
||||
}
|
||||
|
||||
return void cb('UNHANDLED_ADMIN_COMMAND');
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
var Decrees = module.exports;
|
||||
|
||||
/* Admin decrees which modify global server state
|
||||
|
||||
IMPLEMENTED:
|
||||
|
||||
RESTRICT_REGISTRATION
|
||||
|
||||
NOT IMPLEMENTED:
|
||||
|
||||
ADD_QUOTA
|
||||
RM_QUOTA
|
||||
UPDATE_QUOTA
|
||||
ADD_INVITE
|
||||
REVOKE_INVITE
|
||||
REDEEM_INVITE
|
||||
|
||||
|
||||
*/
|
||||
|
||||
var commands = {};
|
||||
/* commands have a simple API:
|
||||
|
||||
* they receive the global Env and the arguments to be applied
|
||||
* if the arguments are invalid the operation will not be applied
|
||||
* the command throws
|
||||
* if the arguments are valid but do not result in a change, the operation is redundant.
|
||||
* return false
|
||||
* if the arguments are valid and will result in a change, the operation should be applied
|
||||
* apply it
|
||||
* return true to indicate that it was applied
|
||||
|
||||
*/
|
||||
|
||||
// Toggles a simple boolean
|
||||
commands.RESTRICT_REGISTRATION = function (Env, args) {
|
||||
if (!Array.isArray(args) || typeof(args[0]) !== 'boolean') {
|
||||
throw new Error('INVALID_ARGS');
|
||||
}
|
||||
var bool = args[0];
|
||||
if (bool === Env.restrictRegistration) { return false; }
|
||||
Env.restrictRegistration = bool;
|
||||
return true;
|
||||
};
|
||||
|
||||
// [<command>, <args>, <author>, <time>]
|
||||
var handleCommand = Decrees.handleCommand = function (Env, line) {
|
||||
var command = line[0];
|
||||
var args = line[1];
|
||||
|
||||
if (typeof(commands[command]) !== 'function') {
|
||||
console.error(line);
|
||||
throw new Error("DECREE_UNSUPPORTED_COMMAND");
|
||||
}
|
||||
|
||||
return commands[command](Env, args);
|
||||
};
|
||||
|
||||
Decrees.createLineHandler = function (Env) {
|
||||
var Log = Env.Log;
|
||||
|
||||
var index = -1;
|
||||
|
||||
return function (err, line) {
|
||||
index++;
|
||||
if (err) {
|
||||
// Log the error and bail out
|
||||
return void Log.error("DECREE_LINE_ERR", {
|
||||
error: err.message,
|
||||
index: index,
|
||||
line: line,
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(line)) {
|
||||
try {
|
||||
return void handleCommand(Env, line);
|
||||
} catch (err2) {
|
||||
return void Log.error("DECREE_COMMAND_ERR", {
|
||||
error: err2.message,
|
||||
index: index,
|
||||
line: line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Log.error("DECREE_HANDLER_WEIRD_LINE", {
|
||||
line: line,
|
||||
index: index,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
var Fs = require("fs");
|
||||
var Path = require("path");
|
||||
var readFileBin = require("./stream-file").readFileBin;
|
||||
var Util = require("./common-util");
|
||||
var Schedule = require("./schedule");
|
||||
|
||||
Decrees.load = function (Env, cb) {
|
||||
Env.scheduleDecree = Env.scheduleDecree || Schedule();
|
||||
|
||||
var decreePath = Env.paths.decree;
|
||||
var decreeName = Path.join(Env.paths.decree, 'decree.ndjson'); // XXX
|
||||
|
||||
var stream = Fs.createReadStream(decreeName, {start: 0});
|
||||
|
||||
var handler = Decrees.createLineHandler(Env);
|
||||
|
||||
Env.scheduleDecree.blocking('', function (unblock) {
|
||||
var done = Util.once(Util.both(cb, unblock));
|
||||
readFileBin(stream, function (msgObj, next) {
|
||||
var text = msgObj.buff.toString('utf8');
|
||||
try {
|
||||
handler(void 0, JSON.parse(text));
|
||||
} catch (err) {
|
||||
handler(err);
|
||||
}
|
||||
next();
|
||||
}, function (err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Decrees.write = function (Env, decree, _cb) {
|
||||
var path = Path.join(Env.paths.decree, 'decree.ndjson');
|
||||
Env.scheduleDecree.ordered('', function (next) {
|
||||
var cb = Util.both(_cb, next);
|
||||
Fs.appendFile(path, JSON.stringify(decree) + '\n', cb);
|
||||
});
|
||||
};
|
|
@ -32,6 +32,8 @@ module.exports.create = function (config, cb) {
|
|||
// store
|
||||
id: Crypto.randomBytes(8).toString('hex'),
|
||||
|
||||
launchTime: +new Date(),
|
||||
|
||||
inactiveTime: config.inactiveTime,
|
||||
archiveRetentionTime: config.archiveRetentionTime,
|
||||
accountRetentionTime: config.accountRetentionTime,
|
||||
|
@ -60,6 +62,7 @@ module.exports.create = function (config, cb) {
|
|||
netfluxUsers: {},
|
||||
|
||||
pinStore: undefined,
|
||||
// XXX deprecated?
|
||||
pinnedPads: {},
|
||||
pinsLoaded: false,
|
||||
pendingPinInquiries: {},
|
||||
|
@ -100,6 +103,7 @@ module.exports.create = function (config, cb) {
|
|||
paths.data = keyOrDefaultString('filePath', './datastore');
|
||||
paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
|
||||
paths.blob = keyOrDefaultString('blobPath', './blob');
|
||||
paths.decree = keyOrDefaultString('decreePath', './data/');
|
||||
|
||||
Env.defaultStorageLimit = typeof(config.defaultStorageLimit) === 'number' && config.defaultStorageLimit >= 0?
|
||||
config.defaultStorageLimit:
|
||||
|
@ -306,6 +310,17 @@ module.exports.create = function (config, cb) {
|
|||
});
|
||||
}, 60 * 1000);
|
||||
}).nThen(function () {
|
||||
// XXX scan the decree log to update Env
|
||||
var Decrees = require("./decrees");
|
||||
|
||||
Decrees.load(Env, function (err) {
|
||||
console.log("DONE LOADING DECREES"); // XXX
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}).nThen(function () {
|
||||
|
||||
RPC.create(Env, function (err, _rpc) {
|
||||
if (err) { throw err; }
|
||||
|
||||
|
|
Loading…
Reference in New Issue