// sf-worker — SSH PTY daemon that executes sf headless runs on behalf of a remote orchestrator. // // Purpose: allow the SF orchestrator to dispatch autonomous unit attempts to remote hosts // (GPU boxes, Windows machines, parallel workers) over SSH without requiring a full SF // installation on the controlling machine. // // Usage: // // sf-worker [flags] // --addr SSH listen address (default ":2222") // --metrics-addr Prometheus /metrics address (default ":9100") // --host-key Path to SSH host key (default "~/.sf/worker_host_key") // --authorized-keys Path to authorized_keys file (default "~/.sf/worker_authorized_keys") // --sf-bin Path to sf binary (default: resolved from $PATH) // --max-sessions Maximum concurrent sessions (default 16) package main import ( "context" "flag" "fmt" "net/http" "os" "os/signal" "path/filepath" "syscall" "time" "github.com/charmbracelet/log" "github.com/prometheus/client_golang/prometheus/promhttp" ) func defaultPath(rel string) string { home, err := os.UserHomeDir() if err != nil { return rel } return filepath.Join(home, ".sf", rel) } func main() { addr := flag.String("addr", ":2222", "SSH listen address") metricsAddr := flag.String("metrics-addr", ":9100", "Prometheus metrics address") hostKeyPath := flag.String("host-key", defaultPath("worker_host_key"), "SSH host key path (RSA/Ed25519 PEM)") authorizedKeysPath := flag.String("authorized-keys", defaultPath("worker_authorized_keys"), "authorized_keys path") sfBin := flag.String("sf-bin", "", "Path to sf binary (default: resolved from $PATH)") maxSessions := flag.Int("max-sessions", 16, "Maximum concurrent sessions") flag.Parse() logger := log.NewWithOptions(os.Stderr, log.Options{ ReportTimestamp: true, TimeFormat: time.RFC3339, Level: log.InfoLevel, }) srv, err := NewServer(ServerConfig{ Addr: *addr, HostKeyPath: *hostKeyPath, AuthorizedKeysPath: *authorizedKeysPath, SFBin: *sfBin, MaxSessions: *maxSessions, Logger: logger, }) if err != nil { logger.Error("failed to create server", "err", err) os.Exit(1) } // Prometheus metrics endpoint. metricsMux := http.NewServeMux() metricsMux.Handle("/metrics", promhttp.Handler()) metricsMux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, "ok") }) metricsServer := &http.Server{ Addr: *metricsAddr, Handler: metricsMux, } go func() { logger.Info("metrics server listening", "addr", *metricsAddr) if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Error("metrics server error", "err", err) } }() ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() logger.Info("sf-worker starting", "addr", *addr) if err := srv.ListenAndServe(ctx); err != nil { logger.Error("server error", "err", err) os.Exit(1) } shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownCancel() _ = metricsServer.Shutdown(shutdownCtx) logger.Info("sf-worker stopped") }