diff --git a/.github/workflows/play-bundle.yml b/.github/workflows/play-bundle.yml new file mode 100644 index 0000000..48028a8 --- /dev/null +++ b/.github/workflows/play-bundle.yml @@ -0,0 +1,71 @@ +name: Play release bundle +on: + push: + branches: [main] + paths: ['app/**', 'i18n/**', 'gradle/**', '*.gradle.kts', 'gradle.properties', '.github/workflows/play-bundle.yml'] + workflow_dispatch: +permissions: + contents: read +concurrency: + group: play-bundle + cancel-in-progress: false +jobs: + bundle: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + environment: play-internal + timeout-minutes: 30 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + persist-credentials: false + - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 + with: + distribution: temurin + java-version: '21' + - name: Release tests and lint + run: ./gradlew testReleaseUnitTest lintRelease --no-daemon --no-configuration-cache + - name: Sign release bundle + env: + KEY_BASE64: ${{ secrets.PINAKES_KEYSTORE_BASE64 }} + PINAKES_KEYSTORE_PASSWORD: ${{ secrets.PINAKES_KEYSTORE_PASSWORD }} + PINAKES_KEY_ALIAS: ${{ secrets.PINAKES_KEY_ALIAS }} + PINAKES_KEY_PASSWORD: ${{ secrets.PINAKES_KEY_PASSWORD }} + run: | + set -euo pipefail + umask 077 + test -n "$KEY_BASE64" + test -n "$PINAKES_KEYSTORE_PASSWORD" + test -n "$PINAKES_KEY_ALIAS" + test -n "$PINAKES_KEY_PASSWORD" + export PINAKES_KEYSTORE="$RUNNER_TEMP/pinakes-upload.jks" + trap 'rm -f "$PINAKES_KEYSTORE"' EXIT + printf '%s' "$KEY_BASE64" | base64 --decode > "$PINAKES_KEYSTORE" + ./gradlew bundleRelease --no-daemon --no-configuration-cache + # `jarsigner -verify` exits 0 on an UNSIGNED artifact — it just prints + # "jar is unsigned". Since the build deliberately falls back to an + # unsigned bundle when the credentials do not reach Gradle, an exit + # code alone would let an unsigned bundle pass as verified. Require + # the positive statement instead. + verify_out="$(jarsigner -verify app/build/outputs/bundle/release/app-release.aab)" + printf '%s\n' "$verify_out" + case "$verify_out" in + *"jar verified"*) ;; + *) echo "::error::release bundle is not signed"; exit 1 ;; + esac + sha256sum app/build/outputs/bundle/release/app-release.aab > app/build/outputs/bundle/release/SHA256SUMS.txt + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pinakes-play-${{ github.sha }} + path: | + app/build/outputs/bundle/release/*.aab + app/build/outputs/bundle/release/SHA256SUMS.txt + app/build/outputs/mapping/release/mapping.txt + if-no-files-found: error + retention-days: 30 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + if: always() + with: + name: play-reports-${{ github.sha }} + path: app/build/reports/ + retention-days: 14 diff --git a/PRIVACY.md b/PRIVACY.md index 99a00b6..5725698 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,10 +1,13 @@ # Privacy Policy — Pinakes Android -_Last updated: 2026-06-19_ +_Technical privacy description updated: 2026-09-09. The publisher has confirmed +the controller and contact below. Retention details and the remaining release +gates still need verification before this becomes the final Play privacy policy._ Pinakes Android is an open-source client app that connects to a **Pinakes -library instance chosen by you**. The app itself has no backend of its own: all -your data lives on the library server you point it at, operated by that library. +library instance chosen by you**. Library services use that instance's server. +The app also integrates Sentry for crash diagnostics and loads media from URLs +supplied by the library; not all requests are limited to the library's domain. ## What the app stores on your device @@ -22,15 +25,22 @@ when you log out or disconnect from the instance. ## What the app sends, and to whom -The app communicates **only** with the Pinakes instance URL you configure. It +For library services, the app communicates with the Pinakes instance you configure. It sends your email and password (over HTTPS) at login, and your bearer token on subsequent requests, to authenticate and to perform the actions you initiate (search, loans/reservations, wishlist, profile edits, contact messages). It also fetches book cover images from URLs returned by that instance. -The app does **not** include third-party analytics, advertising, or tracking -SDKs, and does not transmit your data to the app's authors or any party other -than your chosen library instance. +The app includes **Sentry crash reporting**, configured during app startup. +Default PII attachment is disabled and performance trace sampling is zero. +These settings do not mean no data is transmitted: error events, technical +device/app information and diagnostic context can reach Sentry. Retention and +the precise diagnostic fields must be verified against the publisher's Sentry +configuration before completing the Play Data Safety declaration. + +Catalog and HTTP caches are also stored locally. Signing out or switching +instances clears the relevant caches. Signing out revokes the session; it does +**not** delete the account, loans or other records held by the library. ## Network security @@ -40,17 +50,30 @@ required to be HTTPS. ## Push notifications (optional) -When enabled by your library and by you, push delivery uses **UnifiedPush** -through a distributor of your choosing. Registration data (a push endpoint and -WebPush keys) is sent to your library instance only. +The app has a notification feed and UnifiedPush-related API support. A complete +distributor integration has not been verified for this release; do not describe +background push delivery as universally available. Any enabled distributor and +its data handling must be included in the deployment's privacy review. ## Data controller -Your data is controlled by the **library operating the Pinakes instance** you -connect to. For requests about access, correction, or deletion of your account -data, contact that library directly. For questions about the app itself, open an -issue on the project repository. +The publisher and controller for this Pinakes Android distribution and its app +diagnostics is **D'Alessandro Fabio Gaetano**, reachable at +**[info@fabiodalez.it](mailto:info@fabiodalez.it)**. This is also the contact for +the reference library at **https://biblioteca.fabiodalez.it**. + +When you connect to another independently operated library, that library +operates its own account and circulation services. Consult its privacy policy +and contact it for access, correction or deletion requests relating to those +records. Do not post credentials, loan history or other private data in public +GitHub issues. Never send your password in a privacy request. + +The app supports account registration. A compliant in-app account-deletion +request path and an external request URL remain release gates; logout must not +be presented as account deletion. A library may need to retain specific records +under its obligations, which its policy must explain accurately. -> Libraries publishing their own build: replace this section with your -> organisation's contact details and host this policy at a public URL, then link -> that URL in the Google Play Console (App content → Privacy policy). +For the reference library, account and privacy requests may be sent to +info@fabiodalez.it, identifying the library URL and account email. Identity +verification may be necessary before processing a request. This contact does +not yet replace the in-app and public-web deletion paths listed above. diff --git a/docs/PLAY-RELEASES.md b/docs/PLAY-RELEASES.md new file mode 100644 index 0000000..9cdf6a0 --- /dev/null +++ b/docs/PLAY-RELEASES.md @@ -0,0 +1,15 @@ +# Play releases + +GitHub is the source of truth. Pull requests run Android CI. After merging Android changes into main, `Play release bundle` runs release unit tests and lint, then signs an AAB and retains the bundle, checksum, R8 mapping and reports as GitHub artifacts. Manual dispatch is supported on main only. No PR code receives signing secrets. + +Environment `play-internal` must allow main only. Required environment secrets: `PINAKES_KEYSTORE_BASE64`, `PINAKES_KEYSTORE_PASSWORD`, `PINAKES_KEY_ALIAS`, `PINAKES_KEY_PASSWORD`. Use the existing release key; never generate a replacement silently. Keys are materialized in runner temporary storage and removed after signing; configuration caching is disabled. + +This workflow builds bundles; it does NOT upload to Play or publish to production. Publisher API credentials and app-scoped Play permissions are still required for that separate stage. Increment versionCode before each new Play upload, including corrective rebuilds after a code has been accepted. Debug GitHub releases keep their existing path and are not Play artifacts. + +Review library: https://biblioteca.fabiodalez.it. Mobile API was enabled on 2026-09-09. A dedicated standard reader was created; credentials are private outside Git, not in this document or release artifacts. + +Publisher/controller confirmed by the owner on 2026-09-09: **D'Alessandro Fabio Gaetano**. Public support/privacy email: **info@fabiodalez.it**. Use these for this distribution, not as the controller of independently operated third-party library instances. + +Remaining release gates: accurate privacy policy including Sentry, account-deletion path and public request URL, reviewer access test, minified-device smoke test, native 16 KB compatibility and Play questionnaires. The personal developer account requires the closed-test period before production access. Do not interpret a green bundle build as policy approval. + +The older PLAY_STORE_COMPLIANCE.md audit is historical: its no-third-party-SDK and disabled-R8 statements no longer describe the app. Sentry is present, R8 is enabled, and logout does not delete the user's server account.