mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 11:26:15 +01:00
133 lines
4.2 KiB
JavaScript
133 lines
4.2 KiB
JavaScript
"use strict";
|
|
|
|
const dateUtils = require('./date_utils');
|
|
const optionService = require('./options');
|
|
const fs = require('fs-extra');
|
|
const dataDir = require('./data_dir');
|
|
const log = require('./log');
|
|
const sqlInit = require('./sql_init');
|
|
const syncMutexService = require('./sync_mutex');
|
|
const cls = require('./cls');
|
|
const sqlite = require('sqlite');
|
|
const sqlite3 = require('sqlite3');
|
|
|
|
async function regularBackup() {
|
|
await periodBackup('lastDailyBackupDate', 'daily', 24 * 3600);
|
|
|
|
await periodBackup('lastWeeklyBackupDate', 'weekly', 7 * 24 * 3600);
|
|
|
|
await periodBackup('lastMonthlyBackupDate', 'monthly', 30 * 24 * 3600);
|
|
}
|
|
|
|
async function periodBackup(optionName, fileName, periodInSeconds) {
|
|
const now = new Date();
|
|
const lastDailyBackupDate = dateUtils.parseDateTime(await optionService.getOption(optionName));
|
|
|
|
if (now.getTime() - lastDailyBackupDate.getTime() > periodInSeconds * 1000) {
|
|
await backupNow(fileName);
|
|
|
|
await optionService.setOption(optionName, dateUtils.utcNowDateTime());
|
|
}
|
|
}
|
|
|
|
const COPY_ATTEMPT_COUNT = 50;
|
|
|
|
async function copyFile(backupFile) {
|
|
const sql = require('./sql');
|
|
|
|
try {
|
|
fs.unlinkSync(backupFile);
|
|
} catch (e) {
|
|
} // unlink throws exception if the file did not exist
|
|
|
|
let success = false;
|
|
let attemptCount = 0
|
|
|
|
for (; attemptCount < COPY_ATTEMPT_COUNT && !success; attemptCount++) {
|
|
try {
|
|
await sql.executeNoWrap(`VACUUM INTO '${backupFile}'`);
|
|
|
|
success = true;
|
|
} catch (e) {
|
|
log.info(`Copy DB attempt ${attemptCount + 1} failed with "${e.message}", retrying...`);
|
|
}
|
|
// 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
|
|
}
|
|
|
|
return attemptCount !== COPY_ATTEMPT_COUNT;
|
|
}
|
|
|
|
async function backupNow(name) {
|
|
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state
|
|
return await syncMutexService.doExclusively(async () => {
|
|
const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`;
|
|
|
|
const success = await copyFile(backupFile);
|
|
|
|
if (success) {
|
|
log.info("Created backup at " + backupFile);
|
|
}
|
|
else {
|
|
log.error(`Creating backup ${backupFile} failed`);
|
|
}
|
|
|
|
return backupFile;
|
|
});
|
|
}
|
|
|
|
async function anonymize() {
|
|
if (!fs.existsSync(dataDir.ANONYMIZED_DB_DIR)) {
|
|
fs.mkdirSync(dataDir.ANONYMIZED_DB_DIR, 0o700);
|
|
}
|
|
|
|
const anonymizedFile = dataDir.ANONYMIZED_DB_DIR + "/" + "anonymized-" + dateUtils.getDateTimeForFile() + ".db";
|
|
|
|
const success = await copyFile(anonymizedFile);
|
|
|
|
if (!success) {
|
|
return { success: false };
|
|
}
|
|
|
|
const db = await sqlite.open({
|
|
filename: anonymizedFile,
|
|
driver: sqlite3.Database
|
|
});
|
|
|
|
await db.run("UPDATE api_tokens SET token = 'API token value'");
|
|
await db.run("UPDATE notes SET title = 'title'");
|
|
await db.run("UPDATE note_contents SET content = 'text'");
|
|
await db.run("UPDATE note_revisions SET title = 'title'");
|
|
await db.run("UPDATE note_revision_contents SET content = 'title'");
|
|
await db.run("UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label'");
|
|
await db.run("UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name != 'template'");
|
|
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
|
|
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
|
|
('documentId', 'documentSecret', 'encryptedDataKey', 'passwordVerificationHash',
|
|
'passwordVerificationSalt', 'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')`);
|
|
await db.run("VACUUM");
|
|
|
|
await db.close();
|
|
|
|
return {
|
|
success: true,
|
|
anonymizedFilePath: anonymizedFile
|
|
};
|
|
}
|
|
|
|
if (!fs.existsSync(dataDir.BACKUP_DIR)) {
|
|
fs.mkdirSync(dataDir.BACKUP_DIR, 0o700);
|
|
}
|
|
|
|
sqlInit.dbReady.then(() => {
|
|
setInterval(cls.wrap(regularBackup), 4 * 60 * 60 * 1000);
|
|
|
|
// kickoff first backup soon after start up
|
|
setTimeout(cls.wrap(regularBackup), 5 * 60 * 1000);
|
|
});
|
|
|
|
module.exports = {
|
|
backupNow,
|
|
anonymize
|
|
};
|