Skip to content

Commit 830d147

Browse files
feat: support disabling the built-in updater (#219)
Closes #182. UserDefaults can be forcibly set by MDM admins. When the `disableUpdater` bool in UserDefaults is set to `false`, the updater won't be initialized on launch, and the UI elements for the updater in settings will be hidden. Related to #220.
1 parent de4b0e5 commit 830d147

File tree

4 files changed

+56
-32
lines changed

4 files changed

+56
-32
lines changed

Coder-Desktop/Coder-Desktop/UpdaterService.swift

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,59 @@ import Sparkle
22
import SwiftUI
33

44
final class UpdaterService: NSObject, ObservableObject {
5-
private lazy var inner: SPUStandardUpdaterController = .init(
6-
startingUpdater: true,
7-
updaterDelegate: self,
8-
userDriverDelegate: self
9-
)
10-
private var updater: SPUUpdater!
5+
// The auto-updater can be entirely disabled by setting the
6+
// `disableUpdater` UserDefaults key to `true`. This is designed for use in
7+
// MDM configurations, where the value can be set to `true` permanently.
8+
let disabled: Bool = UserDefaults.standard.bool(forKey: Keys.disableUpdater)
9+
1110
@Published var canCheckForUpdates = true
1211

1312
@Published var autoCheckForUpdates: Bool! {
1413
didSet {
1514
if let autoCheckForUpdates, autoCheckForUpdates != oldValue {
16-
updater.automaticallyChecksForUpdates = autoCheckForUpdates
15+
inner?.updater.automaticallyChecksForUpdates = autoCheckForUpdates
1716
}
1817
}
1918
}
2019

2120
@Published var updateChannel: UpdateChannel {
2221
didSet {
23-
UserDefaults.standard.set(updateChannel.rawValue, forKey: Self.updateChannelKey)
22+
UserDefaults.standard.set(updateChannel.rawValue, forKey: Keys.updateChannel)
2423
}
2524
}
2625

27-
static let updateChannelKey = "updateChannel"
26+
private var inner: (controller: SPUStandardUpdaterController, updater: SPUUpdater)?
2827

2928
override init() {
30-
updateChannel = UserDefaults.standard.string(forKey: Self.updateChannelKey)
29+
updateChannel = UserDefaults.standard.string(forKey: Keys.updateChannel)
3130
.flatMap { UpdateChannel(rawValue: $0) } ?? .stable
3231
super.init()
33-
updater = inner.updater
32+
33+
guard !disabled else {
34+
return
35+
}
36+
37+
let inner = SPUStandardUpdaterController(
38+
startingUpdater: true,
39+
updaterDelegate: self,
40+
userDriverDelegate: self
41+
)
42+
43+
let updater = inner.updater
44+
self.inner = (inner, updater)
45+
3446
autoCheckForUpdates = updater.automaticallyChecksForUpdates
3547
updater.publisher(for: \.canCheckForUpdates).assign(to: &$canCheckForUpdates)
3648
}
3749

3850
func checkForUpdates() {
39-
guard canCheckForUpdates else { return }
40-
updater.checkForUpdates()
51+
guard let inner, canCheckForUpdates else { return }
52+
inner.updater.checkForUpdates()
53+
}
54+
55+
enum Keys {
56+
static let disableUpdater = "disableUpdater"
57+
static let updateChannel = "updateChannel"
4158
}
4259
}
4360

Coder-Desktop/Coder-Desktop/VPN/VPNService.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ final class CoderVPNService: NSObject, VPNService {
5656
var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "vpn")
5757
lazy var xpc: HelperXPCClient = .init(vpn: self)
5858

59-
@Published var tunnelState: VPNServiceState = .disabled {
59+
@Published private(set) var tunnelState: VPNServiceState = .disabled {
6060
didSet {
6161
if tunnelState == .connecting {
6262
progress = .init(stage: .initial, downloadProgress: nil)
@@ -80,9 +80,9 @@ final class CoderVPNService: NSObject, VPNService {
8080
return tunnelState
8181
}
8282

83-
@Published var progress: VPNProgress = .init(stage: .initial, downloadProgress: nil)
83+
@Published private(set) var progress: VPNProgress = .init(stage: .initial, downloadProgress: nil)
8484

85-
@Published var menuState: VPNMenuState = .init()
85+
@Published private(set) var menuState: VPNMenuState = .init()
8686

8787
// Whether the VPN should start as soon as possible
8888
var startWhenReady: Bool = false

Coder-Desktop/Coder-Desktop/Views/FileSync/FilePicker.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ struct FilePicker: View {
6969

7070
@MainActor
7171
class FilePickerModel: ObservableObject {
72-
@Published var rootEntries: [FilePickerEntryModel] = []
73-
@Published var rootIsLoading: Bool = false
74-
@Published var error: SDKError?
72+
@Published private(set) var rootEntries: [FilePickerEntryModel] = []
73+
@Published private(set) var rootIsLoading: Bool = false
74+
@Published private(set) var error: SDKError?
7575

7676
// It's important that `AgentClient` is a reference type (class)
7777
// as we were having performance issues with a struct (unless it was a binding).
@@ -153,9 +153,9 @@ class FilePickerEntryModel: Identifiable, Hashable, ObservableObject {
153153

154154
let client: AgentClient
155155

156-
@Published var entries: [FilePickerEntryModel]?
157-
@Published var isLoading = false
158-
@Published var error: SDKError?
156+
@Published private(set) var entries: [FilePickerEntryModel]?
157+
@Published private(set) var isLoading = false
158+
@Published private(set) var error: SDKError?
159159
@Published private var innerIsExpanded = false
160160
var isExpanded: Bool {
161161
get { innerIsExpanded }

Coder-Desktop/Coder-Desktop/Views/Settings/GeneralTab.swift

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,25 @@ struct GeneralTab: View {
1919
Text("Start Coder Connect on launch")
2020
}
2121
}
22-
Section {
23-
Toggle(isOn: $updater.autoCheckForUpdates) {
24-
Text("Automatically check for updates")
25-
}
26-
Picker("Update channel", selection: $updater.updateChannel) {
27-
ForEach(UpdateChannel.allCases) { channel in
28-
Text(channel.name).tag(channel)
22+
if !updater.disabled {
23+
Section {
24+
Toggle(isOn: $updater.autoCheckForUpdates) {
25+
Text("Automatically check for updates")
26+
}
27+
Picker("Update channel", selection: $updater.updateChannel) {
28+
ForEach(UpdateChannel.allCases) { channel in
29+
Text(channel.name).tag(channel)
30+
}
31+
}
32+
HStack {
33+
Spacer()
34+
Button("Check for updates") { updater.checkForUpdates() }.disabled(!updater.canCheckForUpdates)
2935
}
3036
}
31-
HStack {
32-
Spacer()
33-
Button("Check for updates") { updater.checkForUpdates() }.disabled(!updater.canCheckForUpdates)
37+
} else {
38+
Section {
39+
Text("The app updater has been disabled by a device management policy.")
40+
.foregroundColor(.secondary)
3441
}
3542
}
3643
}.formStyle(.grouped)

0 commit comments

Comments
 (0)