Skip to content

Commit 44d433d

Browse files
Hippie Hackerclaude
andcommitted
feat: add TunnelDomain option to separate API and tunnel URLs
Adds a new `--tunnel-domain` / `TUNNELD_TUNNEL_DOMAIN` option that allows specifying a different domain for tunnel URLs than the API base URL. This enables split-horizon deployments where: - Both internal and external servers respond to the same API URL (e.g., sharing.io) - Internal server returns tunnel URLs on local domain (*.sharing.io) - External server returns tunnel URLs on external domain (*.fly.sharing.io) If TunnelDomain is not set, BaseURL is used for tunnel URLs (preserving backwards compatibility). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b578e62 commit 44d433d

File tree

2 files changed

+34
-8
lines changed

2 files changed

+34
-8
lines changed

cmd/tunneld/main.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,14 @@ func main() {
5656
&cli.StringFlag{
5757
Name: "base-url",
5858
Aliases: []string{"u"},
59-
Usage: "The base URL to use for the tunnel, including scheme. All tunnels will be subdomains of this hostname.",
59+
Usage: "The base URL to use for the API, including scheme. Used for routing API requests.",
6060
EnvVars: []string{"TUNNELD_BASE_URL"},
6161
},
62+
&cli.StringFlag{
63+
Name: "tunnel-domain",
64+
Usage: "The domain to use for tunnel URLs, including scheme. All tunnels will be subdomains of this hostname. If not set, base-url is used.",
65+
EnvVars: []string{"TUNNELD_TUNNEL_DOMAIN"},
66+
},
6267
&cli.StringFlag{
6368
Name: "wireguard-endpoint",
6469
Aliases: []string{"wg-endpoint"},
@@ -142,6 +147,7 @@ func runApp(ctx *cli.Context) error {
142147
verbose = ctx.Bool("verbose")
143148
listenAddress = ctx.String("listen-address")
144149
baseURL = ctx.String("base-url")
150+
tunnelDomain = ctx.String("tunnel-domain")
145151
wireguardEndpoint = ctx.String("wireguard-endpoint")
146152
wireguardPort = ctx.Uint("wireguard-port")
147153
wireguardKey = ctx.String("wireguard-key")
@@ -206,6 +212,13 @@ func runApp(ctx *cli.Context) error {
206212
if err != nil {
207213
return xerrors.Errorf("could not parse base-url %q: %w", baseURL, err)
208214
}
215+
var tunnelDomainParsed *url.URL
216+
if tunnelDomain != "" {
217+
tunnelDomainParsed, err = url.Parse(tunnelDomain)
218+
if err != nil {
219+
return xerrors.Errorf("could not parse tunnel-domain %q: %w", tunnelDomain, err)
220+
}
221+
}
209222
wireguardServerIPParsed, err := netip.ParseAddr(wireguardServerIP)
210223
if err != nil {
211224
return xerrors.Errorf("could not parse wireguard-server-ip %q: %w", wireguardServerIP, err)
@@ -248,6 +261,7 @@ func runApp(ctx *cli.Context) error {
248261

249262
options := &tunneld.Options{
250263
BaseURL: baseURLParsed,
264+
TunnelDomain: tunnelDomainParsed,
251265
WireguardEndpoint: wireguardEndpoint,
252266
WireguardPort: uint16(wireguardPort),
253267
WireguardKey: wireguardKeyParsed,

tunneld/options.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,17 @@ var newHostnameEncoder = base32.HexEncoding.WithPadding(base32.NoPadding)
3535
type Options struct {
3636
Log slog.Logger
3737

38-
// BaseURL is the base URL to use for the tunnel, including scheme. All
39-
// tunnels will be subdomains of this hostname.
38+
// BaseURL is the base URL to use for the API, including scheme. This is
39+
// used for routing API requests.
40+
// e.g. "https://tunnel.example.com"
41+
BaseURL *url.URL
42+
43+
// TunnelDomain is the domain to use for tunnel URLs, including scheme.
44+
// All tunnels will be subdomains of this hostname.
4045
// e.g. "https://tunnel.example.com" will place tunnels at
4146
// "https://xyz.tunnel.example.com"
42-
BaseURL *url.URL
47+
// If not set, BaseURL is used for tunnel URLs.
48+
TunnelDomain *url.URL
4349

4450
// WireguardEndpoint is the UDP address advertised to clients that they will
4551
// connect to for wireguard connections. It should be in the form
@@ -181,21 +187,27 @@ func (options *Options) WireguardPublicKeyToIPAndURLs(publicKey device.NoisePubl
181187
// the first 64 bits of the hash of the public key.
182188
copy(addrBytes[8:], keyHash[:8])
183189

190+
// Determine which domain to use for tunnel URLs
191+
tunnelBase := options.BaseURL
192+
if options.TunnelDomain != nil {
193+
tunnelBase = options.TunnelDomain
194+
}
195+
184196
// Good format:
185197
goodFormatBytes := make([]byte, 8)
186198
copy(goodFormatBytes, keyHash[:8])
187199
goodFormat := newHostnameEncoder.EncodeToString(goodFormatBytes)
188-
goodFormatURL := *options.BaseURL
189-
goodFormatURL.Host = strings.ToLower(goodFormat) + "." + goodFormatURL.Host
200+
goodFormatURL := *tunnelBase
201+
goodFormatURL.Host = strings.ToLower(goodFormat) + "." + tunnelBase.Host
190202

191203
// Old format:
192204
oldFormatBytes := make([]byte, 16)
193205
copy(oldFormatBytes, addrBytes[:])
194206
prefixLenBytes := options.WireguardNetworkPrefix.Bits() / 8
195207
copy(oldFormatBytes[prefixLenBytes:], keyHash[:16-prefixLenBytes])
196208
oldFormat := hex.EncodeToString(oldFormatBytes)
197-
oldFormatURL := *options.BaseURL
198-
oldFormatURL.Host = strings.ToLower(oldFormat) + "." + oldFormatURL.Host
209+
oldFormatURL := *tunnelBase
210+
oldFormatURL.Host = strings.ToLower(oldFormat) + "." + tunnelBase.Host
199211

200212
urls := []*url.URL{&goodFormatURL, &oldFormatURL}
201213
if version == tunnelsdk.TunnelVersion1 {

0 commit comments

Comments
 (0)