1// 2// Installer.m 3// Keybase 4// 5// Created by Gabriel on 11/23/15. 6// Copyright © 2015 Keybase. All rights reserved. 7// 8 9#import "Installer.h" 10 11#import <KBKit/KBKit.h> 12#import "Options.h" 13#import "Uninstaller.h" 14 15@interface Installer () 16@property Options *options; 17@property KBMemLogger *memLogger; 18@end 19 20typedef NS_ENUM (NSInteger, KBExit) { 21 KBExitOK = 0, 22 KBExitIgnoreError = 0, 23 KBExitError = 1, 24 KBExitFuseKextError = 4, 25 KBExitFuseKextPermissionError = 5, 26 KBExitAuthCanceledError = 6, 27 KBExitFuseKextMountsPresentError = 7, 28 KBExitFuseCriticalUpdate = 8, 29}; 30 31@implementation Installer 32 33- (void)applicationDidFinishLaunching:(NSNotification *)notification { 34 dispatch_async(dispatch_get_main_queue(), ^{ 35 [self run]; 36 }); 37} 38 39- (void)run { 40 // Check process arguments directly for debug (instead of GBCli) to setup logging right away 41 NSArray *arguments = [[NSProcessInfo processInfo] arguments]; 42 BOOL debug = NO; 43 if ([arguments containsObject:@"--debug"]) { 44 debug = YES; 45 } 46#if DEBUG 47 debug = YES; 48#endif 49 50 [KBWorkspace setupLogging:debug]; 51 52 _memLogger = [[KBMemLogger alloc] init]; 53 [DDLog addLogger:_memLogger withLevel:DDLogLevelDebug]; 54 55 [KBAppearance setCurrentAppearance:[KBUIAppearance appearance]]; 56 57 DDLogDebug(@"Version: %@", NSBundle.mainBundle.infoDictionary[(NSString *)kCFBundleVersionKey]); 58 59 GBSettings *settings = [GBSettings settingsWithName:@"Settings" parent:nil]; 60#if DEBUG 61 [settings setObject:@"/Applications/Keybase.app" forKey:@"app-path"]; 62 // [self.settings setObject:@"/Volumes/Keybase/Keybase.app" forKey:@"app-path"]; 63 [settings setObject:@"prod" forKey:@"run-mode"]; 64 [settings setObject:@"10" forKey:@"timeout"]; 65#endif 66 _options = [[Options alloc] initWithSettings:settings]; 67 NSError *parseError = nil; 68 if (![_options parseArgs:&parseError]) { 69 DDLogError(@"Error parsing: %@", parseError); 70 [self exit:KBExitError]; 71 return; 72 } 73 74 if ([_options isUninstall]) { 75 [self uninstall]; 76 } else { 77 [self install]; 78 } 79} 80 81- (void)waitForLog { 82 dispatch_semaphore_t sema = dispatch_semaphore_create(0); 83 dispatch_async(DDLog.loggingQueue, ^{ 84 dispatch_semaphore_signal(sema); 85 }); 86 dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC)); 87} 88 89- (void)exit:(KBExit)code { 90 [self waitForLog]; 91 exit(code); 92} 93 94- (void)install { 95 [self install:^(NSError *error, KBEnvironment *environment, KBExit exitCode) { 96 dispatch_async(dispatch_get_main_queue(), ^{ 97 DDLogInfo(@"Exit(%@)", @(exitCode)); 98 [self exit:exitCode]; 99 }); 100 }]; 101} 102 103- (void)uninstall { 104 [Uninstaller uninstallWithOptions:_options completion:^(NSError *error) { 105 if (error) { 106 DDLogError(@"Error uninstalling: %@", error); 107 [self exit:KBExitError]; 108 return; 109 } 110 DDLogInfo(@"Uninstalled"); 111 [self exit:KBExitOK]; 112 }]; 113} 114 115+ (instancetype)sharedDelegate { 116 return (Installer *)[NSApp delegate]; 117} 118 119- (IBAction)quit:(id)sender { 120 [NSApplication.sharedApplication terminate:sender]; 121} 122 123- (void)install:(void (^)(NSError *error, KBEnvironment *environment, KBExit exit))completion { 124 KBEnvironment *environment = [self.options environment]; 125 126 KBInstaller *installer = [[KBInstaller alloc] init]; 127 [installer installWithEnvironment:environment force:NO stopOnError:YES completion:^(NSError *error, NSArray *installables) { 128 [self checkError:error environment:environment completion:^(NSError *error, KBExit exit) { 129 completion(error, environment, exit); 130 }]; 131 }]; 132} 133 134- (void)checkError:(NSError *)error environment:(KBEnvironment *)environment completion:(void (^)(NSError *error, KBExit exit))completion { 135 if (!error) { 136 completion(nil, KBExitOK); 137 return; 138 } 139 140 if (error.code == KBErrorCodeFuseKextPermission) { 141 completion(nil, KBExitFuseKextPermissionError); 142 return; 143 } 144 145 if (error.code == KBErrorCodeFuseKext) { 146 completion(nil, KBExitFuseKextError); 147 return; 148 } 149 150 if (error.code == errAuthorizationCanceled) { 151 completion(nil, KBExitAuthCanceledError); 152 return; 153 } 154 155 if (error.code == KBErrorCodeFuseKextMountsPresent) { 156 completion(nil, KBExitFuseKextMountsPresentError); 157 return; 158 } 159 160 if (error.code == KBErrorCodeFuseCriticalUpdate) { 161 completion(nil, KBExitFuseCriticalUpdate); 162 } 163 164 completion(nil, KBExitError); 165} 166 167- (void)showErrorDialog:(NSError *)error environment:(KBEnvironment *)environment completion:(void (^)(NSError *error, KBExit exit))completion { 168 NSAlert *alert = [[NSAlert alloc] init]; 169 [alert setMessageText:@"Keybase Error"]; 170 171 NSString *info = error.localizedDescription; 172 if (![NSString gh_isBlank:error.localizedRecoverySuggestion]) { 173 info = NSStringWithFormat(@"%@\n\n%@", info, error.localizedRecoverySuggestion); 174 } 175 176 [alert setInformativeText:info]; 177 [alert addButtonWithTitle:@"OK"]; 178 179 NSURL *URL = error.userInfo[NSURLErrorKey]; 180 if (URL) { 181 [alert addButtonWithTitle:@"Troubleshoot"]; 182 } else { 183 [alert addButtonWithTitle:@"More Details"]; 184 } 185 186 [alert setAlertStyle:NSWarningAlertStyle]; 187 NSModalResponse response = [alert runModal]; 188 if (response == NSAlertFirstButtonReturn) { 189 completion(error, KBExitIgnoreError); 190 } else if (response == NSAlertSecondButtonReturn) { 191 if (URL) { 192 [[NSWorkspace sharedWorkspace] openURL:URL]; 193 } else { 194 [self showMoreDetails:error environment:environment completion:completion]; 195 } 196 } else { 197 DDLogError(@"Unknown error dialog return button"); 198 completion(error, KBExitIgnoreError); 199 } 200} 201 202- (void)showMoreDetails:(NSError *)error environment:(KBEnvironment *)environment completion:(void (^)(NSError *error, KBExit exit))completion { 203 NSAlert *alert = [[NSAlert alloc] init]; 204 [alert setMessageText:@"Keybase Error"]; 205 [alert setInformativeText:error.localizedDescription]; 206 [alert addButtonWithTitle:@"OK"]; 207 208 KBTextView *textView = [[KBTextView alloc] init]; 209 textView.editable = NO; 210 textView.view.textContainerInset = CGSizeMake(5, 5); 211 212 NSMutableString *info = [NSMutableString stringWithString:[environment debugInstallables]]; 213 if (_memLogger) { 214 [info appendString:@"Log:\n"]; 215 [info appendString:[_memLogger messages]]; 216 } 217 [textView setText:info style:KBTextStyleDefault options:KBTextOptionsMonospace|KBTextOptionsSmall alignment:NSLeftTextAlignment lineBreakMode:NSLineBreakByCharWrapping]; 218 219 textView.frame = CGRectMake(0, 0, 500, 200); 220 textView.borderType = NSBezelBorder; 221 alert.accessoryView = textView; 222 [alert runModal]; 223 224 completion(error, KBExitIgnoreError); 225} 226 227@end 228