diff --git a/server/cmd/root.go b/server/cmd/root.go index 68fcbae..293f345 100644 --- a/server/cmd/root.go +++ b/server/cmd/root.go @@ -1,11 +1,20 @@ package cmd import ( + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/diode" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/spf13/viper" "n.eko.moe/neko" - "n.eko.moe/neko/internal/preflight" ) func Execute() error { @@ -21,8 +30,92 @@ var root = &cobra.Command{ func init() { cobra.OnInitialize(func() { - preflight.Logs("neko") - preflight.Config("neko") + ////// + // logs + ////// + zerolog.TimeFieldFormat = "" + zerolog.SetGlobalLevel(zerolog.InfoLevel) + + if viper.GetBool("debug") { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } + + console := zerolog.ConsoleWriter{Out: os.Stdout} + + if !viper.GetBool("logs") { + log.Logger = log.Output(console) + } else { + + logs := filepath.Join(".", "logs") + if runtime.GOOS == "linux" { + logs = "/var/log/neko" + } + + if _, err := os.Stat(logs); os.IsNotExist(err) { + os.Mkdir(logs, os.ModePerm) + } + + latest := filepath.Join(logs, "neko-latest.log") + _, err := os.Stat(latest) + if err == nil { + err = os.Rename(latest, filepath.Join(logs, "neko."+time.Now().Format("2006-01-02T15-04-05Z07-00")+".log")) + if err != nil { + log.Panic().Err(err).Msg("failed to rotate log file") + } + } + + logf, err := os.OpenFile(latest, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + log.Panic().Err(err).Msg("failed to create log file") + } + + logger := diode.NewWriter(logf, 1000, 10*time.Millisecond, func(missed int) { + fmt.Printf("logger dropped %d messages", missed) + }) + + log.Logger = log.Output(io.MultiWriter(console, logger)) + } + + ////// + // configs + ////// + config := viper.GetString("config") + if config != "" { + viper.SetConfigFile(config) // Use config file from the flag. + } else { + if runtime.GOOS == "linux" { + viper.AddConfigPath("/etc/neko/") + } + + viper.AddConfigPath(".") + viper.SetConfigName("neko") + } + + viper.SetEnvPrefix("NEKO") + viper.AutomaticEnv() // read in environment variables that match + + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + log.Error().Err(err) + } + if config != "" { + log.Error().Err(err) + } + } + + file := viper.ConfigFileUsed() + logger := log.With(). + Bool("debug", viper.GetBool("debug")). + Str("logging", viper.GetString("logs")). + Str("config", file). + Logger() + + if file == "" { + logger.Warn().Msg("preflight complete without config file") + } else { + logger.Info().Msg("preflight complete") + } + neko.Service.Root.Set() }) diff --git a/server/cmd/serve.go b/server/cmd/serve.go index b1c35d7..dc6d2ce 100644 --- a/server/cmd/serve.go +++ b/server/cmd/serve.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" "n.eko.moe/neko" - "n.eko.moe/neko/internal/config" + "n.eko.moe/neko/internal/types/config" ) func init() { diff --git a/server/go.sum b/server/go.sum index ef59363..6719b1d 100644 --- a/server/go.sum +++ b/server/go.sum @@ -127,7 +127,6 @@ github.com/pion/transport v0.8.10 h1:lTiobMEw2PG6BH/mgIVqTV2mBp/mPT+IJLaN8ZxgdHk github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= github.com/pion/turn v1.4.0 h1:7NUMRehQz4fIo53Qv9ui1kJ0Kr1CA82I81RHKHCeM80= github.com/pion/turn v1.4.0/go.mod h1:aDSi6hWX/hd1+gKia9cExZOR0MU95O7zX9p3Gw/P2aU= -github.com/pion/webrtc v1.2.0 h1:3LGGPQEMacwG2hcDfhdvwQPz315gvjZXOfY4vaF4+I4= github.com/pion/webrtc/v2 v2.1.18 h1:g0VN0xfEUSlVNfQmlCD6yOeXy/tMaktESBmHMnBS3bk= github.com/pion/webrtc/v2 v2.1.18/go.mod h1:m0rKlYgLRZWyhmcMWegpF6xtK1ASxmOg8DAR74ttzQY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/server/internal/gst/gst.go b/server/internal/gst/gst.go index 90a5e4d..a0490d1 100644 --- a/server/internal/gst/gst.go +++ b/server/internal/gst/gst.go @@ -79,7 +79,7 @@ func CreatePipeline(codecName string, pipelineSrc string) (*Pipeline, error) { // gstreamer1.0-plugins-good // vp9enc - // Causes panic! + // Causes panic! not sure why... pipelineStr = pipelineSrc + " ! vp9enc ! " + pipelineStr clockRate = videoClockRate @@ -88,7 +88,6 @@ func CreatePipeline(codecName string, pipelineSrc string) (*Pipeline, error) { } case webrtc.H264: - // https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc // gstreamer1.0-plugins-bad // openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000 diff --git a/server/internal/http/http.go b/server/internal/http/http.go index bc04bd1..e8cec82 100644 --- a/server/internal/http/http.go +++ b/server/internal/http/http.go @@ -10,10 +10,10 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "n.eko.moe/neko/internal/config" "n.eko.moe/neko/internal/http/endpoint" "n.eko.moe/neko/internal/http/middleware" - "n.eko.moe/neko/internal/websocket" + "n.eko.moe/neko/internal/types" + "n.eko.moe/neko/internal/types/config" ) type Server struct { @@ -23,7 +23,7 @@ type Server struct { conf *config.Server } -func New(conf *config.Server, webSocketHandler *websocket.WebSocketHandler) *Server { +func New(conf *config.Server, webSocketHandler types.WebSocketHandler) *Server { logger := log.With().Str("module", "webrtc").Logger() router := chi.NewRouter() diff --git a/server/internal/preflight/config.go b/server/internal/preflight/config.go deleted file mode 100644 index 4ac23e2..0000000 --- a/server/internal/preflight/config.go +++ /dev/null @@ -1,48 +0,0 @@ -package preflight - -import ( - "runtime" - - "github.com/rs/zerolog/log" - "github.com/spf13/viper" -) - -func Config(name string) { - config := viper.GetString("config") - - if config != "" { - viper.SetConfigFile(config) // Use config file from the flag. - } else { - if runtime.GOOS == "linux" { - viper.AddConfigPath("/etc/neko/") - } - - viper.AddConfigPath(".") - viper.SetConfigName(name) - } - - viper.SetEnvPrefix("NEKO") - viper.AutomaticEnv() // read in environment variables that match - - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); !ok { - log.Error().Err(err) - } - if config != "" { - log.Error().Err(err) - } - } - - file := viper.ConfigFileUsed() - logger := log.With(). - Bool("debug", viper.GetBool("debug")). - Str("logging", viper.GetString("logs")). - Str("config", file). - Logger() - - if file == "" { - logger.Warn().Msg("preflight complete without config file") - } else { - logger.Info().Msg("preflight complete") - } -} diff --git a/server/internal/preflight/logs.go b/server/internal/preflight/logs.go deleted file mode 100644 index bc116a9..0000000 --- a/server/internal/preflight/logs.go +++ /dev/null @@ -1,60 +0,0 @@ -package preflight - -import ( - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "time" - - "github.com/rs/zerolog" - "github.com/rs/zerolog/diode" - "github.com/rs/zerolog/log" - "github.com/spf13/viper" -) - -func Logs(name string) { - zerolog.TimeFieldFormat = "" - zerolog.SetGlobalLevel(zerolog.InfoLevel) - - if viper.GetBool("debug") { - zerolog.SetGlobalLevel(zerolog.DebugLevel) - } - - console := zerolog.ConsoleWriter{Out: os.Stdout} - - if !viper.GetBool("logs") { - log.Logger = log.Output(console) - } else { - - logs := filepath.Join(".", "logs") - if runtime.GOOS == "linux" { - logs = "/var/log/neko" - } - - if _, err := os.Stat(logs); os.IsNotExist(err) { - os.Mkdir(logs, os.ModePerm) - } - - latest := filepath.Join(logs, name+"-latest.log") - _, err := os.Stat(latest) - if err == nil { - err = os.Rename(latest, filepath.Join(logs, "neko."+time.Now().Format("2006-01-02T15-04-05Z07-00")+".log")) - if err != nil { - log.Panic().Err(err).Msg("failed to rotate log file") - } - } - - logf, err := os.OpenFile(latest, os.O_RDWR|os.O_CREATE, 0666) - if err != nil { - log.Panic().Err(err).Msg("failed to create log file") - } - - logger := diode.NewWriter(logf, 1000, 10*time.Millisecond, func(missed int) { - fmt.Printf("logger dropped %d messages", missed) - }) - - log.Logger = log.Output(io.MultiWriter(console, logger)) - } -} diff --git a/server/internal/config/config.go b/server/internal/types/config/config.go similarity index 100% rename from server/internal/config/config.go rename to server/internal/types/config/config.go diff --git a/server/internal/config/root.go b/server/internal/types/config/root.go similarity index 100% rename from server/internal/config/root.go rename to server/internal/types/config/root.go diff --git a/server/internal/config/server.go b/server/internal/types/config/server.go similarity index 100% rename from server/internal/config/server.go rename to server/internal/types/config/server.go diff --git a/server/internal/config/webrtc.go b/server/internal/types/config/webrtc.go similarity index 62% rename from server/internal/config/webrtc.go rename to server/internal/types/config/webrtc.go index 642662d..f15da1e 100644 --- a/server/internal/config/webrtc.go +++ b/server/internal/types/config/webrtc.go @@ -1,18 +1,23 @@ package config import ( + "strconv" + "strings" + "github.com/pion/webrtc/v2" "github.com/spf13/cobra" "github.com/spf13/viper" ) type WebRTC struct { - Device string - AudioCodec string - AudioParams string - Display string - VideoCodec string - VideoParams string + Device string + AudioCodec string + AudioParams string + Display string + VideoCodec string + VideoParams string + EphemeralStart uint16 + EphemeralEnd uint16 } func (WebRTC) Init(cmd *cobra.Command) error { @@ -21,54 +26,59 @@ func (WebRTC) Init(cmd *cobra.Command) error { return err } - cmd.PersistentFlags().String("aduio", "", "Audio codec parameters to use for streaming") + cmd.PersistentFlags().String("aduio", "", "Audio codec parameters to use for streaming (unused)") if err := viper.BindPFlag("aparams", cmd.PersistentFlags().Lookup("aduio")); err != nil { return err } - cmd.PersistentFlags().String("display", ":0.0", "XDisplay to capture") + cmd.PersistentFlags().String("display", ":99.0", "XDisplay to capture") if err := viper.BindPFlag("display", cmd.PersistentFlags().Lookup("display")); err != nil { return err } - cmd.PersistentFlags().String("video", "", "Video codec parameters to use for streaming") + cmd.PersistentFlags().String("video", "", "Video codec parameters to use for streaming (unused)") if err := viper.BindPFlag("vparams", cmd.PersistentFlags().Lookup("video")); err != nil { return err } + cmd.PersistentFlags().String("epr", "59000-59100", "Limits the pool of ephemeral ports that ICE UDP connections can allocate from") + if err := viper.BindPFlag("epr", cmd.PersistentFlags().Lookup("epr")); err != nil { + return err + } + // video codecs - cmd.PersistentFlags().Bool("vp8", false, "Use VP8 codec") + cmd.PersistentFlags().Bool("vp8", false, "Use VP8 video codec") if err := viper.BindPFlag("vp8", cmd.PersistentFlags().Lookup("vp8")); err != nil { return err } - cmd.PersistentFlags().Bool("vp9", false, "Use VP9 codec") + cmd.PersistentFlags().Bool("vp9", false, "Use VP9 video codec") if err := viper.BindPFlag("vp9", cmd.PersistentFlags().Lookup("vp9")); err != nil { return err } - cmd.PersistentFlags().Bool("h264", false, "Use H264 codec") + cmd.PersistentFlags().Bool("h264", false, "Use H264 video codec") if err := viper.BindPFlag("h264", cmd.PersistentFlags().Lookup("h264")); err != nil { return err } // audio codecs - cmd.PersistentFlags().Bool("opus", false, "Use Opus codec") + cmd.PersistentFlags().Bool("opus", false, "Use Opus audio codec") if err := viper.BindPFlag("opus", cmd.PersistentFlags().Lookup("opus")); err != nil { return err } - cmd.PersistentFlags().Bool("g722", false, "Use G722 codec") + cmd.PersistentFlags().Bool("g722", false, "Use G722 audio codec") if err := viper.BindPFlag("g722", cmd.PersistentFlags().Lookup("g722")); err != nil { return err } - cmd.PersistentFlags().Bool("pcmu", false, "Use PCMU codec") + cmd.PersistentFlags().Bool("pcmu", false, "Use PCMU audio codec") if err := viper.BindPFlag("pcmu", cmd.PersistentFlags().Lookup("pcmu")); err != nil { return err } - cmd.PersistentFlags().Bool("pcma", false, "Use PCMA codec") + cmd.PersistentFlags().Bool("pcma", false, "Use PCMA audio codec") if err := viper.BindPFlag("pcmu", cmd.PersistentFlags().Lookup("pcmu")); err != nil { return err } @@ -103,4 +113,19 @@ func (s *WebRTC) Set() { s.Display = viper.GetString("display") s.VideoCodec = videoCodec s.VideoParams = viper.GetString("vparams") + s.EphemeralStart = 59000 + s.EphemeralEnd = 59100 + + epr := viper.GetString("epr") + ports := strings.SplitN(epr, "-", -1) + if len(ports[0]) > 1 { + start, err := strconv.ParseUint(ports[0], 16, 16) + if err == nil { + s.EphemeralStart = uint16(start) + } + end, err := strconv.ParseUint(ports[1], 16, 16) + if err == nil { + s.EphemeralEnd = uint16(end) + } + } } diff --git a/server/internal/config/websocket.go b/server/internal/types/config/websocket.go similarity index 100% rename from server/internal/config/websocket.go rename to server/internal/types/config/websocket.go diff --git a/server/internal/types/webscoket.go b/server/internal/types/webscoket.go index ad58721..801d752 100644 --- a/server/internal/types/webscoket.go +++ b/server/internal/types/webscoket.go @@ -1,7 +1,15 @@ package types +import "net/http" + type WebScoket interface { Address() *string Send(v interface{}) error Destroy() error } + +type WebSocketHandler interface { + Start() error + Shutdown() error + Upgrade(w http.ResponseWriter, r *http.Request) error +} diff --git a/server/internal/webrtc/webrtc.go b/server/internal/webrtc/webrtc.go index db809c7..f488dc7 100644 --- a/server/internal/webrtc/webrtc.go +++ b/server/internal/webrtc/webrtc.go @@ -8,10 +8,10 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "n.eko.moe/neko/internal/config" "n.eko.moe/neko/internal/gst" "n.eko.moe/neko/internal/hid" "n.eko.moe/neko/internal/types" + "n.eko.moe/neko/internal/types/config" ) func New(sessions types.SessionManager, config *config.WebRTC) *WebRTCManager { @@ -22,7 +22,7 @@ func New(sessions types.SessionManager, config *config.WebRTC) *WebRTCManager { }, } - setings.SetEphemeralUDPPortRange(59000, 59100) + setings.SetEphemeralUDPPortRange(config.EphemeralStart, config.EphemeralEnd) return &WebRTCManager{ logger: logger, diff --git a/server/internal/websocket/websocket.go b/server/internal/websocket/websocket.go index e24953e..eb052b8 100644 --- a/server/internal/websocket/websocket.go +++ b/server/internal/websocket/websocket.go @@ -9,9 +9,9 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "n.eko.moe/neko/internal/config" "n.eko.moe/neko/internal/hid/clipboard" "n.eko.moe/neko/internal/types" + "n.eko.moe/neko/internal/types/config" "n.eko.moe/neko/internal/types/event" "n.eko.moe/neko/internal/types/message" "n.eko.moe/neko/internal/utils" diff --git a/server/neko.go b/server/neko.go index 5a7cf64..88c9cef 100644 --- a/server/neko.go +++ b/server/neko.go @@ -6,9 +6,9 @@ import ( "os/signal" "runtime" - "n.eko.moe/neko/internal/config" "n.eko.moe/neko/internal/http" "n.eko.moe/neko/internal/session" + "n.eko.moe/neko/internal/types/config" "n.eko.moe/neko/internal/webrtc" "n.eko.moe/neko/internal/websocket"