From 84b2829265429157d78f413075e691b4a1ca5a73 Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Wed, 10 Sep 2025 09:48:32 +0200 Subject: [PATCH] rsync: support rsync-ssl connections too rsync provides a rsync-ssl script which tries to communicate over tls, on port 874 instead of 873, if it finds openssl/gnutls/stunnel. In theory we could add a separate cli option for this since there is no official URL scheme for rsync+tls, but to keep things simple for everyone just allow rsyncs:// in rsync URLs. In case a URL is starting with rsyncs:// we will use rsync-ssl instead of rsync, and rewrite the url back to rsync://. For testing, here are two public servers that support TLS: * rsync-ssl rsync://mirrors.dotsrc.org * rsync-ssl rsync://repo.msys2.org Using "--contimeout" with rsync-ssl leads to: "The --contimeout option may only be used when connecting to an rsync daemon." From my testing "--timeout" aborts though if openssl takes too long to connect, suggesting that it covers both connection and i/o in this scenario. Bump the --timeout to 60 so the upper bound is the same as without openssl. --- scan/rsync.go | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/scan/rsync.go b/scan/rsync.go index 94901824..32bda6d1 100644 --- a/scan/rsync.go +++ b/scan/rsync.go @@ -32,12 +32,21 @@ type RsyncScanner struct { // Scan starts an rsync scan of the given mirror func (r *RsyncScanner) Scan(rsyncURL, identifier string, conn redis.Conn, stop <-chan struct{}) (core.Precision, error) { var env []string - - if !strings.HasPrefix(rsyncURL, "rsync://") { - return 0, fmt.Errorf("%s does not start with rsync://", rsyncURL) + var cmdName string + var actualURL string + + // We allow a custom rsyncs:// scheme which uses rsync-ssl instead + if strings.HasPrefix(rsyncURL, "rsync://") { + cmdName = "rsync" + actualURL = rsyncURL + } else if strings.HasPrefix(rsyncURL, "rsyncs://") { + cmdName = "rsync-ssl" + actualURL = "rsync://" + strings.TrimPrefix(rsyncURL, "rsyncs://") + } else { + return 0, fmt.Errorf("%s does not start with rsync:// or rsyncs://", rsyncURL) } - u, err := url.Parse(rsyncURL) + u, err := url.Parse(actualURL) if err != nil { return 0, err } @@ -58,7 +67,17 @@ func (r *RsyncScanner) Scan(rsyncURL, identifier string, conn redis.Conn, stop < // Don't use the local timezone, use UTC env = append(env, "TZ=UTC") - cmd := exec.Command("rsync", "-r", "--no-motd", "--timeout=30", "--contimeout=30", "--exclude=.~tmp~/", u.String()) + args := []string{"-r", "--no-motd", "--exclude=.~tmp~/"} + // rsync-ssl does not support --contimeout, but from what I see --timeout covers both and + // triggers if openssl takes too long to connect. So use a longer combined timeout there. + if cmdName == "rsync-ssl" { + args = append(args, "--timeout=60") + } else { + args = append(args, "--timeout=30", "--contimeout=30") + } + args = append(args, u.String()) + + cmd := exec.Command(cmdName, args...) // Setup the environnement cmd.Env = env @@ -86,7 +105,7 @@ func (r *RsyncScanner) Scan(rsyncURL, identifier string, conn redis.Conn, stop < return 0, err } - log.Infof("[%s] Requesting file list via rsync...", identifier) + log.Infof("[%s] Requesting file list via %s...", identifier, cmdName) scanfinished := make(chan bool) go func() {