La dernière pièce — Où mettre les mises à jour

En Partie 1 nous avons préparé le certificat Developer ID et la notarisation, et en Partie 2 nous avons préparé la clé de signature Sparkle. Cela signifie que nous avons maintenant un moyen de signer l’application, de la notariser, et de vérifier l’authenticité des mises à jour.

Mais l’emplacement pointé par SUFeedURL (https://updates.example.com/appcast.xml), que nous avons écrit dans le Info.plist de l’application en Partie 2, ne contient encore rien. Dans cette dernière partie, nous allons héberger le flux de mises à jour qui ira à cet endroit et finir les paramètres de build, ce qui complètera l’intégralité de la configuration initiale.

Comme dans les Parties 1 et 2, tous les noms et domaines (FocusTimer, example.com, example-dev, etc.) sont des valeurs d’exemple. En pratique, remplacez-les par vos propres informations.

Pourquoi avoir un dépôt de mises à jour séparé

Pour que les mises à jour automatiques fonctionnent, deux éléments doivent être disponibles sur Internet.

  • appcast.xml — le flux de mises à jour qui indique à l’application quelle version est la dernière et où la récupérer
  • .dmg — le fichier d’installation réel de l’application

Il y a une contrainte importante ici. Le code Sparkle à l’intérieur de l’application récupère ces fichiers avec une requête HTTPS GET simple et non authentifiée. Cela signifie que l’application sur l’ordinateur de l’utilisateur doit pouvoir les télécharger directement, sans aucune procédure telle qu’une connexion.

De nombreux développeurs gardent le dépôt source principal de l’application en privé. Mais les fichiers de publication dans un dépôt privé nécessitent une authentification, donc Sparkle ne peut pas les récupérer. C’est pourquoi une structure courante consiste à séparer les dépôts.

  • Dépôt principal (par ex., FocusTimer) — le code source. Peut être gardé privé.
  • Dépôt de mises à jour (par ex., FocusTimer-updates) — héberge uniquement appcast.xml. Doit être public.

Dans cet article, nous gérerons le dépôt de mises à jour sur GitHub Pages, l’hébergement statique gratuit de GitHub.

Étape 1 — Créer le dépôt public de mises à jour

Créez un nouveau dépôt sur GitHub.

  1. Cliquez sur New repository
  2. Nom : FocusTimer-updates — ce nom est utilisé dans l’adresse du flux sous peu, définissez-le précisément, y compris la casse.
  3. Propriétaire : votre compte ou une organisation (exemple : example-dev)
  4. Visibilité : Public — requis, puisque Sparkle doit le récupérer sans authentification.
  5. Cochez Add a README file (pour un premier commit pratique)
  6. Cliquez sur Create repository

Étape 2 — Activer GitHub Pages

Servez le dépôt que vous venez de créer comme site statique.

  1. Allez dans Settings du dépôt → Pages dans le menu de gauche
  2. Source : Deploy from a branch
  3. Branch : sélectionnez main / (root)Save
  4. Après 1-2 minutes, si https://example-dev.github.io/FocusTimer-updates/ devient accessible, c’est réussi.

À ce stade, vous avez déjà une adresse publique où vous pouvez mettre des fichiers de mise à jour. Mais il reste encore une étape.

Étape 3 — Connecter un domaine personnalisé

Vous pouvez utiliser l’adresse GitHub Pages par défaut (example-dev.github.io/...) telle quelle et ça fonctionnera. Mais si vous intégrez cette adresse dans SUFeedURL de l’application, vous aurez des problèmes si vous devez un jour déplacer l’hébergement de GitHub Pages vers un autre endroit — car l’application de chaque utilisateur déjà distribué pointe toujours vers l’ancienne adresse.

La solution consiste à insérer une couche d’un domaine que vous contrôlez. Si vous définissez SUFeedURL sur votre propre domaine, comme https://updates.example.com/appcast.xml, lorsque vous déplacez l’hébergement plus tard, vous ne changez qu’une ligne de configuration DNS et les utilisateurs existants suivent automatiquement vers le nouvel emplacement. Vous ne devez effectuer cette configuration qu’une seule fois, et elle reste valide indéfiniment.

3-1. Ajouter un enregistrement DNS

Dans l’écran de configuration du fournisseur DNS qui gère votre domaine (example.com), ajoutez l’enregistrement suivant.

ChampValeur
TypeCNAME
Nameupdates
Valueexample-dev.github.io
TTL3600 (par défaut)

Cela fait pointer le sous-domaine updates.example.com vers GitHub Pages.

3-2. Ajouter un fichier CNAME au dépôt

À la racine du dépôt de mises à jour (FocusTimer-updates), créez un fichier appelé CNAME dont le contenu n’est qu’une seule ligne — le domaine.

updates.example.com

Committez et poussez ce fichier.

3-3. Enregistrer le domaine avec GitHub Pages

Dans le champ Settings → Pages → Custom domain du dépôt, entrez updates.example.com et cliquez sur Save. GitHub émet automatiquement un certificat HTTPS, et une fois émis, cochez Enforce HTTPS.

3-4. Vérification

La propagation DNS et l’émission du certificat prennent généralement environ 10 minutes. Après un court délai, vérifiez avec la commande suivante.

curl -I https://updates.example.com/appcast.xml

Si la première ligne de la réponse affiche HTTP/2 200, tout est en ordre. (Si vous n’avez pas encore téléversé appcast.xml, vous pouvez obtenir un 404, mais la connexion au domaine et HTTPS peut être confirmée par d’autres chemins. L’essentiel est que https://updates.example.com réponde.)

Pour information, le fichier d’installation .dmg est généralement téléversé dans GitHub Releases plutôt que dans GitHub Pages. Mettre de gros binaires directement dans un dépôt le gonfle. Les Releases sont servies via un chemin différent de Pages, donc laissez-les tels quels, sans rapport avec la configuration du domaine personnalisé ci-dessus.

Étape 4 — Garder le dépôt de mises à jour en local

Pour modifier et committer appcast.xml lors du travail de publication, vous devez également avoir le dépôt de mises à jour récupéré en local. Si vous le clonez à un emplacement approprié dans le dossier du projet principal (par ex., release/updates), les scripts de build et de publication peuvent gérer les deux dépôts depuis un seul endroit, ce qui est pratique.

git clone [email protected]:example-dev/FocusTimer-updates.git release/updates

Lorsque vous gardez un dépôt dans un autre comme cela, ajoutez release/updates/ au .gitignore du dépôt principal pour qu’ils n’interfèrent pas l’un avec l’autre.

Étape 5 — ExportOptions.plist

Passons maintenant aux paramètres côté build. La commande xcodebuild -exportArchive de Xcode lit un fichier de configuration appelé ExportOptions.plist lors de l’exportation d’une archive en un .app pour la distribution. Le contenu pour la distribution directe (canal Developer ID) est le suivant.

<?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>
  • methoddeveloper-id. Cela signifie « distribution directe, pas le Mac App Store. »
  • signingStyleautomatic. Permet à Xcode de sélectionner et d’utiliser automatiquement le certificat émis en Partie 1.
  • teamID — le Team ID que vous avez noté en Partie 1.

Créez ce fichier une fois dans le projet (par ex., release/ExportOptions.plist) et réutilisez-le pour chaque publication.

Étape 6 — Vérifier les paramètres côté application

Enfin, passons en revue les paramètres qui doivent être présents dans le projet d’application lui-même. Si vous ajoutez Sparkle à une nouvelle application pour la première fois, vous pouvez utiliser cette liste comme une checklist.

Info.plist

Ce sont les clés Sparkle ajoutées en Partie 2. Le Info.plist doit inclure les clés suivantes.

<key>SUFeedURL</key>
<string>https://updates.example.com/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=</string>

Vérifiez doublement que SUFeedURL pointe vers le domaine personnalisé créé à l’Étape 3, et que SUPublicEDKey correspond à la clé publique générée en Partie 2.

Entitlements (droits d’accès)

Si l’application utilise l’App Sandbox, Sparkle a besoin d’entitlements d’exception pour pouvoir communiquer avec les services internes afin d’installer les mises à jour. Les entrées suivantes vont dans le fichier 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>

Au moment du build, Xcode remplace automatiquement $(PRODUCT_BUNDLE_IDENTIFIER) par l’identifiant de bundle réel (com.example.FocusTimer). Pour plus de détails sur chaque entrée, consultez le guide officiel de sandboxing de Sparkle.

Le sandboxing n’est pas obligatoire pour une application distribuée directement (le sandbox est une exigence du Mac App Store). Si votre application n’utilise pas le sandbox, l’exception mach-lookup ci-dessus n’est pas nécessaire. Cependant, puisque les mises à jour automatiques utilisent le réseau, l’entitlement network.client et le Hardened Runtime doivent être activés pour la notarisation.

Paramètres de build (Build Settings)

Dans les paramètres de build de Xcode, vérifiez les points suivants.

  • CODE_SIGN_ENTITLEMENTS — le chemin vers le fichier entitlements ci-dessus
  • ENABLE_HARDENED_RUNTIME = YES — une condition requise pour la notarisation
  • ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES — autorise l’accès réseau pour la vérification des mises à jour

Récapitulatif de la série — Configuration initiale terminée

La configuration initiale en trois parties pour la distribution directe est maintenant terminée. Vous avez maintenant en main les éléments suivants.

  • (Partie 1) Un certificat Developer ID Application + des identifiants pour la notarisation
  • (Partie 2) Une paire de clés EdDSA Sparkle + une sauvegarde de la clé privée
  • (Partie 3) Un dépôt public de mises à jour connecté à un domaine personnalisé + ExportOptions.plist + configuration côté application

Voilà tout ce qui ne doit être fait qu’une seule fois. Vous n’aurez pas à refaire ce travail à chaque fois que vous publiez une nouvelle version.

À partir de maintenant, le flux de distribution d’une nouvelle version se répète de manière similaire à chaque fois — builder l’archive → exporter avec ExportOptions.plist → signer avec le certificat Developer ID → notariser avec notarytool → créer le .dmg → le signer avec la clé Sparkle → mettre à jour appcast.xml → téléverser le .dmg dans une GitHub Release. Ce processus répétitif peut être en grande partie automatisé avec un seul script, et c’est un sujet pour un article séparé.

Parmi ceux-ci, la création du .dmg implique des éléments de design tels que les images de fond et le placement des icônes, il est donc traité en détail dans la série séparée Concevoir un DMG de distribution pour votre application macOS.

La distribution directe peut sembler décourageante au début à cause de tout ce qu’il faut configurer, mais l’essentiel est que « une fois configuré, ça se réutilise indéfiniment. » En contrepartie d’une partie de la commodité de l’App Store, vous obtenez le contrôle total sur chaque partie du processus de distribution.

Références