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:
Sora39831 2026-04-02 16:15:07 +08:00
parent f5c931426d
commit 286056ab03
2 changed files with 130 additions and 50 deletions

View file

@ -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
View file

@ -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
} }