diff --git a/bin/build-pkg.sh b/bin/build-pkg.sh new file mode 100755 index 000000000..ba0840637 --- /dev/null +++ b/bin/build-pkg.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +PKG_DIR=dist/trilium-linux-x64-server + +mkdir $PKG_DIR + +pkg . --targets node8-linux-x64 --output ${PKG_DIR}/trilium + +chmod +x ${PKG_DIR}/trilium + +cp node_modules/sqlite3/lib/binding/node-v57-linux-x64/node_sqlite3.node ${PKG_DIR}/ +cp node_modules/scrypt/build/Release/scrypt.node ${PKG_DIR}/ + +cd dist + +7z a trilium-linux-x64-server.7z trilium-linux-x64-server \ No newline at end of file diff --git a/package.json b/package.json index cab050ab3..1e8640463 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,7 @@ "start-forge": "electron-forge start", "package-forge": "electron-forge package", "make-forge": "electron-forge make", - "publish-forge": "electron-forge publish", - "build-pkg": "pkg . --targets node8-linux-x64 --output dist/trilium-linux-x64-server.elf" + "publish-forge": "electron-forge publish" }, "dependencies": { "async-mutex": "^0.1.3", @@ -119,7 +118,10 @@ "assets": [ "./db/**/*", "./src/public/**/*", - "./src/views/**/*" + "./src/views/**/*", + "./node_modules/mozjpeg/vendor/*", + "./node_modules/pngquant-bin/vendor/*", + "./node_modules/giflossy/vendor/*" ] } } diff --git a/src/public/javascripts/setup.js b/src/public/javascripts/setup.js index fd969d60f..9ad976853 100644 --- a/src/public/javascripts/setup.js +++ b/src/public/javascripts/setup.js @@ -1,36 +1,63 @@ import server from './services/server.js'; -$("#setup-form").submit(() => { - const username = $("#username").val(); - const password1 = $("#password1").val(); - const password2 = $("#password2").val(); +function SetupModel() { + this.step = ko.observable("setup-type"); + this.setupType = ko.observable(); - if (!username) { - showAlert("Username can't be empty"); - return false; - } + this.setupNewDocument = ko.observable(false); + this.setupSyncFromDesktop = ko.observable(false); + this.setupSyncFromServer = ko.observable(false); - if (!password1) { - showAlert("Password can't be empty"); - return false; - } + this.username = ko.observable(); + this.password1 = ko.observable(); + this.password2 = ko.observable(); - if (password1 !== password2) { - showAlert("Both password fields need be identical."); - return false; - } + this.setupTypeSelected = this.getSetupType = () => + this.setupNewDocument() + || this.setupSyncFromDesktop() + || this.setupSyncFromServer(); - server.post('setup', { - username: username, - password: password1 - }).then(() => { - window.location.replace("/"); - }); + this.selectSetupType = () => { + this.step(this.getSetupType()); + this.setupType(this.getSetupType()); + }; - return false; -}); + this.back = () => this.step("setup-type"); + + this.finish = () => { + if (this.setupNewDocument()) { + const username = this.username(); + const password1 = this.password1(); + const password2 = this.password2(); + + if (!username) { + showAlert("Username can't be empty"); + return; + } + + if (!password1) { + showAlert("Password can't be empty"); + return; + } + + if (password1 !== password2) { + showAlert("Both password fields need be identical."); + return; + } + + server.post('setup', { + username: username, + password: password1 + }).then(() => { + window.location.replace("/"); + }); + } + }; +} function showAlert(message) { $("#alert").html(message); $("#alert").show(); -} \ No newline at end of file +} + +ko.applyBindings(new SetupModel(), document.getElementById('setup-dialog')); \ No newline at end of file diff --git a/src/routes/api/setup.js b/src/routes/api/setup.js index 8794c3095..be658387e 100644 --- a/src/routes/api/setup.js +++ b/src/routes/api/setup.js @@ -1,25 +1,11 @@ "use strict"; -const optionService = require('../../services/options'); const sqlInit = require('../../services/sql_init'); -const utils = require('../../services/utils'); -const myScryptService = require('../../services/my_scrypt'); -const passwordEncryptionService = require('../../services/password_encryption'); async function setup(req) { const { username, password } = req.body; - await optionService.setOption('username', username); - - await optionService.setOption('passwordVerificationSalt', utils.randomSecureToken(32)); - await optionService.setOption('passwordDerivedKeySalt', utils.randomSecureToken(32)); - - const passwordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(password)); - await optionService.setOption('passwordVerificationHash', passwordVerificationKey); - - await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16)); - - sqlInit.setDbReadyAsResolved(); + await sqlInit.createInitialDatabase(username, password); } module.exports = { diff --git a/src/services/options.js b/src/services/options.js index 6c27e390f..f5d66ece8 100644 --- a/src/services/options.js +++ b/src/services/options.js @@ -1,10 +1,5 @@ -const repository = require('./repository'); -const utils = require('./utils'); -const dateUtils = require('./date_utils'); -const appInfo = require('./app_info'); - async function getOption(name) { - const option = await repository.getOption(name); + const option = await require('./repository').getOption(name); if (!option) { throw new Error("Option " + name + " doesn't exist"); @@ -14,7 +9,7 @@ async function getOption(name) { } async function setOption(name, value) { - const option = await repository.getOption(name); + const option = await require('./repository').getOption(name); if (!option) { throw new Error(`Option ${name} doesn't exist`); @@ -36,32 +31,8 @@ async function createOption(name, value, isSynced) { }).save(); } -async function initOptions(startNotePath) { - await createOption('documentId', utils.randomSecureToken(16), false); - await createOption('documentSecret', utils.randomSecureToken(16), false); - - await createOption('username', '', true); - await createOption('passwordVerificationHash', '', true); - await createOption('passwordVerificationSalt', '', true); - await createOption('passwordDerivedKeySalt', '', true); - await createOption('encryptedDataKey', '', true); - await createOption('encryptedDataKeyIv', '', true); - - await createOption('startNotePath', startNotePath, false); - await createOption('protectedSessionTimeout', 600, true); - await createOption('noteRevisionSnapshotTimeInterval', 600, true); - await createOption('lastBackupDate', dateUtils.nowDate(), false); - await createOption('dbVersion', appInfo.dbVersion, false); - - await createOption('lastSyncedPull', appInfo.dbVersion, false); - await createOption('lastSyncedPush', 0, false); - - await createOption('zoomFactor', 1.0, false); - await createOption('theme', 'white', false); -} - module.exports = { getOption, setOption, - initOptions + createOption }; \ No newline at end of file diff --git a/src/services/options_init.js b/src/services/options_init.js new file mode 100644 index 000000000..6ebc9fb88 --- /dev/null +++ b/src/services/options_init.js @@ -0,0 +1,41 @@ +const optionService = require('./options'); +const passwordEncryptionService = require('./password_encryption'); +const myScryptService = require('./my_scrypt'); +const appInfo = require('./app_info'); +const utils = require('./utils'); +const dateUtils = require('./date_utils'); + +async function initOptions(startNotePath, username, password) { + await optionService.createOption('documentId', utils.randomSecureToken(16), false); + await optionService.createOption('documentSecret', utils.randomSecureToken(16), false); + + await optionService.createOption('startNotePath', startNotePath, false); + await optionService.createOption('protectedSessionTimeout', 600, true); + await optionService.createOption('noteRevisionSnapshotTimeInterval', 600, true); + await optionService.createOption('lastBackupDate', dateUtils.nowDate(), false); + await optionService.createOption('dbVersion', appInfo.dbVersion, false); + + await optionService.createOption('lastSyncedPull', appInfo.dbVersion, false); + await optionService.createOption('lastSyncedPush', 0, false); + + await optionService.createOption('zoomFactor', 1.0, false); + await optionService.createOption('theme', 'white', false); + + await optionService.createOption('username', username); + + await optionService.createOption('passwordVerificationSalt', utils.randomSecureToken(32)); + await optionService.createOption('passwordDerivedKeySalt', utils.randomSecureToken(32)); + + const passwordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(password)); + await optionService.createOption('passwordVerificationHash', passwordVerificationKey); + + // passwordEncryptionService expects these options to already exist + await optionService.createOption('encryptedDataKey', ''); + await optionService.createOption('encryptedDataKeyIv', ''); + + await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16)); +} + +module.exports = { + initOptions +}; \ No newline at end of file diff --git a/src/services/sql_init.js b/src/services/sql_init.js index ade464f2a..cf49dc260 100644 --- a/src/services/sql_init.js +++ b/src/services/sql_init.js @@ -5,6 +5,7 @@ const sqlite = require('sqlite'); const resourceDir = require('./resource_dir'); const appInfo = require('./app_info'); const sql = require('./sql'); +const options = require('./options'); const cls = require('./cls'); async function createConnection() { @@ -30,7 +31,9 @@ const dbReady = new Promise((resolve, reject) => { const tableResults = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'"); if (tableResults.length !== 1) { - await createInitialDatabase(); + log.info("DB not found, please visit setup page to initialize Trilium."); + + return; } schemaReadyResolve(); @@ -52,7 +55,7 @@ const dbReady = new Promise((resolve, reject) => { }); }); -async function createInitialDatabase() { +async function createInitialDatabase(username, password) { log.info("Connected to db, but schema doesn't exist. Initializing schema ..."); const schema = fs.readFileSync(resourceDir.DB_INIT_DIR + '/schema.sql', 'UTF-8'); @@ -70,14 +73,13 @@ async function createInitialDatabase() { const startNoteId = await sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition"); - await require('./options').initOptions(startNoteId); + await require('./options_init').initOptions(startNoteId, username, password); await require('./sync_table').fillAllSyncRows(); }); log.info("Schema and initial content generated. Waiting for user to enter username/password to finish setup."); - // we don't resolve dbReady promise because user needs to setup the username and password to initialize - // the database + setDbReadyAsResolved(); } function setDbReadyAsResolved() { @@ -113,5 +115,6 @@ module.exports = { schemaReady, isUserInitialized, setDbReadyAsResolved, - isDbUpToDate + isDbUpToDate, + createInitialDatabase }; \ No newline at end of file diff --git a/src/views/setup.ejs b/src/views/setup.ejs index b9bc0273d..45b380364 100644 --- a/src/views/setup.ejs +++ b/src/views/setup.ejs @@ -5,31 +5,60 @@
You're almost done with the setup. That last thing is to choose username and password using which you'll login to the application. - This password is also used for generating encryption key which encrypts protected notes.
+You're almost done with the setup. The last thing is to choose username and password using which you'll login to the application. + This password is also used for generating encryption key which encrypts protected notes.
- + + + + + +