main.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. package main
  2. import (
  3. "auto-ssl/config"
  4. "auto-ssl/handlers"
  5. "auto-ssl/services"
  6. "log"
  7. "net/http"
  8. "os"
  9. "strings"
  10. "time"
  11. "github.com/gin-contrib/cors"
  12. "github.com/gin-gonic/gin"
  13. "github.com/robfig/cron/v3"
  14. )
  15. func main() {
  16. cfg := config.Load()
  17. // Initialize certificate store (JSON file persistence)
  18. config.InitStore(cfg)
  19. // Setup Gin
  20. gin.SetMode(gin.ReleaseMode)
  21. r := gin.Default()
  22. // CORS for Vue frontend
  23. r.Use(cors.New(cors.Config{
  24. AllowOrigins: []string{"*"},
  25. AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
  26. AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"},
  27. AllowCredentials: true,
  28. }))
  29. // Serve static files for frontend
  30. r.Static("/assets", "./dist/assets")
  31. r.StaticFile("/favicon.ico", "./dist/favicon.ico")
  32. r.StaticFile("/favicon.svg", "./dist/favicon.svg")
  33. r.StaticFile("/", "./dist/index.html")
  34. // API routes
  35. api := r.Group("/api")
  36. {
  37. certHandler := handlers.NewCertHandler(cfg)
  38. // Certificate management
  39. api.GET("/certificates", certHandler.ListCertificates)
  40. api.GET("/certificates/:id", certHandler.GetCertificate)
  41. api.POST("/certificates", certHandler.CreateCertificate)
  42. api.PUT("/certificates/:id", certHandler.UpdateCertificate)
  43. api.DELETE("/certificates/:id", certHandler.DeleteCertificate)
  44. api.POST("/certificates/:id/renew", certHandler.RenewCertificate)
  45. api.GET("/certificates/:id/files", certHandler.GetCertFiles)
  46. // Utility
  47. api.GET("/renewals/check", certHandler.CheckRenewals)
  48. api.GET("/stats", certHandler.Stats)
  49. }
  50. r.NoRoute(func(c *gin.Context) {
  51. // 静态资源请求直接返回404,避免返回html导致MIME类型错误
  52. if strings.HasPrefix(c.Request.URL.Path, "/assets/") ||
  53. strings.HasSuffix(c.Request.URL.Path, ".js") ||
  54. strings.HasSuffix(c.Request.URL.Path, ".css") ||
  55. strings.HasSuffix(c.Request.URL.Path, ".png") ||
  56. strings.HasSuffix(c.Request.URL.Path, ".svg") ||
  57. strings.HasSuffix(c.Request.URL.Path, ".ico") {
  58. c.AbortWithStatus(http.StatusNotFound)
  59. return
  60. }
  61. // 其他路由返回index.html给前端处理
  62. c.File("./dist/index.html")
  63. })
  64. // Setup cron for auto-renewal (runs daily at 3:00 AM)
  65. c := cron.New()
  66. c.AddFunc("0 3 * * *", func() {
  67. log.Println("Running scheduled certificate renewal check...")
  68. certs := config.Store.GetActiveWithAutoRenew()
  69. for _, cert := range certs {
  70. if cert.ExpiresAt != nil && time.Until(*cert.ExpiresAt).Hours() < float64(cert.RenewDays*24) {
  71. log.Printf("Auto-renewing certificate for %s (expires %s)", cert.Domain, cert.ExpiresAt.Format(time.RFC3339))
  72. if err := services.RenewCertificate(cert, cfg); err != nil {
  73. cert.Status = "error"
  74. cert.ErrorMessage = "auto renew: " + err.Error()
  75. log.Printf("Auto-renew failed for %s: %v", cert.Domain, err)
  76. } else {
  77. log.Printf("Auto-renew succeeded for %s", cert.Domain)
  78. }
  79. config.Store.Upsert(cert)
  80. }
  81. }
  82. })
  83. c.Start()
  84. // Set ACME HTTP-01 challenge port from env (default 8082)
  85. // Nginx on port 80 should proxy .well-known/acme-challenge/ to this port
  86. acmePort := os.Getenv("ACME_PORT")
  87. if acmePort == "" {
  88. acmePort = "8082"
  89. }
  90. services.SetHTTP01Port(acmePort)
  91. log.Printf("ACME HTTP-01 challenge port: %s (nginx should proxy .well-known/acme-challenge/ to this port)", acmePort)
  92. log.Printf("AutoSSL server starting on :%s", cfg.Port)
  93. if err := r.Run(":" + cfg.Port); err != nil {
  94. log.Fatalf("Failed to start server: %v", err)
  95. }
  96. }