diff --git a/Gruntfile.js b/Gruntfile.js
index 7546a418..ab5a4be4 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -25,9 +25,6 @@ module.exports = function (grunt) {
const dt = date.toISOString().replace(/T.*/, '');
const year = date.getFullYear();
- const zipCommentPlaceholderPart = 'zip_comment_placeholder_that_will_be_replaced_with_hash';
- const zipCommentPlaceholder =
- zipCommentPlaceholderPart + '.'.repeat(512 - zipCommentPlaceholderPart.length);
const electronVersion = pkg.dependencies.electron.replace(/^\D/, '');
const skipSign = grunt.option('skip-sign');
const getCodeSignConfig = () =>
@@ -151,7 +148,7 @@ module.exports = function (grunt) {
options: { mode: '0755' }
},
'desktop-darwin-installer-helper-x64': {
- cwd: 'package/osx/KeeWeb Installer.app',
+ cwd: 'tmp/desktop/KeeWeb Installer.app',
src: '**',
dest:
'tmp/desktop/KeeWeb-darwin-x64/KeeWeb.app/Contents/Installer/KeeWeb Installer.app',
@@ -160,7 +157,7 @@ module.exports = function (grunt) {
options: { mode: true }
},
'desktop-darwin-installer-helper-arm64': {
- cwd: 'package/osx/KeeWeb Installer.app',
+ cwd: 'tmp/desktop/KeeWeb Installer.app',
src: '**',
dest:
'tmp/desktop/KeeWeb-darwin-arm64/KeeWeb.app/Contents/Installer/KeeWeb Installer.app',
@@ -242,6 +239,11 @@ module.exports = function (grunt) {
src: `tmp/desktop/electron-builder/keeweb-${pkg.version}.AppImage`,
dest: `dist/desktop/KeeWeb-${pkg.version}.linux.AppImage`,
nonull: true
+ },
+ 'darwin-installer-icon': {
+ src: 'graphics/icon.icns',
+ dest: 'tmp/desktop/KeeWeb Installer.app/Contents/Resources/applet.icns',
+ nonull: true
}
},
eslint: {
@@ -445,6 +447,16 @@ module.exports = function (grunt) {
}
}
},
+ osacompile: {
+ options: {
+ language: 'JavaScript'
+ },
+ installer: {
+ files: {
+ 'tmp/desktop/KeeWeb Installer.app': 'package/osx/installer/main.js'
+ }
+ }
+ },
compress: {
options: {
level: 6
@@ -595,6 +607,9 @@ module.exports = function (grunt) {
},
'desktop-arm64': {
src: 'tmp/desktop/KeeWeb-darwin-arm64/KeeWeb.app'
+ },
+ 'installer': {
+ src: 'tmp/desktop/KeeWeb Installer.app'
}
},
notarize: {
diff --git a/app/scripts/comp/app/app-rights-checker.js b/app/scripts/comp/app/app-rights-checker.js
index 67054c26..2869bdda 100644
--- a/app/scripts/comp/app/app-rights-checker.js
+++ b/app/scripts/comp/app/app-rights-checker.js
@@ -58,6 +58,7 @@ const AppRightsChecker = {
runInstaller() {
Launcher.spawn({
cmd: this.AppPath + '/Contents/Installer/KeeWeb Installer.app/Contents/MacOS/applet',
+ args: ['--install'],
complete: () => {
this.needRunInstaller((needRun) => {
if (this.alert && !needRun) {
diff --git a/build/tasks/grunt-osacompile.js b/build/tasks/grunt-osacompile.js
new file mode 100644
index 00000000..10571f4f
--- /dev/null
+++ b/build/tasks/grunt-osacompile.js
@@ -0,0 +1,40 @@
+const { spawnSync } = require('child_process');
+
+module.exports = function (grunt) {
+ grunt.registerMultiTask(
+ 'osacompile',
+ 'Builds an executable .app package with osacompile',
+ async function () {
+ const done = this.async();
+ const opt = this.options();
+
+ for (const file of this.files) {
+ const dest = file.dest;
+ const src = file.src[0];
+
+ const args = [];
+ if (opt.language) {
+ args.push('-l', opt.language);
+ }
+ args.push('-o', dest);
+ args.push(src);
+
+ grunt.log.writeln(`Running: osacompile ${args.join(' ')}`);
+
+ const res = spawnSync('osacompile', args);
+
+ if (res.status) {
+ grunt.warn(
+ `osacompile exit code ${
+ res.status
+ }.\nSTDOUT:\n${res.stdout.toString()}\nSTDERR:\n${res.stderr.toString()}`
+ );
+ }
+
+ grunt.log.writeln(`Built ${dest}`);
+ }
+
+ done();
+ }
+ );
+};
diff --git a/grunt.tasks.js b/grunt.tasks.js
index 057893d2..c41fd8b8 100644
--- a/grunt.tasks.js
+++ b/grunt.tasks.js
@@ -33,6 +33,7 @@ module.exports = function (grunt) {
grunt.registerTask('build-desktop-executables-darwin', [
'electron:darwin-x64',
'electron:darwin-arm64',
+ 'build-darwin-installer',
'copy:desktop-darwin-helper-x64',
'copy:desktop-darwin-helper-arm64',
'copy:desktop-darwin-installer-helper-x64',
@@ -45,6 +46,12 @@ module.exports = function (grunt) {
sign ? 'notarize:desktop-arm64' : 'noop'
]);
+ grunt.registerTask('build-darwin-installer', [
+ 'osacompile:installer',
+ 'copy:darwin-installer-icon',
+ sign ? 'osx-sign:installer' : 'noop'
+ ]);
+
grunt.registerTask('build-desktop-executables-win32', [
'electron:win32-x64',
'electron:win32-ia32',
diff --git a/package/osx/KeeWeb Installer.app/Contents/Info.plist b/package/osx/KeeWeb Installer.app/Contents/Info.plist
deleted file mode 100644
index 47cb6892..00000000
--- a/package/osx/KeeWeb Installer.app/Contents/Info.plist
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
- CFBundleAllowMixedLocalizations
-
- CFBundleDevelopmentRegion
- English
- CFBundleExecutable
- applet
- CFBundleIconFile
- applet
- CFBundleIdentifier
- com.apple.ScriptEditor.id.KeeWeb-Installer
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- KeeWeb Installer
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- 1.0
- CFBundleSignature
- aplt
- LSMinimumSystemVersionByArchitecture
-
- x86_64
- 10.6
-
- LSRequiresCarbon
-
- WindowState
-
- bundleDividerCollapsed
-
- bundlePositionOfDivider
- 0.0
- dividerCollapsed
-
- eventLogLevel
- 2
- name
- ScriptWindowState
- positionOfDivider
- 421
- savedFrame
- 20 90 700 672 0 0 1280 777
- selectedTab
- description
-
-
-
diff --git a/package/osx/KeeWeb Installer.app/Contents/MacOS/applet b/package/osx/KeeWeb Installer.app/Contents/MacOS/applet
deleted file mode 100755
index 1a2cfd88..00000000
Binary files a/package/osx/KeeWeb Installer.app/Contents/MacOS/applet and /dev/null differ
diff --git a/package/osx/KeeWeb Installer.app/Contents/PkgInfo b/package/osx/KeeWeb Installer.app/Contents/PkgInfo
deleted file mode 100644
index 3253614c..00000000
--- a/package/osx/KeeWeb Installer.app/Contents/PkgInfo
+++ /dev/null
@@ -1 +0,0 @@
-APPLaplt
\ No newline at end of file
diff --git a/package/osx/KeeWeb Installer.app/Contents/Resources/Scripts/main.scpt b/package/osx/KeeWeb Installer.app/Contents/Resources/Scripts/main.scpt
deleted file mode 100644
index c01ccb99..00000000
Binary files a/package/osx/KeeWeb Installer.app/Contents/Resources/Scripts/main.scpt and /dev/null differ
diff --git a/package/osx/KeeWeb Installer.app/Contents/Resources/applet.icns b/package/osx/KeeWeb Installer.app/Contents/Resources/applet.icns
deleted file mode 100644
index 941c4aa0..00000000
Binary files a/package/osx/KeeWeb Installer.app/Contents/Resources/applet.icns and /dev/null differ
diff --git a/package/osx/KeeWeb Installer.app/Contents/Resources/applet.rsrc b/package/osx/KeeWeb Installer.app/Contents/Resources/applet.rsrc
deleted file mode 100644
index 4367dd9e..00000000
Binary files a/package/osx/KeeWeb Installer.app/Contents/Resources/applet.rsrc and /dev/null differ
diff --git a/package/osx/KeeWeb Installer.app/Contents/Resources/description.rtfd/TXT.rtf b/package/osx/KeeWeb Installer.app/Contents/Resources/description.rtfd/TXT.rtf
deleted file mode 100644
index aa96f701..00000000
--- a/package/osx/KeeWeb Installer.app/Contents/Resources/description.rtfd/TXT.rtf
+++ /dev/null
@@ -1,5 +0,0 @@
-{\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf830
-{\fonttbl}
-{\colortbl;\red255\green255\blue255;}
-{\*\expandedcolortbl;;}
-}
\ No newline at end of file
diff --git a/package/osx/KeeWeb Installer.app/Contents/_CodeSignature/CodeResources b/package/osx/KeeWeb Installer.app/Contents/_CodeSignature/CodeResources
deleted file mode 100644
index 748021de..00000000
--- a/package/osx/KeeWeb Installer.app/Contents/_CodeSignature/CodeResources
+++ /dev/null
@@ -1,177 +0,0 @@
-
-
-
-
- files
-
- Resources/Scripts/main.scpt
-
- hsRwl39Q/JbFKwwQ/MLvOD3Mf4Q=
-
- Resources/applet.icns
-
- vSn/k+EzqMViG5RcXNXpodKsdyk=
-
- Resources/applet.rsrc
-
- 8fN/GtxUP559toTqTl+V3wCZeoU=
-
- Resources/description.rtfd/TXT.rtf
-
- U1/ikutWmrJdAXLoU25d4KUS06c=
-
-
- files2
-
- Resources/Scripts/main.scpt
-
- hash
-
- hsRwl39Q/JbFKwwQ/MLvOD3Mf4Q=
-
- hash2
-
- t7kR7hl6N/mOn8SxdBAqRDqpnWm/sWW4XJ4jYDK4U5I=
-
-
- Resources/applet.icns
-
- hash
-
- vSn/k+EzqMViG5RcXNXpodKsdyk=
-
- hash2
-
- NcZgMymH9fQebOOAufbFmH8w7f0RfzDMZRykqiqlrGE=
-
-
- Resources/applet.rsrc
-
- hash
-
- 8fN/GtxUP559toTqTl+V3wCZeoU=
-
- hash2
-
- eBEutqxEKm3EiJlpx6YIakFWJU2RKjnLue7r2J/WmJo=
-
-
- Resources/description.rtfd/TXT.rtf
-
- hash
-
- U1/ikutWmrJdAXLoU25d4KUS06c=
-
- hash2
-
- vGT9c0omY5YveJ9Gjv3lLYFVn64gjtcT7Nc6UAWGXh4=
-
-
-
- rules
-
- ^Resources/
-
- ^Resources/.*\.lproj/
-
- optional
-
- weight
- 1000
-
- ^Resources/.*\.lproj/locversion.plist$
-
- omit
-
- weight
- 1100
-
- ^Resources/Base\.lproj/
-
- weight
- 1010
-
- ^version.plist$
-
-
- rules2
-
- .*\.dSYM($|/)
-
- weight
- 11
-
- ^(.*/)?\.DS_Store$
-
- omit
-
- weight
- 2000
-
- ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/
-
- nested
-
- weight
- 10
-
- ^.*
-
- ^Info\.plist$
-
- omit
-
- weight
- 20
-
- ^PkgInfo$
-
- omit
-
- weight
- 20
-
- ^Resources/
-
- weight
- 20
-
- ^Resources/.*\.lproj/
-
- optional
-
- weight
- 1000
-
- ^Resources/.*\.lproj/locversion.plist$
-
- omit
-
- weight
- 1100
-
- ^Resources/Base\.lproj/
-
- weight
- 1010
-
- ^[^/]+$
-
- nested
-
- weight
- 10
-
- ^embedded\.provisionprofile$
-
- weight
- 20
-
- ^version\.plist$
-
- weight
- 20
-
-
-
-
diff --git a/package/osx/installer/.eslintrc b/package/osx/installer/.eslintrc
new file mode 100644
index 00000000..65de4c4f
--- /dev/null
+++ b/package/osx/installer/.eslintrc
@@ -0,0 +1,11 @@
+{
+ "env": {
+ "applescript": true
+ },
+ "globals": {
+ "app": false
+ },
+ "rules": {
+ "no-console": "off"
+ }
+}
diff --git a/package/osx/installer/build.sh b/package/osx/installer/build.sh
new file mode 100755
index 00000000..a52a1d50
--- /dev/null
+++ b/package/osx/installer/build.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+set -euxo pipefail
+
+rm -rf package/osx/KeeWeb\ Installer.app
+
+osacompile \
+ -l JavaScript \
+ -o package/osx/KeeWeb\ Installer.app \
+ package/osx/installer/main.js
+
+cp graphics/icon.icns package/osx/KeeWeb\ Installer.app/Contents/Resources/applet.icns
+codesign --remove-signature package/osx/KeeWeb\ Installer.app
+
+CS_ID=$(cat keys/codesign.json | jq -j '.identities.app')
+codesign -s "$CS_ID" package/osx/KeeWeb\ Installer.app
diff --git a/package/osx/installer/main.js b/package/osx/installer/main.js
new file mode 100755
index 00000000..1fe22546
--- /dev/null
+++ b/package/osx/installer/main.js
@@ -0,0 +1,85 @@
+#!/usr/bin/env osascript -l JavaScript
+
+ObjC.import('Foundation');
+ObjC.import('stdlib');
+
+const app = Application.currentApplication();
+app.includeStandardAdditions = true;
+
+const args = getArgs();
+if (args.update) {
+ const waitPid = args.waitPid | 0;
+ if (waitPid) {
+ waitForExitOrKill(waitPid);
+ }
+
+ const dmg = checkFilePath(args.dmg, 'dmg');
+ const target = checkFilePath(args.target, 'app');
+
+ const targetOwner = app.doShellScript(`stat -f '%Su' ${target}`);
+ const setAdminRights = targetOwner === 'root';
+
+ const tmpDir = dmg + '.mount';
+
+ let script = `set -euxo pipefail
+hdiutil detach ${tmpDir} 2>/dev/null || true
+rm -rf ${tmpDir}
+mkdir -p ${tmpDir}
+hdiutil attach -readonly -nobrowse -mountpoint ${tmpDir} ${dmg}
+rm -rf ${target}
+cp -pR ${tmpDir}/KeeWeb.app ${target}
+hdiutil detach ${tmpDir}
+rm -rf ${tmpDir}
+`;
+ const scriptOptions = {};
+ if (setAdminRights) {
+ script += `chown -R 0 ${target}`;
+ scriptOptions.administratorPrivileges = true;
+ }
+ app.doShellScript(script, scriptOptions);
+} else if (args.install) {
+ app.doShellScript('chown -R 0 /Applications/KeeWeb.app', { administratorPrivileges: true });
+} else {
+ throw 'Unknown operation';
+}
+
+function getArgs() {
+ // https://github.com/JXA-Cookbook/JXA-Cookbook/wiki/Shell-and-CLI-Interactions#arguments
+ const objcArgs = $.NSProcessInfo.processInfo.arguments;
+ const argc = objcArgs.count;
+ const args = {};
+ for (let i = 0; i < argc; i++) {
+ const arg = ObjC.unwrap(objcArgs.objectAtIndex(i));
+ const match = arg.match(/^--([^=]+)(=(.*))?/);
+ if (match) {
+ const argName = match[1].replace(/-./g, (m) => m[1].toUpperCase());
+ args[argName] = match[3] || true;
+ }
+ }
+ return args;
+}
+
+function waitForExitOrKill(pid) {
+ for (let i = 0; i < 10; i++) {
+ const psCount = Application('System Events').processes.whose({ unixId: pid }).length;
+ if (psCount === 0) {
+ return;
+ }
+ delay(1);
+ }
+ app.doShellScript(`kill ${pid}`);
+}
+
+function checkFilePath(path, ext) {
+ if (!path) {
+ throw `File not specified: ${ext}`;
+ }
+ if (!path.endsWith('.' + ext)) {
+ throw `Bad file extension: ${ext} (${path})`;
+ }
+ const file = Application('System Events').files.byName(path);
+ if (!file.exists()) {
+ throw `File doesn't exist: ${ext} (${path})`;
+ }
+ return file.posixPath().replace(/ /g, '\\ ');
+}