Add panel domain persistence

This commit is contained in:
Sora39831 2026-04-09 21:39:39 +08:00
parent 87566d22ef
commit 135ef32477
4 changed files with 129 additions and 6 deletions

View file

@ -110,6 +110,48 @@ gen_random_string() {
| head -c "$length"
}
save_panel_domain() {
local domain="$1"
if [[ -z "$domain" ]]; then
return 0
fi
${xui_folder}/x-ui setting -webDomain "${domain}" >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo -e "${red}保存面板域名失败:${domain}${plain}"
return 1
fi
local saved_domain
saved_domain=$(${xui_folder}/x-ui setting -show true 2>/dev/null | grep -Eo 'webDomain: .+' | awk '{print $2}' | tr -d '[:space:]')
if [[ "${saved_domain}" != "${domain}" ]]; then
echo -e "${red}面板域名未写入配置文件:期望 ${domain},实际 ${saved_domain:-}${plain}"
return 1
fi
return 0
}
verify_panel_cert_paths() {
local expected_cert="$1"
local expected_key="$2"
local current_cert current_key
current_cert=$(${xui_folder}/x-ui setting -getCert true 2>/dev/null | grep '^cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
current_key=$(${xui_folder}/x-ui setting -getCert true 2>/dev/null | grep '^key:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
if [[ "${current_cert}" != "${expected_cert}" || "${current_key}" != "${expected_key}" ]]; then
echo -e "${red}证书路径未写入配置文件${plain}"
echo -e "${yellow}期望证书:${expected_cert}${plain}"
echo -e "${yellow}实际证书:${current_cert:-}${plain}"
echo -e "${yellow}期望私钥:${expected_key}${plain}"
echo -e "${yellow}实际私钥:${current_key:-}${plain}"
return 1
fi
return 0
}
install_acme() {
echo -e "${green}正在安装 acme.sh 用于 SSL 证书管理...${plain}"
cd ~ || return 1
@ -182,6 +224,9 @@ setup_ssl_certificate() {
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" >/dev/null 2>&1
if ! verify_panel_cert_paths "$webCertFile" "$webKeyFile"; then
return 1
fi
echo -e "${green}SSL 证书安装并配置成功!${plain}"
return 0
else
@ -345,7 +390,7 @@ setup_ip_certificate() {
# 综合手动 SSL 证书签发(通过 acme.sh
ssl_cert_issue() {
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep 'webBasePath:' | awk -F': ' '{print $2}' | tr -d '[:space:]' | sed 's#^/##')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep '^port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
# 检查 acme.sh
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
@ -488,6 +533,13 @@ ssl_cert_issue() {
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
if ! verify_panel_cert_paths "$webCertFile" "$webKeyFile"; then
echo -e "${red}错误:证书路径未写入配置文件。${plain}"
return 1
fi
if ! save_panel_domain "$domain"; then
return 1
fi
echo -e "${green}面板证书路径已设置${plain}"
echo -e "${green}证书文件:$webCertFile${plain}"
echo -e "${green}私钥文件:$webKeyFile${plain}"
@ -536,6 +588,10 @@ prompt_and_setup_ssl() {
# 从证书中提取使用的域名
local cert_domain=$(~/.acme.sh/acme.sh --list 2>/dev/null | tail -1 | awk '{print $1}')
if [[ -n "${cert_domain}" ]]; then
if ! save_panel_domain "${cert_domain}"; then
SSL_HOST="${server_ip}"
return 1
fi
SSL_HOST="${cert_domain}"
echo -e "${green}✓ SSL 证书配置成功,域名:${cert_domain}${plain}"
else
@ -615,9 +671,17 @@ prompt_and_setup_ssl() {
# 3.4 通过 x-ui 二进制文件应用设置
${xui_folder}/x-ui cert -webCert "$custom_cert" -webCertKey "$custom_key" >/dev/null 2>&1
if ! verify_panel_cert_paths "$custom_cert" "$custom_key"; then
SSL_HOST="${server_ip}"
return 1
fi
# 设置 SSL_HOST 用于组成面板 URL
if [[ -n "$custom_domain" ]]; then
if ! save_panel_domain "$custom_domain"; then
SSL_HOST="${server_ip}"
return 1
fi
SSL_HOST="$custom_domain"
else
SSL_HOST="${server_ip}"
@ -731,6 +795,14 @@ prompt_and_setup_ssl() {
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" >/dev/null 2>&1
if ! verify_panel_cert_paths "$webCertFile" "$webKeyFile"; then
SSL_HOST="${server_ip}"
return 1
fi
if ! save_panel_domain "$cf_domain"; then
SSL_HOST="${server_ip}"
return 1
fi
echo -e "${green}✓ 面板证书已设置。${plain}"
else
echo -e "${red}未找到证书或私钥文件。${plain}"
@ -760,7 +832,7 @@ config_after_install() {
fi
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}' | sed 's#^/##')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep '^port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
# 通过检查 cert: 行是否存在且之后有内容来正确检测空证书
local existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local URL_lists=(
@ -816,6 +888,13 @@ config_after_install() {
fi
${xui_folder}/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
local saved_port
saved_port=$(${xui_folder}/x-ui setting -show true 2>/dev/null | grep '^port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
if [[ "${saved_port}" != "${config_port}" ]]; then
echo -e "${red}端口未写入配置文件:期望 ${config_port},实际 ${saved_port:-}${plain}"
return 1
fi
config_port="${saved_port}"
echo ""
echo -e "${green}═══════════════════════════════════════════${plain}"
@ -843,6 +922,7 @@ config_after_install() {
else
# 已有安装(存在 x-ui.json 或 x-ui.db保留所有配置不重新输入
local config_webBasePath="${existing_webBasePath}"
local existing_webDomain=$(${xui_folder}/x-ui setting -show true | grep '^webDomain:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
if [[ ${#config_webBasePath} -lt 4 ]]; then
config_webBasePath=$(gen_random_string 18)
echo -e "${yellow}WebBasePath 缺失或过短,正在生成新的...${plain}"
@ -853,7 +933,11 @@ config_after_install() {
if [[ -n "${existing_cert}" ]]; then
echo -e "${green}SSL 证书已配置。${plain}"
fi
echo -e "${green}访问地址https://${server_ip}:${existing_port}/${config_webBasePath}${plain}"
local final_host="${server_ip}"
if [[ -n "${existing_webDomain}" ]]; then
final_host="${existing_webDomain}"
fi
echo -e "${green}访问地址https://${final_host}:${existing_port}/${config_webBasePath}${plain}"
fi
${xui_folder}/x-ui migrate

23
main.go
View file

@ -160,6 +160,11 @@ func showSetting(show bool) {
fmt.Println("get webBasePath failed, error info:", err)
}
webDomain, err := settingService.GetWebDomain()
if err != nil {
fmt.Println("get webDomain failed, error info:", err)
}
certFile, err := settingService.GetCertFile()
if err != nil {
fmt.Println("get cert file failed, error info:", err)
@ -192,6 +197,7 @@ func showSetting(show bool) {
fmt.Println("hasDefaultCredential:", hasDefaultCredential)
fmt.Println("port:", port)
fmt.Println("webDomain:", webDomain)
fmt.Println("webBasePath:", webBasePath)
}
}
@ -254,8 +260,8 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
}
}
// updateSetting updates various panel settings including port, credentials, base path, listen IP, and two-factor authentication.
func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) {
// updateSetting updates various panel settings including port, domain, credentials, base path, listen IP, and two-factor authentication.
func updateSetting(port int, username string, password string, webBasePath string, webDomain string, listenIP string, resetTwoFactor bool) {
err := database.InitDB()
if err != nil {
fmt.Println("Database initialization failed:", err)
@ -292,6 +298,15 @@ func updateSetting(port int, username string, password string, webBasePath strin
}
}
if webDomain != "" {
err := settingService.SetWebDomain(webDomain)
if err != nil {
fmt.Println("Failed to set web domain:", err)
} else {
fmt.Printf("Web domain set successfully: %v\n", webDomain)
}
}
if resetTwoFactor {
err := settingService.SetTwoFactorEnable(false)
@ -473,6 +488,7 @@ func main() {
var username string
var password string
var webBasePath string
var webDomain string
var listenIP string
var getListen bool
var webCertFile string
@ -491,6 +507,7 @@ func main() {
settingCmd.StringVar(&username, "username", "", "Set login username")
settingCmd.StringVar(&password, "password", "", "Set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
settingCmd.StringVar(&webDomain, "webDomain", "", "Set panel domain")
settingCmd.StringVar(&listenIP, "listenIP", "", "set panel listenIP IP")
settingCmd.BoolVar(&resetTwoFactor, "resetTwoFactor", false, "Reset two-factor authentication settings")
settingCmd.BoolVar(&getListen, "getListen", false, "Display current panel listenIP IP")
@ -572,7 +589,7 @@ func main() {
if reset {
resetSetting()
} else {
updateSetting(port, username, password, webBasePath, listenIP, resetTwoFactor)
updateSetting(port, username, password, webBasePath, webDomain, listenIP, resetTwoFactor)
}
if show {
showSetting(show)

View file

@ -690,6 +690,10 @@ func (s *SettingService) GetWebDomain() (string, error) {
return s.getString("webDomain")
}
func (s *SettingService) SetWebDomain(domain string) error {
return s.setString("webDomain", domain)
}
func (s *SettingService) GetTgBotToken() (string, error) {
return s.getString("tgBotToken")
}

View file

@ -104,6 +104,24 @@ func TestSettingServiceSetAndGetString(t *testing.T) {
}
}
func TestSettingServiceSetAndGetWebDomain(t *testing.T) {
setupTestSettings(t)
svc := &SettingService{}
if err := svc.SetWebDomain("panel.example.com"); err != nil {
t.Fatalf("SetWebDomain error: %v", err)
}
val, err := svc.GetWebDomain()
if err != nil {
t.Fatalf("GetWebDomain error: %v", err)
}
if val != "panel.example.com" {
t.Fatalf("expected panel.example.com, got %s", val)
}
}
func TestSaveXrayTemplateConfigToDB_UpdatesSingleRow(t *testing.T) {
setupTestSettings(t)
setupTestDB(t)