🎉 live server seems to be working now
This commit is contained in:
206
third_party/sendspin-go/cmd/sendspin-server/main.go
vendored
Normal file
206
third_party/sendspin-go/cmd/sendspin-server/main.go
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
// ABOUTME: Entry point for Sendspin Protocol server
|
||||
// ABOUTME: Thin CLI wrapper around pkg/sendspin.Server with TUI support
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sendspin/sendspin-go/internal/server"
|
||||
"github.com/Sendspin/sendspin-go/pkg/sendspin"
|
||||
)
|
||||
|
||||
var (
|
||||
port = flag.Int("port", 8927, "WebSocket server port")
|
||||
name = flag.String("name", "", "Server friendly name (default: hostname-sendspin-server)")
|
||||
logFile = flag.String("log-file", "sendspin-server.log", "Log file path")
|
||||
debug = flag.Bool("debug", false, "Enable debug logging")
|
||||
noMDNS = flag.Bool("no-mdns", false, "Disable mDNS advertisement")
|
||||
noTUI = flag.Bool("no-tui", false, "Disable TUI, use streaming logs instead")
|
||||
audioFile = flag.String("audio", "", "Audio source to stream (MP3, FLAC, HTTP URL, HLS). Default: test tone")
|
||||
discoverClients = flag.Bool("discover-clients", false, "Enable server-initiated discovery: browse _sendspin._tcp and dial out to clients")
|
||||
daemon = flag.Bool("daemon", false, "Daemon mode: log to stdout only (journalctl-friendly), no TUI, no log file")
|
||||
configPath = flag.String("config", "", "Path to server.yaml config file. Default search: $SENDSPIN_SERVER_CONFIG, ~/.config/sendspin/server.yaml, /etc/sendspin/server.yaml.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Overlay YAML file and SENDSPIN_SERVER_* env vars onto flag vars for
|
||||
// anything the user didn't set on the CLI. --config is excluded because
|
||||
// putting it in the config file would be circular.
|
||||
setByUser := map[string]bool{"config": true}
|
||||
flag.Visit(func(f *flag.Flag) { setByUser[f.Name] = true })
|
||||
|
||||
cfg, _, err := sendspin.LoadServerConfig(*configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("config: %v", err)
|
||||
}
|
||||
if err := sendspin.ApplyEnvAndFile(flag.CommandLine, setByUser, sendspin.ServerEnvPrefix, cfg.AsStringMap()); err != nil {
|
||||
log.Fatalf("config overlay: %v", err)
|
||||
}
|
||||
|
||||
useTUI := !(*noTUI || *daemon)
|
||||
|
||||
if *daemon {
|
||||
// Daemon mode: log to stdout only. systemd/journalctl captures stdout
|
||||
// and adds its own timestamps, so we keep ours for grep-ability.
|
||||
log.SetOutput(os.Stdout)
|
||||
} else {
|
||||
f, err := os.OpenFile(*logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
log.Fatalf("error opening log file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if useTUI {
|
||||
// Log to file only when TUI is running; otherwise the log would stomp the TUI
|
||||
log.SetOutput(f)
|
||||
} else {
|
||||
log.SetOutput(io.MultiWriter(os.Stdout, f))
|
||||
}
|
||||
}
|
||||
|
||||
serverName := *name
|
||||
if serverName == "" {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostname = "unknown"
|
||||
}
|
||||
serverName = fmt.Sprintf("%s-sendspin-server", hostname)
|
||||
}
|
||||
|
||||
if !useTUI {
|
||||
log.Printf("Starting Sendspin Server: %s on port %d", serverName, *port)
|
||||
if *daemon {
|
||||
log.Printf("Daemon mode: logging to stdout only")
|
||||
}
|
||||
}
|
||||
|
||||
var source sendspin.AudioSource
|
||||
if *audioFile == "" {
|
||||
source = sendspin.NewTestTone(sendspin.DefaultSampleRate, sendspin.DefaultChannels)
|
||||
} else {
|
||||
internalSource, err := server.NewAudioSource(*audioFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create audio source: %v", err)
|
||||
}
|
||||
source = internalSource
|
||||
}
|
||||
|
||||
srv, err := sendspin.NewServer(sendspin.ServerConfig{
|
||||
Port: *port,
|
||||
Name: serverName,
|
||||
Source: source,
|
||||
EnableMDNS: !*noMDNS,
|
||||
Debug: *debug,
|
||||
DiscoverClients: *discoverClients,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create server: %v", err)
|
||||
}
|
||||
|
||||
var tui *server.ServerTUI
|
||||
var tuiDone chan struct{}
|
||||
if useTUI {
|
||||
tui = server.NewServerTUI(serverName, *port)
|
||||
tuiDone = make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(tuiDone)
|
||||
if err := tui.Start(serverName, *port); err != nil {
|
||||
log.Printf("TUI error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
updateTUI(tui, srv, source, serverName, *port)
|
||||
case <-tuiDone:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
var tuiQuitChan <-chan struct{}
|
||||
if tui != nil {
|
||||
tuiQuitChan = tui.QuitChan()
|
||||
}
|
||||
|
||||
serverDone := make(chan error, 1)
|
||||
go func() {
|
||||
serverDone <- srv.Start()
|
||||
}()
|
||||
|
||||
serverStopped := false
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
log.Printf("Received %v, shutting down...", sig)
|
||||
case <-tuiQuitChan:
|
||||
log.Printf("TUI quit requested, shutting down...")
|
||||
case err := <-serverDone:
|
||||
serverStopped = true
|
||||
if err != nil {
|
||||
log.Printf("Server error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
srv.Stop()
|
||||
|
||||
if tui != nil {
|
||||
tui.Stop()
|
||||
<-tuiDone
|
||||
}
|
||||
|
||||
if !serverStopped {
|
||||
if err := <-serverDone; err != nil {
|
||||
log.Printf("Server shutdown error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Server stopped")
|
||||
}
|
||||
|
||||
func updateTUI(tui *server.ServerTUI, srv *sendspin.Server, source sendspin.AudioSource, serverName string, port int) {
|
||||
clients := srv.Clients()
|
||||
|
||||
tuiClients := make([]server.ClientInfo, len(clients))
|
||||
for i, c := range clients {
|
||||
tuiClients[i] = server.ClientInfo{
|
||||
Name: c.Name,
|
||||
ID: c.ID,
|
||||
Codec: c.Codec,
|
||||
State: c.State,
|
||||
}
|
||||
}
|
||||
|
||||
title, artist, _ := source.Metadata()
|
||||
audioTitle := title
|
||||
if artist != "" && artist != "Unknown Artist" {
|
||||
audioTitle = artist + " - " + title
|
||||
}
|
||||
|
||||
tui.Update(server.ServerStatus{
|
||||
Name: serverName,
|
||||
Port: port,
|
||||
Clients: tuiClients,
|
||||
AudioTitle: audioTitle,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user