mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-03-05 17:27:24 +01:00
Split out libraries into SPM packages (#298)
This commit is contained in:
48
Sources/Secretive/Controllers/AgentStatusChecker.swift
Normal file
48
Sources/Secretive/Controllers/AgentStatusChecker.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
import AppKit
|
||||
import SecretKit
|
||||
|
||||
protocol AgentStatusCheckerProtocol: ObservableObject {
|
||||
var running: Bool { get }
|
||||
var developmentBuild: Bool { get }
|
||||
}
|
||||
|
||||
class AgentStatusChecker: ObservableObject, AgentStatusCheckerProtocol {
|
||||
|
||||
@Published var running: Bool = false
|
||||
|
||||
init() {
|
||||
check()
|
||||
}
|
||||
|
||||
func check() {
|
||||
running = instanceSecretAgentProcess != nil
|
||||
}
|
||||
|
||||
// All processes, including ones from older versions, etc
|
||||
var secretAgentProcesses: [NSRunningApplication] {
|
||||
NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.main.agentBundleID)
|
||||
}
|
||||
|
||||
// The process corresponding to this instance of Secretive
|
||||
var instanceSecretAgentProcess: NSRunningApplication? {
|
||||
let agents = secretAgentProcesses
|
||||
for agent in agents {
|
||||
guard let url = agent.bundleURL else { continue }
|
||||
if url.absoluteString.hasPrefix(Bundle.main.bundleURL.absoluteString) {
|
||||
return agent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Whether Secretive is being run in an Xcode environment.
|
||||
var developmentBuild: Bool {
|
||||
Bundle.main.bundleURL.absoluteString.contains("/Library/Developer/Xcode")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
|
||||
struct ApplicationDirectoryController {
|
||||
}
|
||||
|
||||
extension ApplicationDirectoryController {
|
||||
|
||||
var isInApplicationsDirectory: Bool {
|
||||
let bundlePath = Bundle.main.bundlePath
|
||||
for directory in NSSearchPathForDirectoriesInDomains(.allApplicationsDirectory, .allDomainsMask, true) {
|
||||
if bundlePath.hasPrefix(directory) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if bundlePath.contains("/Library/Developer/Xcode") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
34
Sources/Secretive/Controllers/JustUpdatedChecker.swift
Normal file
34
Sources/Secretive/Controllers/JustUpdatedChecker.swift
Normal file
@@ -0,0 +1,34 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
import AppKit
|
||||
|
||||
protocol JustUpdatedCheckerProtocol: ObservableObject {
|
||||
var justUpdated: Bool { get }
|
||||
}
|
||||
|
||||
class JustUpdatedChecker: ObservableObject, JustUpdatedCheckerProtocol {
|
||||
|
||||
@Published var justUpdated: Bool = false
|
||||
|
||||
init() {
|
||||
check()
|
||||
}
|
||||
|
||||
func check() {
|
||||
let lastBuild = UserDefaults.standard.object(forKey: Constants.previousVersionUserDefaultsKey) as? String ?? "None"
|
||||
let currentBuild = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String
|
||||
UserDefaults.standard.set(currentBuild, forKey: Constants.previousVersionUserDefaultsKey)
|
||||
justUpdated = lastBuild != currentBuild
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension JustUpdatedChecker {
|
||||
|
||||
enum Constants {
|
||||
static let previousVersionUserDefaultsKey = "com.maxgoedjen.Secretive.lastBuild"
|
||||
}
|
||||
|
||||
}
|
||||
43
Sources/Secretive/Controllers/LaunchAgentController.swift
Normal file
43
Sources/Secretive/Controllers/LaunchAgentController.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
import Foundation
|
||||
import ServiceManagement
|
||||
import AppKit
|
||||
import OSLog
|
||||
import SecretKit
|
||||
|
||||
struct LaunchAgentController {
|
||||
|
||||
func install(completion: (() -> Void)? = nil) {
|
||||
Logger().debug("Installing agent")
|
||||
_ = setEnabled(false)
|
||||
// This is definitely a bit of a "seems to work better" thing but:
|
||||
// Seems to more reliably hit if these are on separate runloops, otherwise it seems like it sometimes doesn't kill old
|
||||
// and start new?
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
_ = setEnabled(true)
|
||||
completion?()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func forceLaunch(completion: ((Bool) -> Void)?) {
|
||||
Logger().debug("Agent is not running, attempting to force launch")
|
||||
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
|
||||
let config = NSWorkspace.OpenConfiguration()
|
||||
config.activates = false
|
||||
NSWorkspace.shared.openApplication(at: url, configuration: config) { app, error in
|
||||
DispatchQueue.main.async {
|
||||
completion?(error == nil)
|
||||
}
|
||||
if let error = error {
|
||||
Logger().error("Error force launching \(error.localizedDescription)")
|
||||
} else {
|
||||
Logger().debug("Agent force launched")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setEnabled(_ enabled: Bool) -> Bool {
|
||||
SMLoginItemSetEnabled(Bundle.main.agentBundleID as CFString, enabled)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import Foundation
|
||||
import Cocoa
|
||||
import SecretKit
|
||||
|
||||
struct ShellConfigurationController {
|
||||
|
||||
let socketPath = (NSHomeDirectory().replacingOccurrences(of: Bundle.main.hostBundleID, with: Bundle.main.agentBundleID) as NSString).appendingPathComponent("socket.ssh") as String
|
||||
|
||||
var shellInstructions: [ShellConfigInstruction] {
|
||||
[
|
||||
ShellConfigInstruction(shell: "global",
|
||||
shellConfigDirectory: "~/.ssh/",
|
||||
shellConfigFilename: "config",
|
||||
text: "Host *\n\tIdentityAgent \(socketPath)"),
|
||||
ShellConfigInstruction(shell: "zsh",
|
||||
shellConfigDirectory: "~/",
|
||||
shellConfigFilename: ".zshrc",
|
||||
text: "export SSH_AUTH_SOCK=\(socketPath)"),
|
||||
ShellConfigInstruction(shell: "bash",
|
||||
shellConfigDirectory: "~/",
|
||||
shellConfigFilename: ".bashrc",
|
||||
text: "export SSH_AUTH_SOCK=\(socketPath)"),
|
||||
ShellConfigInstruction(shell: "fish",
|
||||
shellConfigDirectory: "~/.config/fish",
|
||||
shellConfigFilename: "config.fish",
|
||||
text: "set -x SSH_AUTH_SOCK \(socketPath)"),
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
|
||||
@MainActor func addToShell(shellInstructions: ShellConfigInstruction) -> Bool {
|
||||
let openPanel = NSOpenPanel()
|
||||
// This is sync, so no need to strongly retain
|
||||
let delegate = Delegate(name: shellInstructions.shellConfigFilename)
|
||||
openPanel.delegate = delegate
|
||||
openPanel.message = "Select \(shellInstructions.shellConfigFilename) to let Secretive configure your shell automatically."
|
||||
openPanel.prompt = "Add to \(shellInstructions.shellConfigFilename)"
|
||||
openPanel.canChooseFiles = true
|
||||
openPanel.canChooseDirectories = false
|
||||
openPanel.showsHiddenFiles = true
|
||||
openPanel.directoryURL = URL(fileURLWithPath: shellInstructions.shellConfigDirectory)
|
||||
openPanel.nameFieldStringValue = shellInstructions.shellConfigFilename
|
||||
openPanel.allowedContentTypes = [.symbolicLink, .data, .plainText]
|
||||
openPanel.runModal()
|
||||
guard let fileURL = openPanel.urls.first else { return false }
|
||||
let handle: FileHandle
|
||||
do {
|
||||
handle = try FileHandle(forUpdating: fileURL)
|
||||
guard let existing = try handle.readToEnd(),
|
||||
let existingString = String(data: existing, encoding: .utf8) else { return false }
|
||||
guard !existingString.contains(shellInstructions.text) else {
|
||||
return true
|
||||
}
|
||||
try handle.seekToEnd()
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
handle.write("\n# Secretive Config\n\(shellInstructions.text)\n".data(using: .utf8)!)
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user