fix(agent,acme,cert,web): 修 frps 安装 404 与 nginx 探测两处,证书颁发机构用 LiteSSL 替换 Buypass(v1.2.14)
CI / verify (push) Canceled after 0s
CI / verify (push) Canceled after 0s
- frps 安装/升级一直 404:frp 官方校验文件名始终是 frp_sha256_checksums.txt(不带版本号),代码却拼 frp_<版本>_sha256sums.txt;现按候选名依次尝试并在失败时列出尝试项。同一条 bug 也存在于本机下载 frpc 的路径,一并修复(SHA-256 校验逻辑未改) - nginx 探测(agent):nginxPath() 不再回落到配置里的裸名字 nginx(否则「未安装」被误报为配置校验失败,且一键安装入口永不出现);vhost 目录探测不再要求被加载文件带 .conf 后缀(Debian 的 sites-enabled/default 不带,导致只剩主配置目录可试,报「均无法放置完整 server 配置」),失败信息补上具体原因 - 证书 CA:LiteSSL(亚数 TrustAsia,90 天,支持单域名/通配符/多域名)替换 Buypass。LiteSSL 目录 meta.externalAccountRequired=true,强制 EAB——在 FreeSSL 创建后填 Kid/HMAC 即可,HMAC 只回掩码;Buypass 保留常量与展示元数据但不再可选 - 前端申请证书弹窗的颁发机构卡片改为前端可控清单(原先后端 /api/certs/options 的结果不参与渲染,后端改了弹窗不变) - 测试:internal/frp 校验文件候选名与 sha256sum 解析;internal/acme LiteSSL 目录/别名/通配符/Buypass 停用提示。gofmt / go build / go vet / go test / vue-tsc / vite build 全绿 - AgentVersion 0.12.4 → 0.12.7,双架构二进制已重编:VPS 上需点「升级 Agent」
This commit is contained in:
@@ -43,7 +43,12 @@ import (
|
||||
// 0.12.2:新增域名 vhost 的版本列表 / 对比 / 回滚端点;写配置前留版本,校验失败时恢复上一个可用版本。
|
||||
// 0.12.3:frps 日志尾巴改为最近 20 行、最新在前,并用 short-iso 时间戳(默认格式不带年份、分不清哪一天)。
|
||||
// 0.12.4:日志行重写成「2026-09-22 18:43:34 进程: 消息」,去掉 T 分隔、时区偏移与每行重复的主机名。
|
||||
const AgentVersion = "0.12.4"
|
||||
// 0.12.5:修正 frps 官方校验文件名(frp 一直叫 frp_sha256_checksums.txt,原代码拼的是 frp_<版本>_sha256sums.txt,导致首次安装报 HTTP 404)。
|
||||
// 0.12.6:nginxPath 探测不到时返回空串(原来回落到配置里的裸名字 "nginx"),
|
||||
// 修掉「VPS 未安装 Nginx」告警与一键安装按钮永远不出现、以及把它误报成「配置校验未通过」的问题。
|
||||
// 0.12.7:vhost 目录探测不再要求被加载文件带 .conf 后缀(Debian 的 sites-enabled/default 不带),
|
||||
// 并在失败信息里带上最后一次的具体原因与常见的显式指定目录。
|
||||
const AgentVersion = "0.12.7"
|
||||
|
||||
// Config 是 fonu-agent 的运行配置。agent 只在 VPS 本地管理自己的 Nginx 片段、证书和 frps 配置。
|
||||
type Config struct {
|
||||
@@ -259,6 +264,25 @@ var frpsDownloadProxies = map[string]struct{}{
|
||||
|
||||
const frpsReleaseBaseURL = "https://github.com/fatedier/frp/releases/download/"
|
||||
|
||||
// fetchChecksumForCandidates 依次尝试官方校验文件名的候选。
|
||||
// frp 的校验文件一直叫 frp_sha256_checksums.txt(不带版本号),旧名字保留兜底,
|
||||
// 避开上游改名后整个安装流程直接 404。
|
||||
func fetchChecksumForCandidates(releaseURL, version, asset string) (string, error) {
|
||||
candidates := []string{
|
||||
"frp_sha256_checksums.txt",
|
||||
"frp_" + version + "_sha256sums.txt",
|
||||
}
|
||||
var lastErr error
|
||||
for _, name := range candidates {
|
||||
sum, err := fetchChecksumFor(releaseURL+name, asset)
|
||||
if err == nil {
|
||||
return sum, nil
|
||||
}
|
||||
lastErr = err
|
||||
}
|
||||
return "", fmt.Errorf("%v(已尝试:%s)", lastErr, strings.Join(candidates, "、"))
|
||||
}
|
||||
|
||||
// installFRPS 下载指定版本的 frp release、安装 frps 二进制并注册 systemd 服务(幂等,重复调用=升级)。
|
||||
func (s *Server) installFRPS(w http.ResponseWriter, r *http.Request) {
|
||||
var req frpsInstallRequest
|
||||
@@ -292,7 +316,7 @@ func (s *Server) installFRPS(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
// 先取官方校验文件,再下载安装包并按 SHA-256 校验:避免装上来路不明的 frps 可执行文件
|
||||
wantSum, err := fetchChecksumFor(releaseURL+"frp_"+version+"_sha256sums.txt", asset)
|
||||
wantSum, err := fetchChecksumForCandidates(releaseURL, version, asset)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": "获取 frps 官方校验信息失败: " + err.Error()})
|
||||
return
|
||||
@@ -646,9 +670,11 @@ func detectNginxConfDir(binary string) (string, error) {
|
||||
line = strings.TrimSpace(line)
|
||||
if value, found := strings.CutPrefix(line, "# configuration file "); found {
|
||||
p := strings.TrimSuffix(strings.TrimSpace(value), ":")
|
||||
if strings.HasSuffix(p, ".conf") {
|
||||
loadedDirs[filepath.Dir(p)] = true
|
||||
}
|
||||
// 不要求 .conf 后缀:Debian/Ubuntu 的 sites-enabled/default 本身不带后缀,
|
||||
// 而 nginx.conf 里是 include /etc/nginx/sites-enabled/*(也无后缀限制);
|
||||
// 只认 .conf 会把这个真正该放 vhost 的目录漏掉,只剩主配置目录可试,
|
||||
// 而主配置目录里的新文件并不被 include(表现为「均无法放置完整 server 配置」)。
|
||||
loadedDirs[filepath.Dir(p)] = true
|
||||
}
|
||||
}
|
||||
delete(loadedDirs, ".")
|
||||
@@ -667,13 +693,16 @@ func detectNginxConfDir(binary string) (string, error) {
|
||||
}
|
||||
return dirs[i] < dirs[j]
|
||||
})
|
||||
var lastReason string
|
||||
for _, dir := range dirs {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
lastReason = fmt.Sprintf("%s: %v", dir, err)
|
||||
continue
|
||||
}
|
||||
probe := filepath.Join(dir, "fonu-probe.conf")
|
||||
probeBody := "# Managed by fonu-agent. Do not edit.\nserver {\n listen 127.0.0.1:1;\n server_name fonu-probe.invalid;\n}\n"
|
||||
if err := atomicWrite(probe, []byte(probeBody), 0o644); err != nil {
|
||||
lastReason = fmt.Sprintf("%s: %v", dir, err)
|
||||
continue
|
||||
}
|
||||
// 两级验证:-t 仅验证语法,不保证文件被 include 加载(主配置目录等无 include 的目录也能通过)。
|
||||
@@ -685,6 +714,7 @@ func detectNginxConfDir(binary string) (string, error) {
|
||||
return dir, nil
|
||||
}
|
||||
// -t 通过但 -T 不含探针:该目录不被 include 加载,删除探针换下一个
|
||||
lastReason = fmt.Sprintf("%s: 目录未被 include 加载(nginx -T 输出中无探针文件)", dir)
|
||||
_ = os.Remove(probe)
|
||||
continue
|
||||
}
|
||||
@@ -692,13 +722,19 @@ func detectNginxConfDir(binary string) (string, error) {
|
||||
// 若 -t 失败信息指向历史残留的 fonu-*.conf,也一并清理(仅限 Agent 生成的文件)。
|
||||
_ = os.Remove(probe)
|
||||
if tOut, tErr := exec.Command(nginx, "-t").CombinedOutput(); tErr != nil {
|
||||
lastReason = fmt.Sprintf("%s: nginx -t 失败:%s", dir, tailText(string(tOut), 300))
|
||||
cleanupLegacyFonuConf(string(tOut))
|
||||
if err := runCommand(nginx, "-t"); err == nil {
|
||||
continue // 清理残留后配置恢复可用,换下一个目录
|
||||
}
|
||||
} else {
|
||||
lastReason = fmt.Sprintf("%s: 探针写入后 nginx -t 未通过", dir)
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("在 %d 个被加载目录中均无法放置完整 server 配置(已尝试: %s);可用 FONU_AGENT_NGINX_CONF_DIR 显式指定", len(dirs), strings.Join(dirs, ", "))
|
||||
if lastReason == "" {
|
||||
lastReason = "没有可尝试的目录"
|
||||
}
|
||||
return "", fmt.Errorf("在 %d 个被加载目录中均无法放置完整 server 配置(已尝试: %s);最后一次失败原因:%s;也可用 FONU_AGENT_NGINX_CONF_DIR 显式指定(Debian/Ubuntu 通常是 /etc/nginx/conf.d 或 /etc/nginx/sites-enabled)", len(dirs), strings.Join(dirs, ", "), lastReason)
|
||||
}
|
||||
|
||||
// dirHasVhostConf 判断目录下是否已有含 server_name 的 .conf(HTTP vhost 语义)。
|
||||
@@ -1178,11 +1214,18 @@ func (s *Server) validateNginx() error {
|
||||
}
|
||||
|
||||
func (s *Server) reloadNginx() error {
|
||||
return runCommand(s.nginxPath(), "-s", "reload")
|
||||
nginx := s.nginxPath()
|
||||
if nginx == "" {
|
||||
return errors.New("未找到 nginx 可执行文件:请先在「公网反代」页安装 Nginx,或用 FONU_AGENT_NGINX_BIN 指定路径")
|
||||
}
|
||||
return runCommand(nginx, "-s", "reload")
|
||||
}
|
||||
|
||||
// nginxPath 返回可用的 nginx 绝对路径:配置的路径可用则用之;否则探测常见安装位置
|
||||
// nginxPath 返回**可用的** nginx 可执行路径:配置的路径确实可用才用它,否则探测常见安装位置
|
||||
// (宝塔的 nginx 不在 systemd PATH 里,直接用名字会 exec 失败)。
|
||||
// 探测不到时返回空串。注意:不要回落到配置里的裸名字(如 "nginx"),
|
||||
// 那会让 probeNginx 的 Missing 判定永远不成立——「未安装」会被当成「配置校验失败」
|
||||
// (页面显示 nginx -t 执行失败),且一键安装入口永远不出现。
|
||||
func (s *Server) nginxPath() string {
|
||||
if p := strings.TrimSpace(s.cfg.NginxBinary); p != "" {
|
||||
if filepath.IsAbs(p) {
|
||||
@@ -1198,7 +1241,7 @@ func (s *Server) nginxPath() string {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return s.cfg.NginxBinary
|
||||
return ""
|
||||
}
|
||||
|
||||
// probeNginx 真实探测 Nginx 可用性:解析可执行文件并执行 nginx -t;结果缓存 30 秒。
|
||||
|
||||
Reference in New Issue
Block a user