feat: add initial project structure with logging and watchdog service
Implement basic watchdog service for Linux systemd with: - Configuration loading - Logging system with database support - WebSocket log broadcasting - TCP server for agent communication - Project setup with Go modules
This commit is contained in:
49
.gitignore
vendored
Normal file
49
.gitignore
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# ---> VisualStudioCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
# ---> Go
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# env file
|
||||||
|
.env
|
||||||
|
|
||||||
|
/frp_client
|
||||||
|
/configs
|
||||||
|
agent.md
|
||||||
|
super-frpc
|
||||||
|
super-frpc.exe
|
||||||
|
*.db
|
||||||
|
database.db
|
||||||
|
logs.html
|
||||||
29
config.go
Normal file
29
config.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Watchdog_Linux-systemd/postLog"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Config struct {
|
||||||
|
DebugMode bool `json:"debugMode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig() {
|
||||||
|
configFile, err := os.Open("config.json")
|
||||||
|
if err != nil {
|
||||||
|
postLog.Fatal(fmt.Sprintf("Failed to open config file: %v, err: %v", configFile, err))
|
||||||
|
}
|
||||||
|
defer configFile.Close()
|
||||||
|
decoder := json.NewDecoder(configFile)
|
||||||
|
if err := decoder.Decode(&Config); err != nil {
|
||||||
|
postLog.Fatal(fmt.Sprintf("Failed to decode config file: %v, err: %v", configFile, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if Config.DebugMode {
|
||||||
|
isDebug = true
|
||||||
|
}
|
||||||
|
}
|
||||||
3
config.json
Normal file
3
config.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"debugMode": true
|
||||||
|
}
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module Watchdog_Linux-systemd
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require github.com/gorilla/websocket v1.5.3
|
||||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
78
main.go
Normal file
78
main.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Watchdog_Linux-systemd/postLog"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
listenAddr = "127.0.0.1"
|
||||||
|
listenPort = "10080"
|
||||||
|
Type = "tcp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SoftwareInfo struct {
|
||||||
|
Name string
|
||||||
|
Version string
|
||||||
|
Developer string
|
||||||
|
BuildVer int16
|
||||||
|
Description string
|
||||||
|
BuildType string
|
||||||
|
}
|
||||||
|
|
||||||
|
var softwareInfo SoftwareInfo = SoftwareInfo{
|
||||||
|
Name: "Watchdog_Linux-systemd",
|
||||||
|
Version: "0.0.1",
|
||||||
|
Developer: "Super-frpc",
|
||||||
|
BuildVer: 1,
|
||||||
|
Description: "Super-frpc Watchdog service for Linux systemd",
|
||||||
|
BuildType: "Debug",
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDebug bool
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
loadConfig()
|
||||||
|
if isDebug == true {
|
||||||
|
postLog.SetDebugMode(true)
|
||||||
|
}
|
||||||
|
postLog.Info(fmt.Sprintf("%s %s (Build %d.%s) by %s", softwareInfo.Name, softwareInfo.Version, softwareInfo.BuildVer, softwareInfo.BuildType, softwareInfo.Developer))
|
||||||
|
listen, err := net.Listen(Type, fmt.Sprintf("%s:%s", listenAddr, listenPort))
|
||||||
|
if err != nil {
|
||||||
|
postLog.Fatal(fmt.Sprintf("Failed to listen: %v, err: %v, %v", listenAddr, listenPort, err))
|
||||||
|
}
|
||||||
|
defer listen.Close()
|
||||||
|
postLog.Info(fmt.Sprintf("Server is running on %s:%s", listenAddr, listenPort))
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listen.Accept()
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("Failed to accept: %v, err: %v", conn, err))
|
||||||
|
}
|
||||||
|
go handleRequest(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRequest(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
buffer := make([]byte, 1024)
|
||||||
|
n, err := conn.Read(buffer)
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("Failed to read: %v, err: %v", conn, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
recvMsg := string(buffer[:n])
|
||||||
|
postLog.Debug(fmt.Sprintf("Received: %v", recvMsg))
|
||||||
|
responseMsg := ""
|
||||||
|
if recvMsg == "watchdogAgentConnectionTest" {
|
||||||
|
responseMsg = "success"
|
||||||
|
} else {
|
||||||
|
responseMsg = "error: unknown message"
|
||||||
|
}
|
||||||
|
if _, err := conn.Write([]byte(responseMsg)); err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("Failed to write: %v, err: %v", conn, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
60
postLog/database.go
Normal file
60
postLog/database.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package postLog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logsDB *sql.DB
|
||||||
|
tableName string
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitLogsDatabase(dbPath string) error {
|
||||||
|
var err error
|
||||||
|
logsDB, err = sql.Open("sqlite", dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open logs database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = logsDB.Ping(); err != nil {
|
||||||
|
return fmt.Errorf("failed to ping logs database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := time.Now().Format("20060102_150405")
|
||||||
|
tableName = fmt.Sprintf("logs_%s", timestamp)
|
||||||
|
|
||||||
|
createTableSQL := fmt.Sprintf(`
|
||||||
|
CREATE TABLE IF NOT EXISTS %s (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
level INTEGER NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);`, tableName)
|
||||||
|
|
||||||
|
_, err = logsDB.Exec(createTableSQL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create logs table %s: %w", tableName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertLogToDB(db *sql.DB, level int, content string, timestamp string) {
|
||||||
|
if db != nil {
|
||||||
|
_, err := db.Exec(fmt.Sprintf(`INSERT INTO %s (level, content, timestamp) VALUES (%d, '%s', '%s')`, tableName,
|
||||||
|
level, content, timestamp))
|
||||||
|
// fmt.Fprintf(os.Stderr, `INSERT INTO %s (level, content, timestamp) VALUES (%d, '%s', '%s')`, tableName, level, content, timestamp)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to write log to database: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLogsDatabase(db *sql.DB) {
|
||||||
|
loggerMutex.Lock()
|
||||||
|
defer loggerMutex.Unlock()
|
||||||
|
logsDB = db
|
||||||
|
}
|
||||||
96
postLog/logBroadcaster.go
Normal file
96
postLog/logBroadcaster.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package postLog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogMessage struct {
|
||||||
|
Level int `json:"level"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
ID string
|
||||||
|
Messages chan LogMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogBroadcaster struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
clients map[string]chan LogMessage
|
||||||
|
history []LogMessage
|
||||||
|
historyM sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var broadcaster *LogBroadcaster
|
||||||
|
|
||||||
|
func InitLogBroadcaster() {
|
||||||
|
broadcaster = &LogBroadcaster{
|
||||||
|
clients: make(map[string]chan LogMessage),
|
||||||
|
history: make([]LogMessage, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLogBroadcaster() *LogBroadcaster {
|
||||||
|
return broadcaster
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LogBroadcaster) AddClient(id string) chan LogMessage {
|
||||||
|
lb.mu.Lock()
|
||||||
|
defer lb.mu.Unlock()
|
||||||
|
|
||||||
|
ch := make(chan LogMessage, 100)
|
||||||
|
lb.clients[id] = ch
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LogBroadcaster) RemoveClient(id string) {
|
||||||
|
lb.mu.Lock()
|
||||||
|
defer lb.mu.Unlock()
|
||||||
|
|
||||||
|
if ch, exists := lb.clients[id]; exists {
|
||||||
|
close(ch)
|
||||||
|
delete(lb.clients, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LogBroadcaster) Broadcast(msg LogMessage) {
|
||||||
|
lb.mu.RLock()
|
||||||
|
defer lb.mu.RUnlock()
|
||||||
|
|
||||||
|
for _, ch := range lb.clients {
|
||||||
|
select {
|
||||||
|
case ch <- msg:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LogBroadcaster) GetHistory() []LogMessage {
|
||||||
|
lb.historyM.RLock()
|
||||||
|
defer lb.historyM.RUnlock()
|
||||||
|
|
||||||
|
result := make([]LogMessage, len(lb.history))
|
||||||
|
copy(result, lb.history)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LogBroadcaster) AddToHistory(msg LogMessage) {
|
||||||
|
lb.historyM.Lock()
|
||||||
|
defer lb.historyM.Unlock()
|
||||||
|
|
||||||
|
lb.history = append(lb.history, msg)
|
||||||
|
if len(lb.history) > 100 {
|
||||||
|
lb.history = lb.history[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LogBroadcaster) SendHistory(clientCh chan LogMessage) {
|
||||||
|
history := lb.GetHistory()
|
||||||
|
for _, msg := range history {
|
||||||
|
select {
|
||||||
|
case clientCh <- msg:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
postLog/logSocketHandler.go
Normal file
66
postLog/logSocketHandler.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package postLog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogSocketHandler struct {
|
||||||
|
broadcaster *LogBroadcaster
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogSocketHandler(b *LogBroadcaster) *LogSocketHandler {
|
||||||
|
return &LogSocketHandler{
|
||||||
|
broadcaster: b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LogSocketHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to upgrade connection: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
clientID := conn.RemoteAddr().String() + "-" + time.Now().Format("20060102150405")
|
||||||
|
clientCh := h.broadcaster.AddClient(clientID)
|
||||||
|
defer h.broadcaster.RemoveClient(clientID)
|
||||||
|
|
||||||
|
h.broadcaster.SendHistory(clientCh)
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
for {
|
||||||
|
msg := <-clientCh
|
||||||
|
data, err := json.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to marshal log message: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, _, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
postLog/postLog.go
Normal file
95
postLog/postLog.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package postLog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loggerMutex sync.Mutex
|
||||||
|
isDebug bool
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEBUG = iota
|
||||||
|
INFO
|
||||||
|
WARNING
|
||||||
|
ERROR
|
||||||
|
FATAL
|
||||||
|
)
|
||||||
|
|
||||||
|
var levelNames = []string{"DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}
|
||||||
|
var levelColors = []int{34, 27, 220, 196, 124}
|
||||||
|
|
||||||
|
func colorOut_256(s string, foreColor int) string {
|
||||||
|
return fmt.Sprintf("\033[38;5;%dm%s\033[m", foreColor, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSystemTime() string {
|
||||||
|
now := time.Now()
|
||||||
|
return now.Format("2006-01-02 15:04:05.000")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetDebugMode(debug bool) {
|
||||||
|
loggerMutex.Lock()
|
||||||
|
defer loggerMutex.Unlock()
|
||||||
|
isDebug = debug
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostLog(message string, level int) {
|
||||||
|
timeNow := getSystemTime()
|
||||||
|
|
||||||
|
idx := level
|
||||||
|
if idx < 0 || idx >= len(levelNames) {
|
||||||
|
idx = INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
loggerMutex.Lock()
|
||||||
|
|
||||||
|
if idx == DEBUG && !isDebug {
|
||||||
|
loggerMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
levelDisplay := colorOut_256(levelNames[idx], levelColors[idx])
|
||||||
|
db := logsDB
|
||||||
|
|
||||||
|
loggerMutex.Unlock()
|
||||||
|
|
||||||
|
fmt.Printf("[%s - %s] %s\n", timeNow, levelDisplay, message)
|
||||||
|
insertLogToDB(db, level, message, timeNow)
|
||||||
|
|
||||||
|
if broadcaster != nil {
|
||||||
|
broadcaster.AddToHistory(LogMessage{
|
||||||
|
Level: level,
|
||||||
|
Content: message,
|
||||||
|
Timestamp: timeNow,
|
||||||
|
})
|
||||||
|
broadcaster.Broadcast(LogMessage{
|
||||||
|
Level: level,
|
||||||
|
Content: message,
|
||||||
|
Timestamp: timeNow,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(message string) {
|
||||||
|
PostLog(message, DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(message string) {
|
||||||
|
PostLog(message, INFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warning(message string) {
|
||||||
|
PostLog(message, WARNING)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(message string) {
|
||||||
|
PostLog(message, ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatal(message string) {
|
||||||
|
PostLog(message, FATAL)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user