feat(watchdog): add watchdog service with TCP client implementation
- Implement TCP client for watchdog service communication - Add watchdog configuration in config.json - Update main.go to initialize and connect to watchdog - Add watchdog related functions (connect, command handling) - Update README.md with new watchdog feature - Improve route setup logging in router.go
This commit is contained in:
@@ -72,6 +72,8 @@ For detailed API documentation, please see [docs/api.md](docs/api.md)
|
|||||||
- [x] Fix backend can still start frpc instance when it is already running
|
- [x] Fix backend can still start frpc instance when it is already running
|
||||||
- [ ] Develop an agent software to handle windows service management
|
- [ ] Develop an agent software to handle windows service management
|
||||||
- [ ] Refactor all log output level to be more clear
|
- [ ] Refactor all log output level to be more clear
|
||||||
|
- [ ] Add global websocket endpoint for posting notifications
|
||||||
|
- [ ] Add frpc instance watchdog
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
11
config.go
11
config.go
@@ -17,6 +17,9 @@ type Config struct {
|
|||||||
FrpcPath string `json:"frpcPath"`
|
FrpcPath string `json:"frpcPath"`
|
||||||
InstancePath string `json:"instancePath"`
|
InstancePath string `json:"instancePath"`
|
||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
|
Watchdog struct {
|
||||||
|
Port int `json:"port"`
|
||||||
|
} `json:"watchdog"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstanceInfo struct {
|
type InstanceInfo struct {
|
||||||
@@ -75,13 +78,21 @@ func LoadConfig(configPath string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.FrpcPath == "" {
|
if config.FrpcPath == "" {
|
||||||
|
if GetInitSystem() == "windows" {
|
||||||
|
config.FrpcPath = "frp_client/frpc.exe"
|
||||||
|
} else {
|
||||||
config.FrpcPath = "/usr/bin/frpc"
|
config.FrpcPath = "/usr/bin/frpc"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if config.InstancePath == "" {
|
if config.InstancePath == "" {
|
||||||
config.InstancePath = "./configs"
|
config.InstancePath = "./configs"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Watchdog.Port == 0 {
|
||||||
|
config.Watchdog.Port = 12380
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(config.InstancePath, 0755); err != nil {
|
if err := os.MkdirAll(config.InstancePath, 0755); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create config directory: %w", err)
|
return nil, fmt.Errorf("failed to create config directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
12
main.go
12
main.go
@@ -8,6 +8,7 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"super-frpc/frpLogger"
|
"super-frpc/frpLogger"
|
||||||
"super-frpc/postLog"
|
"super-frpc/postLog"
|
||||||
|
"super-frpc/watchdog"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -90,6 +91,17 @@ func main() {
|
|||||||
|
|
||||||
setupRoutes()
|
setupRoutes()
|
||||||
|
|
||||||
|
err = watchdog.Init()
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("Unable to initialize Watchdog: %s", err))
|
||||||
|
} else {
|
||||||
|
if !watchdog.Connect("127.0.0.1", config.Watchdog.Port) {
|
||||||
|
postLog.Error(fmt.Sprintf("Failed to connect to Watchdog at %s:%d", "127.0.0.1", config.Watchdog.Port))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%s", config.ListenAddr, config.ListenPort)
|
addr := fmt.Sprintf("%s:%s", config.ListenAddr, config.ListenPort)
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func setupRoutes() {
|
func setupRoutes() {
|
||||||
postLog.Info("Setting up routes...")
|
|
||||||
http.HandleFunc("/system/getStatus", GetStatusHandler)
|
http.HandleFunc("/system/getStatus", GetStatusHandler)
|
||||||
http.HandleFunc("/system/getSoftwareInfo", GetSoftwareInfoHandler)
|
http.HandleFunc("/system/getSoftwareInfo", GetSoftwareInfoHandler)
|
||||||
systemLogHandler := postLog.NewLogSocketHandler(postLog.GetLogBroadcaster())
|
systemLogHandler := postLog.NewLogSocketHandler(postLog.GetLogBroadcaster())
|
||||||
@@ -44,6 +43,8 @@ func setupRoutes() {
|
|||||||
|
|
||||||
http.HandleFunc("/", NotFoundHandler)
|
http.HandleFunc("/", NotFoundHandler)
|
||||||
|
|
||||||
|
postLog.Info("Routes setup successfully")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
|
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
33
watchdog/command.go
Normal file
33
watchdog/command.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package watchdog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddInstance(serviceName string) bool {
|
||||||
|
if !IsConnected() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
message := fmt.Sprintf("[addInstance] <serviceName>%s</serviceName>", serviceName)
|
||||||
|
response, err := sendMsg(message, 3)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return response == "success"
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveInstance(serviceName string) bool {
|
||||||
|
if !IsConnected() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
message := fmt.Sprintf("[removeInstance] <serviceName>%s</serviceName>", serviceName)
|
||||||
|
response, err := sendMsg(message, 3)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return response == "success"
|
||||||
|
}
|
||||||
47
watchdog/connect.go
Normal file
47
watchdog/connect.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package watchdog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Connect(ipaddr string, port int) bool {
|
||||||
|
if IsConnected() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Init(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tcpConnect(ipaddr, port); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := sendMsg("watchdogAgentConnectionTest", 3)
|
||||||
|
if err != nil {
|
||||||
|
Destroy()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if response != "success" {
|
||||||
|
Destroy()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Disconnect() bool {
|
||||||
|
if !IsConnected() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Destroy()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
164
watchdog/tcpClient.go
Normal file
164
watchdog/tcpClient.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package watchdog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"super-frpc/postLog"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tcpConn net.Conn
|
||||||
|
tcpConnMutex sync.Mutex
|
||||||
|
isConnected bool
|
||||||
|
recvChan chan string
|
||||||
|
stopRecvChan chan struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func notifyMessage(message string) {
|
||||||
|
postLog.Info(fmt.Sprintf("%s", message))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init() error {
|
||||||
|
tcpConnMutex.Lock()
|
||||||
|
defer tcpConnMutex.Unlock()
|
||||||
|
|
||||||
|
if tcpConn != nil {
|
||||||
|
return fmt.Errorf("TCP client already initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
recvChan = make(chan string, 100)
|
||||||
|
stopRecvChan = make(chan struct{})
|
||||||
|
isConnected = false
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcpConnect(ipaddr string, port int) error {
|
||||||
|
tcpConnMutex.Lock()
|
||||||
|
defer tcpConnMutex.Unlock()
|
||||||
|
|
||||||
|
if tcpConn != nil {
|
||||||
|
return fmt.Errorf("already connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
address := fmt.Sprintf("%s:%d", ipaddr, port)
|
||||||
|
conn, err := net.DialTimeout("tcp", address, 3*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to %s: %v", address, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpConn = conn
|
||||||
|
isConnected = true
|
||||||
|
|
||||||
|
go recvMsg()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendMsg(message string, target int) (string, error) {
|
||||||
|
tcpConnMutex.Lock()
|
||||||
|
|
||||||
|
if tcpConn == nil {
|
||||||
|
tcpConnMutex.Unlock()
|
||||||
|
return "", fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := tcpConn.Write([]byte(message + "\n"))
|
||||||
|
if err != nil {
|
||||||
|
tcpConnMutex.Unlock()
|
||||||
|
return "", fmt.Errorf("failed to send message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpConnMutex.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case response := <-recvChan:
|
||||||
|
return response, nil
|
||||||
|
case <-time.After(time.Duration(target) * time.Second):
|
||||||
|
return "", fmt.Errorf("timeout waiting for response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvMsg() {
|
||||||
|
reader := bufio.NewReader(tcpConn)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stopRecvChan:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
tcpConn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||||
|
line, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tcpConnMutex.Lock()
|
||||||
|
if tcpConn != nil {
|
||||||
|
tcpConn.Close()
|
||||||
|
tcpConn = nil
|
||||||
|
isConnected = false
|
||||||
|
}
|
||||||
|
tcpConnMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
line = line[:len(line)-1]
|
||||||
|
if len(line) > 0 {
|
||||||
|
select {
|
||||||
|
case recvChan <- line:
|
||||||
|
default:
|
||||||
|
select {
|
||||||
|
case <-recvChan:
|
||||||
|
recvChan <- line
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(line) > 0 && line != "success" && !isResponseMessage(line) {
|
||||||
|
notifyMessage(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isResponseMessage(msg string) bool {
|
||||||
|
return msg == "success" || msg == "failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func Destroy() error {
|
||||||
|
tcpConnMutex.Lock()
|
||||||
|
defer tcpConnMutex.Unlock()
|
||||||
|
|
||||||
|
if stopRecvChan != nil {
|
||||||
|
close(stopRecvChan)
|
||||||
|
stopRecvChan = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if tcpConn != nil {
|
||||||
|
err := tcpConn.Close()
|
||||||
|
tcpConn = nil
|
||||||
|
isConnected = false
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to close connection: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if recvChan != nil {
|
||||||
|
close(recvChan)
|
||||||
|
recvChan = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsConnected() bool {
|
||||||
|
tcpConnMutex.Lock()
|
||||||
|
defer tcpConnMutex.Unlock()
|
||||||
|
return isConnected && tcpConn != nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user