moki пре 2 недеља
родитељ
комит
14a6d11f3e
3 измењених фајлова са 132 додато и 4 уклоњено
  1. 33 4
      cmd/monitor/main.go
  2. 15 0
      internal/api/api.go
  3. 84 0
      internal/api/gencert.go

+ 33 - 4
cmd/monitor/main.go

@@ -65,6 +65,9 @@ func runServe(args []string) {
 	dbPath := fs.String("db", defaultDBPath, "数据库路径")
 	logFile := fs.String("log-file", "./logs", "日志文件路径(默认 ./logs/monitor.log)")
 	logLevel := fs.String("log-level", "info", "日志级别 (debug/info/warn/error)")
+	tls := fs.Bool("tls", false, "启用 HTTPS (使用自签名证书)")
+	tlsCert := fs.String("tls-cert", "./data/tls/cert.pem", "TLS 证书文件路径")
+	tlsKey := fs.String("tls-key", "./data/tls/key.pem", "TLS 私钥文件路径")
 	fs.Parse(args)
 
 	logger.SetLevel(logger.ParseLevel(*logLevel))
@@ -84,9 +87,20 @@ func runServe(args []string) {
 	logger.Info("数据库已连接: %s", *dbPath)
 
 	server := api.New(db, *addr)
-	logger.Info("API 服务已启动: %s", *addr)
 
-	fmt.Printf("API 服务已启动: %s\n", *addr)
+	if *tls {
+		if err := api.EnsureSelfSignedCert(*tlsCert, *tlsKey); err != nil {
+			logger.Error("生成自签名证书失败: %v", err)
+			fmt.Printf("生成自签名证书失败: %v\n", err)
+			return
+		}
+		server.EnableTLS(*tlsCert, *tlsKey)
+		logger.Info("HTTPS 已启用")
+		fmt.Println("HTTPS 已启用 (自签名证书)")
+	} else {
+		fmt.Printf("API 服务已启动: %s\n", *addr)
+	}
+
 	fmt.Println("接口文档:")
 	fmt.Println("  GET    /api/health       - 健康检查")
 	fmt.Println("  GET    /api/mqtt         - 获取所有配置")
@@ -109,6 +123,9 @@ func runMonitor(args []string) {
 	intervalFlag := fs.Int("interval", 1, "动态扫描间隔(秒), 默认1")
 	dbPath := fs.String("db", defaultDBPath, "数据库路径")
 	apiAddr := fs.String("api-addr", "", "API 服务地址 (如: :8080)")
+	tls := fs.Bool("tls", false, "启用 HTTPS (使用自签名证书)")
+	tlsCert := fs.String("tls-cert", "./data/tls/cert.pem", "TLS 证书文件路径")
+	tlsKey := fs.String("tls-key", "./data/tls/key.pem", "TLS 私钥文件路径")
 	logFile := fs.String("log-file", "./logs", "日志文件路径(默认 ./logs/monitor.log)")
 	logLevel := fs.String("log-level", "info", "日志级别 (debug/info/warn/error)")
 	fs.Parse(args)
@@ -170,9 +187,21 @@ func runMonitor(args []string) {
 	var apiServer *api.Server
 	if *apiAddr != "" {
 		apiServer = api.New(db, *apiAddr)
+		if *tls {
+			if err := api.EnsureSelfSignedCert(*tlsCert, *tlsKey); err != nil {
+				logger.Error("生成自签名证书失败: %v", err)
+				fmt.Printf("生成自签名证书失败: %v\n", err)
+				return
+			}
+			apiServer.EnableTLS(*tlsCert, *tlsKey)
+		}
 		go func() {
-			logger.Info("API 服务已启动: %s", *apiAddr)
-			fmt.Printf("API 服务已启动: %s\n", *apiAddr)
+			scheme := "http"
+			if *tls {
+				scheme = "https"
+			}
+			logger.Info("API 服务已启动: %s://%s", scheme, *apiAddr)
+			fmt.Printf("API 服务已启动: %s://%s\n", scheme, *apiAddr)
 			if err := apiServer.Start(); err != nil {
 				logger.Error("API 服务失败: %v", err)
 				fmt.Printf("API 服务失败: %v\n", err)

+ 15 - 0
internal/api/api.go

@@ -31,6 +31,8 @@ type Server struct {
 	upgrader  websocket.Upgrader
 	statusMap map[int]*ClientStatus
 	statusMu  sync.RWMutex
+	certFile  string
+	keyFile   string
 }
 
 type Response struct {
@@ -66,7 +68,20 @@ func New(db *database.DB, addr string) *Server {
 	return s
 }
 
+func (s *Server) EnableTLS(certFile, keyFile string) {
+	s.certFile = certFile
+	s.keyFile = keyFile
+}
+
 func (s *Server) Start() error {
+	if s.certFile != "" && s.keyFile != "" {
+		logger.Info("API 服务器开始监听 (HTTPS): %s", s.server.Addr)
+		err := s.server.ListenAndServeTLS(s.certFile, s.keyFile)
+		if err != nil && err != http.ErrServerClosed {
+			logger.Error("API 服务器监听失败: %v", err)
+		}
+		return err
+	}
 	logger.Info("API 服务器开始监听: %s", s.server.Addr)
 	err := s.server.ListenAndServe()
 	if err != nil && err != http.ErrServerClosed {

+ 84 - 0
internal/api/gencert.go

@@ -0,0 +1,84 @@
+package api
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"math/big"
+	"net"
+	"os"
+	"path/filepath"
+	"time"
+)
+
+func EnsureSelfSignedCert(certFile, keyFile string) error {
+	if _, err := os.Stat(certFile); err == nil {
+		if _, err := os.Stat(keyFile); err == nil {
+			return nil
+		}
+	}
+
+	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		return err
+	}
+
+	serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
+	if err != nil {
+		return err
+	}
+
+	tmpl := &x509.Certificate{
+		SerialNumber: serial,
+		Subject: pkix.Name{
+			Organization: []string{"AI-Status-Light"},
+			CommonName:   "localhost",
+		},
+		NotBefore:             time.Now(),
+		NotAfter:              time.Now().Add(10 * 365 * 24 * time.Hour),
+		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+		BasicConstraintsValid: true,
+		DNSNames:              []string{"localhost"},
+		IPAddresses:           []net.IP{net.ParseIP("127.0.0.1")},
+	}
+
+	certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &priv.PublicKey, priv)
+	if err != nil {
+		return err
+	}
+
+	if err := os.MkdirAll(filepath.Dir(certFile), 0700); err != nil {
+		return err
+	}
+
+	certOut, err := os.Create(certFile)
+	if err != nil {
+		return err
+	}
+	if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil {
+		certOut.Close()
+		return err
+	}
+	certOut.Close()
+
+	keyOut, err := os.Create(keyFile)
+	if err != nil {
+		return err
+	}
+	privBytes, err := x509.MarshalECPrivateKey(priv)
+	if err != nil {
+		keyOut.Close()
+		return err
+	}
+	if err := pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}); err != nil {
+		keyOut.Close()
+		return err
+	}
+	keyOut.Close()
+
+	return nil
+}