Add a simple backup/restore page

This commit is contained in:
Robbie Antenesse 2019-01-04 13:46:41 -07:00
parent b1a57ee534
commit 37ffb3144c
6 changed files with 338 additions and 3 deletions

1
.gitignore vendored
View File

@ -3,4 +3,5 @@ public/files/*.epub
public/files/*.mobi
public/files/*.pdf
public/files/*.json
public/files/*.zip
public/history/*.json

View File

@ -18,6 +18,7 @@
"filenamify": "^2.1.0",
"helmet": "^3.15.0",
"jquery": "^3.3.1",
"onezip": "^3.1.1",
"snarkdown": "^1.2.2",
"socket.io": "^2.2.0",
"socket.io-client": "^2.2.0",

104
server.js
View File

@ -32,11 +32,12 @@ function Server () {
this.server.use(bodyParser.json()); // support json encoded bodies
this.server.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
this.server.use(fileUpload({ // support file uploads
this.server.use('/give', fileUpload({ // support file uploads
limits: {
fileSize: (settings.maxFileSize > 0 ? settings.maxFileSize * 1024 * 1024 : Infinity), // filesize in bytes (settings accepts MB)
},
}));
this.server.use('/backup', fileUpload()); // Allow file upload on backup with no limits.
this.server.use('/files', express.static(path.join(__dirname, './public/files/')));
this.server.use('/css', express.static(path.resolve('./node_modules/bulma/css/')));
@ -136,6 +137,107 @@ function Server () {
}
});
this.server.get('/backup', (req, res) => {
if (req.query.pass === settings.backupPassword) {
const templateValues = {};
let html = this.fillTemplate('./templates/pages/backup.html', templateValues);
if (req.query.dl && ['files', 'history'].includes(req.query.dl)) {
const onezip = require('onezip');
const { dl } = req.query;
const saveLocation = path.resolve(this.fileLocation, dl + 'Backup.zip');
const backupLocation = dl === 'history' ? this.historyLocation : this.fileLocation;
const files = fs.readdirSync(backupLocation).filter(fileName => !fileName.includes('.zip'));
onezip.pack(backupLocation, saveLocation, files)
.on('start', () => {
console.info('Starting a backup zip of ' + dl)
})
.on('error', (error) => {
console.error(error);
templateValues[dl + 'Download'] = 'Something went wrong: ' + JSON.stringify(error);
html = this.fillTemplate('./templates/pages/backup.html', templateValues);
res.send(html);
})
.on('end', () => {
console.log('Backup complete. Saved to ' + saveLocation);
let backupLocation = saveLocation.replace(/\\/g, '/');
backupLocation = backupLocation.substr(backupLocation.lastIndexOf('/'));
templateValues[dl + 'Download'] = '<a download href="' + encodeURI('./files' + backupLocation) + '">Download</a> (This will be removed from the server in 1 hour)';
html = this.fillTemplate('./templates/pages/backup.html', templateValues);
res.send(html);
console.log('Will delete ' + saveLocation + ' in 1 hour');
setTimeout(() => {
fs.unlink(saveLocation, (err) => {
if (err) {
console.error(err);
} else {
console.log('Deleted backup file ' + saveLocation);
}
})
}, 60 * 60 * 1000);
});
} else {
res.send(html);
}
} else {
res.status(400).send();
}
});
this.server.post('/backup', (req, res) => {
if (req.query.pass === settings.backupPassword) {
const templateValues = {};
let html = this.fillTemplate('./templates/pages/backup.html', templateValues);
const { files } = req;
if (Object.keys(files).length > 0) {
const backupType = Object.keys(files)[0];
if (['files', 'history'].includes(backupType)) {
const onezip = require('onezip');
const uploadPath = path.resolve('./', backupType + 'UploadedBackup.zip');
files[backupType].mv(uploadPath, (err) => {
if (err) {
console.error(error);
templateValues[backupType + 'UploadSuccess'] = 'Could not upload the file.';
html = this.fillTemplate('./templates/pages/backup.html', templateValues);
res.send(html);
} else {
onezip.extract(uploadPath, path.resolve('./public', backupType))
.on('start', () => {
console.info('Extracting file ' + uploadPath)
})
.on('error', (error) => {
console.error(error);
templateValues[backupType + 'UploadSuccess'] = 'Something went wrong: ' + JSON.stringify(error);
html = this.fillTemplate('./templates/pages/backup.html', templateValues);
res.send(html);
})
.on('end', () => {
templateValues[backupType + 'UploadSuccess'] = 'Uploaded Successfully!';
html = this.fillTemplate('./templates/pages/backup.html', templateValues);
res.send(html);
fs.unlink(uploadPath, (err) => {
if (err) {
console.error(err);
} else {
console.log('Deleted backup file ' + uploadPath);
}
})
});
}
});
} else {
templateValues['generalError'] = '<p>' + backupType + ' is not a valid backup type.</p>';
html = this.fillTemplate('./templates/pages/backup.html', templateValues);
res.send(html);
}
} else {
res.send(html);
}
} else {
res.status(400).send();
}
});
this.io.on('connection', socket => {
this.connections++;
this.io.emit('update visitors', this.connections);

View File

@ -7,5 +7,6 @@
"maxLibrarySize": 0,
"maxFileSize": 0,
"maxHistory": 0,
"allowedFormats": [".epub", ".mobi", ".pdf"]
"allowedFormats": [".epub", ".mobi", ".pdf"],
"backupPassword": "password"
}

View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<title>Backup</title>
</head>
<body>
<h1>Backup/Restore</h1>
<p>
This page allows you to download a <code>.zip</code> file of your Files folder and your History
folder or re-import one of the zipped files you received from a backup.
</p>
<h2>Export</h2>
<p>
<a href="#" id="filesDownload">
<button>Back Up the <code>files</code> Folder</button>
</a> {{filesDownload}}
</p>
<p>
<a href="#" id="historyDownload">
<button>Back Up the <code>history</code> Folder</button>
</a> {{historyDownload}}
</p>
<h2>Import</h2>
{{generalError}}
<form id="filesForm" action="." method="post" enctype="multipart/form-data">
<p>
<label for="filesUpload">Files Backup <code>.zip</code></label><br>
<input type="file" name="files" id="filesUpload" accept=".zip">
{{filesUploadSuccess}}
</p>
<p>
<input type="submit" value="Upload Files Backup">
</p>
</form>
<form id="historyForm" action="." method="post" enctype="multipart/form-data">
<p>
<label for="historyUpload">History Backup <code>.zip</code></label><br>
<input type="file" name="history" id="historyUpload" accept=".zip">
{{historyUploadSuccess}}
</p>
<p>
<input type="submit" value="Upload History Backup">
</p>
</form>
<script>
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
window.onload = function() {
var pass = getParameterByName('pass');
var filesDownloadLink = document.getElementById('filesDownload');
var historyDownloadLink = document.getElementById('historyDownload');
var filesForm = document.getElementById('filesForm');
var historyForm = document.getElementById('historyForm');
filesDownloadLink.href = './backup?pass=' + pass + '&dl=files';
historyDownloadLink.href = './backup?pass=' + pass + '&dl=history';
filesForm.action = './backup?pass=' + pass;
historyForm.action = './backup?pass=' + pass;
}
</script>
</body>
</html>

160
yarn.lock
View File

@ -35,6 +35,11 @@ backo2@1.0.2:
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
base64-arraybuffer@0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
@ -73,6 +78,19 @@ body-parser@1.18.3, body-parser@^1.18.3:
raw-body "2.3.3"
type-is "~1.6.16"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
bulma@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.7.2.tgz#8e944377b74c7926558830d38d8e19eaf49f5fb6"
@ -96,6 +114,11 @@ callsite@1.0.0:
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
camelcase@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
camelize@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
@ -121,6 +144,11 @@ component-inherit@0.0.3:
resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
content-disposition@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
@ -182,6 +210,11 @@ debug@~4.1.0:
dependencies:
ms "^2.1.1"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@ -325,6 +358,13 @@ express@^4.16.4:
utils-merge "1.0.1"
vary "~1.1.2"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
dependencies:
pend "~1.2.0"
feature-policy@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.2.0.tgz#22096de49ab240176878ffe2bde2f6ff04d48c43"
@ -362,6 +402,11 @@ finalhandler@1.1.1:
statuses "~1.4.0"
unpipe "~1.0.0"
findit2@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/findit2/-/findit2-2.2.3.tgz#58a466697df8a6205cdfdbf395536b8bd777a5f6"
integrity sha1-WKRmaX34piBc39vzlVNri9d3pfY=
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@ -377,6 +422,23 @@ fresh@0.5.2:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
glob@^7.0.0:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
has-binary2@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
@ -466,7 +528,15 @@ indexof@0.0.1:
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
inherits@2.0.3, inherits@~2.0.1:
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.3, inherits@~2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
@ -537,6 +607,25 @@ mime@1.4.1:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"
modify-filename@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/modify-filename/-/modify-filename-1.1.0.tgz#9a2dec83806fbb2d975f22beec859ca26b393aa1"
@ -574,6 +663,27 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
onezip@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/onezip/-/onezip-3.1.1.tgz#160f5369c1c943976d6576f8534855d6a1082981"
integrity sha512-a0n77aH4Posl6XDU3QAJgd5KPoL0RIEGnmU0xrvpgDfJBVKL7btNQ+dTTVO3KYdA6lnsrmjiKd+W2PRToJbJJQ==
dependencies:
findit2 "^2.2.3"
glob "^7.0.0"
mkdirp "^0.5.1"
pipe-io "^3.0.0"
try-to-catch "^1.0.2"
yargs-parser "^11.0.0"
yauzl "^2.6.0"
yazl "^2.4.1"
parseqs@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
@ -598,11 +708,26 @@ path-exists@^3.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
pipe-io@^3.0.0:
version "3.0.11"
resolved "https://registry.yarnpkg.com/pipe-io/-/pipe-io-3.0.11.tgz#61cf893c7887908397927675a1fb5188fffbc9c5"
integrity sha512-Mj9M+vdptBRBqHOf52kIDVeKVb2gLHuuqQyTemBfaK8J+5tdQhIL4RFP3bQ15G7+2Ib/RCHBinnRW7TJz4VuaA==
platform@1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444"
@ -795,6 +920,11 @@ trim-repeated@^1.0.0:
dependencies:
escape-string-regexp "^1.0.2"
try-to-catch@^1.0.2:
version "1.1.1"
resolved "https://registry.yarnpkg.com/try-to-catch/-/try-to-catch-1.1.1.tgz#770162dd13b9a0e55da04db5b7f888956072038a"
integrity sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA==
type-is@~1.6.16:
version "1.6.16"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
@ -826,6 +956,11 @@ vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@~6.1.0:
version "6.1.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.2.tgz#3cc7462e98792f0ac679424148903ded3b9c3ad8"
@ -843,6 +978,29 @@ xmlhttprequest-ssl@~1.5.4:
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
yargs-parser@^11.0.0:
version "11.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yauzl@^2.6.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"
yazl@^2.4.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35"
integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==
dependencies:
buffer-crc32 "~0.2.3"
yeast@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"