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"
"fmt"
"net"
"os"
"reflect"
"strconv"
"strings"
"time"
"github.com/mhsanaei/3x-ui/v2/config"
"github.com/mhsanaei/3x-ui/v2/database"
"github.com/mhsanaei/3x-ui/v2/database/model"
"github.com/mhsanaei/3x-ui/v2/logger"
@ -104,6 +106,69 @@ var defaultValueMap = map[string]string{
"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.
// It handles configuration storage, retrieval, and validation for all system settings.
type SettingService struct{}
@ -118,9 +183,7 @@ func (s *SettingService) GetDefaultJSONConfig() (any, error) {
}
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
db := database.GetDB()
settings := make([]*model.Setting, 0)
err := db.Model(model.Setting{}).Not("key = ?", "xrayTemplateConfig").Find(&settings).Error
settings, err := loadSettings()
if err != nil {
return nil, err
}
@ -148,7 +211,6 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
}
if !found {
// Some settings are automatically generated, no need to return to the front end to modify the user
return nil
}
@ -171,15 +233,18 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
}
keyMap := map[string]bool{}
for _, setting := range settings {
err := setSetting(setting.Key, setting.Value)
for key, value := range settings {
err := setSetting(key, value)
if err != nil {
return nil, err
}
keyMap[setting.Key] = true
keyMap[key] = true
}
for key, value := range defaultValueMap {
if key == "xrayTemplateConfig" {
continue
}
if keyMap[key] {
continue
}
@ -193,53 +258,41 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
}
func (s *SettingService) ResetSettings() error {
db := database.GetDB()
err := db.Where("1 = 1").Delete(model.Setting{}).Error
if err != nil {
// Delete the JSON settings file
err := os.Remove(config.GetSettingPath())
if err != nil && !os.IsNotExist(err) {
return err
}
return db.Model(model.User{}).
Where("1 = 1").Error
}
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
// Reset certificate settings to empty
if err := s.SetCertFile(""); err != nil {
return err
}
return setting, nil
return s.SetKeyFile("")
}
func (s *SettingService) saveSetting(key string, value string) error {
setting, err := s.getSetting(key)
db := database.GetDB()
if database.IsNotFound(err) {
return db.Create(&model.Setting{
Key: key,
Value: value,
}).Error
} else if err != nil {
settings, err := loadSettings()
if err != nil {
return err
}
setting.Key = key
setting.Value = value
return db.Save(setting).Error
settings[key] = value
return saveSettings(settings)
}
func (s *SettingService) getString(key string) (string, error) {
setting, err := s.getSetting(key)
if database.IsNotFound(err) {
value, ok := defaultValueMap[key]
if !ok {
return "", common.NewErrorf("key <%v> not in defaultValueMap", key)
}
return value, nil
} else if err != nil {
settings, err := loadSettings()
if err != nil {
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 {
@ -271,7 +324,12 @@ func (s *SettingService) setInt(key string, value int) 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) {
@ -693,20 +751,20 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
return err
}
settings, err := loadSettings()
if err != nil {
return err
}
v := reflect.ValueOf(allSetting).Elem()
t := reflect.TypeFor[entity.AllSetting]()
fields := reflect_util.GetFields(t)
errs := make([]error, 0)
for _, field := range fields {
key := field.Tag.Get("json")
fieldV := v.FieldByName(field.Name)
value := fmt.Sprint(fieldV.Interface())
err := s.saveSetting(key, value)
if err != nil {
errs = append(errs, err)
}
settings[key] = fmt.Sprint(fieldV.Interface())
}
return common.Combine(errs...)
return saveSettings(settings)
}
func (s *SettingService) GetDefaultXrayConfig() (any, error) {

24
x-ui.sh
View file

@ -188,6 +188,22 @@ uninstall() {
return 0
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
rc-service x-ui stop
rc-update del x-ui
@ -268,15 +284,21 @@ reset_webbasepath() {
}
reset_config() {
confirm "确定要重置所有面板设置吗?账户数据不会丢失,用户名和密码不会改变" "n"
confirm "确定要重置所有面板设置吗?这将清除面板的端口、路径、证书等配置,但不会删除账户数据和流量数据。" "n"
if [[ $? != 0 ]]; then
if [[ $# == 0 ]]; then
show_menu
fi
return 0
fi
# 重置面板证书配置
${xui_folder}/x-ui cert -reset 2>/dev/null
# 重置面板设置(端口、路径等)
${xui_folder}/x-ui setting -reset
echo -e "所有面板设置已重置为默认值。"
echo -e "${yellow}面板将使用默认端口 2053 和随机用户名/密码重新启动。${plain}"
restart
}