package nginx import ( "fmt" "os" "os/exec" "path/filepath" "strings" "github.com/panelhosting/panel/internal/ssl" ) type Manager struct { sitesDir string reloadCmd string sslStorage string } func NewManager(sitesDir, reloadCmd, sslStorage string) *Manager { return &Manager{sitesDir: sitesDir, reloadCmd: reloadCmd, sslStorage: sslStorage} } func (m *Manager) Enabled() bool { return m.sitesDir != "" } func (m *Manager) PrepareHTTPChallenge(domain, documentRoot string) error { if !m.Enabled() { return nil } if err := os.MkdirAll(m.sitesDir, 0o755); err != nil { return err } if err := os.MkdirAll(filepath.Join(documentRoot, ".well-known", "acme-challenge"), 0o755); err != nil { return err } cfg := fmt.Sprintf(`# PanelHosting — HTTP challenge for %s server { listen 80; listen [::]:80; server_name %s; location ^~ /.well-known/acme-challenge/ { root %s; default_type "text/plain"; try_files $uri =404; } location / { return 404; } } `, domain, domain, documentRoot) path := filepath.Join(m.sitesDir, sanitize(domain)+".conf") if err := os.WriteFile(path, []byte(cfg), 0o644); err != nil { return err } return m.reload() } func (m *Manager) ApplyHTTPS(domain, documentRoot string) error { if !m.Enabled() { return nil } certPath, keyPath := ssl.CertPaths(m.sslStorage, domain) cfg := fmt.Sprintf(`# PanelHosting — %s server { listen 80; listen [::]:80; server_name %s; location ^~ /.well-known/acme-challenge/ { root %s; default_type "text/plain"; try_files $uri =404; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name %s; ssl_certificate %s; ssl_certificate_key %s; root %s; index index.php index.html; location / { try_files $uri $uri/ =404; } } `, domain, domain, documentRoot, domain, domain, certPath, keyPath, documentRoot) path := filepath.Join(m.sitesDir, sanitize(domain)+".conf") if err := os.WriteFile(path, []byte(cfg), 0o644); err != nil { return err } return m.reload() } func (m *Manager) reload() error { cmd := strings.TrimSpace(m.reloadCmd) if cmd == "" { return nil } parts := strings.Fields(cmd) if len(parts) == 0 { return nil } c := exec.Command(parts[0], parts[1:]...) if out, err := c.CombinedOutput(); err != nil { return fmt.Errorf("nginx reload: %w: %s", err, strings.TrimSpace(string(out))) } return nil } func sanitize(domain string) string { return strings.NewReplacer(".", "_", ":", "_").Replace(domain) }