158 lines
3.9 KiB
Go
158 lines
3.9 KiB
Go
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
|
||
|
|
}
|