diff --git a/api/waf_sys_info.go b/api/waf_sys_info.go index 51b7be1..a322910 100644 --- a/api/waf_sys_info.go +++ b/api/waf_sys_info.go @@ -168,6 +168,49 @@ func (w *WafSysInfoApi) CheckVersionApi(c *gin.Context) { } +// SystemParamsApi 返回认证后才能获取的系统参数(可扩展) +// GET /api/v1/sysinfo/systemparams +func (w *WafSysInfoApi) SystemParamsApi(c *gin.Context) { + response.OkWithDetailed(gin.H{ + "emergency_path": "/" + global.GWAF_SECURITY_EMERGENCY_PATH, + }, "获取成功", c) +} + +// RollbackListApi 列出所有可回退的备份版本 +// GET /api/v1/sysinfo/rollbacklist +func (w *WafSysInfoApi) RollbackListApi(c *gin.Context) { + list, err := wafupdate.ListBackups() + if err != nil { + response.FailWithMessage("获取备份列表失败: "+err.Error(), c) + return + } + response.OkWithDetailed(list, "获取成功", c) +} + +// RollbackApi 触发版本回退并重启 +// GET /api/v1/sysinfo/rollback?version=v1.x.x +func (w *WafSysInfoApi) RollbackApi(c *gin.Context) { + if global.GWAF_RUNTIME_IS_UPDATETING { + response.FailWithMessage("正在升级/回退中,请稍后", c) + return + } + version := c.Query("version") + global.GWAF_RUNTIME_IS_UPDATETING = true + err := wafupdate.RollbackExecutable(version) + if err != nil { + global.GWAF_RUNTIME_IS_UPDATETING = false + response.FailWithMessage("回退失败: "+err.Error(), c) + return + } + global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.UpdateResultMessageInfo{ + BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "系统即将重启", Server: global.GWAF_CUSTOM_SERVER_NAME}, + Msg: "版本回退成功,等待重启", + Success: "true", + }) + global.GWAF_CHAN_UPDATE <- 1 + response.OkWithMessage("已发起回退,等待通知结果", c) +} + // 去升级 func (w *WafSysInfoApi) UpdateApi(c *gin.Context) { // 获取请求中的 channel 参数 diff --git a/build_test_update.bat b/build_test_update.bat new file mode 100644 index 0000000..5cc2fff --- /dev/null +++ b/build_test_update.bat @@ -0,0 +1,36 @@ +@echo off +setlocal + +set "CURDIR=%~dp0" +set "CURDIR=%CURDIR:~0,-1%" + +SET CGO_ENABLED=1 +SET GOOS=windows +SET GOARCH=amd64 +SET GIN_MODE=release + +:: ---- Step 1: Build v1.1.0 ---- +echo [1/3] Building v1.1.0... +if not exist "%CURDIR%\release\githubci\v1.1.0" mkdir "%CURDIR%\release\githubci\v1.1.0" +go build -ldflags="-X SamWaf/global.GWAF_RELEASE=true -X SamWaf/global.GWAF_RELEASE_VERSION_NAME=20260224 -X SamWaf/global.GWAF_RELEASE_VERSION=v1.1.0 -X SamWaf/global.GUPDATE_VERSION_URL=http://127.0.0.1:8111/ -s -w" -o "%CURDIR%\release\githubci\v1.1.0\SamWaf64.exe" ./cmd/samwaf/main.go +if %ERRORLEVEL% neq 0 ( echo FAILED: v1.1.0 build error & pause & exit /b 1 ) +echo OK: release\githubci\v1.1.0\SamWaf64.exe + +:: ---- Step 2: Build v1.1.1 ---- +echo [2/3] Building v1.1.1... +if not exist "%CURDIR%\release\githubci\v1.1.1" mkdir "%CURDIR%\release\githubci\v1.1.1" +go build -ldflags="-X SamWaf/global.GWAF_RELEASE=true -X SamWaf/global.GWAF_RELEASE_VERSION_NAME=20260224 -X SamWaf/global.GWAF_RELEASE_VERSION=v1.1.1 -X SamWaf/global.GUPDATE_VERSION_URL=http://127.0.0.1:8111/ -s -w" -o "%CURDIR%\release\githubci\v1.1.1\SamWaf64.exe" ./cmd/samwaf/main.go +if %ERRORLEVEL% neq 0 ( echo FAILED: v1.1.1 build error & pause & exit /b 1 ) +echo OK: release\githubci\v1.1.1\SamWaf64.exe + +:: ---- Step 3: Package v1.1.1 update ---- +echo [3/3] Packaging v1.1.1... +"%CURDIR%\setup\go_gen_updatefile\go_gen_updatefile.exe" -desc "local-test-1.1.1" -o "%CURDIR%\release\web\samwaf_update" -platform windows-amd64 "%CURDIR%\release\githubci\v1.1.1\SamWaf64.exe" v1.1.1 +if %ERRORLEVEL% neq 0 ( echo FAILED: package error & pause & exit /b 1 ) +echo OK: release\web\samwaf_update\v1.1.1\windows-amd64.gz + +echo. +echo All done. Start v1.1.0 to test upgrade. +echo. +pause +endlocal diff --git a/cmd/samwaf/main.go b/cmd/samwaf/main.go index 50b9f10..3f4f453 100644 --- a/cmd/samwaf/main.go +++ b/cmd/samwaf/main.go @@ -29,6 +29,7 @@ import ( "SamWaf/wafsnowflake" "SamWaf/waftask" "SamWaf/waftunnelengine" + "SamWaf/wafupdate" "crypto/tls" "embed" _ "embed" @@ -926,6 +927,74 @@ func main() { fmt.Println("\n💻 SQL 执行工具") fmt.Println("可以在指定数据库上执行 SQL 语句\n") wafdb.ExecuteSQLCommand("") + case "rollback": //版本回退 + fmt.Println("================================================") + fmt.Println(" SamWaf 版本回退工具") + fmt.Println("================================================") + fmt.Printf("当前运行版本: %s\n\n", global.GWAF_RELEASE_VERSION) + + list, err := wafupdate.ListBackups() + if err != nil { + fmt.Println("获取备份列表失败:", err) + return + } + if len(list) == 0 { + fmt.Println("没有可用的备份版本,无法回退") + return + } + + fmt.Printf("%-4s %-15s %-22s %-10s %s\n", "序号", "版本", "备份时间", "大小(MB)", "备注") + fmt.Println("------------------------------------------------------------------------") + for i, b := range list { + note := "" + if b.Version == global.GWAF_RELEASE_VERSION { + note = "[当前版本]" + } + fmt.Printf("%-4d %-15s %-22s %-10.2f %s\n", + i+1, + b.Version, + b.BackupTime.Format("2006-01-02 15:04:05"), + float64(b.FileSize)/(1024*1024), + note) + } + fmt.Println("------------------------------------------------------------------------") + + fmt.Print("\n请输入要回退的序号,或输入 'q' 退出: ") + var input string + fmt.Scanln(&input) + if input == "q" || input == "Q" { + fmt.Println("已退出版本回退工具") + return + } + + idx := 0 + _, parseErr := fmt.Sscanf(input, "%d", &idx) + if parseErr != nil || idx < 1 || idx > len(list) { + fmt.Printf("无效的序号: %s\n", input) + return + } + + target := list[idx-1] + if target.Version == global.GWAF_RELEASE_VERSION { + fmt.Printf("所选版本 %s 与当前运行版本相同,无需回退\n", target.Version) + return + } + + fmt.Printf("\n即将回退到: %s(%s)\n", target.Version, target.BackupTime.Format("2006-01-02 15:04:05")) + fmt.Print("确认回退?回退后需要手动重启服务 (y/n): ") + var confirm string + fmt.Scanln(&confirm) + if confirm != "y" && confirm != "Y" { + fmt.Println("已取消") + return + } + + fmt.Printf("正在回退到版本 %s...\n", target.Version) + if rollbackErr := wafupdate.RollbackExecutable(target.Version); rollbackErr != nil { + fmt.Println("回退失败:", rollbackErr) + return + } + fmt.Println("回退成功,请重启服务 (samwaf start 或 samwaf restart)") default: fmt.Printf("Command '%s' is not recognized.\n", command) fmt.Println("\n可用命令:") @@ -938,6 +1007,7 @@ func main() { fmt.Println(" resetotp - 重置安全码") fmt.Println(" repairdb - 修复损坏的数据库") fmt.Println(" execsql - 执行SQL语句(支持SELECT/UPDATE/DELETE等)") + fmt.Println(" rollback - 回退到历史版本 (--list 列出, --version=v1.x.x 指定版本)") fmt.Println("") } return diff --git a/global/global.go b/global/global.go index 8b641c2..f7cdf92 100644 --- a/global/global.go +++ b/global/global.go @@ -76,10 +76,11 @@ var ( GWAF_TENANT_ID string = "SamWafCom" // 当前租户ID //管理端访问控制 - GWAF_IP_WHITELIST string = "0.0.0.0/0,::/0" //IP白名单 后台默认放行所有 - GWAF_SSL_ENABLE bool = false //是否启用SSL证书 - GWAF_SECURITY_ENTRY_ENABLE bool = false //是否启用安全路径入口 - GWAF_SECURITY_ENTRY_PATH string = "" //安全路径(18位随机码) + GWAF_IP_WHITELIST string = "0.0.0.0/0,::/0" //IP白名单 后台默认放行所有 + GWAF_SSL_ENABLE bool = false //是否启用SSL证书 + GWAF_SECURITY_ENTRY_ENABLE bool = false //是否启用安全路径入口 + GWAF_SECURITY_ENTRY_PATH string = "" //安全路径(18位随机码) + GWAF_SECURITY_EMERGENCY_PATH string = "" //应急恢复路径(随机生成,首次启动自动写入 conf/config.yml) //zlog 日志相关信息 GWAF_LOG_OUTPUT_FORMAT string = "console" //zlog输出格式 控制台格式console,json格式 diff --git a/router/waf_sysinfo.go b/router/waf_sysinfo.go index b4c5bb8..6ea117e 100644 --- a/router/waf_sysinfo.go +++ b/router/waf_sysinfo.go @@ -15,4 +15,7 @@ func (receiver *WebSysInfoRouter) InitSysInfoRouter(group *gin.RouterGroup) { router.GET("/api/v1/sysinfo/checkversion", api.CheckVersionApi) router.GET("/api/v1/sysinfo/update", api.UpdateApi) router.GET("/api/v1/sysinfo/announcement", api.GetAnnouncementApi) + router.GET("/api/v1/sysinfo/systemparams", api.SystemParamsApi) + router.GET("/api/v1/sysinfo/rollbacklist", api.RollbackListApi) + router.GET("/api/v1/sysinfo/rollback", api.RollbackApi) } diff --git a/wafconfig/wafconfig.go b/wafconfig/wafconfig.go index fda5c7e..4566725 100644 --- a/wafconfig/wafconfig.go +++ b/wafconfig/wafconfig.go @@ -187,6 +187,21 @@ func LoadAndInitConfig() { fmt.Printf("%s\tINFO\t安全路径入口已启用,自动生成访问码: %s\n", currentTime, global.GWAF_SECURITY_ENTRY_PATH) } + //配置和提取应急路径 + if config.IsSet("security.emergency_path") { + global.GWAF_SECURITY_EMERGENCY_PATH = config.GetString("security.emergency_path") + } else { + config.Set("security.emergency_path", "") + configChanged = true + } + //应急路径为空时自动生成(首次启动或手动清空后重启均会重新生成) + if global.GWAF_SECURITY_EMERGENCY_PATH == "" { + global.GWAF_SECURITY_EMERGENCY_PATH = generateSecurityEntryPath() + config.Set("security.emergency_path", global.GWAF_SECURITY_EMERGENCY_PATH) + configChanged = true + fmt.Printf("%s\tINFO\t应急恢复路径已生成: %s\n", currentTime, global.GWAF_SECURITY_EMERGENCY_PATH) + } + // 只有在配置发生变化时才写入文件 if configChanged { err := config.WriteConfig() diff --git a/wafmangeweb/emergency_page.html b/wafmangeweb/emergency_page.html new file mode 100644 index 0000000..a2c7433 --- /dev/null +++ b/wafmangeweb/emergency_page.html @@ -0,0 +1,397 @@ + + +
+ + +当前版本无法正常访问时,可在此执行版本回退
+ + + + +