Skip to content

Instantly share code, notes, and snippets.

@alitaheri
Last active May 13, 2020 06:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alitaheri/b4bb09638f88bdd7d3e3456cc215900f to your computer and use it in GitHub Desktop.
Save alitaheri/b4bb09638f88bdd7d3e3456cc215900f to your computer and use it in GitHub Desktop.
Minecraft Backup Script
const { resolve } = require('path');
const { existsSync, writeFileSync, readFileSync } = require('fs');
const { promisify } = require('util');
const exec = promisify(require('child_process').exec);
const { Rcon } = require('rcon-client');
const moment = require('moment');
const configFile = resolve(__dirname, 'backup-config.json');
if (!existsSync(configFile)) {
console.log('Missing config file. The file backup-config.json has been created, please provide the correct config');
const configSample = {
world: 'my-world-name',
rcon: { host: 'localhost', port: 25575, password: 'rcon-pass' },
mega: { path: '/' },
backup: { interval: 30 * 60 * 1000 },
};
writeFileSync(configFile, JSON.stringify(configSample, null, ' '), 'utf8');
process.exit(1);
}
const config = JSON.parse(readFileSync(configFile, { encoding: 'utf8' }));
let rcon = null;
let playersWereOnlineOnPreviousAttempt = null;
function log(message) {
if (process.argv.includes('--verbose') || process.argv.includes('--once')) {
console.log(message);
}
}
async function kill(error) {
const message = (error && error.message) || error || 'Unknown';
console.log(`An Error has occurred: ${message}`);
try {
console.log('Attempting to restore "save-on"');
const failsafeConnection = await Rcon.connect({
host: config.rcon.host,
port: config.rcon.port,
password: config.rcon.password,
});
await failsafeConnection.send('save-on');
} finally {
process.exit(1);
}
}
function execute(command) {
log('Executing bash command: ' + command);
return exec(command, { cwd: __dirname });
}
async function getConnection() {
if (rcon) {
return rcon;
}
try {
rcon = await Rcon.connect({
host: config.rcon.host,
port: config.rcon.port,
password: config.rcon.password,
});
} catch (error) {
await kill(error);
}
rcon.on('end', () => kill('RCON closed unexpectedly'));
return rcon;
}
async function sendRCONCommand(command) {
const rcon = await getConnection();
try {
log('Sending RCON command: ' + command);
return await rcon.send(command);
} catch (error) {
await kill(error);
}
}
async function makeBackup() {
try {
await sendRCONCommand('save-off');
await sendRCONCommand('save-all');
log('Waiting 10 seconds for the save to complete');
await new Promise(resolve => setTimeout(resolve, 10 * 1000));
const worldFolder = resolve(__dirname, config.world);
const backupFile = resolve(__dirname, 'backup.tar.gz');
const backupName = `${config.world}/${moment().utc().format('YYYY-MM-DD HH:mm')}.tar.gz`;
log('Making a compressed archive of the world: ' + config.world);
await execute(`tar -czf "${backupFile}" "${worldFolder}"`);
await execute(`mega-put -c "${backupFile}" "${config.mega.path}/${backupName}"`)
await sendRCONCommand('save-on');
await sendRCONCommand(`say "Successfully Made a Backup [${backupName}]"`);
} catch (error) {
await kill(error);
}
}
async function attemptBackup() {
const noPlayersOnline = (await sendRCONCommand('list')).startsWith('There are 0 ');
if (!noPlayersOnline) {
log('There are players online, making backup of their progress');
playersWereOnlineOnPreviousAttempt = true;
await makeBackup();
return;
}
if (playersWereOnlineOnPreviousAttempt === null) {
log('It is unknown whether there were any players online or not before this backup attemp, making one anyway.');
playersWereOnlineOnPreviousAttempt = false;
await makeBackup();
return;
}
if (playersWereOnlineOnPreviousAttempt) {
log('There are no players online, but there were previously. Making backup to ensure their progress is saved');
playersWereOnlineOnPreviousAttempt = false;
await makeBackup();
return;
}
log('There has not been any players online for 2 attempts, skipping backup');
}
if (process.argv.includes('--once')) {
makeBackup().finally(() => process.kill(0));
} else {
setInterval(attemptBackup, config.backup.interval);
if (process.argv.includes('--preemptive')) {
makeBackup();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment