Mảnh Cuối Cùng — Đặt Bản Cập Nhật Ở Đâu
Ở Phần 1 chúng ta đã chuẩn bị chứng chỉ Developer ID và công chứng, và ở Phần 2 chúng ta đã chuẩn bị khóa ký Sparkle. Nghĩa là chúng ta hiện có cách để ký ứng dụng, công chứng nó, và xác minh tính xác thực của bản cập nhật.
Nhưng vị trí được trỏ đến bởi SUFeedURL (https://updates.example.com/appcast.xml), mà chúng ta đã viết vào Info.plist của ứng dụng ở Phần 2, vẫn chưa có gì. Trong phần cuối này, chúng ta sẽ lưu trữ update feed cho vị trí đó và hoàn thiện cài đặt build, hoàn tất toàn bộ phần thiết lập một lần.
Như ở Phần 1 và 2, tất cả tên và domain (
FocusTimer,example.com,example-dev, v.v.) đều là giá trị mẫu. Trong thực tế, hãy thay thế bằng thông tin của bạn.
Tại Sao Cần Repository Cập Nhật Riêng
Để cập nhật tự động hoạt động, hai thứ phải có trên internet.
appcast.xml— update feed cho biết ứng dụng phiên bản nào là mới nhất và lấy ở đâu.dmg— file cài đặt ứng dụng thực tế
Có một ràng buộc quan trọng ở đây. Code Sparkle bên trong ứng dụng tải các file này bằng HTTPS GET đơn giản, không xác thực. Điều này có nghĩa là ứng dụng trên máy tính của người dùng phải có thể tải chúng trực tiếp, không cần bất kỳ thủ tục nào như đăng nhập.
Nhiều nhà phát triển giữ repository nguồn chính của ứng dụng ở chế độ riêng tư (private). Nhưng các file phát hành trong repository riêng tư yêu cầu xác thực, vì vậy Sparkle không thể tải chúng. Đó là lý do tại sao cấu trúc phổ biến là tách repository.
- Repository chính (ví dụ:
FocusTimer) — mã nguồn. Có thể để riêng tư. - Repository cập nhật (ví dụ:
FocusTimer-updates) — chỉ lưu trữappcast.xml. Phải là công khai (public).
Trong bài viết này, chúng ta sẽ chạy repository cập nhật trên GitHub Pages, dịch vụ lưu trữ tĩnh miễn phí của GitHub.
Bước 1 — Tạo Repository Cập Nhật Công Khai
Tạo repository mới trên GitHub.
- Nhấp New repository
- Tên:
FocusTimer-updates— tên này được dùng trong địa chỉ feed ngay sau đây, vì vậy hãy đặt chính xác, kể cả chữ hoa chữ thường. - Owner: tài khoản hoặc tổ chức của bạn (ví dụ:
example-dev) - Visibility: Public — bắt buộc, vì Sparkle phải tải nó mà không cần xác thực.
- Đánh dấu Add a README file (để có commit đầu tiên thuận tiện)
- Nhấp Create repository
Bước 2 — Kích Hoạt GitHub Pages
Phục vụ repository bạn vừa tạo như một site tĩnh.
- Vào Settings của repository → Pages trong menu bên trái
- Source: Deploy from a branch
- Branch: chọn
main/(root)→ Save - Sau 1–2 phút, nếu
https://example-dev.github.io/FocusTimer-updates/có thể truy cập được, nghĩa là thành công.
Tại điểm này bạn đã có địa chỉ công khai để đặt file cập nhật. Nhưng còn một bước nữa.
Bước 3 — Kết Nối Domain Tùy Chỉnh
Bạn có thể dùng địa chỉ GitHub Pages mặc định (example-dev.github.io/...) như vậy và nó sẽ hoạt động. Nhưng nếu bạn nhúng địa chỉ đó vào SUFeedURL của ứng dụng, bạn sẽ gặp rắc rối nếu sau này cần chuyển hosting từ GitHub Pages sang nơi khác — vì ứng dụng của mọi người dùng đã được phân phối vẫn trỏ đến địa chỉ cũ.
Giải pháp là chèn vào một domain bạn kiểm soát. Nếu bạn đặt SUFeedURL thành domain của mình, như https://updates.example.com/appcast.xml, thì khi bạn chuyển hosting sau này, bạn chỉ cần thay đổi một dòng cấu hình DNS và người dùng hiện tại tự động theo đến vị trí mới. Bạn chỉ cần thiết lập này một lần, và nó có hiệu lực mãi mãi.
3-1. Thêm Bản Ghi DNS
Trong màn hình cấu hình của nhà cung cấp DNS quản lý domain của bạn (example.com), thêm bản ghi sau.
| Trường | Giá trị |
|---|---|
| Type | CNAME |
| Name | updates |
| Value | example-dev.github.io |
| TTL | 3600 (mặc định) |
Điều này làm cho subdomain updates.example.com trỏ đến GitHub Pages.
3-2. Thêm File CNAME Vào Repository
Trong thư mục gốc của repository cập nhật (FocusTimer-updates), tạo file có tên CNAME với nội dung chỉ là một dòng — tên domain.
updates.example.com
Commit và push file này.
3-3. Đăng Ký Domain Với GitHub Pages
Trong trường Settings → Pages → Custom domain của repository, nhập updates.example.com và nhấp Save. GitHub tự động cấp chứng chỉ HTTPS, và sau khi cấp xong, hãy đánh dấu Enforce HTTPS.
3-4. Xác Nhận
Truyền bá DNS và cấp chứng chỉ thường mất khoảng 10 phút. Sau khi chờ một chút, hãy xác nhận bằng lệnh sau.
curl -I https://updates.example.com/appcast.xml
Nếu dòng đầu tiên của phản hồi hiển thị HTTP/2 200, mọi thứ đều ổn. (Nếu bạn chưa tải appcast.xml lên, bạn có thể nhận 404, nhưng kết nối domain và HTTPS có thể được xác nhận qua các đường dẫn khác. Điểm mấu chốt là https://updates.example.com trả về phản hồi.)
Để tham khảo, file cài đặt
.dmgthường được tải lên GitHub Releases chứ không phải GitHub Pages. Đặt các file nhị phân lớn trực tiếp vào repository sẽ làm nó cồng kềnh. Releases được phục vụ qua đường dẫn khác với Pages, vì vậy hãy để nguyên như vậy, không liên quan đến cài đặt domain tùy chỉnh ở trên.
Bước 4 — Giữ Repository Cập Nhật Ở Cục Bộ
Để chỉnh sửa và commit appcast.xml trong quá trình phát hành, bạn cần có repository cập nhật được checkout ở cục bộ. Nếu bạn clone nó đến vị trí thích hợp trong thư mục dự án chính (ví dụ: release/updates), các script build và phát hành có thể xử lý cả hai repository từ một nơi, điều này rất tiện lợi.
git clone [email protected]:example-dev/FocusTimer-updates.git release/updates
Khi bạn giữ một repository bên trong repository khác như thế này, hãy thêm release/updates/ vào .gitignore của repository chính để chúng không can thiệp lẫn nhau.
Bước 5 — ExportOptions.plist
Bây giờ là cài đặt phía build. Lệnh xcodebuild -exportArchive của Xcode đọc file cấu hình có tên ExportOptions.plist khi xuất archive thành .app để phân phối. Nội dung dành cho phân phối trực tiếp (kênh Developer ID) như sau.
<?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>method</key>
<string>developer-id</string>
<key>signingStyle</key>
<string>automatic</string>
<key>teamID</key>
<string>ABCDE12345</string>
</dict>
</plist>
method—developer-id. Có nghĩa là “phân phối trực tiếp, không phải Mac App Store”.signingStyle—automatic. Để Xcode tự động chọn và dùng chứng chỉ được cấp ở Phần 1.teamID— Team ID bạn đã ghi lại ở Phần 1.
Tạo file này một lần trong dự án (ví dụ: release/ExportOptions.plist) và tái sử dụng cho mọi lần phát hành.
Bước 6 — Kiểm Tra Cài Đặt Phía Ứng Dụng
Cuối cùng, hãy xem lại các cài đặt phải có trong dự án ứng dụng. Nếu bạn đang thêm Sparkle vào ứng dụng mới lần đầu, bạn có thể dùng danh sách này như một checklist.
Info.plist
Đây là các khóa Sparkle được thêm ở Phần 2. Info.plist phải bao gồm các khóa sau.
<key>SUFeedURL</key>
<string>https://updates.example.com/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=</string>
Kiểm tra kỹ rằng SUFeedURL trỏ đến domain tùy chỉnh được tạo ở Bước 3, và SUPublicEDKey khớp với khóa công khai được tạo ở Phần 2.
Quyền Hạn (Entitlements)
Nếu ứng dụng sử dụng App Sandbox, Sparkle cần các quyền ngoại lệ để có thể giao tiếp với các dịch vụ nội bộ để cài đặt bản cập nhật. Các mục sau được đưa vào file FocusTimer.entitlements.
<key>com.apple.security.app-sandbox</key><true/>
<key>com.apple.security.network.client</key><true/>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string>
</array>
Tại thời điểm build, Xcode tự động thay thế $(PRODUCT_BUNDLE_IDENTIFIER) bằng bundle identifier thực tế (com.example.FocusTimer). Để biết chi tiết về từng mục, hãy xem hướng dẫn sandboxing chính thức của Sparkle.
Sandboxing không bắt buộc đối với ứng dụng phân phối trực tiếp (sandbox là yêu cầu của Mac App Store). Nếu ứng dụng của bạn không dùng sandbox, không cần ngoại lệ
mach-lookupở trên. Tuy nhiên, vì cập nhật tự động dùng mạng, quyền hạnnetwork.clientvà Hardened Runtime phải được bật để công chứng.
Cài Đặt Build (Build Settings)
Trong cài đặt build của Xcode, hãy kiểm tra những điều sau.
CODE_SIGN_ENTITLEMENTS— đường dẫn đến file entitlements ở trênENABLE_HARDENED_RUNTIME = YES— điều kiện bắt buộc để công chứngENABLE_OUTGOING_NETWORK_CONNECTIONS = YES— cho phép truy cập mạng để kiểm tra cập nhật
Tổng Kết Series — Hoàn Tất Thiết Lập Một Lần
Phần thiết lập một lần cho phân phối trực tiếp qua ba phần hiện đã hoàn tất. Bạn hiện có:
- ✅ (Phần 1) Chứng chỉ Developer ID Application + thông tin xác thực để công chứng
- ✅ (Phần 2) Cặp khóa ký EdDSA Sparkle + sao lưu khóa riêng tư
- ✅ (Phần 3) Repository cập nhật công khai được kết nối với domain tùy chỉnh +
ExportOptions.plist+ cấu hình phía ứng dụng
Đây là tất cả những gì chỉ cần làm một lần. Bạn sẽ không cần làm lại công việc này mỗi lần phát hành phiên bản mới.
Từ đây trở đi, luồng phân phối phiên bản mới lặp lại theo cách tương tự mỗi lần — build archive → xuất với ExportOptions.plist → ký bằng chứng chỉ Developer ID → công chứng bằng notarytool → tạo .dmg → ký bằng khóa Sparkle → cập nhật appcast.xml → tải .dmg lên GitHub Release. Quy trình lặp đi lặp lại này có thể được tự động hóa phần lớn bằng một script duy nhất, và đó là chủ đề cho một bài viết khác.
Trong số đó, tạo
.dmgliên quan đến các yếu tố thiết kế như hình nền và vị trí icon, vì vậy nó được đề cập chi tiết trong series riêng Thiết Kế DMG Phân Phối Cho Ứng Dụng macOS Của Bạn.
Phân phối trực tiếp trông có vẻ đáng sợ lúc đầu vì có nhiều thứ cần thiết lập, nhưng điểm mấu chốt là “một khi thiết lập xong, nó cứ được tái sử dụng tiếp tục.” Đổi lại việc từ bỏ một phần sự tiện lợi của App Store, bạn kiểm soát được mọi phần của quy trình phân phối.