Skip to content

Commit

Permalink
Merge pull request #25 from teaxyz/refactor
Browse files Browse the repository at this point in the history
refactor into multiple categories/files
  • Loading branch information
mxcl authored Dec 17, 2024
2 parents a358e53 + 58fcbd6 commit b027bd5
Show file tree
Hide file tree
Showing 9 changed files with 1,083 additions and 1,053 deletions.
201 changes: 201 additions & 0 deletions Sources/teaBASE+DevTools.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#import "teaBASE.h"

@implementation teaBASE (DevTools)

- (IBAction)installBrew:(NSSwitch *)sender {
if (![NSFileManager.defaultManager isExecutableFileAtPath:@"/Library/Developer/CommandLineTools/usr/bin/git"]) {
NSAlert *alert = [NSAlert new];
alert.messageText = @"Prerequisite Unsatisfied";
alert.informativeText = @"Homebrew requires the Xcode Command Line Tools (CLT) to be installed first";
[alert runModal];

[sender setState:NSControlStateValueOff];
return;
}

if (sender.state == NSControlStateValueOn) {
[self.brewManualInstallInstructions setEditable:YES];
[self.brewManualInstallInstructions checkTextInDocument:sender];
[self.brewManualInstallInstructions setEditable:NO];

[self.mainView.window beginSheet:self.brewInstallWindow completionHandler:^(NSModalResponse returnCode) {
if (returnCode != NSModalResponseOK) {
[self.homebrewSwitch setState:NSControlStateValueOff];
} else {
[self updateVersions];
}
[self.brewInstallWindowSpinner stopAnimation:sender];
}];
} else {
#if __arm64
// Get the contents of the directory
NSError *error = nil;
NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"/opt/homebrew" error:&error];

if (error) {
[[NSAlert alertWithError:error] runModal];
return;
}

// Iterate over each item in the directory
for (NSString *item in contents) {
NSString *itemPath = [@"/opt/homebrew" stringByAppendingPathComponent:item];

BOOL success = [[NSFileManager defaultManager] removeItemAtPath:itemPath error:&error];
if (!success) {
[[NSAlert alertWithError:error] runModal];
return;
}
}

[self updateVersions];
#else
NSAlert *alert = [NSAlert new];
alert.informativeText = @"Please manually run the Homebrew uninstall script";
[alert runModal];
[sender setState:NSControlStateValueOn];
#endif
}
}

static BOOL installer(NSURL *url) {
NSURL *newurl = [[url URLByDeletingPathExtension] URLByAppendingPathExtension:@".pkg"];
[NSFileManager.defaultManager moveItemAtURL:url toURL:newurl error:nil];

char *arguments[] = {"-pkg", (char*)newurl.fileSystemRepresentation, "-target", "/", NULL};

return sudo_run_cmd("/usr/sbin/installer", arguments, @"Homebrew install failed");
}

static NSString* fetchLatestBrewVersion(void) {
NSURL *url = [NSURL URLWithString:@"https://api.github.com/repos/Homebrew/brew/releases/latest"];
NSData *data = [NSData dataWithContentsOfURL:url];
if (!data) return nil;

NSError *error = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error || !json[@"tag_name"]) return nil;

NSString *version = json[@"tag_name"];
if ([version hasPrefix:@"v"]) {
version = [version substringFromIndex:1];
}
return version;
}

- (IBAction)installBrewStep2:(NSButton *)sender {
[sender setEnabled:NO];
[self.brewInstallWindowSpinner startAnimation:sender];

NSString *version = fetchLatestBrewVersion();
if (!version) {
NSAlert *alert = [NSAlert new];
alert.messageText = @"Failed to fetch latest Homebrew version";
alert.informativeText = @"Please try again later or install manually.";
[alert runModal];
[NSApp endSheet:self.brewInstallWindow returnCode:NSModalResponseAbort];
[sender setEnabled:YES];
return;
}

NSString *urlstr = [NSString stringWithFormat:@"https://github.com/Homebrew/brew/releases/download/%@/Homebrew-%@.pkg", version, version];
NSURL *url = [NSURL URLWithString:urlstr];

[[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSAlert alertWithError:error] runModal];
[NSApp endSheet:self.brewInstallWindow returnCode:NSModalResponseAbort];
[sender setEnabled:YES];
});
} else if (installer(location)) {
// ^^ runs the installer on the NSURLSession queue as the download
// is deleted when it exits. afaict this is fine.
dispatch_async(dispatch_get_main_queue(), ^{

if (self.setupBrewShellEnvCheckbox.state == NSControlStateValueOn) {
NSString *zprofilePath = [NSHomeDirectory() stringByAppendingPathComponent:@".zprofile"];
NSString *cmdline = [NSString stringWithFormat:@"eval \"$(%@ shellenv)\"", brewPath()];

BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:zprofilePath];

// Check if the file exists, if not create it
if (!exists) {
[[NSFileManager defaultManager] createFileAtPath:zprofilePath contents:nil attributes:nil];
}
if (!file_contains(zprofilePath, cmdline)) {
// Open the file for appending
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:zprofilePath];
if (fileHandle) {
[fileHandle seekToEndOfFile];
if (exists) {
[fileHandle writeData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
[fileHandle writeData:[cmdline dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle writeData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
} else {
//TODO
}
}
}

[NSApp endSheet:self.brewInstallWindow returnCode:NSModalResponseOK];
[sender setEnabled:YES];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
NSAlert *alert = [NSAlert new];
alert.messageText = @"Installation Error";
alert.informativeText = @"Unknown error occurred. Please install Homebrew manually.";
[alert runModal];
[NSApp endSheet:self.brewInstallWindow returnCode:NSModalResponseAbort];
[sender setEnabled:YES];
});
}
}] resume];
}

- (IBAction)installPkgx:(NSSwitch *)sender {
if (sender.state == NSControlStateValueOn) {
[self installSubexecutable:@"pkgx"];
[self updateVersions];
} else {
char *args[] = {"/usr/local/bin/pkgx", NULL};
sudo_run_cmd("/bin/rm", args, @"Couldn’t delete /usr/local/bin/pkgx");
}
}

- (IBAction)installDocker:(NSSwitch *)sender {
// using Terminal as the install steps requires `sudo`
// using script as you can only pass a single arg to Terminal.app apparently
id path = [[[NSBundle bundleForClass:[self class]] bundlePath] stringByAppendingPathComponent:@"Contents/Scripts/install-docker.sh"];
run(@"/usr/bin/open", @[@"-a", @"Terminal.app", path], nil);
}

- (IBAction)openDockerHome:(id)sender {
NSURL *url = [NSURL URLWithString:@"https://docker.com"];
[[NSWorkspace sharedWorkspace] openURL:url];
}

- (IBAction)openPkgxHome:(id)sender {
NSURL *url = [NSURL URLWithString:@"https://pkgx.sh"];
[[NSWorkspace sharedWorkspace] openURL:url];
}

- (IBAction)openHomebrewHome:(id)sender {
NSURL *url = [NSURL URLWithString:@"https://brew.sh"];
[[NSWorkspace sharedWorkspace] openURL:url];
}

- (IBAction)openXcodeCLTHome:(id)sender {
NSURL *url = [NSURL URLWithString:@"https://developer.apple.com/xcode/resources/"];
[[NSWorkspace sharedWorkspace] openURL:url];
}

- (IBAction)gitAddOnsHelpButton:(id)sender {
NSURL *url = [NSURL URLWithString:@"https://github.com/pkgxdev/git-gud"];
[[NSWorkspace sharedWorkspace] openURL:url];
}

@end
70 changes: 70 additions & 0 deletions Sources/teaBASE+DotfileSync.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#import "teaBASE.h"

@implementation teaBASE (dotfileSync)

- (BOOL)dotfileSyncEnabled {
return run(@"/bin/launchctl", @[@"list", @"xyz.tea.BASE.dotfile-sync"], nil);
}

- (BOOL)dotfileDirThere {
BOOL isdir = NO;
id path = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/teaBASE/dotfiles.git"];
if (![NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isdir]) return NO;
if (!isdir) return NO;
return YES;
}

- (IBAction)onDotfileSyncToggled:(NSSwitch *)sender {

id dst = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/LaunchAgents/xyz.tea.BASE.dotfile-sync.plist"];

if (sender.state == NSControlStateValueOn) {
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
id src = [bundle pathForResource:@"xyz.tea.BASE.dotfile-sync" ofType:@"plist"];
NSString *contents = [NSString stringWithContentsOfFile:src encoding:NSUTF8StringEncoding error:nil];
id prefpane_path = [bundle bundlePath];
contents = [contents stringByReplacingOccurrencesOfString:@"$PREFPANE" withString:prefpane_path];
contents = [contents stringByReplacingOccurrencesOfString:@"$HOME" withString:NSHomeDirectory()];
[NSFileManager.defaultManager createDirectoryAtPath:[dst stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
[contents writeToFile:dst atomically:NO encoding:NSUTF8StringEncoding error:nil];
run(@"/bin/launchctl", @[@"load", dst], nil);

//TODO need to wait for the above launchctl job to finish lol

NSLog(@"teaBASE: %@", self.dotfileDirThere ? @"YES" :@"NO");

if (!self.dotfileDirThere) {
NSString *script_path = [[[NSBundle bundleForClass:[self class]] bundlePath] stringByAppendingPathComponent:@"Contents/Scripts/dotfile-sync.sh"];

run(@"/usr/bin/open", @[
@"-a", @"Terminal.app", script_path
], nil);
}

} else {
run(@"/bin/launchctl", @[@"unload", dst], nil);
[[NSFileManager defaultManager] removeItemAtPath:dst error:nil];
}

//FIXME need to know when the below script finishes before loading us into launchctl

BOOL worked = [self dotfileSyncEnabled];

[self.dotfileSyncEditWhitelistButton setEnabled:worked];
[self.dotfileSyncViewRepoButton setEnabled:worked];
[self.dotfileSyncSwitch setState:worked ? NSControlStateValueOn : NSControlStateValueOff];
}

- (IBAction)viewDotfilesRepo:(id)sender {
//TODO use the origin remote URL to figure this out instead
id pkgx = [[[NSBundle bundleForClass:[self class]] bundlePath] stringByAppendingPathComponent:@"Contents/MacOS/pkgx"];
run(pkgx, @[@"gh", @"repo", @"view", @"--web", @"dotfiles"], nil);
}

- (IBAction)editWhitelist:(id)sender {
id url = @"https://github.com/teaxyz/teaBASE/blob/main/Scripts/dotfile-sync.sh";
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
}

@end

111 changes: 111 additions & 0 deletions Sources/teaBASE+GPG.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#import "teaBASE.h"

@implementation teaBASE (GPG)

- (BOOL)gpgSignEnabled {
id pkgx = [[[NSBundle bundleForClass:[self class]] bundlePath] stringByAppendingPathComponent:@"Contents/MacOS/pkgx"];
return [output(pkgx, @[@"git", @"config", @"--global", @"commit.gpgsign"]) isEqualToString:@"true"];
}

- (IBAction)signCommits:(NSSwitch *)sender {
NSString *git = which(@"git");
NSArray *config = @[@"config", @"--global"];

if ([git isEqualToString:@"/usr/bin/git"] && ![self xcodeCLTInstalled]) {
git = [[[NSBundle bundleForClass:[self class]] bundlePath] stringByAppendingPathComponent:@"Contents/MacOS/pkgx"];
config = @[@"git", @"config", @"--global"];
}

if (sender.state == NSControlStateValueOn) {

//FIXME if XDG_* vars set uses that which requires us to run a script in a login shell to extract
id configfile = [NSHomeDirectory() stringByAppendingPathComponent:@".config/pkgx/bpb.toml"];
if (![NSFileManager.defaultManager isReadableFileAtPath:configfile]) {
configfile = [NSHomeDirectory() stringByAppendingPathComponent:@".local/share/pkgx/bpb.toml"];
}

if ([NSFileManager.defaultManager isReadableFileAtPath:configfile]) {
run(git, [config arrayByAddingObjectsFromArray:@[@"commit.gpgsign", @"true"]], nil);
run(git, [config arrayByAddingObjectsFromArray:@[@"gpg.program", @"bpb"]], nil);

if (![NSFileManager.defaultManager isExecutableFileAtPath:@"/usr/local/bin/bpb"]) {
[self installSubexecutable:@"bpb"];
}

[self calculateSecurityRating];
}
else [self.mainView.window beginSheet:self.gpgPassphraseWindow completionHandler:^(NSModalResponse returnCode) {
if (returnCode != NSModalResponseOK) {
[self.gpgSignSwitch setState:NSControlStateValueOff];
} else {
id username = self.setupGPGWindowUsername.stringValue;
id email = self.setupGPGWindowEmail.stringValue;
[self installBPB:username email:email];

//TODO need to have a git installed first
run(git, [config arrayByAddingObjectsFromArray:@[@"commit.gpgsign", @"true"]], nil);
run(git, [config arrayByAddingObjectsFromArray:@[@"gpg.program", @"bpb"]], nil);
[self calculateSecurityRating];
}
}];
} else {
run(git, [config arrayByAddingObjectsFromArray:@[@"commit.gpgsign", @"false"]], nil);
[self calculateSecurityRating];
}
}

- (void)installBPB:(id)username email:(id)email {
if (![NSFileManager.defaultManager isExecutableFileAtPath:@"/usr/local/bin/bpb"]) {
[self installSubexecutable:@"bpb"];
}

if (![NSFileManager.defaultManager isReadableFileAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@".bpb_keys.toml"]]) {
id initstr = [NSString stringWithFormat:@"%@ <%@>", username, email];
run(@"/usr/local/bin/bpb", @[@"init", initstr], nil);
}
}

- (IBAction)printGPGEmergencyKit:(id)sender {
NSString *pubkey = output(@"/usr/local/bin/bpb", @[@"print"]);
NSString *privkey = output(@"/usr/bin/security", @[@"find-generic-password", @"-s", @"xyz.tea.BASE.bpb", @"-w"]);

if (!pubkey || !privkey) {
NSAlert *alert = [NSAlert new];
alert.informativeText = @"An error occurred trying to obtain your GPG keypair";
[alert runModal];
return;
}

id content = @"Public Key:\n\n";
content = [content stringByAppendingString:pubkey];
content = [content stringByAppendingString:@"\n\nPrivate Key:\n\n"];
content = [content stringByAppendingString:privkey];

// Create an NSTextView and set the document content
NSTextView *textView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, 612, 612)]; // Typical page size
[textView setString:content];

// Set font to Menlo (monospace) and make it smaller in order to fit the page
NSFont *monoFont = [NSFont fontWithName:@"Menlo" size:9.6];
[textView setFont:monoFont];
[[textView textStorage] setFont:monoFont]; // Ensure the entire text storage uses the font

// Configure the print operation for the text view
NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView:textView];
[printOperation setShowsPrintPanel:YES];
[printOperation setShowsProgressPanel:YES];
[printOperation setJobTitle:@"GPG_Emergency_Kit.pdf"];

// Run the print operation (this will display the print dialog)
[printOperation runOperationModalForWindow:[sender window] delegate:self didRunSelector:@selector(didPrintGPGEmergencyKit:success:) contextInfo:nil];
}

- (void)didPrintGPGEmergencyKit:(NSPrintOperation *)op success:(BOOL)success {
if (success) {
self.greenCheckGPGBackup.hidden = NO;
[self.defaultsController.defaults setValue:@YES forKey:@"xyz.tea.BASE.printed-GPG-emergency-kit"];
[self calculateSecurityRating];
}
}

@end
Loading

0 comments on commit b027bd5

Please sign in to comment.