Wire wake and identity into crossbar; v2 smoke and README
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
+50
-1
@@ -17,10 +17,12 @@ import (
|
||||
"git.wntrmute.dev/kyle/crossbar/internal/admin"
|
||||
"git.wntrmute.dev/kyle/crossbar/internal/config"
|
||||
"git.wntrmute.dev/kyle/crossbar/internal/health"
|
||||
"git.wntrmute.dev/kyle/crossbar/internal/identity"
|
||||
"git.wntrmute.dev/kyle/crossbar/internal/lease"
|
||||
"git.wntrmute.dev/kyle/crossbar/internal/limiter"
|
||||
"git.wntrmute.dev/kyle/crossbar/internal/proxy"
|
||||
"git.wntrmute.dev/kyle/crossbar/internal/store"
|
||||
"git.wntrmute.dev/kyle/crossbar/internal/wake"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -59,6 +61,18 @@ func run() error {
|
||||
|
||||
hosts := proxy.HostView(table, cfg)
|
||||
lim := limiter.New()
|
||||
|
||||
// Wake: rouse a sleeping host when a route has no healthy host left. Built
|
||||
// from every host that carries a wake target; the health table satisfies the
|
||||
// waker's Health interface.
|
||||
targets := make(map[string]wake.Target, len(cfg.Hosts))
|
||||
for name, h := range cfg.Hosts {
|
||||
if h.Wake == nil {
|
||||
continue
|
||||
}
|
||||
targets[name] = wake.Target{MAC: h.Wake.MAC, Broadcast: h.Wake.Broadcast, Wait: h.Wake.Wait.Duration}
|
||||
}
|
||||
waker := wake.New(targets, hosts)
|
||||
for name, h := range cfg.Hosts {
|
||||
for model, m := range h.Models {
|
||||
lim.Configure(name, model, m.Parallel, cfg.QueueMax)
|
||||
@@ -73,9 +87,31 @@ func run() error {
|
||||
leases.Candidates(name, rt.Hosts)
|
||||
}
|
||||
|
||||
// Identity: gate the proxy on the route's peers when a backend is
|
||||
// configured; off leaves the proxy unwrapped.
|
||||
logIdentityMode(log, cfg.Identity)
|
||||
|
||||
p := proxy.New(cfg, table, leases, lim, st, log)
|
||||
p.SetWaker(waker)
|
||||
|
||||
var handler http.Handler = p
|
||||
if cfg.Identity != "off" {
|
||||
var checker *identity.Checker
|
||||
switch cfg.Identity {
|
||||
case "tailscale":
|
||||
checker = identity.NewChecker(identity.TailscaleResolver{})
|
||||
default: // "header"
|
||||
checker = identity.NewHeaderChecker()
|
||||
}
|
||||
handler = identity.Middleware(checker, func(route string) ([]string, bool) {
|
||||
rt, ok := cfg.Routes[route]
|
||||
return rt.Peers, ok
|
||||
}, p)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/_crossbar/", admin.Handler(cfg, table, leases, lim, st, hosts))
|
||||
mux.Handle("/", proxy.New(cfg, table, leases, lim, st, log))
|
||||
mux.Handle("/", handler)
|
||||
|
||||
// Background maintenance until ctx is done. Errors are logged, never fatal.
|
||||
go func() {
|
||||
@@ -156,3 +192,16 @@ func run() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// logIdentityMode logs which identity backend is active and, for the unauthenticated header
|
||||
// backend used by the smoke run, warns that it must not be exposed.
|
||||
func logIdentityMode(log *slog.Logger, mode string) {
|
||||
if mode == "off" {
|
||||
log.Info("identity", "mode", "off")
|
||||
return
|
||||
}
|
||||
log.Info("identity", "mode", mode)
|
||||
if mode == "header" {
|
||||
log.Warn("identity header mode is not authenticated; do not expose it")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
// SSE chunks 200 ms apart when the body has "stream": true, then a final chunk carrying
|
||||
// "usage" and llama-server style "timings", then [DONE]; one JSON answer with usage and
|
||||
// timings otherwise. -slow adds that many milliseconds before answering (for queue tests).
|
||||
// Every response carries X-Upstream: <name>.
|
||||
// Every response carries X-Upstream: <name>. /props reports -n-ctx and -slots. With -wol-listen,
|
||||
// a valid wake-on-LAN magic packet for -wol-mac received on that UDP address removes the down
|
||||
// file, so the fake "boots" when woken.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -16,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -28,7 +31,14 @@ func main() {
|
||||
models := flag.String("models", "m", "comma-separated model ids for /v1/models")
|
||||
downFile := flag.String("down-file", "", "while this file exists, /health answers 503")
|
||||
slow := flag.Int("slow", 0, "milliseconds to wait before answering a completion")
|
||||
nCtx := flag.Int("n-ctx", 8192, "n_ctx reported by /props")
|
||||
slots := flag.Int("slots", 2, "total_slots reported by /props")
|
||||
wolListen := flag.String("wol-listen", "", "UDP address to listen on for a wake-on-LAN magic packet")
|
||||
wolMAC := flag.String("wol-mac", "aa:bb:cc:dd:ee:01", "MAC the magic packet must carry")
|
||||
flag.Parse()
|
||||
if *wolListen != "" && *downFile != "" {
|
||||
go wakeOnPacket(*wolListen, *wolMAC, *downFile)
|
||||
}
|
||||
|
||||
ids := strings.Split(*models, ",")
|
||||
mux := http.NewServeMux()
|
||||
@@ -56,7 +66,7 @@ func main() {
|
||||
})
|
||||
mux.HandleFunc("/props", func(w http.ResponseWriter, r *http.Request) {
|
||||
stamp(w)
|
||||
writeJSON(w, map[string]any{"default_generation_settings": map[string]any{"n_ctx": 8192}, "total_slots": 2, "model_path": *name})
|
||||
writeJSON(w, map[string]any{"default_generation_settings": map[string]any{"n_ctx": *nCtx}, "total_slots": *slots, "model_path": *name})
|
||||
})
|
||||
mux.HandleFunc("/v1/chat/completions", func(w http.ResponseWriter, r *http.Request) {
|
||||
stamp(w)
|
||||
@@ -113,3 +123,39 @@ func writeJSON(w http.ResponseWriter, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
// wakeOnPacket removes downFile when a magic packet for mac arrives: 6×0xff then the MAC 16 times.
|
||||
func wakeOnPacket(addr, mac, downFile string) {
|
||||
hw, err := net.ParseMAC(mac)
|
||||
if err != nil {
|
||||
log.Fatalf("wol-mac: %v", err)
|
||||
}
|
||||
pc, err := net.ListenPacket("udp4", addr)
|
||||
if err != nil {
|
||||
log.Fatalf("wol-listen: %v", err)
|
||||
}
|
||||
log.Printf("fakeupstream listening for wake-on-LAN on %s (mac %s)", addr, hw)
|
||||
buf := make([]byte, 256)
|
||||
for {
|
||||
n, _, err := pc.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != 102 {
|
||||
continue
|
||||
}
|
||||
ok := true
|
||||
for i := 0; i < 6; i++ {
|
||||
ok = ok && buf[i] == 0xff
|
||||
}
|
||||
for i := 0; i < 16 && ok; i++ {
|
||||
for j := 0; j < 6; j++ {
|
||||
ok = ok && buf[6+6*i+j] == hw[j]
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
log.Printf("magic packet received: waking (removing %s)", downFile)
|
||||
_ = os.Remove(downFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user