diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 3da4d86..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,112 +0,0 @@ -# ── Android SDK + Node (ambiente di build) ──────────────────────────────────── -# eclipse-temurin:21 (Adoptium) su Ubuntu Jammy include Java 21 -FROM eclipse-temurin:21-jdk-jammy - -# Installa Node.js 20 e dipendenze di sistema -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl \ - wget \ - unzip \ - imagemagick \ - && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ - && apt-get install -y --no-install-recommends nodejs \ - && rm -rf /var/lib/apt/lists/* - -# ── Android SDK ───────────────────────────────────────────────────────────────── -ENV ANDROID_HOME=/opt/android-sdk -ENV PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools - -RUN mkdir -p $ANDROID_HOME/cmdline-tools && \ - wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip \ - -O /tmp/cmdline-tools.zip && \ - unzip -q /tmp/cmdline-tools.zip -d /tmp && \ - mv /tmp/cmdline-tools $ANDROID_HOME/cmdline-tools/latest && \ - rm /tmp/cmdline-tools.zip - -RUN yes | sdkmanager --licenses > /dev/null && \ - sdkmanager \ - "platform-tools" \ - "platforms;android-34" \ - "build-tools;34.0.0" - -# ── Progetto (solo dipendenze npm, senza sorgenti) ──────────────────────────── -WORKDIR /app - -COPY package*.json ./ -RUN npm ci - -# ── Capacitor config ────────────────────────────────────────────────────────── -RUN cat > capacitor.config.json <<'EOF' -{ - "appId": "com.davide.biteplan", - "appName": "BitePlan", - "webDir": "dist", - "server": { - "androidScheme": "https" - } -} -EOF - -# ── Installa Capacitor e prepara la piattaforma Android ─────────────────────── -RUN npm install @capacitor/core @capacitor/cli @capacitor/android --save - -RUN npx cap add android - -# ── Permessi Android ────────────────────────────────────────────────────────── -RUN sed -i 's|| \n|' \ - android/app/src/main/AndroidManifest.xml - -# ── Orientamento bloccato in portrait ──────────────────────────────────────── -RUN sed -i 's/android:exported="true"/android:exported="true"\n android:screenOrientation="portrait"/' \ - android/app/src/main/AndroidManifest.xml - -# ── Versione da package.json → android/app/build.gradle ────────────────────── -RUN node -e "\ - const v = require('./package.json').version; \ - const [a,b,c] = v.split('.').map(Number); \ - const vc = a*10000 + b*100 + c; \ - const fs = require('fs'); \ - let g = fs.readFileSync('android/app/build.gradle','utf8'); \ - g = g.replace(/versionCode \d+/, 'versionCode '+vc); \ - g = g.replace(/versionName \"[^\"]*\"/, 'versionName \"'+v+'\"'); \ - fs.writeFileSync('android/app/build.gradle', g); \ - " - -# ── Icone Android da assets/icon-only.png (via ImageMagick) ───────────────── -COPY assets/ ./assets/ -RUN rm -rf android/app/src/main/res/mipmap-anydpi-v26 && \ - for d in mdpi:48 hdpi:72 xhdpi:96 xxhdpi:144 xxxhdpi:192; do \ - dest=android/app/src/main/res/mipmap-${d%:*}; \ - convert assets/icon-only.png -resize ${d#*:}x${d#*:} $dest/ic_launcher.png; \ - cp $dest/ic_launcher.png $dest/ic_launcher_round.png; \ - done - -# Fix kotlin-stdlib duplicate class conflict (stdlib 1.8+ already includes jdk7/jdk8) -RUN printf '\nsubprojects {\n configurations.all {\n resolutionStrategy {\n force "org.jetbrains.kotlin:kotlin-stdlib:1.8.22"\n force "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.22"\n force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22"\n }\n }\n}\n' >> android/build.gradle - -# ── Runtime: dist/ e (opzionale) keystore montati come volumi dall'host ─────── -# build.sh esegue: docker run -v ./dist:/app/dist ... -# Con --release: aggiunge -e BUILD_TYPE=release -v biteplan.jks:/app/biteplan.jks:ro -# BUILD_TYPE=release → assembleRelease + zipalign + apksigner -# BUILD_TYPE vuoto → assembleDebug (default) - -CMD npx cap sync android && \ - cd android && chmod +x gradlew && \ - if [ "$BUILD_TYPE" = "release" ]; then \ - ./gradlew assembleRelease --no-daemon && \ - $ANDROID_HOME/build-tools/34.0.0/zipalign -v 4 \ - app/build/outputs/apk/release/app-release-unsigned.apk \ - /tmp/aligned.apk && \ - $ANDROID_HOME/build-tools/34.0.0/apksigner sign \ - --ks /app/biteplan.jks \ - --ks-key-alias biteplan \ - --ks-pass pass:$KEYSTORE_PASS \ - --key-pass pass:$KEY_PASS \ - --out /app/dist/biteplan.apk \ - /tmp/aligned.apk && \ - echo "APK release firmato: /app/dist/biteplan.apk"; \ - else \ - ./gradlew assembleDebug --no-daemon && \ - cp app/build/outputs/apk/debug/app-debug.apk /app/dist/biteplan.apk && \ - echo "APK debug: /app/dist/biteplan.apk"; \ - fi diff --git a/docker/README.md b/docker/README.md index e0ea1a7..68faa85 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,109 +1,104 @@ -# Build APK — Docker +# Docker — BitePlan -Genera un APK Android senza installare nulla sull'host oltre a Docker. - -## Requisiti host - -| Requisito | Dettaglio | -|-----------|-----------| -| **Architettura** | x86_64 (amd64) — ARM/aarch64 non supportato | -| **OS** | Linux x86_64 o macOS x86_64 | -| **Docker** | Installato e avviato | -| **Node.js** | v18+ (per il build Vite locale) | - -> Gli Android build-tools (`aapt2`, `zipalign`, `apksigner`) sono binari nativi x86_64 e non girano su host ARM senza emulazione QEMU. +Due container distinti: uno per lo sviluppo, uno per la build APK. --- -## Build debug (default) +## Container dev (`docker/dev/`) -APK firmato con il debug keystore di Android. Adatto per test su dispositivo. +Avvia un web server Flutter con hot reload accessibile dal browser. ```bash -bash docker/build.sh +cd docker/dev +docker compose up ``` +Apri **http://localhost:5173** nel browser. + +**Hot reload** — con il container attivo, in un altro terminale: + +```bash +docker compose attach dev +# premi 'r' → hot reload | 'R' → hot restart | 'q' → esci +``` + +Le modifiche ai file `.dart` sull'host sono visibili subito nel container +grazie al volume montato — basta premere `r` per ricaricare. + +> Prima esecuzione: scarica Flutter SDK (~500 MB), richiede 5-10 minuti. +> Le successive usano la cache Docker e sono molto più rapide. + --- -## Build release (distribuzione) +## Container build (`docker/build/`) -APK firmato con il tuo keystore personale. Necessario per distribuire l'app. +Build headless riproducibile per generare l'APK Android. -### 1. Genera il keystore (una volta sola) +**Prerequisito**: la cartella `android/` deve esistere nel progetto. +Se non esiste, generala con Flutter installato localmente o nel container dev: + +```bash +flutter create --project-name biteplan --org com.biteplan . +``` + +### Build debug + +APK firmato con il debug keystore di Android — adatto per test su dispositivo. + +```bash +bash docker/build/build.sh +# → dist/biteplan-debug.apk +``` + +### Build release + +APK firmato con keystore personale — necessario per la distribuzione. + +**1. Genera il keystore (una sola volta)** ```bash keytool -genkey -v \ -keystore docker/biteplan.jks \ -alias biteplan \ - -keyalg RSA \ - -keysize 2048 \ - -validity 10000 + -keyalg RSA -keysize 2048 -validity 10000 ``` -> Il file `docker/biteplan.jks` è già in `.gitignore` — non verrà mai committato. -> Conservalo in un posto sicuro: senza di esso non puoi aggiornare l'app. +> `docker/biteplan.jks` è in `.gitignore` — non verrà mai committato. +> Conservalo in un posto sicuro: senza di esso non puoi pubblicare aggiornamenti. -### 2. Esegui la build release +**2. Esegui la build release** ```bash -bash docker/build.sh --release -# chiede interattivamente: Password keystore / Password chiave +bash docker/build/build.sh --release +# → dist/biteplan-release.apk ``` -Oppure passando le password come variabili d'ambiente (utile in CI): +Il keystore viene montato nel container come volume read-only e non viene +mai copiato nell'immagine Docker. -```bash -KEYSTORE_PASS=tuapassword KEY_PASS=tuapassword bash docker/build.sh --release -``` - -Per usare un keystore in un percorso diverso da `docker/biteplan.jks`: - -```bash -KEYSTORE_PATH=/percorso/biteplan.jks bash docker/build.sh --release -``` - -### 3. Verifica la firma - -```bash -$ANDROID_HOME/build-tools/34.0.0/apksigner verify --verbose dist/biteplan.apk -``` +> Prima esecuzione: scarica Flutter SDK + Android SDK (~1-2 GB), richiede 10-15 minuti. --- -## Flag combinabili +## Flusso completo -| Comando | Risultato | -|---------|-----------| -| `bash docker/build.sh` | APK debug dalla working directory | -| `bash docker/build.sh --head` | APK debug dall'ultimo commit git | -| `bash docker/build.sh --release` | APK release firmato dalla working directory | -| `bash docker/build.sh --head --release` | APK release firmato dall'ultimo commit git | +``` +cd docker/dev && docker compose up + → http://localhost:5173 (sviluppo con hot reload) + ↓ +flutter create --project-name biteplan --org com.biteplan . + (una volta sola, genera android/) + ↓ +bash docker/build/build.sh # → dist/biteplan-debug.apk +bash docker/build/build.sh --release # → dist/biteplan-release.apk + ↓ +adb install dist/biteplan-debug.apk +``` --- -## Prima build - -La prima esecuzione scarica Android SDK (~1 GB) e può richiedere **10-15 minuti**. -Le build successive usano la cache Docker e sono molto più rapide. - -## Installazione su dispositivo - -```bash -adb install dist/biteplan.apk -``` - -## Pipeline - -``` -[host] npm run build → dist/ -[docker] cap sync → copia dist/ in android/assets/ -[docker] gradlew assembleDebug/Release -[docker] zipalign + apksigner → solo in modalità release -[host] dist/biteplan.apk -``` - ## Note -- App ID: `com.davide.biteplan` +- App ID: `com.biteplan.biteplan` - Android target: API 34 -- Il keystore non viene mai copiato nell'immagine Docker (montato come volume read-only) +- Il keystore non viene mai copiato nell'immagine Docker (volume read-only) diff --git a/docker/build.sh b/docker/build.sh deleted file mode 100755 index 56247a4..0000000 --- a/docker/build.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" -DIST_DIR="$PROJECT_ROOT/dist" - -FROM_HEAD=false -RELEASE=false -for arg in "$@"; do - [[ "$arg" == "--head" ]] && FROM_HEAD=true - [[ "$arg" == "--release" ]] && RELEASE=true -done - -# ── Keystore (solo in modalità release) ─────────────────────────────────────── -KEYSTORE_PATH="${KEYSTORE_PATH:-$SCRIPT_DIR/biteplan.jks}" - -if $RELEASE; then - if [[ ! -f "$KEYSTORE_PATH" ]]; then - echo "Errore: keystore non trovato in $KEYSTORE_PATH" - echo "Generalo con:" - echo " keytool -genkey -v -keystore docker/biteplan.jks -alias biteplan -keyalg RSA -keysize 2048 -validity 10000" - exit 1 - fi - - if [[ -z "${KEYSTORE_PASS:-}" ]]; then - read -rsp "Password keystore: " KEYSTORE_PASS; echo - fi - if [[ -z "${KEY_PASS:-}" ]]; then - read -rsp "Password chiave: " KEY_PASS; echo - fi -fi - -# ── Build Vite ──────────────────────────────────────────────────────────────── -if $FROM_HEAD; then - COMMIT_SHA=$(git -C "$PROJECT_ROOT" rev-parse --short HEAD) - echo "==> Build Vite da HEAD ($COMMIT_SHA)..." - TMPDIR=$(mktemp -d) - trap 'rm -rf "$TMPDIR"' EXIT - git -C "$PROJECT_ROOT" archive HEAD | tar -x -C "$TMPDIR" - cd "$TMPDIR" - npm ci --silent - npm run build --silent - DIST_DIR="$TMPDIR/dist" - cd "$PROJECT_ROOT" -else - echo "==> Build Vite dalla working directory..." - cd "$PROJECT_ROOT" - npm ci --silent - npm run build --silent -fi - -# ── Build immagine Docker ───────────────────────────────────────────────────── -echo "==> Build immagine Docker..." -docker build \ - -f "$SCRIPT_DIR/Dockerfile" \ - -t biteplan-builder \ - "$PROJECT_ROOT" - -# ── Generazione APK ─────────────────────────────────────────────────────────── -echo "==> Generazione APK${RELEASE:+ release}..." -mkdir -p "$PROJECT_ROOT/dist" - -DOCKER_ARGS=( - --rm - -v "$DIST_DIR:/app/dist" -) - -if $RELEASE; then - DOCKER_ARGS+=( - -e BUILD_TYPE=release - -e KEYSTORE_PASS="$KEYSTORE_PASS" - -e KEY_PASS="$KEY_PASS" - -v "$KEYSTORE_PATH:/app/biteplan.jks:ro" - ) -fi - -docker run "${DOCKER_ARGS[@]}" biteplan-builder - -# Con --head l'APK è nella dir temporanea — copiarlo in dist/ del progetto -if $FROM_HEAD; then - mkdir -p "$PROJECT_ROOT/dist" - cp "$DIST_DIR/biteplan.apk" "$PROJECT_ROOT/dist/biteplan.apk" -fi - -echo "" -echo "APK pronto in: $PROJECT_ROOT/dist/biteplan.apk" diff --git a/docker/build/Dockerfile b/docker/build/Dockerfile new file mode 100644 index 0000000..13d5030 --- /dev/null +++ b/docker/build/Dockerfile @@ -0,0 +1,35 @@ +FROM ubuntu:22.04 + +ARG ANDROID_SDK_BUILD=11076708 + +ENV DEBIAN_FRONTEND=noninteractive \ + FLUTTER_HOME=/opt/flutter \ + ANDROID_SDK_ROOT=/opt/android-sdk \ + JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \ + PATH="/opt/flutter/bin:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:${PATH}" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl wget git unzip xz-utils ca-certificates \ + openjdk-17-jdk \ + && rm -rf /var/lib/apt/lists/* + +RUN git clone --depth 1 --branch stable \ + https://github.com/flutter/flutter.git ${FLUTTER_HOME} && \ + flutter config --no-analytics && \ + flutter precache --android + +RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools && \ + wget -q "https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_BUILD}_latest.zip" \ + -O /tmp/cmdtools.zip && \ + unzip -q /tmp/cmdtools.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools && \ + mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools \ + ${ANDROID_SDK_ROOT}/cmdline-tools/latest && \ + rm /tmp/cmdtools.zip + +RUN yes | sdkmanager --licenses && \ + sdkmanager \ + "platform-tools" \ + "platforms;android-34" \ + "build-tools;34.0.0" + +WORKDIR /workspace diff --git a/docker/build/build.sh b/docker/build/build.sh new file mode 100755 index 0000000..81ea4d4 --- /dev/null +++ b/docker/build/build.sh @@ -0,0 +1,77 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +RELEASE=false + +for arg in "$@"; do + [[ "$arg" == "--release" ]] && RELEASE=true +done + +echo "→ Build immagine biteplan-build..." +docker build -t biteplan-build "${SCRIPT_DIR}" + +if [[ ! -d "${PROJECT_ROOT}/android" ]]; then + echo "→ Cartella android/ non trovata, eseguo flutter create..." + docker run --rm \ + -v "${PROJECT_ROOT}:/workspace" \ + biteplan-build \ + bash -c "cd /workspace && flutter create --project-name biteplan --org com.biteplan --platforms=android ." +fi + +mkdir -p "${PROJECT_ROOT}/dist" + +if [[ "$RELEASE" == "true" ]]; then + KEYSTORE="${SCRIPT_DIR}/../biteplan.jks" + if [[ ! -f "$KEYSTORE" ]]; then + echo "ERRORE: keystore non trovato in docker/biteplan.jks" + echo "Generalo con:" + echo " keytool -genkey -v -keystore docker/biteplan.jks -alias biteplan -keyalg RSA -keysize 2048 -validity 10000" + exit 1 + fi + + echo "→ Building release APK..." + docker run --rm \ + -v "${PROJECT_ROOT}:/workspace" \ + -v "${HOME}/.gradle:/root/.gradle" \ + biteplan-build \ + bash -c "cd /workspace && flutter pub get && flutter build apk --release" + + echo "→ Zipalign..." + docker run --rm \ + -v "${PROJECT_ROOT}:/workspace" \ + biteplan-build \ + bash -c "zipalign -v -p 4 \ + /workspace/build/app/outputs/flutter-apk/app-release.apk \ + /workspace/dist/biteplan-aligned.apk" + + echo "→ Firma APK..." + docker run --rm \ + -v "${PROJECT_ROOT}:/workspace" \ + -v "$(realpath "${KEYSTORE%/*}"):/keystore:ro" \ + biteplan-build \ + bash -c "apksigner sign \ + --ks /keystore/biteplan.jks \ + --out /workspace/dist/biteplan-release.apk \ + /workspace/dist/biteplan-aligned.apk && \ + rm -f /workspace/dist/biteplan-aligned.apk && \ + apksigner verify --verbose /workspace/dist/biteplan-release.apk" + + echo "" + echo "✓ Release APK: dist/biteplan-release.apk" + +else + echo "→ Building debug APK..." + docker run --rm \ + -v "${PROJECT_ROOT}:/workspace" \ + -v "${HOME}/.gradle:/root/.gradle" \ + biteplan-build \ + bash -c "cd /workspace && flutter pub get && flutter build apk --debug" + + cp "${PROJECT_ROOT}/build/app/outputs/flutter-apk/app-debug.apk" \ + "${PROJECT_ROOT}/dist/biteplan-debug.apk" + + echo "" + echo "✓ Debug APK: dist/biteplan-debug.apk" +fi diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile new file mode 100644 index 0000000..4cc903b --- /dev/null +++ b/docker/dev/Dockerfile @@ -0,0 +1,16 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive \ + FLUTTER_HOME=/opt/flutter \ + PATH="/opt/flutter/bin:${PATH}" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl git unzip xz-utils ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN git clone --depth 1 --branch stable \ + https://github.com/flutter/flutter.git /opt/flutter && \ + flutter config --no-analytics --enable-web && \ + flutter precache --web + +WORKDIR /workspace diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml new file mode 100644 index 0000000..7d88e92 --- /dev/null +++ b/docker/dev/docker-compose.yml @@ -0,0 +1,14 @@ +services: + dev: + build: . + ports: + - "5173:5173" + volumes: + - ../../:/workspace + - pub-cache:/root/.pub-cache + command: bash /workspace/docker/dev/entrypoint.sh + stdin_open: true + tty: true + +volumes: + pub-cache: diff --git a/docker/dev/entrypoint.sh b/docker/dev/entrypoint.sh new file mode 100755 index 0000000..dd3acad --- /dev/null +++ b/docker/dev/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +# Aggiunge supporto web se mancante (crea solo web/, non tocca lib/) +if [[ ! -d /workspace/web ]]; then + echo "→ Inizializzazione supporto web..." + flutter create . --platforms=web +fi + +flutter pub get +exec flutter run -d web-server --web-hostname=0.0.0.0 --web-port=5173