refactor: replace REST API with Unix socket IPC + CLI subcommands
- Remove insecure HTTP API endpoints (positions, close, close-all, pnl) - Add Unix socket IPC at /tmp/exchange-monitor.sock - Add CLI subcommands: status, close-all, close <COIN>, stop, start - Bind dashboard HTTP to 127.0.0.1:8888 (localhost only)
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const sockPath = "/tmp/exchange-monitor.sock"
|
||||
|
||||
// IPCCommand is sent from CLI client to daemon.
|
||||
type IPCCommand struct {
|
||||
Action string `json:"action"` // status, close-all, close, stop, start
|
||||
Coin string `json:"coin,omitempty"`
|
||||
}
|
||||
|
||||
// IPCResponse is sent back from daemon to CLI client.
|
||||
type IPCResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// startIPCServer starts the Unix socket listener for CLI commands.
|
||||
func (t *Trader) startIPCServer() {
|
||||
os.Remove(sockPath) // clean up stale socket
|
||||
|
||||
ln, err := net.Listen("unix", sockPath)
|
||||
if err != nil {
|
||||
log.Printf("[IPC] Failed to create socket: %v", err)
|
||||
return
|
||||
}
|
||||
log.Printf("[IPC] Listening on %s", sockPath)
|
||||
|
||||
go func() {
|
||||
defer ln.Close()
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go t.handleIPC(conn)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *Trader) handleIPC(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
|
||||
var cmd IPCCommand
|
||||
if err := json.NewDecoder(conn).Decode(&cmd); err != nil {
|
||||
json.NewEncoder(conn).Encode(IPCResponse{Success: false, Error: "invalid command: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var resp IPCResponse
|
||||
switch cmd.Action {
|
||||
case "status":
|
||||
positions := t.ReadSnapshot()
|
||||
c, d, f, tot := t.GetClosedStats()
|
||||
resp = IPCResponse{Success: true, Data: map[string]interface{}{
|
||||
"positions": positions,
|
||||
"converged": c, "diverged": d, "flat": f, "total": tot,
|
||||
}}
|
||||
case "close-all":
|
||||
count := t.CloseAllPositions()
|
||||
resp = IPCResponse{Success: true, Data: map[string]interface{}{
|
||||
"closed": count, "message": fmt.Sprintf("Closed %d positions", count),
|
||||
}}
|
||||
case "close":
|
||||
if cmd.Coin == "" {
|
||||
resp = IPCResponse{Success: false, Error: "missing coin name"}
|
||||
} else if err := t.ClosePosition(cmd.Coin); err != nil {
|
||||
resp = IPCResponse{Success: false, Error: err.Error()}
|
||||
} else {
|
||||
resp = IPCResponse{Success: true, Data: map[string]string{"closed": cmd.Coin}}
|
||||
}
|
||||
case "stop":
|
||||
t.Stop()
|
||||
resp = IPCResponse{Success: true, Data: map[string]string{"status": "stopped"}}
|
||||
case "start":
|
||||
t.Start()
|
||||
resp = IPCResponse{Success: true, Data: map[string]string{"status": "started"}}
|
||||
default:
|
||||
resp = IPCResponse{Success: false, Error: "unknown action: " + cmd.Action}
|
||||
}
|
||||
json.NewEncoder(conn).Encode(resp)
|
||||
}
|
||||
|
||||
// runIPCClient sends a command to the running daemon and prints the response.
|
||||
func runIPCClient(action, coin string) {
|
||||
conn, err := net.DialTimeout("unix", sockPath, 2*time.Second)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: daemon not running? (%v)\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
cmd := IPCCommand{Action: action, Coin: coin}
|
||||
if err := json.NewEncoder(conn).Encode(cmd); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var resp IPCResponse
|
||||
if err := json.NewDecoder(conn).Decode(&resp); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading response: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", resp.Error)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Pretty-print response
|
||||
data, _ := json.MarshalIndent(resp.Data, "", " ")
|
||||
fmt.Println(string(data))
|
||||
}
|
||||
Reference in New Issue
Block a user