53 lines
2.6 KiB
Swift
53 lines
2.6 KiB
Swift
import CryptoKit
|
|
import Foundation
|
|
|
|
/// Answers "is the helper a fleet device runs the same one vendored in this
|
|
/// repo?" — the Devices tab's freshness badge. The repo copy is the source of
|
|
/// truth (mcp/README's deploy step ships it byte-for-byte), so freshness is a
|
|
/// straight content-hash comparison: the device's `stats` report carries the
|
|
/// sha256 of the script that produced it; the app hashes the vendored source.
|
|
/// No version constants to bump, nothing that can drift.
|
|
public enum HelperDeployment {
|
|
public enum Freshness: Equatable, Sendable {
|
|
case current
|
|
/// `deployed` is the device-reported hash; nil means the deployed helper
|
|
/// predates self-reporting entirely (necessarily stale).
|
|
case outdated(deployed: String?)
|
|
}
|
|
|
|
/// Repo source for each known helper bin, keyed by basename (the bin path on
|
|
/// the device — e.g. `/usr/local/bin/black-tv` — names the deployed copy).
|
|
private static let vendoredSources: [String: String] = [
|
|
"black-tv": "mcp/src/blacktv/black-tv.sh",
|
|
]
|
|
|
|
/// The repo file that is the source of truth for `bin`, or nil when the bin
|
|
/// is not a known helper or the checkout doesn't actually hold the file.
|
|
public static func vendoredSource(forBin bin: String) -> URL? {
|
|
let name = (bin as NSString).lastPathComponent
|
|
guard let rel = vendoredSources[name] else { return nil }
|
|
let url = RepoPaths.root.appendingPathComponent(rel)
|
|
return FileManager.default.fileExists(atPath: url.path) ? url : nil
|
|
}
|
|
|
|
/// Lowercase-hex sha256 — the same digest `sha256sum` prints on the device.
|
|
public static func sha256(_ data: Data) -> String {
|
|
SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined()
|
|
}
|
|
|
|
/// sha256 of the vendored source for `bin`, or nil when the bin is not a
|
|
/// known helper or the repo checkout is absent (an installed app without a
|
|
/// repo can't judge freshness — the badge simply stays off).
|
|
public static func expectedSHA(forBin bin: String) -> String? {
|
|
guard let url = vendoredSource(forBin: bin),
|
|
let data = try? Data(contentsOf: url) else { return nil }
|
|
return sha256(data)
|
|
}
|
|
|
|
/// Judge a device report against an expectation. nil expectation (unknown
|
|
/// helper / no repo) → nil: freshness can't be judged, show nothing.
|
|
public static func freshness(expected: String?, reported: String?) -> Freshness? {
|
|
guard let expected else { return nil }
|
|
return reported == expected ? .current : .outdated(deployed: reported)
|
|
}
|
|
}
|