package controller import ( "fmt" "github.com/gin-gonic/gin" "hy2xs-admin/model/constant" "hy2xs-admin/model/dto" "hy2xs-admin/model/vo" "hy2xs-admin/service" "time" ) // Ключи, которыми владеет install-оркестратор: панель обязана отказать в // записи, а не молча создать второй источник истины. // // Сетевых настроек самой панели в этом списке больше нет, потому что их нет // и в базе: порт и bind приходят из контракта запуска, TLS терминируется // снаружи. См. model/constant/config.go. func isOrchestratorManagedConfigKey(key string) bool { switch key { case constant.Hysteria2Enable, constant.Hysteria2Config, constant.Hysteria2TrafficStatsSecret: return true default: return false } } func UpdateConfigs(c *gin.Context) { configsUpdateDto, err := validateField(c, dto.ConfigsUpdateDto{}) if err != nil { return } needRestart := false for _, item := range configsUpdateDto.ConfigUpdateDtos { key := *item.Key value := *item.Value if isOrchestratorManagedConfigKey(key) { vo.Fail(fmt.Sprintf("%s managed by orchestrator: use hy2xs-orchestrator reconfigure", key), c) return } if key == constant.ResetTrafficCron { resetTrafficCron, err := service.GetConfig(constant.ResetTrafficCron) if err != nil { vo.Fail(err.Error(), c) return } if *resetTrafficCron.Value != value { needRestart = true } } if err = service.UpdateConfig(key, value); err != nil { vo.Fail(err.Error(), c) return } } if needRestart { go func() { _ = service.StopServer() }() } vo.Success(nil, c) } func GetConfig(c *gin.Context) { configDto, err := validateField(c, dto.ConfigDto{}) if err != nil { return } config, err := service.GetConfig(*configDto.Key) if err != nil { vo.Fail(err.Error(), c) return } configVo := vo.ConfigVo{ Key: *config.Key, Value: *config.Value, } running := service.Hysteria2IsRunning() if *config.Key == constant.Hysteria2Enable { if running { configVo.Value = "1" } else { configVo.Value = "0" } } vo.Success(configVo, c) } func ListConfig(c *gin.Context) { configsDto, err := validateField(c, dto.ConfigsDto{}) if err != nil { return } configs, err := service.ListConfig(configsDto.Keys) if err != nil { vo.Fail(err.Error(), c) return } var configVos []vo.ConfigVo for _, item := range configs { configVo := vo.ConfigVo{ Key: *item.Key, Value: *item.Value, } configVos = append(configVos, configVo) } vo.Success(configVos, c) } func GetHysteria2Config(c *gin.Context) { config, err := service.GetHysteria2Config() if err != nil { vo.Fail(err.Error(), c) return } vo.Success(config, c) } // ExportHysteria2Config отдаёт оператору фактический серверный конфиг. // // Экспорт работает от исходного YAML, а не от типизированной модели: поля, // о которых HY2XS ещё не знает, обязаны пережить выгрузку. Секреты при этом // вырезаются — файл покидает сервер. func ExportHysteria2Config(c *gin.Context) { sanitized, err := service.ExportHysteria2ConfigYaml() if err != nil { vo.Fail(err.Error(), c) return } fileName := fmt.Sprintf("Hysteria2Config-%s.yaml", time.Now().Format("20060102150405")) c.Header("Content-Type", "application/octet-stream") c.Header("Content-Transfer-Encoding", "binary") c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName)) c.Data(200, "application/octet-stream", sanitized) } // Generic-выгрузки и загрузки таблицы `config` здесь нет намеренно. // // Она отдавала таблицу целиком, исключая только сырой Hysteria YAML, а в той // же таблице лежат JWT_SECRET, PEER_SECRET_KEY, PEER_SECRET_ENCRYPTION_KEY и // HYSTERIA2_TRAFFIC_STATS_SECRET. Кнопка «Export» в панели выгружала их в // открытом виде, а зеркальный импорт позволял их подменить — включая ключ // шифрования, без которого перестают расшифровываться секреты уже // существующих пиров. // // Осмысленного production-сценария у этой пары не было: конфигурацией сервера // владеет install-оркестратор, перенос пиров делают ImportPeer/ExportPeer, а // серверный конфиг Hysteria выгружается отдельным санитизирующим маршрутом. // Поэтому маршруты удалены, а не оставлены с фильтром: список ключей, // требующий ручного сопровождения, снова разошёлся бы со схемой базы.