Serveur Go qui construit un APK Android WebView à partir d'une simple URL, en déléguant la compilation à un workflow GitHub Actions. On envoie une URL + un nom + un package, on récupère un APK installable qui affiche le site en plein écran.
Client ──POST /api/builds──▶ Serveur Go ──workflow_dispatch──▶ GitHub Actions
◀─── build_id ─────── ◀── suivi du run ──── (Gradle → APK)
──GET /api/builds/{id}──▶ (poll statut)
──GET .../apk──▶ APK ◀── artifact du run ────────────────
- Serveur Go (
cmd/server,internal/*) : API HTTP, déclenche le workflow, suit l'exécution du run et récupère l'APK depuis l'artifact. - Workflow (
.github/workflows/build-apk.yml) : reçoit les paramètres, injecte URL/nom/package dans le template, compile un APK debug, l'expose en artifact. - Template Android (
android-template/) : projet Gradle minimal avec uneActivityWebView (JS activé, gestion du bouton retour, sauvegarde d'état).
Corrélation serveur ↔ run : le serveur génère un build_id, le workflow nomme son
run apk-<build_id> (run-name), et le serveur retrouve le run par ce nom.
- Un dépôt GitHub contenant ce projet (workflow +
android-template/). - Un token GitHub (PAT classique ou fine-grained) avec Actions: read and write sur ce dépôt.
- Go 1.26+ pour lancer le serveur (ou Docker).
Copier .env.example puis renseigner les variables :
| Variable | Rôle | Défaut |
|---|---|---|
PORT |
Port HTTP | 8080 |
GITHUB_TOKEN |
Token avec droit Actions RW | — |
GITHUB_OWNER |
Propriétaire du dépôt | — |
GITHUB_REPO |
Nom du dépôt | — |
GITHUB_WORKFLOW |
Fichier de workflow | build-apk.yml |
GITHUB_REF |
Branche de build | main |
RELEASES_DIR |
Dossier où sont écrits les APK produits | releases |
DB_PATH |
Fichier SQLite (builds + historique par utilisateur) | data.db |
BASE_URL |
URL publique de l'app (sert au redirect OAuth Google) | http://localhost:$PORT |
GOOGLE_LOGIN_CLIENT_ID / GOOGLE_LOGIN_CLIENT_SECRET |
Connexion Google (optionnelle, voir ci-dessous) | — |
SESSION_SECRET |
Signe les cookies de session (auto-généré si absent) | — |
Sans configuration, l'app fonctionne comme avant : chaque navigateur garde son propre
historique en local (localStorage), partagé par tout le monde qui utilise ce
navigateur. En connectant un compte Google, l'historique des builds est rattaché
au compte et persiste en base (SQLite), accessible depuis n'importe quel navigateur.
Pour l'activer, créer un client OAuth dédié :
- console.cloud.google.com → créer/sélectionner un projet.
- APIs & Services → OAuth consent screen : configurer l'écran de consentement
(type External suffit pour un usage personnel/test), scopes par défaut
(
openid,email,profile) — pas besoin d'ajouter de scope sensible. - APIs & Services → Credentials → Create credentials → OAuth client ID, type Web application.
- Dans Authorized redirect URIs, ajouter exactement
BASE_URL+/auth/google/callback(ex.http://localhost:8080/auth/google/callbacken local). Cette valeur doit correspondre au caractère près àBASE_URLdans.env. - Copier le Client ID et le Client Secret générés dans
.env(GOOGLE_LOGIN_CLIENT_ID/GOOGLE_LOGIN_CLIENT_SECRET— préfixésLOGIN_pour ne pas entrer en collision avec un éventuel autre usage deGOOGLE_CLIENT_ID/SECRETdans le même.env).
Tant que l'écran de consentement reste en mode Testing, seuls les comptes Google ajoutés comme Test users (même page) peuvent se connecter.
export $(grep -v '^#' .env | xargs) # ou: source .env
go run ./cmd/serverPuis ouvre http://localhost:8080/ : une interface web (thème sombre sableux) permet de saisir une URL, un nom et un package, de lancer le build et de suivre son avancement jusqu'au téléchargement de l'APK. L'API reste utilisable directement (voir plus bas).
Ou via Docker :
docker build -t android-builder .
docker run --rm -p 8080:8080 --env-file .env android-buildercurl -X POST http://localhost:8080/api/builds \
-H 'Content-Type: application/json' \
-d '{
"url": "https://news.ycombinator.com",
"app_name": "Hacker News",
"package": "com.exemple.hn",
"version_name": "1.0",
"version_code": "1"
}'
# → 202 {"id":"a1b2c3...","status":"pending"}Champs : url (http/https, requis), app_name (requis), package
(com.exemple.app, optionnel — déduit du nom de l'app si absent, ex.
« Mon Application » → app.webview.monapplication), version_name (déf. 1.0),
version_code (entier, déf. 1).
Au lieu d'une URL distante, on peut uploader le dist/ d'un projet web (zip
contenant index.html). Les fichiers sont embarqués dans l'APK
(assets/www/) et chargés via file:///android_asset/www/index.html — l'app
fonctionne hors-ligne.
Dans l'interface, on peut fournir soit un dossier (sélection ou glisser-déposer
du dossier dist/), soit un .zip. Le dossier est zippé côté navigateur (sans
dépendance) avant l'envoi ; l'API, elle, reçoit toujours un zip.
curl -X POST http://localhost:8080/api/builds \
-F app_name="Mon SPA" \
-F package="com.exemple.spa" \
-F dist=@dist.zipMultipart form-data : dist (zip requis, ≤ 80 Mo), app_name (requis),
package (optionnel), version_name, version_code. Le zip est relayé au
workflow via une release GitHub temporaire (assets-<build_id>), supprimée
automatiquement après le build.
En multipart (URL ou bundle), on peut aussi personnaliser l'apparence :
icon: fichier PNG (≤ 3 Mo, idéalement carré 512×512) → devient l'icône du lanceur. L'interface web fournit un outil de recadrage/zoom qui produit ce PNG.splash_bg: couleur de fond de l'écran de démarrage au format#RRGGBB(défaut#14110b). Le splash affiche l'icône centrée sur ce fond au lancement.
curl -X POST http://localhost:8080/api/builds \
-F app_name="Mon App" -F url="https://exemple.com" \
-F splash_bg="#1b2a4a" -F icon=@icon.pngL'interface web propose un aperçu façon téléphone en direct (fond + icône + nom).
Le serveur génère une vraie miniature de chaque build via un Chrome headless
local (GET /api/builds/{id}/thumb), affichée sur les cartes de l'interface :
- Mode URL : screenshot de la page — fonctionne aussi pour les URL locales/LAN (le serveur est sur le même réseau), contrairement à un service externe.
- Mode bundle : le dist est servi sur un serveur statique local puis capturé.
Chrome est détecté automatiquement (macOS/Linux) ; sinon, définir CHROME_PATH.
Si Chrome est absent, les miniatures sont simplement désactivées (repli sur un
dégradé, ou sur un screenshot public via mShots pour les URL publiques).
Les téléchargements déclenchés dans la page (liens de fichiers, blob:, data:)
sont enregistrés dans le dossier public Téléchargements du téléphone, via le
DownloadManager Android (avec notification). Les cookies de session sont
transmis pour les fichiers protégés. Aucune permission n'est demandée sur
Android 10+ ; sur Android ≤ 9, la permission de stockage est demandée à la volée.
hide_scrollbar:true/false(défautfalse) — masque la barre de défilement de la WebView. Disponible en JSON et en multipart, et via un interrupteur dans les Options avancées de l'interface.
Le dist est servi dans l'app via un domaine virtuel https interne
(WebViewAssetLoader), pas via file://. Les chemins absolus des SPA
(/assets/…, /logo.png) et les modules ES (<script type="module">)
fonctionnent donc sans configuration — un build Vite/React/Vue standard marche
directement.
curl http://localhost:8080/api/builds/a1b2c3...
# → {"id":"...","status":"building","run_url":"https://github.com/.../actions/runs/123", ...}Statuts : pending → building → success (ou failed, voir champ error).
Le statut inclut aussi progress (0-100), current_step et la liste des steps
du run GitHub Actions.
curl -N http://localhost:8080/api/builds/a1b2c3.../eventsFlux Server-Sent Events poussant l'état complet du build (progression, étape courante, statut) à chaque changement, jusqu'à la fin. C'est ce que l'interface web utilise pour afficher la barre de progression et la liste des étapes en direct.
curl -L -o app.apk http://localhost:8080/api/builds/a1b2c3.../apkChaque APK réussi est aussi écrit sur disque dans le dossier releases/
(configurable via RELEASES_DIR), nommé <nom-app>-<build_id>.apk. Le endpoint
de téléchargement sert directement ce fichier.
Le workflow est optimisé pour que les builds après le premier soient rapides :
- App sans dépendance :
Activityframework pure (pas d'AppCompat/AndroidX) → rien à télécharger ni à compiler côté bibliothèques. - Caches Gradle persistés entre runs par
gradle/actions/setup-gradle(dépendances + build cache + configuration cache). - Compilation Kotlin réutilisée : le code ne change pas d'un build à l'autre (seules les ressources — URL, nom, package — changent), donc sa sortie est mise en cache et n'est pas recompilée.
En pratique : 1er build ~2-3 min (remplissage des caches), builds suivants ~1 min.
- APK debug signé avec la clé debug Android → installable, mais pas
publiable sur le Play Store. Ajouter un keystore +
signingConfigsrelease (secrets GitHub) pour un APK/AAB signé. - Icône : icône système par défaut. Passer une
icon_urlen input et générer les mipmaps dans le workflow pour une icône personnalisée. - Webhook au lieu du polling : faire notifier le serveur par le workflow
(
repository_dispatchou endpoint HTTP) en fin de build. - Sécurité : la connexion Google protège l'historique (privé par compte), mais pas encore l'API de déclenchement elle-même — n'importe qui connaissant l'URL peut toujours lancer un build. À ajouter avant toute exposition publique large.
cmd/server/ point d'entrée
internal/config/ chargement de la config (env)
internal/ghclient/ appels API GitHub (dispatch, runs, artifacts)
internal/buildstore/ store des builds (SQLite, historique par utilisateur)
internal/auth/ connexion Google (OAuth2) optionnelle
internal/server/ API HTTP + orchestration du suivi
.github/workflows/ build-apk.yml
android-template/ projet Android WebView (template)