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.
import PackageDescription
let secretiveDefaults: [PackageDescription.SwiftSetting]? = [.swiftLanguageMode(.v6), .unsafeFlags(["-warnings-as-errors"])]
let package = Package(
name: "SecretivePackages",
platforms: [
.macOS(.v12)
.macOS(.v13)
],
products: [
.library(
@ -34,27 +36,27 @@ let package = Package(
.target(
name: "SecretKit",
dependencies: [],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])]
swiftSettings: secretiveDefaults
),
.testTarget(
name: "SecretKitTests",
dependencies: ["SecretKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])]
swiftSettings: secretiveDefaults
),
.target(
name: "SecureEnclaveSecretKit",
dependencies: ["SecretKit"],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])]
swiftSettings: secretiveDefaults
),
.target(
name: "SmartCardSecretKit",
dependencies: ["SecretKit"],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])]
swiftSettings: secretiveDefaults
),
.target(
name: "SecretAgentKit",
dependencies: ["SecretKit", "SecretAgentKitHeaders"],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])]
swiftSettings: secretiveDefaults
),
.systemLibrary(
name: "SecretAgentKitHeaders"
@ -73,3 +75,4 @@ let package = Package(
),
]
)

View File

@ -1,7 +1,7 @@
import Foundation
/// 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"
public let name: String

View File

@ -2,7 +2,7 @@ import Foundation
import Combine
/// 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?
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.
/// - osVersion: The current OS version.
/// - 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.currentVersion = currentVersion
testBuild = currentVersion == SemVer("0.0.0")
if checkOnLaunch {
// Don't do a launch check if the user hasn't seen the setup prompt explaining updater yet.
checkForUpdates()
Task {
if checkOnLaunch {
// 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.
public func checkForUpdates() {
URLSession.shared.dataTask(with: Constants.updateURL) { data, _, _ in
guard let data = data else { return }
guard let releases = try? JSONDecoder().decode([Release].self, from: data) else { return }
self.evaluate(releases: releases)
}.resume()
public func checkForUpdates() async {
guard let (data, _) = try? await URLSession.shared.data(from: Constants.updateURL) else { return }
guard let releases = try? JSONDecoder().decode([Release].self, from: data) else { return }
evaluate(releases: releases)
}
/// 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 {
/// 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)
var testBuild: Bool { get }

View File

@ -5,7 +5,7 @@ import SecretKit
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.
public final class Agent {
public actor Agent {
private let storeList: SecretStoreList
private let witness: SigningWitness?
@ -35,7 +35,7 @@ extension Agent {
/// - writer: A ``FileHandleWriter`` to write the response to.
/// - Return value:
/// - 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")
let data = Data(reader.availableData)
guard data.count > 4 else { return false}

View File

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

View File

@ -1,7 +1,7 @@
import Foundation
/// 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.
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.
public enum Algorithm: Hashable {
public enum Algorithm: Hashable, Sendable {
case ellipticCurve
case rsa

View File

@ -8,7 +8,7 @@ import SecretKit
extension SecureEnclave {
/// An implementation of Store backed by the Secure Enclave.
public final class Store: SecretStoreModifiable {
public final class Store: SecretStoreModifiable, Sendable {
public var isAvailable: Bool {
// 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 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.
public init() {
DistributedNotificationCenter.default().addObserver(forName: .secretStoreUpdated, object: nil, queue: .main) { [reload = reloadSecretsInternal(notifyAgent:)] _ in
reload(false)
DistributedNotificationCenter.default().addObserver(forName: .secretStoreUpdated, object: nil, queue: .main) { _ in
self.reloadSecretsInternal(notifyAgent: false)
}
loadSecrets()
}
@ -211,7 +215,7 @@ extension SecureEnclave.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.
@Sendable private func reloadSecretsInternal(notifyAgent: Bool = true) {
private func reloadSecretsInternal(notifyAgent: Bool = true) {
let before = secrets
secrets.removeAll()
loadSecrets()
@ -304,8 +308,8 @@ extension SecureEnclave.Store {
extension SecureEnclave {
enum Constants {
static let keyTag = "com.maxgoedjen.secretive.secureenclave.key".data(using: .utf8)! as CFData
static let keyType = kSecAttrKeyTypeECSECPrimeRandom
static let keyTag = "com.maxgoedjen.secretive.secureenclave.key".data(using: .utf8)!
static let keyType = kSecAttrKeyTypeECSECPrimeRandom as String
static let unauthenticatedThreshold: TimeInterval = 0.05
}

View File

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

View File

@ -5,7 +5,7 @@ import SecretKit
import SecretAgentKit
import Brief
class Notifier {
final class Notifier: Sendable {
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 ignore: ((Release) -> Void)?
@ -138,7 +139,7 @@ class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
fileprivate var pendingPersistableStores: [String: AnySecretStore] = [:]
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_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
};
name = Debug;
};
@ -678,6 +679,7 @@
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 6.0;
};
name = Release;
};
@ -700,13 +702,12 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@ -729,13 +730,12 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
};
name = Release;
};
@ -755,7 +755,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretiveTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive";
};
name = Debug;
@ -776,7 +775,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretiveTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive";
};
name = Release;
@ -844,6 +842,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
};
name = Test;
};
@ -863,12 +862,11 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
};
name = Test;
};
@ -889,7 +887,6 @@
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretiveTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive";
};
name = Test;
@ -908,12 +905,11 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
};
name = Test;
};
@ -933,12 +929,11 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@ -959,13 +954,12 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
};
name = Release;
};

View File

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