This commit is contained in:
@@ -20,7 +20,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// AgentClient 调用公网 VPS 上的 fonu-agent。凭据由调用方解密后只在内存中使用。
|
||||
// AgentClient 调用公网 VPS 上的 havline-agent。凭据由调用方解密后只在内存中使用。
|
||||
type AgentClient struct {
|
||||
baseURL string
|
||||
token string
|
||||
|
||||
@@ -32,7 +32,7 @@ func (s *Service) AgentNginxLogs(ctx context.Context, serverID int64, kind strin
|
||||
return client.NginxLogs(ctx, kind, lines)
|
||||
}
|
||||
|
||||
// AgentCerts 列出 VPS 证书目录下的域名与到期时间,供与 Fonu 证书库交叉核对
|
||||
// AgentCerts 列出 VPS 证书目录下的域名与到期时间,供与 Havline 证书库交叉核对
|
||||
func (s *Service) AgentCerts(ctx context.Context, serverID int64) (map[string]any, error) {
|
||||
client, ok := s.agentClient(ctx, serverID)
|
||||
if !ok {
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fonu/fonu/internal/frp/agentbin"
|
||||
"github.com/havline/havline/internal/frp/agentbin"
|
||||
)
|
||||
|
||||
// AgentInstaller 负责把内嵌的 fonu-agent 通过 SSH 装到 VPS 上,并收集安装结果。
|
||||
// AgentInstaller 负责把内嵌的 havline-agent 通过 SSH 装到 VPS 上,并收集安装结果。
|
||||
type AgentInstaller struct {
|
||||
SSH SSHInstaller
|
||||
}
|
||||
@@ -28,12 +28,12 @@ func (i AgentInstaller) Probe(ctx context.Context) (map[string]any, error) {
|
||||
return map[string]any{
|
||||
"arch": arch,
|
||||
"nginx_conf": out(`conf=$(nginx -V 2>&1 | grep -o '\-\-conf-path=[^ ]*' | head -1 | cut -d= -f2-); [ -z "$conf" ] && conf=/etc/nginx/nginx.conf; grep '^[[:space:]]*include' "$conf" 2>/dev/null | grep -m1 -E 'conf\.d|sites-enabled|vhost/nginx' || ls -d /etc/nginx/conf.d 2>/dev/null`),
|
||||
"agent_exist": out("test -x /usr/local/bin/fonu-agent && echo yes || echo no") == "yes",
|
||||
"agent_running": out("systemctl is-active fonu-agent 2>/dev/null || true"),
|
||||
"agent_version": out("/usr/local/bin/fonu-agent -version 2>/dev/null || echo 未知"),
|
||||
"agent_exist": out("test -x /usr/local/bin/havline-agent && echo yes || echo no") == "yes",
|
||||
"agent_running": out("systemctl is-active havline-agent 2>/dev/null || true"),
|
||||
"agent_version": out("/usr/local/bin/havline-agent -version 2>/dev/null || echo 未知"),
|
||||
// Token 只回「是否已配置 + 末 4 位」:明文不经过 HTTP 返回前端(库里同一字段也是掩码)
|
||||
"agent_token_configured": out("test -s /etc/fonu-agent/agent.env && grep -q '^FONU_AGENT_TOKEN=' /etc/fonu-agent/agent.env && echo yes || echo no") == "yes",
|
||||
"agent_token_hint": out("sed -n 's/^FONU_AGENT_TOKEN=//p' /etc/fonu-agent/agent.env 2>/dev/null | tr -d '\\n' | tail -c 4"),
|
||||
"agent_token_configured": out("test -s /etc/havline-agent/agent.env && grep -q '^HAVLINE_AGENT_TOKEN=' /etc/havline-agent/agent.env && echo yes || echo no") == "yes",
|
||||
"agent_token_hint": out("sed -n 's/^HAVLINE_AGENT_TOKEN=//p' /etc/havline-agent/agent.env 2>/dev/null | tr -d '\\n' | tail -c 4"),
|
||||
"frps_alive": out("systemctl is-active frps 2>/dev/null; pgrep -x frps >/dev/null && echo active || true"),
|
||||
"frps_version": out("/usr/local/bin/frps --version 2>/dev/null || echo 未安装"),
|
||||
"frps_config": out("test -f /etc/frp/frps.toml && echo /etc/frp/frps.toml || echo 配置文件不存在"),
|
||||
@@ -57,7 +57,7 @@ func (i AgentInstaller) Install(ctx context.Context) (string, error) {
|
||||
}
|
||||
// 开发环境(go run / 非 Docker 构建)里内嵌的是占位文件;提前报错而不是让 VPS 上 systemd 反复 Exec format error
|
||||
if !isELFBinary(binary) {
|
||||
return "", fmt.Errorf("内嵌 agent 二进制无效(开发构建占位文件)。请用 Docker 构建或先执行构建脚本生成真实 agent:go build -o internal/frp/agentbin/fonu-agent-linux-amd64 ./cmd/fonu-agent(GOOS=linux GOARCH=amd64)")
|
||||
return "", fmt.Errorf("内嵌 agent 二进制无效(开发构建占位文件)。请用 Docker 构建或先执行构建脚本生成真实 agent:go build -o internal/frp/agentbin/havline-agent-linux-amd64 ./cmd/havline-agent(GOOS=linux GOARCH=amd64)")
|
||||
}
|
||||
script, err := agentInstallScript()
|
||||
if err != nil {
|
||||
@@ -76,7 +76,7 @@ func (i AgentInstaller) Install(ctx context.Context) (string, error) {
|
||||
|
||||
// Uninstall 停止并移除 agent 服务;--keep-data 保留路由与证书。
|
||||
func (i AgentInstaller) Uninstall(ctx context.Context, keepData bool) (string, error) {
|
||||
command := "/usr/local/bin/fonu-agent-uninstall"
|
||||
command := "/usr/local/bin/havline-agent-uninstall"
|
||||
if keepData {
|
||||
command += " --keep-data"
|
||||
}
|
||||
@@ -86,14 +86,14 @@ func (i AgentInstaller) Uninstall(ctx context.Context, keepData bool) (string, e
|
||||
|
||||
// RestorePreviousConfig 在安全迁移验证失败时恢复安装前的 agent.env,并重启旧监听配置。
|
||||
func (i AgentInstaller) RestorePreviousConfig(ctx context.Context) error {
|
||||
command := `if [ -f /etc/fonu-agent/agent.env.previous ]; then cp /etc/fonu-agent/agent.env.previous /etc/fonu-agent/agent.env && chmod 600 /etc/fonu-agent/agent.env && systemctl restart fonu-agent; fi`
|
||||
command := `if [ -f /etc/havline-agent/agent.env.previous ]; then cp /etc/havline-agent/agent.env.previous /etc/havline-agent/agent.env && chmod 600 /etc/havline-agent/agent.env && systemctl restart havline-agent; fi`
|
||||
_, err := i.SSH.Run(ctx, command)
|
||||
return err
|
||||
}
|
||||
|
||||
// Diagnose agent 失联时走 SSH 急救:进程、端口、服务日志尾部。
|
||||
func (i AgentInstaller) Diagnose(ctx context.Context) (string, error) {
|
||||
return i.SSH.Run(ctx, "systemctl status fonu-agent --no-pager -l 2>/dev/null | head -20; echo '---'; journalctl -u fonu-agent -n 15 --no-pager 2>/dev/null; echo '---'; ss -tlnp 2>/dev/null | grep ':7700 ' || echo '7700 not listening'")
|
||||
return i.SSH.Run(ctx, "systemctl status havline-agent --no-pager -l 2>/dev/null | head -20; echo '---'; journalctl -u havline-agent -n 15 --no-pager 2>/dev/null; echo '---'; ss -tlnp 2>/dev/null | grep ':7700 ' || echo '7700 not listening'")
|
||||
}
|
||||
|
||||
func extractToken(output string) string {
|
||||
@@ -110,25 +110,25 @@ func agentInstallScript() ([]byte, error) {
|
||||
return []byte(`#!/usr/bin/env bash
|
||||
set -eu
|
||||
if [ "$(id -u)" -ne 0 ]; then echo "请使用 root 或 sudo 运行" >&2; exit 1; fi
|
||||
AGENT_BINARY=${1:-/tmp/fonu-agent}
|
||||
PREFIX=/usr/local/bin/fonu-agent
|
||||
CONF_DIR=/etc/fonu-agent
|
||||
AGENT_BINARY=${1:-/tmp/havline-agent}
|
||||
PREFIX=/usr/local/bin/havline-agent
|
||||
CONF_DIR=/etc/havline-agent
|
||||
CONF_FILE="$CONF_DIR/agent.env"
|
||||
UNIT=/etc/systemd/system/fonu-agent.service
|
||||
UNIT=/etc/systemd/system/havline-agent.service
|
||||
|
||||
if [ "${1:-}" = "--uninstall" ]; then
|
||||
systemctl stop fonu-agent 2>/dev/null || true
|
||||
systemctl disable fonu-agent 2>/dev/null || true
|
||||
rm -f "$UNIT" "$PREFIX" /usr/local/bin/fonu-agent-uninstall
|
||||
systemctl stop havline-agent 2>/dev/null || true
|
||||
systemctl disable havline-agent 2>/dev/null || true
|
||||
rm -f "$UNIT" "$PREFIX" /usr/local/bin/havline-agent-uninstall
|
||||
systemctl daemon-reload
|
||||
if [ "${2:-}" != "--keep-data" ]; then rm -rf /var/lib/fonu-agent; fi
|
||||
echo "fonu-agent 已卸载"
|
||||
if [ "${2:-}" != "--keep-data" ]; then rm -rf /var/lib/havline-agent; fi
|
||||
echo "havline-agent 已卸载"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$CONF_DIR" /var/lib/fonu-agent/certs
|
||||
mkdir -p "$CONF_DIR" /var/lib/havline-agent/certs
|
||||
# 先停服务,再原子替换;安装后必须确认目标存在、可执行且能输出版本
|
||||
systemctl stop fonu-agent 2>/dev/null || true
|
||||
systemctl stop havline-agent 2>/dev/null || true
|
||||
TMP_PREFIX="${PREFIX}.new"
|
||||
# 迁移旧版 Agent 留在 nginx include 范围内的 <配置>.versions 目录(保留历史版本内容)
|
||||
for NGINX_DIR in /etc/nginx/sites-enabled /etc/nginx/conf.d; do
|
||||
@@ -157,23 +157,23 @@ fi
|
||||
printf 'agent version: %s\n' "$AGENT_VERSION"
|
||||
if [ ! -f "$CONF_FILE" ]; then
|
||||
umask 077
|
||||
# 用 /dev/urandom 生成 64 位 hex token,不依赖 openssl(缺失时会静默写入空 token,导致 Fonu 无法认证 agent)
|
||||
printf 'FONU_AGENT_TOKEN=%s\n' "$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')" > "$CONF_FILE"
|
||||
# 用 /dev/urandom 生成 64 位 hex token,不依赖 openssl(缺失时会静默写入空 token,导致 Havline 无法认证 agent)
|
||||
printf 'HAVLINE_AGENT_TOKEN=%s\n' "$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')" > "$CONF_FILE"
|
||||
fi
|
||||
cp "$CONF_FILE" "$CONF_FILE.previous" 2>/dev/null || true
|
||||
touch "$CONF_FILE"
|
||||
sed -i '/^FONU_AGENT_LISTEN=/d; /^FONU_AGENT_ALLOW_REMOTE=/d' "$CONF_FILE"
|
||||
printf 'FONU_AGENT_LISTEN=127.0.0.1:7700\nFONU_AGENT_ALLOW_REMOTE=0\n' >> "$CONF_FILE"
|
||||
sed -i '/^HAVLINE_AGENT_LISTEN=/d; /^HAVLINE_AGENT_ALLOW_REMOTE=/d' "$CONF_FILE"
|
||||
printf 'HAVLINE_AGENT_LISTEN=127.0.0.1:7700\nHAVLINE_AGENT_ALLOW_REMOTE=0\n' >> "$CONF_FILE"
|
||||
cat > "$UNIT" <<'UNIT'
|
||||
[Unit]
|
||||
Description=Fonu FRPS management agent
|
||||
Description=Havline FRPS management agent
|
||||
After=network-online.target nginx.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/etc/fonu-agent/agent.env
|
||||
ExecStart=/usr/local/bin/fonu-agent
|
||||
EnvironmentFile=/etc/havline-agent/agent.env
|
||||
ExecStart=/usr/local/bin/havline-agent
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
@@ -181,11 +181,11 @@ RestartSec=3
|
||||
WantedBy=multi-user.target
|
||||
UNIT
|
||||
systemctl daemon-reload
|
||||
systemctl restart fonu-agent
|
||||
systemctl enable fonu-agent
|
||||
cp "$0" /usr/local/bin/fonu-agent-uninstall
|
||||
echo "fonu-agent installed"
|
||||
echo "Token: $(sed -n 's/^FONU_AGENT_TOKEN=//p' "$CONF_FILE")"
|
||||
systemctl restart havline-agent
|
||||
systemctl enable havline-agent
|
||||
cp "$0" /usr/local/bin/havline-agent-uninstall
|
||||
echo "havline-agent installed"
|
||||
echo "Token: $(sed -n 's/^HAVLINE_AGENT_TOKEN=//p' "$CONF_FILE")"
|
||||
`), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package agentbin 内嵌各架构 fonu-agent 二进制,供 SSH 安装器按 VPS 架构选择。
|
||||
// Package agentbin 内嵌各架构 havline-agent 二进制,供 SSH 安装器按 VPS 架构选择。
|
||||
// 仓库中的占位文件保证开发机构建可通过;Docker 构建阶段会用真实二进制覆盖它们
|
||||
// (见 Dockerfile:先交叉编译 agent,再构建主程序)。
|
||||
package agentbin
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed fonu-agent-linux-amd64
|
||||
//go:embed havline-agent-linux-amd64
|
||||
var LinuxAMD64 []byte
|
||||
|
||||
//go:embed fonu-agent-linux-arm64
|
||||
//go:embed havline-agent-linux-arm64
|
||||
var LinuxARM64 []byte
|
||||
|
||||
// BinaryForArch 返回对应架构的 agent 二进制;arch 取 uname -m 输出(x86_64/aarch64)。
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -42,7 +42,7 @@ func (s *Service) buildFRPSServerConfig(ctx context.Context, server Server) (str
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("# 由 Fonu 生成:与本服务端的 frpc 配置同源(bindPort/auth 与客户端一致)\n")
|
||||
b.WriteString("# 由 Havline 生成:与本服务端的 frpc 配置同源(bindPort/auth 与客户端一致)\n")
|
||||
b.WriteString("# 格式:frp v0.52.0+ 的 TOML 配置(INI 自 v0.52.0 起已被官方弃用)\n")
|
||||
b.WriteString("# 用法:保存为 VPS 上的 frps.toml,执行 ./frps -c frps.toml\n\n")
|
||||
// TOML 规范:顶层标量键必须写在任何 [table] 段之前,否则会被归入上一个表
|
||||
@@ -118,7 +118,7 @@ func (s *Service) buildFRPSServerConfig(ctx context.Context, server Server) (str
|
||||
b.WriteString("\nauth.method = \"token\"\n")
|
||||
b.WriteString(fmt.Sprintf("auth.token = %q\n", token))
|
||||
} else {
|
||||
b.WriteString("\n# 注意:frpc 侧为 Token 认证但尚未保存 Token;保存后在 Fonu 重新生成本文件以保持两端一致\n")
|
||||
b.WriteString("\n# 注意:frpc 侧为 Token 认证但尚未保存 Token;保存后在 Havline 重新生成本文件以保持两端一致\n")
|
||||
}
|
||||
case "none":
|
||||
b.WriteString("\n# frpc 侧为无认证(auth.method = \"none\"),frps 无需 [auth] 配置;\n")
|
||||
@@ -143,7 +143,7 @@ func (s *Service) buildFRPSServerConfig(ctx context.Context, server Server) (str
|
||||
b.WriteString(fmt.Sprintf("webServer.password = %q\n", password))
|
||||
}
|
||||
}
|
||||
b.WriteString("\n# 安全提示:webServer 暴露穿透流量与连接信息,建议用防火墙限制仅 Fonu 所在机器访问该端口\n")
|
||||
b.WriteString("\n# 安全提示:webServer 暴露穿透流量与连接信息,建议用防火墙限制仅 Havline 所在机器访问该端口\n")
|
||||
}
|
||||
|
||||
if hasSubdomain {
|
||||
|
||||
@@ -272,7 +272,7 @@ func (m *Manager) binaryPathLocked() (string, error) {
|
||||
}
|
||||
path, err := exec.LookPath(m.bin)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("未找到 frpc 二进制,请设置 FONU_FRPC_BIN")
|
||||
return "", fmt.Errorf("未找到 frpc 二进制,请设置 HAVLINE_FRPC_BIN")
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ type ServerOptions struct {
|
||||
TCPMuxHTTPConnectPort int `json:"tcpmux_http_connect_port,omitempty"`
|
||||
FrpsLogToFile bool `json:"frps_log_to_file"`
|
||||
FrpsLogMaxDays int `json:"frps_log_max_days,omitempty"`
|
||||
// frps 管理接口(webServer),仅用于 Fonu 拉取穿透流量等只读信息,不写入 frpc.toml
|
||||
// frps 管理接口(webServer),仅用于 Havline 拉取穿透流量等只读信息,不写入 frpc.toml
|
||||
DashboardAddr string `json:"dashboard_addr,omitempty"`
|
||||
DashboardPort int `json:"dashboard_port,omitempty"`
|
||||
DashboardUser string `json:"dashboard_user,omitempty"`
|
||||
|
||||
@@ -25,7 +25,7 @@ type RouteDriftItem struct {
|
||||
Actual string `json:"actual,omitempty"`
|
||||
}
|
||||
|
||||
// RouteDriftReport 部署漂移报告:Expected = 库里启用中的规则域名数,Deployed = VPS 上 fonu-*.conf 数,Loaded = 其中被加载的数
|
||||
// RouteDriftReport 部署漂移报告:Expected = 库里启用中的规则域名数,Deployed = VPS 上 havline-*.conf 数,Loaded = 其中被加载的数
|
||||
type RouteDriftReport struct {
|
||||
ServerID int64 `json:"server_id"`
|
||||
Expected int `json:"expected"`
|
||||
@@ -35,7 +35,7 @@ type RouteDriftReport struct {
|
||||
CheckedAt string `json:"checked_at"`
|
||||
}
|
||||
|
||||
// RouteDrift 比对「数据库里启用中的公网反代规则」与「VPS 上 fonu-*.conf 的实际状态」。
|
||||
// RouteDrift 比对「数据库里启用中的公网反代规则」与「VPS 上 havline-*.conf 的实际状态」。
|
||||
// 内容比对只做上流地址(proxy_pass):配置文件由 agent 渲染,应用侧不重复实现一套渲染器,
|
||||
// 因此不做全文 diff,避免两套渲染逻辑漂移带来的误报。
|
||||
func (s *Service) RouteDrift(ctx context.Context, serverID int64) (RouteDriftReport, error) {
|
||||
@@ -113,7 +113,7 @@ func (s *Service) RouteDrift(ctx context.Context, serverID int64) (RouteDriftRep
|
||||
if got != "" && got != normalizeUpstream(want) {
|
||||
report.Items = append(report.Items, RouteDriftItem{
|
||||
Domain: domain, Kind: RouteDriftContentMismatch,
|
||||
Detail: "VPS 上的上流地址与 Fonu 记录不一致(可能被手工修改过)",
|
||||
Detail: "VPS 上的上流地址与 Havline 记录不一致(可能被手工修改过)",
|
||||
Expected: want, Actual: got,
|
||||
})
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func (s *Service) RouteDrift(ctx context.Context, serverID int64) (RouteDriftRep
|
||||
}
|
||||
report.Items = append(report.Items, RouteDriftItem{
|
||||
Domain: domain, Kind: RouteDriftOrphan,
|
||||
Detail: "VPS 上存在该域名配置,但 Fonu 里没有对应规则(手工添加,或已删除规则的残留)",
|
||||
Detail: "VPS 上存在该域名配置,但 Havline 里没有对应规则(手工添加,或已删除规则的残留)",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ func fetchBytesWithProgress(ctx context.Context, url string, limit int64, progre
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Header.Set("User-Agent", "Fonu-FRP-Downloader")
|
||||
request.Header.Set("User-Agent", "Havline-FRP-Downloader")
|
||||
if strings.HasPrefix(url, "https://api.github.com/repos/fatedier/frp/releases/assets/") {
|
||||
request.Header.Set("Accept", "application/octet-stream")
|
||||
request.Header.Set("X-GitHub-Api-Version", "2022-11-28")
|
||||
|
||||
+11
-11
@@ -13,10 +13,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fonu/fonu/internal/certificate"
|
||||
"github.com/fonu/fonu/internal/config"
|
||||
"github.com/fonu/fonu/internal/fsutil"
|
||||
"github.com/fonu/fonu/internal/secret"
|
||||
"github.com/havline/havline/internal/certificate"
|
||||
"github.com/havline/havline/internal/config"
|
||||
"github.com/havline/havline/internal/fsutil"
|
||||
"github.com/havline/havline/internal/secret"
|
||||
)
|
||||
|
||||
// maskedToken 是敏感凭据的统一回显掩码:API 永不返回明文;保存时收到掩码一律视为保留旧值
|
||||
@@ -29,7 +29,7 @@ type Service struct {
|
||||
secretBox *secret.Box
|
||||
manager *Manager
|
||||
logger *slog.Logger
|
||||
certStore *certificate.Store // 用于 HTTPS 规则部署时把 Fonu 本地证书推送到公网 agent
|
||||
certStore *certificate.Store // 用于 HTTPS 规则部署时把 Havline 本地证书推送到公网 agent
|
||||
|
||||
managersMu sync.RWMutex
|
||||
managers map[int64]*Manager
|
||||
@@ -790,7 +790,7 @@ func (s *Service) AgentInstall(ctx context.Context, serverID int64) (Server, err
|
||||
_ = (AgentInstaller{SSH: installer}).RestorePreviousConfig(ctx)
|
||||
return Server{}, err
|
||||
}
|
||||
// 旧版升级流程可能残留 fonuagent-https.conf,这里通过隧道顺手关闭,避免 7443 继续暴露。
|
||||
// 旧版升级流程可能残留 havlineagent-https.conf,这里通过隧道顺手关闭,避免 7443 继续暴露。
|
||||
_ = client.DisableHTTPS(ctx)
|
||||
s.invalidateAgentClient(serverID)
|
||||
return updated, nil
|
||||
@@ -930,7 +930,7 @@ func (s *Service) StartAgentTunnels(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupLegacyAgentHTTPS 清理隧道模式下可能残留的 fonuagent-https.conf,避免 7443 继续暴露。
|
||||
// cleanupLegacyAgentHTTPS 清理隧道模式下可能残留的 havlineagent-https.conf,避免 7443 继续暴露。
|
||||
func (s *Service) cleanupLegacyAgentHTTPS(ctx context.Context, serverID int64) {
|
||||
client, ok := s.agentClient(ctx, serverID)
|
||||
if !ok {
|
||||
@@ -1655,7 +1655,7 @@ func (s *Service) RouteHealth(ctx context.Context, serverID int64) ([]RouteHealt
|
||||
} else {
|
||||
item.TunnelDetail = dashboardDiag
|
||||
}
|
||||
// ④ 内网服务探活:Fonu 与服务同在内网,直接 TCP 拨测
|
||||
// ④ 内网服务探活:Havline 与服务同在内网,直接 TCP 拨测
|
||||
conn, serr2 := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", proxy.LocalIP, proxy.LocalPort), 2*time.Second)
|
||||
if serr2 == nil {
|
||||
_ = conn.Close()
|
||||
@@ -1774,7 +1774,7 @@ func (s *Service) DeployAgentRoute(ctx context.Context, proxyID int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// pushAgentCertificate 从 Fonu 本地证书存储读取域名对应证书并推送到公网 agent;
|
||||
// pushAgentCertificate 从 Havline 本地证书存储读取域名对应证书并推送到公网 agent;
|
||||
// 支持精确域名与通配符(*.example.com)匹配。
|
||||
func (s *Service) pushAgentCertificate(ctx context.Context, client *AgentClient, domain string) error {
|
||||
if s.certStore == nil {
|
||||
@@ -1807,7 +1807,7 @@ func (s *Service) pushAgentCertificate(ctx context.Context, client *AgentClient,
|
||||
}
|
||||
return client.PushCertificate(ctx, domain, string(certPEM), string(keyPEM))
|
||||
}
|
||||
return fmt.Errorf("Fonu 本地没有域名 %s 的证书;请先在「证书」页申请,或改用 HTTP 类型规则", domain)
|
||||
return fmt.Errorf("Havline 本地没有域名 %s 的证书;请先在「证书」页申请,或改用 HTTP 类型规则", domain)
|
||||
}
|
||||
|
||||
// RemoveAgentRoute 手动移除单条穿透规则对应的公网 Nginx 反代。
|
||||
@@ -2356,7 +2356,7 @@ func (s *Service) saveOIDCSecret(ctx context.Context, serverID int64, in ServerI
|
||||
return s.store.SetOIDCSecret(ctx, serverID, value)
|
||||
}
|
||||
|
||||
// saveDashboardPassword 保存 frps 管理接口密码(与 frpc Token 无关,仅用于 Fonu 拉取流量信息)。
|
||||
// saveDashboardPassword 保存 frps 管理接口密码(与 frpc Token 无关,仅用于 Havline 拉取流量信息)。
|
||||
func (s *Service) saveDashboardPassword(ctx context.Context, serverID int64, in ServerInput) error {
|
||||
if in.ClearDashboardPassword {
|
||||
return s.store.SetDashboardPassword(ctx, serverID, "")
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"golang.org/x/crypto/ssh/knownhosts"
|
||||
)
|
||||
|
||||
// SSHInstaller 通过固定白名单命令在 VPS 上安装/升级 fonu-agent。
|
||||
// SSHInstaller 通过固定白名单命令在 VPS 上安装/升级 havline-agent。
|
||||
// agent 的日常管理仍走 AgentClient;SSH 只承担生命周期和失联自救。
|
||||
type SSHInstaller struct {
|
||||
Host string
|
||||
@@ -132,13 +132,13 @@ func (i SSHInstaller) Install(ctx context.Context, binary []byte, script []byte)
|
||||
return "", err
|
||||
}
|
||||
defer client.Close()
|
||||
if err := uploadBytes(client, "/tmp/fonu-agent", binary, 0o755); err != nil {
|
||||
if err := uploadBytes(client, "/tmp/havline-agent", binary, 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := uploadBytes(client, "/tmp/fonu-agent-install.sh", script, 0o700); err != nil {
|
||||
if err := uploadBytes(client, "/tmp/havline-agent-install.sh", script, 0o700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return runSSHSession(client, "sudo /tmp/fonu-agent-install.sh /tmp/fonu-agent && test -x /usr/local/bin/fonu-agent && /usr/local/bin/fonu-agent -version && rm -f /tmp/fonu-agent /tmp/fonu-agent-install.sh")
|
||||
return runSSHSession(client, "sudo /tmp/havline-agent-install.sh /tmp/havline-agent && test -x /usr/local/bin/havline-agent && /usr/local/bin/havline-agent -version && rm -f /tmp/havline-agent /tmp/havline-agent-install.sh")
|
||||
}
|
||||
|
||||
func uploadBytes(client *ssh.Client, path string, data []byte, mode uint32) error {
|
||||
|
||||
@@ -6,7 +6,7 @@ import "testing"
|
||||
// 单引号必须转义,否则路径里的引号能截断命令。
|
||||
func TestShellPathEscapesSingleQuotes(t *testing.T) {
|
||||
cases := []struct{ in, want string }{
|
||||
{"/tmp/fonu-agent", "'/tmp/fonu-agent'"},
|
||||
{"/tmp/havline-agent", "'/tmp/havline-agent'"},
|
||||
{"/tmp/it's here", `'/tmp/it'\''s here'`},
|
||||
{"", "''"},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user