Files
-Fonu/internal/frp-1.0.2/status.go
T

158 lines
3.9 KiB
Go
Raw Normal View History

2026-09-23 09:40:59 +08:00
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
}