68 lines
2.4 KiB
Markdown
68 lines
2.4 KiB
Markdown
|
|
# Gallery Upload — Android client
|
||
|
|
|
||
|
|
Native uploader for the photogallery `POST /upload` endpoint. Pick or share a
|
||
|
|
photo, add a caption and date, post. Credentials are entered once and sealed
|
||
|
|
with a key held in the Android Keystore.
|
||
|
|
|
||
|
|
The server is not modified by this app. Everything it needs, it does client
|
||
|
|
side.
|
||
|
|
|
||
|
|
## Build
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd android
|
||
|
|
nix develop # Android SDK 36, build-tools 36.0.0, JDK 17, gradle
|
||
|
|
gradle wrapper # first time only, generates ./gradlew
|
||
|
|
./gradlew assembleDebug
|
||
|
|
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||
|
|
```
|
||
|
|
|
||
|
|
The dev shell exports `ANDROID_HOME`, `JAVA_HOME` and a `GRADLE_OPTS` that
|
||
|
|
points AGP at the nix-provided `aapt2`. Without that last one AGP downloads a
|
||
|
|
generic-linux `aapt2` from Maven which will not run on NixOS.
|
||
|
|
|
||
|
|
## Release signing
|
||
|
|
|
||
|
|
`app/build.gradle.kts` wires up a release signing config only when
|
||
|
|
`android/keystore.properties` exists. Both that file and `*.jks` are
|
||
|
|
gitignored — keep the keystore outside the repo and back it up somewhere you
|
||
|
|
will still have in five years, because losing it means you can never upgrade an
|
||
|
|
installed build in place.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mkdir -p ~/keys
|
||
|
|
keytool -genkeypair -v \
|
||
|
|
-keystore ~/keys/photogallery-release.jks \
|
||
|
|
-alias photogallery \
|
||
|
|
-keyalg RSA -keysize 4096 -validity 10000
|
||
|
|
```
|
||
|
|
|
||
|
|
Then write `android/keystore.properties`:
|
||
|
|
|
||
|
|
```properties
|
||
|
|
storeFile=/home/you/keys/photogallery-release.jks
|
||
|
|
storePassword=…
|
||
|
|
keyAlias=photogallery
|
||
|
|
keyPassword=…
|
||
|
|
```
|
||
|
|
|
||
|
|
```bash
|
||
|
|
./gradlew assembleRelease
|
||
|
|
```
|
||
|
|
|
||
|
|
Without `keystore.properties` the release build still runs, it just comes out
|
||
|
|
unsigned.
|
||
|
|
|
||
|
|
## How it maps onto the server
|
||
|
|
|
||
|
|
| Server behaviour | What the app does about it |
|
||
|
|
|---|---|
|
||
|
|
| Slug derived from the uploaded filename | Sends `yyyy-MM-dd-HHmmss.jpg`, generated once at enqueue |
|
||
|
|
| Same filename overwrites the same post | Reused across retries, which makes retries idempotent |
|
||
|
|
| Uploaded file published verbatim as "full size" | Strips EXIF, including GPS, before upload |
|
||
|
|
| Extension check rejects HEIC | Converts HEIC to JPEG; JPEG and PNG pass through losslessly |
|
||
|
|
| nginx `client_max_body_size 20M` | Steps quality, then resolution, if the result exceeds 18 MiB |
|
||
|
|
| Thumbnailer ignores EXIF orientation | Bakes rotation into the pixels rather than relying on the tag |
|
||
|
|
| nginx only proxies `/upload`, so `/health` 404s | Tests credentials with `GET /upload` instead |
|
||
|
|
| Replies `uploaded <slug>.jpg` in plain text | Parses that into `<base>/photo/<slug>/` for the "Open" link |
|