CI / verify (push) Canceled after 0s
- 配置版本:fsutil 版本化备份(内容相同自动去重),主 nginx.conf 与 VPS vhost 的版本列表/逐行差异/一键回滚,回滚先校验语法与 nginx -t 再写回 - agent 0.12.2:写 vhost 前留版本;nginx -t 校验失败改为恢复上一个可用版本(此前直接删文件),新增域名 vhost 版本端点,双架构二进制已重编 - 通知:补企业微信 / 钉钉 / 飞书机器人,切换预设时服务地址跟随该预设默认值 - 阈值告警:新增 internal/monitor 巡检(CPU/内存/磁盘持续超阈值、隧道连续 N 次未注册),默认关闭,带静默期与恢复通知 - API Token:api_tokens 表 + Authorization Bearer 认证,只读/读写两种范围;令牌管理与改密/退出仅限登录会话
105 lines
3.3 KiB
Go
105 lines
3.3 KiB
Go
package nginx
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/fonu/fonu/internal/fsutil"
|
|
)
|
|
|
|
// maxConfigVersions 主配置保留的版本份数。Apply 每次保存都会留一份,fsutil 会跳过内容相同的版本,
|
|
// 因此这里对应的是最近 10 次「配置真的变了」的改动。
|
|
const maxConfigVersions = 10
|
|
|
|
// ConfigVersion 是主配置(nginx.conf)的一个历史版本。
|
|
type ConfigVersion struct {
|
|
Name string `json:"name"`
|
|
Size int64 `json:"size"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// ReadConfig 读取当前主配置内容,供前端与历史版本做差异对比。
|
|
func (m *Manager) ReadConfig() (string, error) {
|
|
data, err := os.ReadFile(m.cfg.NginxConfigPath())
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", nil
|
|
}
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
// ListConfigVersions 列出主配置的历史版本(新的在前);从未写过配置时返回空列表。
|
|
func (m *Manager) ListConfigVersions() ([]ConfigVersion, error) {
|
|
versions, err := fsutil.ListVersions(m.cfg.NginxConfigPath())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]ConfigVersion, 0, len(versions))
|
|
for _, version := range versions {
|
|
out = append(out, ConfigVersion{Name: version.Name, Size: version.Size, CreatedAt: version.CreatedAt})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// ReadConfigVersion 读取某个历史版本的内容。
|
|
func (m *Manager) ReadConfigVersion(name string) (string, error) {
|
|
data, err := fsutil.ReadVersion(m.cfg.NginxConfigPath(), name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
// RollbackConfigVersion 把主配置回退到指定版本。
|
|
//
|
|
// 与 Apply 同构:先语法校验、再 nginx -t 校验,两关都过才写回并重载;校验失败保持当前配置不动。
|
|
// 写回前把当前内容也留成一个版本,所以回退本身同样可以再回退。
|
|
func (m *Manager) RollbackConfigVersion(ctx context.Context, name string) (ApplyResult, error) {
|
|
if name == "" {
|
|
return ApplyResult{}, fmt.Errorf("请选择要回滚的版本")
|
|
}
|
|
content, err := fsutil.ReadVersion(m.cfg.NginxConfigPath(), name)
|
|
if err != nil {
|
|
return ApplyResult{}, err
|
|
}
|
|
if err := m.EnsureDirs(); err != nil {
|
|
return ApplyResult{}, err
|
|
}
|
|
if err := ValidateConfigSyntax(string(content)); err != nil {
|
|
return ApplyResult{}, fmt.Errorf("该版本无法通过语法校验,已保持当前配置:%s", err)
|
|
}
|
|
if m.available() {
|
|
tmpPath := m.cfg.NginxConfigPath() + ".rollback.tmp"
|
|
if err := fsutil.WriteFileAtomic(tmpPath, content, 0o644); err != nil {
|
|
return ApplyResult{}, err
|
|
}
|
|
validateErr := m.validate(ctx, tmpPath)
|
|
_ = os.Remove(tmpPath)
|
|
if validateErr != nil {
|
|
return ApplyResult{}, fmt.Errorf("该版本无法通过 nginx 校验,已保持当前配置:%s", validateErr)
|
|
}
|
|
}
|
|
|
|
currentPath := m.cfg.NginxConfigPath()
|
|
if err := fsutil.Backup(currentPath); err != nil {
|
|
return ApplyResult{}, err
|
|
}
|
|
if _, err := fsutil.BackupVersioned(currentPath, maxConfigVersions); err != nil {
|
|
return ApplyResult{}, err
|
|
}
|
|
if err := fsutil.WriteFileAtomic(currentPath, content, 0o644); err != nil {
|
|
return ApplyResult{}, err
|
|
}
|
|
|
|
result, err := m.activate(ctx)
|
|
if err != nil {
|
|
return ApplyResult{}, err
|
|
}
|
|
result.Message = "已回滚到所选版本," + result.Message
|
|
return result, nil
|
|
}
|