개요
이 글은 Xcode Cloud를 통해 간편하게 CI/CD를 수행하고, Sparkle을 통해 앱에서 간편하게 업데이트 구현하는 법에 대해 정리한다
Sparkle을 이용한다면 앱에 조금의 코드 추가로 앱에서 설정한 URL을 통해 appcast.xml을 얻어 업데이트를 수행할 수 있다
앞으로 우선 Xcode Cloud에 대해 잠깐 정리하고, Sparkle을 통한 업데이트 구현을 위해 알아야 할 내용들을 간단하게 이해하고, 이후 앱 부분에서 좀 더 자세한 내용들에 대해 알아본다
- Xcode Cloud
- Sparkle Framework
- 앱 설정과 구현
Xcode Cloud
Xcode Cloud는 클라우드에서 앱 빌드, 테스트 등의 서비스를 제공한다
Xcode Cloud 설정에서 깃헙 레포지터리를 등록하면, 해당 브랜치에 새로 커밋이 추가될 때마다 빌드를 수행할 수 있다(github action과 유사하다)
Custom build script
Xcode Cloud는 위와 같이 각 단계를 기반한 이름으로 아래와 같은 폴더로 프로젝트에 스크립트를 작성하고 추가하면, 해당 단계에서 스크립트를 수행한다
- ci_scripts/
- ci_post_clone.sh
- ci_pre_xcodebuild.sh
- ci_post_xcodebuild.sh
Environment variable reference
아무래도 스크립트를 작성하다보면, 프로젝트와 관련된 정보를 이용해야 하는 경우도 존재할 텐데, 이미 다양한 CI_ 환경변수들을 제공해 준다
특히 유용했던 것은 다음과 같다
- CI_PRIMARY_REPOSITORY_PATH - 프로젝트 경로, 이후에 /PROJECT_NAME이 온다
- CI_BUILD_NUMBER - 빌드 버전
예) 빌드 버전 자동 업데이트
앱의 버전이나 빌드 버전의 경우는 Info.plist에 기록되는데, CFBundleShortVersionString나 CFBundleVersion은 빌드 시 스크립트를 통해 자동으로 업데이트할 수 있다
/usr/libexec/PlistBuddy는 plist 파일에 키나 값을 지정하는 데 사용된다
만약 현재 빌드버전을 바탕으로 빌드 시, CFBundleVersion로 지정하려면 다음과 같이 스크립트를 작성할 수 있다
#!/bin/sh
INFO_PLIST_PATH="${CI_PRIMARY_REPOSITORY_PATH}/TodoMate/Info.plist"
BUILD_VERSION="${CI_BUILD_NUMBER}"
if [ -f "$INFO_PLIST_PATH" ]; then
# Info.plist에 키가 없으면 생성 후 추가
# /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $VERSION" "$INFO_PLIST_PATH"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$INFO_PLIST_PATH"
echo "Updated CFBundleVersion to $VERSION in Info.plist"
else
echo "Info.plist not found at $INFO_PLIST_PATH"
fi
Sparkle Framework
이후 앱 부분에서 Sparkle 업데이트를 이용하기 위해 설정해야 할 것과 구현에 대해 자세히 알게 될 것이다
그에 앞서 실제로 앱에서 업데이트를 수행하기 위해, 업데이트 가능한 앱에 대한 정보를 제공해주는 appcast.xml에 대해 정리해보면 다음과 같다
appcast.xml
macOS 앱을 배포할 때, 개발자 서명이나 공증 과정을 이미 거치지만, Sparkle을 통해 업데이트된 앱으로 교체되도록 하려면 추가적인 작업들이 필요하다
앱에서 Sparkle은 배포자의 appcast.xml 파일을 읽어서 업데이트 여부를 확인하고 업데이트를 수행하는데, 이때 파일은 https를 통해 제공되어야 한다
appcast.xml는 Sparkle에서 제공해주는 툴로 만들 수 있지만, 필자는 그냥 백엔드에서 파일 서명과 appcast.xml 생성을 수행하기로 했다
아래는 appcast.xml의 예시이다
이때 해당 정보와 업데이트 파일의 무결성을 보장하기 위해 다음과 같은 내용이 사용된다
shortVersionString, version
앱에서 새로운 버전 업데이트를 확인하는데 사용된다
shortVersionString은 우리가 흔히 볼 수 있는 버전(예시-1.0.0)이고, version은 빌드 넘버다
sha256
SHA-256(Secure Hash Algorithm 256-bit)은 데이터를 입력으로 받아 256비트(32바이트)의 해시 값을 생성하는 해시 함수로 다운로드 한 파일의 무결성을 확인하는 데 사용된다
edSignature
앱에서 공개키를 통해 검증하여 신뢰하는 개발자인지 확인하는데 이때 EdDSA를 사용한다
ED25519는 공용 키 암호화와 서명 생성에 사용되는 비대칭 암호화 알고리즘으로 타원 곡선 암호화(EC) 알고리즘 중 하나로 높은 성능과 보안성을 제공한다
서명
검증
배포 시, 비밀키로 서명을 수행하고 앱에서 공개키로 검증을 수행한다
이때 공개키는 Info.plist에 등록되어한다(앱 부분에서 설명)
앱
App과 AppDelegate
import SwiftUI
@main
struct TodoMateApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
CommandGroup(replacing: .appInfo) {
Button("Check for Updates") {
appDelegate.checkForUpdates()
}
}
}
}
}
import SwiftUI
import AppKit
import Sparkle
class AppDelegate: NSObject, SPUUpdaterDelegate {
var updater: SPUUpdater?
func applicationDidFinishLaunching(_ notification: Notification) {
/// Sparkle 업데이트 관리
let updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: self, userDriverDelegate: nil)
updater = updaterController.updater
}
}
extension AppDelegate {
func checkForUpdates() {
updater?.checkForUpdates()
}
}
업데이트 기능을 이용하기 위해 작성하는 코드 부분은 간단하다
프로젝트에 Sparkle 패키지를 추가하고, AppDelegate에서 SPUUdaterDelegate를 채택하여 updater를 지정하고 이를 CommandGroup으로 등록하면 끝이다
이때 업데이트 시, 앱은 배포자가 제공하는 appcast.xml을 바탕으로 업데이트를 진행한다
Info
- SUPublicEDKey - EdDSA(ed25519) 공개키 추가
- SUFeedURL - appcast.xml 제공 주소
- SUEnableInstallerLauncherService
위의 정보를 Info.plist에 추가한다
<key>SUEnableInstallerLauncherService</key>
<true/>
<key>SUFeedURL</key>
<string><https://$>(UPDATE_SERVER_URL)</string>
<key>SUPublicEDKey</key>
<string>$(PUBLIC_EDKEY)</string>
이때 서명을 위한 키 세팅은 Sparkle에서 제공하는 툴을 이용하거나 직접 만들면 된다
필자는 appcast.xml을 제공하는 서버를 만들면서 파이썬으로 코드로 작성하다 보니 그냥 해당 작업을 하는 파이썬 코드를 이용해서 키를 만들었다
import nacl.signing
import base64
# 키 쌍 생성
private_key = nacl.signing.SigningKey.generate()
public_key = private_key.verify_key
# 키를 Base64로 인코딩
private_key_base64 = base64.b64encode(bytes(private_key)).decode('utf-8')
public_key_base64 = base64.b64encode(bytes(public_key)).decode('utf-8')
# 키 저장
with open('ed25519_private.key', 'w') as f:
f.write(private_key_base64)
with open('ed25519_public.key', 'w') as f:
f.write(public_key_base64)
마무리
Xcode Cloud를 이용하면 정말 간단하게 스크립트를 통해 CI/CD 파이프라인을 작성할 수 있다
Sparkle을 통해 앱에서 새로운 버전을 손쉽게 업데이트할 수 있다
Sparkle은 앱 설정에서 등록한 주소를 통해 appcast.xml을 얻어 업데이트 관련 정보를 통해 업데이트를 수행한다
앱 코드 부분에서는 우선 해당 패키지를 추가하고, AppDelegate에서 Sparkle의 SPUUpdater를 통해 업데이트 시 이용한다
이때 WindowGroup에서 CommandGroup을 이용해서 업데이트 관련 설정창을 등록한다
앱 설정 부분에서는 Info와 Entitlements에 Sparkle 관련 설정을 하는데, 공개키는 appcast.xml에서 비밀키로 서명한 edSignature를 검증하기 위해 사용된다
참고로 현재 프로젝트의 CI/CD 파이프라인은 다음과 같다
'iOS+' 카테고리의 다른 글
인증서와 프로파일 (0) | 2025.02.02 |
---|---|
#Preview를 위한 환경 구분 방법 (2) | 2024.12.29 |
Objective-C (1) | 2024.09.22 |
WidgetKit (0) | 2024.09.15 |
앱 배포 관련 정리(macOS) (1) | 2024.08.31 |