diff --git a/.forgejo/workflows/android-apk.yml b/.forgejo/workflows/android-apk.yml deleted file mode 100644 index d39364d5..00000000 --- a/.forgejo/workflows/android-apk.yml +++ /dev/null @@ -1,198 +0,0 @@ -name: Forgejo Android APK - -on: - workflow_dispatch: - push: - branches: - - '**' - tags: - - 'v*' - -jobs: - root-test: - name: Root app tests - runs-on: forgejo-local - steps: - - uses: actions/checkout@v4 - - uses: https://github.com/actions/setup-node@v4 - with: - node-version: '24' - cache: npm - cache-dependency-path: package-lock.json - - run: npm ci - - run: npm test - - build: - needs: root-test - name: Build signed APK - runs-on: forgejo-local - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up JDK 17 - uses: https://github.com/actions/setup-java@v4 - with: - distribution: temurin - java-version: '17' - - - name: Set up Node 20 - uses: https://github.com/actions/setup-node@v4 - with: - node-version: '20' - cache: npm - cache-dependency-path: mobile/package-lock.json - - - name: Set up Android SDK - uses: https://github.com/android-actions/setup-android@v3 - - - name: Install Capacitor dependencies - working-directory: mobile - run: | - npm install --no-audit --no-fund - npx cap sync android - - - name: Restore signing keystore - env: - KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} - run: | - test -n "$KEYSTORE_B64" - CLEAN_KEYSTORE_B64="${KEYSTORE_B64#ANDROID_KEYSTORE_BASE64=}" - printf '%s' "$CLEAN_KEYSTORE_B64" | tr -d '\r\n' | base64 -d > "$RUNNER_TEMP/pedscribe-release.jks" - test -s "$RUNNER_TEMP/pedscribe-release.jks" - - - name: Build signed release APK - working-directory: mobile/android - env: - KS_PASS: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} - KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} - KEY_PASS: ${{ secrets.ANDROID_KEY_PASSWORD }} - run: | - ./gradlew assembleRelease \ - -Pandroid.injected.signing.store.file="$RUNNER_TEMP/pedscribe-release.jks" \ - -Pandroid.injected.signing.store.password="$KS_PASS" \ - -Pandroid.injected.signing.key.alias="$KEY_ALIAS" \ - -Pandroid.injected.signing.key.password="$KEY_PASS" \ - --no-daemon --stacktrace - - - name: Check Google Play secret - id: play_publish - run: | - if [[ "$GITHUB_REF" != refs/tags/v* ]]; then - echo "enabled=false" >> "$GITHUB_OUTPUT" - elif [ -z "${GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_B64:-}" ]; then - echo "enabled=false" >> "$GITHUB_OUTPUT" - else - echo "enabled=true" >> "$GITHUB_OUTPUT" - fi - env: - GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_B64: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_B64 }} - - - name: Build signed release App Bundle - if: steps.play_publish.outputs.enabled == 'true' - working-directory: mobile/android - env: - KS_PASS: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} - KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} - KEY_PASS: ${{ secrets.ANDROID_KEY_PASSWORD }} - run: | - ./gradlew bundleRelease \ - -Pandroid.injected.signing.store.file="$RUNNER_TEMP/pedscribe-release.jks" \ - -Pandroid.injected.signing.store.password="$KS_PASS" \ - -Pandroid.injected.signing.key.alias="$KEY_ALIAS" \ - -Pandroid.injected.signing.key.password="$KEY_PASS" \ - --no-daemon --stacktrace - - - name: Install fastlane - if: steps.play_publish.outputs.enabled == 'true' - working-directory: mobile/android - run: | - gem install bundler -N - bundle install - - - name: Upload bundle to Google Play (internal track) - if: steps.play_publish.outputs.enabled == 'true' - working-directory: mobile/android - env: - GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_B64: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_B64 }} - PLAY_TRACK: internal - run: | - test -n "$GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_B64" - - CLEAN_PLAY_JSON_B64="${GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_B64#GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_B64=}" - printf '%s' "$CLEAN_PLAY_JSON_B64" | tr -d '\r\n' | base64 -d > fastlane/google-play-service-account.json - - AAB=$(find app/build/outputs/bundle/release -name '*.aab' | head -1) - test -n "$AAB" - - AAB_PATH="$AAB" bundle exec fastlane android publish_internal - - rm -f fastlane/google-play-service-account.json - - - name: Collect APK - run: | - mkdir -p artifacts - APK=$(find mobile/android/app/build/outputs/apk/release -name '*.apk' | head -1) - test -n "$APK" - cp "$APK" "artifacts/pedscribe-${GITHUB_REF_NAME:-manual}.apk" - - - name: Upload APK artifact - uses: https://github.com/actions/upload-artifact@v3 - with: - name: pedscribe-android-apk - path: artifacts/*.apk - retention-days: 30 - - - name: Publish Forgejo release - if: startsWith(github.ref, 'refs/tags/v') - env: - FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} - TAG_NAME: ${{ github.ref_name }} - TARGET_COMMIT: ${{ github.sha }} - run: | - test -n "$FORGEJO_TOKEN" - API_URL="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" - APK=$(find artifacts -name '*.apk' | head -1) - test -n "$APK" - - node - <<'NODE' - const fs = require('fs'); - fs.writeFileSync('release-payload.json', JSON.stringify({ - tag_name: process.env.TAG_NAME, - target_commitish: process.env.TARGET_COMMIT, - name: process.env.TAG_NAME, - body: 'Signed Android APK for Obtainium updates.', - draft: false, - prerelease: false, - })); - NODE - - status=$(curl -sS -o release.json -w '%{http_code}' \ - -X POST "$API_URL/releases" \ - -H "Authorization: token $FORGEJO_TOKEN" \ - -H 'Content-Type: application/json' \ - --data-binary @release-payload.json) - if [ "$status" = "409" ]; then - curl -fsS "$API_URL/releases/tags/$TAG_NAME" \ - -H "Authorization: token $FORGEJO_TOKEN" > release.json - elif [ "$status" != "201" ]; then - cat release.json - exit 1 - fi - - RELEASE_ID=$(node -e "console.log(JSON.parse(require('fs').readFileSync('release.json', 'utf8')).id)") - ASSET_NAME=$(basename "$APK") - export ASSET_NAME - curl -fsS "$API_URL/releases/$RELEASE_ID/assets" \ - -H "Authorization: token $FORGEJO_TOKEN" > release-assets.json - EXISTING_ASSET_ID=$(node -e "const fs=require('fs'); const name=process.env.ASSET_NAME; const assets=JSON.parse(fs.readFileSync('release-assets.json','utf8')); const asset=assets.find((item)=>item.name===name); if (asset) console.log(asset.id);" ) - if [ -n "$EXISTING_ASSET_ID" ]; then - curl -fsS -X DELETE "$API_URL/releases/$RELEASE_ID/assets/$EXISTING_ASSET_ID" \ - -H "Authorization: token $FORGEJO_TOKEN" - fi - - curl -fsS -X POST "$API_URL/releases/$RELEASE_ID/assets?name=$ASSET_NAME" \ - -H "Authorization: token $FORGEJO_TOKEN" \ - -F "attachment=@$APK" > release-asset.json diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml deleted file mode 100644 index d0c08386..00000000 --- a/.github/workflows/android-release.yml +++ /dev/null @@ -1,135 +0,0 @@ -name: Build & release Android APK - -# Fires whenever a semver tag is pushed (e.g. v6.1.1). Use -# scripts/release.sh --push from your laptop to mint the -# tag; this workflow does everything downstream. -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - workflow_dispatch: - inputs: - version: - description: 'Manual tag to build (e.g. v6.1.1)' - required: true - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' - -permissions: - contents: write # needed to create GitHub releases from the runner - -jobs: - root-test: - if: ${{ github.server_url == 'https://github.com' }} - name: Root app tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '24' - cache: npm - cache-dependency-path: package-lock.json - - run: npm ci - - run: npm test - - build: - needs: root-test - if: ${{ github.server_url == 'https://github.com' }} - name: Build signed APK - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Resolve tag - id: tag - run: | - TAG="${GITHUB_REF_NAME}" - if [[ -z "$TAG" || "$TAG" == "main" ]]; then - TAG="${{ github.event.inputs.version }}" - fi - echo "tag=$TAG" >> "$GITHUB_OUTPUT" - echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: '17' - - - name: Set up Node 20 - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: npm - cache-dependency-path: mobile/package-lock.json - - - name: Set up Android SDK - uses: android-actions/setup-android@v3 - - - name: Cache Gradle packages - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: gradle-${{ runner.os }}-${{ hashFiles('mobile/android/**/*.gradle*', 'mobile/android/gradle/wrapper/gradle-wrapper.properties') }} - restore-keys: gradle-${{ runner.os }}- - - - name: Install Capacitor + sync - working-directory: mobile - run: | - npm install --no-audit --no-fund - npx cap sync android - - - name: Restore keystore from secret - env: - KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} - run: | - echo "$KEYSTORE_B64" | base64 -d > $RUNNER_TEMP/pedscribe-release.jks - ls -la $RUNNER_TEMP/pedscribe-release.jks - - - name: Build signed release APK - working-directory: mobile/android - env: - KS_PASS: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} - KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} - KEY_PASS: ${{ secrets.ANDROID_KEY_PASSWORD }} - run: | - ./gradlew assembleRelease \ - -Pandroid.injected.signing.store.file=$RUNNER_TEMP/pedscribe-release.jks \ - -Pandroid.injected.signing.store.password="$KS_PASS" \ - -Pandroid.injected.signing.key.alias="$KEY_ALIAS" \ - -Pandroid.injected.signing.key.password="$KEY_PASS" \ - --no-daemon --stacktrace - - - name: Locate APK - id: apk - run: | - APK=$(find mobile/android/app/build/outputs/apk/release -name '*.apk' | head -1) - test -n "$APK" || { echo "no APK found"; exit 1; } - echo "path=$APK" >> "$GITHUB_OUTPUT" - echo "found: $APK ($(stat -c%s "$APK") bytes)" - - - name: Rename APK with version - id: rename - run: | - DST="pedscribe-${{ steps.tag.outputs.version }}.apk" - cp "${{ steps.apk.outputs.path }}" "$DST" - echo "path=$DST" >> "$GITHUB_OUTPUT" - - - name: Create or update GitHub release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.tag.outputs.tag }} - name: PedScribe ${{ steps.tag.outputs.version }} - make_latest: 'true' - generate_release_notes: true - files: | - ${{ steps.rename.outputs.path }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml deleted file mode 100644 index 416b0b4e..00000000 --- a/.github/workflows/build-apk.yml +++ /dev/null @@ -1,119 +0,0 @@ -name: Build TWA APK - -on: - push: - tags: ['v*'] - workflow_dispatch: - inputs: - app_url: - description: 'App URL override (default: https://peds.danvics.com)' - required: false - -env: - APP_URL: ${{ github.event.inputs.app_url || secrets.APP_URL || 'https://peds.danvics.com' }} - -jobs: - root-test: - if: ${{ github.server_url == 'https://github.com' }} - name: Root app tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '24' - cache: npm - cache-dependency-path: package-lock.json - - run: npm ci - - run: npm test - - build-apk: - needs: root-test - if: ${{ github.server_url == 'https://github.com' }} - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Generate Gradle wrapper - working-directory: android - run: | - gradle wrapper --gradle-version=8.5 - - - name: Build APK - working-directory: android - run: | - TWA_HOST=$(echo "${{ env.APP_URL }}" | sed 's|https://||;s|http://||;s|/.*||') - ./gradlew assembleRelease -PTWA_HOST="${TWA_HOST}" - - - name: Sign APK - if: success() && env.HAS_SIGNING_KEY == 'true' - env: - HAS_SIGNING_KEY: ${{ secrets.ANDROID_SIGNING_KEY != '' }} - run: | - # Decode signing key - echo "${{ secrets.ANDROID_SIGNING_KEY }}" | base64 -d > /tmp/release.jks - - # Find the latest build-tools version - BUILD_TOOLS=$(ls -d $ANDROID_HOME/build-tools/*/ | sort -V | tail -1) - echo "Using build-tools: $BUILD_TOOLS" - - UNSIGNED=$(find android/app/build/outputs/apk/release -name "*.apk" | head -1) - echo "Signing: $UNSIGNED" - - # Zipalign - ${BUILD_TOOLS}zipalign -v -p 4 "$UNSIGNED" /tmp/aligned.apk - - # Sign with apksigner - ${BUILD_TOOLS}apksigner sign \ - --ks /tmp/release.jks \ - --ks-key-alias "${{ secrets.ANDROID_KEY_ALIAS }}" \ - --ks-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" \ - --key-pass "pass:${{ secrets.ANDROID_KEY_PASSWORD }}" \ - --out android/app/build/outputs/apk/release/PedScribe-v9-signed.apk \ - /tmp/aligned.apk - - # Verify - ${BUILD_TOOLS}apksigner verify --print-certs android/app/build/outputs/apk/release/PedScribe-v9-signed.apk - - # Cleanup - rm -f /tmp/release.jks /tmp/aligned.apk - - - name: Upload APK to Release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 - with: - files: android/app/build/outputs/apk/release/*.apk - generate_release_notes: true - - - name: Upload artifact - if: success() - uses: actions/upload-artifact@v4 - with: - name: pediatric-scribe-apk - path: android/app/build/outputs/apk/release/*.apk - retention-days: 30 - - - name: Summary - run: | - echo "### TWA APK Build" >> $GITHUB_STEP_SUMMARY - echo "Built for: ${{ env.APP_URL }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Install options:**" >> $GITHUB_STEP_SUMMARY - echo "- Download from GitHub Releases" >> $GITHUB_STEP_SUMMARY - echo "- Obtainium: add repo \`https://github.com/ifedan-ed/pediatric-ai-scribe-v3\`" >> $GITHUB_STEP_SUMMARY diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 90a7d5fc..f030f1f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,6 @@ or Actions tab → **Version bump & release** → Run workflow → pick bump typ | Workflow | Output | |---|---| -| `.forgejo/workflows/android-apk.yml` | signed APK on Forgejo release (`pedscribe-.apk`), optional Google Play internal track upload | | `docker-publish.yml` | `danielonyejesi/pediatric-ai-scribe-v3:{version,latest}` on Docker Hub (amd64) | ## Local dev diff --git a/docs/deployment.md b/docs/deployment.md index 35c386ce..be8add73 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -204,7 +204,7 @@ Container marked unhealthy after 5 failures. ## Resource footprint - RAM: 256 MB minimum, 512 MB recommended for one instance with a handful of concurrent users. -- Disk: Postgres size scales with audit log retention, saved encounters, documents, and Learning Hub content. +- Disk: Postgres size scales with audit log retention, saved encounters, and documents. - CPU: idle load negligible; AI calls are network-bound on the LLM provider side. ## Production checklist @@ -226,9 +226,7 @@ On push (and tag push), these workflows run (depending on runner/site): | Workflow | Output | Runtime | |---|---|---| -| `.forgejo/workflows/android-apk.yml` | Signed APK attached to the Forgejo release, plus optional Google Play internal track upload | ~8 min | | `docker-publish.yml` | Multi-arch image (amd64 + arm64 via native runners) on Docker Hub | ~4 min | -| `build-apk.yml` | Legacy TWA APK (optional second artifact) | ~2 min | Triggered by `auto-version.yml` (reads commit messages, bumps + tags via `RELEASE_PAT`) or manually via `Actions → Version bump & release` or diff --git a/docs/mobile-build.md b/docs/mobile-build.md index e704205d..af3b0e68 100644 --- a/docs/mobile-build.md +++ b/docs/mobile-build.md @@ -1,5 +1,10 @@ # Mobile Build And Release +> **The CI build has been removed.** `.forgejo/workflows/android-apk.yml` and +> the two GitHub APK workflows are gone: CI now covers the Docker image and the +> web app only. The Capacitor project under `mobile/` is still here and still +> builds by hand with the Gradle commands below — nothing builds it for you. + Capacitor 6 wrapper around the hosted Ped-AI web app. The launcher defaults to `https://app.pedshub.com`, lets the user change the server URL, and stores that URL locally. Android is buildable on Linux. The iOS project exists but requires macOS and Xcode to produce an `.ipa`. This is not a separate native clinical app. The native shell provides WebView hosting, microphone permission plumbing, secure storage, and mobile packaging for the same authenticated web app. @@ -24,14 +29,6 @@ export CAPACITOR_ANDROID_STUDIO_PATH="/snap/android-studio/current/bin/studio.sh npx cap open android ``` -## CI build (preferred) - -Push-triggered. Any push to `main`/feature branches and any `vX.Y.Z` tag push -→ `.forgejo/workflows/android-apk.yml` builds a signed APK on the Forgejo -runner. - -Tagged builds additionally publish the artifact to the matching Forgejo release -as `pedscribe-.apk` so Obtainium can track updates. Required repo secrets (set once, via Settings → Secrets and variables → Actions or `gh secret set`): @@ -129,5 +126,4 @@ user to uninstall + reinstall. | `mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java` | JS bridge + WebView mic permission | | `mobile/android/app/src/main/java/com/pedshub/scribe/AudioRecordingService.java` | foreground service for background recording | | `mobile/android/app/src/main/AndroidManifest.xml` | permissions, intents, backup rules | -| `.forgejo/workflows/android-apk.yml` | CI build | | `mobile/android/fastlane/Fastfile` | internal Play track upload lane |