chore: 初始提交 Fonu 1.0.2
- 公网反代:Nginx 状态卡片(版本 / 运行状态 / 配置校验 / 端口监听) - 部署设置第一、二梯队(HTTPS 开关、安全头、限流、连接数、仅中国大陆 IP) - 隧道检测兼容 frps v0.68 / v0.71(V2 与旧版端点) - 恢复被清空的 internal/frp/store.go、internal/api/ddns_handler.go、web/src/views/DdnsView.vue
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package frp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fonu/fonu/internal/config"
|
||||
"github.com/fonu/fonu/internal/nginx"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Connected bool `json:"connected"`
|
||||
Message string `json:"message"`
|
||||
LastError string `json:"last_error,omitempty"`
|
||||
ServerEndpoint string `json:"server_endpoint,omitempty"`
|
||||
ConnectedAt string `json:"connected_at,omitempty"`
|
||||
UptimeSeconds int64 `json:"uptime_seconds"`
|
||||
ClientVersion string `json:"client_version,omitempty"`
|
||||
HTTPGatewayEnabled bool `json:"http_gateway_enabled"`
|
||||
HTTPSGatewayEnabled bool `json:"https_gateway_enabled"`
|
||||
SyncedDomainCount int `json:"synced_domain_count"`
|
||||
}
|
||||
|
||||
func BuildStatus(cfg config.Config, frpCfg Config, enabled bool, lastError, startedAt, clientVersion string) Status {
|
||||
st := Status{
|
||||
Enabled: enabled,
|
||||
Connected: false,
|
||||
Message: "未启用",
|
||||
LastError: strings.TrimSpace(lastError),
|
||||
ClientVersion: clientVersion,
|
||||
SyncedDomainCount: len(frpCfg.CustomDomains),
|
||||
}
|
||||
if enabled {
|
||||
addr := strings.TrimSpace(frpCfg.ServerAddr)
|
||||
if addr != "" {
|
||||
port := frpCfg.ServerPort
|
||||
if port <= 0 {
|
||||
port = 7000
|
||||
}
|
||||
st.ServerEndpoint = addr + ":" + strconv.Itoa(port)
|
||||
}
|
||||
st.HTTPGatewayEnabled = len(frpCfg.CustomDomains) > 0
|
||||
st.HTTPSGatewayEnabled = len(frpCfg.CustomDomains) > 0
|
||||
}
|
||||
if !enabled {
|
||||
return st
|
||||
}
|
||||
running, err := isProcessRunning(cfg.FrpPIDFile)
|
||||
if err != nil || !running {
|
||||
st.Message = "未连接"
|
||||
if st.LastError != "" {
|
||||
st.Message = st.LastError
|
||||
} else if hint := readLogHint(cfg.FrpLogPath()); hint != "" {
|
||||
st.Message = hint
|
||||
}
|
||||
return st
|
||||
}
|
||||
ok, logHint := logIndicatesConnected(cfg.FrpLogPath())
|
||||
if ok {
|
||||
st.Connected = true
|
||||
st.Message = "已连接"
|
||||
st.LastError = ""
|
||||
applyConnectedTiming(&st, startedAt)
|
||||
return st
|
||||
}
|
||||
st.Message = "连接中"
|
||||
if logHint != "" {
|
||||
st.Message = logHint
|
||||
}
|
||||
if st.LastError != "" {
|
||||
st.Message = st.LastError
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func applyConnectedTiming(st *Status, startedAt string) {
|
||||
st.ConnectedAt = strings.TrimSpace(startedAt)
|
||||
if st.ConnectedAt == "" {
|
||||
return
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339, st.ConnectedAt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
st.UptimeSeconds = int64(time.Since(t).Seconds())
|
||||
if st.UptimeSeconds < 0 {
|
||||
st.UptimeSeconds = 0
|
||||
}
|
||||
}
|
||||
|
||||
func isProcessRunning(pidFile string) (bool, error) {
|
||||
pid, err := nginx.ReadPID(pidFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return nginx.IsPIDAlive(pid), nil
|
||||
}
|
||||
|
||||
func readLogHint(path string) string {
|
||||
lines := tailLines(path, 40)
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
line := strings.ToLower(lines[i])
|
||||
if strings.Contains(line, "login to server success") {
|
||||
return ""
|
||||
}
|
||||
if strings.Contains(line, "connect to server error") ||
|
||||
strings.Contains(line, "authorization failed") ||
|
||||
strings.Contains(line, "token in login doesn't match") {
|
||||
return sanitizeLogLine(lines[i])
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func logIndicatesConnected(path string) (bool, string) {
|
||||
lines := tailLines(path, 80)
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
line := strings.ToLower(lines[i])
|
||||
if strings.Contains(line, "login to server success") {
|
||||
return true, ""
|
||||
}
|
||||
if strings.Contains(line, "connect to server error") ||
|
||||
strings.Contains(line, "authorization failed") {
|
||||
return false, sanitizeLogLine(lines[i])
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func tailLines(path string, max int) []string {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
lines := strings.Split(string(data), "\n")
|
||||
if len(lines) > max {
|
||||
lines = lines[len(lines)-max:]
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func sanitizeLogLine(line string) string {
|
||||
line = strings.TrimSpace(line)
|
||||
if idx := strings.Index(line, "]"); idx >= 0 && idx+1 < len(line) {
|
||||
line = strings.TrimSpace(line[idx+1:])
|
||||
}
|
||||
if len(line) > 200 {
|
||||
line = line[:200] + "…"
|
||||
}
|
||||
return line
|
||||
}
|
||||
Reference in New Issue
Block a user