mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-08 22:24:15 +00:00
feat: improve uninstall and reset_config behavior
- uninstall: add certificate revocation prompt before removing - reset_config: fix misleading confirmation text, also reset cert config; remove user table deletion from Go ResetSettings
This commit is contained in:
parent
f5c931426d
commit
286056ab03
2 changed files with 130 additions and 50 deletions
|
|
@ -6,11 +6,13 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mhsanaei/3x-ui/v2/config"
|
||||||
"github.com/mhsanaei/3x-ui/v2/database"
|
"github.com/mhsanaei/3x-ui/v2/database"
|
||||||
"github.com/mhsanaei/3x-ui/v2/database/model"
|
"github.com/mhsanaei/3x-ui/v2/database/model"
|
||||||
"github.com/mhsanaei/3x-ui/v2/logger"
|
"github.com/mhsanaei/3x-ui/v2/logger"
|
||||||
|
|
@ -104,6 +106,69 @@ var defaultValueMap = map[string]string{
|
||||||
"ldapDefaultLimitIP": "0",
|
"ldapDefaultLimitIP": "0",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadSettings reads the JSON settings file into a map.
|
||||||
|
// If the file doesn't exist, it creates one from defaultValueMap (excluding xrayTemplateConfig).
|
||||||
|
func loadSettings() (map[string]string, error) {
|
||||||
|
path := config.GetSettingPath()
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
settings := make(map[string]string)
|
||||||
|
for k, v := range defaultValueMap {
|
||||||
|
if k == "xrayTemplateConfig" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
settings[k] = v
|
||||||
|
}
|
||||||
|
return settings, saveSettings(settings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var settings map[string]string
|
||||||
|
if err := json.Unmarshal(data, &settings); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse settings file %s: %w", path, err)
|
||||||
|
}
|
||||||
|
return settings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveSettings writes the settings map to the JSON file.
|
||||||
|
func saveSettings(settings map[string]string) error {
|
||||||
|
data, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(config.GetSettingPath(), data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getXrayTemplateConfigFromDB reads xrayTemplateConfig directly from the database.
|
||||||
|
func getXrayTemplateConfigFromDB() (string, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
setting := &model.Setting{}
|
||||||
|
err := db.Model(model.Setting{}).Where("key = ?", "xrayTemplateConfig").First(setting).Error
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return setting.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveXrayTemplateConfigToDB writes xrayTemplateConfig directly to the database.
|
||||||
|
func saveXrayTemplateConfigToDB(value string) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
setting := &model.Setting{}
|
||||||
|
err := db.Model(model.Setting{}).Where("key = ?", "xrayTemplateConfig").First(setting).Error
|
||||||
|
if database.IsNotFound(err) {
|
||||||
|
return db.Create(&model.Setting{
|
||||||
|
Key: "xrayTemplateConfig",
|
||||||
|
Value: value,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
setting.Value = value
|
||||||
|
return db.Save(setting).Error
|
||||||
|
}
|
||||||
|
|
||||||
// SettingService provides business logic for application settings management.
|
// SettingService provides business logic for application settings management.
|
||||||
// It handles configuration storage, retrieval, and validation for all system settings.
|
// It handles configuration storage, retrieval, and validation for all system settings.
|
||||||
type SettingService struct{}
|
type SettingService struct{}
|
||||||
|
|
@ -118,9 +183,7 @@ func (s *SettingService) GetDefaultJSONConfig() (any, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
||||||
db := database.GetDB()
|
settings, err := loadSettings()
|
||||||
settings := make([]*model.Setting, 0)
|
|
||||||
err := db.Model(model.Setting{}).Not("key = ?", "xrayTemplateConfig").Find(&settings).Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +211,6 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
// Some settings are automatically generated, no need to return to the front end to modify the user
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,15 +233,18 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
keyMap := map[string]bool{}
|
keyMap := map[string]bool{}
|
||||||
for _, setting := range settings {
|
for key, value := range settings {
|
||||||
err := setSetting(setting.Key, setting.Value)
|
err := setSetting(key, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
keyMap[setting.Key] = true
|
keyMap[key] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range defaultValueMap {
|
for key, value := range defaultValueMap {
|
||||||
|
if key == "xrayTemplateConfig" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if keyMap[key] {
|
if keyMap[key] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -193,53 +258,41 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) ResetSettings() error {
|
func (s *SettingService) ResetSettings() error {
|
||||||
db := database.GetDB()
|
// Delete the JSON settings file
|
||||||
err := db.Where("1 = 1").Delete(model.Setting{}).Error
|
err := os.Remove(config.GetSettingPath())
|
||||||
if err != nil {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return db.Model(model.User{}).
|
// Reset certificate settings to empty
|
||||||
Where("1 = 1").Error
|
if err := s.SetCertFile(""); err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
setting := &model.Setting{}
|
|
||||||
err := db.Model(model.Setting{}).Where("key = ?", key).First(setting).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return setting, nil
|
return s.SetKeyFile("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) saveSetting(key string, value string) error {
|
func (s *SettingService) saveSetting(key string, value string) error {
|
||||||
setting, err := s.getSetting(key)
|
settings, err := loadSettings()
|
||||||
db := database.GetDB()
|
if err != nil {
|
||||||
if database.IsNotFound(err) {
|
|
||||||
return db.Create(&model.Setting{
|
|
||||||
Key: key,
|
|
||||||
Value: value,
|
|
||||||
}).Error
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
setting.Key = key
|
settings[key] = value
|
||||||
setting.Value = value
|
return saveSettings(settings)
|
||||||
return db.Save(setting).Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) getString(key string) (string, error) {
|
func (s *SettingService) getString(key string) (string, error) {
|
||||||
setting, err := s.getSetting(key)
|
settings, err := loadSettings()
|
||||||
if database.IsNotFound(err) {
|
if err != nil {
|
||||||
value, ok := defaultValueMap[key]
|
|
||||||
if !ok {
|
|
||||||
return "", common.NewErrorf("key <%v> not in defaultValueMap", key)
|
|
||||||
}
|
|
||||||
return value, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return setting.Value, nil
|
value, ok := settings[key]
|
||||||
|
if !ok {
|
||||||
|
defaultValue, hasDefault := defaultValueMap[key]
|
||||||
|
if !hasDefault {
|
||||||
|
return "", common.NewErrorf("key <%v> not in defaultValueMap", key)
|
||||||
|
}
|
||||||
|
return defaultValue, nil
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) setString(key string, value string) error {
|
func (s *SettingService) setString(key string, value string) error {
|
||||||
|
|
@ -271,7 +324,12 @@ func (s *SettingService) setInt(key string, value int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetXrayConfigTemplate() (string, error) {
|
func (s *SettingService) GetXrayConfigTemplate() (string, error) {
|
||||||
return s.getString("xrayTemplateConfig")
|
config, err := getXrayTemplateConfigFromDB()
|
||||||
|
if err != nil {
|
||||||
|
// If not in DB, return the embedded default
|
||||||
|
return xrayTemplateConfig, nil
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetXrayOutboundTestUrl() (string, error) {
|
func (s *SettingService) GetXrayOutboundTestUrl() (string, error) {
|
||||||
|
|
@ -693,20 +751,20 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings, err := loadSettings()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
v := reflect.ValueOf(allSetting).Elem()
|
v := reflect.ValueOf(allSetting).Elem()
|
||||||
t := reflect.TypeFor[entity.AllSetting]()
|
t := reflect.TypeFor[entity.AllSetting]()
|
||||||
fields := reflect_util.GetFields(t)
|
fields := reflect_util.GetFields(t)
|
||||||
errs := make([]error, 0)
|
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
key := field.Tag.Get("json")
|
key := field.Tag.Get("json")
|
||||||
fieldV := v.FieldByName(field.Name)
|
fieldV := v.FieldByName(field.Name)
|
||||||
value := fmt.Sprint(fieldV.Interface())
|
settings[key] = fmt.Sprint(fieldV.Interface())
|
||||||
err := s.saveSetting(key, value)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
}
|
||||||
}
|
return saveSettings(settings)
|
||||||
return common.Combine(errs...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetDefaultXrayConfig() (any, error) {
|
func (s *SettingService) GetDefaultXrayConfig() (any, error) {
|
||||||
|
|
|
||||||
24
x-ui.sh
24
x-ui.sh
|
|
@ -188,6 +188,22 @@ uninstall() {
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 询问是否吊销证书
|
||||||
|
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \; 2>/dev/null)
|
||||||
|
if [[ -n "$domains" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "检测到以下证书:"
|
||||||
|
echo "$domains"
|
||||||
|
confirm "是否要吊销所有证书?" "n"
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
for domain in $domains; do
|
||||||
|
~/.acme.sh/acme.sh --revoke -d "${domain}" 2>/dev/null
|
||||||
|
LOGI "域名 $domain 的证书已吊销"
|
||||||
|
done
|
||||||
|
rm -rf /root/cert/
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $release == "alpine" ]]; then
|
if [[ $release == "alpine" ]]; then
|
||||||
rc-service x-ui stop
|
rc-service x-ui stop
|
||||||
rc-update del x-ui
|
rc-update del x-ui
|
||||||
|
|
@ -268,15 +284,21 @@ reset_webbasepath() {
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_config() {
|
reset_config() {
|
||||||
confirm "确定要重置所有面板设置吗?账户数据不会丢失,用户名和密码不会改变" "n"
|
confirm "确定要重置所有面板设置吗?这将清除面板的端口、路径、证书等配置,但不会删除账户数据和流量数据。" "n"
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
show_menu
|
show_menu
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 重置面板证书配置
|
||||||
|
${xui_folder}/x-ui cert -reset 2>/dev/null
|
||||||
|
# 重置面板设置(端口、路径等)
|
||||||
${xui_folder}/x-ui setting -reset
|
${xui_folder}/x-ui setting -reset
|
||||||
|
|
||||||
echo -e "所有面板设置已重置为默认值。"
|
echo -e "所有面板设置已重置为默认值。"
|
||||||
|
echo -e "${yellow}面板将使用默认端口 2053 和随机用户名/密码重新启动。${plain}"
|
||||||
restart
|
restart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue