mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
safer backup to file using VACUUM INTO + possibility to explicitly ask for backup now
This commit is contained in:
parent
1911d64c1c
commit
13f9d037dc
@ -24,6 +24,13 @@ const TPL = `
|
|||||||
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
|
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
|
||||||
for sharing online for debugging purposes without fear of leaking your personal data.</p>
|
for sharing online for debugging purposes without fear of leaking your personal data.</p>
|
||||||
|
|
||||||
|
<h4>Backup database</h4>
|
||||||
|
|
||||||
|
<button id="backup-database-button" class="btn">Backup database</button>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
<h4>Vacuum database</h4>
|
<h4>Vacuum database</h4>
|
||||||
|
|
||||||
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
|
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
|
||||||
@ -37,6 +44,7 @@ export default class AdvancedOptions {
|
|||||||
this.$forceFullSyncButton = $("#force-full-sync-button");
|
this.$forceFullSyncButton = $("#force-full-sync-button");
|
||||||
this.$fillSyncRowsButton = $("#fill-sync-rows-button");
|
this.$fillSyncRowsButton = $("#fill-sync-rows-button");
|
||||||
this.$anonymizeButton = $("#anonymize-button");
|
this.$anonymizeButton = $("#anonymize-button");
|
||||||
|
this.$backupDatabaseButton = $("#backup-database-button");
|
||||||
this.$vacuumDatabaseButton = $("#vacuum-database-button");
|
this.$vacuumDatabaseButton = $("#vacuum-database-button");
|
||||||
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
|
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
|
||||||
|
|
||||||
@ -58,16 +66,22 @@ export default class AdvancedOptions {
|
|||||||
toastService.showMessage("Created anonymized database");
|
toastService.showMessage("Created anonymized database");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$backupDatabaseButton.on('click', async () => {
|
||||||
|
const {backupFile} = await server.post('database/backup-database');
|
||||||
|
|
||||||
|
toastService.showMessage("Database has been backed up to " + backupFile, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
this.$vacuumDatabaseButton.on('click', async () => {
|
this.$vacuumDatabaseButton.on('click', async () => {
|
||||||
await server.post('cleanup/vacuum-database');
|
await server.post('database/vacuum-database');
|
||||||
|
|
||||||
toastService.showMessage("Database has been vacuumed");
|
toastService.showMessage("Database has been vacuumed");
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
|
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
|
||||||
await server.post('cleanup/find-and-fix-consistency-issues');
|
await server.post('database/find-and-fix-consistency-issues');
|
||||||
|
|
||||||
toastService.showMessage("Consistency issues should be fixed.");
|
toastService.showMessage("Consistency issues should be fixed.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,15 @@
|
|||||||
|
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
|
const backupService = require('../../services/backup');
|
||||||
const consistencyChecksService = require('../../services/consistency_checks');
|
const consistencyChecksService = require('../../services/consistency_checks');
|
||||||
|
|
||||||
|
async function backupDatabase() {
|
||||||
|
return {
|
||||||
|
backupFile: await backupService.backupNow("now")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function vacuumDatabase() {
|
async function vacuumDatabase() {
|
||||||
await sql.execute("VACUUM");
|
await sql.execute("VACUUM");
|
||||||
|
|
||||||
@ -15,6 +22,7 @@ async function findAndFixConsistencyIssues() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
backupDatabase,
|
||||||
vacuumDatabase,
|
vacuumDatabase,
|
||||||
findAndFixConsistencyIssues
|
findAndFixConsistencyIssues
|
||||||
};
|
};
|
@ -25,7 +25,7 @@ const importRoute = require('./api/import');
|
|||||||
const setupApiRoute = require('./api/setup');
|
const setupApiRoute = require('./api/setup');
|
||||||
const sqlRoute = require('./api/sql');
|
const sqlRoute = require('./api/sql');
|
||||||
const anonymizationRoute = require('./api/anonymization');
|
const anonymizationRoute = require('./api/anonymization');
|
||||||
const cleanupRoute = require('./api/cleanup');
|
const databaseRoute = require('./api/database');
|
||||||
const imageRoute = require('./api/image');
|
const imageRoute = require('./api/image');
|
||||||
const attributesRoute = require('./api/attributes');
|
const attributesRoute = require('./api/attributes');
|
||||||
const scriptRoute = require('./api/script');
|
const scriptRoute = require('./api/script');
|
||||||
@ -222,10 +222,13 @@ function register(app) {
|
|||||||
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
|
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
|
||||||
apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize);
|
apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize);
|
||||||
|
|
||||||
// VACUUM requires execution outside of transaction
|
// backup requires execution outside of transaction
|
||||||
route(POST, '/api/cleanup/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.vacuumDatabase, apiResultHandler, false);
|
route(POST, '/api/database/backup-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.backupDatabase, apiResultHandler, false);
|
||||||
|
|
||||||
route(POST, '/api/cleanup/find-and-fix-consistency-issues', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.findAndFixConsistencyIssues, apiResultHandler, false);
|
// VACUUM requires execution outside of transaction
|
||||||
|
route(POST, '/api/database/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.vacuumDatabase, apiResultHandler, false);
|
||||||
|
|
||||||
|
route(POST, '/api/database/find-and-fix-consistency-issues', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.findAndFixConsistencyIssues, apiResultHandler, false);
|
||||||
|
|
||||||
apiRoute(POST, '/api/script/exec', scriptRoute.exec);
|
apiRoute(POST, '/api/script/exec', scriptRoute.exec);
|
||||||
apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run);
|
apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run);
|
||||||
@ -267,4 +270,4 @@ function register(app) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
register
|
register
|
||||||
};
|
};
|
||||||
|
@ -29,13 +29,38 @@ async function periodBackup(optionName, fileName, periodInSeconds) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function backupNow(name) {
|
async function backupNow(name) {
|
||||||
|
const sql = require('./sql');
|
||||||
|
|
||||||
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state
|
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state
|
||||||
await syncMutexService.doExclusively(async () => {
|
return await syncMutexService.doExclusively(async () => {
|
||||||
const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`;
|
const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`;
|
||||||
|
|
||||||
fs.copySync(dataDir.DOCUMENT_PATH, backupFile);
|
try {
|
||||||
|
fs.unlinkSync(backupFile);
|
||||||
|
}
|
||||||
|
catch (e) {} // unlink throws exception if the file did not exist
|
||||||
|
|
||||||
log.info("Created backup at " + backupFile);
|
let success = false;
|
||||||
|
let attemptCount = 0
|
||||||
|
|
||||||
|
for (; attemptCount < 50 && !success; attemptCount++) {
|
||||||
|
try {
|
||||||
|
await sql.executeNoWrap(`VACUUM INTO '${backupFile}'`);
|
||||||
|
success++;
|
||||||
|
}
|
||||||
|
catch (e) {}
|
||||||
|
// we re-try since VACUUM is very picky and it can't run if there's any other query currently running
|
||||||
|
// which is difficult to guarantee so we just re-try
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attemptCount === 10) {
|
||||||
|
log.error(`Creating backup ${backupFile} failed`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.info("Created backup at " + backupFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return backupFile;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,4 +77,4 @@ sqlInit.dbReady.then(() => {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
backupNow
|
backupNow
|
||||||
};
|
};
|
||||||
|
@ -153,6 +153,10 @@ async function execute(query, params = []) {
|
|||||||
return await wrap(async db => db.run(query, ...params), query);
|
return await wrap(async db => db.run(query, ...params), query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function executeNoWrap(query, params = []) {
|
||||||
|
await dbConnection.run(query, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
async function executeMany(query, params) {
|
async function executeMany(query, params) {
|
||||||
// essentially just alias
|
// essentially just alias
|
||||||
await getManyRows(query, params);
|
await getManyRows(query, params);
|
||||||
@ -264,6 +268,7 @@ module.exports = {
|
|||||||
getMap,
|
getMap,
|
||||||
getColumn,
|
getColumn,
|
||||||
execute,
|
execute,
|
||||||
|
executeNoWrap,
|
||||||
executeMany,
|
executeMany,
|
||||||
executeScript,
|
executeScript,
|
||||||
transactional,
|
transactional,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user