Aller au contenuAller à la navigationAller au contact

Applications

Alpage

Tous nos sites, en local, depuis la barre des menus.

Alpage fait tourner nos sites sur le Mac : chaque dossier devient une adresse en .test, en HTTPS, avec sa version de PHP et ses bases de données. Une app native, écrite dans Xcode, testée et livrée comme un produit.

Un site Laravel vit d'abord sur le Mac du développeur, bien avant sa mise en ligne. Pendant des années, cet environnement s'est assemblé à partir de plusieurs outils : un pour PHP, un pour les certificats, un autre pour les bases de données, chacun avec ses mises à jour et ses pannes.

En février 2026, nous avons écrit le nôtre. Alpage est une application macOS native en SwiftUI, accompagnée de la commande alpage pour le terminal. Elle fait tourner chaque jour tous nos projets, et se met à jour toute seule.

Pour
Développeurs PHP et Laravel
Plateformes
  • macOS
Technologies
  • Swift
  • SwiftUI
  • Swift Testing
  • Caddy
  • GitHub Actions
  • Sparkle

Un dossier, une adresse, et ça tourne.

La fenêtre Alpage et son menu dans la barre des menus

Un site par dossier

Alpage surveille les dossiers de projets. Un nouveau dossier devient aussitôt une adresse en .test, et dès que « laravel new » ou « composer create-project » a fini d'écrire, Alpage reconnaît le type de projet : Laravel, WordPress, PHP, site statique, application monopage, ou proxy vers le serveur de développement de Next.js ou Nuxt.

Chaque site répond aussi sur tous ses sous-domaines : une application multi-clients sert client.site.test en HTTPS sans rien configurer. Les certificats viennent d'une autorité locale, approuvée une seule fois par le système.

Le détail d’un site : type, HTTPS, version de PHP, sous-domaines
Un nouveau dossier devient un site en .test

PHP, au complet

Alpage installe PHP de la 8.1 à la 8.6, cette dernière dès sa release candidate. Chaque site peut fixer sa propre version, et Node.js se gère de la même façon.

Les binaires statiques publics laissaient de côté des extensions dont Laravel a besoin, comme sodium ou intl. Nous compilons donc les nôtres, dans un dépôt dédié et par l'intégration continue : plus de quarante extensions, Redis et Imagick compris, et Xdebug pour le débogage et la couverture de code. Chaque téléchargement est vérifié par son empreinte avant d'être installé.

Les versions de PHP installées, de 8.1 à 8.6
Les versions de Node.js
Les extensions compilées, vérifiées par leur empreinte
Un site passe à une autre version de PHP

Les bases de données, et le partage

MySQL, MariaDB et PostgreSQL démarrent depuis la barre des menus, chacun sur son port. Dans le terminal, « alpage db » crée, exporte et importe une base quel que soit le moteur, et mysql, psql ou pg_dump visent d'office le bon port. Pour les explorer, il y a Tables.

Un site se partage sur le réseau local avec un QR code, pour l'ouvrir sur un téléphone. Pour une démo à distance, un tunnel le rend accessible depuis Internet, jusqu'à ce qu'on l'arrête.

Les services et les bases de données
Le partage sur le réseau local, avec son QR code
Un tunnel public vers un site local

Des services qui se relèvent

Un service qui tombe redémarre seul, à son rythme. Un pool PHP repart en quelques millisecondes ; une base de données attend plus longtemps entre deux essais, parce qu'insister sur des fichiers à moitié ouverts aggrave les choses. Un service resté stable assez longtemps retrouve ses essais, et celui qui refuse de démarrer affiche les dernières lignes de son journal au lieu de s'éteindre sans rien dire.

Le superviseur est une fonction pure : un état, un événement et une politique donnent un nouvel état et une action. Chaque règle se vérifie par un test, sans lancer un seul processus : une base de données attend plus longtemps qu'un pool PHP, le proxy a droit au plus d'essais, et aucun plafond ne passe sous le premier délai.

Une base de données attend plus longtemps qu’un pool PHP avant de repartir

Et si vous tentiez de casser ce code ?

Alpage
SupervisorPolicy.swift
    SupervisorPolicyTests

      Sans droits administrateur

      Depuis août 2026, rien dans Alpage ne tourne en administrateur. Le serveur web écoute sur des ports ordinaires, et une règle du pare-feu de macOS, limitée à la boucle locale, lui renvoie le trafic des ports 80 et 443.

      Deux opérations seulement demandent le mot de passe, une fois : approuver le certificat local et diriger les adresses .test vers Alpage. « sudo alpage unelevate » défait tout, et les anciennes installations sont nettoyées au passage.

      Les ports 80 et 443 renvoyés vers Caddy, lancé sans droits administrateur
      Alpage/Elevation/PfAnchor.swift
      static func rules(httpFrom: Int, httpTo: Int, httpsFrom: Int, httpsTo: Int) -> String {
          """
          rdr pass on lo0 inet proto tcp from any to any port = \(httpFrom) -> 127.0.0.1 port \(httpTo)
          rdr pass on lo0 inet proto tcp from any to any port = \(httpsFrom) -> 127.0.0.1 port \(httpsTo)
      
          """
      }

      Le terminal, et le docteur

      « alpage php », « alpage composer » et « alpage laravel » lancent la version de PHP du dossier courant. « alpage secure --all » passe tous les sites en HTTPS en un seul rechargement, et le plugin Vite de Laravel y retrouve ses certificats comme avec Valet.

      « alpage doctor » passe toute la machine en revue : chaque problème vient avec la commande exacte qui le règle, et « alpage doctor fix » applique seul les réparations qui ne demandent aucun mot de passe. Un contrôle qui ne peut pas conclure ne dit rien : un avertissement sur lequel on ne peut pas agir ne sert à rien.

      La commande alpage dans le terminal
      « alpage doctor » trouve un problème, « alpage doctor fix » le règle

      Écrite dans Xcode

      Alpage est une app SwiftUI. Une fenêtre et un menu dans la barre des menus partagent les mêmes gestionnaires d'état, en Swift moderne : @Observable, async/await, sans Combine.

      La ligne de commande est un second produit, compilé avec Swift Package Manager. Elle reprend les modèles et les générateurs de configuration de l'app, si bien qu'un site sécurisé dans le terminal apparaît sécurisé dans la fenêtre.

      L’app et la commande partagent les modèles et les générateurs de configuration
      L’anatomie de la fenêtre

      Plus de six cents tests

      Alpage touche au DNS, au pare-feu, aux certificats et aux bases de données de la machine. Ces décisions sont isolées dans des fonctions pures, que plus de six cents tests Swift Testing exercent un à un : le diagnostic du docteur, les règles du pare-feu, les manifestes d'empreintes, la reprise des services.

      Chaque push lance la compilation de la ligne de commande, celle de l'app, puis toute la suite. C'est elle qui a révélé qu'un appel bloquant pouvait épuiser les threads de Swift Concurrency : la suite se figeait, et le docteur, qui lance ses contrôles en parallèle, courait le même risque chez l'utilisateur.

      Le menu de la barre des menus change la version de PHP
      Les cibles du projet et les dossiers de tests
      Les 603 tests passent au vert
      AlpageTests
      @Test("The delay doubles with each attempt")
      func delayDoubles() {
          let first = step(.starting(attempt: 1), .startTimedOut)
          let second = step(.starting(attempt: 2), .startTimedOut)
      
          #expect(first.action == .waitThenRetry(delay: .seconds(1)))
          #expect(second.action == .waitThenRetry(delay: .seconds(2)))
      }
      
      @Test("A mismatching digest throws and deletes the artifact")
      func mismatchDeletesArtifact() throws {
          try withTempFile(contents: Data("abc".utf8)) { url in
              #expect(throws: ChecksumError.self) {
                  try ChecksumVerifier.verify(fileAt: url, expected: .sha256(emptySha256))
              }
              #expect(!FileManager.default.fileExists(atPath: url.path))
          }
      }

      Une app maison, livrée comme un produit.

      Versions de PHP
      8.1–8.6
      Versions de PHP
      Extensions compilées
      40+
      Extensions compilées
      Tests automatisés
      600+
      Tests automatisés
      Versions publiées depuis juillet
      12
      Versions publiées depuis juillet

      Du tag à la mise à jour

      Une version se publie en poussant un tag. L'intégration continue construit l'app en Release, la signe avec le certificat Developer ID, la fait notariser par Apple et agrafe le ticket. L'archive est ensuite signée pour Sparkle, publiée, et le flux des mises à jour se régénère en gardant les versions précédentes.

      Le code reste privé, les binaires vivent dans un dépôt public. Les Mac où Alpage est installée vérifient chaque jour s'il y a du nouveau : de la v1.1 à la v1.6, douze versions sont arrivées ainsi entre juillet et septembre 2026, sans réinstallation.

      À chaque push, puis à chaque tag
      .github/workflows/release.yml
      - name: Notarize and staple
        run: |
          ditto -c -k --keepParent "$RUNNER_TEMP/export/Alpage.app" "$RUNNER_TEMP/Alpage-notarize.zip"
          xcrun notarytool submit "$RUNNER_TEMP/Alpage-notarize.zip" \
            --key "$RUNNER_TEMP/AuthKey.p8" \
            --key-id "$APPLE_API_KEY_ID" \
            --issuer "$APPLE_API_ISSUER_ID" \
            --wait
          xcrun stapler staple "$RUNNER_TEMP/export/Alpage.app"
      
      - name: Sign update and generate appcast
        run: |
          "$RUNNER_TEMP/sparkle/bin/generate_appcast" \
            --ed-key-file "$RUNNER_TEMP/ed_private_key" \
            --download-url-prefix "https://github.com/${RELEASES_REPO}/releases/download/${GITHUB_REF_NAME}/" \
            releases/
      Douze versions de juillet à septembre 2026
      Alpage se met à jour toute seule

      Un outil maison qui vous manque ?Ou le projet que vous n’osez pas lancer.

      Un gros projet se mène comme un petit : une étape à la fois. La première, c’est un café à Vevey ou un appel.

      Parlons-en