Compare commits
4 Commits
maxgoedjen
...
fixquote
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d16a6ffb1 | ||
|
|
95658072e7 | ||
|
|
7eeb09b1ec | ||
|
|
e6f21c13b0 |
BIN
.github/readme/app-dark.png
vendored
|
Before Width: | Height: | Size: 668 KiB After Width: | Height: | Size: 520 KiB |
BIN
.github/readme/app-light.png
vendored
|
Before Width: | Height: | Size: 618 KiB After Width: | Height: | Size: 519 KiB |
BIN
.github/readme/localize_add.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
.github/readme/localize_sidebar.png
vendored
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
.github/readme/localize_translate.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
.github/readme/notification.png
vendored
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 1.4 MiB |
BIN
.github/readme/touchid.png
vendored
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 259 KiB |
4
.github/workflows/codeql.yml
vendored
@@ -25,8 +25,8 @@ jobs:
|
|||||||
- language: actions
|
- language: actions
|
||||||
build-mode: none
|
build-mode: none
|
||||||
# Disable this until CodeQL supports Xcode 26 builds.
|
# Disable this until CodeQL supports Xcode 26 builds.
|
||||||
- language: swift
|
# - language: swift
|
||||||
build-mode: manual
|
# build-mode: manual
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
26
.github/workflows/nightly.yml
vendored
@@ -12,7 +12,6 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: write
|
||||||
attestations: write
|
attestations: write
|
||||||
actions: read
|
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
@@ -34,30 +33,23 @@ jobs:
|
|||||||
DATE=$(date "+%Y-%m-%d")
|
DATE=$(date "+%Y-%m-%d")
|
||||||
sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0_nightly-$DATE/g" Sources/Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0_nightly-$DATE/g" Sources/Config/Config.xcconfig
|
||||||
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
|
||||||
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Secretive/Credits.rtf
|
||||||
- name: Build
|
- name: Build
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
||||||
- name: Move to Artifact Folder
|
- name: Create ZIP
|
||||||
run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
|
|
||||||
- name: Upload App to Artifacts
|
|
||||||
id: upload
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Secretive
|
|
||||||
path: Artifact
|
|
||||||
- name: Download Zipped Artifact
|
|
||||||
id: download
|
|
||||||
env:
|
|
||||||
ZIP_ID: ${{ steps.upload.outputs.artifact-id }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
|
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive/Products/Applications/Secretive.app ./Secretive.zip
|
||||||
https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.zip
|
|
||||||
- name: Notarize
|
- name: Notarize
|
||||||
env:
|
env:
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
|
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
|
||||||
|
- name: Upload App to Artifacts
|
||||||
|
id: upload
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Secretive.zip
|
||||||
|
path: Secretive.zip
|
||||||
- name: Attest
|
- name: Attest
|
||||||
id: attest
|
id: attest
|
||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v2
|
||||||
|
|||||||
39
.github/workflows/release.yml
vendored
@@ -32,7 +32,6 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: write
|
||||||
attestations: write
|
attestations: write
|
||||||
actions: read
|
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
@@ -56,43 +55,37 @@ jobs:
|
|||||||
export CLEAN_TAG=$(echo $TAG_NAME | sed -e 's/refs\/tags\/v//')
|
export CLEAN_TAG=$(echo $TAG_NAME | sed -e 's/refs\/tags\/v//')
|
||||||
sed -i '' -e "s/GITHUB_CI_VERSION/$CLEAN_TAG/g" Sources/Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_CI_VERSION/$CLEAN_TAG/g" Sources/Config/Config.xcconfig
|
||||||
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
|
||||||
sed -i '' -e "s/GITHUB_BUILD_URL/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Secretive/Credits.rtf
|
||||||
- name: Build
|
- name: Build
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
||||||
- name: Move to Artifact Folder
|
- name: Create ZIP
|
||||||
run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
|
|
||||||
- name: Upload App to Artifacts
|
|
||||||
id: upload
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Secretive.zip
|
|
||||||
path: Artifact
|
|
||||||
- name: Download Zipped Artifact
|
|
||||||
id: download
|
|
||||||
env:
|
|
||||||
ZIP_ID: ${{ steps.upload.outputs.artifact-id }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
|
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive/Products/Applications/Secretive.app ./Secretive.zip
|
||||||
https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.zip
|
|
||||||
- name: Notarize
|
- name: Notarize
|
||||||
env:
|
env:
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
|
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
|
||||||
|
- name: Upload App to Artifacts
|
||||||
|
id: upload
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Secretive.zip
|
||||||
|
path: Secretive.zip
|
||||||
- name: Attest
|
- name: Attest
|
||||||
id: attest
|
id: attest
|
||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v2
|
||||||
with:
|
with:
|
||||||
subject-path: "Secretive.zip"
|
subject-name: "Secretive.zip"
|
||||||
|
subject-digest: ${{ steps.upload.outputs.artifact-digest }}
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
|
run: |
|
||||||
|
sed -i.tmp "s/RUN_ID/$RUN_ID/g" .github/templates/release.md
|
||||||
|
sed -i.tmp "s/ATTESTATION_ID/$ATTESTATION_ID/g" .github/templates/release.md
|
||||||
|
gh release create $TAG_NAME -d -F .github/templates/release.md
|
||||||
|
gh release upload Secretive.zip
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAG_NAME: ${{ github.ref }}
|
TAG_NAME: ${{ github.ref }}
|
||||||
RUN_ID: ${{ github.run_id }}
|
RUN_ID: ${{ github.run_id }}
|
||||||
ATTESTATION_ID: ${{ steps.attest.outputs.attestation-id }}
|
ATTESTATION_ID: ${{ steps.attest.outputs.attestation-id }}
|
||||||
run: |
|
|
||||||
sed -i.tmp "s/RUN_ID/$RUN_ID/g" .github/templates/release.md
|
|
||||||
sed -i.tmp "s/ATTESTATION_ID/$ATTESTATION_ID/g" .github/templates/release.md
|
|
||||||
gh release create $TAG_NAME -d -F .github/templates/release.md
|
|
||||||
gh release upload $TAG_NAME Secretive.zip
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
18
README.md
@@ -1,11 +1,11 @@
|
|||||||
# Secretive [](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml) 
|
# Secretive [](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml) 
|
||||||
|
|
||||||
|
|
||||||
Secretive is an app for protecting and managing SSH keys with the Secure Enclave.
|
Secretive is an app for storing and managing SSH keys in the Secure Enclave. It is inspired by the [sekey project](https://github.com/sekey/sekey), but rewritten in Swift with no external dependencies and with a handy native management app.
|
||||||
|
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="/.github/readme/app-dark.png">
|
<source media="(prefers-color-scheme: dark)" srcset="/.github/readme/app-dark.png">
|
||||||
<source media="(prefers-color-scheme: light)" srcset="/.github/readme/app-light.png">
|
<img src="/.github/readme/app-light.png" alt="Screenshot of Secretive" width="600">
|
||||||
<img src="/.github/readme/app-dark.png" alt="Screenshot of Secretive" width="600">
|
|
||||||
</picture>
|
</picture>
|
||||||
|
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ Secretive is an app for protecting and managing SSH keys with the Secure Enclave
|
|||||||
|
|
||||||
### Safer Storage
|
### Safer Storage
|
||||||
|
|
||||||
The most common setup for SSH keys is just keeping them on disk, guarded by proper permissions. This is fine in most cases, but it's not super hard for malicious users or malware to copy your private key. If you protect your keys with the Secure Enclave, it's impossible to export them, by design.
|
The most common setup for SSH keys is just keeping them on disk, guarded by proper permissions. This is fine in most cases, but it's not super hard for malicious users or malware to copy your private key. If you store your keys in the Secure Enclave, it's impossible to export them, by design.
|
||||||
|
|
||||||
### Access Control
|
### Access Control
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ Builds are produced by GitHub Actions with an auditable build and release genera
|
|||||||
|
|
||||||
### A Note Around Code Signing and Keychains
|
### A Note Around Code Signing and Keychains
|
||||||
|
|
||||||
While Secretive uses the Secure Enclave to protect keys, it still relies on Keychain APIs to store and access them. Keychain restricts reads of keys to the app (and specifically, the bundle ID) that created them. If you build Secretive from source, make sure you are consistent in which bundle ID you use so that the Keychain is able to locate your keys.
|
While Secretive uses the Secure Enclave for key storage, it still relies on Keychain APIs to access them. Keychain restricts reads of keys to the app (and specifically, the bundle ID) that created them. If you build Secretive from source, make sure you are consistent in which bundle ID you use so that the Keychain is able to locate your keys.
|
||||||
|
|
||||||
### Backups and Transfers to New Machines
|
### Backups and Transfers to New Machines
|
||||||
|
|
||||||
@@ -62,11 +62,3 @@ Because secrets in the Secure Enclave are not exportable, they are not able to b
|
|||||||
## Security
|
## Security
|
||||||
|
|
||||||
Secretive's security policy is detailed in [SECURITY.md](SECURITY.md). To report security issues, please use [GitHub's private reporting feature.](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)
|
Secretive's security policy is detailed in [SECURITY.md](SECURITY.md). To report security issues, please use [GitHub's private reporting feature.](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)
|
||||||
|
|
||||||
## Acknowledgements
|
|
||||||
|
|
||||||
### sekey
|
|
||||||
Secretive was inspired by the [sekey project](https://github.com/sekey/sekey).
|
|
||||||
|
|
||||||
### Localization
|
|
||||||
Secretive is localized to many languages by a generous team of volunteers. To learn more, see [LOCALIZING.md](LOCALIZING.md). Secretive's localization workflow is generously provided by [Crowdin](https://crowdin.com).
|
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
CI_VERSION = GITHUB_CI_VERSION
|
CI_VERSION = GITHUB_CI_VERSION
|
||||||
CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER
|
CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER
|
||||||
CI_BUILD_LINK = GITHUB_BUILD_URL
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
/// A release is a representation of a downloadable update.
|
/// A release is a representation of a downloadable update.
|
||||||
public struct Release: Codable, Sendable, Hashable {
|
public struct Release: Codable, Sendable {
|
||||||
|
|
||||||
/// The user-facing name of the release. Typically "Secretive 1.2.3"
|
/// The user-facing name of the release. Typically "Secretive 1.2.3"
|
||||||
public let name: String
|
public let name: String
|
||||||
@@ -16,8 +15,6 @@ public struct Release: Codable, Sendable, Hashable {
|
|||||||
/// A user-facing description of the contents of the update.
|
/// A user-facing description of the contents of the update.
|
||||||
public let body: String
|
public let body: String
|
||||||
|
|
||||||
public let attributedBody: AttributedString
|
|
||||||
|
|
||||||
/// Initializes a Release.
|
/// Initializes a Release.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - name: The user-facing name of the release.
|
/// - name: The user-facing name of the release.
|
||||||
@@ -29,56 +26,6 @@ public struct Release: Codable, Sendable, Hashable {
|
|||||||
self.prerelease = prerelease
|
self.prerelease = prerelease
|
||||||
self.html_url = html_url
|
self.html_url = html_url
|
||||||
self.body = body
|
self.body = body
|
||||||
self.attributedBody = AttributedString(_markdown: body)
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(_ release: GitHubRelease) {
|
|
||||||
self.name = release.name
|
|
||||||
self.prerelease = release.prerelease
|
|
||||||
self.html_url = release.html_url
|
|
||||||
self.body = release.body
|
|
||||||
self.attributedBody = AttributedString(_markdown: release.body)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct GitHubRelease: Codable, Sendable {
|
|
||||||
let name: String
|
|
||||||
let prerelease: Bool
|
|
||||||
let html_url: URL
|
|
||||||
let body: String
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate extension AttributedString {
|
|
||||||
|
|
||||||
init(_markdown markdown: String) {
|
|
||||||
let split = markdown.split(whereSeparator: \.isNewline)
|
|
||||||
let lines = split
|
|
||||||
.compactMap {
|
|
||||||
try? AttributedString(markdown: String($0), options: .init(allowsExtendedAttributes: true, interpretedSyntax: .full))
|
|
||||||
}
|
|
||||||
.map { (string: AttributedString) in
|
|
||||||
guard case let .header(level) = string.runs.first?.presentationIntent?.components.first?.kind else { return string }
|
|
||||||
return AttributedString("\n") + string
|
|
||||||
.transformingAttributes(\.font) { font in
|
|
||||||
font.value = switch level {
|
|
||||||
case 2: .headline.bold()
|
|
||||||
case 3: .headline
|
|
||||||
default: .subheadline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.transformingAttributes(\.underlineStyle) { underline in
|
|
||||||
underline.value = switch level {
|
|
||||||
case 2: .single
|
|
||||||
default: .none
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+ AttributedString("\n")
|
|
||||||
}
|
|
||||||
self = lines.reduce(into: AttributedString()) { partialResult, next in
|
|
||||||
partialResult.append(next)
|
|
||||||
partialResult.append(AttributedString("\n"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ extension Agent {
|
|||||||
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
||||||
case .unknown(let value):
|
case .unknown(let value):
|
||||||
logger.error("Agent received unknown request of type \(value).")
|
logger.error("Agent received unknown request of type \(value).")
|
||||||
throw UnhandledRequestError()
|
|
||||||
default:
|
default:
|
||||||
logger.debug("Agent received valid request of type \(request.debugDescription), but not currently supported.")
|
logger.debug("Agent received valid request of type \(request.debugDescription), but not currently supported.")
|
||||||
throw UnhandledRequestError()
|
throw UnhandledRequestError()
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab
|
|||||||
private let _create: @Sendable (String, Attributes) async throws -> AnySecret
|
private let _create: @Sendable (String, Attributes) async throws -> AnySecret
|
||||||
private let _delete: @Sendable (AnySecret) async throws -> Void
|
private let _delete: @Sendable (AnySecret) async throws -> Void
|
||||||
private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void
|
private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void
|
||||||
private let _supportedKeyTypes: @Sendable () -> KeyAvailability
|
private let _supportedKeyTypes: @Sendable () -> [KeyType]
|
||||||
|
|
||||||
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
||||||
_create = { AnySecret(try await secretStore.create(name: $0, attributes: $1)) }
|
_create = { AnySecret(try await secretStore.create(name: $0, attributes: $1)) }
|
||||||
@@ -87,7 +87,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab
|
|||||||
try await _update(secret, name, attributes)
|
try await _update(secret, name, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var supportedKeyTypes: KeyAvailability {
|
public var supportedKeyTypes: [KeyType] {
|
||||||
_supportedKeyTypes()
|
_supportedKeyTypes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ public final class PublicKeyFileStoreController: Sendable {
|
|||||||
public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws {
|
public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws {
|
||||||
logger.log("Writing public keys to disk")
|
logger.log("Writing public keys to disk")
|
||||||
if clear {
|
if clear {
|
||||||
let validPaths = Set(secrets.map { publicKeyPath(for: $0) })
|
let validPaths = Set(secrets.map { publicKeyPath(for: $0) }).union(Set(secrets.map { sshCertificatePath(for: $0) }))
|
||||||
.union(Set(secrets.map { sshCertificatePath(for: $0) }))
|
|
||||||
let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? []
|
let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? []
|
||||||
let fullPathContents = contentsOfDirectory.map { directory.appending(path: $0).path() }
|
let fullPathContents = contentsOfDirectory.map { "\(directory)/\($0)" }
|
||||||
|
|
||||||
let untracked = Set(fullPathContents)
|
let untracked = Set(fullPathContents)
|
||||||
.subtracting(validPaths)
|
.subtracting(validPaths)
|
||||||
|
|||||||
@@ -62,37 +62,10 @@ public protocol SecretStoreModifiable<SecretType>: SecretStore {
|
|||||||
/// - attributes: The new attributes for the secret.
|
/// - attributes: The new attributes for the secret.
|
||||||
func update(secret: SecretType, name: String, attributes: Attributes) async throws
|
func update(secret: SecretType, name: String, attributes: Attributes) async throws
|
||||||
|
|
||||||
var supportedKeyTypes: KeyAvailability { get }
|
var supportedKeyTypes: [KeyType] { get }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct KeyAvailability: Sendable {
|
|
||||||
|
|
||||||
public let available: [KeyType]
|
|
||||||
public let unavailable: [UnavailableKeyType]
|
|
||||||
|
|
||||||
public init(available: [KeyType], unavailable: [UnavailableKeyType]) {
|
|
||||||
self.available = available
|
|
||||||
self.unavailable = unavailable
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct UnavailableKeyType: Sendable {
|
|
||||||
public let keyType: KeyType
|
|
||||||
public let reason: Reason
|
|
||||||
|
|
||||||
public init(keyType: KeyType, reason: Reason) {
|
|
||||||
self.keyType = keyType
|
|
||||||
self.reason = reason
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Reason: Sendable {
|
|
||||||
case macOSUpdateRequired
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extension NSNotification.Name {
|
extension NSNotification.Name {
|
||||||
|
|
||||||
// Distributed notification that keys were modified out of process (ie, that the management tool added/removed secrets)
|
// Distributed notification that keys were modified out of process (ie, that the management tool added/removed secrets)
|
||||||
|
|||||||
@@ -50,16 +50,16 @@ extension SecureEnclave {
|
|||||||
let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth))
|
let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth))
|
||||||
guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
|
guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
|
||||||
logger.log("Skipping \(name), public key already present. Marking as migrated.")
|
logger.log("Skipping \(name), public key already present. Marking as migrated.")
|
||||||
markMigrated(secret: secret, oldID: id)
|
try markMigrated(secret: secret, oldID: id)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logger.log("Migrating \(name).")
|
logger.log("Migrating \(name).")
|
||||||
try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes)
|
try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes)
|
||||||
logger.log("Migrated \(name).")
|
logger.log("Migrated \(name).")
|
||||||
markMigrated(secret: secret, oldID: id)
|
try markMigrated(secret: secret, oldID: id)
|
||||||
migratedAny = true
|
migratedAny = true
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("Failed to migrate \(name): \(error.localizedDescription).")
|
logger.error("Failed to migrate \(name): \(error).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if migratedAny {
|
if migratedAny {
|
||||||
@@ -69,10 +69,10 @@ extension SecureEnclave {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public func markMigrated(secret: Secret, oldID: Data) {
|
public func markMigrated(secret: Secret, oldID: Data) throws {
|
||||||
let updateQuery = KeychainDictionary([
|
let updateQuery = KeychainDictionary([
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrApplicationLabel: oldID
|
kSecAttrApplicationLabel: secret.id
|
||||||
])
|
])
|
||||||
|
|
||||||
let newID = oldID + Constants.migrationMagicNumber
|
let newID = oldID + Constants.migrationMagicNumber
|
||||||
@@ -82,7 +82,7 @@ extension SecureEnclave {
|
|||||||
|
|
||||||
let status = SecItemUpdate(updateQuery, updatedAttributes)
|
let status = SecItemUpdate(updateQuery, updatedAttributes)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
logger.warning("Failed to mark \(secret.name) as migrated: \(status).")
|
throw KeychainError(statusCode: status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,22 +186,17 @@ extension SecureEnclave {
|
|||||||
await reloadSecrets()
|
await reloadSecrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
public let supportedKeyTypes: KeyAvailability = {
|
public var supportedKeyTypes: [KeyType] {
|
||||||
let macOS26Keys: [KeyType] = [.mldsa65, .mldsa87]
|
if #available(macOS 26, *) {
|
||||||
let isAtLeastMacOS26 = if #available(macOS 26, *) {
|
[
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
return KeyAvailability(
|
|
||||||
available: [
|
|
||||||
.ecdsa256,
|
.ecdsa256,
|
||||||
] + (isAtLeastMacOS26 ? macOS26Keys : []),
|
.mldsa65,
|
||||||
unavailable: (isAtLeastMacOS26 ? [] : macOS26Keys).map {
|
.mldsa87,
|
||||||
KeyAvailability.UnavailableKeyType(keyType: $0, reason: .macOSUpdateRequired)
|
]
|
||||||
|
} else {
|
||||||
|
[.ecdsa256]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate {
|
|||||||
if let error = error as? Codable & Error {
|
if let error = error as? Codable & Error {
|
||||||
reply(nil, NSError(error))
|
reply(nil, NSError(error))
|
||||||
} else {
|
} else {
|
||||||
// Sending cast directly tries to serialize it and crashes XPCEncoder.
|
reply(nil, error)
|
||||||
let cast = error as NSError
|
|
||||||
reply(nil, NSError(domain: cast.domain, code: cast.code, userInfo: [NSLocalizedDescriptionKey: error.localizedDescription]))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,18 @@ import Foundation
|
|||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
import Brief
|
import Brief
|
||||||
import XPCWrappers
|
import XPCWrappers
|
||||||
import OSLog
|
|
||||||
|
|
||||||
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH
|
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH
|
||||||
public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
||||||
|
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "XPCAgentInputParser")
|
|
||||||
private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError>
|
private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError>
|
||||||
|
|
||||||
public init() async throws {
|
public init() async throws {
|
||||||
logger.debug("Creating XPCAgentInputParser")
|
|
||||||
session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true)
|
session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true)
|
||||||
logger.debug("XPCAgentInputParser is warmed up.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parse(data: Data) async throws -> SSHAgent.Request {
|
public func parse(data: Data) async throws -> SSHAgent.Request {
|
||||||
logger.debug("Parsing input")
|
try await session.send(data)
|
||||||
defer { logger.debug("Parsed input") }
|
|
||||||
return try await session.send(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
5003EF632780081B00DF2006 /* SecureEnclaveSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */; };
|
5003EF632780081B00DF2006 /* SecureEnclaveSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */; };
|
||||||
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF642780081B00DF2006 /* SmartCardSecretKit */; };
|
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF642780081B00DF2006 /* SmartCardSecretKit */; };
|
||||||
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
||||||
|
5008C2402E52792400507AC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
|
||||||
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
||||||
501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; };
|
501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; };
|
||||||
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
@@ -32,13 +33,16 @@
|
|||||||
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; };
|
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; };
|
||||||
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; };
|
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; };
|
||||||
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
||||||
|
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
|
||||||
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.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 */; };
|
||||||
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
|
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
|
||||||
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; };
|
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; };
|
||||||
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; };
|
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; };
|
||||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; };
|
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; };
|
||||||
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.swift */; };
|
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.swift */; };
|
||||||
|
506772C72424784600034DED /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 506772C62424784600034DED /* Credits.rtf */; };
|
||||||
506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; };
|
506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; };
|
||||||
50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
50692D282E6FDB8D0043C7BB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692D242E6FDB8D0043C7BB /* main.swift */; };
|
50692D282E6FDB8D0043C7BB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692D242E6FDB8D0043C7BB /* main.swift */; };
|
||||||
@@ -69,10 +73,6 @@
|
|||||||
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
|
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
||||||
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
||||||
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */; };
|
|
||||||
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; };
|
|
||||||
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
|
|
||||||
50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -193,10 +193,11 @@
|
|||||||
504788F52E68206F00B4556F /* GettingStartedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GettingStartedView.swift; sourceTree = "<group>"; };
|
504788F52E68206F00B4556F /* GettingStartedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GettingStartedView.swift; sourceTree = "<group>"; };
|
||||||
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.swift; sourceTree = "<group>"; };
|
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.swift; sourceTree = "<group>"; };
|
||||||
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
||||||
5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; 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 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
|
50617D8223FCE48E0099B055 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.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>"; };
|
||||||
|
50617D8623FCE48E0099B055 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
50617D8923FCE48E0099B055 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
50617D8923FCE48E0099B055 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
50617D8E23FCE48E0099B055 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
50617D8E23FCE48E0099B055 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
50617D8F23FCE48E0099B055 /* Secretive.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Secretive.entitlements; sourceTree = "<group>"; };
|
50617D8F23FCE48E0099B055 /* Secretive.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Secretive.entitlements; sourceTree = "<group>"; };
|
||||||
@@ -204,6 +205,7 @@
|
|||||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonStyle.swift; sourceTree = "<group>"; };
|
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
5066A6C12516F303004B5A36 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
|
5066A6C12516F303004B5A36 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
|
||||||
5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = "<group>"; };
|
5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = "<group>"; };
|
||||||
|
506772C62424784600034DED /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; };
|
||||||
506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = "<group>"; };
|
506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = "<group>"; };
|
||||||
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
|
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
|
||||||
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveUpdater.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveUpdater.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -226,6 +228,7 @@
|
|||||||
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSecretView.swift; sourceTree = "<group>"; };
|
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSecretView.swift; sourceTree = "<group>"; };
|
||||||
50A3B78A24026B7500D209EA /* SecretAgent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SecretAgent.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
50A3B78A24026B7500D209EA /* SecretAgent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SecretAgent.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
50A3B79324026B7600D209EA /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
50A3B79324026B7600D209EA /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
|
50A3B79624026B7600D209EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
50A3B79824026B7600D209EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
50A3B79824026B7600D209EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
50A3B79924026B7600D209EA /* SecretAgent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgent.entitlements; sourceTree = "<group>"; };
|
50A3B79924026B7600D209EA /* SecretAgent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgent.entitlements; sourceTree = "<group>"; };
|
||||||
50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationsView.swift; sourceTree = "<group>"; };
|
50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationsView.swift; sourceTree = "<group>"; };
|
||||||
@@ -236,9 +239,6 @@
|
|||||||
50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = "<group>"; };
|
50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = "<group>"; };
|
||||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowBackgroundStyle.swift; sourceTree = "<group>"; };
|
|
||||||
50E4C4C22E7765DF00C73783 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
|
||||||
50E4C4C72E777E4200C73783 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -293,16 +293,15 @@
|
|||||||
path = Helpers;
|
path = Helpers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
504788ED2E681EB200B4556F /* Modifiers */ = {
|
504788ED2E681EB200B4556F /* Styles */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */,
|
|
||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
||||||
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
||||||
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
|
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
|
||||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
||||||
);
|
);
|
||||||
path = Modifiers;
|
path = Styles;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
504788EE2E681EC300B4556F /* Secrets */ = {
|
504788EE2E681EC300B4556F /* Secrets */ = {
|
||||||
@@ -336,7 +335,6 @@
|
|||||||
504788F02E681F0100B4556F /* Views */ = {
|
504788F02E681F0100B4556F /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
50E4C4C22E7765DF00C73783 /* AboutView.swift */,
|
|
||||||
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */,
|
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */,
|
||||||
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
||||||
5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
|
5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
|
||||||
@@ -377,10 +375,11 @@
|
|||||||
508A58B0241ED1C40069DC07 /* Views */,
|
508A58B0241ED1C40069DC07 /* Views */,
|
||||||
508A58B1241ED1EA0069DC07 /* Controllers */,
|
508A58B1241ED1EA0069DC07 /* Controllers */,
|
||||||
50033AC427813F1C00253856 /* Helpers */,
|
50033AC427813F1C00253856 /* Helpers */,
|
||||||
|
50617D8623FCE48E0099B055 /* Assets.xcassets */,
|
||||||
50617D8E23FCE48E0099B055 /* Info.plist */,
|
50617D8E23FCE48E0099B055 /* Info.plist */,
|
||||||
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
|
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
|
||||||
50E4C4C72E777E4200C73783 /* AppIcon.icon */,
|
|
||||||
50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
|
50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
|
||||||
|
506772C62424784600034DED /* Credits.rtf */,
|
||||||
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */,
|
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */,
|
||||||
50617D8823FCE48E0099B055 /* Preview Content */,
|
50617D8823FCE48E0099B055 /* Preview Content */,
|
||||||
);
|
);
|
||||||
@@ -433,7 +432,7 @@
|
|||||||
children = (
|
children = (
|
||||||
504788EF2E681ED700B4556F /* Configuration */,
|
504788EF2E681ED700B4556F /* Configuration */,
|
||||||
504788EE2E681EC300B4556F /* Secrets */,
|
504788EE2E681EC300B4556F /* Secrets */,
|
||||||
504788ED2E681EB200B4556F /* Modifiers */,
|
504788ED2E681EB200B4556F /* Styles */,
|
||||||
504788F02E681F0100B4556F /* Views */,
|
504788F02E681F0100B4556F /* Views */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
@@ -446,6 +445,7 @@
|
|||||||
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
|
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
|
||||||
5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */,
|
5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */,
|
||||||
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */,
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */,
|
||||||
|
50571E0424393D1500F76F6C /* LaunchAgentController.swift */,
|
||||||
);
|
);
|
||||||
path = Controllers;
|
path = Controllers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -613,6 +613,7 @@
|
|||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
en,
|
en,
|
||||||
|
Base,
|
||||||
it,
|
it,
|
||||||
fr,
|
fr,
|
||||||
de,
|
de,
|
||||||
@@ -642,8 +643,9 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */,
|
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */,
|
||||||
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */,
|
|
||||||
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */,
|
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */,
|
||||||
|
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */,
|
||||||
|
506772C72424784600034DED /* Credits.rtf in Resources */,
|
||||||
508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */,
|
508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -669,8 +671,8 @@
|
|||||||
50A3B79724026B7600D209EA /* Main.storyboard in Resources */,
|
50A3B79724026B7600D209EA /* Main.storyboard in Resources */,
|
||||||
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */,
|
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */,
|
||||||
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */,
|
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */,
|
||||||
50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */,
|
|
||||||
508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */,
|
508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */,
|
||||||
|
5008C2402E52792400507AC2 /* Assets.xcassets in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -683,9 +685,7 @@
|
|||||||
files = (
|
files = (
|
||||||
504788F22E681F3A00B4556F /* Instructions.swift in Sources */,
|
504788F22E681F3A00B4556F /* Instructions.swift in Sources */,
|
||||||
50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */,
|
50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */,
|
||||||
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */,
|
|
||||||
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
||||||
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */,
|
|
||||||
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
||||||
504788EC2E680DC800B4556F /* URLs.swift in Sources */,
|
504788EC2E680DC800B4556F /* URLs.swift in Sources */,
|
||||||
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
|
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
|
||||||
@@ -704,6 +704,7 @@
|
|||||||
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
||||||
50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */,
|
50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */,
|
||||||
50153E20250AFCB200525160 /* UpdateView.swift in Sources */,
|
50153E20250AFCB200525160 /* UpdateView.swift in Sources */,
|
||||||
|
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */,
|
||||||
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */,
|
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */,
|
||||||
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
|
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
|
||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
||||||
@@ -783,7 +784,7 @@
|
|||||||
50A3B79524026B7600D209EA /* Main.storyboard */ = {
|
50A3B79524026B7600D209EA /* Main.storyboard */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
5059933F2E7A3B5B0092CFFA /* en */,
|
50A3B79624026B7600D209EA /* Base */,
|
||||||
);
|
);
|
||||||
name = Main.storyboard;
|
name = Main.storyboard;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -830,6 +831,8 @@
|
|||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
|
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -904,7 +907,9 @@
|
|||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
@@ -945,9 +950,11 @@
|
|||||||
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
|
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
@@ -983,9 +990,11 @@
|
|||||||
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
|
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
@@ -1036,7 +1045,7 @@
|
|||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -1076,7 +1085,7 @@
|
|||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -1118,7 +1127,7 @@
|
|||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -1150,7 +1159,7 @@
|
|||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -1180,7 +1189,7 @@
|
|||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -1212,7 +1221,7 @@
|
|||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -1266,6 +1275,8 @@
|
|||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
|
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -1312,9 +1323,11 @@
|
|||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = NO;
|
ENABLE_HARDENED_RUNTIME = NO;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
|
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
|
|||||||
@@ -4,88 +4,6 @@ import SecureEnclaveSecretKit
|
|||||||
import SmartCardSecretKit
|
import SmartCardSecretKit
|
||||||
import Brief
|
import Brief
|
||||||
|
|
||||||
@main
|
|
||||||
struct Secretive: App {
|
|
||||||
|
|
||||||
@Environment(\.agentLaunchController) var agentLaunchController
|
|
||||||
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
|
||||||
|
|
||||||
@SceneBuilder var body: some Scene {
|
|
||||||
WindowGroup {
|
|
||||||
ContentView()
|
|
||||||
.environment(EnvironmentValues._secretStoreList)
|
|
||||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
|
||||||
Task {
|
|
||||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
|
||||||
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
|
|
||||||
guard hasRunSetup && !explicitlyDisabled else { return }
|
|
||||||
agentLaunchController.check()
|
|
||||||
guard !agentLaunchController.developmentBuild else { return }
|
|
||||||
if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running {
|
|
||||||
// Relaunch the agent, since it'll be running from earlier update still
|
|
||||||
try await agentLaunchController.forceLaunch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.commands {
|
|
||||||
AppCommands()
|
|
||||||
}
|
|
||||||
WindowGroup(id: String(describing: IntegrationsView.self)) {
|
|
||||||
IntegrationsView()
|
|
||||||
}
|
|
||||||
.windowResizability(.contentMinSize)
|
|
||||||
WindowGroup(id: String(describing: AboutView.self)) {
|
|
||||||
AboutView()
|
|
||||||
}
|
|
||||||
.windowStyle(.hiddenTitleBar)
|
|
||||||
.windowResizability(.contentSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Secretive {
|
|
||||||
|
|
||||||
struct AppCommands: Commands {
|
|
||||||
|
|
||||||
@Environment(\.openWindow) var openWindow
|
|
||||||
@Environment(\.openURL) var openURL
|
|
||||||
@FocusedValue(\.showCreateSecret) var showCreateSecret
|
|
||||||
|
|
||||||
var body: some Commands {
|
|
||||||
CommandGroup(replacing: .appInfo) {
|
|
||||||
Button(.aboutMenuBarTitle, systemImage: "info.circle") {
|
|
||||||
openWindow(id: String(describing: AboutView.self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandGroup(before: CommandGroupPlacement.appSettings) {
|
|
||||||
Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") {
|
|
||||||
openWindow(id: String(describing: IntegrationsView.self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandGroup(after: CommandGroupPlacement.newItem) {
|
|
||||||
Button(.appMenuNewSecretButton, systemImage: "plus") {
|
|
||||||
showCreateSecret?()
|
|
||||||
}
|
|
||||||
.keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift]))
|
|
||||||
.disabled(showCreateSecret?.isEnabled == false)
|
|
||||||
}
|
|
||||||
CommandGroup(replacing: .help) {
|
|
||||||
Button(.appMenuHelpButton) {
|
|
||||||
openURL(Constants.helpURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SidebarCommands()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Constants {
|
|
||||||
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")!
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extension EnvironmentValues {
|
extension EnvironmentValues {
|
||||||
|
|
||||||
// This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip).
|
// This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip).
|
||||||
@@ -99,8 +17,8 @@ extension EnvironmentValues {
|
|||||||
return list
|
return list
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private static let _agentLaunchController = AgentLaunchController()
|
private static let _agentStatusChecker = AgentStatusChecker()
|
||||||
@Entry var agentLaunchController: any AgentLaunchControllerProtocol = _agentLaunchController
|
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
|
||||||
private static let _updater: any UpdaterProtocol = {
|
private static let _updater: any UpdaterProtocol = {
|
||||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||||
return Updater(checkOnLaunch: hasRunSetup)
|
return Updater(checkOnLaunch: hasRunSetup)
|
||||||
@@ -115,22 +33,88 @@ extension EnvironmentValues {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FocusedValues {
|
@main
|
||||||
@Entry var showCreateSecret: OpenSheet?
|
struct Secretive: App {
|
||||||
}
|
|
||||||
|
|
||||||
final class OpenSheet {
|
@Environment(\.agentStatusChecker) var agentStatusChecker
|
||||||
|
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
||||||
|
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||||
|
@State private var showingSetup = false
|
||||||
|
@State private var showingIntegrations = false
|
||||||
|
@State private var showingCreation = false
|
||||||
|
|
||||||
let closure: () -> Void
|
@SceneBuilder var body: some Scene {
|
||||||
let isEnabled: Bool
|
WindowGroup {
|
||||||
|
ContentView(showingCreation: $showingCreation, runningSetup: $showingSetup, hasRunSetup: $hasRunSetup)
|
||||||
init(isEnabled: Bool = true, closure: @escaping () -> Void) {
|
.environment(EnvironmentValues._secretStoreList)
|
||||||
self.isEnabled = isEnabled
|
.onAppear {
|
||||||
self.closure = closure
|
if !hasRunSetup {
|
||||||
|
showingSetup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||||
|
guard hasRunSetup else { return }
|
||||||
|
agentStatusChecker.check()
|
||||||
|
if agentStatusChecker.running && justUpdatedChecker.justUpdatedBuild {
|
||||||
|
// Relaunch the agent, since it'll be running from earlier update still
|
||||||
|
reinstallAgent()
|
||||||
|
} else if !agentStatusChecker.running && !agentStatusChecker.developmentBuild {
|
||||||
|
forceLaunchAgent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showingIntegrations) {
|
||||||
|
IntegrationsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.commands {
|
||||||
|
CommandGroup(before: CommandGroupPlacement.appSettings) {
|
||||||
|
Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") {
|
||||||
|
showingIntegrations = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandGroup(after: CommandGroupPlacement.newItem) {
|
||||||
|
Button(.appMenuNewSecretButton) {
|
||||||
|
showingCreation = true
|
||||||
|
}
|
||||||
|
.keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift]))
|
||||||
|
}
|
||||||
|
CommandGroup(replacing: .help) {
|
||||||
|
Button(.appMenuHelpButton) {
|
||||||
|
NSWorkspace.shared.open(Constants.helpURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SidebarCommands()
|
||||||
}
|
}
|
||||||
|
|
||||||
func callAsFunction() {
|
|
||||||
closure()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Secretive {
|
||||||
|
|
||||||
|
private func reinstallAgent() {
|
||||||
|
Task {
|
||||||
|
_ = await LaunchAgentController().install()
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
agentStatusChecker.check()
|
||||||
|
if !agentStatusChecker.running {
|
||||||
|
forceLaunchAgent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func forceLaunchAgent() {
|
||||||
|
// We've run setup, we didn't just update, launchd is just not doing it's thing.
|
||||||
|
// Force a launch directly.
|
||||||
|
Task {
|
||||||
|
_ = await LaunchAgentController().forceLaunch()
|
||||||
|
agentStatusChecker.check()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private enum Constants {
|
||||||
|
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")!
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-16x16@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-16x16@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-32x32@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-32x32@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-128x128@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-128x128@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-256x256@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-256x256@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-512x512@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "512x512"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-1024x1024@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 856 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 356 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 356 KiB |
6
Sources/Secretive/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,25 +2,18 @@ import Foundation
|
|||||||
import AppKit
|
import AppKit
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import Observation
|
import Observation
|
||||||
import OSLog
|
|
||||||
import ServiceManagement
|
|
||||||
|
|
||||||
@MainActor protocol AgentLaunchControllerProtocol: Observable, Sendable {
|
@MainActor protocol AgentStatusCheckerProtocol: Observable, Sendable {
|
||||||
var running: Bool { get }
|
var running: Bool { get }
|
||||||
var developmentBuild: Bool { get }
|
var developmentBuild: Bool { get }
|
||||||
var process: NSRunningApplication? { get }
|
var process: NSRunningApplication? { get }
|
||||||
func check()
|
func check()
|
||||||
func install() async throws
|
|
||||||
func uninstall() async throws
|
|
||||||
func forceLaunch() async throws
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Observable @MainActor final class AgentLaunchController: AgentLaunchControllerProtocol {
|
@Observable @MainActor final class AgentStatusChecker: AgentStatusCheckerProtocol {
|
||||||
|
|
||||||
var running: Bool = false
|
var running: Bool = false
|
||||||
var process: NSRunningApplication? = nil
|
var process: NSRunningApplication? = nil
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController")
|
|
||||||
private let service = SMAppService.loginItem(identifier: Bundle.agentBundleID)
|
|
||||||
|
|
||||||
nonisolated init() {
|
nonisolated init() {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
@@ -40,7 +33,7 @@ import ServiceManagement
|
|||||||
|
|
||||||
// The process corresponding to this instance of Secretive
|
// The process corresponding to this instance of Secretive
|
||||||
var instanceSecretAgentProcess: NSRunningApplication? {
|
var instanceSecretAgentProcess: NSRunningApplication? {
|
||||||
// TODO: CHECK VERSION
|
// FIXME: CHECK VERSION
|
||||||
let agents = allSecretAgentProcesses
|
let agents = allSecretAgentProcesses
|
||||||
for agent in agents {
|
for agent in agents {
|
||||||
guard let url = agent.bundleURL else { continue }
|
guard let url = agent.bundleURL else { continue }
|
||||||
@@ -56,47 +49,6 @@ import ServiceManagement
|
|||||||
Bundle.main.bundleURL.isXcodeURL
|
Bundle.main.bundleURL.isXcodeURL
|
||||||
}
|
}
|
||||||
|
|
||||||
func install() async throws {
|
|
||||||
logger.debug("Installing agent")
|
|
||||||
try? await service.unregister()
|
|
||||||
// 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?
|
|
||||||
try await Task.sleep(for: .seconds(1))
|
|
||||||
try service.register()
|
|
||||||
try await Task.sleep(for: .seconds(1))
|
|
||||||
check()
|
|
||||||
}
|
|
||||||
|
|
||||||
func uninstall() async throws {
|
|
||||||
logger.debug("Uninstalling agent")
|
|
||||||
try await Task.sleep(for: .seconds(1))
|
|
||||||
try await service.unregister()
|
|
||||||
try await Task.sleep(for: .seconds(1))
|
|
||||||
check()
|
|
||||||
}
|
|
||||||
|
|
||||||
func forceLaunch() async throws {
|
|
||||||
logger.debug("Agent is not running, attempting to force launch by reinstalling")
|
|
||||||
try await install()
|
|
||||||
if running {
|
|
||||||
logger.debug("Agent successfully force launched by reinstalling")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.debug("Agent is not running, attempting to force launch by launching directly")
|
|
||||||
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
|
|
||||||
let config = NSWorkspace.OpenConfiguration()
|
|
||||||
config.activates = false
|
|
||||||
do {
|
|
||||||
try await NSWorkspace.shared.openApplication(at: url, configuration: config)
|
|
||||||
logger.debug("Agent force launched")
|
|
||||||
try await Task.sleep(for: .seconds(1))
|
|
||||||
} catch {
|
|
||||||
logger.error("Error force launching \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
check()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension URL {
|
extension URL {
|
||||||
|
|||||||
65
Sources/Secretive/Controllers/LaunchAgentController.swift
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import Foundation
|
||||||
|
import ServiceManagement
|
||||||
|
import AppKit
|
||||||
|
import OSLog
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
|
struct LaunchAgentController {
|
||||||
|
|
||||||
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController")
|
||||||
|
|
||||||
|
func install() async -> Bool {
|
||||||
|
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?
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
let result = await MainActor.run {
|
||||||
|
setEnabled(true)
|
||||||
|
}
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func uninstall() async -> Bool {
|
||||||
|
logger.debug("Uninstalling agent")
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
let result = await MainActor.run {
|
||||||
|
setEnabled(false)
|
||||||
|
}
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func forceLaunch() async -> Bool {
|
||||||
|
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
|
||||||
|
do {
|
||||||
|
try await NSWorkspace.shared.openApplication(at: url, configuration: config)
|
||||||
|
logger.debug("Agent force launched")
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
logger.error("Error force launching \(error.localizedDescription)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setEnabled(_ enabled: Bool) -> Bool {
|
||||||
|
let service = SMAppService.loginItem(identifier: Bundle.agentBundleID)
|
||||||
|
do {
|
||||||
|
if enabled {
|
||||||
|
try service.register()
|
||||||
|
} else {
|
||||||
|
try service.unregister()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
36
Sources/Secretive/Credits.rtf
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{\rtf1\ansi\ansicpg1252\cocoartf2580
|
||||||
|
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||||
|
{\colortbl;\red255\green255\blue255;}
|
||||||
|
{\*\expandedcolortbl;;}
|
||||||
|
\margl1440\margr1440\vieww9000\viewh8400\viewkind0
|
||||||
|
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6119\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/maxgoedjen/secretive"}}{\fldrslt
|
||||||
|
\f0\fs24 \cf0 GitHub Repository}}
|
||||||
|
\f0\fs24 \
|
||||||
|
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||||
|
\cf0 \
|
||||||
|
{\field{\*\fldinst{HYPERLINK "GITHUB_BUILD_URL"}}{\fldrslt Build Log}}\
|
||||||
|
\
|
||||||
|
Special Thanks To:\
|
||||||
|
\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/maxgoedjen/secretive/graphs/contributors"}}{\fldrslt Contributors}}:\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/0xflotus"}}{\fldrslt 0xflotus}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/aaron-trout"}}{\fldrslt Aaron Trout}}\
|
||||||
|
\pard\pardeftab720\partightenfactor0
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/EppO"}}{\fldrslt \cf0 Florent Monbillard}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/vladimyr"}}{\fldrslt Dario Vladovi\uc0\u263 }}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/lavalleeale"}}{\fldrslt Alex Lavallee}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/joshheyse"}}{\fldrslt Josh}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/diesal11"}}{\fldrslt Dylan Lundy}}\
|
||||||
|
\
|
||||||
|
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||||
|
\cf0 Testers:\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/bdash"}}{\fldrslt Mark Rowe}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/danielctull"}}{\fldrslt Daniel Tull}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/davedelong"}}{\fldrslt Dave DeLong}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/esttorhe"}}{\fldrslt Esteban Torres}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/joeblau"}}{\fldrslt Joe Blau}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/marksands"}}{\fldrslt Mark Sands}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/mergesort"}}{\fldrslt Joe Fabisevich}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/phillco"}}{\fldrslt Phil Cohen}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/zackdotcomputer"}}{\fldrslt Zack Sheppard}}}
|
||||||
@@ -20,8 +20,6 @@
|
|||||||
<string>$(CI_VERSION)</string>
|
<string>$(CI_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CI_BUILD_NUMBER)</string>
|
<string>$(CI_BUILD_NUMBER)</string>
|
||||||
<key>GitHubBuildLog</key>
|
|
||||||
<string>https://$(CI_BUILD_LINK)</string>
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
class PreviewAgentLaunchController: AgentLaunchControllerProtocol {
|
class PreviewAgentStatusChecker: AgentStatusCheckerProtocol {
|
||||||
|
|
||||||
let running: Bool
|
let running: Bool
|
||||||
let process: NSRunningApplication?
|
let process: NSRunningApplication?
|
||||||
@@ -15,13 +15,4 @@ class PreviewAgentLaunchController: AgentLaunchControllerProtocol {
|
|||||||
func check() {
|
func check() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func install() async throws {
|
|
||||||
}
|
|
||||||
|
|
||||||
func uninstall() async throws {
|
|
||||||
}
|
|
||||||
|
|
||||||
func forceLaunch() async throws {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,17 +60,16 @@ extension Preview {
|
|||||||
let id = UUID()
|
let id = UUID()
|
||||||
var name: String { "Modifiable Preview Store" }
|
var name: String { "Modifiable Preview Store" }
|
||||||
let secrets: [Secret]
|
let secrets: [Secret]
|
||||||
var supportedKeyTypes: KeyAvailability {
|
var supportedKeyTypes: [KeyType] {
|
||||||
return KeyAvailability(
|
if #available(macOS 26, *) {
|
||||||
available: [
|
[
|
||||||
.ecdsa256,
|
.ecdsa256,
|
||||||
.mldsa65,
|
.mldsa65,
|
||||||
.mldsa87
|
.mldsa87,
|
||||||
],
|
|
||||||
unavailable: [
|
|
||||||
.init(keyType: .ecdsa384, reason: .macOSUpdateRequired)
|
|
||||||
]
|
]
|
||||||
)
|
} else {
|
||||||
|
[.ecdsa256]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(secrets: [Secret]) {
|
init(secrets: [Secret]) {
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>com.apple.security.hardened-process</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.hardened-process.dyld-ro</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.hardened-process.enhanced-security-version</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>com.apple.security.hardened-process.hardened-heap</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.hardened-process.platform-restrictions</key>
|
||||||
|
<integer>2</integer>
|
||||||
<key>com.apple.security.smartcard</key>
|
<key>com.apple.security.smartcard</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>keychain-access-groups</key>
|
<key>keychain-access-groups</key>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ struct ConfigurationItemView<Content: View>: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
switch action {
|
switch action {
|
||||||
case .copy(let string):
|
case .copy(let string):
|
||||||
Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") {
|
Button(.copyableClickToCopyButton, systemImage: "document.on.document") {
|
||||||
NSPasteboard.general.declareTypes([.string], owner: nil)
|
NSPasteboard.general.declareTypes([.string], owner: nil)
|
||||||
NSPasteboard.general.setString(string, forType: .string)
|
NSPasteboard.general.setString(string, forType: .string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,18 +22,46 @@ struct IntegrationsView: View {
|
|||||||
}
|
}
|
||||||
} detail: {
|
} detail: {
|
||||||
IntegrationsDetailView(selectedInstruction: $selectedInstruction)
|
IntegrationsDetailView(selectedInstruction: $selectedInstruction)
|
||||||
}
|
.fauxToolbar {
|
||||||
.toolbar {
|
|
||||||
Button(.setupDoneButton) {
|
Button(.setupDoneButton) {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
.normalButton()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.hiddenToolbar()
|
|
||||||
.windowBackgroundStyle(.thinMaterial)
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
selectedInstruction = instructions.gettingStarted
|
selectedInstruction = instructions.gettingStarted
|
||||||
}
|
}
|
||||||
.frame(minWidth: 400, minHeight: 400)
|
.frame(minHeight: 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
|
||||||
|
func fauxToolbar<Content: View>(content: () -> Content) -> some View {
|
||||||
|
modifier(FauxToolbarModifier(toolbarContent: content()))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FauxToolbarModifier<ToolbarContent: View>: ViewModifier {
|
||||||
|
|
||||||
|
var toolbarContent: ToolbarContent
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
content
|
||||||
|
Divider()
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
toolbarContent
|
||||||
|
.padding(.top, 8)
|
||||||
|
.padding(.trailing, 16)
|
||||||
|
.padding(.bottom, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import SwiftUI
|
|||||||
struct SetupView: View {
|
struct SetupView: View {
|
||||||
|
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@Environment(\.agentLaunchController) private var agentLaunchController
|
|
||||||
@Binding var setupComplete: Bool
|
@Binding var setupComplete: Bool
|
||||||
|
|
||||||
@State var showingIntegrations = false
|
@State var showingIntegrations = false
|
||||||
@@ -32,7 +31,7 @@ struct SetupView: View {
|
|||||||
) {
|
) {
|
||||||
installed = true
|
installed = true
|
||||||
Task {
|
Task {
|
||||||
try? await agentLaunchController.install()
|
await LaunchAgentController().install()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,10 +85,7 @@ struct SetupView: View {
|
|||||||
integrations = true
|
integrations = true
|
||||||
}, content: {
|
}, content: {
|
||||||
IntegrationsView()
|
IntegrationsView()
|
||||||
.frame(minWidth: 500, minHeight: 400)
|
|
||||||
})
|
})
|
||||||
.frame(idealWidth: 600)
|
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,13 +172,10 @@ struct StepView<Content: View>: View {
|
|||||||
.frame(width: 20)
|
.frame(width: 20)
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(title)
|
Text(title)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
.bold()
|
.bold()
|
||||||
Text(description)
|
Text(description)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
if let detail {
|
if let detail {
|
||||||
Text(detail)
|
Text(detail)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.italic()
|
.italic()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ struct ToolConfigurationView: View {
|
|||||||
|
|
||||||
@State var creating = false
|
@State var creating = false
|
||||||
@State var selectedSecret: AnySecret?
|
@State var selectedSecret: AnySecret?
|
||||||
@State var email = ""
|
|
||||||
|
|
||||||
init(selectedInstruction: ConfigurationFileInstructions) {
|
init(selectedInstruction: ConfigurationFileInstructions) {
|
||||||
self.selectedInstruction = selectedInstruction
|
self.selectedInstruction = selectedInstruction
|
||||||
@@ -33,7 +32,6 @@ struct ToolConfigurationView: View {
|
|||||||
selectedSecret = created
|
selectedSecret = created
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.fixedSize()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,12 +47,6 @@ struct ToolConfigurationView: View {
|
|||||||
.tag(secret)
|
.tag(secret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextField(text: $email, prompt: Text(.integrationsConfigureUsingEmailPlaceholder)) {
|
|
||||||
Text(.integrationsConfigureUsingEmailTitle)
|
|
||||||
Text(.integrationsConfigureUsingEmailSubtitle)
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
} header: {
|
} header: {
|
||||||
Text(.integrationsConfigureUsingSecretHeader)
|
Text(.integrationsConfigureUsingSecretHeader)
|
||||||
}
|
}
|
||||||
@@ -67,7 +59,7 @@ struct ToolConfigurationView: View {
|
|||||||
Section {
|
Section {
|
||||||
ConfigurationItemView(title: .integrationsPathTitle, value: stepGroup.path, action: .revealInFinder(stepGroup.path))
|
ConfigurationItemView(title: .integrationsPathTitle, value: stepGroup.path, action: .revealInFinder(stepGroup.path))
|
||||||
ForEach(stepGroup.steps, id: \.self.key) { step in
|
ForEach(stepGroup.steps, id: \.self.key) { step in
|
||||||
ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(placeholdersReplaced(text: String(localized: step)))) {
|
ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(String(localized: step))) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(placeholdersReplaced(text: String(localized: step)))
|
Text(placeholdersReplaced(text: String(localized: step)))
|
||||||
.padding(8)
|
.padding(8)
|
||||||
@@ -109,11 +101,9 @@ struct ToolConfigurationView: View {
|
|||||||
func placeholdersReplaced(text: String) -> String {
|
func placeholdersReplaced(text: String) -> String {
|
||||||
guard let selectedSecret else { return text }
|
guard let selectedSecret else { return text }
|
||||||
let writer = OpenSSHPublicKeyWriter()
|
let writer = OpenSSHPublicKeyWriter()
|
||||||
let gitAllowedSignersString = [email.isEmpty ? String(localized: .integrationsConfigureUsingEmailPlaceholder) : email, writer.openSSHString(secret: selectedSecret)]
|
|
||||||
.joined(separator: " ")
|
|
||||||
let fileController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
|
let fileController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
|
||||||
return text
|
return text
|
||||||
.replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: gitAllowedSignersString)
|
.replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: writer.openSSHString(secret: selectedSecret))
|
||||||
.replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: fileController.publicKeyPath(for: selectedSecret))
|
.replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: fileController.publicKeyPath(for: selectedSecret))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct WindowBackgroundStyleModifier: ViewModifier {
|
|
||||||
|
|
||||||
let shapeStyle: any ShapeStyle
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
if #available(macOS 15.0, *) {
|
|
||||||
content
|
|
||||||
.containerBackground(
|
|
||||||
shapeStyle, for: .window
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
func windowBackgroundStyle(_ style: some ShapeStyle) -> some View {
|
|
||||||
modifier(WindowBackgroundStyleModifier(shapeStyle: style))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HiddenToolbarModifier: ViewModifier {
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
if #available(macOS 15.0, *) {
|
|
||||||
content
|
|
||||||
.toolbarBackgroundVisibility(.hidden, for: .automatic)
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
func hiddenToolbar() -> some View {
|
|
||||||
modifier(HiddenToolbarModifier())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -75,23 +75,10 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
Section {
|
Section {
|
||||||
VStack {
|
VStack {
|
||||||
Picker(.createSecretKeyTypeLabel, selection: $keyType) {
|
Picker(.createSecretKeyTypeLabel, selection: $keyType) {
|
||||||
ForEach(store.supportedKeyTypes.available, id: \.self) { option in
|
ForEach(store.supportedKeyTypes, id: \.self) { option in
|
||||||
Text(String(describing: option))
|
Text(String(describing: option))
|
||||||
.tag(option)
|
.tag(option)
|
||||||
}
|
.font(.caption)
|
||||||
Divider()
|
|
||||||
ForEach(store.supportedKeyTypes.unavailable, id: \.keyType) { option in
|
|
||||||
VStack {
|
|
||||||
Button {
|
|
||||||
} label: {
|
|
||||||
Text(String(describing: option.keyType))
|
|
||||||
switch option.reason {
|
|
||||||
case .macOSUpdateRequired:
|
|
||||||
Text(.createSecretKeyTypeMacOSUpdateRequiredLabel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.selectionDisabled()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if keyType?.algorithm == .mldsa {
|
if keyType?.algorithm == .mldsa {
|
||||||
@@ -132,7 +119,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
keyType = store.supportedKeyTypes.available.first
|
keyType = store.supportedKeyTypes.first
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ extension View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ToolbarCircleButtonModifier: ViewModifier {
|
struct MenuButtonModifier: ViewModifier {
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
if #available(macOS 26.0, *) {
|
if #available(macOS 26.0, *) {
|
||||||
@@ -40,8 +40,8 @@ struct ToolbarCircleButtonModifier: ViewModifier {
|
|||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
|
|
||||||
func toolbarCircleButton() -> some View {
|
func menuButton() -> some View {
|
||||||
modifier(ToolbarCircleButtonModifier())
|
modifier(MenuButtonModifier())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ToolbarStatusButtonStyle: ButtonStyle {
|
struct ToolbarButtonStyle: ButtonStyle {
|
||||||
|
|
||||||
private let lightColor: Color
|
private let lightColor: Color
|
||||||
private let darkColor: Color
|
private let darkColor: Color
|
||||||
@@ -39,7 +39,6 @@ struct ToolbarStatusButtonStyle: ButtonStyle {
|
|||||||
} else {
|
} else {
|
||||||
configuration
|
configuration
|
||||||
.label
|
.label
|
||||||
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
|
|
||||||
.background(colorScheme == .light ? lightColor : darkColor)
|
.background(colorScheme == .light ? lightColor : darkColor)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||||
@@ -56,27 +55,3 @@ struct ToolbarStatusButtonStyle: ButtonStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ToolbarButtonStyle: PrimitiveButtonStyle {
|
|
||||||
|
|
||||||
var tint: Color = .white.opacity(0.1)
|
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
|
||||||
if #available(macOS 26.0, *) {
|
|
||||||
configuration
|
|
||||||
.label
|
|
||||||
.padding(.vertical, 10)
|
|
||||||
.padding(.horizontal, 12)
|
|
||||||
.glassEffect(.regular.interactive().tint(tint))
|
|
||||||
.onTapGesture {
|
|
||||||
configuration.trigger()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
BorderedButtonStyle().makeBody(configuration: configuration)
|
|
||||||
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct AboutView: View {
|
|
||||||
var body: some View {
|
|
||||||
if #available(macOS 15.0, *) {
|
|
||||||
AboutViewContent()
|
|
||||||
.containerBackground(
|
|
||||||
.thinMaterial, for: .window
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
AboutViewContent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AboutViewContent: View {
|
|
||||||
|
|
||||||
@Environment(\.openURL) var openURL
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 10) {
|
|
||||||
HStack {
|
|
||||||
Image(nsImage: NSApplication.shared.applicationIconImage)
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text(verbatim: "Secretive")
|
|
||||||
.font(.system(.largeTitle, weight: .bold))
|
|
||||||
Text("**\(Bundle.main.versionNumber)** (\(Bundle.main.buildNumber))")
|
|
||||||
.fixedSize(horizontal: true, vertical: false)
|
|
||||||
HStack {
|
|
||||||
Button(.aboutViewOnGithubButton) {
|
|
||||||
openURL(URL(string: "https://github.com/maxgoedjen/secretive")!)
|
|
||||||
}
|
|
||||||
.normalButton()
|
|
||||||
Button(.aboutBuildLogButton) {
|
|
||||||
openURL(Bundle.main.buildLog)
|
|
||||||
}
|
|
||||||
.normalButton()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text(.aboutThanks(contributorsLink: "https://github.com/maxgoedjen/secretive/graphs/contributors", sponsorsLink: "https://github.com/sponsors/maxgoedjen"))
|
|
||||||
.font(.headline)
|
|
||||||
Text(.aboutOpenSourceNotice)
|
|
||||||
.font(.subheadline)
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: 10, leading: 30, bottom: 30, trailing: 30))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension Bundle {
|
|
||||||
|
|
||||||
var buildLog: URL {
|
|
||||||
URL(string: infoDictionary!["GitHubBuildLog"] as! String)!
|
|
||||||
}
|
|
||||||
|
|
||||||
var versionNumber: String {
|
|
||||||
infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
var buildNumber: String {
|
|
||||||
infoDictionary?["CFBundleVersion"] as? String ?? "0.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
AboutView()
|
|
||||||
.frame(width: 500, height: 250)
|
|
||||||
}
|
|
||||||
@@ -2,10 +2,10 @@ import SwiftUI
|
|||||||
|
|
||||||
struct AgentStatusView: View {
|
struct AgentStatusView: View {
|
||||||
|
|
||||||
@Environment(\.agentLaunchController) private var agentLaunchController: any AgentLaunchControllerProtocol
|
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if agentLaunchController.running {
|
if agentStatusChecker.running {
|
||||||
AgentRunningView()
|
AgentRunningView()
|
||||||
} else {
|
} else {
|
||||||
AgentNotRunningView()
|
AgentNotRunningView()
|
||||||
@@ -14,13 +14,12 @@ struct AgentStatusView: View {
|
|||||||
}
|
}
|
||||||
struct AgentRunningView: View {
|
struct AgentRunningView: View {
|
||||||
|
|
||||||
@Environment(\.agentLaunchController) private var agentLaunchController: any AgentLaunchControllerProtocol
|
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
|
||||||
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
if let process = agentLaunchController.process {
|
if let process = agentStatusChecker.process {
|
||||||
ConfigurationItemView(
|
ConfigurationItemView(
|
||||||
title: .agentDetailsLocationTitle,
|
title: .agentDetailsLocationTitle,
|
||||||
value: process.bundleURL!.path(),
|
value: process.bundleURL!.path(),
|
||||||
@@ -54,14 +53,19 @@ struct AgentRunningView: View {
|
|||||||
Menu(.agentDetailsRestartAgentButton) {
|
Menu(.agentDetailsRestartAgentButton) {
|
||||||
Button(.agentDetailsDisableAgentButton) {
|
Button(.agentDetailsDisableAgentButton) {
|
||||||
Task {
|
Task {
|
||||||
explicitlyDisabled = true
|
_ = await LaunchAgentController()
|
||||||
try? await agentLaunchController
|
|
||||||
.uninstall()
|
.uninstall()
|
||||||
|
agentStatusChecker.check()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} primaryAction: {
|
} primaryAction: {
|
||||||
Task {
|
Task {
|
||||||
try? await agentLaunchController.forceLaunch()
|
let controller = LaunchAgentController()
|
||||||
|
let installed = await controller.install()
|
||||||
|
if !installed {
|
||||||
|
_ = await controller.forceLaunch()
|
||||||
|
}
|
||||||
|
agentStatusChecker.check()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,10 +82,9 @@ struct AgentRunningView: View {
|
|||||||
|
|
||||||
struct AgentNotRunningView: View {
|
struct AgentNotRunningView: View {
|
||||||
|
|
||||||
@Environment(\.agentLaunchController) private var agentLaunchController
|
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
|
||||||
@State var triedRestart = false
|
@State var triedRestart = false
|
||||||
@State var loading = false
|
@State var loading = false
|
||||||
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
@@ -97,14 +100,18 @@ struct AgentNotRunningView: View {
|
|||||||
if !triedRestart {
|
if !triedRestart {
|
||||||
Spacer()
|
Spacer()
|
||||||
Button {
|
Button {
|
||||||
explicitlyDisabled = false
|
|
||||||
guard !loading else { return }
|
guard !loading else { return }
|
||||||
loading = true
|
loading = true
|
||||||
Task {
|
Task {
|
||||||
try await agentLaunchController.forceLaunch()
|
let controller = LaunchAgentController()
|
||||||
|
let installed = await controller.install()
|
||||||
|
if !installed {
|
||||||
|
_ = await controller.forceLaunch()
|
||||||
|
}
|
||||||
|
agentStatusChecker.check()
|
||||||
loading = false
|
loading = false
|
||||||
|
|
||||||
if !agentLaunchController.running {
|
if !agentStatusChecker.running {
|
||||||
triedRestart = true
|
triedRestart = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,9 +145,9 @@ struct AgentNotRunningView: View {
|
|||||||
|
|
||||||
//#Preview {
|
//#Preview {
|
||||||
// AgentStatusView()
|
// AgentStatusView()
|
||||||
// .environment(\.agentLaunchController, PreviewAgentLaunchController(running: false))
|
// .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: false))
|
||||||
//}
|
//}
|
||||||
//#Preview {
|
//#Preview {
|
||||||
// AgentStatusView()
|
// AgentStatusView()
|
||||||
// .environment(\.agentLaunchController, PreviewAgentLaunchController(running: true, process: .current))
|
// .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: true, process: .current))
|
||||||
//}
|
//}
|
||||||
|
|||||||
@@ -6,21 +6,19 @@ import Brief
|
|||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
|
|
||||||
|
@Binding var showingCreation: Bool
|
||||||
|
@Binding var runningSetup: Bool
|
||||||
|
@Binding var hasRunSetup: Bool
|
||||||
|
@State var showingAgentInfo = false
|
||||||
@State var activeSecret: AnySecret?
|
@State var activeSecret: AnySecret?
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
|
@Environment(\.secretStoreList) private var storeList
|
||||||
|
@Environment(\.updater) private var updater: any UpdaterProtocol
|
||||||
|
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
|
||||||
|
|
||||||
@State private var selectedUpdate: Release?
|
@State private var selectedUpdate: Release?
|
||||||
|
|
||||||
@Environment(\.colorScheme) private var colorScheme
|
|
||||||
@Environment(\.openWindow) private var openWindow
|
|
||||||
@Environment(\.secretStoreList) private var storeList
|
|
||||||
@Environment(\.updater) private var updater
|
|
||||||
@Environment(\.agentLaunchController) private var agentLaunchController
|
|
||||||
|
|
||||||
@AppStorage("defaultsHasRunSetup") private var hasRunSetup = false
|
|
||||||
@State private var showingCreation = false
|
|
||||||
@State private var showingAppPathNotice = false
|
@State private var showingAppPathNotice = false
|
||||||
@State private var runningSetup = false
|
|
||||||
@State private var showingAgentInfo = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@@ -37,23 +35,6 @@ struct ContentView: View {
|
|||||||
toolbarItem(appPathNoticeView, id: "appPath")
|
toolbarItem(appPathNoticeView, id: "appPath")
|
||||||
toolbarItem(newItemView, id: "new")
|
toolbarItem(newItemView, id: "new")
|
||||||
}
|
}
|
||||||
.onAppear {
|
|
||||||
if !hasRunSetup {
|
|
||||||
runningSetup = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.focusedSceneValue(\.showCreateSecret, .init(isEnabled: !runningSetup) {
|
|
||||||
showingCreation = true
|
|
||||||
})
|
|
||||||
.sheet(isPresented: $showingCreation) {
|
|
||||||
if let modifiable = storeList.modifiableStore {
|
|
||||||
CreateSecretView(store: modifiable) { created in
|
|
||||||
if let created {
|
|
||||||
activeSecret = created
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $runningSetup) {
|
.sheet(isPresented: $runningSetup) {
|
||||||
SetupView(setupComplete: $hasRunSetup)
|
SetupView(setupComplete: $hasRunSetup)
|
||||||
}
|
}
|
||||||
@@ -104,12 +85,27 @@ extension ContentView {
|
|||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
})
|
})
|
||||||
.buttonStyle(ToolbarStatusButtonStyle(color: color))
|
.buttonStyle(ToolbarButtonStyle(color: color))
|
||||||
.sheet(item: $selectedUpdate) { update in
|
.sheet(item: $selectedUpdate) { update in
|
||||||
|
VStack {
|
||||||
|
if updater.currentVersion.isTestBuild {
|
||||||
|
VStack {
|
||||||
|
if let description = updater.currentVersion.previewDescription {
|
||||||
|
Text(description)
|
||||||
|
}
|
||||||
|
Link(destination: URL(string: "https://github.com/maxgoedjen/secretive/actions/workflows/nightly.yml")!) {
|
||||||
|
Button(.updaterDownloadLatestNightlyButton) {}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.primaryButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
UpdateDetailView(update: update)
|
UpdateDetailView(update: update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var newItemView: some View {
|
var newItemView: some View {
|
||||||
@@ -117,7 +113,16 @@ extension ContentView {
|
|||||||
Button(.appMenuNewSecretButton, systemImage: "plus") {
|
Button(.appMenuNewSecretButton, systemImage: "plus") {
|
||||||
showingCreation = true
|
showingCreation = true
|
||||||
}
|
}
|
||||||
.toolbarCircleButton()
|
.menuButton()
|
||||||
|
.sheet(isPresented: $showingCreation) {
|
||||||
|
if let modifiable = storeList.modifiableStore {
|
||||||
|
CreateSecretView(store: modifiable) { created in
|
||||||
|
if let created {
|
||||||
|
activeSecret = created
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +132,7 @@ extension ContentView {
|
|||||||
showingAgentInfo = true
|
showingAgentInfo = true
|
||||||
}, label: {
|
}, label: {
|
||||||
HStack {
|
HStack {
|
||||||
if agentLaunchController.running {
|
if agentStatusChecker.running {
|
||||||
Text(.agentRunningNoticeTitle)
|
Text(.agentRunningNoticeTitle)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white)
|
.foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white)
|
||||||
@@ -144,9 +149,9 @@ extension ContentView {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.buttonStyle(
|
.buttonStyle(
|
||||||
ToolbarStatusButtonStyle(
|
ToolbarButtonStyle(
|
||||||
lightColor: agentLaunchController.running ? .black.opacity(0.05) : .red.opacity(0.75),
|
lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75),
|
||||||
darkColor: agentLaunchController.running ? .white.opacity(0.05) : .red.opacity(0.5),
|
darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {
|
.popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {
|
||||||
@@ -166,18 +171,18 @@ extension ContentView {
|
|||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
})
|
})
|
||||||
.buttonStyle(ToolbarStatusButtonStyle(color: .orange))
|
.buttonStyle(ToolbarButtonStyle(color: .orange))
|
||||||
.confirmationDialog(.appNotInApplicationsNoticeTitle, isPresented: $showingAppPathNotice) {
|
.popover(isPresented: $showingAppPathNotice, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {
|
||||||
Button(.appNotInApplicationsNoticeCancelButton, role: .cancel) {
|
VStack {
|
||||||
}
|
Image(systemName: "exclamationmark.triangle")
|
||||||
Button(.appNotInApplicationsNoticeQuitButton) {
|
.resizable()
|
||||||
NSWorkspace.shared.selectFile(Bundle.main.bundlePath, inFileViewerRootedAtPath: Bundle.main.bundlePath)
|
.aspectRatio(contentMode: .fit)
|
||||||
NSApplication.shared.terminate(nil)
|
.frame(width: 64)
|
||||||
}
|
|
||||||
} message: {
|
|
||||||
Text(.appNotInApplicationsNoticeDetailDescription)
|
Text(.appNotInApplicationsNoticeDetailDescription)
|
||||||
|
.frame(maxWidth: 300)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
.dialogIcon(Image(systemName: "folder.fill.badge.questionmark"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ struct CopyableView: View {
|
|||||||
@State private var interactionState: InteractionState = .normal
|
@State private var interactionState: InteractionState = .normal
|
||||||
|
|
||||||
var content: some View {
|
var content: some View {
|
||||||
VStack(alignment: .leading, spacing: 15) {
|
VStack(alignment: .leading) {
|
||||||
HStack {
|
HStack {
|
||||||
image
|
image
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
@@ -31,16 +31,17 @@ struct CopyableView: View {
|
|||||||
.foregroundColor(secondaryTextColor)
|
.foregroundColor(secondaryTextColor)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
.padding(EdgeInsets(top: 20, leading: 20, bottom: 10, trailing: 20))
|
||||||
Divider()
|
Divider()
|
||||||
.ignoresSafeArea()
|
|
||||||
Text(text)
|
Text(text)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.foregroundColor(primaryTextColor)
|
.foregroundColor(primaryTextColor)
|
||||||
|
.padding(EdgeInsets(top: 10, leading: 20, bottom: 20, trailing: 20))
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.font(.system(.body, design: .monospaced))
|
.font(.system(.body, design: .monospaced))
|
||||||
}
|
}
|
||||||
.safeAreaPadding(20)
|
|
||||||
._background(interactionState: interactionState)
|
._background(interactionState: interactionState)
|
||||||
.frame(minWidth: 150, maxWidth: .infinity)
|
.frame(minWidth: 150, maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
@@ -52,12 +53,12 @@ struct CopyableView: View {
|
|||||||
interactionState = hovering ? .hovering : .normal
|
interactionState = hovering ? .hovering : .normal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.draggable(text) {
|
.onDrag({
|
||||||
|
NSItemProvider(item: NSData(data: text.data(using: .utf8)!), typeIdentifier: UTType.utf8PlainText.identifier)
|
||||||
|
}, preview: {
|
||||||
content
|
content
|
||||||
.lineLimit(3)
|
|
||||||
.frame(maxWidth: 300)
|
|
||||||
._background(interactionState: .dragging)
|
._background(interactionState: .dragging)
|
||||||
}
|
})
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
copy()
|
copy()
|
||||||
withAnimation {
|
withAnimation {
|
||||||
@@ -78,7 +79,7 @@ struct CopyableView: View {
|
|||||||
var copyButton: some View {
|
var copyButton: some View {
|
||||||
switch interactionState {
|
switch interactionState {
|
||||||
case .hovering:
|
case .hovering:
|
||||||
Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") {
|
Button(.copyableClickToCopyButton, systemImage: "document.on.document") {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
// Button will eat the click, so we set interaction state manually.
|
// Button will eat the click, so we set interaction state manually.
|
||||||
interactionState = .clicking
|
interactionState = .clicking
|
||||||
@@ -158,8 +159,7 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
|||||||
// Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow.
|
// Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow.
|
||||||
.background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15))
|
.background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15))
|
||||||
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: 15))
|
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: 15))
|
||||||
.mask(RoundedRectangle(cornerRadius: 15))
|
|
||||||
.shadow(color: .black.opacity(0.1), radius: 5)
|
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
.background(backgroundColor(interactionState: interactionState))
|
.background(backgroundColor(interactionState: interactionState))
|
||||||
@@ -170,36 +170,21 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
|||||||
|
|
||||||
func backgroundColor(interactionState: InteractionState) -> Color {
|
func backgroundColor(interactionState: InteractionState) -> Color {
|
||||||
guard appearsActive else { return Color.clear }
|
guard appearsActive else { return Color.clear }
|
||||||
if #available(macOS 26.0, *) {
|
|
||||||
let base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
|
|
||||||
switch interactionState {
|
|
||||||
case .normal:
|
|
||||||
return base
|
|
||||||
case .hovering:
|
|
||||||
return base.mix(with: .accentColor, by: colorScheme == .dark ? 0.2 : 0.1)
|
|
||||||
case .clicking, .dragging:
|
|
||||||
return base.mix(with: .accentColor, by: 0.8)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch interactionState {
|
switch interactionState {
|
||||||
case .normal:
|
case .normal:
|
||||||
return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885)
|
return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885)
|
||||||
case .hovering:
|
case .hovering, .dragging:
|
||||||
return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82)
|
return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82)
|
||||||
case .clicking, .dragging:
|
case .clicking:
|
||||||
return .accentColor
|
return .accentColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
VStack {
|
|
||||||
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.")
|
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.")
|
||||||
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.")
|
|
||||||
}
|
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,18 @@ import Brief
|
|||||||
|
|
||||||
struct UpdateDetailView: View {
|
struct UpdateDetailView: View {
|
||||||
|
|
||||||
@Environment(\.updater) var updater
|
@Environment(\.updater) var updater: any UpdaterProtocol
|
||||||
@Environment(\.openURL) var openURL
|
|
||||||
|
|
||||||
let update: Release
|
let update: Release
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack {
|
||||||
|
Text(.updateVersionName(updateName: update.name)).font(.title)
|
||||||
|
GroupBox(label: Text(.updateReleaseNotesTitle)) {
|
||||||
|
ScrollView {
|
||||||
|
attributedBody
|
||||||
|
}
|
||||||
|
}
|
||||||
HStack {
|
HStack {
|
||||||
if !update.critical {
|
if !update.critical {
|
||||||
Button(.updateIgnoreButton) {
|
Button(.updateIgnoreButton) {
|
||||||
@@ -17,36 +22,42 @@ struct UpdateDetailView: View {
|
|||||||
await updater.ignore(release: update)
|
await updater.ignore(release: update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(ToolbarButtonStyle())
|
|
||||||
}
|
|
||||||
Spacer()
|
Spacer()
|
||||||
if updater.currentVersion.isTestBuild {
|
|
||||||
Button(.updaterDownloadLatestNightlyButton) {
|
|
||||||
openURL(URL(string: "https://github.com/maxgoedjen/secretive/actions/workflows/nightly.yml")!)
|
|
||||||
}
|
|
||||||
.buttonStyle(ToolbarButtonStyle(tint: .accentColor))
|
|
||||||
}
|
}
|
||||||
Button(.updateUpdateButton) {
|
Button(.updateUpdateButton) {
|
||||||
openURL(update.html_url)
|
NSWorkspace.shared.open(update.html_url)
|
||||||
}
|
}
|
||||||
.buttonStyle(ToolbarButtonStyle(tint: .accentColor))
|
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
.padding()
|
.padding()
|
||||||
Divider()
|
.frame(maxWidth: 500)
|
||||||
Form {
|
|
||||||
Section {
|
|
||||||
Text(update.attributedBody)
|
|
||||||
} header: {
|
|
||||||
Text(.updateVersionName(updateName: update.name)) .headerProminence(.increased)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var attributedBody: Text {
|
||||||
|
var text = Text(verbatim: "")
|
||||||
|
for line in update.body.split(whereSeparator: \.isNewline) {
|
||||||
|
let attributed: Text
|
||||||
|
let split = line.split(separator: " ")
|
||||||
|
let unprefixed = split.dropFirst().joined(separator: " ")
|
||||||
|
if let prefix = split.first {
|
||||||
|
switch prefix {
|
||||||
|
case "#":
|
||||||
|
attributed = Text(unprefixed).font(.title) + Text(verbatim: "\n")
|
||||||
|
case "##":
|
||||||
|
attributed = Text(unprefixed).font(.title2) + Text(verbatim: "\n")
|
||||||
|
case "###":
|
||||||
|
attributed = Text(unprefixed).font(.title3) + Text(verbatim: "\n")
|
||||||
|
default:
|
||||||
|
attributed = Text(line) + Text(verbatim: "\n\n")
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
} else {
|
||||||
|
attributed = Text(line) + Text(verbatim: "\n\n")
|
||||||
}
|
}
|
||||||
|
text = text + attributed
|
||||||
|
}
|
||||||
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
|
||||||
UpdateDetailView(update: .init(name: "3.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Hello"))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ final class SecretiveUpdater: NSObject, XPCProtocol {
|
|||||||
|
|
||||||
func process(_: Data) async throws -> [Release] {
|
func process(_: Data) async throws -> [Release] {
|
||||||
let (data, _) = try await URLSession.shared.data(from: Constants.updateURL)
|
let (data, _) = try await URLSession.shared.data(from: Constants.updateURL)
|
||||||
return try JSONDecoder()
|
return try JSONDecoder().decode([Release].self, from: data)
|
||||||
.decode([GitHubRelease].self, from: data)
|
|
||||||
.map(Release.init)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||