option to create a portable installation

pull/1513/head
antelle 2020-05-14 20:11:41 +02:00
parent a2bf157b01
commit 4d678184f3
No known key found for this signature in database
GPG Key ID: 094A2F2D6136A4EE
5 changed files with 85 additions and 30 deletions

View File

@ -29,7 +29,8 @@ module.exports = function(grunt) {
const zipCommentPlaceholder =
zipCommentPlaceholderPart + '.'.repeat(512 - zipCommentPlaceholderPart.length);
const electronVersion = pkg.dependencies.electron.replace(/^\D/, '');
const getCodeSignConfig = () => require('./keys/codesign.json');
const skipSign = grunt.option('skip-sign');
const getCodeSignConfig = () => (skipSign ? undefined : require('./keys/codesign.json'));
const webpackOptions = {
date,
@ -306,24 +307,28 @@ module.exports = function(grunt) {
appBundleId: 'net.antelle.keeweb',
appCategoryType: 'public.app-category.productivity',
extendInfo: 'package/osx/extend.plist',
osxSign: {
get identity() {
return getCodeSignConfig().identities.app;
},
hardenedRuntime: true,
entitlements: 'package/osx/entitlements.mac.plist',
'entitlements-inherit': 'package/osx/entitlements.mac.plist',
'gatekeeper-assess': false
},
osxNotarize: {
get appleId() {
return getCodeSignConfig().appleId;
},
appleIdPassword: '@keychain:AC_PASSWORD',
get ascProvider() {
return getCodeSignConfig().teamId;
}
},
osxSign: skipSign
? undefined
: {
get identity() {
return getCodeSignConfig().identities.app;
},
hardenedRuntime: true,
entitlements: 'package/osx/entitlements.mac.plist',
'entitlements-inherit': 'package/osx/entitlements.mac.plist',
'gatekeeper-assess': false
},
osxNotarize: skipSign
? undefined
: {
get appleId() {
return getCodeSignConfig().appleId;
},
appleIdPassword: '@keychain:AC_PASSWORD',
get ascProvider() {
return getCodeSignConfig().teamId;
}
},
afterCopy: [
(buildPath, electronVersion, platform, arch, callback) => {
if (path.basename(buildPath) !== 'app') {

View File

@ -16,6 +16,10 @@ let restartPending = false;
let mainWindowPosition = {};
let updateMainWindowPositionTimeout = null;
const windowPositionFileName = 'window-position.json';
const appSettingsFileName = 'app-settings.json';
const portableConfigFileName = 'keeweb-portable.json';
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
@ -23,11 +27,9 @@ if (!gotTheLock) {
perfTimestamps?.push({ name: 'single instance lock', ts: process.hrtime() });
initUserDataDir();
let openFile = process.argv.filter(arg => /\.kdbx$/i.test(arg))[0];
const overrideUserDataDir = process.env.KEEWEB_PORTABLE_EXECUTABLE_DIR;
const userDataDir = overrideUserDataDir || app.getPath('userData');
const windowPositionFileName = path.join(userDataDir, 'window-position.json');
const appSettingsFileName = path.join(userDataDir, 'app-settings.json');
const isDev = !__dirname.endsWith('.asar');
const htmlPath =
@ -160,8 +162,10 @@ app.reqNative = function(mod) {
};
function readAppSettings() {
const appSettingsFilePath = path.join(app.getPath('userData'), appSettingsFileName);
try {
return JSON.parse(fs.readFileSync(appSettingsFileName, 'utf8'));
return JSON.parse(fs.readFileSync(appSettingsFilePath, 'utf8'));
} catch (e) {
return null;
} finally {
@ -310,12 +314,17 @@ function saveMainWindowPosition() {
}
delete mainWindowPosition.changed;
try {
fs.writeFileSync(windowPositionFileName, JSON.stringify(mainWindowPosition), 'utf8');
fs.writeFileSync(
path.join(app.getPath('userData'), windowPositionFileName),
JSON.stringify(mainWindowPosition),
'utf8'
);
} catch (e) {}
}
function restoreMainWindowPosition() {
fs.readFile(windowPositionFileName, 'utf8', (e, data) => {
const fileName = path.join(app.getPath('userData'), windowPositionFileName);
fs.readFile(fileName, 'utf8', (e, data) => {
if (data) {
mainWindowPosition = JSON.parse(data);
if (mainWindow && mainWindowPosition) {
@ -477,11 +486,44 @@ function subscribePowerEvents() {
perfTimestamps?.push({ name: 'subscribing to power events', ts: process.hrtime() });
}
function setEnv() {
if (overrideUserDataDir) {
app.setPath('userData', overrideUserDataDir);
function initUserDataDir() {
let execPath = process.execPath;
let isPortable;
switch (process.platform) {
case 'darwin':
isPortable = !execPath.startsWith('/Applications/');
if (isPortable) {
execPath = execPath.substring(0, execPath.indexOf('.app'));
}
break;
case 'win32':
isPortable = !execPath.includes('Program Files');
break;
case 'linux':
isPortable = !execPath.startsWith('/usr/') && !execPath.startsWith('/opt/');
break;
}
if (isPortable) {
const portableConfigDir = path.dirname(execPath);
const portableConfigPath = path.join(portableConfigDir, portableConfigFileName);
if (fs.existsSync(portableConfigPath)) {
const portableConfig = JSON.parse(fs.readFileSync(portableConfigPath, 'utf8'));
const portableUserDataDir = path.resolve(portableConfigDir, portableConfig.userDataDir);
if (!fs.existsSync(portableUserDataDir)) {
fs.mkdirSync(portableUserDataDir);
}
app.setPath('userData', portableUserDataDir);
}
}
perfTimestamps?.push({ name: 'userdata dir', ts: process.hrtime() });
}
function setEnv() {
if (
process.platform === 'linux' &&
['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1
@ -507,7 +549,7 @@ function deleteOldTempFiles() {
return;
}
setTimeout(() => {
const tempPath = path.join(userDataDir, 'temp');
const tempPath = path.join(app.getPath('userData'), 'temp');
if (fs.existsSync(tempPath)) {
deleteRecursive(tempPath);
}

View File

@ -51,6 +51,12 @@ module.exports = function(grunt) {
'sign-dist'
]);
grunt.registerTask('dev-desktop-darwin', 'Build a macOS app in dev environment', [
'default',
'build-desktop-app-content',
'build-desktop-executables-darwin'
]);
grunt.registerTask('cordova', 'Build cordova app', [
'default',
'build-cordova'

View File

@ -105,6 +105,7 @@
"build-beta": "grunt --beta && cp dist/index.html ../keeweb-beta/index.html && cd ../keeweb-beta && git add index.html && git commit -a -m 'beta' && git push origin master",
"electron": "cross-env ELECTRON_DISABLE_SECURITY_WARNINGS=1 KEEWEB_HTML_PATH=http://localhost:8085 electron desktop",
"dev": "grunt dev",
"dev-desktop": "grunt dev-desktop-darwin --skip-sign",
"babel-helpers": "babel-external-helpers -l 'slicedToArray,toConsumableArray,defineProperty,typeof' -t global > app/lib/babel-helpers.js"
},
"author": {

View File

@ -4,6 +4,7 @@ Release notes
`+` YubiKey integration in two modes: OATH and Challenge-Response
`+` #557: Argon2 speed improvements in desktop apps
`+` #1503: ARM64 Windows support
`+` #1480: option to create a portable installation
`+` #1400: auto-apply tag when creating new entry in tag view
`+` #1342: hint that the data will be stored in unencrypted form after exporting
`*` #1471: WebDAV url validation, only HTTPS is allowed