Restore pad to new location at a given revision
This script gives an admin with shell access the ability to restore a pad at a given revision by essentially rebuilding it at a new location with data associated with the original pad. The upsides to creating a new pad vs. changing the original are: 1) avoiding service disruptions (no deletes, no moving targets - builds from previous revision); and 2) preservation of data (no deletes, no overwriting of the source pad). The most obvious downside is the pad has a new ID which could require folks to update their links, bookmarks, etc. to point at the new location.
This commit is contained in:
parent
ff9a2a687f
commit
01f6d85371
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
This is a repair tool. It rebuilds an old pad at a new pad location up to a
|
||||||
|
known "good" revision.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(process.argv.length != 4) {
|
||||||
|
console.error("Use: node bin/repairPad.js $PADID $REV");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var npm = require("../src/node_modules/npm");
|
||||||
|
var async = require("../src/node_modules/async");
|
||||||
|
var ueberDB = require("../src/node_modules/ueberDB");
|
||||||
|
|
||||||
|
var padId = process.argv[2];
|
||||||
|
var newRevHead = process.argv[3];
|
||||||
|
var newPadId = padId + "-rebuilt";
|
||||||
|
|
||||||
|
var db, pad, newPad, settings;
|
||||||
|
var AuthorManager, ChangeSet, PadManager;
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
function(callback) {
|
||||||
|
npm.load({}, function(err) {
|
||||||
|
if(err) {
|
||||||
|
console.error("Could not load NPM: " + err)
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
function(callback) {
|
||||||
|
// Get a handle into the database
|
||||||
|
db = require('../src/node/db/DB');
|
||||||
|
db.init(callback);
|
||||||
|
}, function(callback) {
|
||||||
|
// Get references to the original pad and to a newly created pad
|
||||||
|
// HACK: This is a standalone script, so we want to write everything
|
||||||
|
// out to the database immediately. The only problem with this is
|
||||||
|
// that a driver (like the mysql driver) hardcodes these values.
|
||||||
|
db.db.db.settings = {cache: 0, writeInterval: 0, json: true};
|
||||||
|
PadManager = require('../src/node/db/PadManager');
|
||||||
|
PadManager.getPad(padId, function(err, _pad) {
|
||||||
|
pad = _pad;
|
||||||
|
PadManager.getPad(newPadId, function(err, _newPad) {
|
||||||
|
newPad = _newPad;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, function(callback) {
|
||||||
|
// Clone all Chat revisions
|
||||||
|
var chatHead = pad.chatHead;
|
||||||
|
for(var i = 0; i <= chatHead; i++) {
|
||||||
|
db.db.get("pad:" + padId + ":chat:" + i, function (err, chat) {
|
||||||
|
db.db.set("pad:" + newPadId + ":chat:" + i, chat);
|
||||||
|
console.log("Created: Chat Revision: pad:" + newPadId + ":chat:" + i)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}, function(callback) {
|
||||||
|
// Rebuild Pad from revisions up to and including the new revision head
|
||||||
|
AuthorManager = require("../src/node/db/AuthorManager");
|
||||||
|
Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||||
|
// Author attributes are derived from changesets, but there can also be
|
||||||
|
// non-author attributes with specific mappings that changesets depend on
|
||||||
|
// and, AFAICT, cannot be recreated any other way
|
||||||
|
newPad.pool.numToAttrib = pad.pool.numToAttrib;
|
||||||
|
for(var i = 1; i <= newRevHead; i++) {
|
||||||
|
db.db.get("pad:" + padId + ":revs:" + i, function(err, rev) {
|
||||||
|
var author = rev.meta.author;
|
||||||
|
var timestamp = rev.meta.timestamp;
|
||||||
|
var changeset = rev.changeset;
|
||||||
|
|
||||||
|
var newAText = Changeset.applyToAText(changeset, newPad.atext, newPad.pool);
|
||||||
|
Changeset.copyAText(newAText, newPad.atext);
|
||||||
|
|
||||||
|
var newRev = ++newPad.head;
|
||||||
|
|
||||||
|
var newRevData = {};
|
||||||
|
newRevData.changeset = changeset;
|
||||||
|
newRevData.meta = {};
|
||||||
|
newRevData.meta.author = author;
|
||||||
|
newRevData.meta.timestamp = timestamp;
|
||||||
|
|
||||||
|
newPad.pool.putAttrib(['author', author || '']);
|
||||||
|
|
||||||
|
if(newRev % 100 == 0)
|
||||||
|
{
|
||||||
|
newRevData.meta.atext = newPad.atext;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.db.set("pad:"+newPad.id+":revs:"+newRev, newRevData);
|
||||||
|
console.log("Created: Revision: pad:" + newPad.id + ":revs:" + newRev);
|
||||||
|
|
||||||
|
if(author)
|
||||||
|
AuthorManager.addPad(author, newPad.id);
|
||||||
|
|
||||||
|
if (newRev == newRevHead) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, function(callback) {
|
||||||
|
// Add saved revisions up to the new revision head
|
||||||
|
console.log(newPad.head);
|
||||||
|
var newSavedRevisions = [];
|
||||||
|
for(var i in pad.savedRevisions) {
|
||||||
|
savedRev = pad.savedRevisions[i]
|
||||||
|
if (savedRev.revNum <= newRevHead) {
|
||||||
|
newSavedRevisions.push(savedRev);
|
||||||
|
console.log("Added: Saved Revision: " + savedRev.revNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newPad.savedRevisions = newSavedRevisions;
|
||||||
|
callback();
|
||||||
|
}, function(callback) {
|
||||||
|
// Save the source pad
|
||||||
|
db.db.set("pad:"+newPadId, newPad, function(err) {
|
||||||
|
console.log("Created: Source Pad: pad:" + newPadId);
|
||||||
|
newPad.saveToDatabase();
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
if(err) throw err;
|
||||||
|
else {
|
||||||
|
console.info("finished");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in New Issue