144 lines
3.1 KiB
Go
144 lines
3.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"setec-manager/internal/acme"
|
|
)
|
|
|
|
type certInfo struct {
|
|
Domain string `json:"domain"`
|
|
Issuer string `json:"issuer"`
|
|
NotBefore time.Time `json:"not_before"`
|
|
NotAfter time.Time `json:"not_after"`
|
|
DaysLeft int `json:"days_left"`
|
|
AutoRenew bool `json:"auto_renew"`
|
|
}
|
|
|
|
func (h *Handler) SSLOverview(w http.ResponseWriter, r *http.Request) {
|
|
certs := h.listCerts()
|
|
h.render(w, "ssl.html", certs)
|
|
}
|
|
|
|
func (h *Handler) SSLStatus(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, h.listCerts())
|
|
}
|
|
|
|
func (h *Handler) SSLIssue(w http.ResponseWriter, r *http.Request) {
|
|
domain := paramStr(r, "domain")
|
|
if domain == "" {
|
|
writeError(w, http.StatusBadRequest, "domain required")
|
|
return
|
|
}
|
|
|
|
client := acme.NewClient(
|
|
h.Config.ACME.Email,
|
|
h.Config.ACME.Staging,
|
|
h.Config.Nginx.CertbotWebroot,
|
|
h.Config.ACME.AccountDir,
|
|
)
|
|
|
|
info, err := client.Issue(domain)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("certbot failed: %s", err))
|
|
return
|
|
}
|
|
|
|
// Update site SSL paths
|
|
site, _ := h.DB.GetSiteByDomain(domain)
|
|
if site != nil {
|
|
site.SSLEnabled = true
|
|
site.SSLCertPath = info.CertPath
|
|
site.SSLKeyPath = info.KeyPath
|
|
h.DB.UpdateSite(site)
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "issued", "cert": info.CertPath})
|
|
}
|
|
|
|
func (h *Handler) SSLRenew(w http.ResponseWriter, r *http.Request) {
|
|
domain := paramStr(r, "domain")
|
|
|
|
client := acme.NewClient(
|
|
h.Config.ACME.Email,
|
|
h.Config.ACME.Staging,
|
|
h.Config.Nginx.CertbotWebroot,
|
|
h.Config.ACME.AccountDir,
|
|
)
|
|
|
|
if err := client.Renew(domain); err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("renewal failed: %s", err))
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "renewed"})
|
|
}
|
|
|
|
func (h *Handler) listCerts() []certInfo {
|
|
var certs []certInfo
|
|
|
|
// First, gather certs from DB-tracked sites
|
|
sites, _ := h.DB.ListSites()
|
|
for _, site := range sites {
|
|
if !site.SSLEnabled || site.SSLCertPath == "" {
|
|
continue
|
|
}
|
|
|
|
ci := certInfo{
|
|
Domain: site.Domain,
|
|
AutoRenew: site.SSLAuto,
|
|
}
|
|
|
|
client := acme.NewClient(
|
|
h.Config.ACME.Email,
|
|
h.Config.ACME.Staging,
|
|
h.Config.Nginx.CertbotWebroot,
|
|
h.Config.ACME.AccountDir,
|
|
)
|
|
|
|
info, err := client.GetCertInfo(site.Domain)
|
|
if err == nil {
|
|
ci.Issuer = info.Issuer
|
|
ci.NotBefore = info.ExpiresAt.Add(-90 * 24 * time.Hour) // approximate
|
|
ci.NotAfter = info.ExpiresAt
|
|
ci.DaysLeft = info.DaysLeft
|
|
}
|
|
|
|
certs = append(certs, ci)
|
|
}
|
|
|
|
// Also check Let's Encrypt certs directory via ACME client
|
|
client := acme.NewClient(
|
|
h.Config.ACME.Email,
|
|
h.Config.ACME.Staging,
|
|
h.Config.Nginx.CertbotWebroot,
|
|
h.Config.ACME.AccountDir,
|
|
)
|
|
|
|
leCerts, _ := client.ListCerts()
|
|
for _, le := range leCerts {
|
|
// Skip if already found via site
|
|
found := false
|
|
for _, c := range certs {
|
|
if c.Domain == le.Domain {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
continue
|
|
}
|
|
|
|
certs = append(certs, certInfo{
|
|
Domain: le.Domain,
|
|
Issuer: le.Issuer,
|
|
NotAfter: le.ExpiresAt,
|
|
DaysLeft: le.DaysLeft,
|
|
})
|
|
}
|
|
|
|
return certs
|
|
}
|