rsend reproducible builds ========================= How rsend stays reproducible and what F-Droid needs to build it. Pinning ------- Every tool and source version lives in ./versions: the rsync source, Go, JDK, Android NDK, SDK platform, build-tools, command-line tools, Gradle, the Android Gradle Plugin, and Kotlin. Directly downloaded archives have repository-owned checksums. Temurin's glibc and musl archives have separate checksums and cache directories. The Gradle wrapper records its distribution checksum too. The gradle files and go.mod repeat some pins because gradle cannot source a shell file. Gradle's verification-metadata.xml locks every resolved Maven and plugin artifact by SHA-256. ci/test.sh fails if direct pins or packaged notices drift. The packages sdkmanager installs are the exception. The NDK and build-tools are pinned by revision and verified against the checksums Google publishes in the SDK repository manifest, not against a checksum this repository owns; only the platform's android.jar is checked here byte for byte. A republished revision would therefore change the output without any pin in this tree changing. Adding repository-owned hashes would mean bypassing sdkmanager and fetching the multi-gigabyte archives directly, which is not worth the exchange. After a deliberate dependency change, regenerate the checksum manifest while running every Gradle path that resolves artifacts, then review its diff: gradle --write-verification-metadata sha256 \ :app:testReleaseUnitTest :app:lintRelease :app:assembleRelease Determinism ----------- - rsh (Go): built with the NDK clang, -trimpath, -ldflags "-s -w -buildid=", CGO_CFLAGS=-ffile-prefix-map to drop build paths, and 16 KB ELF alignment. - rsync (C): cross-compiled with -ffile-prefix-map and stripped; optional dependencies are disabled and zlib and popt come from rsync's bundled copies, so the binary needs nothing outside Bionic. Stack protection, fortified libc calls, RELRO, immediate binding, and a non-executable stack are explicit. Configure's host-dependent nobody group is pinned explicitly. Linker flags give it 16 KB ELF alignment too. - APK: no code shrinking (minifyEnabled false), the Google dependency-metadata block is dropped (dependenciesInfo off), and native libs use legacy packaging. Verify locally from a clean commit. The script extracts that commit twice at different absolute paths, builds both complete unsigned APKs, and compares them byte for byte: make verify-repro A local gitignored keystore.properties is harmless: git archive cannot copy it into either clean build tree. F-Droid ------- The build recipe lives in fdroiddata, not in this repo. F-Droid supplies the NDK named by the recipe's ndk field and exports ANDROID_NDK_HOME. It pins Go through the go srclib (the official Go source) and builds it with make.bash, and installs make and g++ for rsync. The native lib*.so are then built in the recipe's build step, which runs after F-Droid's source scanner and calls rsync/build.sh and rsh/build.sh; Gradle then assembles the APK. The release build is unsigned unless a local keystore.properties is present, so F-Droid signs the published APK with its own key. Each release recipe must set the go srclib ref to the exact GO_VERSION in ./versions. In particular, the published 0.1.3 recipe's go1.25.0 ref must not be reused for a release built with a newer Go patch version. All-files access ---------------- rsend requests MANAGE_EXTERNAL_STORAGE. It is required: backup targets such as OEM call-recording directories and app-specific media folders live outside the scoped-media buckets, and rsync needs real filesystem paths rather than SAF content URIs. At runtime the app makes no network access other than SSH connections to hosts the user configures. Android backup and device-transfer rules exclude all app data, including the encrypted identity, host pins, paths, and logs.