mirror of
https://github.com/livekit/livekit.git
synced 2026-03-30 04:49:53 +00:00
334 lines
8.0 KiB
Go
334 lines
8.0 KiB
Go
// Copyright 2023 LiveKit, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/urfave/cli/v3"
|
|
|
|
"github.com/livekit/protocol/logger"
|
|
"github.com/livekit/protocol/tracer/jaeger"
|
|
|
|
"github.com/livekit/livekit-server/pkg/config"
|
|
"github.com/livekit/livekit-server/pkg/routing"
|
|
"github.com/livekit/livekit-server/pkg/rtc"
|
|
"github.com/livekit/livekit-server/pkg/service"
|
|
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
|
"github.com/livekit/livekit-server/version"
|
|
)
|
|
|
|
var baseFlags = []cli.Flag{
|
|
&cli.StringSliceFlag{
|
|
Name: "bind",
|
|
Usage: "IP address to listen on, use flag multiple times to specify multiple addresses",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "config",
|
|
Usage: "path to LiveKit config file",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "config-body",
|
|
Usage: "LiveKit config in YAML, typically passed in as an environment var in a container",
|
|
Sources: cli.EnvVars("LIVEKIT_CONFIG"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "key-file",
|
|
Usage: "path to file that contains API keys/secrets",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "keys",
|
|
Usage: "api keys (key: secret\\n)",
|
|
Sources: cli.EnvVars("LIVEKIT_KEYS"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "region",
|
|
Usage: "region of the current node. Used by regionaware node selector",
|
|
Sources: cli.EnvVars("LIVEKIT_REGION"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "node-ip",
|
|
Usage: "IP address of the current node, used to advertise to clients. Automatically determined by default",
|
|
Sources: cli.EnvVars("NODE_IP"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "udp-port",
|
|
Usage: "UDP port(s) to use for WebRTC traffic",
|
|
Sources: cli.EnvVars("UDP_PORT"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "redis-host",
|
|
Usage: "host (incl. port) to redis server",
|
|
Sources: cli.EnvVars("REDIS_HOST"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "redis-password",
|
|
Usage: "password to redis",
|
|
Sources: cli.EnvVars("REDIS_PASSWORD"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "turn-cert",
|
|
Usage: "tls cert file for TURN server",
|
|
Sources: cli.EnvVars("LIVEKIT_TURN_CERT"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "turn-key",
|
|
Usage: "tls key file for TURN server",
|
|
Sources: cli.EnvVars("LIVEKIT_TURN_KEY"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "cpuprofile",
|
|
Usage: "write CPU profile to `file`",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "memprofile",
|
|
Usage: "write memory profile to `file`",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "dev",
|
|
Usage: "sets log-level to debug, console formatter, and /debug/pprof. insecure for production",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "disable-strict-config",
|
|
Usage: "disables strict config parsing",
|
|
Hidden: true,
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
rand.Seed(time.Now().Unix())
|
|
}
|
|
|
|
func main() {
|
|
defer func() {
|
|
if rtc.Recover(logger.GetLogger()) != nil {
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
generatedFlags, err := config.GenerateCLIFlags(baseFlags, true)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
|
|
cmd := &cli.Command{
|
|
Name: "livekit-server",
|
|
Usage: "High performance WebRTC server",
|
|
Description: "run without subcommands to start the server",
|
|
Flags: append(baseFlags, generatedFlags...),
|
|
Action: startServer,
|
|
Commands: []*cli.Command{
|
|
{
|
|
Name: "generate-keys",
|
|
Usage: "generates an API key and secret pair",
|
|
Action: generateKeys,
|
|
},
|
|
{
|
|
Name: "ports",
|
|
Usage: "print ports that server is configured to use",
|
|
Action: printPorts,
|
|
},
|
|
{
|
|
// this subcommand is deprecated, token generation is provided by CLI
|
|
Name: "create-join-token",
|
|
Hidden: true,
|
|
Usage: "create a room join token for development use",
|
|
Action: createToken,
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "room",
|
|
Usage: "name of room to join",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "identity",
|
|
Usage: "identity of participant that holds the token",
|
|
Required: true,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "recorder",
|
|
Usage: "creates a hidden participant that can only subscribe",
|
|
Required: false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "list-nodes",
|
|
Usage: "list all nodes",
|
|
Action: listNodes,
|
|
},
|
|
{
|
|
Name: "help-verbose",
|
|
Usage: "prints app help, including all generated configuration flags",
|
|
Action: helpVerbose,
|
|
},
|
|
},
|
|
Version: version.Version,
|
|
}
|
|
|
|
if err := cmd.Run(context.Background(), os.Args); err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
func getConfig(c *cli.Command) (*config.Config, error) {
|
|
confString, err := getConfigString(c.String("config"), c.String("config-body"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
strictMode := true
|
|
if c.Bool("disable-strict-config") {
|
|
strictMode = false
|
|
}
|
|
|
|
conf, err := config.NewConfig(confString, strictMode, c, baseFlags)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.InitLoggerFromConfig(&conf.Logging)
|
|
|
|
if conf.Development {
|
|
logger.Infow("starting in development mode")
|
|
|
|
if len(conf.Keys) == 0 {
|
|
logger.Infow("no keys provided, using placeholder keys",
|
|
"API Key", "devkey",
|
|
"API Secret", "secret",
|
|
)
|
|
conf.Keys = map[string]string{
|
|
"devkey": "secret",
|
|
}
|
|
shouldMatchRTCIP := false
|
|
// when dev mode and using shared keys, we'll bind to localhost by default
|
|
if conf.BindAddresses == nil {
|
|
conf.BindAddresses = []string{
|
|
"127.0.0.1",
|
|
"::1",
|
|
}
|
|
} else {
|
|
// if non-loopback addresses are provided, then we'll match RTC IP to bind address
|
|
// our IP discovery ignores loopback addresses
|
|
for _, addr := range conf.BindAddresses {
|
|
ip := net.ParseIP(addr)
|
|
if ip != nil && !ip.IsLoopback() && !ip.IsUnspecified() {
|
|
shouldMatchRTCIP = true
|
|
}
|
|
}
|
|
}
|
|
if shouldMatchRTCIP {
|
|
for _, bindAddr := range conf.BindAddresses {
|
|
conf.RTC.IPs.Includes = append(conf.RTC.IPs.Includes, bindAddr+"/24")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return conf, nil
|
|
}
|
|
|
|
func startServer(ctx context.Context, c *cli.Command) error {
|
|
conf, err := getConfig(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if url := conf.Trace.JaegerURL; url != "" {
|
|
jaeger.Configure(ctx, url, "livekit")
|
|
}
|
|
|
|
// validate API key length
|
|
err = conf.ValidateKeys()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cpuProfile := c.String("cpuprofile"); cpuProfile != "" {
|
|
if f, err := os.Create(cpuProfile); err != nil {
|
|
return err
|
|
} else {
|
|
if err := pprof.StartCPUProfile(f); err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
defer func() {
|
|
pprof.StopCPUProfile()
|
|
f.Close()
|
|
}()
|
|
}
|
|
}
|
|
|
|
if memProfile := c.String("memprofile"); memProfile != "" {
|
|
if f, err := os.Create(memProfile); err != nil {
|
|
return err
|
|
} else {
|
|
defer func() {
|
|
// run memory profile at termination
|
|
runtime.GC()
|
|
_ = pprof.WriteHeapProfile(f)
|
|
_ = f.Close()
|
|
}()
|
|
}
|
|
}
|
|
|
|
currentNode, err := routing.NewLocalNode(conf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := prometheus.Init(string(currentNode.NodeID()), currentNode.NodeType()); err != nil {
|
|
return err
|
|
}
|
|
|
|
server, err := service.InitializeServer(conf, currentNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
|
|
|
go func() {
|
|
for i := range 2 {
|
|
sig := <-sigChan
|
|
force := i > 0
|
|
logger.Infow("exit requested, shutting down", "signal", sig, "force", force)
|
|
go server.Stop(force)
|
|
}
|
|
}()
|
|
|
|
return server.Start()
|
|
}
|
|
|
|
func getConfigString(configFile string, inConfigBody string) (string, error) {
|
|
if inConfigBody != "" || configFile == "" {
|
|
return inConfigBody, nil
|
|
}
|
|
|
|
outConfigBody, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(outConfigBody), nil
|
|
}
|