Add Linux systemd support and --install-service/--remove-service flags
The service package now has a Linux implementation alongside the Windows one: systemd unit install/remove (/etc/systemd/system), readiness notification (READY=1 via go-systemd) sent only after the listeners are bound, a 30s watchdog, and STOPPING on shutdown. Listeners are pre-bound so port conflicts fail fast and the readiness signal is truthful. The notify calls are no-ops without NOTIFY_SOCKET (containers, shells) and on non-Linux builds. --install-service/--remove-service work on both platforms; the 'service install|remove' subcommand remains as an alias.
This commit is contained in:
+48
-13
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -34,12 +35,22 @@ var version = "dev"
|
||||
const exitCodeUpdate = 3
|
||||
|
||||
func main() {
|
||||
configPath, args := splitConfigFlag(os.Args[1:])
|
||||
configPath, install, remove, args := parseFlags(os.Args[1:])
|
||||
switch {
|
||||
case install && remove:
|
||||
fmt.Fprintf(os.Stderr, "gpu-turnstile: --install-service and --remove-service are mutually exclusive\n")
|
||||
os.Exit(2)
|
||||
case install:
|
||||
os.Exit(serviceCommand(configPath, []string{"install"}))
|
||||
case remove:
|
||||
os.Exit(serviceCommand(configPath, []string{"remove"}))
|
||||
}
|
||||
if len(args) > 0 && args[0] == "service" {
|
||||
os.Exit(serviceCommand(configPath, args[1:]))
|
||||
}
|
||||
if len(args) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "usage: gpu-turnstile [-config path] | gpu-turnstile service install|remove [-config path]\n")
|
||||
fmt.Fprintf(os.Stderr, "usage: gpu-turnstile [-config path] [--install-service | --remove-service]\n")
|
||||
fmt.Fprintf(os.Stderr, " gpu-turnstile service install|remove [-config path]\n")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
@@ -70,10 +81,10 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// splitConfigFlag extracts -config <path> (or -config=<path>) from args.
|
||||
func splitConfigFlag(args []string) (string, []string) {
|
||||
var configPath string
|
||||
rest := args[:0]
|
||||
// parseFlags extracts -config <path> (or -config=<path>) and the
|
||||
// --install-service / --remove-service switches from args.
|
||||
func parseFlags(args []string) (configPath string, install, remove bool, rest []string) {
|
||||
rest = args[:0]
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch {
|
||||
case args[i] == "-config" && i+1 < len(args):
|
||||
@@ -81,11 +92,15 @@ func splitConfigFlag(args []string) (string, []string) {
|
||||
i++
|
||||
case strings.HasPrefix(args[i], "-config="):
|
||||
configPath = strings.TrimPrefix(args[i], "-config=")
|
||||
case args[i] == "--install-service" || args[i] == "-install-service":
|
||||
install = true
|
||||
case args[i] == "--remove-service" || args[i] == "-remove-service":
|
||||
remove = true
|
||||
default:
|
||||
rest = append(rest, args[i])
|
||||
}
|
||||
}
|
||||
return configPath, rest
|
||||
return configPath, install, remove, rest
|
||||
}
|
||||
|
||||
// defaultConfigPath returns gpu-turnstile.env next to the executable.
|
||||
@@ -282,21 +297,41 @@ func run(ctx context.Context, cfg config.Config, log *slog.Logger, logOut io.Wri
|
||||
}
|
||||
probeCancel()
|
||||
|
||||
// Bind the listeners up front so a port conflict fails fast and the
|
||||
// readiness notification below really means "accepting connections".
|
||||
var servers []*http.Server
|
||||
var listeners []net.Listener
|
||||
bind := func(addr string, handler http.Handler, consumer string) error {
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen %s on %s: %w", consumer, addr, err)
|
||||
}
|
||||
servers = append(servers, &http.Server{Addr: addr, Handler: handler})
|
||||
listeners = append(listeners, ln)
|
||||
log.Warn("listening", "consumer", consumer, "addr", addr)
|
||||
return nil
|
||||
}
|
||||
if ollamaClient != nil {
|
||||
servers = append(servers, &http.Server{Addr: cfg.ListenOllama, Handler: srv.OllamaHandler()})
|
||||
log.Warn("listening", "consumer", "ollama", "addr", cfg.ListenOllama)
|
||||
if err := bind(cfg.ListenOllama, srv.OllamaHandler(), "ollama"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if comfyClient != nil {
|
||||
servers = append(servers, &http.Server{Addr: cfg.ListenComfy, Handler: srv.ComfyHandler()})
|
||||
log.Warn("listening", "consumer", "comfy", "addr", cfg.ListenComfy)
|
||||
if err := bind(cfg.ListenComfy, srv.ComfyHandler(), "comfy"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
errCh := make(chan error, len(servers))
|
||||
for _, s := range servers {
|
||||
go func(s *http.Server) { errCh <- s.ListenAndServe() }(s)
|
||||
for i := range servers {
|
||||
go func(s *http.Server, ln net.Listener) { errCh <- s.Serve(ln) }(servers[i], listeners[i])
|
||||
}
|
||||
|
||||
// Tell systemd we are up and start the watchdog pings; both are no-ops
|
||||
// when not running under a notify/watchdog unit.
|
||||
service.NotifyReady()
|
||||
service.StartWatchdog(ctx)
|
||||
|
||||
if cfg.AutoUpdate {
|
||||
go updateLoop(ctx, cfg, log, lk, isService)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user