diff options
author | Kenneth Skovhede <kenneth@hexad.dk> | 2019-10-29 01:21:50 +0300 |
---|---|---|
committer | Kenneth Skovhede <kenneth@hexad.dk> | 2019-10-29 01:21:50 +0300 |
commit | 22a4aec31220422afa6771d08930e9f0894858c6 (patch) | |
tree | 7c6a5a6ffee3f2d5d954181fed38784ec1f4c193 /Installer | |
parent | da0b1eefa42cb527b9e7a7b26f78eb196f47c1a4 (diff) |
Ported `run-with-mono.sh` bash script to Objective C version to work better with MacOS Catalina security features
Diffstat (limited to 'Installer')
-rw-r--r-- | Installer/OSX/Duplicati-commandline-launcher | 7 | ||||
-rw-r--r-- | Installer/OSX/Duplicati-server-launcher | 7 | ||||
-rw-r--r-- | Installer/OSX/Duplicati-trayicon-launcher | 7 | ||||
-rwxr-xr-x | Installer/OSX/launchers/compile.sh | 10 | ||||
-rw-r--r-- | Installer/OSX/launchers/duplicati-cli.m | 13 | ||||
-rw-r--r-- | Installer/OSX/launchers/duplicati-server.m | 13 | ||||
-rw-r--r-- | Installer/OSX/launchers/duplicati.m | 13 | ||||
-rw-r--r-- | Installer/OSX/launchers/run-with-mono.h | 8 | ||||
-rw-r--r-- | Installer/OSX/launchers/run-with-mono.m | 162 | ||||
-rw-r--r-- | Installer/OSX/make-dmg.sh | 15 | ||||
-rw-r--r-- | Installer/OSX/run-with-mono.sh | 106 |
11 files changed, 228 insertions, 133 deletions
diff --git a/Installer/OSX/Duplicati-commandline-launcher b/Installer/OSX/Duplicati-commandline-launcher deleted file mode 100644 index 03146d9ff..000000000 --- a/Installer/OSX/Duplicati-commandline-launcher +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -export APP_NAME="Duplicati CommandLine" -export ASSEMBLY="Duplicati.CommandLine.exe" - -bash "${SCRIPT_DIR}/run-with-mono.sh" "$@"
\ No newline at end of file diff --git a/Installer/OSX/Duplicati-server-launcher b/Installer/OSX/Duplicati-server-launcher deleted file mode 100644 index bb2ccbca2..000000000 --- a/Installer/OSX/Duplicati-server-launcher +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -export APP_NAME="Duplicati Server" -export ASSEMBLY="Duplicati.Server.exe" - -bash "${SCRIPT_DIR}/run-with-mono.sh" "$@" diff --git a/Installer/OSX/Duplicati-trayicon-launcher b/Installer/OSX/Duplicati-trayicon-launcher deleted file mode 100644 index d1385bacf..000000000 --- a/Installer/OSX/Duplicati-trayicon-launcher +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -export APP_NAME="Duplicati" -export ASSEMBLY="Duplicati.GUI.TrayIcon.exe" - -bash "${SCRIPT_DIR}/run-with-mono.sh" "$@"
\ No newline at end of file diff --git a/Installer/OSX/launchers/compile.sh b/Installer/OSX/launchers/compile.sh new file mode 100755 index 000000000..6a6e42dc8 --- /dev/null +++ b/Installer/OSX/launchers/compile.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# -fobjc-arc: enables ARC +# -fmodules: enables modules so you can import with `@import AppKit;` +# -mmacosx-version-min=10.6: support older OS X versions, this might increase the binary size + +if [ ! -d "bin" ]; then mkdir bin; fi + +clang run-with-mono.m duplicati.m -fobjc-arc -fmodules -mmacosx-version-min=10.6 -o bin/duplicati +clang run-with-mono.m duplicati-cli.m -fobjc-arc -fmodules -mmacosx-version-min=10.6 -o bin/duplicati-cli +clang run-with-mono.m duplicati-server.m -fobjc-arc -fmodules -mmacosx-version-min=10.6 -o bin/duplicati-server
\ No newline at end of file diff --git a/Installer/OSX/launchers/duplicati-cli.m b/Installer/OSX/launchers/duplicati-cli.m new file mode 100644 index 000000000..434bd0f9f --- /dev/null +++ b/Installer/OSX/launchers/duplicati-cli.m @@ -0,0 +1,13 @@ +#import "run-with-mono.h" + +NSString * const ASSEMBLY = @"Duplicati.CommandLine.exe"; +NSString * const APP_NAME = @"Duplicati.CommandLine"; +int const MONO_VERSION_MAJOR = 4; +int const MONO_VERSION_MINOR = 0; + +int main() { + @autoreleasepool { + return [RunWithMono runAssemblyWithMono:APP_NAME assembly:ASSEMBLY major:MONO_VERSION_MAJOR minor:MONO_VERSION_MINOR]; + } +} + diff --git a/Installer/OSX/launchers/duplicati-server.m b/Installer/OSX/launchers/duplicati-server.m new file mode 100644 index 000000000..f4d81ef42 --- /dev/null +++ b/Installer/OSX/launchers/duplicati-server.m @@ -0,0 +1,13 @@ +#import "run-with-mono.h" + +NSString * const ASSEMBLY = @"Duplicati.Server.exe"; +NSString * const APP_NAME = @"Duplicati.Server"; +int const MONO_VERSION_MAJOR = 4; +int const MONO_VERSION_MINOR = 0; + +int main() { + @autoreleasepool { + return [RunWithMono runAssemblyWithMono:APP_NAME assembly:ASSEMBLY major:MONO_VERSION_MAJOR minor:MONO_VERSION_MINOR]; + } +} + diff --git a/Installer/OSX/launchers/duplicati.m b/Installer/OSX/launchers/duplicati.m new file mode 100644 index 000000000..407b923dd --- /dev/null +++ b/Installer/OSX/launchers/duplicati.m @@ -0,0 +1,13 @@ +#import "run-with-mono.h" + +NSString * const ASSEMBLY = @"Duplicati.GUI.TrayIcon.exe"; +NSString * const APP_NAME = @"Duplicati"; +int const MONO_VERSION_MAJOR = 4; +int const MONO_VERSION_MINOR = 0; + +int main() { + @autoreleasepool { + return [RunWithMono runAssemblyWithMono:APP_NAME assembly:ASSEMBLY major:MONO_VERSION_MAJOR minor:MONO_VERSION_MINOR]; + } +} + diff --git a/Installer/OSX/launchers/run-with-mono.h b/Installer/OSX/launchers/run-with-mono.h new file mode 100644 index 000000000..7131bd6df --- /dev/null +++ b/Installer/OSX/launchers/run-with-mono.h @@ -0,0 +1,8 @@ +@import Foundation; + +@interface RunWithMono : NSObject { +} + ++ (int) runAssemblyWithMono:(NSString *)appName assembly:(NSString *)assembly major:(int) major minor:(int) minor; + +@end
\ No newline at end of file diff --git a/Installer/OSX/launchers/run-with-mono.m b/Installer/OSX/launchers/run-with-mono.m new file mode 100644 index 000000000..99e4647a1 --- /dev/null +++ b/Installer/OSX/launchers/run-with-mono.m @@ -0,0 +1,162 @@ +#import "run-with-mono.h" + +@import Foundation; +@import AppKit; + +NSString * const VERSION_TITLE = @"Cannot launch %@"; +NSString * const VERSION_MSG = @"%@ requires the Mono Framework version %d.%d or later."; +NSString * const DOWNLOAD_URL = @"http://www.mono-project.com/download/stable/#download-mac"; + +// Helper method to see if the user has requested debug output +bool D() { + NSString* v = [[[NSProcessInfo processInfo]environment]objectForKey:@"DEBUG"]; + if (v == nil || v.length == 0 || [v isEqual:@"0"] || [v isEqual:@"false"] || [v isEqual:@"f"]) + return false; + return true; +} + +// Wrapper method to invoke commandline operations and return the string output +NSString *runCommand(NSString *program, NSArray<NSString *> *arguments) { + NSPipe *pipe = [NSPipe pipe]; + NSFileHandle *file = pipe.fileHandleForReading; + + NSTask *task = [[NSTask alloc] init]; + task.launchPath = program; + task.arguments = arguments; + task.standardOutput = pipe; + + [task launch]; + + NSData *data = [file readDataToEndOfFile]; + [file closeFile]; + [task waitUntilExit]; + + NSString *cmdOutput = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; + if (cmdOutput == nil || cmdOutput.length == 0) + return nil; + + return [cmdOutput stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; +} + +// Checks if the Mono version is greater than or equal to the desired version +bool isValidMono(NSString *mono, int major, int minor) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if (mono == nil) + return false; + + if (![fileManager fileExistsAtPath:mono] || ![fileManager isExecutableFileAtPath:mono]) + return false; + + NSString *versionInfo = runCommand(mono, @[@"--version"]); + + NSRange rg = [versionInfo rangeOfString:@"Mono JIT compiler version \\d+\\.\\d+" options:NSRegularExpressionSearch]; + if (rg.location != NSNotFound) { + versionInfo = [versionInfo substringWithRange:rg]; + if (D()) NSLog(@"Matched version: %@", versionInfo); + rg = [versionInfo rangeOfString:@"\\d+\\.\\d+" options:NSRegularExpressionSearch]; + if (rg.location != NSNotFound) { + versionInfo = [versionInfo substringWithRange:rg]; + if (D()) NSLog(@"Matched version: %@", versionInfo); + + NSArray<NSString *> *versionComponents = [versionInfo componentsSeparatedByString:@"."]; + if ([versionComponents[0] intValue] < major) + return false; + if ([versionComponents[1] intValue] < minor) + return false; + + return true; + } + } + + return false; +} + +// Attempts to locate a mono with a valid version +NSString *findMono(int major, int minor) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSString *currentMono = runCommand(@"/usr/bin/which", @[@"mono"]); + if (D()) NSLog(@"which mono: %@", currentMono); + + if (isValidMono(currentMono, major, minor)) { + if (D()) NSLog(@"Found mono with: %@", currentMono); + return currentMono; + } + + NSArray *probepaths = @[@"/usr/local/bin/mono", @"/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono", @"/opt/local/bin/mono"]; + for(NSString* probepath in probepaths) { + if (D()) NSLog(@"Trying mono with: %@", probepath); + if (isValidMono(probepath, major, minor)) { + if (D()) NSLog(@"Found mono with: %@", probepath); + return probepath; + } + } + + if (D()) NSLog(@"Failed to find Mono, returning: %@", nil); + return nil; +} + +// Shows the download dialog, prompting to download Mono +void showDownloadMonoDialog(NSString *appName, int major, int minor) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setInformativeText:[NSString stringWithFormat:VERSION_MSG, appName, major, minor]]; + [alert setMessageText:[NSString stringWithFormat:VERSION_TITLE, appName]]; + [alert addButtonWithTitle:@"Cancel"]; + [alert addButtonWithTitle:@"Download"]; + NSModalResponse btn = [alert runModal]; + if (btn == NSAlertSecondButtonReturn) { + if (D()) NSLog(@"Clicked download"); + runCommand(@"/usr/bin/open", @[DOWNLOAD_URL]); + //[[UIApplication sharedApplication] openURL:[NSURL URLWithString:DOWNLOAD_URL] options:@{} completionHandler:nil]; + } +} + +// Top-level method, finds Mono with an appropriate version and launches the assembly +int runAssemblyWithMono(NSString *appName, NSString *assembly, int major, int minor) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSString *entryFolder = [[NSBundle mainBundle] resourcePath]; + if (D()) NSLog(@"entryFolder: %@", entryFolder); + + NSString *assemblyPath = [NSString pathWithComponents:@[entryFolder, assembly]]; + if (D()) NSLog(@"assemblyPath: %@", assemblyPath); + + if (![fileManager fileExistsAtPath:assemblyPath]) { + NSLog(@"Assembly file not found: %@", assemblyPath); + return 1; + } + + NSString *currentMono = findMono(major, minor); + if (currentMono == nil) { + NSLog(@"No valid mono found!"); + showDownloadMonoDialog(appName, major, minor); + return 1; + } + + if (D()) NSLog(@"Running %@ %@", currentMono, assemblyPath); + + // Copy commandline arguments + NSMutableArray* arguments = [[NSMutableArray alloc] init]; + [arguments addObjectsFromArray:[[NSProcessInfo processInfo] arguments]]; + + // replace the executable-path with the assembly path + [arguments replaceObjectAtIndex:0 withObject:assemblyPath]; + + NSTask *task = [[NSTask alloc] init]; + task.launchPath = currentMono; + task.arguments = arguments; + + [task launch]; + [task waitUntilExit]; + + return [task terminationStatus]; +} + +@implementation RunWithMono ++ (int) runAssemblyWithMono:(NSString *)appName assembly:(NSString *)assembly major:(int) major minor:(int) minor { + return runAssemblyWithMono(appName, assembly, major, minor); +} +@end + diff --git a/Installer/OSX/make-dmg.sh b/Installer/OSX/make-dmg.sh index 68feae02a..1889e6985 100644 --- a/Installer/OSX/make-dmg.sh +++ b/Installer/OSX/make-dmg.sh @@ -99,14 +99,17 @@ done # Install the LauncAgent if anyone needs it cp -R "daemon" "Duplicati.app/Contents/Resources" -# Install executables -cp "run-with-mono.sh" "Duplicati.app/Contents/MacOS/" -cp "Duplicati-trayicon-launcher" "Duplicati.app/Contents/MacOS/duplicati" -cp "Duplicati-commandline-launcher" "Duplicati.app/Contents/MacOS/duplicati-cli" -cp "Duplicati-server-launcher" "Duplicati.app/Contents/MacOS/duplicati-server" +# Build launchers +cd "launchers" +bash "compile.sh" +cd .. + +# Install launchers +mv "launchers/bin/duplicati" "Duplicati.app/Contents/MacOS/duplicati" +mv "launchers/bin/duplicati-cli" "Duplicati.app/Contents/MacOS/duplicati-cli" +mv "launchers/bin/duplicati-server" "Duplicati.app/Contents/MacOS/duplicati-server" cp "uninstall.sh" "Duplicati.app/Contents/MacOS/" -chmod +x "Duplicati.app/Contents/MacOS/run-with-mono.sh" chmod +x "Duplicati.app/Contents/MacOS/duplicati" chmod +x "Duplicati.app/Contents/MacOS/duplicati-cli" chmod +x "Duplicati.app/Contents/MacOS/duplicati-server" diff --git a/Installer/OSX/run-with-mono.sh b/Installer/OSX/run-with-mono.sh deleted file mode 100644 index a954b4123..000000000 --- a/Installer/OSX/run-with-mono.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/bin/bash -# -# This is a stub script that allows .apps to be relocatable on OSX but still -# find the managed assembly. -# -# This is copied from the Mono macpack tool and modified to fit Duplicati -# -# The Mono Version Check is from here: -# http://mjhutchinson.com/journal/2010/01/24/creating_mac_app_bundle_for_gtk_app -# - -# Figure out the full path to this script -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -if [ "z${SCRIPT_DIR}" == "z" ]; then - SCRIPT_DIR="$( dirname "$0" )" -fi - -APP_PATH="$( dirname "${SCRIPT_DIR}" )" - -export GDIPLUS_NOX=1 - -#mono version check - -REQUIRED_MAJOR=3 -REQUIRED_MINOR=0 - -VERSION_TITLE="Cannot launch $APP_NAME" -VERSION_MSG="$APP_NAME requires the Mono Framework version $REQUIRED_MAJOR.$REQUIRED_MINOR or later." -DOWNLOAD_URL="http://www.mono-project.com/download/stable/#download-mac" - -# Try to find system default Mono if an override was not supplied -if [ "z${MONO_BIN}" == "z" ]; then - MONO_BIN=$(which mono) - - # If the result is broken, don't use it - if [ ! -f "${MONO_BIN}" ]; then - MONO_BIN="" - fi - - # Check if there was no Mono found - if [ "z${MONO_BIN}" == "z" ]; then - # Check if there is a HomeBrew install of Mono - if [ -f "/usr/local/bin/mono" ]; then - MONO_BIN="/usr/local/bin/mono" - - # Check if there is a system version of Mono - elif [ -f "/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono" ]; then - MONO_BIN="/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono" - - # Check if there is a MacPorts version of Mono - elif [ -f "/opt/local/bin/mono" ]; then - MONO_BIN="/opt/local/bin/mono" - - # Set up some default that will likely fail - else - MONO_BIN="mono" - - fi - fi -fi - -MONO_VERSION="$(${MONO_BIN} --version | grep 'Mono JIT compiler version ' | cut -f5 -d\ )" -MONO_VERSION_MAJOR="$(echo $MONO_VERSION | cut -f1 -d.)" -MONO_VERSION_MINOR="$(echo $MONO_VERSION | cut -f2 -d.)" -if [ -z "$MONO_VERSION" ] \ - || [ $MONO_VERSION_MAJOR -lt $REQUIRED_MAJOR ] \ - || [ $MONO_VERSION_MAJOR -eq $REQUIRED_MAJOR -a $MONO_VERSION_MINOR -lt $REQUIRED_MINOR ] -then - osascript \ - -e "set question to display dialog \"$VERSION_MSG\" with title \"$VERSION_TITLE\" buttons {\"Cancel\", \"Download...\"} default button 2" \ - -e "if button returned of question is equal to \"Download...\" then open location \"$DOWNLOAD_URL\"" - echo "$VERSION_TITLE" - echo "$VERSION_MSG" - exit 1 -fi - -# Move into the folder where all the assemblies are located -cd "${APP_PATH}/Resources" - -# Get the current running version -OSX_VERSION=$(uname -r | cut -f1 -d.) - -if [ $OSX_VERSION -lt 9 ]; then # If OSX version is 10.4 or less, the exec command is missing the -a option - - # Attempt to create a folder inside the Resources folder - if [ ! -d "./bin" ]; then mkdir bin ; fi - - # If we failed, the meduim is probably read-only, so we revert to exec - if [ ! -d "./bin" ] - then - exec "${MONO_BIN}" "$ASSEMBLY" $@ - else - - # We can make the helper file, lets use that - if [ -f "./bin/$APP_NAME" ]; then rm -f "./bin/$APP_NAME" ; fi - ln -s "${MONO_BIN}" "./bin/$APP_NAME" - - # Start Duplicati using the renamed symlink to Mono - "./bin/$APP_NAME" "$ASSEMBLY" $@ - fi -else - # On a modern OSX, so we avoid modifying the bundle contents - exec -a "$APP_NAME" "${MONO_BIN}" "$ASSEMBLY" $@ -fi - - |