aboutsummaryrefslogtreecommitdiff
path: root/README
diff options
context:
space:
mode:
Diffstat (limited to 'README')
-rw-r--r--README122
1 files changed, 122 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..eff8e99
--- /dev/null
+++ b/README
@@ -0,0 +1,122 @@
+rsend
+=====
+Push configured phone folders to a home SSH host using real rsync.
+
+rsend backs up folders such as DCIM, WhatsApp media, and call recordings to a
+remote host over SSH. One-way push only, on WiFi by default, periodic or
+manual. Small, boring, self-contained: real rsync and a pure-Go SSH transport,
+both built from source and shipped inside the APK. No foreign prebuilt
+binaries.
+
+
+What it does
+------------
+- Pushes one or more local folders to remote paths over rsync-over-SSH.
+- Incremental: only changed files move; large videos resume (rsync --partial).
+- Per-folder deletion policy: additive backup (default) or mirror (--delete).
+- Runs on unmetered WiFi, periodically (WorkManager) or on demand.
+- ed25519 key auth with strict, pinned host-key verification.
+
+
+Layout
+------
+- rsh/ pure-Go SSH transport used as rsync's remote shell (-e).
+- rsync/ build script that compiles pinned rsync from source via the NDK.
+- app/ Android app (Kotlin, classic Views); bundles both as lib*.so.
+- ci/ CI-agnostic build and test scripts; the Makefile drives them.
+- metadata/ reproducibility and F-Droid build notes.
+- versions pinned toolchain and source versions; the single source of truth.
+
+
+Build
+-----
+The repository is self-contained: clone it and the build fetches and pins
+everything else from source. The only host prerequisites are a POSIX shell,
+curl, python3, tar, and git. The toolchain (JDK, Go, Android SDK and NDK,
+Gradle) and the rsync source are downloaded and version-pinned by the build
+(see versions for every pin).
+
+ make setup # provision the toolchain into $HOME/toolchains
+ . "$HOME/toolchains/env.sh" # put it on PATH (do this in each shell)
+ make # rsync (NDK) -> rsh (Go) -> APK
+ make test # Go tests plus the host-side end-to-end push
+ make verify-repro # build twice, diff the artifacts
+
+The APK lands under app/build/outputs/apk/. It is unsigned by default; signing
+config is local and gitignored. Continuous integration runs the same scripts:
+ci/setup-toolchain.sh then ci/build.sh and ci/test.sh.
+
+
+Run
+---
+1. Install the APK and grant all-files access and notifications.
+2. Generate a key in the app and add the shown public key to the home host's
+ ~/.ssh/authorized_keys.
+3. Set the remote host, user, and port; run Test connection and accept the
+ pinned host-key fingerprint.
+4. Add folders, choose each folder's deletion policy, set the schedule.
+5. Tap Sync now, or wait for the periodic WiFi sync.
+
+For scheduled backup to run reliably, tap Battery and allow rsend to ignore
+battery optimization. Phones with aggressive power management (Samsung, Xiaomi,
+and others) otherwise delay or skip background jobs; see dontkillmyapp.com.
+
+
+Server
+------
+Any host with sshd and rsync works; rsend pushes over rsync-over-SSH, not SFTP.
+The account is confined to "rsync into one folder and nothing else" by a forced
+rrsync command, not by its shell. sshd runs that command through the account's
+login shell, so the shell must be real: /bin/sh works, while /bin/false or
+/sbin/nologin would break rsync. The confinement comes from key-only auth, the
+forced command, and rrsync's write-only root. As root on the server:
+
+ useradd -m -d /srv/backup -s /bin/sh rsendbackup
+ passwd -l rsendbackup
+ mkdir -p /srv/backup/phone && chown -R rsendbackup:rsendbackup /srv/backup
+
+This assumes UsePAM yes in sshd_config (the default on most distros; set it if
+your build has it off). With UsePAM yes a password-locked account still accepts
+key logins. With UsePAM no, sshd refuses any locked account even for keys
+("account is locked"); there, skip passwd -l, set PasswordAuthentication no, and
+leave the account with a non-locked password field.
+
+Put the app's public key in /srv/backup/.ssh/authorized_keys as one restricted
+line that forces rrsync, write-only, into that directory:
+
+ command="/usr/bin/rrsync -wo /srv/backup/phone",restrict ssh-ed25519 AAAA... rsend
+
+To apply the same limit account-wide, so it holds even if another key is added
+later and not only on this key, force the command in sshd_config and reload
+sshd:
+
+ Match User rsendbackup
+ ForceCommand /usr/bin/rrsync -wo /srv/backup/phone
+ PasswordAuthentication no
+ PermitTTY no
+ AllowTcpForwarding no
+ AllowAgentForwarding no
+ AllowStreamLocalForwarding no
+ X11Forwarding no
+
+The account can then do nothing but receive rsync into that folder: every
+session, with any key, is forced through rrsync, which rejects anything but a
+plain rsync transfer into its root. Remote paths are relative to that root, so
+set a folder's remote path to DCIM, not an absolute path. rrsync may instead
+live at /usr/share/rsync/scripts/rrsync; check command -v rrsync. Verify the
+host key from Test connection against
+ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub on the server.
+
+
+Debug
+-----
+- The in-app log viewer tails the plain-text rsync log.
+- Reproduce a sync from a shell using the same rsync invocation rsend logs at
+ the top of each run.
+- rsh transport: RSH_KEY, RSH_KNOWN_HOSTS, and RSH_PORT select the key,
+ known_hosts file, and port. Run rsh by hand to isolate SSH from rsync.
+
+
+License
+-------
+GPLv3. Bundling rsync makes the whole app GPLv3; see LICENSE.