Files

122 lines
3.0 KiB
Go

package main
import (
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/orohi/vpn-panel/internal/xraycfg"
)
const xrayContainer = "vpn-panel-xray"
// Official Xray-core image: https://github.com/XTLS/Xray-core
const xrayImage = "ghcr.io/xtls/xray-core:latest"
func (a *Agent) applyXray(cfg map[string]any) {
xrayDir := filepath.Join(a.dataDir, "xray")
certDir := filepath.Join(xrayDir, "certs")
_ = os.MkdirAll(certDir, 0o755)
configPath := filepath.Join(xrayDir, "config.json")
var list []xraycfg.Inbound
inboundsRaw, _ := cfg["inbounds"].([]any)
for _, raw := range inboundsRaw {
m, ok := raw.(map[string]any)
if !ok {
continue
}
in := xraycfg.FromMap(m)
if in.TLSCertPEM != "" && in.TLSKeyPEM != "" {
id := in.ID
if id == "" {
id = sanitizeFile(in.Tag)
}
_ = os.WriteFile(filepath.Join(certDir, id+".crt"), []byte(in.TLSCertPEM), 0o600)
_ = os.WriteFile(filepath.Join(certDir, id+".key"), []byte(in.TLSKeyPEM), 0o600)
}
list = append(list, in)
}
// Paths inside the Xray container
containerCertDir := "/usr/local/etc/xray/certs"
xrayCfg, n, err := xraycfg.Build(list, containerCertDir)
if err != nil {
log.Printf("xray build: %v", err)
return
}
b, err := json.MarshalIndent(xrayCfg, "", " ")
if err != nil {
log.Printf("xray marshal: %v", err)
return
}
if err := os.WriteFile(configPath, b, 0o600); err != nil {
log.Printf("xray write config: %v", err)
return
}
log.Printf("xray-core config written: %d inbound(s)", n)
if n == 0 {
_ = dockerRun("rm", "-f", xrayContainer)
log.Printf("xray stopped (no inbounds)")
return
}
hostData := os.Getenv("HOST_DATA_DIR")
if hostData == "" {
hostData = a.dataDir
}
hostXray := filepath.Join(hostData, "xray")
if err := ensureXrayContainer(hostXray); err != nil {
log.Printf("xray container: %v", err)
return
}
log.Printf("xray-core container ready (%s)", xrayImage)
}
func ensureXrayContainer(hostXrayDir string) error {
if _, err := exec.LookPath("docker"); err != nil {
return fmt.Errorf("docker not found (mount docker.sock): %w", err)
}
abs := hostXrayDir
if a, err := filepath.Abs(hostXrayDir); err == nil {
abs = a
}
// Prefer recreate for clean config pickup (official image has no inotify reload by default).
_ = dockerRun("rm", "-f", xrayContainer)
return dockerRun("run", "-d",
"--name", xrayContainer,
"--network", "host",
"--restart", "unless-stopped",
"-v", abs+":/usr/local/etc/xray",
xrayImage,
"run", "-c", "/usr/local/etc/xray/config.json",
)
}
func dockerRun(args ...string) error {
cmd := exec.Command("docker", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("docker %v: %v (%s)", args, err, strings.TrimSpace(string(out)))
}
return nil
}
func sanitizeFile(tag string) string {
tag = strings.Map(func(r rune) rune {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_' {
return r
}
return '_'
}, tag)
if tag == "" {
return "inbound"
}
return tag
}