Skip to content

Commit 27ec4fc

Browse files
icecube092claude
andcommitted
Ask for the TLS fallback target in setup (default 127.0.0.1:8081)
The TLSFallbackDest override could only be set by hand-editing config.json, and a re-run of setup never captured it. Setup now prompts for the masquerade target whenever TLS mode is chosen (both DuckDNS and bring-your-own-domain), offering 127.0.0.1:8081 by default; Enter keeps it, another host:port changes it, and 'no' falls back to the built-in decoy site. The choice persists across re-runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9d5ed4a commit 27ec4fc

2 files changed

Lines changed: 68 additions & 9 deletions

File tree

src/internal/commands/commands_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,38 @@ func TestAskProtocolPortKeepsSavedPort(t *testing.T) {
2828
}
2929
}
3030

31+
func TestAskTLSFallback(t *testing.T) {
32+
noop := func() {}
33+
34+
// Enter on a fresh config accepts the offered default (127.0.0.1:8081).
35+
c := &config.AppConfig{}
36+
askTLSFallback(newInputFrom(strings.NewReader("\n")), c, noop)
37+
if c.TLSFallbackDest != defaultTLSFallbackDest {
38+
t.Errorf("Enter = %q, want default %q", c.TLSFallbackDest, defaultTLSFallbackDest)
39+
}
40+
41+
// A typed host:port overrides it.
42+
c = &config.AppConfig{}
43+
askTLSFallback(newInputFrom(strings.NewReader("127.0.0.1:9000\n")), c, noop)
44+
if c.TLSFallbackDest != "127.0.0.1:9000" {
45+
t.Errorf("typed value = %q, want 127.0.0.1:9000", c.TLSFallbackDest)
46+
}
47+
48+
// 'no' clears it back to the built-in site (empty).
49+
c = &config.AppConfig{TLSFallbackDest: "127.0.0.1:8081"}
50+
askTLSFallback(newInputFrom(strings.NewReader("no\n")), c, noop)
51+
if c.TLSFallbackDest != "" {
52+
t.Errorf("'no' = %q, want empty (built-in site)", c.TLSFallbackDest)
53+
}
54+
55+
// A value without a port is rejected and falls back to the built-in site.
56+
c = &config.AppConfig{}
57+
askTLSFallback(newInputFrom(strings.NewReader("example.com\n")), c, noop)
58+
if c.TLSFallbackDest != "" {
59+
t.Errorf("no-port value = %q, want empty", c.TLSFallbackDest)
60+
}
61+
}
62+
3163
func TestCheckInbounds(t *testing.T) {
3264
// Without a config: only the default VLESS port is reported (public == bind).
3365
got := checkInbounds(config.AppConfig{}, false)

src/internal/commands/setup.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -517,21 +517,20 @@ func configureCamouflage(r *input, c *config.AppConfig, save func()) error {
517517
mode := strings.ToLower(strings.TrimSpace(ask(r, "Camouflage mode (reality/tls)", def)))
518518

519519
if mode == config.CamouflageTLSMode {
520-
switch {
521-
case c.DuckDNSHost() != "":
520+
if c.DuckDNSHost() == "" && c.CustomDomain == "" {
521+
fmt.Println(" ! TLS mode needs a domain — either DuckDNS (token + subdomain) or")
522+
fmt.Println(" your own domain — but none is configured. Using REALITY.")
523+
} else {
522524
c.Camouflage = config.CamouflageTLSMode
523525
save()
524-
return configureTLS(r, c, save)
525-
case c.CustomDomain != "":
526+
askTLSFallback(r, c, save)
527+
if c.DuckDNSHost() != "" {
528+
return configureTLS(r, c, save)
529+
}
526530
// Bring-your-own domain: the node can't run the DNS-01 challenge
527531
// without DuckDNS, so the operator provides the certificate.
528-
c.Camouflage = config.CamouflageTLSMode
529-
save()
530532
configureTLSManual(r, c)
531533
return nil
532-
default:
533-
fmt.Println(" ! TLS mode needs a domain — either DuckDNS (token + subdomain) or")
534-
fmt.Println(" your own domain — but none is configured. Using REALITY.")
535534
}
536535
}
537536

@@ -561,6 +560,34 @@ func configureCamouflage(r *input, c *config.AppConfig, save func()) error {
561560
return nil
562561
}
563562

563+
// defaultTLSFallbackDest is offered in setup as the masquerade target: the
564+
// plain-HTTP vhost operators commonly run to front a real site behind the node
565+
// (see the 443-sharing deploy). Enter accepts it; 'no' keeps the built-in site.
566+
const defaultTLSFallbackDest = "127.0.0.1:8081"
567+
568+
// askTLSFallback asks where non-proxy TLS traffic (browsers, active probes) is
569+
// sent — the masquerade target xray falls back to. It offers 127.0.0.1:8081 by
570+
// default (a local reverse proxy fronting a real site); Enter keeps it, another
571+
// host:port changes it, and 'no' uses the node's built-in decoy website (stored
572+
// as an empty TLSFallbackDest). Persisted so a re-run of setup keeps the choice
573+
// instead of forcing re-entry.
574+
func askTLSFallback(r *input, c *config.AppConfig, save func()) {
575+
fmt.Println("\nTLS fallback target — where non-proxy visitors (and probes) land.")
576+
fmt.Println(" Default 127.0.0.1:8081 fronts your own site (e.g. a local reverse proxy);")
577+
fmt.Println(" enter a different host:port to change it, or 'no' for the built-in decoy site.")
578+
def := c.TLSFallbackDest
579+
if def == "" {
580+
def = defaultTLSFallbackDest
581+
}
582+
v := askClearable(r, " Fallback target (host:port; 'no' = built-in site)", def)
583+
if v != "" && !strings.Contains(v, ":") {
584+
fmt.Printf(" ! expected host:port (e.g. %s) — using the built-in site\n", defaultTLSFallbackDest)
585+
v = ""
586+
}
587+
c.TLSFallbackDest = v
588+
save()
589+
}
590+
564591
// leAgreementURL is the Let's Encrypt Subscriber Agreement shown before we
565592
// register an ACME account on the operator's behalf.
566593
const leAgreementURL = "https://letsencrypt.org/repository/"

0 commit comments

Comments
 (0)