diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml deleted file mode 100644 index 69e216d..0000000 --- a/.github/workflows/android.yml +++ /dev/null @@ -1,203 +0,0 @@ -name: Build Android APK - -on: - push: - branches: - - main - paths: - - 'apps/web/**' - - 'apps/tauri/**' - - 'packages/shared/**' - - '.github/workflows/android.yml' - pull_request: - paths: - - 'apps/web/**' - - 'apps/tauri/**' - - 'packages/shared/**' - - '.github/workflows/android.yml' - workflow_dispatch: - -env: - VITE_API_BASE_URL: ${{ vars.VITE_API_BASE_URL || 'https://zeavisedu.asepharyana.my.id' }} - -jobs: - build-apk: - runs-on: ubuntu-latest - timeout-minutes: 45 - permissions: - contents: write - outputs: - version: ${{ steps.version.outputs.version }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: | - ~/.bun/install/cache - node_modules - apps/*/node_modules - packages/*/node_modules - key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', '**/package.json') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install dependencies - run: bun install - - - name: Setup Java 21 - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: '21' - - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - with: - packages: 'platforms;android-36 build-tools;36.0.0' - - name: Pin NDK version - run: echo "ANDROID_NDK_HOME=${ANDROID_SDK_ROOT}/ndk/$(ls ${ANDROID_SDK_ROOT}/ndk | sort -V | head -1)" >> $GITHUB_ENV - - - name: Setup Rust with Android targets - uses: dtolnay/rust-toolchain@stable - with: - targets: >- - aarch64-linux-android, - armv7-linux-androideabi, - i686-linux-android, - x86_64-linux-android - - - name: Cache Cargo - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - apps/tauri/target - key: ${{ runner.os }}-cargo-android-${{ hashFiles('apps/tauri/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-android- - - - name: Cache Gradle - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-android-${{ hashFiles('apps/tauri/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-gradle-android- - - - name: Install Tauri CLI - run: cd apps/tauri && bun install - - - name: Compute version - id: version - run: | - # Get latest git tag, default to v0.1.0 - LATEST_TAG=$(git tag --list 'v*' --sort=-v:refname | head -1) - if [ -z "$LATEST_TAG" ]; then - NEW_VERSION="0.1.0" - else - # Strip 'v' prefix, bump patch - BASE="${LATEST_TAG#v}" - IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE" - PATCH=$((PATCH + 1)) - NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" - fi - echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT - echo "New version: ${NEW_VERSION}" - - # Update tauri.conf.json - jq --arg v "${NEW_VERSION}" '.version = $v' apps/tauri/tauri.conf.json > /tmp/tauri.conf.json && mv /tmp/tauri.conf.json apps/tauri/tauri.conf.json - - # Update Cargo.toml - sed -i "s/^version = \".*\"/version = \"${NEW_VERSION}\"/" apps/tauri/Cargo.toml - - echo "Updated tauri.conf.json and Cargo.toml to ${NEW_VERSION}" - - - name: Init Tauri Android project - working-directory: apps/tauri - env: - JAVA_HOME: ${{ env.JAVA_HOME_21_X64 }} - ANDROID_HOME: ${{ env.ANDROID_SDK_ROOT }} - NDK_HOME: ${{ env.ANDROID_NDK_HOME }} - run: | - rm -rf gen/android - bun tauri android init - - - name: Generate Android launcher icons from SVG logo - working-directory: apps/tauri - run: bun run scripts/generate-icons.js - - - name: Patch AndroidManifest (CAMERA permission + deep link) - working-directory: apps/tauri - run: bash scripts/patch-android-manifest.sh - - - name: Build Tauri Android APK - working-directory: apps/tauri - env: - JAVA_HOME: ${{ env.JAVA_HOME_21_X64 }} - ANDROID_HOME: ${{ env.ANDROID_SDK_ROOT }} - NDK_HOME: ${{ env.ANDROID_NDK_HOME }} - run: bun tauri android build --apk - - - name: Decode keystore - if: github.event_name != 'pull_request' - env: - ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} - run: | - echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > apps/tauri/zeavis.keystore - - - name: Sign APK - if: github.event_name != 'pull_request' - env: - ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} - ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} - ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} - run: | - APK_UNSIGNED=$(find apps/tauri/gen/android/app/build/outputs/apk -name '*.apk' ! -name '*-signed*' | head -1) - APK_SIGNED="apps/tauri/gen/android/app/build/outputs/apk/universal/release/zeavis-edu-v${{ steps.version.outputs.version }}.apk" - $ANDROID_SDK_ROOT/build-tools/36.0.0/apksigner sign \ - --ks apps/tauri/zeavis.keystore \ - --ks-pass "pass:${ANDROID_KEYSTORE_PASSWORD}" \ - --ks-key-alias "${ANDROID_KEY_ALIAS}" \ - --key-pass "pass:${ANDROID_KEY_PASSWORD}" \ - --out "$APK_SIGNED" \ - "$APK_UNSIGNED" - echo "signed_apk=${APK_SIGNED}" >> $GITHUB_ENV - echo "Signed APK: $APK_SIGNED" - ls -lh "$APK_SIGNED" - - - name: Create Release and upload APK - if: github.event_name != 'pull_request' - id: release - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ steps.version.outputs.version }} - name: v${{ steps.version.outputs.version }} - body: | - ZeaVis Edu Android APK v${{ steps.version.outputs.version }} - - 📦 Built from ${{ github.sha }} - 🔗 Triggered by ${{ github.actor }} - - ### Install - Download the APK below and install on your Android device. - - 🤖 Generated with [Claude Code](https://claude.com/claude-code) - files: | - ${{ env.signed_apk }} - apps/tauri/gen/android/app/build/outputs/bundle/**/*.aab - draft: false - prerelease: false diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index e780b73..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,209 +0,0 @@ -name: Build and Deploy - -on: - push: - branches: - - main - workflow_dispatch: - -env: - REGISTRY: ghcr.io - VITE_API_BASE_URL: ${{ vars.VITE_API_BASE_URL || '' }} -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - strategy: - fail-fast: false - matrix: - service: - - name: web - dockerfile: apps/web/Dockerfile - - name: api - dockerfile: apps/api/Dockerfile - - name: ml - dockerfile: apps/ml-service/Dockerfile - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set image prefix - run: echo "IMAGE_PREFIX=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV" - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Set up Python for model download & export - if: matrix.service.name == 'ml' - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Download ONNX model from Hugging Face - if: matrix.service.name == 'ml' - working-directory: Machine_Learning - env: - HF_TOKEN: ${{ secrets.HF_TOKEN }} - run: | - set -eu - echo "::group::Install huggingface_hub" - python -m pip install --upgrade pip -q - python -m pip install huggingface_hub -q - echo "::endgroup::" - - echo "::group::Check HF_TOKEN" - if [ -z "${HF_TOKEN:-}" ]; then - echo "ERROR: HF_TOKEN secret is not set." - echo "Add it: https://github.com/ATLAS-PJK-GM007/ZeaVis-Edu/settings/secrets/actions" - exit 1 - fi - echo "HF_TOKEN is set (length: ${#HF_TOKEN})" - echo "::endgroup::" - - echo "::group::Download model files from Hugging Face" - python -c " - from huggingface_hub import hf_hub_download - import os, shutil - repo = 'MythEclipse2737/zeavis-edu-corn-leaf-classifier' - token = os.environ['HF_TOKEN'] - base = os.path.abspath('.') - - # Files sit at root of HF repo → copy to correct subdirs - # model.onnx goes to model/ for Docker COPY - os.makedirs(os.path.join(base, 'model'), exist_ok=True) - os.makedirs(os.path.join(base, 'best_model'), exist_ok=True) - - # ONNX → model/model.onnx (Docker expects this path) - p = hf_hub_download(repo_id=repo, filename='model.onnx', token=token) - shutil.copy2(p, os.path.join(base, 'model', 'model.onnx')) - print('model/model.onnx OK') - - # TFLite (optional, for edge) - p = hf_hub_download(repo_id=repo, filename='model.tflite', token=token) - shutil.copy2(p, os.path.join(base, 'model', 'model.tflite')) - print('model/model.tflite OK') - - # Labels - p = hf_hub_download(repo_id=repo, filename='labels.json', token=token) - shutil.copy2(p, os.path.join(base, 'model', 'labels.json')) - print('model/labels.json OK') - - # Keras model + calibration for re-export - p = hf_hub_download(repo_id=repo, filename='best_model.keras', token=token) - shutil.copy2(p, os.path.join(base, 'best_model', 'best_model.keras')) - print('best_model/best_model.keras OK') - - p = hf_hub_download(repo_id=repo, filename='calibration.json', token=token) - shutil.copy2(p, os.path.join(base, 'best_model', 'calibration.json')) - print('best_model/calibration.json OK') - " - ls -lh model/model.onnx model/model.tflite model/labels.json best_model/best_model.keras 2>/dev/null - echo "::endgroup::" - - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.IMAGE_PREFIX }}/${{ matrix.service.name }} - tags: | - type=ref,event=branch - type=sha - - - name: Build and push image - uses: docker/build-push-action@v5 - with: - context: . - file: ${{ matrix.service.dockerfile }} - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - VITE_API_BASE_URL=${{ env.VITE_API_BASE_URL }} - cache-from: type=gha,scope=${{ matrix.service.name }} - cache-to: type=gha,mode=max,scope=${{ matrix.service.name }} - - deploy: - needs: build - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - permissions: - contents: read - packages: read - steps: - - name: Validate deploy secrets - env: - VPS_HOST: ${{ secrets.VPS_HOST }} - VPS_USER: ${{ secrets.VPS_USER }} - VPS_SSH_KEY: ${{ secrets.VPS_SSH_KEY }} - run: | - if [ -z "$VPS_HOST" ] || [ -z "$VPS_USER" ] || [ -z "$VPS_SSH_KEY" ]; then - echo "Missing deploy secrets: VPS_HOST, VPS_USER, VPS_SSH_KEY." >&2 - exit 1 - fi - - - name: Deploy to VPS - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.VPS_HOST }} - username: ${{ secrets.VPS_USER }} - key: ${{ secrets.VPS_SSH_KEY }} - passphrase: ${{ secrets.VPS_SSH_PASSPHRASE }} - port: ${{ secrets.VPS_PORT || 22 }} - script: | - set -e - DEPLOY_PATH="${DEPLOY_PATH:-/opt/ZeaVis-Edu}" - REPO_SLUG="$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')" - - if [ ! -d "$DEPLOY_PATH/.git" ]; then - mkdir -p "$DEPLOY_PATH" - git clone https://github.com/${{ github.repository }}.git "$DEPLOY_PATH" - fi - - cd "$DEPLOY_PATH" - git fetch origin main - git reset --hard origin/main - - { - printf 'GITHUB_REPOSITORY=%s\n' "$REPO_SLUG" - cat << 'ENVEOF' - DATABASE_URL=${{ secrets.DATABASE_URL }} - SESSION_SECRET=${{ secrets.SESSION_SECRET }} - WEB_APP_URL=https://zeavisedu.asepharyana.my.id - ML_SERVICE_URL=http://zeavis-ml:8000 - TS_IP=${{ vars.TS_IP || '100.96.248.86' }} - GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }} - GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }} - GOOGLE_REDIRECT_URI=https://zeavisedu.asepharyana.my.id/api/v1/auth/google/callback - ENVEOF - } > .env - - docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} - docker network create app-shared-net 2>/dev/null || true - docker network create telemetry-net 2>/dev/null || true - docker compose down --remove-orphans || true - docker rm -f zeavis-web zeavis-api zeavis-ml zeavis-node-exporter 2>/dev/null || true - docker compose pull - docker compose up -d - # Wait for containers to be healthy (up to 60s) - wait_container() { - local name=$1 - for i in $(seq 1 30); do - docker compose ps | grep -q "${name}.*Up" && return 0 - sleep 2 - done - return 1 - } - wait_container zeavis-web || exit 1 - wait_container zeavis-api || exit 1 - wait_container zeavis-ml || exit 1 - wait_container zeavis-node-exporter || echo "⚠️ node_exporter not running (non-fatal)" - docker compose ps