mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-07-01 17:53:36 +00:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
8bbf489146 | |||
30148ee3a4 | |||
c306801149 | |||
9c60a0b7dd | |||
5b38ef00c1 | |||
ccbf92785d | |||
f1e8e43f62 | |||
7b1563f167 | |||
cca070dbe4 | |||
31e51e45c1 | |||
4bfb3a0012 | |||
a0ed880386 | |||
92b9648e04 | |||
4a2a342670 | |||
1d147d8e34 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
github: maxgoedjen
|
37
FAQ.md
Normal file
37
FAQ.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# FAQ
|
||||||
|
|
||||||
|
### How do I import my current SSH keys, or export my Secretive Keys?
|
||||||
|
|
||||||
|
The secure enclave doesn't allow import or export of private keys. For any new computer, you should just create a new set of keys. If you're using a smart card, you _might_ be able to export your private key from the vendor's software.
|
||||||
|
|
||||||
|
### Secretive doesn't work with my git client
|
||||||
|
|
||||||
|
Secretive relies on the `SSH_AUTH_SOCK` environment variable being respected. The `git` and `ssh` command line tools natively respect this, but third party apps may require some configuration to work. A non-exhaustive list of clients is provided here:
|
||||||
|
|
||||||
|
Tower - [Instructions](https://www.git-tower.com/help/mac/integration/environment)
|
||||||
|
|
||||||
|
GitHub Desktop: Should just work, no configuration needed
|
||||||
|
|
||||||
|
### Secretive isn't working for me
|
||||||
|
|
||||||
|
Please run `ssh -Tv git@github.com` in your terminal and paste the output in a [new GitHub issue](https://github.com/maxgoedjen/secretive/issues/new) with a description of your issue.
|
||||||
|
|
||||||
|
### Why should I trust you?
|
||||||
|
|
||||||
|
You shouldn't, for a piece of software like this. Secretive, by design, has an auditable build process. Each build has a fully auditable build log, showing the source it was built from and a SHA of the build product. You can check the SHA of the zip you download against the SHA output in the build log (which is linked in the About window).
|
||||||
|
|
||||||
|
### I want to build Secretive from source
|
||||||
|
|
||||||
|
Awesome! Just bear in mind that because an app only has access to the keychain items that it created, if you have secrets that you created with the prebuilt version of Secretive, you'll be unable to access them using your own custom build (since you'll have changed the bundled ID).
|
||||||
|
|
||||||
|
### I have a security issue
|
||||||
|
|
||||||
|
Please contact [max.goedjen@gmail.com](mailto:max.goedjen@gmail.com) with a subject containing "SECRETIVE SECURITY" immediately with details, and I'll address the issue and credit you ASAP.
|
||||||
|
|
||||||
|
### I have a non-security related bug
|
||||||
|
|
||||||
|
Please file a [GitHub issue](https://github.com/maxgoedjen/secretive/issues/new) for it. I will not provide email support with the exception of the critical security issues mentioned above.
|
||||||
|
|
||||||
|
### I want to contribute to Secretive
|
||||||
|
|
||||||
|
Sweet! Please check out the [contributing guidelines](CONTRIBUTING.md) and go from there.
|
14
README.md
14
README.md
@ -14,7 +14,7 @@ The most common setup for SSH keys is just keeping them on disk, guarded by prop
|
|||||||
|
|
||||||
### Access Control
|
### Access Control
|
||||||
|
|
||||||
If your Mac has a Secure Enclave, it also has support for strong biometric access controls like Touch ID. You can configure your key so that they require Touch ID (or Watch) authentication before they're accessed.
|
If your Mac has a Secure Enclave, it also has support for strong access controls like Touch ID, or authentication with Apple Watch. You can configure your key so that they require Touch ID (or Watch) authentication before they're accessed.
|
||||||
|
|
||||||
<img src="/.github/readme/touchid.png" alt="Screenshot of Secretive authenticating with Touch ID">
|
<img src="/.github/readme/touchid.png" alt="Screenshot of Secretive authenticating with Touch ID">
|
||||||
|
|
||||||
@ -30,15 +30,11 @@ For Macs without Secure Enclaves, you can configure a Smart Card (such as a Yubi
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Setup for Third Party Apps
|
### FAQ
|
||||||
|
|
||||||
When you first launch Secretive, you'll be prompted to set up your command line environment. You can redisplay this prompt at any time by going to `Menu > Help -> Set Up Helper App`.
|
There's a [FAQ here](FAQ.md).
|
||||||
For non-command-line based apps, like GUI Git clients, you may need to go through app-specific setup.
|
|
||||||
|
|
||||||
[Tower](https://www.git-tower.com/help/mac/integration/environment)
|
### Auditable Build Process
|
||||||
|
|
||||||
|
|
||||||
### Security Considerations
|
|
||||||
|
|
||||||
Builds are produced by GitHub Actions with an auditable build and release generation process. Each build has a "Document SHAs" step, which will output SHA checksums for the build produced by the GitHub Action, so you can verify that the source code for a given build corresponds to any given release.
|
Builds are produced by GitHub Actions with an auditable build and release generation process. Each build has a "Document SHAs" step, which will output SHA checksums for the build produced by the GitHub Action, so you can verify that the source code for a given build corresponds to any given release.
|
||||||
|
|
||||||
@ -52,4 +48,4 @@ Beacuse secrets in the Secure Enclave are not exportable, they are not able to b
|
|||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
If you discover any vulnerabilities in this project, please notify max.goedjen@gmail.com
|
If you discover any vulnerabilities in this project, please notify [max.goedjen@gmail.com](mailto:max.goedjen@gmail.com) with the subject containing "SECRETIVE SECURITY."
|
||||||
|
@ -113,8 +113,17 @@ extension Agent {
|
|||||||
|
|
||||||
|
|
||||||
let rawLength = rawRepresentation.count/2
|
let rawLength = rawRepresentation.count/2
|
||||||
let r = rawRepresentation[0..<rawLength]
|
// Check if we need to pad with 0x00 to prevent certain
|
||||||
let s = rawRepresentation[rawLength...]
|
// ssh servers from thinking r or s is negative
|
||||||
|
let paddingRange: ClosedRange<UInt8> = 0x80...0xFF
|
||||||
|
var r = Data(rawRepresentation[0..<rawLength])
|
||||||
|
if paddingRange ~= r.first! {
|
||||||
|
r.insert(0x00, at: 0)
|
||||||
|
}
|
||||||
|
var s = Data(rawRepresentation[rawLength...])
|
||||||
|
if paddingRange ~= s.first! {
|
||||||
|
s.insert(0x00, at: 0)
|
||||||
|
}
|
||||||
|
|
||||||
var signatureChunk = Data()
|
var signatureChunk = Data()
|
||||||
signatureChunk.append(writer.lengthAndData(of: r))
|
signatureChunk.append(writer.lengthAndData(of: r))
|
||||||
|
@ -49,8 +49,15 @@ class AgentTests: XCTestCase {
|
|||||||
_ = inner.readNextChunk()
|
_ = inner.readNextChunk()
|
||||||
let signedData = inner.readNextChunk()
|
let signedData = inner.readNextChunk()
|
||||||
let rsData = OpenSSHReader(data: signedData)
|
let rsData = OpenSSHReader(data: signedData)
|
||||||
let r = rsData.readNextChunk()
|
var r = rsData.readNextChunk()
|
||||||
let s = rsData.readNextChunk()
|
var s = rsData.readNextChunk()
|
||||||
|
// This is fine IRL, but it freaks out CryptoKit
|
||||||
|
if r[0] == 0 {
|
||||||
|
r.removeFirst()
|
||||||
|
}
|
||||||
|
if s[0] == 0 {
|
||||||
|
s.removeFirst()
|
||||||
|
}
|
||||||
var rs = r
|
var rs = r
|
||||||
rs.append(s)
|
rs.append(s)
|
||||||
let signature = try! P256.Signing.ECDSASignature(rawRepresentation: rs)
|
let signature = try! P256.Signing.ECDSASignature(rawRepresentation: rs)
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
||||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
||||||
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */; };
|
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */; };
|
||||||
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
||||||
|
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
|
||||||
50617D8323FCE48E0099B055 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* AppDelegate.swift */; };
|
50617D8323FCE48E0099B055 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* AppDelegate.swift */; };
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
|
||||||
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
|
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
|
||||||
@ -206,6 +208,8 @@
|
|||||||
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
||||||
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = "<group>"; };
|
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = "<group>"; };
|
||||||
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
||||||
|
50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = "<group>"; };
|
||||||
50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
50617D8223FCE48E0099B055 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
50617D8223FCE48E0099B055 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
@ -346,6 +350,15 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
504BA92D243171F20064740E /* Types */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
50617DCA23FCECA10099B055 /* Secret.swift */,
|
||||||
|
50617DC623FCE4EA0099B055 /* SecretStore.swift */,
|
||||||
|
);
|
||||||
|
path = Types;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
50617D7623FCE48D0099B055 = {
|
50617D7623FCE48D0099B055 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -418,8 +431,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
50617DAA23FCE4AB0099B055 /* SecretKit.h */,
|
50617DAA23FCE4AB0099B055 /* SecretKit.h */,
|
||||||
50617DCA23FCECA10099B055 /* Secret.swift */,
|
|
||||||
50617DC623FCE4EA0099B055 /* SecretStore.swift */,
|
|
||||||
5099A02C23FE56D70062B6F2 /* Common */,
|
5099A02C23FE56D70062B6F2 /* Common */,
|
||||||
50617DCC23FCECEE0099B055 /* SecureEnclave */,
|
50617DCC23FCECEE0099B055 /* SecureEnclave */,
|
||||||
5099A02523FE34DE0062B6F2 /* SmartCard */,
|
5099A02523FE34DE0062B6F2 /* SmartCard */,
|
||||||
@ -505,6 +516,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
|
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
|
||||||
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */,
|
||||||
|
50571E0424393D1500F76F6C /* LaunchAgentController.swift */,
|
||||||
);
|
);
|
||||||
path = Controllers;
|
path = Controllers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -522,6 +535,7 @@
|
|||||||
5099A02C23FE56D70062B6F2 /* Common */ = {
|
5099A02C23FE56D70062B6F2 /* Common */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
504BA92D243171F20064740E /* Types */,
|
||||||
5068389F2415EA4F00F55094 /* Erasers */,
|
5068389F2415EA4F00F55094 /* Erasers */,
|
||||||
506838A42415EA6800F55094 /* OpenSSH */,
|
506838A42415EA6800F55094 /* OpenSSH */,
|
||||||
5068389D241471CD00F55094 /* SecretStoreList.swift */,
|
5068389D241471CD00F55094 /* SecretStoreList.swift */,
|
||||||
@ -908,10 +922,12 @@
|
|||||||
files = (
|
files = (
|
||||||
50C385A9240B636500AF2719 /* SetupView.swift in Sources */,
|
50C385A9240B636500AF2719 /* SetupView.swift in Sources */,
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
||||||
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
|
||||||
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
|
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
|
||||||
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
|
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
|
||||||
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
||||||
|
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */,
|
||||||
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
|
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
|
||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
||||||
50731669241E00C20023809E /* NoticeView.swift in Sources */,
|
50731669241E00C20023809E /* NoticeView.swift in Sources */,
|
||||||
|
@ -17,6 +17,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
}()
|
}()
|
||||||
let updater = Updater()
|
let updater = Updater()
|
||||||
let agentStatusChecker = AgentStatusChecker()
|
let agentStatusChecker = AgentStatusChecker()
|
||||||
|
let justUpdatedChecker = JustUpdatedChecker()
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
let contentView = ContentView(storeList: storeList, updater: updater, agentStatusChecker: agentStatusChecker, runSetupBlock: { self.runSetup(sender: nil) })
|
let contentView = ContentView(storeList: storeList, updater: updater, agentStatusChecker: agentStatusChecker, runSetupBlock: { self.runSetup(sender: nil) })
|
||||||
@ -40,6 +41,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
newMenuItem.isEnabled = true
|
newMenuItem.isEnabled = true
|
||||||
}
|
}
|
||||||
runSetupIfNeeded()
|
runSetupIfNeeded()
|
||||||
|
relaunchAgentIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidBecomeActive(_ notification: Notification) {
|
func applicationDidBecomeActive(_ notification: Notification) {
|
||||||
@ -89,6 +91,12 @@ extension AppDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func relaunchAgentIfNeeded() {
|
||||||
|
if agentStatusChecker.running && justUpdatedChecker.justUpdated {
|
||||||
|
LaunchAgentController().relaunch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppDelegate {
|
extension AppDelegate {
|
||||||
|
36
Secretive/Controllers/JustUpdatedChecker.swift
Normal file
36
Secretive/Controllers/JustUpdatedChecker.swift
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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)
|
||||||
|
if lastBuild != currentBuild {
|
||||||
|
justUpdated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JustUpdatedChecker {
|
||||||
|
|
||||||
|
enum Constants {
|
||||||
|
static let previousVersionUserDefaultsKey = "com.maxgoedjen.Secretive.lastBuild"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
19
Secretive/Controllers/LaunchAgentController.swift
Normal file
19
Secretive/Controllers/LaunchAgentController.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Foundation
|
||||||
|
import ServiceManagement
|
||||||
|
|
||||||
|
struct LaunchAgentController {
|
||||||
|
|
||||||
|
func install() -> Bool {
|
||||||
|
setEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func relaunch() {
|
||||||
|
_ = setEnabled(false)
|
||||||
|
_ = setEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setEnabled(_ enabled: Bool) -> Bool {
|
||||||
|
SMLoginItemSetEnabled("com.maxgoedjen.Secretive.SecretAgent" as CFString, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ServiceManagement
|
|
||||||
|
|
||||||
struct SetupView: View {
|
struct SetupView: View {
|
||||||
|
|
||||||
@ -117,7 +116,7 @@ struct SetupStepCommandView: View {
|
|||||||
extension SetupView {
|
extension SetupView {
|
||||||
|
|
||||||
func installLaunchAgent() -> Bool {
|
func installLaunchAgent() -> Bool {
|
||||||
SMLoginItemSetEnabled("com.maxgoedjen.Secretive.SecretAgent" as CFString, true)
|
LaunchAgentController().install()
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAsDone() -> Bool {
|
func markAsDone() -> Bool {
|
||||||
|
Reference in New Issue
Block a user