142 lines
4.3 KiB
Go
142 lines
4.3 KiB
Go
// oci-portal 后端服务启动入口。
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
// 纯 Go 时区数据(+~450KB):distroless/scratch 容器无 tzdata,
|
|
// 嵌入后 TZ 环境变量即可让 cron 按本地时区解释
|
|
_ "time/tzdata"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
// swag 生成的 OpenAPI spec(init 时注册,SWAGGER=1 的 /swagger 路由消费)
|
|
_ "oci-portal/docs"
|
|
"oci-portal/internal/api"
|
|
"oci-portal/internal/config"
|
|
"oci-portal/internal/crypto"
|
|
"oci-portal/internal/database"
|
|
"oci-portal/internal/oci"
|
|
"oci-portal/internal/service"
|
|
)
|
|
|
|
// @title OCI Portal API
|
|
// @version 0.0.1
|
|
// @description 自托管 OCI 多租户管理面板 API。业务接口用 JWT(Bearer);AI 网关端点(/ai/v1/*)用独立网关密钥(Authorization: Bearer 或 x-api-key)。
|
|
// @BasePath /
|
|
// @securityDefinitions.apikey BearerAuth
|
|
// @in header
|
|
// @name Authorization
|
|
// @description 登录接口返回的 JWT,格式: Bearer <token>
|
|
func main() {
|
|
healthcheck := flag.Bool("healthcheck", false, "探活本机服务后退出:健康 0,异常 1(容器 HEALTHCHECK 用)")
|
|
flag.Parse()
|
|
if *healthcheck {
|
|
os.Exit(runHealthcheck())
|
|
}
|
|
if err := run(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// runHealthcheck 请求本机 HTTP 端口:能响应且非 5xx 视为健康。
|
|
// distroless 镜像无 shell/wget,靠二进制自检 flag 支撑 HEALTHCHECK。
|
|
func runHealthcheck() int {
|
|
addr := os.Getenv("ADDR")
|
|
if addr == "" {
|
|
addr = ":8080"
|
|
}
|
|
_, port, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "healthcheck: bad ADDR %q: %v\n", addr, err)
|
|
return 1
|
|
}
|
|
client := &http.Client{Timeout: 3 * time.Second}
|
|
resp, err := client.Get("http://127.0.0.1:" + port + "/")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "healthcheck: %v\n", err)
|
|
return 1
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode >= http.StatusInternalServerError {
|
|
fmt.Fprintf(os.Stderr, "healthcheck: status %d\n", resp.StatusCode)
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// bootstrap 完成配置加载、数据库打开与加密组件构建。
|
|
func bootstrap() (*config.Config, *gorm.DB, *crypto.Cipher, error) {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
db, err := database.Open(cfg.DBDriver, cfg.DBDSN, cfg.DBPath)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
cipher, err := crypto.NewCipher(cfg.DataKey)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
return cfg, db, cipher, nil
|
|
}
|
|
|
|
func run() error {
|
|
cfg, db, cipher, err := bootstrap()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
auth := service.NewAuthService(db, cfg.JWTSecret)
|
|
auth.SetCipher(cipher)
|
|
if err := auth.EnsureAdmin(cfg.AdminUsername, cfg.AdminPassword); err != nil {
|
|
return err
|
|
}
|
|
ociClient := oci.NewCachedClient(oci.NewClient())
|
|
ociConfigs := service.NewOciConfigService(db, cipher, ociClient)
|
|
settings := service.NewSettingService(db, cipher)
|
|
settings.SetEnvPublicURL(cfg.PublicURL)
|
|
if err := settings.ReloadSecurity(context.Background()); err != nil {
|
|
return err
|
|
}
|
|
notifier := service.NewNotifier(settings)
|
|
auth.SetNotifier(notifier, settings)
|
|
oauth := service.NewOAuthService(db, settings, auth)
|
|
systemLogs := service.NewSystemLogService(db)
|
|
logEvents := service.NewLogEventService(db)
|
|
logEvents.SetRelayDeps(ociConfigs, ociClient, cfg.PublicURL)
|
|
logEvents.SetNotifier(notifier, settings)
|
|
cleanupCtx, stopCleanup := context.WithCancel(context.Background())
|
|
systemLogs.StartCleanup(cleanupCtx)
|
|
logEvents.StartParser(cleanupCtx)
|
|
logEvents.StartCleanup(cleanupCtx)
|
|
aiGateway := service.NewAiGatewayService(db, ociConfigs, ociClient)
|
|
aiGateway.StartCleanup(cleanupCtx)
|
|
// defer 为 LIFO:先停调度与清理,再等在途通知与日志写完
|
|
defer logEvents.Wait()
|
|
defer systemLogs.Wait()
|
|
defer notifier.Wait()
|
|
defer aiGateway.Wait()
|
|
defer stopCleanup()
|
|
tasks := service.NewTaskService(db, ociConfigs, notifier, settings)
|
|
tasks.AttachAiGateway(aiGateway)
|
|
aiGateway.SetOnChannelsChanged(tasks.SyncAiProbeTask)
|
|
if err := tasks.Start(); err != nil {
|
|
return err
|
|
}
|
|
defer tasks.Stop()
|
|
// 启动时对齐一次探测任务(已有渠道而任务缺失/暂停时自动建立或激活)
|
|
tasks.SyncAiProbeTask(context.Background())
|
|
console := service.NewConsoleService(ociConfigs)
|
|
proxies := service.NewProxyService(db, cipher)
|
|
defer proxies.Wait()
|
|
return api.NewRouter(auth, oauth, ociConfigs, tasks, console, settings, notifier, systemLogs, logEvents, proxies, aiGateway).Run(cfg.Addr)
|
|
}
|