feat(ci): build, teste et empaquette un APK Android signé sur chaque push main
Some checks failed
CI / build-android (push) Blocked by required conditions
CI / build (push) Successful in 3m21s
CI / lint (push) Successful in 3m31s
CI / intent-service-test (push) Successful in 4m51s
CI / e2e (push) Successful in 10m34s
CI / test (push) Failing after 14m37s
Some checks failed
CI / build-android (push) Blocked by required conditions
CI / build (push) Successful in 3m21s
CI / lint (push) Successful in 3m31s
CI / intent-service-test (push) Successful in 4m51s
CI / e2e (push) Successful in 10m34s
CI / test (push) Failing after 14m37s
Nouveau job build-android dans ci.yml, gardé par needs: sur les 5 jobs existants (lint/test/intent-service-test/build/e2e) et limité à main via if: github.ref == 'refs/heads/main' — c'est ce qui garantit que l'APK n'est empaqueté qu'une fois la suite de tests existante verte, pas juste construit en parallèle d'elle. APK signé avec un vrai keystore de release (jamais généré par la CI — Android refuse une mise à jour signée par une clé différente de celle déjà installée) : ajoute signingConfigs.release à build.gradle, lu depuis l'environnement (ANDROID_KEYSTORE_PATH/_PASSWORD, ANDROID_KEY_ALIAS/_PASSWORD), jamais codé en dur. *.jks/*.keystore désormais réellement ignorés par git (étaient commentés dans le .gitignore généré par Capacitor). README : documente les 5 secrets Forgejo requis et la génération du keystore. Aucune suite de tests instrumentés Android n'existe (zéro code natif au-delà de la coquille Capacitor) — "testée" est satisfait par le gate sur la suite existante, pas par une suite native à inventer. Risque documenté, pas résolu par avance : aucun build Android n'a jamais tourné sur ce runner Forgejo auto-hébergé, contrairement aux 4 autres jobs. Closes #64 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
dcfb86d6c9
commit
76a99f0e77
4 changed files with 134 additions and 4 deletions
78
.github/workflows/ci.yml
vendored
78
.github/workflows/ci.yml
vendored
|
|
@ -203,3 +203,81 @@ jobs:
|
||||||
# `component.devServer`), unlike `e2e` above which needs the real app
|
# `component.devServer`), unlike `e2e` above which needs the real app
|
||||||
# running first.
|
# running first.
|
||||||
- run: pnpm --filter web cy:run:component
|
- run: pnpm --filter web cy:run:component
|
||||||
|
|
||||||
|
# Builds and packages a signed, downloadable Android APK on every push to
|
||||||
|
# `main` — issue #64. `needs:` on every other job is what actually makes
|
||||||
|
# this "build, TEST, and package" rather than just "build and package in
|
||||||
|
# parallel with tests that might still be failing": Forgejo/GitHub Actions
|
||||||
|
# has no native way for a job in a *different* workflow file to block on
|
||||||
|
# one here, so this has to live in this same file to get a real dependency
|
||||||
|
# rather than a same-push coincidence. `if:` (not a separate trigger) keeps
|
||||||
|
# every other job running on every branch as today — only this one is
|
||||||
|
# main-only.
|
||||||
|
#
|
||||||
|
# No dedicated Android test suite exists (zero native code beyond the
|
||||||
|
# Capacitor-generated shell) — "testée" is satisfied by gating on the 5
|
||||||
|
# existing jobs, not by inventing an instrumented test suite for this.
|
||||||
|
#
|
||||||
|
# Unverified on this runner as of writing: no Android build has ever run
|
||||||
|
# on this self-hosted Forgejo `act` runner (unlike lint/test/build/e2e,
|
||||||
|
# exercised for months). If SDK installation turns out too slow/heavy here,
|
||||||
|
# cache `~/.android/sdk`/`~/.gradle` (same idea as the Cypress binary cache
|
||||||
|
# in `e2e` above) rather than accepting a slow job on every push.
|
||||||
|
build-android:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [lint, test, intent-service-test, build, e2e]
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
steps:
|
||||||
|
- uses: https://github.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- uses: https://github.com/actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
# Matches what Android Gradle Plugin 8.x (bundled with the
|
||||||
|
# Capacitor-generated project, see apps/web/android/build.gradle)
|
||||||
|
# expects — bump alongside AGP if that version ever moves.
|
||||||
|
java-version: "21"
|
||||||
|
|
||||||
|
- uses: https://github.com/android-actions/setup-android@v3
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Unlike the normal web build (served same-origin as the API), a
|
||||||
|
# packaged Capacitor build runs from the WebView's own local origin and
|
||||||
|
# needs an absolute, public HTTPS URL baked in at build time — see
|
||||||
|
# apps/web/.env.capacitor.example's own comment on this.
|
||||||
|
- name: Build le bundle web pour Capacitor
|
||||||
|
env:
|
||||||
|
VITE_API_URL: ${{ secrets.MOBILE_API_URL }}
|
||||||
|
run: pnpm --filter web build:capacitor
|
||||||
|
- run: pnpm --filter web exec cap sync android
|
||||||
|
|
||||||
|
# Le keystore de release est un secret généré une fois à la main (voir
|
||||||
|
# le README, section "Application mobile") — jamais régénéré ici, un
|
||||||
|
# nouveau keystore casserait la mise à jour de l'appli déjà installée
|
||||||
|
# (Android refuse une signature différente de celle déjà installée).
|
||||||
|
- name: Décode le keystore de release
|
||||||
|
env:
|
||||||
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||||
|
run: echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > apps/web/android/app/release.keystore
|
||||||
|
|
||||||
|
- name: Build l'APK signé
|
||||||
|
working-directory: apps/web/android
|
||||||
|
env:
|
||||||
|
ANDROID_KEYSTORE_PATH: release.keystore
|
||||||
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
|
run: ./gradlew assembleRelease
|
||||||
|
|
||||||
|
- uses: https://github.com/actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: batchcooking-release.apk
|
||||||
|
path: apps/web/android/app/build/outputs/apk/release/app-release.apk
|
||||||
|
|
|
||||||
28
README.md
28
README.md
|
|
@ -266,6 +266,34 @@ Les icônes et le splash sont générés depuis `apps/web/assets/logo.svg` (voir
|
||||||
`apps/web/assets/README.md`). iOS est hors périmètre pour l'instant (nécessite
|
`apps/web/assets/README.md`). iOS est hors périmètre pour l'instant (nécessite
|
||||||
Xcode/macOS).
|
Xcode/macOS).
|
||||||
|
|
||||||
|
**CI — build automatique d'un APK signé** (`.github/workflows/ci.yml`, job
|
||||||
|
`build-android`, issue #64) : à chaque push sur `main`, une fois les 5 autres
|
||||||
|
jobs verts, un APK release signé est construit et déposé comme artefact de
|
||||||
|
run téléchargeable. Secrets du dépôt Forgejo requis (Réglages du dépôt →
|
||||||
|
Secrets) :
|
||||||
|
|
||||||
|
| Secret | Contenu |
|
||||||
|
|---|---|
|
||||||
|
| `MOBILE_API_URL` | URL HTTPS publique de l'API — même valeur que `VITE_API_URL` dans `.env.capacitor` |
|
||||||
|
| `ANDROID_KEYSTORE_BASE64` | Le keystore de release (`.jks`) encodé en base64 |
|
||||||
|
| `ANDROID_KEYSTORE_PASSWORD` | Mot de passe du keystore |
|
||||||
|
| `ANDROID_KEY_ALIAS` | Alias de la clé dans le keystore |
|
||||||
|
| `ANDROID_KEY_PASSWORD` | Mot de passe de la clé |
|
||||||
|
|
||||||
|
Le keystore doit être généré **une seule fois**, à la main, et rester stable
|
||||||
|
pour toujours : Android refuse d'installer la mise à jour d'une appli signée
|
||||||
|
par une clé différente de celle déjà installée.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
keytool -genkeypair -v -keystore batchcooking-release.jks \
|
||||||
|
-alias batchcooking -keyalg RSA -keysize 2048 -validity 10000
|
||||||
|
base64 -w0 batchcooking-release.jks # → coller comme secret ANDROID_KEYSTORE_BASE64
|
||||||
|
```
|
||||||
|
|
||||||
|
Conserver le fichier `.jks` et ses mots de passe en lieu sûr en dehors du
|
||||||
|
dépôt (jamais commité — voir `apps/web/android/.gitignore`) : le perdre
|
||||||
|
empêche de publier toute future mise à jour de l'appli.
|
||||||
|
|
||||||
## Auth (apps/api)
|
## Auth (apps/api)
|
||||||
|
|
||||||
Inscription (création de profil + foyer) et connexion, JWT dans un cookie httpOnly.
|
Inscription (création de profil + foyer) et connexion, JWT dans un cookie httpOnly.
|
||||||
|
|
|
||||||
10
apps/web/android/.gitignore
vendored
10
apps/web/android/.gitignore
vendored
|
|
@ -52,10 +52,12 @@ captures/
|
||||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
.idea/navEditor.xml
|
.idea/navEditor.xml
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files — jamais commités : le keystore de release est un secret
|
||||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
# généré une fois à la main (voir la doc du README, section "Application
|
||||||
#*.jks
|
# mobile"), décodé depuis le secret CI ANDROID_KEYSTORE_BASE64 à chaque build
|
||||||
#*.keystore
|
# de release, jamais versionné ici (issue #64).
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
|
||||||
# External native build folder generated in Android Studio 2.2 and later
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,30 @@ android {
|
||||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Lu depuis l'environnement, jamais codé en dur — même règle que les
|
||||||
|
// secrets de apps/api (JWT_SECRET, etc.) : aucune valeur par défaut
|
||||||
|
// devinable. Le keystore lui-même (ANDROID_KEYSTORE_PATH) est un fichier
|
||||||
|
// décodé à la volée depuis le secret CI ANDROID_KEYSTORE_BASE64, jamais
|
||||||
|
// commité (voir apps/web/android/.gitignore) — généré une fois à la main,
|
||||||
|
// voir la doc du README "Application mobile". N'affecte que
|
||||||
|
// `assembleRelease` : `assembleDebug` continue d'utiliser le keystore de
|
||||||
|
// debug généré automatiquement par Android, sans configuration.
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
storeFile System.getenv("ANDROID_KEYSTORE_PATH") ? file(System.getenv("ANDROID_KEYSTORE_PATH")) : null
|
||||||
|
storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD")
|
||||||
|
keyAlias System.getenv("ANDROID_KEY_ALIAS")
|
||||||
|
keyPassword System.getenv("ANDROID_KEY_PASSWORD")
|
||||||
|
}
|
||||||
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
// `signingConfig` reste posé même quand les variables d'environnement
|
||||||
|
// ci-dessus sont absentes (dev local) : Gradle accepte une
|
||||||
|
// signingConfig aux champs `null`, la build échoue seulement au
|
||||||
|
// moment de la signature effective (assembleRelease), jamais à la
|
||||||
|
// configuration — un `assembleDebug` local n'en a jamais besoin.
|
||||||
|
signingConfig signingConfigs.release
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue