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)
	}
}