Skip to content

fix: rsync uploads fail on openrsync ("empty remote host")#10

Merged
nlemoine merged 1 commit into
mainfrom
fix/rsync-rsh-double-escape
Jun 17, 2026
Merged

fix: rsync uploads fail on openrsync ("empty remote host")#10
nlemoine merged 1 commit into
mainfrom
fix/rsync-rsh-double-escape

Conversation

@nlemoine

@nlemoine nlemoine commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Problem

Syncing uploads fails on some machines with:

rsync: empty remote host

sync_uploads() pointed rsync at a target like ':/…/uploads/' — an empty host before the colon — with the real host baked into --rsh (ssh … 'user@host'). GNU rsync tolerates the empty host (it falls back to the host in the rsh command), so it works on Linux and Homebrew rsync. But openrsync — the /usr/bin/rsync Apple ships on macOS 15+, and the BSDs — validates the target and rejects an empty remote host outright, before it even runs the transport.

Two related fragilities in the same construction:

  • the --rsh value was double shell-escapedgenerate_ssh_command() returns an already-escaped command, then assoc_args_to_str() escaped it again. GNU rsync's -e parser peels the extra quotes back off; stricter parsers may not.
  • it carried -t (request a TTY), inherited from generate_ssh_command()'s interactive-wp purpose, which is wrong for rsync's binary stream.

Fix

Build the rsync invocation directly instead of reusing generate_ssh_command():

  • Alias::get_rsync_location() — a standard [user@]host:path location, so the host is always explicit (no empty-host target).
  • Alias::get_rsync_rsh() — a transport-only ssh -T [-p port] [-i key], escaped exactly once; -T keeps rsync's stream off a TTY.

generate_ssh_command() is unchanged and still used by run_command() / export_db().

Before / after

- rsync … --rsh='ssh -t -q '\''user@host'\''' ':/…/uploads/' '/…/uploads/'
+ rsync … --rsh='ssh -T' 'user@host:/…/uploads/' '/…/uploads/'

Testing

  • Reproduced end-to-end against a real sshd in Docker, including a hostile RequestTTY force ssh_config; the standard user@host:path form transfers and verifies by checksum.
  • Matches a real openrsync failure (rsync: empty remote host) reported by a macOS user.

Follow-up

  • Unit tests for get_rsync_rsh() / get_rsync_location(), plus a forced-TTY fix for export_db(), are in a follow-up branch.
  • proxyjump is not carried into the transport (it was already unused for rsync — Alias::create() never captured it), so no regression; trivial to add if needed.

sync_uploads() built rsync's --rsh by reusing generate_ssh_command(), which
returns an already shell-escaped command; routing that through
assoc_args_to_str escaped it a second time. GNU rsync strips the extra quotes,
but openrsync (the /usr/bin/rsync on macOS 15+ and the BSDs) does not, so the
literal quotes reached ssh and the host failed to resolve -- breaking uploads
on those environments.

Build a transport-only `ssh -T [-p port] [-i key]` escaped exactly once, and
pass the host via the standard [user@]host:path location instead of baking it
into --rsh with an empty-host ':path' target. -T also keeps rsync's binary
stream off a TTY.

generate_ssh_command() is unchanged and still used by run_command and
export_db.

Refs: sync uploads fail on openrsync (macOS 15+/BSD)
@nlemoine nlemoine changed the title fix: build rsync transport without double-escaping rsh fix: rsync uploads fail on openrsync ("empty remote host") Jun 17, 2026
@nlemoine nlemoine merged commit a742237 into main Jun 17, 2026
6 checks passed
@nlemoine nlemoine deleted the fix/rsync-rsh-double-escape branch June 17, 2026 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant