diff --git a/README.md b/README.md
index e6c32ad..a651a10 100644
--- a/README.md
+++ b/README.md
@@ -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
- [ ] Develop an agent software to handle windows service management
- [ ] Refactor all log output level to be more clear
+- [ ] Add global websocket endpoint for posting notifications
+- [ ] Add frpc instance watchdog
## License
diff --git a/config.go b/config.go
index 58ad055..0e96f40 100644
--- a/config.go
+++ b/config.go
@@ -17,6 +17,9 @@ type Config struct {
FrpcPath string `json:"frpcPath"`
InstancePath string `json:"instancePath"`
Debug bool `json:"debug"`
+ Watchdog struct {
+ Port int `json:"port"`
+ } `json:"watchdog"`
}
type InstanceInfo struct {
@@ -75,13 +78,21 @@ func LoadConfig(configPath string) (*Config, error) {
}
if config.FrpcPath == "" {
- config.FrpcPath = "/usr/bin/frpc"
+ if GetInitSystem() == "windows" {
+ config.FrpcPath = "frp_client/frpc.exe"
+ } else {
+ config.FrpcPath = "/usr/bin/frpc"
+ }
}
if config.InstancePath == "" {
config.InstancePath = "./configs"
}
+ if config.Watchdog.Port == 0 {
+ config.Watchdog.Port = 12380
+ }
+
if err := os.MkdirAll(config.InstancePath, 0755); err != nil {
return nil, fmt.Errorf("failed to create config directory: %w", err)
}
diff --git a/main.go b/main.go
index 30b8474..bd29aba 100644
--- a/main.go
+++ b/main.go
@@ -8,6 +8,7 @@ import (
"os/signal"
"super-frpc/frpLogger"
"super-frpc/postLog"
+ "super-frpc/watchdog"
"syscall"
"time"
)
@@ -90,6 +91,17 @@ func main() {
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)
server := &http.Server{
Addr: addr,
diff --git a/router.go b/router.go
index fc055d7..558e5da 100644
--- a/router.go
+++ b/router.go
@@ -8,7 +8,6 @@ import (
)
func setupRoutes() {
- postLog.Info("Setting up routes...")
http.HandleFunc("/system/getStatus", GetStatusHandler)
http.HandleFunc("/system/getSoftwareInfo", GetSoftwareInfoHandler)
systemLogHandler := postLog.NewLogSocketHandler(postLog.GetLogBroadcaster())
@@ -44,6 +43,8 @@ func setupRoutes() {
http.HandleFunc("/", NotFoundHandler)
+ postLog.Info("Routes setup successfully")
+
}
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
diff --git a/watchdog/command.go b/watchdog/command.go
new file mode 100644
index 0000000..20f4a35
--- /dev/null
+++ b/watchdog/command.go
@@ -0,0 +1,33 @@
+package watchdog
+
+import (
+ "fmt"
+)
+
+func AddInstance(serviceName string) bool {
+ if !IsConnected() {
+ return false
+ }
+
+ message := fmt.Sprintf("[addInstance] %s", 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] %s", serviceName)
+ response, err := sendMsg(message, 3)
+ if err != nil {
+ return false
+ }
+
+ return response == "success"
+}
diff --git a/watchdog/connect.go b/watchdog/connect.go
new file mode 100644
index 0000000..b6d7f82
--- /dev/null
+++ b/watchdog/connect.go
@@ -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
+}
diff --git a/watchdog/tcpClient.go b/watchdog/tcpClient.go
new file mode 100644
index 0000000..811a7b2
--- /dev/null
+++ b/watchdog/tcpClient.go
@@ -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
+}