Fix busy binary reinstall and add per-node log viewing.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,10 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -18,6 +20,7 @@ type Agent struct {
|
||||
secret string
|
||||
version string
|
||||
dataDir string
|
||||
logPath string
|
||||
startedAt time.Time
|
||||
mu sync.RWMutex
|
||||
config map[string]any
|
||||
@@ -34,11 +37,19 @@ func main() {
|
||||
dataDir := env("DATA_DIR", "/var/lib/vpn-node")
|
||||
_ = os.MkdirAll(dataDir, 0o755)
|
||||
|
||||
logPath := filepath.Join(dataDir, "agent.log")
|
||||
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||
if err == nil {
|
||||
log.SetOutput(io.MultiWriter(os.Stderr, logFile))
|
||||
defer logFile.Close()
|
||||
}
|
||||
|
||||
a := &Agent{
|
||||
name: name,
|
||||
secret: secret,
|
||||
version: version,
|
||||
dataDir: dataDir,
|
||||
logPath: logPath,
|
||||
startedAt: time.Now().UTC(),
|
||||
config: map[string]any{},
|
||||
}
|
||||
@@ -48,6 +59,7 @@ func main() {
|
||||
mux.HandleFunc("/health", a.auth(a.health))
|
||||
mux.HandleFunc("/api/v1/config", a.auth(a.configHandler))
|
||||
mux.HandleFunc("/api/v1/status", a.auth(a.status))
|
||||
mux.HandleFunc("/api/v1/logs", a.auth(a.logs))
|
||||
|
||||
addr := ":" + port
|
||||
log.Printf("vpn-node %s listening on %s name=%s", version, addr, name)
|
||||
@@ -130,6 +142,34 @@ func (a *Agent) status(w http.ResponseWriter, _ *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Agent) logs(w http.ResponseWriter, r *http.Request) {
|
||||
lines := 300
|
||||
if v := r.URL.Query().Get("lines"); v != "" {
|
||||
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 5000 {
|
||||
lines = n
|
||||
}
|
||||
}
|
||||
content, err := tailFile(a.logPath, lines)
|
||||
if err != nil {
|
||||
writeJSON(w, map[string]any{"ok": true, "logs": "(no agent.log yet)"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"ok": true, "logs": content})
|
||||
}
|
||||
|
||||
func tailFile(path string, maxLines int) (string, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
text := string(b)
|
||||
parts := strings.Split(text, "\n")
|
||||
if len(parts) > maxLines {
|
||||
parts = parts[len(parts)-maxLines:]
|
||||
}
|
||||
return strings.Join(parts, "\n"), nil
|
||||
}
|
||||
|
||||
func (a *Agent) configHandler(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
@@ -146,6 +186,7 @@ func (a *Agent) configHandler(w http.ResponseWriter, r *http.Request) {
|
||||
a.config = cfg
|
||||
a.mu.Unlock()
|
||||
_ = a.saveConfig(filepath.Join(a.dataDir, "config.json"))
|
||||
log.Printf("config updated: installed=%v enabled=%v", stringList(cfg, "protocols_installed"), stringList(cfg, "protocols_enabled"))
|
||||
writeJSON(w, map[string]any{"ok": true})
|
||||
default:
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
|
||||
Reference in New Issue
Block a user