feat(tls): add optional nginx setup and gateway preview

This commit is contained in:
opsx committed 2026-10-21 11:33:44 +08:00
1 parent 9f743e19ad
commit d2f1f5040e
15 files changed
+753 -16

No files matched your search

+59 -4
View File
@@ -23,6 +23,7 @@ var domainPattern = regexp.MustCompile(`^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:
type configuration struct {
domain string
mode string
dataDir string
cacheDir string
httpAddr string
@@ -36,6 +37,8 @@ var state struct {
type CertificateStatus struct {
Enabled bool `json:"enabled"`
Mode string `json:"mode"`
ConfiguredMode string `json:"configured_mode"`
Domain string `json:"domain"`
ConfiguredDomain string `json:"configured_domain"`
RestartRequired bool `json:"restart_required"`
@@ -55,6 +58,20 @@ func ValidDomain(domain string) bool {
}
func configFromEnvironment(dataDir string) (configuration, error) {
mode := os.Getenv("VPN_TLS_MODE")
if mode == "" {
if raw, err := os.ReadFile(filepath.Join(dataDir, "tls-mode")); err == nil {
mode = strings.TrimSpace(string(raw))
} else if !errors.Is(err, os.ErrNotExist) {
return configuration{}, fmt.Errorf("读取 TLS 模式配置: %w", err)
}
}
if mode == "" {
mode = "autocert"
}
if mode != "autocert" && mode != "nginx" {
return configuration{}, fmt.Errorf("VPN_TLS_MODE 仅支持 autocert 或 nginx")
}
domain := strings.ToLower(strings.TrimSpace(os.Getenv("VPN_TLS_DOMAIN")))
if domain == "" {
if raw, err := os.ReadFile(filepath.Join(dataDir, "tls-domain")); err == nil {
@@ -74,7 +91,7 @@ func configFromEnvironment(dataDir string) (configuration, error) {
if httpsAddr == "" {
httpsAddr = ":443"
}
return configuration{domain: domain, dataDir: dataDir, cacheDir: filepath.Join(dataDir, "acme"), httpAddr: httpAddr, httpsAddr: httpsAddr}, nil
return configuration{domain: domain, mode: mode, dataDir: dataDir, cacheDir: filepath.Join(dataDir, "acme"), httpAddr: httpAddr, httpsAddr: httpsAddr}, nil
}
func ConfigureDomain(dataDir, domain string) error {
@@ -109,6 +126,37 @@ func ConfigureDomain(dataDir, domain string) error {
return os.Rename(temp.Name(), filepath.Join(dataDir, "tls-domain"))
}
func ConfigureMode(dataDir, mode string) error {
if os.Getenv("VPN_TLS_MODE") != "" {
return fmt.Errorf("当前由 VPN_TLS_MODE 环境变量管理入口模式,请修改启动配置")
}
if mode != "autocert" && mode != "nginx" {
return fmt.Errorf("入口模式仅支持 autocert 或 nginx")
}
if err := os.MkdirAll(dataDir, 0o700); err != nil {
return err
}
temp, err := os.CreateTemp(dataDir, ".tls-mode-*")
if err != nil {
return err
}
defer os.Remove(temp.Name())
defer temp.Close()
if err := temp.Chmod(0o600); err != nil {
return err
}
if _, err := temp.WriteString(mode + "\n"); err != nil {
return err
}
if err := temp.Sync(); err != nil {
return err
}
if err := temp.Close(); err != nil {
return err
}
return os.Rename(temp.Name(), filepath.Join(dataDir, "tls-mode"))
}
// Start keeps certificates in the persistent data directory. Issuance occurs
// on the first TLS handshake, and autocert handles renewal.
func Start(handler http.Handler, dataDir string) error {
@@ -116,7 +164,7 @@ func Start(handler http.Handler, dataDir string) error {
if err != nil {
return err
}
if config.domain == "" {
if config.domain == "" || config.mode == "nginx" {
state.Lock()
state.config = config
state.Unlock()
@@ -173,10 +221,17 @@ func Status() CertificateStatus {
state.RLock()
config := state.config
state.RUnlock()
status := CertificateStatus{Enabled: config.domain != "", Domain: config.domain, DNS: []string{}, HTTPAddr: config.httpAddr, HTTPSAddr: config.httpsAddr, Renewal: "autocert"}
status := CertificateStatus{Enabled: config.domain != "" && config.mode == "autocert", Domain: config.domain, Mode: config.mode, DNS: []string{}, HTTPAddr: config.httpAddr, HTTPSAddr: config.httpsAddr, Renewal: config.mode}
if desired, err := configFromEnvironment(config.dataDir); err == nil {
status.ConfiguredDomain = desired.domain
status.RestartRequired = desired.domain != config.domain
status.ConfiguredMode = desired.mode
status.RestartRequired = desired.domain != config.domain || desired.mode != config.mode
}
if config.mode == "nginx" {
// In proxy mode, certbot owns the certificate; the Go service does not
// claim that Nginx is listening merely because a domain was saved.
status.Domain = ""
return status
}
if !status.Enabled {
return status