Skip to content

Commit c32b23c

Browse files
committed
Add restic-less builds
1 parent 571e605 commit c32b23c

File tree

5 files changed

+57
-12
lines changed

5 files changed

+57
-12
lines changed
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ on:
55
- '*'
66
permissions:
77
contents: write
8-
name: goreleaser
8+
name: Release
99
jobs:
10-
GoReleaser:
10+
goreleaser:
11+
name: GoReleaser
1112
env:
1213
RESTIC_VERSION: '0.17.3'
1314
runs-on: macos-14
@@ -36,6 +37,7 @@ jobs:
3637
- name: Pack application
3738
run: |
3839
(cd Build/Products/Release && zip -9 -r 'Restic Scheduler ${{ github.ref_name }}.zip' 'Restic Scheduler.app')
40+
(cd Build/Products/Release && rm 'Restic Scheduler.app/Contents/XPCServices/Restic Runner.xpc/Contents/Resources/restic' && zip -9 -r 'Restic Scheduler ${{ github.ref_name }} (without restic).zip' 'Restic Scheduler.app')
3941
- name: Run GoReleaser
4042
uses: goreleaser/goreleaser-action@v6
4143
with:
@@ -45,8 +47,8 @@ jobs:
4547
env:
4648
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4749
- name: Upload artifacts
48-
uses: softprops/action-gh-release@v2
50+
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
4951
with:
50-
files: 'Build/Products/Release/Restic Scheduler ${{ github.ref_name }}.zip'
52+
files: Build/Products/Release/*.zip
5153
env:
5254
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

ResticRunner/ResticRunnerService.swift

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ class ResticRunnerService: ResticRunnerProtocol {
5757
func version(binary: String?, reply: @escaping (String?, Error?) -> Void) {
5858
let process = Process()
5959
process.qualityOfService = .userInitiated
60-
process.executableURL = resticURL(forBinary: binary)
60+
guard let executableURL = resticURL(forBinary: binary) else {
61+
reply(nil, ProcessError.missingRestic)
62+
return
63+
}
64+
65+
process.executableURL = executableURL
6166
process.arguments = ["version"]
6267
let standardOutput = Pipe()
6368
let standardError = Pipe()
@@ -103,7 +108,12 @@ class ResticRunnerService: ResticRunnerProtocol {
103108
}
104109
let process = Process()
105110
process.qualityOfService = .background
106-
process.executableURL = resticURL(forBinary: binary)
111+
guard let executableURL = resticURL(forBinary: binary) else {
112+
reply(ProcessError.missingRestic)
113+
return
114+
}
115+
116+
process.executableURL = executableURL
107117
process.environment = ProcessInfo.processInfo.environment
108118
.merging(options.environment) { _, new in new }
109119
.merging(["RESTIC_PROGRESS_FPS": "0.2"]) { _, new in new }
@@ -237,6 +247,10 @@ class ResticRunnerService: ResticRunnerProtocol {
237247
}
238248
}
239249

250+
func includesBuiltIn(reply: @escaping (Bool) -> Void) {
251+
reply(resticURL(forBinary: nil) != nil)
252+
}
253+
240254
private func runHook(_ hook: String, ofType type: HookType, loggingTo logURL: URL) {
241255
do {
242256
try "\(Self.logPadding)Invoking \(type) hook...\n".append(to: logURL, encoding: .utf8)
@@ -278,16 +292,15 @@ class ResticRunnerService: ResticRunnerProtocol {
278292
let error = ProcessError.abnormalTermination(terminationStatus: process.terminationStatus, standardError: standardErrorOutput.trimmingCharacters(in: .whitespacesAndNewlines))
279293
TypeLogger.function().error("Failed to run \(type) hook: \(error.localizedDescription, privacy: .public)")
280294
}
281-
282295
} catch {
283296
TypeLogger.function().error("\(error.localizedDescription, privacy: .public)")
284297
}
285298
}
286299
}
287300

288-
func resticURL(forBinary binary: String?) -> URL {
301+
func resticURL(forBinary binary: String?) -> URL? {
289302
guard let binary, !binary.isEmpty else {
290-
return Bundle.main.url(forResource: "restic", withExtension: "")!
303+
return Bundle.main.url(forResource: "restic", withExtension: "")
291304
}
292305

293306
return URL(fileURLWithPath: binary)

ResticScheduler/AdvancedSettingsView.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct AdvancedSettingsView: View {
4646
}
4747

4848
@State private var browseBinary = false
49+
@State private var includesBuiltIn: Bool?
4950
@StateObject private var binaryVersion = BinaryVersion()
5051
@EnvironmentObject private var resticScheduler: ResticScheduler
5152
@UserDefault(\.binary) private var binary
@@ -87,8 +88,10 @@ struct AdvancedSettingsView: View {
8788
.tag(BinaryType.manual)
8889
Divider()
8990
}
90-
Text("Built-in")
91-
.tag(BinaryType.builtIn)
91+
if includesBuiltIn != false {
92+
Text("Built-in")
93+
.tag(BinaryType.builtIn)
94+
}
9295
Text("Other…")
9396
.tag(BinaryType.browse)
9497
}
@@ -158,7 +161,16 @@ struct AdvancedSettingsView: View {
158161
arguments,
159162
] as [AnyHashable]) { _ in resticScheduler.rescheduleStaleBackupCheck() }
160163
.onChange(of: binary) { _ in binaryVersion.scheduleUpdate(via: resticScheduler, for: binary) }
161-
.onAppear { binaryVersion.scheduleUpdate(via: resticScheduler, for: binary) }
164+
.onAppear {
165+
if includesBuiltIn == nil {
166+
resticScheduler.includesBuiltIn { result in
167+
DispatchQueue.main.async {
168+
includesBuiltIn = result
169+
}
170+
}
171+
}
172+
binaryVersion.scheduleUpdate(via: resticScheduler, for: binary)
173+
}
162174
}
163175
}
164176

ResticScheduler/ResticScheduler.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ class ResticScheduler: ObservableObject, ResticSchedulerProtocol {
2929
activateRemoteObjectProxyWithErrorHandler { error in replyOnce(error) }?.stop(reply: replyOnce)
3030
}
3131

32+
func includesBuiltIn(reply: @escaping (Bool) -> Void) {
33+
let replyOnce = withCallingReplyOnce(reply)
34+
activateRemoteObjectProxyWithErrorHandler { _ in replyOnce(true) }?.includesBuiltIn(reply: replyOnce)
35+
}
36+
3237
private func activateRemoteObjectProxyWithErrorHandler(_ handler: @escaping (XPCConnectionError) -> Void) -> ResticRunnerProtocol? {
3338
NSXPCConnection(serviceName: Self.serviceName).activateRemoteObjectProxyWithErrorHandler(protocol: ResticRunnerProtocol.self) { error in handler(error) }
3439
}
@@ -216,6 +221,10 @@ class ResticScheduler: ObservableObject, ResticSchedulerProtocol {
216221
runner.version(binary: binary, reply: completion)
217222
}
218223

224+
func includesBuiltIn(completion: @escaping (Bool) -> Void) {
225+
runner.includesBuiltIn(reply: completion)
226+
}
227+
219228
func rescheduleBackup() {
220229
lock.withLock {
221230
backupScheduler?.invalidate()

ResticSchedulerKit/ResticRunner.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import Foundation
5757
public enum ProcessError: CustomNSError, LocalizedError, _ObjectiveCBridgeableError {
5858
public enum Code: Int {
5959
case abnormalTermination = 1
60+
case missingRestic = 2
6061
}
6162

6263
public enum UserInfoKey: String {
@@ -65,12 +66,14 @@ public enum ProcessError: CustomNSError, LocalizedError, _ObjectiveCBridgeableEr
6566
}
6667

6768
case abnormalTermination(terminationStatus: Int32, standardError: String)
69+
case missingRestic
6870

6971
public static let errorDomain = String(describing: Self.self)
7072

7173
public var errorCode: Int {
7274
switch self {
7375
case .abnormalTermination: Code.abnormalTermination.rawValue
76+
case .missingRestic: Code.missingRestic.rawValue
7477
}
7578
}
7679

@@ -82,12 +85,15 @@ public enum ProcessError: CustomNSError, LocalizedError, _ObjectiveCBridgeableEr
8285
UserInfoKey.terminationStatus.rawValue: terminationStatus,
8386
UserInfoKey.standardError.rawValue: standardError,
8487
]
88+
default:
89+
[NSLocalizedDescriptionKey: errorDescription!]
8590
}
8691
}
8792

8893
public var errorDescription: String? {
8994
switch self {
9095
case let .abnormalTermination(terminationStatus, standardError): "Process exited with code \(terminationStatus): \(standardError != "" ? standardError : "<no output>")"
96+
case .missingRestic: "Unavailable built-in restic and no custom was provided"
9197
}
9298
}
9399

@@ -102,6 +108,8 @@ public enum ProcessError: CustomNSError, LocalizedError, _ObjectiveCBridgeableEr
102108
terminationStatus: error.userInfo[UserInfoKey.terminationStatus.rawValue] as! Int32,
103109
standardError: error.userInfo[UserInfoKey.standardError.rawValue] as! String
104110
)
111+
case Code.missingRestic.rawValue:
112+
self = .missingRestic
105113
default:
106114
return nil
107115
}
@@ -157,4 +165,5 @@ public enum BackupError: CustomNSError, LocalizedError, _ObjectiveCBridgeableErr
157165
func version(binary: String?, reply: @escaping (String?, Error?) -> Void)
158166
func backup(binary: String?, options: BackupOptions, reply: @escaping (Error?) -> Void)
159167
func stop(reply: @escaping (Error?) -> Void)
168+
func includesBuiltIn(reply: @escaping (Bool) -> Void)
160169
}

0 commit comments

Comments
 (0)