글가드는 Sparkle 2로 업데이트를 확인하고, 공식 GitHub Release의 서명·공증된
PKG를 설치한다. 업데이트 설치에는 사용자 동의와 macOS 관리자 인증이 필요하다.
입력기의 설치 경로는 /Library/Input Methods다. PKG 설치 성공과 새 입력기의
활성화는 별개이며, 새 버전 적용에 로그아웃·로그인이 필요할 수 있다.
scripts/build-app.sh는 SwiftPM의 Sparkle 프레임워크를 앱에 복사하고 내부
XPC 서비스, Autoupdate, Updater.app, 프레임워크, 앱 순서로 서명한다. 프레임워크의
심볼릭 링크와 실행 권한을 보존한다. 기존 CONFIGURATION,
GEUL_GUARD_ARCHITECTURES, GEUL_GUARD_SCRATCH_PATH, CODESIGN_IDENTITY 설정을
그대로 사용한다. 개발 빌드는 기본적으로 ad-hoc 서명이다.
다음 두 환경변수는 공개 정보이며 빌드한 앱의 Info.plist에 포함된다.
| 환경변수 | 용도 |
|---|---|
GEUL_GUARD_UPDATE_FEED_URL |
SUFeedURL에 기록할 고정 HTTPS appcast 주소 |
GEUL_GUARD_UPDATE_PUBLIC_KEY |
SUPublicEDKey에 기록할 Base64 Ed25519 공개키, 디코딩 후 32바이트 |
공식 배포용 공개 설정은 config/updates.env에 보관한다. 저장소 루트에서
다음 명령을 실행한 같은 터미널에서 빌드·릴리스 스크립트를 실행한다.
source config/updates.env피드는 https://geulguard.jureuk.dev/.well-known/appcast.xml이다.
이 파일에는 공개키와 공개 URL만 포함하며, 개인키·공증 자격 증명은 넣지 않는다.
개발 빌드에 자동 적용하지 않으며 명시적으로 불러온 경우에만 적용한다.
둘 다 생략하면 업데이트 서버가 설정되지 않은 개발 빌드다. 하나만 지정하거나 유효하지 않은 값을 지정하면 빌드가 실패한다. 피드 URL에는 사용자명·암호를 넣을 수 없다. 비공개 키를 환경변수나 저장소에 넣지 않는다.
릴리스 담당자는 고정 HTTPS 피드 호스팅과 Sparkle 서명 키를 먼저 준비한다.
Sparkle의 generate_keys는 키체인에 키를 저장하는 도구다. 키 생성은 자동 빌드에
포함되지 않으며, 운영자가 공식 키 관리 안내
에 따라 별도로 수행한다. PKG 업데이트는 키 분실 시 일반 앱 번들의 키 교체
대체 경로를 지원하지 않으므로 키 보관 정책을 먼저 정한다.
scripts/release-pkg.sh에는 위 두 공개 설정 외에 다음 환경변수가 필요하다.
| 환경변수 | 용도 |
|---|---|
DEVELOPER_ID_APPLICATION |
키체인의 앱 서명 인증서 이름 |
DEVELOPER_ID_INSTALLER |
키체인의 설치 패키지 서명 인증서 이름 |
NOTARY_PROFILE |
기존 notarytool 키체인 프로필 이름 |
GEUL_GUARD_RELEASE_DOWNLOAD_URL |
https://github.com/jureuk7/GeulGuard/releases/download/v버전/GeulGuard.pkg 형식의 공식 다운로드 주소 |
GEUL_GUARD_RELEASE_PATH |
선택 사항. 격리된 릴리스 작업 디렉터리 |
다운로드 주소의 버전은 빌드된 앱의 CFBundleShortVersionString과 같아야 한다.
latest/download 같은 가변 주소는 허용하지 않는다. 새 릴리스의
CFBundleVersion은 기존 버전보다 증가해야 한다.
릴리스 스크립트는 universal 앱과 서명된 PKG를 생성하고 공증·staple·Gatekeeper
검증을 수행한다. 그 후 Sparkle의 sign_update가 키체인의 기본 ed25519
계정으로 최종 PKG를 서명한다. 키 파일과 비공개 키 환경변수는 사용하지 않는다.
scripts/update-metadata.swift는 PKG 서명을 앱에 내장된 공개키로 검증한 뒤
dist/appcast.xml을 만든다. 따라서 키체인의 키와 앱의 공개키가 다르면 실패한다.
appcast는 빌드된 앱의 버전·최소 macOS, 최종 PKG의 크기·EdDSA 서명,
installationType="package"를 포함하는 단일 최신 버전 피드다. 기존 피드의
항목을 합치거나 배포하지 않는다. 공식 generate_appcast는 PKG 자동 생성을
지원하지 않으므로 이 전용 생성기를 사용한다.
- 서명·공증된 구버전과 신버전을 준비하고 테스트용 HTTPS 피드로 검증한다.
- 사용자 확인, 취소, 다운로드 실패, 서명 불일치, 조합 중 설치 시도를 확인한다.
- 관리자 인증 후 PKG 설치와 새 입력기의 활성화를 각각 확인한다. 조합 내용, 기존 입력 소스와 Karabiner 규칙이 보존되는지 확인한다.
- 공식 GitHub Release의 버전별 주소에 최종
GeulGuard.pkg를 게시한다. 이미 공개한 버전의 PKG 파일은 교체하지 않는다. - 해당 주소의 다운로드와 바이트 일치를 확인한 후 마지막으로 고정 HTTPS
주소에
dist/appcast.xml을 게시한다. 피드의 최소 OS보다 오래된 사용자를 계속 지원하려면 기존 호환 항목도 함께 보존해야 한다.
스크립트는 업로드, GitHub Release 생성, 사용자 Mac 설치 또는 설정 변경을 수행하지 않는다. ad-hoc 빌드의 실행만으로 PKG 업데이트 호환성을 주장하지 않는다. 실제 입력 앱별 호환성 범위는 compatibility.md를 따른다.
각 스크립트는 --help를 지원하며 알 수 없는 인수를 거절한다.
update-metadata.swift의 메타데이터 생성은 공개 RFC 8032 서명 벡터로 검사할 수
있다. 이 검사는 XML 생성과 서명 검증 검사이며, 실제 PKG 설치 검증을 대신하지
않는다.
참고: Sparkle PKG 업데이트, Sparkle 설치·서명 문서.
앱 시작 시 한 번 자동 확인하는 것이 기본값이며 설정에서 끌 수 있다. 주기적 자동 확인과 자동 다운로드·무인 설치는 비활성화한다. 사용자가 설치를 선택하면 종료 확인창을 표시한다. ABC가 현재 입력 소스이고 모든 입력 세션의 조합이 끝난 경우에만 앱 종료를 허용한다. API로 입력 소스를 강제로 전환하지 않으며, 기존 입력 소스와 Karabiner 설정도 변경하지 않는다.
| 보안·입력 불변식 | 구현 위치 |
|---|---|
| 피드·공개키 미설정 시 업데이터 비활성 | Sources/GeulGuardInput/UpdateController.swift의 UpdateConfiguration.isValid |
| 공식 버전별 PKG만 허용, Sparkle에서 다운로드 서명 검증 | UpdateController.updater(_:shouldProceedWithUpdate:updateCheck:), SUPublicEDKey |
| 키 입력·시스템 프로필을 업데이트 요청에 포함하지 않음 | UpdateController.init, allowedSystemProfileKeys(for:) |
| ABC 전환·조합 종료 확인 전 업데이트 종료 거부 | UpdateController.confirmTermination, GeulGuardInputController.hasPendingComposition |
| 비공개 키를 파일·환경변수에 수집하지 않음 | scripts/release-pkg.sh의 키체인 sign_update 호출 |
다음은 설치 검증 전의 초기 기록이다. 이후 확인 결과는 아래 후속 검증 기록을 따른다.
- Swift 테스트 32개 통과: 기존 조합·입력·표시 검사와 업데이트 설정·URL 제한· 모든 세션의 조합 종료 감지·시작 시 확인 기본값/비활성화 검사.
- 앱 번들 빌드와 Sparkle 내부 구성요소를 포함한
codesign --verify --deep --strict통과. 테스트 런너에는 Sparkle 프레임워크를 찾도록 전용 rpath를 추가했다. - 별도 QA 번들 ID로 실제 설정 화면을 열어 시작 시 확인 기본 켜짐, 끄기/켜기, 재실행 후 설정 유지, 미설정 빌드의 비활성 상태를 확인했다. 설정 화면에는 중복 수동 확인 버튼이 없다.
- 테스트용 공개키와 HTTPS 피드를 지정한 QA 앱에서 시작 시 마지막 확인 시간이 갱신되고, 옵션을 끈 뒤 재실행하면 갱신되지 않으며, 다시 켜고 실행하면 갱신되는 것을 확인했다. 테스트 피드 실패 시 시작을 막는 경고창은 없었다.
- 공개 RFC 8032 벡터로 appcast 생성/XML 문법, 잘못된 주소·HTTP 피드·변조된 데이터의 거부를 확인했다. 테스트 벡터는 배포용 키가 아니다.
초기 기록 시점에는 메뉴바 클릭, 새 버전 발견 안내, 관리자 인증을 거친 PKG 업데이트, 취소 후 재시도 및 업데이트 후 실제 입력 앱의 재활성화를 검증하지 않았다.
- 서명·공증된 0.6.0(빌드 7) 설치 후 0.6.1-rc.1(빌드 8) 업데이트를 사용자가 확인했고, 설치된 버전과 시스템 설치 경로의 새 프로세스를 확인했다.
- 운영 공개키와 Keychain 서명 키 일치, PKG 공증·staple·Gatekeeper 검증 및 공개 GitHub PKG 다운로드 바이트 일치를 확인했다.
- 로그아웃 안내를 포함한 소스에서 테스트 34개와 앱 빌드를 통과했다. 로그아웃 화면 확인은 사용자 보고이며, 에이전트가 실제 로그아웃을 실행한 것은 아니다.
- 모든 취소·재시도·오류 경로와 설치 후 앱별 입력 호환성을 실기 검증한 것은 아니다. 실제 검증 범위는 compatibility.md를 따른다.
이 기능이 포함된 버전에서 Sparkle 설치를 진행하면 대상 빌드 번호를 기록한다. 재실행한 앱의 빌드가 대상과 일치할 때 ‘나중에’와 ‘지금 로그아웃’ 안내를 한 번 표시한다. ‘나중에’가 기본 선택이며, 새 버전이 바로 적용되었다면 로그아웃하지 않고 사용할 수 있다. ‘지금 로그아웃’은 작업 저장을 안내한 뒤 macOS의 일반 로그아웃 확인 절차를 요청한다. 강제 종료하거나 즉시 세션을 종료하지 않는다. 요청이 실패하면 Apple 메뉴를 통한 수동 로그아웃을 안내한다.
설치 취소·실패 또는 대상 빌드와 다른 앱 실행에서는 설치 완료 안내를 표시하지 않는다. 이전 버전에는 설치 대상 기록 기능이 없으므로, 이 기능을 처음 설치하는 업데이트에서는 안내가 나오지 않을 수 있다. PKG를 직접 설치한 경우에도 별도 안내를 표시하지 않는다. 안내를 미뤘다면 필요할 때 Apple 메뉴에서 로그아웃한다.