diff --git a/install.sh b/install.sh index 1ff18dfd..1dd63586 100644 --- a/install.sh +++ b/install.sh @@ -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 diff --git a/main.go b/main.go index ec47b742..8c9f02bc 100644 --- a/main.go +++ b/main.go @@ -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) diff --git a/web/service/setting.go b/web/service/setting.go index e5a3a3d5..27357e86 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -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") } diff --git a/web/service/setting_test.go b/web/service/setting_test.go index 93ed9d03..22b606f1 100644 --- a/web/service/setting_test.go +++ b/web/service/setting_test.go @@ -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)