mirror of
https://github.com/livekit/livekit.git
synced 2026-03-30 15:35:41 +00:00
275 lines
6.2 KiB
Go
275 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/go-redis/redis/v8"
|
|
"github.com/pkg/errors"
|
|
"github.com/urfave/cli/v2"
|
|
|
|
"github.com/livekit/livekit-server/pkg/config"
|
|
"github.com/livekit/livekit-server/pkg/logger"
|
|
"github.com/livekit/livekit-server/pkg/routing"
|
|
"github.com/livekit/livekit-server/pkg/service"
|
|
"github.com/livekit/livekit-server/version"
|
|
"github.com/livekit/protocol/auth"
|
|
)
|
|
|
|
func init() {
|
|
rand.Seed(time.Now().Unix())
|
|
}
|
|
|
|
func main() {
|
|
app := &cli.App{
|
|
Name: "livekit-server",
|
|
Usage: "distributed audio/video rooms over WebRTC",
|
|
Description: "run without subcommands to start the server",
|
|
Flags: []cli.Flag{
|
|
&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",
|
|
EnvVars: []string{"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)",
|
|
EnvVars: []string{"LIVEKIT_KEYS"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "redis-host",
|
|
Usage: "host (incl. port) to redis server",
|
|
EnvVars: []string{"REDIS_HOST"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "redis-password",
|
|
Usage: "password to redis",
|
|
EnvVars: []string{"REDIS_PASSWORD"},
|
|
},
|
|
&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, and console formatter",
|
|
},
|
|
},
|
|
Action: startServer,
|
|
Commands: []*cli.Command{
|
|
{
|
|
Name: "generate-keys",
|
|
Usage: "generates a pair of API & secret keys",
|
|
Action: generateKeys,
|
|
},
|
|
{
|
|
Name: "ports",
|
|
Usage: "print ports that server is configured to use",
|
|
Action: printPorts,
|
|
},
|
|
{
|
|
Name: "create-join-token",
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Version: version.Version,
|
|
}
|
|
|
|
if err := app.Run(os.Args); err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
func getConfig(c *cli.Context) (*config.Config, error) {
|
|
confString, err := getConfigString(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conf, err := config.NewConfig(confString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = conf.UpdateFromCLI(c); err != nil {
|
|
return nil, err
|
|
}
|
|
return conf, nil
|
|
}
|
|
|
|
func startServer(c *cli.Context) error {
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
cpuProfile := c.String("cpuprofile")
|
|
memProfile := c.String("memprofile")
|
|
|
|
conf, err := getConfig(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if conf.Development {
|
|
logger.InitDevelopment(conf.LogLevel)
|
|
} else {
|
|
logger.InitProduction(conf.LogLevel)
|
|
}
|
|
|
|
if cpuProfile != "" {
|
|
if f, err := os.Create(cpuProfile); err != nil {
|
|
return err
|
|
} else {
|
|
defer f.Close()
|
|
if err := pprof.StartCPUProfile(f); err != nil {
|
|
return err
|
|
}
|
|
defer pprof.StopCPUProfile()
|
|
}
|
|
}
|
|
|
|
if 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()
|
|
}()
|
|
}
|
|
}
|
|
|
|
// require a key provider
|
|
keyProvider, err := createKeyProvider(conf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
logger.Infow("configured key provider", "num_keys", keyProvider.NumKeys())
|
|
|
|
currentNode, err := routing.NewLocalNode(conf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// local routing and store
|
|
router, roomStore, err := createRouterAndStore(conf, currentNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
server, err := service.InitializeServer(conf, keyProvider,
|
|
roomStore, router, currentNode, &routing.RandomSelector{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
|
|
|
go func() {
|
|
sig := <-sigChan
|
|
logger.Infow("exit requested, shutting down", "signal", sig)
|
|
server.Stop()
|
|
}()
|
|
|
|
return server.Start()
|
|
}
|
|
|
|
func createRouterAndStore(config *config.Config, node routing.LocalNode) (router routing.Router, store service.RoomStore, err error) {
|
|
if config.HasRedis() {
|
|
logger.Infow("using multi-node routing via redis", "address", config.Redis.Address)
|
|
rc := redis.NewClient(&redis.Options{
|
|
Addr: config.Redis.Address,
|
|
Username: config.Redis.Username,
|
|
Password: config.Redis.Password,
|
|
DB: config.Redis.DB,
|
|
})
|
|
if err = rc.Ping(context.Background()).Err(); err != nil {
|
|
err = errors.Wrap(err, "unable to connect to redis")
|
|
return
|
|
}
|
|
|
|
router = routing.NewRedisRouter(node, rc)
|
|
store = service.NewRedisRoomStore(rc)
|
|
} else {
|
|
// local routing and store
|
|
logger.Infow("using single-node routing")
|
|
router = routing.NewLocalRouter(node)
|
|
store = service.NewLocalRoomStore()
|
|
}
|
|
return
|
|
}
|
|
|
|
func createKeyProvider(conf *config.Config) (auth.KeyProvider, error) {
|
|
// prefer keyfile if set
|
|
if conf.KeyFile != "" {
|
|
if st, err := os.Stat(conf.KeyFile); err != nil {
|
|
return nil, err
|
|
} else if st.Mode().Perm() != 0600 {
|
|
return nil, fmt.Errorf("key file must have permission set to 600")
|
|
}
|
|
f, err := os.Open(conf.KeyFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
_ = f.Close()
|
|
}()
|
|
return auth.NewFileBasedKeyProviderFromReader(f)
|
|
}
|
|
|
|
if conf.Keys != nil {
|
|
return auth.NewFileBasedKeyProviderFromMap(conf.Keys), nil
|
|
}
|
|
|
|
return nil, errors.New("one of key-file or keys must be provided in order to support a secure installation")
|
|
}
|
|
|
|
func getConfigString(c *cli.Context) (string, error) {
|
|
configFile := c.String("config")
|
|
configBody := c.String("config-body")
|
|
if configBody == "" {
|
|
if configFile != "" {
|
|
content, err := ioutil.ReadFile(configFile)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
configBody = string(content)
|
|
}
|
|
}
|
|
return configBody, nil
|
|
}
|