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, '\\ '); +}