1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/varlink/go/varlink"
)
// serviceFlag allows repeated --service flags
type serviceFlag []string
func (s *serviceFlag) String() string {
return strings.Join(*s, ", ")
}
func (s *serviceFlag) Set(value string) error {
*s = append(*s, value)
return nil
}
var (
listenAddr = flag.String("listen", ":8080", "HTTP address to bind (e.g., :8080, localhost:3000)")
debugLog = flag.Bool("debug", false, "Enable debug logging to stderr")
services serviceFlag
)
// ServiceConfig maps an interface name to a varlink socket
type ServiceConfig struct {
InterfaceName string
SocketAddr string
}
func main() {
flag.Var(&services, "service", "Varlink service mapping: <interface-name>=<socket-address> (repeatable)")
flag.Parse()
// Setup logging
if !*debugLog {
log.SetOutput(os.Stderr)
log.SetFlags(0)
log.SetPrefix("")
} else {
log.SetOutput(os.Stderr)
log.SetFlags(log.Ltime | log.Lshortfile)
}
if len(services) == 0 {
fmt.Fprintf(os.Stderr, "Usage: %s --service=<interface>=<socket> [--service=...]\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Example: %s --listen=:8080 --service=de.profpatsch.ClaudeLastPosition=unix:/run/user/1000/de.Profpatsch.ClaudeLastPosition\n", os.Args[0])
os.Exit(1)
}
// Parse service mappings
serviceMap := make(map[string]string)
for _, svc := range services {
parts := strings.SplitN(svc, "=", 2)
if len(parts) != 2 {
log.Fatalf("Invalid service format: %s (expected <interface>=<socket>)\n", svc)
}
interfaceName := strings.TrimSpace(parts[0])
socketAddr := strings.TrimSpace(parts[1])
if _, exists := serviceMap[interfaceName]; exists {
log.Fatalf("Duplicate interface name: %s\n", interfaceName)
}
serviceMap[interfaceName] = socketAddr
}
// Validate all sockets are reachable
ctx := context.Background()
log.Println("Validating varlink services...")
for interfaceName, socketAddr := range serviceMap {
conn, err := varlink.NewConnection(ctx, socketAddr)
if err != nil {
log.Printf(" ⚠ Warning: Failed to connect to %s at %s: %v\n", interfaceName, socketAddr, err)
log.Printf(" Service %s will be listed but may not be available\n", interfaceName)
continue
}
conn.Close()
log.Printf(" ✓ %s -> %s\n", interfaceName, socketAddr)
}
// Setup HTTP server
proxy := &Proxy{
services: serviceMap,
debug: *debugLog,
}
http.HandleFunc("/", proxy.handleHTTP)
server := &http.Server{
Addr: *listenAddr,
Handler: http.DefaultServeMux,
ReadTimeout: 0, // No timeout for streaming
WriteTimeout: 0, // No timeout for streaming
}
// Graceful shutdown
go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
log.Println("\nShutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
server.Shutdown(ctx)
}()
log.Printf("Listening on http://%s\n", *listenAddr)
log.Printf("Configured %d service(s):\n", len(serviceMap))
for interfaceName := range serviceMap {
log.Printf(" - GET/POST /%s\n", interfaceName)
}
log.Printf("Visit http://%s/ for web UI\n", *listenAddr)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("Server error: %v\n", err)
}
}
|