From cb542092ce49793ef448e3113ec781c19b2e805d Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Tue, 27 Jan 2026 22:01:07 +0800 Subject: [PATCH] feat: Template Xray TCP certificate and key file paths using a domain placeholder. --- cmd/accountsvc/main.go | 2 ++ config/config.go | 1 + internal/agentmode/runner.go | 3 +++ internal/xrayconfig/generator.go | 37 +++++++++++++++++++++++++-- internal/xrayconfig/template_tcp.json | 4 +-- 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/cmd/accountsvc/main.go b/cmd/accountsvc/main.go index 92d4e3e..bdfc46f 100644 --- a/cmd/accountsvc/main.go +++ b/cmd/accountsvc/main.go @@ -211,10 +211,12 @@ func runServer(ctx context.Context, cfg *config.Config, logger *slog.Logger) err { Definition: xrayconfig.XHTTPDefinition(), OutputPath: "/usr/local/etc/xray/config.json", // Match user's xhttp config path + Domain: cfg.Xray.Sync.Domain, }, { Definition: xrayconfig.TCPDefinition(), OutputPath: "/usr/local/etc/xray/tcp-config.json", // Match user's tcp config path + Domain: cfg.Xray.Sync.Domain, }, }, ValidateCommand: cfg.Xray.Sync.ValidateCommand, diff --git a/config/config.go b/config/config.go index 1b30369..b9fa799 100644 --- a/config/config.go +++ b/config/config.go @@ -119,6 +119,7 @@ type Xray struct { type XraySync struct { Enabled bool `yaml:"enabled"` Interval time.Duration `yaml:"interval"` + Domain string `yaml:"domain"` OutputPath string `yaml:"outputPath"` TemplatePath string `yaml:"templatePath"` ValidateCommand []string `yaml:"validateCommand"` diff --git a/internal/agentmode/runner.go b/internal/agentmode/runner.go index 02983c8..d73165e 100644 --- a/internal/agentmode/runner.go +++ b/internal/agentmode/runner.go @@ -82,10 +82,12 @@ func Run(ctx context.Context, opts Options) error { { Definition: xrayconfig.XHTTPDefinition(), OutputPath: "/usr/local/etc/xray/config.json", + Domain: opts.Xray.Sync.Domain, }, { Definition: xrayconfig.TCPDefinition(), OutputPath: "/usr/local/etc/xray/tcp-config.json", + Domain: opts.Xray.Sync.Domain, }, } if templatePath := strings.TrimSpace(opts.Xray.Sync.TemplatePath); templatePath != "" { @@ -100,6 +102,7 @@ func Run(ctx context.Context, opts Options) error { { Definition: xrayconfig.JSONDefinition{Raw: append([]byte(nil), payload...)}, OutputPath: effectivePath, + Domain: opts.Xray.Sync.Domain, }, } } diff --git a/internal/xrayconfig/generator.go b/internal/xrayconfig/generator.go index fd28f6d..ef5b17f 100644 --- a/internal/xrayconfig/generator.go +++ b/internal/xrayconfig/generator.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "strings" + "text/template" ) const ( @@ -38,6 +39,9 @@ type Generator struct { // FileMode controls the permissions for the generated file. When zero it // defaults to 0644. FileMode fs.FileMode + + // Domain is the hostname used to interpolate templates (e.g. for cert paths). + Domain string } // Generate writes a new Xray configuration with the provided clients. The base @@ -72,9 +76,9 @@ func (g Generator) Render(clients []Client) ([]byte, error) { definition = DefaultDefinition() } - root, err := definition.Base() + root, err := g.renderTemplate(definition) if err != nil { - return nil, fmt.Errorf("load template: %w", err) + return nil, fmt.Errorf("render template: %w", err) } if err := replaceClients(root, clients); err != nil { @@ -89,6 +93,35 @@ func (g Generator) Render(clients []Client) ([]byte, error) { return buf, nil } +func (g Generator) renderTemplate(definition Definition) (map[string]interface{}, error) { + jsonDef, ok := definition.(JSONDefinition) + if !ok { + return definition.Base() + } + + tmpl, err := template.New("xray").Parse(string(jsonDef.Raw)) + if err != nil { + return nil, fmt.Errorf("parse template: %w", err) + } + + data := struct { + Domain string + }{ + Domain: g.Domain, + } + + var buf strings.Builder + if err := tmpl.Execute(&buf, data); err != nil { + return nil, fmt.Errorf("execute template: %w", err) + } + + var root map[string]interface{} + if err := json.Unmarshal([]byte(buf.String()), &root); err != nil { + return nil, fmt.Errorf("unmarshal rendered template: %w", err) + } + return root, nil +} + func replaceClients(root map[string]interface{}, clients []Client) error { inboundsValue, ok := root["inbounds"] if !ok { diff --git a/internal/xrayconfig/template_tcp.json b/internal/xrayconfig/template_tcp.json index 9268580..89f4445 100644 --- a/internal/xrayconfig/template_tcp.json +++ b/internal/xrayconfig/template_tcp.json @@ -43,8 +43,8 @@ "certificates": [ { "ocspStapling": 3600, - "certificateFile": "/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/ha-proxy-jp.svc.plus/ha-proxy-jp.svc.plus.crt", - "keyFile": "/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/ha-proxy-jp.svc.plus/ha-proxy-jp.svc.plus.key" + "certificateFile": "/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/{{.Domain}}/{{.Domain}}.crt", + "keyFile": "/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/{{.Domain}}/{{.Domain}}.key" } ] }