feat: Template Xray TCP certificate and key file paths using a domain placeholder.

This commit is contained in:
Haitao Pan 2026-01-27 22:01:07 +08:00
parent 96a080bdf5
commit 283274e359
5 changed files with 43 additions and 4 deletions

View File

@ -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,

View File

@ -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"`

View File

@ -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,
},
}
}

View File

@ -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 {

View File

@ -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"
}
]
}