| Age | Commit message (Collapse) | Author | Files | Lines |
|
Advance the version once from the released 0.2.0 base and publish the
matching changelog. No transfer, protocol or storage behaviour changed,
so the store description stands.
The dashboard screenshot prints BuildConfig.VERSION_NAME, so it is the
one store image a version bump invalidates. Recaptured on the same
1080x2400 profile with the same demo state.
|
|
The Rsync comment about draining a hostile remote's pipe sat on the
Tally type, where it means nothing; it describes boundedLines. Scheduler
explained itself with what happened during development instead of the
constraint that WorkManager persists unique names across upgrades, and
MainActivity and SyncLog did the same. Keys pointed at a pin() that does
not exist. The App header restated the class name and omitted half of
what onCreate does. OUT_FORMAT had no caller outside its file.
The README layout list omitted fastlane. The e2e test hardcodes a copy
of the app's rsync vector with nothing saying so. useradd already writes
a locked password field, so "skip passwd -l" does not leave a non-locked
one and key auth is still refused; verified against sshd with UsePAM no.
The CI file also runs verify-repro on a tag, and the NDK and build-tools
are pinned by revision and verified against Google's manifest rather
than a checksum this repository owns, which the reproducibility notes
claimed for every input.
metadata/ held one document and nothing else, and reads as a store
metadata directory beside fastlane/metadata. Root, extensionless,
beside THIRD_PARTY.
|
|
upgradeLegacy assembled every remote, folder and schedule twice: once as
Remote and Folder objects to validate, and again as a parallel JSON tree
to return. Build the objects and serialise them, and drop the dead
endpointAllowed branch, the redundant pin emptiness test, and the
excludes length pre-check that excludesAllowed already makes.
atomicWrite existed verbatim in Config and Keys and was open-coded a
third time in LastSync, which then reached into Config's companion for
the bounded reader. The same bounded read was open-coded twice more, for
the encrypted key and for an imported one. Both are plain file
operations on the same private directory and belong in one place, with
readText a thin wrapper over readBytes.
Generating and importing an identity key ran the same twenty lines of
worker thread, error capture, refresh, toast and dialog; only the action
and two strings differed. rotateIfBig rotated at length >= MAX_BYTES,
which line() already subsumes, and its only caller invoked it
immediately before a line(). notifyError and notifyDeleted were the same
builder twice. openSettings nested a try/catch and repeated one toast;
take the candidates as a vararg. RemoteActivity.save defaulted its
argument to current(), which validates and writes the status field, so
the default hid that side effect from one of two callers.
Dead on arrival: Outcome.deleted's default, which no caller omits; the
rsa-sha2-* arms of hostKeyFile, since rsh prints PublicKey.Type() and an
RSA host key is always ssh-rsa; and the limit <= 0 branch in
SyncLog.text, which tail already handles and handled more accurately.
The config recovery note is now written with an explicit charset like
every other write here.
|
|
Runner names the architecture rather than the thing. The object is the
rsync invocation.
|
|
Scheduler.apply cancels the periodic job while no folder points at a
pinned remote and enqueues it once one does, so adding the first folder
or removing the last one changes what should be scheduled. The remote
and schedule editors already re-applied on save; this one did not, and a
user who left the app from this screen kept a job that could only fire
and refuse.
|
|
Browsing restarted at the storage root on every configuration change.
The restored path goes through safeDirectory like a typed one, so it
still cannot escape the root.
A pinned remote wrote its index into both the instance state and the
intent. The instance state already survives recreation.
|
|
Every value the dashboard draws comes from an app-private file, and it
read all of them during layout, on every resume and at the end of every
sync. Scheduler.apply did the same at process start, where it also opens
WorkManager's database. Read on a worker thread and draw the result;
overlapping refreshes are settled by generation, the same way the log
viewer already settles its own.
|
|
The remote and folder counts and the serialized config size are enforced
only inside save, which throws. Every other rule an editor can break is
reported in the form, so a config that outgrew a limit crashed the
activity instead of saying so. overLimit answers before the entry is
added, and the editors report the answer.
hostKeyAllowed reconstructs the known_hosts address that rsh writes with
knownhosts.Normalize, and nothing recorded or tested that coupling.
Normalize leaves the host bare on port 22 for IPv6 literals too, so
bracketing them here looks right and would reject every IPv6 pin.
|
|
Every other version repeated outside ./versions is asserted in test.sh.
KOTLIN_VERSION was not, so bumping it would leave a stale version in a
license notice with nothing to catch it.
In verify-repro the two git diff calls preceded the status check, so
under set -eu a dirty tree exited 1 with no output and the message below
was never reached. git status --porcelain already covers staged,
unstaged and untracked changes.
|
|
The native scripts picked an NDK host tag for macOS as well, but
ci/setup-toolchain.sh provisions nothing outside Linux x86_64 and no
pipeline exercises the other branch. Fail with the message setup already
gives instead of naming a toolchain nobody can obtain here.
The armeabi-v7a and x86 cases were never reachable either: ABIS ships
arm64-v8a and x86_64, and ci/verify-apk.sh has no machine check for the
other two, so adding one to ABIS failed the build it was meant to
enable. Record what a new ABI actually costs instead of inviting a
one-line edit that cannot work.
|
|
Both callers already read and used RSH_PORT before dialling. Reading it
a third time inside dial hid the data flow and duplicated the error
path.
|
|
Document the multi-remote release, publish matching store metadata,
and advance the version once from the released 0.1.3 base.
|
|
Support Alpine/musl and Debian/glibc hosts, lock downloaded and
Gradle artifacts, and verify native and APK security properties.
|
|
Validate persisted state and destination boundaries, make interruption
and mirror deletion explicit, and keep scheduled work singular.
|
|
Update rsync to 3.5.0 and Go to 1.26.6. Bound SSH handshakes, pin
host-key types, and build 16 KB-aligned hardened executables.
|
|
Let each folder select a named SSH endpoint and migrate the published
0.1.x configuration without discarding its folders or host pin.
|
|
Migrating the project off Codeberg to GitLab. The release publisher was
written against the Forgejo API and hardcodes codeberg.org; remove it
rather than port it for now.
|
|
versionCode 4. Bound the SSH handshake so a stalled server cannot hang
a sync. Persist config, key, and host pin atomically so an interrupted
write cannot corrupt state. Replace the chained one-time jobs with
periodic work that survives reboots; the schedule floor is now
WorkManager's 15-minute minimum.
|
|
Checksum-pin every downloaded toolchain archive. Before publishing,
verify the APK was built from HEAD, the tag matches versionName,
apksigner passes, and the tree is clean; publish a sha256 sidecar
and treat published assets as immutable. Compare full unsigned APKs
in verify-repro and run the Android unit tests in CI.
|
|
Persist config, key blob, and host pin via AtomicFile so an
interrupted write cannot corrupt state. Validate the port, clear the
host pin when the remote changes, cap imported key size, time out
native helpers, and escalate rsync termination to destroyForcibly.
Serialize manual and scheduled syncs behind a mutex.
Replace the chained one-time jobs with plain periodic work: it
survives reboots without a boot receiver and cannot silently die
like a broken chain. Costs the sub-15-minute interval, which photo
backup does not need. Document the floor and that syncs run on any
unmetered network, not only WiFi.
|
|
ssh.Dial applies its Timeout only to the TCP connect, so a host that
accepts the connection and then stalls the SSH handshake hangs the
sync forever. Dial with a timeout, run the handshake under a deadline,
and clear the deadline once the connection is established.
|
|
versionCode 3. Drop --mkpath so remotes with an rsync older than 3.2.3
(stock macOS included) can receive syncs; rsync still creates the final
remote path component on its own. NDK host toolchain detection so the
native build also runs on macOS hosts.
|
|
The NDK names its prebuilt toolchain directory after the build host,
not the target. Hardcoding linux-x86_64 broke rsync/build.sh and
rsh/build.sh on macOS, which ships the toolchain under darwin-x86_64
(x86_64 binaries, run under Rosetta on Apple Silicon). Detect the
host with uname and fail loud on anything else.
Link: https://codeberg.org/0xlena/rsend/pulls/1
|
|
rsync forwards --mkpath to the remote side when sending, so a remote
rsync older than 3.2.3 (notably stock macOS, whose openrsync is
"rsync 2.6.9 compatible") rejects every sync with "unrecognized
option '--mkpath'".
rsync creates the final component of the destination path on its own,
so flat remote paths keep working everywhere, including the documented
rrsync setup whose root is pre-created. Only a nested remote path with
missing parents now needs a one-time mkdir -p on the server; the log
shows rsync's error when it is missing.
Reported-by: jsvk
Link: https://codeberg.org/0xlena/rsend/pulls/1
|
|
versionCode 2. Empty-path and config-corruption fixes, rsync process
cleanup on stopped syncs, keygen error reporting, and the store icon.
|
|
The app ships only an adaptive vector launcher icon, which F-Droid
cannot render, so the store listing showed no icon. Ship a 512x512
raster of the same mark where F-Droid looks for it (fastlane images/
icon.png).
|
|
Nothing referenced it: the rsh tests run their own in-process SSH
server (sshserver_test.go), which testsshd largely duplicated.
|
|
Re-running on the same tag died creating a release that already
existed. Reuse the tag's release when present, replace a stale asset
before uploading, and parse API responses with python3 instead of sed.
|
|
The gradle files and go.mod repeat pins from versions because gradle
cannot source a shell file. Compare them in ci/test.sh so a bump that
misses a copy fails loud instead of silently building with the old
version.
|
|
The Adoptium "latest 17 GA" endpoint floats while versions promises
never to float. Pin the full Temurin build and fetch it by version.
|
|
The containment check was a bare prefix match, so a sibling such as
/storage/emulated/0-evil passed for a root of /storage/emulated/0.
|
|
A crash mid-write corrupted config.json, and every later load then
threw on parse, crash-looping the app until its data was cleared,
losing the key and pinned host. Write to a temp file and rename.
|
|
A stopped worker (schedule replaced, constraints lost) left rsync
running detached until its own network timeout, including a window
where a WiFi-only sync kept pushing on another network. A watchdog
coroutine destroys the process on cancellation, which also closes its
pipes and unblocks the log reader; the worker rethrows cancellation
instead of logging it as a folder error.
|
|
The trailing-slash fix-up turns an empty local path into /, so a blank
folder row would rsync the entire filesystem; an empty remote path
pushes into the rrsync root. Reject both when saving a folder, skip and
log them at run time in case the config was edited by hand, and log why
a sync was skipped when the app is not configured yet.
|
|
A garbage or out-of-range port silently fell back to 22 and connected
to the wrong place; fail loud instead.
|
|
rsh -keygen printed the pubkey but wrote the key pair into a directory,
so generating a key briefly left the plaintext private key on flash,
contradicting the documented invariant that it only ever exists in
memory. -keygen now emits the private key PEM on stdout and nothing
else; the app encrypts it immediately and derives the public key via
-pubkey, reusing the validated import path. Keygen failures now surface
as an error dialog instead of crashing the app from a bare thread.
|
|
Typing absolute paths is error-prone; let the user browse real
filesystem folders and pick one. Stays on real paths (no SAF) so
rsync keeps operating on POSIX paths.
|
|
A small Android app for one-way folder backup, a KISS alternative to
Syncthing. It bundles rsync (built from pinned source via the NDK) and
a pure-Go SSH transport, both shipped in the APK as lib*.so and run from
the native library directory.
rsend pins the host key, stores the ed25519 identity Keystore-encrypted,
pushes each folder additively or as a mirror, and runs on demand or on a
WiFi-only schedule. The build is self-contained and reproducible: make
setup provisions the toolchain, make builds rsync, rsh, and the APK.
|