generate anonymization script into a file

This commit is contained in:
zadam 2022-01-17 23:47:26 +01:00
parent 66a6c76552
commit d1b39ee8fa
18 changed files with 119 additions and 116 deletions

View File

@ -2,7 +2,7 @@ image:
file: .gitpod.dockerfile file: .gitpod.dockerfile
tasks: tasks:
- before: nvm install 16.13.1 && nvm use 16.13.1 - before: nvm install 16.13.2 && nvm use 16.13.2
init: npm install init: npm install
command: npm run start-server command: npm run start-server

View File

@ -1,4 +1,4 @@
FROM node:16.13.1-alpine FROM node:16.13.2-alpine
# Create app directory # Create app directory
WORKDIR /usr/src/app WORKDIR /usr/src/app

View File

@ -27,6 +27,8 @@ cp images/app-icons/png/128x128.png $BUILD_DIR/icon.png
# removing software WebGL binaries because they are pretty huge and not necessary # removing software WebGL binaries because they are pretty huge and not necessary
rm -r $BUILD_DIR/swiftshader rm -r $BUILD_DIR/swiftshader
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
cp bin/tpl/trilium-portable.sh $BUILD_DIR/ cp bin/tpl/trilium-portable.sh $BUILD_DIR/
chmod 755 $BUILD_DIR/trilium-portable.sh chmod 755 $BUILD_DIR/trilium-portable.sh

View File

@ -23,6 +23,8 @@ rm -rf $BUILD_DIR
# Mac build has by default useless directory level # Mac build has by default useless directory level
mv "./dist/Trilium Notes-darwin-x64" $BUILD_DIR mv "./dist/Trilium Notes-darwin-x64" $BUILD_DIR
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
echo "Zipping mac x64 electron distribution..." echo "Zipping mac x64 electron distribution..."
VERSION=`jq -r ".version" package.json` VERSION=`jq -r ".version" package.json`

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
PKG_DIR=dist/trilium-linux-x64-server PKG_DIR=dist/trilium-linux-x64-server
NODE_VERSION=16.13.1 NODE_VERSION=16.13.2
if [ "$1" != "DONTCOPY" ] if [ "$1" != "DONTCOPY" ]
then then
@ -20,12 +20,16 @@ rm -r $PKG_DIR/node/lib/node_modules/npm
rm -r $PKG_DIR/node/include/node rm -r $PKG_DIR/node/include/node
rm -r $PKG_DIR/node_modules/electron* rm -r $PKG_DIR/node_modules/electron*
rm -r $PKG_DIR/webpack*
rm -r $PKG_DIR/electron.js
cp -r bin/better-sqlite3/linux-server-better_sqlite3.node $PKG_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node cp -r bin/better-sqlite3/linux-server-better_sqlite3.node $PKG_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node
printf "#!/bin/sh\n./node/bin/node src/www" > $PKG_DIR/trilium.sh printf "#!/bin/sh\n./node/bin/node src/www" > $PKG_DIR/trilium.sh
chmod 755 $PKG_DIR/trilium.sh chmod 755 $PKG_DIR/trilium.sh
cp bin/tpl/anonymize-database.sql $PKG_DIR/
VERSION=`jq -r ".version" package.json` VERSION=`jq -r ".version" package.json`
cd dist cd dist

View File

@ -25,6 +25,8 @@ mv "./dist/Trilium Notes-win32-x64" $BUILD_DIR
# removing software WebGL binaries because they are pretty huge and not necessary # removing software WebGL binaries because they are pretty huge and not necessary
rm -r $BUILD_DIR/swiftshader rm -r $BUILD_DIR/swiftshader
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
cp bin/tpl/trilium-portable.bat $BUILD_DIR/ cp bin/tpl/trilium-portable.bat $BUILD_DIR/
cp bin/tpl/trilium-no-cert-check.bat $BUILD_DIR/ cp bin/tpl/trilium-no-cert-check.bat $BUILD_DIR/
cp bin/tpl/trilium-safe-mode.bat $BUILD_DIR/ cp bin/tpl/trilium-safe-mode.bat $BUILD_DIR/

View File

@ -5,7 +5,7 @@ if [[ $# -eq 0 ]] ; then
exit 1 exit 1
fi fi
n exec 16.13.1 npm run webpack n exec 16.13.2 npm run webpack
DIR=$1 DIR=$1
@ -27,7 +27,7 @@ cp -r electron.js $DIR/
cp webpack-* $DIR/ cp webpack-* $DIR/
# run in subshell (so we return to original dir) # run in subshell (so we return to original dir)
(cd $DIR && n exec 16.13.1 npm install --only=prod) (cd $DIR && n exec 16.13.2 npm install --only=prod)
# cleanup of useless files in dependencies # cleanup of useless files in dependencies
rm -r $DIR/node_modules/image-q/demo rm -r $DIR/node_modules/image-q/demo

View File

@ -0,0 +1,7 @@
#!/usr/bin/env node
const anonymizationService = require('../src/services/anonymization');
const fs = require('fs');
const path = require('path');
fs.writeFileSync(path.resolve(__dirname, 'tpl', 'anonymize-database.sql'), anonymizationService.getAnonymizationScript());

View File

@ -1,31 +0,0 @@
#!/usr/bin/env node
const BUILTIN_ATTRIBUTES = require('../src/services/builtin_attributes');
const fs = require('fs');
const path = require('path');
// we want to delete all non-builtin attributes because they can contain sensitive names and values
// on the other hand builtin/system attrs should not contain any sensitive info
const builtinAttrNames = BUILTIN_ATTRIBUTES
.map(attr => "'" + attr.name + "'").join(', ');
const anonymizeScript = `
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
UPDATE notes SET title = 'title';
UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_revisions SET title = 'title';
UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrNames});
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrNames});
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL;
UPDATE options SET value = 'anonymized' WHERE
('documentId', 'documentSecret', 'encryptedDataKey',
'passwordVerificationHash', 'passwordVerificationSalt',
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
AND value != '';
VACUUM;
`;
fs.writeFileSync(path.resolve(__dirname, 'anonymize-database.sql'), anonymizeScript);

View File

@ -8,7 +8,7 @@ UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'hoistedCssClass', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'hoistedInbox', 'hoistedSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarked', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon'); UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'hoistedCssClass', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'hoistedInbox', 'hoistedSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarked', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'hoistedCssClass', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'hoistedInbox', 'hoistedSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarked', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon'); UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'hoistedCssClass', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'hoistedInbox', 'hoistedSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarked', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL; UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL;
UPDATE options SET value = 'anonymized' WHERE UPDATE options SET value = 'anonymized' WHERE name IN
('documentId', 'documentSecret', 'encryptedDataKey', ('documentId', 'documentSecret', 'encryptedDataKey',
'passwordVerificationHash', 'passwordVerificationSalt', 'passwordVerificationHash', 'passwordVerificationSalt',
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy') 'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')

18
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "trilium", "name": "trilium",
"version": "0.49.4", "version": "0.49.5",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "trilium", "name": "trilium",
"version": "0.49.4", "version": "0.49.5",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@electron/remote": "2.0.1", "@electron/remote": "2.0.1",
@ -62,7 +62,7 @@
"tmp": "0.2.1", "tmp": "0.2.1",
"turndown": "7.1.1", "turndown": "7.1.1",
"unescape": "1.0.1", "unescape": "1.0.1",
"ws": "8.4.0", "ws": "8.4.1",
"yauzl": "2.10.0" "yauzl": "2.10.0"
}, },
"bin": { "bin": {
@ -11045,9 +11045,9 @@
} }
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "8.4.0", "version": "8.4.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.4.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.1.tgz",
"integrity": "sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==", "integrity": "sha512-6eqQ4yN2y2xv8b+BgbkUzPPyfo/PDl3VOWb06ZE0jIFYwuHMsMQN6F7o84yxJYCblfCRAxzpU59We4Rr4w0Luw==",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@ -19881,9 +19881,9 @@
} }
}, },
"ws": { "ws": {
"version": "8.4.0", "version": "8.4.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.4.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.1.tgz",
"integrity": "sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==", "integrity": "sha512-6eqQ4yN2y2xv8b+BgbkUzPPyfo/PDl3VOWb06ZE0jIFYwuHMsMQN6F7o84yxJYCblfCRAxzpU59We4Rr4w0Luw==",
"requires": {} "requires": {}
}, },
"xdg-basedir": { "xdg-basedir": {

View File

@ -2,7 +2,6 @@
const dateUtils = require('../../services/date_utils'); const dateUtils = require('../../services/date_utils');
const AbstractEntity = require("./abstract_entity"); const AbstractEntity = require("./abstract_entity");
const sql = require("../../services/sql.js");
/** /**
* EtapiToken is an entity representing token used to authenticate against Trilium REST API from client applications. * EtapiToken is an entity representing token used to authenticate against Trilium REST API from client applications.

View File

@ -1,7 +1,7 @@
const becca = require("../becca/becca"); const becca = require("../becca/becca");
const eu = require("./etapi_utils"); const eu = require("./etapi_utils");
const passwordEncryptionService = require("../services/password_encryption.js"); const passwordEncryptionService = require("../services/password_encryption");
const etapiTokenService = require("../services/etapi_tokens.js"); const etapiTokenService = require("../services/etapi_tokens");
function register(router) { function register(router) {
eu.NOT_AUTHENTICATED_ROUTE(router, 'post', '/etapi/auth/login', (req, res, next) => { eu.NOT_AUTHENTICATED_ROUTE(router, 'post', '/etapi/auth/login', (req, res, next) => {

View File

@ -2,8 +2,8 @@ const cls = require("../services/cls");
const sql = require("../services/sql"); const sql = require("../services/sql");
const log = require("../services/log"); const log = require("../services/log");
const becca = require("../becca/becca"); const becca = require("../becca/becca");
const etapiTokenService = require("../services/etapi_tokens.js"); const etapiTokenService = require("../services/etapi_tokens");
const config = require("../services/config.js"); const config = require("../services/config");
const GENERIC_CODE = "GENERIC"; const GENERIC_CODE = "GENERIC";
const noAuthentication = config.General && config.General.noAuthentication === true; const noAuthentication = config.General && config.General.noAuthentication === true;

View File

@ -3,10 +3,11 @@
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 backupService = require('../../services/backup');
const anonymizationService = require('../../services/anonymization');
const consistencyChecksService = require('../../services/consistency_checks'); const consistencyChecksService = require('../../services/consistency_checks');
async function anonymize() { async function anonymize() {
return await backupService.anonymize(); return await anonymizationService.createAnonymizedCopy();
} }
async function backupDatabase() { async function backupDatabase() {

View File

@ -0,0 +1,60 @@
const BUILTIN_ATTRIBUTES = require("./builtin_attributes");
const fs = require("fs-extra");
const dataDir = require("./data_dir");
const dateUtils = require("./date_utils");
const Database = require("better-sqlite3");
const sql = require("./sql");
function getAnonymizationScript() {
// we want to delete all non-builtin attributes because they can contain sensitive names and values
// on the other hand builtin/system attrs should not contain any sensitive info
const builtinAttrNames = BUILTIN_ATTRIBUTES
.map(attr => "'" + attr.name + "'").join(', ');
const anonymizeScript = `
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
UPDATE notes SET title = 'title';
UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_revisions SET title = 'title';
UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrNames});
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrNames});
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL;
UPDATE options SET value = 'anonymized' WHERE name IN
('documentId', 'documentSecret', 'encryptedDataKey',
'passwordVerificationHash', 'passwordVerificationSalt',
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
AND value != '';
VACUUM;
`;
return anonymizeScript;
}
async function createAnonymizedCopy() {
if (!fs.existsSync(dataDir.ANONYMIZED_DB_DIR)) {
fs.mkdirSync(dataDir.ANONYMIZED_DB_DIR, 0o700);
}
const anonymizedFile = dataDir.ANONYMIZED_DB_DIR + "/" + "anonymized-" + dateUtils.getDateTimeForFile() + ".db";
await sql.copyDatabase(anonymizedFile);
const db = new Database(anonymizedFile);
db.exec(getAnonymizationScript());
db.close();
return {
success: true,
anonymizedFilePath: anonymizedFile
};
}
module.exports = {
getAnonymizationScript,
createAnonymizedCopy
}

View File

@ -6,9 +6,8 @@ const fs = require('fs-extra');
const dataDir = require('./data_dir'); const dataDir = require('./data_dir');
const log = require('./log'); const log = require('./log');
const syncMutexService = require('./sync_mutex'); const syncMutexService = require('./sync_mutex');
const attributeService = require('./attributes');
const cls = require('./cls'); const cls = require('./cls');
const Database = require('better-sqlite3'); const sql = require('./sql');
function regularBackup() { function regularBackup() {
cls.init(() => { cls.init(() => {
@ -41,23 +40,12 @@ function periodBackup(optionName, backupType, periodInSeconds) {
} }
} }
async function copyFile(backupFile) {
const sql = require('./sql');
try {
fs.unlinkSync(backupFile);
} catch (e) {
} // unlink throws exception if the file did not exist
await sql.dbConnection.backup(backupFile);
}
async function backupNow(name) { async function backupNow(name) {
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state // we don't want to back up DB in the middle of sync with potentially inconsistent DB state
return 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`;
await copyFile(backupFile); await sql.copyDatabase(backupFile);
log.info("Created backup at " + backupFile); log.info("Created backup at " + backupFile);
@ -65,53 +53,11 @@ async function backupNow(name) {
}); });
} }
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";
await copyFile(anonymizedFile);
const db = new Database(anonymizedFile);
db.prepare("UPDATE etapi_tokens SET tokenHash = 'API token hash value'").run();
db.prepare("UPDATE notes SET title = 'title'").run();
db.prepare("UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL").run();
db.prepare("UPDATE note_revisions SET title = 'title'").run();
db.prepare("UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL").run();
// we want to delete all non-builtin attributes because they can contain sensitive names and values
// on the other hand builtin/system attrs should not contain any sensitive info
const builtinAttrs = attributeService
.getBuiltinAttributeNames()
.map(name => "'" + name + "'").join(', ');
db.prepare(`UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrs})`).run();
db.prepare(`UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrs})`).run();
db.prepare("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL").run();
db.prepare(`UPDATE options SET value = 'anonymized' WHERE name IN
('documentId', 'documentSecret', 'encryptedDataKey',
'passwordVerificationHash', 'passwordVerificationSalt',
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
AND value != ''`).run();
db.prepare("VACUUM").run();
db.close();
return {
success: true,
anonymizedFilePath: anonymizedFile
};
}
if (!fs.existsSync(dataDir.BACKUP_DIR)) { if (!fs.existsSync(dataDir.BACKUP_DIR)) {
fs.mkdirSync(dataDir.BACKUP_DIR, 0o700); fs.mkdirSync(dataDir.BACKUP_DIR, 0o700);
} }
module.exports = { module.exports = {
backupNow, backupNow,
anonymize,
regularBackup regularBackup
}; };

View File

@ -8,6 +8,7 @@ const log = require('./log');
const Database = require('better-sqlite3'); const Database = require('better-sqlite3');
const dataDir = require('./data_dir'); const dataDir = require('./data_dir');
const cls = require('./cls'); const cls = require('./cls');
const fs = require("fs-extra");
const dbConnection = new Database(dataDir.DOCUMENT_PATH); const dbConnection = new Database(dataDir.DOCUMENT_PATH);
dbConnection.pragma('journal_mode = WAL'); dbConnection.pragma('journal_mode = WAL');
@ -276,6 +277,15 @@ function fillParamList(paramIds, truncate = true) {
s.run(paramIds); s.run(paramIds);
} }
async function copyDatabase(targetFilePath) {
try {
fs.unlinkSync(targetFilePath);
} catch (e) {
} // unlink throws exception if the file did not exist
await dbConnection.backup(targetFilePath);
}
module.exports = { module.exports = {
dbConnection, dbConnection,
insert, insert,
@ -347,5 +357,6 @@ module.exports = {
executeScript, executeScript,
transactional, transactional,
upsert, upsert,
fillParamList fillParamList,
copyDatabase
}; };