mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-09-23 00:15:45 +00:00
fix: allow multihost sync to use real h2 (rather than h2c) when using https
Docs / build (push) Has been cancelled
Release Please / release-please (push) Has been cancelled
Release Preview / call-reusable-release (push) Has been cancelled
Test / test-nix (push) Has been cancelled
Test / test-win (push) Has been cancelled
Docs / deploy (push) Has been cancelled
Docs / build (push) Has been cancelled
Release Please / release-please (push) Has been cancelled
Release Preview / call-reusable-release (push) Has been cancelled
Test / test-nix (push) Has been cancelled
Test / test-win (push) Has been cancelled
Docs / deploy (push) Has been cancelled
This commit is contained in:
@@ -125,9 +125,43 @@ Server manages configuration for all clients:
|
||||
- **Server permissions**: `Read/Write Config` scoped to `*` + `Receive Shared Repos`
|
||||
- **Result**: The server can push config changes (repos and plans) to connected clients
|
||||
|
||||
## Reverse Proxy
|
||||
|
||||
When exposing a Backrest server to remote clients, you only need to expose the sync RPC path. All other Backrest endpoints (UI, admin API, metrics, downloads) should remain on your trusted network.
|
||||
|
||||
**Path to expose**: `/v1sync.BackrestSyncService/`
|
||||
|
||||
This is the single bidirectional gRPC/Connect stream peers use to sync. The protocol runs its own post-quantum-safe encrypted transport on top of the connection, but you should still terminate TLS at the proxy.
|
||||
|
||||
Requirements:
|
||||
|
||||
- **HTTP/2 end-to-end** (or h2c to the upstream) — the sync stream is a long-lived bidi stream and will not work over HTTP/1.1.
|
||||
- **No response buffering** on the proxy.
|
||||
- **Long timeouts** (hours, not seconds) — the stream is intentionally persistent.
|
||||
- **No request/response size limits** on the sync path.
|
||||
|
||||
### Caddy
|
||||
|
||||
```Caddyfile
|
||||
backrest.example.com {
|
||||
@sync path /v1sync.BackrestSyncService/*
|
||||
reverse_proxy @sync h2c://127.0.0.1:9898 {
|
||||
flush_interval -1
|
||||
transport http {
|
||||
read_timeout 24h
|
||||
write_timeout 24h
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Replace `127.0.0.1:9898` with your Backrest instance's bind address. Any path other than `/v1sync.BackrestSyncService/*` will return 404, keeping the UI and admin API off the public internet.
|
||||
|
||||
If you also want to expose the UI publicly (not recommended without additional auth in front), add a second `reverse_proxy` block without the path matcher — but be aware this also exposes the admin API.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Client can't connect**: Verify the Instance URL is reachable from the client. The URL should include the port (default 9898). If using a reverse proxy, ensure it supports HTTP/2 (needed for the bidirectional sync stream) and is configured to allow long polling requests (e.g. 10+ minutes). Disable any proxy timeouts or payload size limits that could interfere with the sync connection. Recommend using a modern reverse proxy like Caddy.
|
||||
**Client can't connect**: Verify the Instance URL is reachable from the client. The URL should include the port (default 9898). If using a reverse proxy, ensure it supports HTTP/2 (needed for the bidirectional sync stream) and is configured to allow long polling requests (e.g. 10+ minutes). Disable any proxy timeouts or payload size limits that could interfere with the sync connection. Recommend using a modern reverse proxy like Caddy. See the [Reverse Proxy](#reverse-proxy) section above for a working Caddy config.
|
||||
|
||||
**Pairing fails**: Check that the pairing token hasn't expired and hasn't exceeded its max uses. Generate a new token if needed.
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -37,16 +39,37 @@ type SyncClient struct {
|
||||
reconnectAttempts int
|
||||
}
|
||||
|
||||
func newInsecureClient() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http2.Transport{
|
||||
AllowHTTP: true,
|
||||
DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
|
||||
return net.Dial(network, addr)
|
||||
// newSyncHTTPClient builds the HTTP/2 client used to dial a peer's instance URL.
|
||||
// http:// URLs use h2c prior knowledge (plaintext HTTP/2). https:// URLs do a
|
||||
// real TLS handshake and require the peer to negotiate "h2" via ALPN, which
|
||||
// matches what reverse proxies like Caddy serve on their TLS listeners.
|
||||
func newSyncHTTPClient(instanceURL string) (*http.Client, error) {
|
||||
u, err := url.Parse(instanceURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse instance URL %q: %w", instanceURL, err)
|
||||
}
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http":
|
||||
return &http.Client{
|
||||
Transport: &http2.Transport{
|
||||
AllowHTTP: true,
|
||||
DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
return d.DialContext(ctx, network, addr)
|
||||
},
|
||||
IdleConnTimeout: 300 * time.Second,
|
||||
ReadIdleTimeout: 60 * time.Second,
|
||||
},
|
||||
IdleConnTimeout: 300 * time.Second,
|
||||
ReadIdleTimeout: 60 * time.Second,
|
||||
},
|
||||
}, nil
|
||||
case "https":
|
||||
return &http.Client{
|
||||
Transport: &http2.Transport{
|
||||
IdleConnTimeout: 300 * time.Second,
|
||||
ReadIdleTimeout: 60 * time.Second,
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported instance URL scheme %q in %q (expected http or https)", u.Scheme, instanceURL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +83,12 @@ func NewSyncClient(
|
||||
return nil, errors.New("peer instance URL is required")
|
||||
}
|
||||
|
||||
httpClient, err := newSyncHTTPClient(peer.GetInstanceUrl())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := v1syncconnect.NewBackrestSyncServiceClient(
|
||||
newInsecureClient(),
|
||||
httpClient,
|
||||
peer.GetInstanceUrl(),
|
||||
)
|
||||
c := &SyncClient{
|
||||
|
||||
Reference in New Issue
Block a user