package cmd import ( "errors" "fmt" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "hy2xs-admin/dao" "hy2xs-admin/middleware" "hy2xs-admin/model/constant" "hy2xs-admin/router" "hy2xs-admin/service" "hy2xs-admin/util" "net" "net/http" "os" "path/filepath" "strconv" "strings" ) func resolveUiBindHost() (string, error) { host := os.Getenv("HY2XS_UI_BIND_HOST") if host == "" { host = "127.0.0.1" } ip := net.ParseIP(host) if ip == nil || ip.To4() == nil { return "", errors.New("HY2XS_UI_BIND_HOST must be IPv4") } return host, nil } // resolveUiPort проверяет порт, полученный из контракта запуска. // // Источник истины — HY2XS_UI_PORT, который оркестратор подставляет в ExecStart // как `-p`. Раньше это значение по дороге записывалось в SQLite и читалось // оттуда обратно: круг, в котором база не добавляла ни одного факта, но делала // вид, что порт принадлежит ей. func resolveUiPort(port string) (int, error) { trimmed := strings.TrimSpace(port) if trimmed == "" { return 0, errors.New("UI port is required: pass -p ") } value, err := strconv.Atoi(trimmed) if err != nil || value < 1 || value > 65535 { return 0, fmt.Errorf("port: %s is invalid", port) } return value, nil } func runServer(port string) error { defer releaseResource() middleware.InitLog() uiPort, err := resolveUiPort(port) if err != nil { return err } bindHost, err := resolveUiBindHost() if err != nil { return err } if err := initFile(); err != nil { return err } if err := dao.InitSql(); err != nil { return err } if err := middleware.InitCron(); err != nil { return err } if err := service.InitHysteria2(); err != nil { return err } r := gin.Default() router.Router(r) // TLS внутри админки не поддерживается намеренно: панель слушает // loopback и публикуется через SSH-туннель или reverse proxy, на котором // TLS и заканчивается. service.InitServer(fmt.Sprintf("%s:%d", bindHost, uiPort), r) if err := service.StartServer(); err != nil && err != http.ErrServerClosed { logrus.Errorf("start server err: %v", err) return errors.New("start server err") } return nil } func releaseResource() { if err := dao.CloseSqliteDB(); err != nil { logrus.Errorf(err.Error()) } if err := service.ReleaseHysteria2(); err != nil { logrus.Errorf(err.Error()) } } func initFile() error { var dirs = []string{ constant.LogDir, constant.SqliteDBDir, constant.BinDir, filepath.Dir(constant.SqliteDBPath), } for _, item := range dirs { if !util.Exists(item) { if err := os.MkdirAll(item, 0o750); err != nil { logrus.Errorf("%s create err: %v", item, err) return errors.New(fmt.Sprintf("%s create err", item)) } } } return nil }