Add signed, notarized releases with the server inside the app
This commit is contained in:
@@ -16,6 +16,12 @@
|
||||
<string>27.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>SUFeedURL</key>
|
||||
<string>https://g.fc.run/misc/byre/releases/download/latest/appcast.xml</string>
|
||||
<key>SUPublicEDKey</key>
|
||||
<string>350+qbxFCpm+LhbRlCxtBHST+FTz6o19jhGOSs8D0XU=</string>
|
||||
<key>SUEnableAutomaticChecks</key>
|
||||
<true/>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>byre's agents drive Orion on this Mac through AppleScript when a task asks them to.</string>
|
||||
</dict>
|
||||
|
||||
+10
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "c8eddc113e1728df643cb0e886195a9376637e4c97c431bbf2201b07be34a7c6",
|
||||
"originHash" : "1e64f8e620bfd60399ac1276949a869cf223612c3875be1481772d36a8a2181d",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "h",
|
||||
@@ -19,6 +19,15 @@
|
||||
"version" : "6.4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sparkle",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sparkle-project/Sparkle",
|
||||
"state" : {
|
||||
"revision" : "eef1a539a373c1f1a320624b1130fc5de7b2e100",
|
||||
"version" : "2.10.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-algorithms",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
+4
-1
@@ -9,6 +9,7 @@ let package = Package(
|
||||
.package(url: "https://github.com/apple/swift-openapi-generator", from: "1.10.0"),
|
||||
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.8.0"),
|
||||
.package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.1.0"),
|
||||
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.10.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
@@ -26,8 +27,10 @@ let package = Package(
|
||||
.product(name: "SwiftTerm", package: "SwiftTerm"),
|
||||
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
|
||||
.product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession"),
|
||||
.product(name: "Sparkle", package: "Sparkle"),
|
||||
],
|
||||
swiftSettings: [.strictMemorySafety()]
|
||||
swiftSettings: [.strictMemorySafety()],
|
||||
linkerSettings: [.unsafeFlags(["-Xlinker", "-rpath", "-Xlinker", "@executable_path/../Frameworks"])]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
Notifier.shared.install()
|
||||
Updater.shared.start()
|
||||
}
|
||||
|
||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
@@ -64,6 +65,9 @@ struct ByreApp: App {
|
||||
.defaultSize(width: 1280, height: 820)
|
||||
.commands {
|
||||
CommandGroup(after: .appInfo) {
|
||||
Button("Check for Updates…") {
|
||||
Updater.shared.check()
|
||||
}
|
||||
Button("Change Server…") {
|
||||
connection.changeServer()
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ private actor Hands {
|
||||
let stdin = Pipe()
|
||||
let stdout = Pipe()
|
||||
let p = Process()
|
||||
p.executableURL = Bundle.main.executableURL!.deletingLastPathComponent().appending(path: "byre-hands")
|
||||
p.executableURL = Bundle.main.bundleURL.appending(path: "Contents/Helpers/byre-hands")
|
||||
p.standardInput = stdin
|
||||
p.standardOutput = stdout
|
||||
p.standardError = FileHandle.nullDevice
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import Foundation
|
||||
import Sparkle
|
||||
|
||||
private struct LaunchAgent: Decodable {
|
||||
let programArguments: [String]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case programArguments = "ProgramArguments"
|
||||
}
|
||||
}
|
||||
|
||||
private let lastVersionKey = "lastLaunchedVersion"
|
||||
|
||||
@MainActor
|
||||
final class Updater {
|
||||
static let shared = Updater()
|
||||
private lazy var controller = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
|
||||
|
||||
func start() {
|
||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String
|
||||
let last = UserDefaults.standard.string(forKey: lastVersionKey)
|
||||
UserDefaults.standard.set(version, forKey: lastVersionKey)
|
||||
if last != nil && last != version {
|
||||
restartServer()
|
||||
}
|
||||
_ = controller
|
||||
}
|
||||
|
||||
func check() {
|
||||
controller.checkForUpdates(nil)
|
||||
}
|
||||
|
||||
private func restartServer() {
|
||||
let agent = FileManager.default.homeDirectoryForCurrentUser.appending(path: "Library/LaunchAgents/run.fc.byre.plist")
|
||||
let server = Bundle.main.bundleURL.appending(path: "Contents/Helpers/byre").path
|
||||
guard let data = try? Data(contentsOf: agent),
|
||||
let plist = try? PropertyListDecoder().decode(LaunchAgent.self, from: data),
|
||||
plist.programArguments.first == server
|
||||
else {
|
||||
return
|
||||
}
|
||||
let p = Process()
|
||||
p.executableURL = URL(filePath: "/bin/launchctl")
|
||||
p.arguments = ["kickstart", "-k", "gui/\(getuid())/run.fc.byre"]
|
||||
do {
|
||||
try p.run()
|
||||
p.waitUntilExit()
|
||||
log.info("restarted byre server: status \(p.terminationStatus)")
|
||||
} catch {
|
||||
log.error("restart byre server: \(errorText(error), privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.automation.apple-events</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
+27
-6
@@ -7,14 +7,22 @@ build=$mac/.build
|
||||
app=$build/byre.app
|
||||
svg=$mac/icon.svg
|
||||
icns=$build/byre.icns
|
||||
identity="Developer ID Application"
|
||||
mkdir -p "$shared" "$build"
|
||||
if [ "$(security cms -D -i "$mac/byre.provisionprofile" | plutil -extract ProvisionsAllDevices raw -)" != true ]; then
|
||||
echo "bundle: mac/byre.provisionprofile is not a Developer ID profile" >&2
|
||||
exit 1
|
||||
fi
|
||||
lockf -k "$shared.lock" sh -ec '
|
||||
rsync -rlc --delete --exclude /mac/.build --exclude /hands/.build "$1/mac" "$1/api" "$1/hands" "$2/"
|
||||
swift build --package-path "$2/mac" --configuration release --no-color-diagnostics
|
||||
swift build --package-path "$2/hands" --configuration release --no-color-diagnostics
|
||||
cp "$2/mac/.build/release/Byre" "$3"
|
||||
cp "$2/hands/.build/release/byre-hands" "$4"
|
||||
' sh "$root" "$shared" "$build/Byre" "$build/byre-hands"
|
||||
rm -rf "$5"
|
||||
ditto "$2/mac/.build/release/Sparkle.framework" "$5"
|
||||
' sh "$root" "$shared" "$build/Byre" "$build/byre-hands" "$build/Sparkle.framework"
|
||||
go -C "$root" build -trimpath -o "$build/server/byre" ./cmd/byre
|
||||
if [ ! "$icns" -nt "$svg" ]; then
|
||||
iconset=$build/byre.iconset
|
||||
rm -rf "$iconset"
|
||||
@@ -25,17 +33,30 @@ if [ ! "$icns" -nt "$svg" ]; then
|
||||
done
|
||||
iconutil --convert icns --output "$icns" "$iconset"
|
||||
fi
|
||||
version=$(git -C "$root" describe --tags --always)
|
||||
version=${version#v}
|
||||
rm -rf "$app"
|
||||
mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources/fonts"
|
||||
mkdir -p "$app/Contents/MacOS" "$app/Contents/Helpers" "$app/Contents/Frameworks" "$app/Contents/Resources/fonts"
|
||||
cp "$build/Byre" "$app/Contents/MacOS/Byre"
|
||||
cp "$build/byre-hands" "$app/Contents/MacOS/byre-hands"
|
||||
codesign --force --sign "Apple Development" --identifier run.fc.byre.hands "$app/Contents/MacOS/byre-hands"
|
||||
cp "$build/server/byre" "$app/Contents/Helpers/byre"
|
||||
cp "$build/byre-hands" "$app/Contents/Helpers/byre-hands"
|
||||
ditto "$build/Sparkle.framework" "$app/Contents/Frameworks/Sparkle.framework"
|
||||
cp "$mac/Info.plist" "$app/Contents/Info.plist"
|
||||
plutil -insert ByreCommit -string "$(git -C "$root" rev-parse HEAD)" "$app/Contents/Info.plist"
|
||||
plutil -insert CFBundleShortVersionString -string "$(git -C "$root" rev-parse --short HEAD)" "$app/Contents/Info.plist"
|
||||
plutil -insert CFBundleShortVersionString -string "$version" "$app/Contents/Info.plist"
|
||||
plutil -insert CFBundleVersion -string "$(git -C "$root" rev-list --count HEAD)" "$app/Contents/Info.plist"
|
||||
cp "$mac"/fonts/*.woff2 "$app/Contents/Resources/fonts/"
|
||||
cp "$icns" "$app/Contents/Resources/byre.icns"
|
||||
cp "$mac/byre.provisionprofile" "$app/Contents/embedded.provisionprofile"
|
||||
codesign --force --sign "Apple Development" --entitlements "$mac/byre.entitlements" "$app"
|
||||
sparkle=$app/Contents/Frameworks/Sparkle.framework
|
||||
rm -rf "$sparkle/Versions/B/XPCServices"
|
||||
sign() {
|
||||
codesign --force --timestamp --options runtime --sign "$identity" "$@"
|
||||
}
|
||||
sign --identifier run.fc.byre.hands --entitlements "$mac/automation.entitlements" "$app/Contents/Helpers/byre-hands"
|
||||
sign --identifier run.fc.byre --entitlements "$mac/automation.entitlements" "$app/Contents/Helpers/byre"
|
||||
sign "$sparkle/Versions/B/Autoupdate"
|
||||
sign "$sparkle/Versions/B/Updater.app"
|
||||
sign "$sparkle"
|
||||
sign --entitlements "$mac/byre.entitlements" "$app"
|
||||
echo "$app"
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
<array>
|
||||
<string>CloudKit</string>
|
||||
</array>
|
||||
<key>com.apple.developer.icloud-container-environment</key>
|
||||
<string>Production</string>
|
||||
<key>com.apple.developer.aps-environment</key>
|
||||
<string>development</string>
|
||||
<string>production</string>
|
||||
<key>com.apple.security.automation.apple-events</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
Binary file not shown.
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
mac=$(cd "$(dirname "$0")" && pwd)
|
||||
root=$(dirname "$mac")
|
||||
repo=misc/byre
|
||||
api=https://g.fc.run/api/v1/repos/$repo
|
||||
sparkle_version=2.10.0
|
||||
sparkle=$HOME/Library/Caches/byre/sparkle-$sparkle_version
|
||||
identity="Developer ID Application"
|
||||
|
||||
if [ -n "$(git -C "$root" status --porcelain)" ]; then
|
||||
echo "release: the tree has uncommitted changes" >&2
|
||||
exit 1
|
||||
fi
|
||||
tag=$(git -C "$root" describe --tags --exact-match HEAD)
|
||||
version=${tag#v}
|
||||
git -C "$root" fetch origin "refs/tags/$tag"
|
||||
if [ "$(git -C "$root" rev-parse "FETCH_HEAD^{commit}")" != "$(git -C "$root" rev-parse HEAD)" ]; then
|
||||
echo "release: $tag on origin is not HEAD" >&2
|
||||
exit 1
|
||||
fi
|
||||
token=$(security find-generic-password -s byre-release -w)
|
||||
|
||||
if [ ! -x "$sparkle/bin/sign_update" ]; then
|
||||
mkdir -p "$sparkle"
|
||||
curl -fsSL "https://github.com/sparkle-project/Sparkle/releases/download/$sparkle_version/Sparkle-$sparkle_version.tar.xz" | tar -xJ -C "$sparkle"
|
||||
fi
|
||||
|
||||
"$mac/bundle.sh"
|
||||
app=$mac/.build/byre.app
|
||||
out=$mac/.build/release/$version
|
||||
rm -rf "$out"
|
||||
mkdir -p "$out"
|
||||
|
||||
ditto -c -k --keepParent "$app" "$out/byre.zip"
|
||||
xcrun notarytool submit "$out/byre.zip" --keychain-profile byre-notary --wait
|
||||
xcrun stapler staple "$app"
|
||||
rm "$out/byre.zip"
|
||||
|
||||
dmg=byre-$version.dmg
|
||||
stage=$out/stage
|
||||
mkdir "$stage"
|
||||
ditto "$app" "$stage/byre.app"
|
||||
ln -s /Applications "$stage/Applications"
|
||||
hdiutil create -volname byre -srcfolder "$stage" -format UDZO "$out/$dmg"
|
||||
rm -rf "$stage"
|
||||
codesign --force --timestamp --sign "$identity" "$out/$dmg"
|
||||
xcrun notarytool submit "$out/$dmg" --keychain-profile byre-notary --wait
|
||||
xcrun stapler staple "$out/$dmg"
|
||||
(cd "$out" && shasum -a 256 "$dmg" > "$dmg.sha256")
|
||||
|
||||
build=$(plutil -extract CFBundleVersion raw "$app/Contents/Info.plist")
|
||||
minimum=$(plutil -extract LSMinimumSystemVersion raw "$app/Contents/Info.plist")
|
||||
signature=$("$sparkle/bin/sign_update" --account byre "$out/$dmg")
|
||||
cat > "$out/appcast.xml" <<EOF
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
|
||||
<channel>
|
||||
<title>byre</title>
|
||||
<item>
|
||||
<title>byre $version</title>
|
||||
<pubDate>$(LC_ALL=C date -u "+%a, %d %b %Y %H:%M:%S +0000")</pubDate>
|
||||
<sparkle:version>$build</sparkle:version>
|
||||
<sparkle:shortVersionString>$version</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>$minimum</sparkle:minimumSystemVersion>
|
||||
<enclosure url="https://g.fc.run/$repo/releases/download/$tag/$dmg" type="application/octet-stream" $signature/>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
EOF
|
||||
|
||||
previous=$(git -C "$root" describe --tags --abbrev=0 "$tag^" || true)
|
||||
if [ -n "$previous" ]; then
|
||||
notes=$(git -C "$root" log --format='- %s' "$previous..$tag")
|
||||
else
|
||||
notes="First release."
|
||||
fi
|
||||
request=$out/release.json
|
||||
plutil -create xml1 "$request"
|
||||
plutil -insert tag_name -string "$tag" "$request"
|
||||
plutil -insert name -string "byre $version" "$request"
|
||||
plutil -insert body -string "$notes" "$request"
|
||||
plutil -insert draft -bool true "$request"
|
||||
plutil -convert json "$request"
|
||||
id=$(curl -fsS -X POST -H "Authorization: token $token" -H "Content-Type: application/json" --data-binary "@$request" "$api/releases" | plutil -extract id raw -)
|
||||
for file in "$dmg" "$dmg.sha256" appcast.xml; do
|
||||
curl -fsS -o /dev/null -H "Authorization: token $token" -F "attachment=@$out/$file" "$api/releases/$id/assets?name=$file"
|
||||
done
|
||||
curl -fsS -o /dev/null -X PATCH -H "Authorization: token $token" -H "Content-Type: application/json" --data '{"draft":false}' "$api/releases/$id"
|
||||
echo "https://g.fc.run/$repo/releases/tag/$tag"
|
||||
Reference in New Issue
Block a user