Package and updater

This commit is contained in:
Max Goedjen 2024-08-15 16:50:03 -07:00
parent 6f4226f97a
commit 56a662a9dd
No known key found for this signature in database
12 changed files with 126 additions and 58 deletions

View File

@ -1,12 +1,14 @@
// swift-tools-version:5.9 // swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package. // The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription import PackageDescription
let secretiveDefaults: [PackageDescription.SwiftSetting]? = [.swiftLanguageMode(.v6), .unsafeFlags(["-warnings-as-errors"])]
let package = Package( let package = Package(
name: "SecretivePackages", name: "SecretivePackages",
platforms: [ platforms: [
.macOS(.v12) .macOS(.v13)
], ],
products: [ products: [
.library( .library(
@ -34,27 +36,27 @@ let package = Package(
.target( .target(
name: "SecretKit", name: "SecretKit",
dependencies: [], dependencies: [],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])] swiftSettings: secretiveDefaults
), ),
.testTarget( .testTarget(
name: "SecretKitTests", name: "SecretKitTests",
dependencies: ["SecretKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"], dependencies: ["SecretKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])] swiftSettings: secretiveDefaults
), ),
.target( .target(
name: "SecureEnclaveSecretKit", name: "SecureEnclaveSecretKit",
dependencies: ["SecretKit"], dependencies: ["SecretKit"],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])] swiftSettings: secretiveDefaults
), ),
.target( .target(
name: "SmartCardSecretKit", name: "SmartCardSecretKit",
dependencies: ["SecretKit"], dependencies: ["SecretKit"],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])] swiftSettings: secretiveDefaults
), ),
.target( .target(
name: "SecretAgentKit", name: "SecretAgentKit",
dependencies: ["SecretKit", "SecretAgentKitHeaders"], dependencies: ["SecretKit", "SecretAgentKitHeaders"],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])] swiftSettings: secretiveDefaults
), ),
.systemLibrary( .systemLibrary(
name: "SecretAgentKitHeaders" name: "SecretAgentKitHeaders"
@ -73,3 +75,4 @@ let package = Package(
), ),
] ]
) )

View File

@ -1,7 +1,7 @@
import Foundation import Foundation
/// A release is a representation of a downloadable update. /// A release is a representation of a downloadable update.
public struct Release: Codable { 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

View File

@ -2,7 +2,7 @@ import Foundation
import Combine import Combine
/// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version. /// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version.
public final class Updater: ObservableObject, UpdaterProtocol { @MainActor public final class Updater: ObservableObject, UpdaterProtocol, Sendable {
@Published public var update: Release? @Published public var update: Release?
public let testBuild: Bool public let testBuild: Bool
@ -18,27 +18,27 @@ public final class Updater: ObservableObject, UpdaterProtocol {
/// - checkFrequency: The interval at which the Updater should check for updates. Subject to a tolerance of 1 hour. /// - checkFrequency: The interval at which the Updater should check for updates. Subject to a tolerance of 1 hour.
/// - osVersion: The current OS version. /// - osVersion: The current OS version.
/// - currentVersion: The current version of the app that is running. /// - currentVersion: The current version of the app that is running.
public init(checkOnLaunch: Bool, checkFrequency: TimeInterval = Measurement(value: 24, unit: UnitDuration.hours).converted(to: .seconds).value, osVersion: SemVer = SemVer(ProcessInfo.processInfo.operatingSystemVersion), currentVersion: SemVer = SemVer(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0")) { public init(checkOnLaunch: Bool, checkFrequency: Duration = .seconds(24*60*60), osVersion: SemVer = SemVer(ProcessInfo.processInfo.operatingSystemVersion), currentVersion: SemVer = SemVer(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0")) {
self.osVersion = osVersion self.osVersion = osVersion
self.currentVersion = currentVersion self.currentVersion = currentVersion
testBuild = currentVersion == SemVer("0.0.0") testBuild = currentVersion == SemVer("0.0.0")
if checkOnLaunch { Task {
// Don't do a launch check if the user hasn't seen the setup prompt explaining updater yet. if checkOnLaunch {
checkForUpdates() // Don't do a launch check if the user hasn't seen the setup prompt explaining updater yet.
await checkForUpdates()
}
while true {
try await Task.sleep(for: checkFrequency, tolerance: .seconds(60*60))
await checkForUpdates()
}
} }
let timer = Timer.scheduledTimer(withTimeInterval: checkFrequency, repeats: true) { _ in
self.checkForUpdates()
}
timer.tolerance = 60*60
} }
/// Manually trigger an update check. /// Manually trigger an update check.
public func checkForUpdates() { public func checkForUpdates() async {
URLSession.shared.dataTask(with: Constants.updateURL) { data, _, _ in guard let (data, _) = try? await URLSession.shared.data(from: Constants.updateURL) else { return }
guard let data = data else { return } guard let releases = try? JSONDecoder().decode([Release].self, from: data) else { return }
guard let releases = try? JSONDecoder().decode([Release].self, from: data) else { return } evaluate(releases: releases)
self.evaluate(releases: releases)
}.resume()
} }
/// Ignores a specified release. `update` will be nil if the user has ignored the latest available release. /// Ignores a specified release. `update` will be nil if the user has ignored the latest available release.

View File

@ -5,7 +5,7 @@ import Combine
public protocol UpdaterProtocol: ObservableObject { public protocol UpdaterProtocol: ObservableObject {
/// The latest update /// The latest update
var update: Release? { get } @MainActor var update: Release? { get }
/// A boolean describing whether or not the current build of the app is a "test" build (ie, a debug build or otherwise special build) /// A boolean describing whether or not the current build of the app is a "test" build (ie, a debug build or otherwise special build)
var testBuild: Bool { get } var testBuild: Bool { get }

View File

@ -5,7 +5,7 @@ import SecretKit
import AppKit import AppKit
/// The `Agent` is an implementation of an SSH agent. It manages coordination and access between a socket, traces requests, notifies witnesses and passes requests to stores. /// The `Agent` is an implementation of an SSH agent. It manages coordination and access between a socket, traces requests, notifies witnesses and passes requests to stores.
public final class Agent { public actor Agent {
private let storeList: SecretStoreList private let storeList: SecretStoreList
private let witness: SigningWitness? private let witness: SigningWitness?
@ -35,7 +35,7 @@ extension Agent {
/// - writer: A ``FileHandleWriter`` to write the response to. /// - writer: A ``FileHandleWriter`` to write the response to.
/// - Return value: /// - Return value:
/// - Boolean if data could be read /// - Boolean if data could be read
@discardableResult @Sendable public func handle(reader: FileHandleReader, writer: FileHandleWriter) async -> Bool { @discardableResult public func handle(reader: FileHandleReader, writer: FileHandleWriter) async -> Bool {
logger.debug("Agent handling new data") logger.debug("Agent handling new data")
let data = Data(reader.availableData) let data = Data(reader.availableData)
guard data.count > 4 else { return false} guard data.count > 4 else { return false}

View File

@ -1,7 +1,7 @@
import Foundation import Foundation
/// Type eraser for Secret. /// Type eraser for Secret.
public struct AnySecret: Secret { public struct AnySecret: Secret, @unchecked Sendable {
let base: Any let base: Any
private let hashable: AnyHashable private let hashable: AnyHashable

View File

@ -1,7 +1,7 @@
import Foundation import Foundation
/// The base protocol for describing a Secret /// The base protocol for describing a Secret
public protocol Secret: Identifiable, Hashable { public protocol Secret: Identifiable, Hashable, Sendable {
/// A user-facing string identifying the Secret. /// A user-facing string identifying the Secret.
var name: String { get } var name: String { get }
@ -17,7 +17,7 @@ public protocol Secret: Identifiable, Hashable {
} }
/// The type of algorithm the Secret uses. Currently, only elliptic curve algorithms are supported. /// The type of algorithm the Secret uses. Currently, only elliptic curve algorithms are supported.
public enum Algorithm: Hashable { public enum Algorithm: Hashable, Sendable {
case ellipticCurve case ellipticCurve
case rsa case rsa

View File

@ -8,7 +8,7 @@ import SecretKit
extension SecureEnclave { extension SecureEnclave {
/// An implementation of Store backed by the Secure Enclave. /// An implementation of Store backed by the Secure Enclave.
public final class Store: SecretStoreModifiable { public final class Store: SecretStoreModifiable, Sendable {
public var isAvailable: Bool { public var isAvailable: Bool {
// For some reason, as of build time, CryptoKit.SecureEnclave.isAvailable always returns false // For some reason, as of build time, CryptoKit.SecureEnclave.isAvailable always returns false
@ -18,14 +18,18 @@ extension SecureEnclave {
} }
public let id = UUID() public let id = UUID()
public let name = String(localized: "secure_enclave") public let name = String(localized: "secure_enclave")
@Published public private(set) var secrets: [Secret] = [] @MainActor public private(set) var secrets: [Secret] = [] {
willSet {
self.objectWillChange.send()
}
}
private var persistedAuthenticationContexts: [Secret: PersistentAuthenticationContext] = [:] @MainActor private var persistedAuthenticationContexts: [Secret: PersistentAuthenticationContext] = [:]
/// Initializes a Store. /// Initializes a Store.
public init() { public init() {
DistributedNotificationCenter.default().addObserver(forName: .secretStoreUpdated, object: nil, queue: .main) { [reload = reloadSecretsInternal(notifyAgent:)] _ in DistributedNotificationCenter.default().addObserver(forName: .secretStoreUpdated, object: nil, queue: .main) { _ in
reload(false) self.reloadSecretsInternal(notifyAgent: false)
} }
loadSecrets() loadSecrets()
} }
@ -211,7 +215,7 @@ extension SecureEnclave.Store {
/// Reloads all secrets from the store. /// Reloads all secrets from the store.
/// - Parameter notifyAgent: A boolean indicating whether a distributed notification should be posted, notifying other processes (ie, the SecretAgent) to reload their stores as well. /// - Parameter notifyAgent: A boolean indicating whether a distributed notification should be posted, notifying other processes (ie, the SecretAgent) to reload their stores as well.
@Sendable private func reloadSecretsInternal(notifyAgent: Bool = true) { private func reloadSecretsInternal(notifyAgent: Bool = true) {
let before = secrets let before = secrets
secrets.removeAll() secrets.removeAll()
loadSecrets() loadSecrets()
@ -304,8 +308,8 @@ extension SecureEnclave.Store {
extension SecureEnclave { extension SecureEnclave {
enum Constants { enum Constants {
static let keyTag = "com.maxgoedjen.secretive.secureenclave.key".data(using: .utf8)! as CFData static let keyTag = "com.maxgoedjen.secretive.secureenclave.key".data(using: .utf8)!
static let keyType = kSecAttrKeyTypeECSECPrimeRandom static let keyType = kSecAttrKeyTypeECSECPrimeRandom as String
static let unauthenticatedThreshold: TimeInterval = 0.05 static let unauthenticatedThreshold: TimeInterval = 0.05
} }

View File

@ -20,13 +20,13 @@ extension SmartCard {
/// Initializes a Store. /// Initializes a Store.
public init() { public init() {
tokenID = watcher.nonSecureEnclaveTokens.first tokenID = watcher.nonSecureEnclaveTokens.first
watcher.setInsertionHandler { [reload = reloadSecretsInternal] string in watcher.setInsertionHandler { string in
guard self.tokenID == nil else { return } guard self.tokenID == nil else { return }
guard !string.contains("setoken") else { return } guard !string.contains("setoken") else { return }
self.tokenID = string self.tokenID = string
DispatchQueue.main.async { DispatchQueue.main.async {
reload() // reload()
} }
self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string) self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string)
} }
@ -117,7 +117,7 @@ extension SmartCard {
extension SmartCard.Store { extension SmartCard.Store {
@Sendable private func reloadSecretsInternal() { private func reloadSecretsInternal() {
self.isAvailable = self.tokenID != nil self.isAvailable = self.tokenID != nil
let before = self.secrets let before = self.secrets
self.secrets.removeAll() self.secrets.removeAll()

View File

@ -5,7 +5,7 @@ import SecretKit
import SecretAgentKit import SecretAgentKit
import Brief import Brief
class Notifier { final class Notifier: Sendable {
private let notificationDelegate = NotificationDelegate() private let notificationDelegate = NotificationDelegate()
@ -129,7 +129,8 @@ extension Notifier {
} }
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate { // FIXME: UNCHECKED SENDABLE
@MainActor final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate, @unchecked Sendable {
fileprivate var release: Release? fileprivate var release: Release?
fileprivate var ignore: ((Release) -> Void)? fileprivate var ignore: ((Release) -> Void)?
@ -138,7 +139,7 @@ class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
fileprivate var pendingPersistableStores: [String: AnySecretStore] = [:] fileprivate var pendingPersistableStores: [String: AnySecretStore] = [:]
fileprivate var pendingPersistableSecrets: [String: AnySecret] = [:] fileprivate var pendingPersistableSecrets: [String: AnySecret] = [:]
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) { nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {
} }

View File

@ -619,6 +619,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
}; };
name = Debug; name = Debug;
}; };
@ -678,6 +679,7 @@
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 6.0;
}; };
name = Release; name = Release;
}; };
@ -700,13 +702,12 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_STRICT_CONCURRENCY = complete; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -729,13 +730,12 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host"; PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host";
SWIFT_STRICT_CONCURRENCY = complete; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@ -755,7 +755,6 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretiveTests; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretiveTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive";
}; };
name = Debug; name = Debug;
@ -776,7 +775,6 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretiveTests; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretiveTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive";
}; };
name = Release; name = Release;
@ -844,6 +842,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
}; };
name = Test; name = Test;
}; };
@ -863,12 +862,11 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
}; };
name = Test; name = Test;
}; };
@ -889,7 +887,6 @@
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretiveTests; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretiveTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive";
}; };
name = Test; name = Test;
@ -908,12 +905,11 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
}; };
name = Test; name = Test;
}; };
@ -933,12 +929,11 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -959,13 +954,12 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent"; PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
SWIFT_STRICT_CONCURRENCY = complete; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };

View File

@ -2,6 +2,7 @@
"sourceLanguage" : "en", "sourceLanguage" : "en",
"strings" : { "strings" : {
"agent_not_running_notice_title" : { "agent_not_running_notice_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -48,6 +49,7 @@
} }
}, },
"agent_running_notice_detail_description" : { "agent_running_notice_detail_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -94,6 +96,7 @@
} }
}, },
"agent_running_notice_detail_title" : { "agent_running_notice_detail_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -140,6 +143,7 @@
} }
}, },
"agent_running_notice_title" : { "agent_running_notice_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -186,6 +190,7 @@
} }
}, },
"agent_setup_notice_title" : { "agent_setup_notice_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -232,6 +237,7 @@
} }
}, },
"app_menu_help_button" : { "app_menu_help_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -278,6 +284,7 @@
} }
}, },
"app_menu_new_secret_button" : { "app_menu_new_secret_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -324,6 +331,7 @@
} }
}, },
"app_menu_setup_button" : { "app_menu_setup_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -370,6 +378,7 @@
} }
}, },
"app_not_in_applications_notice_detail_description" : { "app_not_in_applications_notice_detail_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -416,6 +425,7 @@
} }
}, },
"app_not_in_applications_notice_title" : { "app_not_in_applications_notice_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -798,6 +808,7 @@
} }
}, },
"copyable_click_to_copy_button" : { "copyable_click_to_copy_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -844,6 +855,7 @@
} }
}, },
"copyable_copied" : { "copyable_copied" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -890,6 +902,7 @@
} }
}, },
"create_secret_cancel_button" : { "create_secret_cancel_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -936,6 +949,7 @@
} }
}, },
"create_secret_create_button" : { "create_secret_create_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -982,6 +996,7 @@
} }
}, },
"create_secret_name_label" : { "create_secret_name_label" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1028,6 +1043,7 @@
} }
}, },
"create_secret_name_placeholder" : { "create_secret_name_placeholder" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1074,6 +1090,7 @@
} }
}, },
"create_secret_notify_description" : { "create_secret_notify_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1120,6 +1137,7 @@
} }
}, },
"create_secret_notify_title" : { "create_secret_notify_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1166,6 +1184,7 @@
} }
}, },
"create_secret_require_authentication_description" : { "create_secret_require_authentication_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1212,6 +1231,7 @@
} }
}, },
"create_secret_require_authentication_title" : { "create_secret_require_authentication_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1258,6 +1278,7 @@
} }
}, },
"create_secret_title" : { "create_secret_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1304,6 +1325,7 @@
} }
}, },
"delete_confirmation_cancel_button" : { "delete_confirmation_cancel_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1350,6 +1372,7 @@
} }
}, },
"delete_confirmation_confirm_name_label" : { "delete_confirmation_confirm_name_label" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1396,6 +1419,7 @@
} }
}, },
"delete_confirmation_delete_button" : { "delete_confirmation_delete_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1442,6 +1466,7 @@
} }
}, },
"delete_confirmation_description_%@_%@" : { "delete_confirmation_description_%@_%@" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1488,6 +1513,7 @@
} }
}, },
"delete_confirmation_title_%@" : { "delete_confirmation_title_%@" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1534,6 +1560,7 @@
} }
}, },
"empty_store_modifiable_click_here_description" : { "empty_store_modifiable_click_here_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1580,6 +1607,7 @@
} }
}, },
"empty_store_modifiable_click_here_title" : { "empty_store_modifiable_click_here_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1626,6 +1654,7 @@
} }
}, },
"empty_store_modifiable_title" : { "empty_store_modifiable_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1672,6 +1701,7 @@
} }
}, },
"empty_store_nonmodifiable_description" : { "empty_store_nonmodifiable_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1718,6 +1748,7 @@
} }
}, },
"empty_store_nonmodifiable_supported_key_types" : { "empty_store_nonmodifiable_supported_key_types" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1764,6 +1795,7 @@
} }
}, },
"empty_store_nonmodifiable_title" : { "empty_store_nonmodifiable_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1810,6 +1842,7 @@
} }
}, },
"no_secure_storage_description" : { "no_secure_storage_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1856,6 +1889,7 @@
} }
}, },
"no_secure_storage_title" : { "no_secure_storage_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -1902,6 +1936,7 @@
} }
}, },
"no_secure_storage_yubico_link" : { "no_secure_storage_yubico_link" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2044,6 +2079,7 @@
} }
}, },
"rename_cancel_button" : { "rename_cancel_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2084,6 +2120,7 @@
} }
}, },
"rename_rename_button" : { "rename_rename_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2124,6 +2161,7 @@
} }
}, },
"rename_title_%@" : { "rename_title_%@" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2164,6 +2202,7 @@
} }
}, },
"secret_detail_md5_fingerprint_label" : { "secret_detail_md5_fingerprint_label" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2204,6 +2243,7 @@
} }
}, },
"secret_detail_public_key_label" : { "secret_detail_public_key_label" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2244,6 +2284,7 @@
} }
}, },
"secret_detail_public_key_path_label" : { "secret_detail_public_key_path_label" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2284,6 +2325,7 @@
} }
}, },
"secret_detail_sha256_fingerprint_label" : { "secret_detail_sha256_fingerprint_label" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2324,6 +2366,7 @@
} }
}, },
"secret_list_delete_button" : { "secret_list_delete_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2364,6 +2407,7 @@
} }
}, },
"secret_list_rename_button" : { "secret_list_rename_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2445,6 +2489,7 @@
} }
}, },
"setup_agent_activity_monitor_description" : { "setup_agent_activity_monitor_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2485,6 +2530,7 @@
} }
}, },
"setup_agent_description" : { "setup_agent_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2525,6 +2571,7 @@
} }
}, },
"setup_agent_install_button" : { "setup_agent_install_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2565,6 +2612,7 @@
} }
}, },
"setup_agent_title" : { "setup_agent_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2605,6 +2653,7 @@
} }
}, },
"setup_ssh_add_for_me_button" : { "setup_ssh_add_for_me_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2645,6 +2694,7 @@
} }
}, },
"setup_ssh_add_to_config_button_%@" : { "setup_ssh_add_to_config_button_%@" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2685,6 +2735,7 @@
} }
}, },
"setup_ssh_added_manually_button" : { "setup_ssh_added_manually_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2725,6 +2776,7 @@
} }
}, },
"setup_ssh_description" : { "setup_ssh_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2765,6 +2817,7 @@
} }
}, },
"setup_ssh_title" : { "setup_ssh_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2805,6 +2858,7 @@
} }
}, },
"setup_step_complete_symbol" : { "setup_step_complete_symbol" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2845,6 +2899,7 @@
} }
}, },
"setup_third_party_faq_link" : { "setup_third_party_faq_link" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2885,6 +2940,7 @@
} }
}, },
"setup_updates_description" : { "setup_updates_description" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2925,6 +2981,7 @@
} }
}, },
"setup_updates_ok" : { "setup_updates_ok" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -2965,6 +3022,7 @@
} }
}, },
"setup_updates_readmore" : { "setup_updates_readmore" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -3005,6 +3063,7 @@
} }
}, },
"setup_updates_title" : { "setup_updates_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -3211,6 +3270,7 @@
} }
}, },
"update_critical_notice_title" : { "update_critical_notice_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -3251,6 +3311,7 @@
} }
}, },
"update_ignore_button" : { "update_ignore_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -3291,6 +3352,7 @@
} }
}, },
"update_normal_notice_title" : { "update_normal_notice_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -3541,6 +3603,7 @@
} }
}, },
"update_release_notes_title" : { "update_release_notes_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -3581,6 +3644,7 @@
} }
}, },
"update_test_notice_title" : { "update_test_notice_title" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -3621,6 +3685,7 @@
} }
}, },
"update_update_button" : { "update_update_button" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@ -3661,6 +3726,7 @@
} }
}, },
"update_version_name_%@" : { "update_version_name_%@" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {