From 8417c563645f51a18f4a3edf18444b9b55dde549 Mon Sep 17 00:00:00 2001 From: Rod Christiansen Date: Sun, 14 Jun 2026 22:52:57 -0700 Subject: [PATCH 1/2] Rename MDM detector and references to Management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unify the management-detection naming with the Windows side (which uses the same concept) under a single 'Management' vocabulary: - MDMDetector -> ManagementDetector (file + type) - isManagedByMDM(key:) / isMDMManaged(_:) -> isManaged - mdmPreferenceDomains -> managementPreferenceDomains - loadFromMDMOrLocal() -> loadFromManagementOrLocal() - mdmTimeout -> managementTimeout - 'MDM' in comments, log lines, and the GUI lock label -> 'Management'/'Managed' Pure rename — no behavioural change. External preference domains (com.github.bootstrapmate, io.macadmins.installapplications) are unchanged. swift build and swift test pass. --- .../app/ViewModels/SettingsViewModel.swift | 16 ++++---- Sources/app/Views/SettingsView.swift | 6 +-- Sources/cli/bootstrapmate.swift | 38 +++++++++---------- Sources/core/Managers/ConfigManager.swift | 28 +++++++------- Sources/core/Managers/ManifestManager.swift | 4 +- Sources/core/Managers/StatusManager.swift | 2 +- ...etector.swift => ManagementDetector.swift} | 16 ++++---- 7 files changed, 55 insertions(+), 55 deletions(-) rename Sources/core/Utilities/{MDMDetector.swift => ManagementDetector.swift} (86%) diff --git a/Sources/app/ViewModels/SettingsViewModel.swift b/Sources/app/ViewModels/SettingsViewModel.swift index b3a779d..79775f3 100644 --- a/Sources/app/ViewModels/SettingsViewModel.swift +++ b/Sources/app/ViewModels/SettingsViewModel.swift @@ -3,7 +3,7 @@ // BootstrapMate // // Manages all configuration settings for the GUI. -// Reads current values from ConfigManager and MDM status from MDMDetector. +// Reads current values from ConfigManager and management status from ManagementDetector. // Writes changes via XPC helper for system-level persistence. // @@ -127,11 +127,11 @@ final class SettingsViewModel { manifestPreviewState = .idle } - // MARK: - MDM Status + // MARK: - management Status private(set) var managedKeys: Set = [] - func isMDMManaged(_ key: String) -> Bool { + func isManaged(_ key: String) -> Bool { managedKeys.contains(key) } @@ -143,7 +143,7 @@ final class SettingsViewModel { ConfigManager.shared.reloadPreferences() let config = ConfigManager.shared.config - let detector = MDMDetector.shared + let detector = ManagementDetector.shared managedKeys = detector.allManagedKeys() @@ -169,10 +169,10 @@ final class SettingsViewModel { // MARK: - Saving - /// Saves all non-MDM-managed settings via the XPC helper. + /// Saves all non-management-managed settings via the XPC helper. func save(using client: XPCClient) { func saveString(_ key: String, _ value: String) { - guard !isMDMManaged(key) else { return } + guard !isManaged(key) else { return } if value.isEmpty { client.removePreference(key: key) } else { @@ -181,12 +181,12 @@ final class SettingsViewModel { } func saveBool(_ key: String, _ value: Bool) { - guard !isMDMManaged(key) else { return } + guard !isManaged(key) else { return } client.setBoolPreference(key: key, value: value) } func saveInt(_ key: String, _ value: Int) { - guard !isMDMManaged(key) else { return } + guard !isManaged(key) else { return } client.setIntPreference(key: key, value: value) } diff --git a/Sources/app/Views/SettingsView.swift b/Sources/app/Views/SettingsView.swift index bbd6a7e..71bf11d 100644 --- a/Sources/app/Views/SettingsView.swift +++ b/Sources/app/Views/SettingsView.swift @@ -3,7 +3,7 @@ // BootstrapMate // // Main tab with centered app info header and settings below. -// MDM-managed settings display a lock icon and are disabled. +// management-managed settings display a lock icon and are disabled. // import SwiftUI @@ -308,7 +308,7 @@ struct SettingsView: View { label: String? = nil, @ViewBuilder content: () -> Content ) -> some View { - let managed = viewModel.isMDMManaged(key) + let managed = viewModel.isManaged(key) VStack(alignment: .leading, spacing: 2) { if let label { Text(label) @@ -318,7 +318,7 @@ struct SettingsView: View { content() .disabled(managed) if managed { - Label("Managed by MDM", systemImage: "lock.fill") + Label("Managed", systemImage: "lock.fill") .font(.caption) .foregroundStyle(.secondary) } diff --git a/Sources/cli/bootstrapmate.swift b/Sources/cli/bootstrapmate.swift index 74a9920..751194a 100644 --- a/Sources/cli/bootstrapmate.swift +++ b/Sources/cli/bootstrapmate.swift @@ -3,7 +3,7 @@ // BootstrapMate // // CLI entry point for BootstrapMate - Swift deployment utility for ADE-driven macOS setup. -// Supports MDM managed preferences, CLI arguments, and graceful fallbacks. +// Supports managed preferences, CLI arguments, and graceful fallbacks. // import Foundation @@ -183,39 +183,39 @@ struct BootstrapMate: ParsableCommand { } } - // Wait for MDM configuration profile to be applied (during Setup Assistant) - // The MDM profile with url preference may not be applied immediately at boot - let mdmTimeout = 300 // 5 minutes + // Wait for management configuration profile to be applied (during Setup Assistant) + // The management profile with url preference may not be applied immediately at boot + let managementTimeout = 300 // 5 minutes if jsonurl == nil || jsonurl!.isEmpty { - // No CLI URL provided, we need MDM config + // No CLI URL provided, we need management config if !ConfigManager.shared.isValid() { - Logger.info("Waiting for MDM configuration profile (timeout: \(mdmTimeout)s)...") - earlyLog("Waiting for MDM config...") + Logger.info("Waiting for management configuration profile (timeout: \(managementTimeout)s)...") + earlyLog("Waiting for management config...") - for i in 1...mdmTimeout { - // Reload preferences from MDM domains + for i in 1...managementTimeout { + // Reload preferences from management domains if ConfigManager.shared.reloadManagedPreferences() { - Logger.success("MDM configuration received after \(i)s") - earlyLog("MDM config received after \(i)s") + Logger.success("management configuration received after \(i)s") + earlyLog("management config received after \(i)s") break } // Log progress every 30 seconds if i % 30 == 0 { - Logger.debug("Still waiting for MDM config... (\(i)s elapsed)") + Logger.debug("Still waiting for management config... (\(i)s elapsed)") } Thread.sleep(forTimeInterval: 1) - if i == mdmTimeout { - Logger.warning("MDM configuration not received within \(mdmTimeout)s") - earlyLog("MDM config timeout after \(mdmTimeout)s") + if i == managementTimeout { + Logger.warning("management configuration not received within \(managementTimeout)s") + earlyLog("management config timeout after \(managementTimeout)s") } } } } - // Apply CLI arguments to ConfigManager (overrides MDM settings) + // Apply CLI arguments to ConfigManager (overrides management settings) ConfigManager.shared.applyCliArguments( jsonUrl: jsonurl, headers: headers, @@ -256,15 +256,15 @@ struct BootstrapMate: ParsableCommand { } } else { // No URL provided - check if we have embedded config or should fail - Logger.warning("No JSON URL provided via CLI or MDM") + Logger.warning("No JSON URL provided via CLI or management") // Try to fetch from ConfigManager's external config if ConfigManager.shared.fetchExternalConfig() { - Logger.info("Loaded external config from MDM preferences") + Logger.info("Loaded external config from management preferences") // Convert BootstrapConfig to manifest loading // For now, we require jsonUrl } else { - Logger.error("No manifest URL configured. Use --jsonurl or configure via MDM profile.") + Logger.error("No manifest URL configured. Use --jsonurl or configure via management profile.") Foundation.exit(1) } } diff --git a/Sources/core/Managers/ConfigManager.swift b/Sources/core/Managers/ConfigManager.swift index afce143..e26986d 100644 --- a/Sources/core/Managers/ConfigManager.swift +++ b/Sources/core/Managers/ConfigManager.swift @@ -4,7 +4,7 @@ // // Configuration loader with fallback chain: // 1. CLI arguments (highest priority) -// 2. MDM managed preferences +// 2. managed preferences // 3. Embedded/default values (lowest priority) // @@ -73,9 +73,9 @@ public struct BootstrapMateConfig { public final class ConfigManager { nonisolated(unsafe) public static let shared = ConfigManager() - // MDM preference domains to check (in order of priority) - private let mdmPreferenceDomains = [ - "com.github.bootstrapmate", // Primary BootstrapMate domain (MDM profile) + // management preference domains to check (in order of priority) + private let managementPreferenceDomains = [ + "com.github.bootstrapmate", // Primary BootstrapMate domain (management profile) "io.macadmins.installapplications" // InstallApplications compatibility ] @@ -92,13 +92,13 @@ public final class ConfigManager { // Start with defaults self.config = BootstrapMateConfig() - // Load MDM managed preferences as baseline + // Load managed preferences as baseline loadManagedPreferences() } // MARK: - Public API - /// Apply CLI arguments (highest priority - overrides MDM settings) + /// Apply CLI arguments (highest priority - overrides management settings) public func applyCliArguments( jsonUrl: String? = nil, headers: String? = nil, @@ -166,14 +166,14 @@ public final class ConfigManager { return config.jsonUrl != nil && !config.jsonUrl!.isEmpty } - /// Reload MDM managed preferences (call when waiting for MDM profile to be applied) + /// Reload managed preferences (call when waiting for management profile to be applied) /// Returns true if a valid JSON URL was found public func reloadManagedPreferences() -> Bool { // Clear existing URL to force re-read config.jsonUrl = nil // Try each domain in priority order - for domain in mdmPreferenceDomains { + for domain in managementPreferenceDomains { if loadPreferencesFromDomain(domain) { if config.jsonUrl != nil && !config.jsonUrl!.isEmpty { Logger.info("Loaded managed preferences from: \(domain)") @@ -182,7 +182,7 @@ public final class ConfigManager { } } - // Also check for managed preferences via MDM profile + // Also check for managed preferences via management profile loadFromManagedAppConfig() return config.jsonUrl != nil && !config.jsonUrl!.isEmpty @@ -248,21 +248,21 @@ public final class ConfigManager { Logger.debug("Loading managed preferences...") // Try each domain in priority order - for domain in mdmPreferenceDomains { + for domain in managementPreferenceDomains { if loadPreferencesFromDomain(domain) { Logger.info("Loaded managed preferences from: \(domain)") return } } - // Also check for managed preferences via MDM profile + // Also check for managed preferences via management profile loadFromManagedAppConfig() - Logger.debug("No MDM managed preferences found, using defaults") + Logger.debug("No managed preferences found, using defaults") } private func loadPreferencesFromDomain(_ domain: String) -> Bool { - // First try CFPreferences (works better for MDM-pushed preferences) + // First try CFPreferences (works better for management-pushed preferences) let cfDomain = domain as CFString // Check for URL key (various names) @@ -381,7 +381,7 @@ public final class ConfigManager { } private func loadFromManagedAppConfig() { - // Check for MDM-deployed configuration profile + // Check for management-deployed configuration profile // This handles the case where config is delivered via custom configuration profile let configDomains = [ "com.github.bootstrapmate" diff --git a/Sources/core/Managers/ManifestManager.swift b/Sources/core/Managers/ManifestManager.swift index 43b3aa2..a576a22 100644 --- a/Sources/core/Managers/ManifestManager.swift +++ b/Sources/core/Managers/ManifestManager.swift @@ -45,8 +45,8 @@ public final class ManifestManager { dryRun = enable } - public func loadFromMDMOrLocal() { - Logger.log("Loading manifest from MDM or local fallback.") + public func loadFromManagementOrLocal() { + Logger.log("Loading manifest from management or local fallback.") // TODO: Implement managed preferences fallback handling. } diff --git a/Sources/core/Managers/StatusManager.swift b/Sources/core/Managers/StatusManager.swift index 4374f2e..f60f896 100644 --- a/Sources/core/Managers/StatusManager.swift +++ b/Sources/core/Managers/StatusManager.swift @@ -2,7 +2,7 @@ // StatusManager.swift // BootstrapMate // -// Tracks installation status via plist and JSON for MDM detection. +// Tracks installation status via plist and JSON for management detection. // macOS equivalent of Windows StatusManager.cs using property lists. // diff --git a/Sources/core/Utilities/MDMDetector.swift b/Sources/core/Utilities/ManagementDetector.swift similarity index 86% rename from Sources/core/Utilities/MDMDetector.swift rename to Sources/core/Utilities/ManagementDetector.swift index 87e8607..1b4e1d5 100644 --- a/Sources/core/Utilities/MDMDetector.swift +++ b/Sources/core/Utilities/ManagementDetector.swift @@ -1,18 +1,18 @@ // -// MDMDetector.swift +// ManagementDetector.swift // BootstrapMate // -// Detects which preferences are managed by MDM configuration profiles +// Detects which preferences are managed by configuration profiles // vs. set locally. Used by the GUI to show lock indicators. // import Foundation -public final class MDMDetector: Sendable { +public final class ManagementDetector: Sendable { - public static let shared = MDMDetector() + public static let shared = ManagementDetector() - /// Preference domains checked for MDM management, in priority order. + /// Preference domains checked for management, in priority order. private let managedDomains = [ "com.github.bootstrapmate", "io.macadmins.installapplications" @@ -36,7 +36,7 @@ public final class MDMDetector: Sendable { // MARK: - Public API /// Returns true if the canonical key is present in any managed preferences plist. - public func isManagedByMDM(key: String) -> Bool { + public func isManaged(key: String) -> Bool { let keysToCheck = Self.keyAliases[key] ?? [key] for domain in managedDomains { for plistPath in managedPlistPaths(for: domain) { @@ -49,7 +49,7 @@ public final class MDMDetector: Sendable { return false } - /// Returns the MDM-managed value for a canonical key, or nil. + /// Returns the management-managed value for a canonical key, or nil. public func managedValue(forKey key: String) -> Any? { let keysToCheck = Self.keyAliases[key] ?? [key] for domain in managedDomains { @@ -63,7 +63,7 @@ public final class MDMDetector: Sendable { return nil } - /// Returns the set of canonical keys that are MDM-managed. + /// Returns the set of canonical keys that are management-managed. public func allManagedKeys() -> Set { var result = Set() for (canonical, aliases) in Self.keyAliases { From c84ef5563cc758bdbdf355a3d693e086be307ca0 Mon Sep 17 00:00:00 2001 From: Rod Christiansen Date: Mon, 15 Jun 2026 16:49:36 -0700 Subject: [PATCH 2/2] Address review: tidy management wording - Replace the awkward 'management-managed' / 'non-management-managed' phrasing with 'managed' / 'non-managed' in doc and header comments. - Title-case the SettingsViewModel MARK section. - Sentence-case user-facing CLI log lines that begin with 'management', and clarify 'via CLI or managed preferences'. --- Sources/app/ViewModels/SettingsViewModel.swift | 4 ++-- Sources/app/Views/SettingsView.swift | 2 +- Sources/cli/bootstrapmate.swift | 10 +++++----- Sources/core/Utilities/ManagementDetector.swift | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/app/ViewModels/SettingsViewModel.swift b/Sources/app/ViewModels/SettingsViewModel.swift index 79775f3..487b73e 100644 --- a/Sources/app/ViewModels/SettingsViewModel.swift +++ b/Sources/app/ViewModels/SettingsViewModel.swift @@ -127,7 +127,7 @@ final class SettingsViewModel { manifestPreviewState = .idle } - // MARK: - management Status + // MARK: - Management private(set) var managedKeys: Set = [] @@ -169,7 +169,7 @@ final class SettingsViewModel { // MARK: - Saving - /// Saves all non-management-managed settings via the XPC helper. + /// Saves all non-managed settings via the XPC helper. func save(using client: XPCClient) { func saveString(_ key: String, _ value: String) { guard !isManaged(key) else { return } diff --git a/Sources/app/Views/SettingsView.swift b/Sources/app/Views/SettingsView.swift index 71bf11d..77fd08c 100644 --- a/Sources/app/Views/SettingsView.swift +++ b/Sources/app/Views/SettingsView.swift @@ -3,7 +3,7 @@ // BootstrapMate // // Main tab with centered app info header and settings below. -// management-managed settings display a lock icon and are disabled. +// Managed settings display a lock icon and are disabled. // import SwiftUI diff --git a/Sources/cli/bootstrapmate.swift b/Sources/cli/bootstrapmate.swift index 751194a..ae6e876 100644 --- a/Sources/cli/bootstrapmate.swift +++ b/Sources/cli/bootstrapmate.swift @@ -195,8 +195,8 @@ struct BootstrapMate: ParsableCommand { for i in 1...managementTimeout { // Reload preferences from management domains if ConfigManager.shared.reloadManagedPreferences() { - Logger.success("management configuration received after \(i)s") - earlyLog("management config received after \(i)s") + Logger.success("Management configuration received after \(i)s") + earlyLog("Management config received after \(i)s") break } @@ -208,8 +208,8 @@ struct BootstrapMate: ParsableCommand { Thread.sleep(forTimeInterval: 1) if i == managementTimeout { - Logger.warning("management configuration not received within \(managementTimeout)s") - earlyLog("management config timeout after \(managementTimeout)s") + Logger.warning("Management configuration not received within \(managementTimeout)s") + earlyLog("Management config timeout after \(managementTimeout)s") } } } @@ -256,7 +256,7 @@ struct BootstrapMate: ParsableCommand { } } else { // No URL provided - check if we have embedded config or should fail - Logger.warning("No JSON URL provided via CLI or management") + Logger.warning("No JSON URL provided via CLI or managed preferences") // Try to fetch from ConfigManager's external config if ConfigManager.shared.fetchExternalConfig() { diff --git a/Sources/core/Utilities/ManagementDetector.swift b/Sources/core/Utilities/ManagementDetector.swift index 1b4e1d5..d270fc9 100644 --- a/Sources/core/Utilities/ManagementDetector.swift +++ b/Sources/core/Utilities/ManagementDetector.swift @@ -49,7 +49,7 @@ public final class ManagementDetector: Sendable { return false } - /// Returns the management-managed value for a canonical key, or nil. + /// Returns the managed value for a canonical key, or nil. public func managedValue(forKey key: String) -> Any? { let keysToCheck = Self.keyAliases[key] ?? [key] for domain in managedDomains { @@ -63,7 +63,7 @@ public final class ManagementDetector: Sendable { return nil } - /// Returns the set of canonical keys that are management-managed. + /// Returns the set of canonical keys that are managed. public func allManagedKeys() -> Set { var result = Set() for (canonical, aliases) in Self.keyAliases {