Wharfy, Mac App Store dışında dağıtılıyor. Bu bize çok şey kazandırıyor — inceleme kuyruğu yok, uygulamanın tek işi başka süreçlerin soketlerini incelemekken sandbox'la boğuşmak yok — ve tek bir şeye mal oluyor: uygulamayı bizim yerimize kimse güncellemiyor.
Bunu kurmak gerçekte neye mal oldu, onu anlatıyorum.
Neden App Store değil
Wharfy, geliştirme portlarını dinleyen her süreci listeliyor ve hangi projenin, hangi yapay zekâ ajanının başlattığını gösteriyor. Başka bir sürecin açık soketlerini okumak, App Store sandbox'ının tam olarak engellemek için var olduğu şey. Bu uygulamanın oraya girebilecek bir versiyonu yoktu; karar kendiliğinden verildi.
Mağaza dışında platform sana noter onayı (notarization) veriyor, başka bir şey değil. Noter onayı ikili dosyanın zararlı yazılım olmadığını kanıtlar. Dört hafta önce 0.1.2 indirmiş birine 0.1.5'in nasıl ulaşacağı hakkında hiçbir şey söylemez.
Tek paragrafta Sparkle
Sparkle, mağaza dışı hemen her Mac uygulamasının kullandığı güncelleme çatısı. Model basit: sürümlerini listeleyen bir XML akışı — appcast — yayınlarsın. Uygulama onu yoklar, sürümleri karşılaştırır, yeni derlemeyi indirir, imzayı doğrular ve çıkışta kendini değiştirir. 2006'dan beri bu işi yapıyor ve 2.x hattı bakımı yapılan bir kalıntı değil, gerçek bir yeniden yazım.
İmza anahtarı güvenlik modelinin tamamı
Sparkle 2 güncellemeleri EdDSA ile imzalıyor. Anahtar çiftini bir kez üretiyorsun:
./bin/generate_keys
Özel anahtar giriş anahtarlığına gidiyor. Genel anahtar Info.plist içine SUPublicEDKey olarak yazılıyor. Her appcast girdisi indirmenin edSignature'ını taşıyor ve uygulama, gömülü genel anahtarla doğrulanmayan hiçbir şeyi kabul etmiyor.
Sonucu açıkça söylemekte fayda var: o özel anahtarı elinde tutan, uygulamanın kurulu her kopyasına kod gönderebilir. "Kötü bir indirme sunabilir" değil — kullanıcının makinesinde, senin imzanla, tam da sen öyle söylediğin için güvendikleri bir yoldan kod çalıştırabilir. Bu, yalnızca Apple'a verdiğin şeyi imzalayan noter kimlik bilgilerinden daha tehlikeli bir sır.
Bizimki depoda değil, CI'da değil, başka bir şeyle paylaşılan bir parola yöneticisinde değil.
Appcast
Appcast, Sparkle ad alanı taşıyan bir RSS akışı. Her sürüm için bir girdi:
<item>
<title>0.1.5</title>
<sparkle:version>15</sparkle:version>
<sparkle:shortVersionString>0.1.5</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
<enclosure
url="https://wharfy.dev/releases/Wharfy-0.1.5.dmg"
sparkle:edSignature="…"
length="8123456"
type="application/octet-stream" />
</item>
Kafa karışıklığının çoğu iki alandan çıkıyor. sparkle:version derleme numarası — monoton artan bir tam sayı ve Sparkle'ın gerçekten karşılaştırdığı tek şey. shortVersionString insanın gördüğü "0.1.5" ve hiç kimse tarafından karşılaştırılmıyor. Bunları ters yazarsan güncellemeler ya hiç görünmez ya da sonsuza kadar görünür.
minimumSystemVersion unutulan alan. O olmadan eski bir macOS'taki kullanıcıya açılmayacak bir derleme sunulur ve çalışan kurulumu, açılışta çöken bir sürümle değiştirilir.
Ters giden üç şey
DMG noter onaylıydı, içindeki uygulama zımbalanmamıştı. Noter onayı (notarize) ve zımbalama (staple) ayrı adımlar. Noter onayı Apple'a derlemeyi bildirir; zımbalama, ortaya çıkan bileti dosyaya iliştirir ki Gatekeeper çevrimdışı doğrulayabilsin. Noter onaylı ama zımbalanmamış bir DMG, bileti önbellekte olan derleme makinesinde çalışır — ve tertemiz bir Mac'te korkutucu bir uyarı gösterir. Kendi bilgisayarından göremeyeceğin bir hata, ki hataların en kötüsü budur.
Appcast yanlış içerik tipiyle sunuldu. Statik sunucumuz .xml uzantılı dosya için text/html tahmin etti. Sparkle'ın ayrıştırıcısı sessizce başarısız oldu ve uygulama "güncelleme yok" dedi. Bunun bir hata penceresi yok — güncel olmakla birebir aynı görünüyor. Artık CI'da sadece dosya içeriğini değil, yanıt başlıklarını da doğruluyoruz.
Güncelleme, menü çubuğu paneli açıkken çalıştı. Menü çubuğu uygulamasının kapatılacak penceresi yok, dolayısıyla Sparkle'ın alışıldık "çık ve yeniden başlat" akışının tutunacağı bir dal yok. İlk sürüm, ekranda duran bir panelin altındaki ikili dosyayı değiştirdi; uygulama panelin arkasında yeniden başladı ve kullanıcıya, çalışan bir uygulamanın üstünde asılı duran ölü bir panel kaldı. Artık kontrolü Sparkle'a devretmeden önce paneli kapatıyoruz.
Sonuncusu genel dersi veriyor: Sparkle'ın varsayılanları pencereli bir belge uygulaması varsayıyor. Menü çubuğu uygulaması başka bir canlı ve uygulamanın yaşam döngüsüyle ilgili her varsayımın yeniden kontrol edilmesi gerekiyor.
Sürüm çıkmak
Sürüm çıkmak tek komut. Derliyor, imzalıyor, noter onayına gönderiyor, zımbalıyor, edSignature üretiyor, appcast girdisini başa ekliyor ve yüklüyor. Bu süsleme değil — elle adım içeren bir sürüm süreci, atlanan bir sürüm sürecidir; ve güncelleme çıkarmayı bırakmış bir Mac uygulaması, kullanıcının makinesinde tek bir CVE uzaklıkta duran bir yüktür.
Wharfy'nin CLI yarısı npm'e OIDC güvenilir yayımlama ile gidiyor; bu da uzun ömürlü jetonu denklemden tamamen çıkarıyor. O ayrı bir yazı.
Değdi mi?
Bakımını sürdürmeyi planladığın her uygulama için evet, hem de fark açık. Alternatifi bir indirme sayfası ve insanların oraya bakacağı umudu. İki günlük iş, salı günü bulduğumuz bir hatayı çarşamba günü kullanıcının makinesinde düzeltmiş olma yeteneğini satın aldı — ki mağaza dışında dağıtmanın asıl sebebi bu ve takası değerli kılan da bu.