Fix busy binary reinstall and add per-node log viewing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-07-29 04:31:09 +03:00
co-authored by Cursor
parent 2d8b1d4438
commit 1fb87f5f6c
10 changed files with 222 additions and 19 deletions
+41
View File
@@ -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)