QMAX
QMAX is a private Android messenger client backed by a personal bridge server. The Android app talks to your QMAX server, and the server uses PyMax as the only MAX bridge.
Current shape:
server/QMax.Api- ASP.NET Core API, SQLite cache, JWT pairing auth, SignalR hub, attachment storage, APK update catalog.pymax-worker- Python/PyMax worker with a persistent MAX mobile API session.android- Kotlin + Jetpack Compose Android client pointed athttps://qmax.kusoft.xyz.deploy- Docker Compose + Caddy for Raspberry Pi 5.
Local Checks
dotnet test QMax.slnx
python -m py_compile pymax-worker/src/server.py
cd android
.\gradlew.bat :app:assembleDebug :app:assembleRelease --console=plain --no-daemon
Raspberry Pi Deployment
- Point DNS
qmax.kusoft.xyzto the Raspberry Pi public IP. - Install Docker Engine and the Docker Compose plugin on the Pi.
- Copy the repository or the archive produced by
scripts/deploy-rpi.ps1. - On the Pi:
cd ~/qmax/deploy
cp .env.example .env
nano .env
docker compose up -d --build
This compose publishes QMAX API on 127.0.0.1:18080. The existing system Caddy should contain:
qmax.kusoft.xyz {
encode zstd gzip
reverse_proxy 127.0.0.1:18080
}
Set strong values in .env:
QMAX_JWT_SECRET- at least 32 random characters.QMAX_PAIRING_CODE- one-time-ish pairing password for your Android client and/admin/max.QMAX_MAX_PHONE_NUMBER- the phone number linked to the MAX account used by PyMax.
Secrets must stay in .env, not in git.
MAX Login
Open:
https://qmax.kusoft.xyz/admin/max?pairingCode=YOUR_PAIRING_CODE
Use Start phone login. When MAX sends the confirmation code, submit it on the same page or from the Android app settings.
The worker stores PyMax session state in the qmax-pymax-session Docker volume, so the MAX session should survive restarts until MAX expires the login token.
Android Pairing
Build or install:
cd android
.\gradlew.bat :app:assembleRelease --console=plain --no-daemon
On first launch:
- Server:
https://qmax.kusoft.xyz - Code:
QMAX_PAIRING_CODEfrom.env
The first screen after pairing is the chat list.
APK Update Catalog
Create a release package:
.\scripts\package-android-release.ps1 -Version 0.1.4 -VersionCode 5
The compose file mounts deploy/releases as /data/releases, so files generated by the packaging script are served automatically after docker compose up -d. The server exposes:
GET /api/app-updates/android/latest
GET /api/app-updates/android/download/{fileName}
Use one stable release key for production APKs. Do not install a new APK signed by a different key over an already installed QMAX app.
Push Notifications
The server has an FCM HTTP v1 dispatcher. To enable real push delivery on the Raspberry Pi, put a Firebase service account JSON under deploy/secrets/ and set:
QMAX_FIREBASE_PROJECT_ID=qmax-29df6
QMAX_FIREBASE_SERVICE_ACCOUNT_PATH=/run/secrets/qmax/firebase-service-account.json
The Android app registers its FCM token after pairing. It uses the Google Services Gradle plugin with android/app/google-services.json for the package xyz.kusoft.qmax. FirebaseBootstrap still supports asset-based firebase.android.json or google-services.json as a fallback for custom local builds.
Argus Publication
QMAX is published in Argus with:
- base URL:
https://argus.kusoft.xyz - slug:
qmax - platform:
android - channel:
stable
Manifest:
https://argus.kusoft.xyz/api/apps/qmax/manifest?platform=android&channel=stable
Latest APK:
https://argus.kusoft.xyz/api/apps/qmax/download/latest?platform=android&channel=stable
The Android app checks this manifest, compares semantic versions, downloads the APK to a temporary file, verifies SHA-256, and only then opens Android's package installer.
Current MAX Mapping Status
The deployed worker is authorized through PyMax and currently maps:
- chat list and message history through PyMax;
- text sending through PyMax;
- attachment upload through PyMax file/photo/video models;
- image, video, file and voice attachment projection through the API;
- Android image attachment caching with
.partdownloads before a file is shown from local storage; - explicit session status and phone-code re-login from Android settings.