feat: add support for reading configuration from YAML file

This commit is contained in:
zarazaex69
2026-05-13 17:17:31 +03:00
parent 359a2d94df
commit cf6490b5e0
6 changed files with 119 additions and 355 deletions

View File

@@ -1,11 +1,14 @@
// Package main provides the olcrtc CLI entrypoint.
//
// Usage: olcrtc <config.yaml>
//
// All runtime settings come from the YAML file. There are no other CLI flags.
package main
import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"io"
"log"
@@ -26,8 +29,11 @@ import (
const modeGen = "gen"
// ErrDataDirRequired is returned when no data directory is specified.
var ErrDataDirRequired = errors.New("data directory required (use -data data)")
// ErrConfigPathRequired is returned when no config file is provided.
var ErrConfigPathRequired = errors.New("usage: olcrtc <config.yaml>")
// ErrDataDirRequired is returned when the YAML config does not specify a data directory.
var ErrDataDirRequired = errors.New("data directory required (set 'data:' in YAML)")
//nolint:gochecknoglobals // Tests replace the long-running session runner with a bounded function.
var runSession = session.Run
@@ -35,45 +41,12 @@ var runSession = session.Run
//nolint:gochecknoglobals // Tests replace gen runner with a stub.
var runGen = execGen
type config struct {
configPath string
mode string
link string
transport string
auth string
engine string
url string
token string
roomID string
clientID string
socksPort int
socksHost string
socksUser string
socksPass string
keyHex string
debug bool
dataDir string
dnsServer string
socksProxyAddr string
socksProxyPort int
videoWidth int
videoHeight int
videoFPS int
videoBitrate string
videoHW string
videoQRSize int
videoQRRecovery string
videoCodec string
videoTileModule int
videoTileRS int
vp8FPS int
vp8BatchSize int
seiFPS int
seiBatchSize int
seiFragmentSize int
seiAckTimeoutMS int
amount int
ffmpegPath string
// loadedConfig bundles the parsed YAML file and the derived session config.
type loadedConfig struct {
scfg session.Config
dataDir string
debug bool
ffmpegPath string
}
func main() {
@@ -90,76 +63,54 @@ func run() error {
func runWithArgs(args []string) error {
session.RegisterDefaults()
cfg, err := parseFlagsFrom(args, flag.ExitOnError)
if len(args) != 1 || args[0] == "-h" || args[0] == "--help" || args[0] == "-help" {
return ErrConfigPathRequired
}
cfg, err := loadConfig(args[0])
if err != nil {
return err
}
return runWithConfig(cfg)
}
// applyConfigFile loads cfg.configPath (if set) and merges its values into scfg.
// CLI flags (already populated) take precedence over YAML.
func applyConfigFile(cfg config, scfg session.Config) (session.Config, error) {
if cfg.configPath == "" {
return scfg, nil
}
f, err := configpkg.Load(cfg.configPath)
func loadConfig(path string) (loadedConfig, error) {
f, err := configpkg.Load(path)
if err != nil {
return scfg, fmt.Errorf("load config: %w", err)
return loadedConfig{}, fmt.Errorf("load config: %w", err)
}
return configpkg.Apply(scfg, f), nil
return loadedConfig{
scfg: configpkg.Apply(session.Config{}, f),
dataDir: f.Data,
debug: f.Debug,
ffmpegPath: f.FFmpeg,
}, nil
}
// mergeFileMeta fills cmd-level fields (data dir, debug, ffmpeg) that aren't
// part of session.Config but still need to come from the YAML file.
func mergeFileMeta(cfg *config, f configpkg.File) {
if cfg.dataDir == "" {
cfg.dataDir = f.Data
}
if !cfg.debug {
cfg.debug = f.Debug
}
if (cfg.ffmpegPath == "" || cfg.ffmpegPath == "ffmpeg") && f.FFmpeg != "" {
cfg.ffmpegPath = f.FFmpeg
}
}
func runWithConfig(cfg config) error {
if cfg.configPath != "" {
f, err := configpkg.Load(cfg.configPath)
if err != nil {
return fmt.Errorf("load config: %w", err)
}
mergeFileMeta(&cfg, f)
}
func runWithConfig(cfg loadedConfig) error {
configureLogging(cfg.debug)
if cfg.ffmpegPath != "ffmpeg" && cfg.ffmpegPath != "" {
videochannel.FFmpegPath = cfg.ffmpegPath
}
if cfg.mode == modeGen {
return runGen(cfg)
}
return runSessionMode(cfg)
}
func runSessionMode(cfg config) error {
scfg, err := applyConfigFile(cfg, toSessionConfig(cfg))
if err != nil {
return err
}
scfg, err = session.ApplyAuthDefaults(scfg)
scfg, err := session.ApplyAuthDefaults(cfg.scfg)
if err != nil {
return fmt.Errorf("validate config: %w", err)
}
if scfg.Mode == modeGen {
return runGen(scfg)
}
return runSessionMode(cfg.dataDir, scfg)
}
func runSessionMode(dataDir string, scfg session.Config) error {
if err := session.Validate(scfg); err != nil {
return fmt.Errorf("validate config: %w", err)
}
dataDir := cfg.dataDir
if dataDir == "" {
return ErrDataDirRequired
}
@@ -194,15 +145,7 @@ func runSessionMode(cfg config) error {
}
}
func execGen(cfg config) error {
scfg, err := applyConfigFile(cfg, toSessionConfig(cfg))
if err != nil {
return err
}
scfg, err = session.ApplyAuthDefaults(scfg)
if err != nil {
return fmt.Errorf("validate gen config: %w", err)
}
func execGen(scfg session.Config) error {
if err := session.ValidateGen(scfg); err != nil {
return fmt.Errorf("validate gen config: %w", err)
}
@@ -227,62 +170,6 @@ func execGen(cfg config) error {
}
}
func parseFlagsFrom(args []string, errorHandling flag.ErrorHandling) (config, error) {
cfg := config{}
fs := flag.NewFlagSet("olcrtc", errorHandling)
if errorHandling == flag.ContinueOnError {
fs.SetOutput(io.Discard)
}
fs.StringVar(&cfg.configPath, "config", "", "Path to YAML config file (CLI flags override file values)")
fs.StringVar(&cfg.mode, "mode", "", "Mode: srv or cnc")
fs.StringVar(&cfg.link, "link", "", "Link: direct (p2p connection type)")
fs.StringVar(&cfg.transport, "transport", "", "Transport: datachannel, videochannel, seichannel")
fs.StringVar(&cfg.auth, "auth", "", "Auth provider: telemost, jazz, wbstream, none")
fs.StringVar(&cfg.engine, "engine", "", "Engine (required when -auth none): livekit, goolom, salutejazz")
fs.StringVar(&cfg.url, "url", "", "SFU WebSocket URL (required when -auth none)")
fs.StringVar(&cfg.token, "token", "", "Access token (required when -auth none)")
fs.StringVar(&cfg.roomID, "id", "", "Room ID")
fs.StringVar(&cfg.clientID, "client-id", "", "Client ID: binds one srv to one cnc (required)")
fs.IntVar(&cfg.socksPort, "socks-port", 0, "SOCKS5 port (client only)")
fs.StringVar(&cfg.socksHost, "socks-host", "", "SOCKS5 listen host (client only)")
fs.StringVar(&cfg.socksUser, "socks-user", "", "SOCKS5 username for incoming connections (client only, optional)")
fs.StringVar(&cfg.socksPass, "socks-pass", "", "SOCKS5 password for incoming connections (client only, optional)")
fs.StringVar(&cfg.keyHex, "key", "", "Shared encryption key (hex)")
fs.BoolVar(&cfg.debug, "debug", false, "Enable verbose logging")
fs.StringVar(&cfg.dataDir, "data", "", "Path to data directory")
fs.StringVar(&cfg.dnsServer, "dns", "", "DNS server (e.g. 1.1.1.1:53)")
fs.StringVar(&cfg.socksProxyAddr, "socks-proxy", "", "SOCKS5 proxy address (server only)")
fs.IntVar(&cfg.socksProxyPort, "socks-proxy-port", 0, "SOCKS5 proxy port (server only)")
fs.IntVar(&cfg.videoWidth, "video-w", 0, "Video logical width (videochannel only)")
fs.IntVar(&cfg.videoHeight, "video-h", 0, "Video logical height (videochannel only)")
fs.IntVar(&cfg.videoFPS, "video-fps", 0, "Video frames per second (videochannel only)")
fs.StringVar(&cfg.videoBitrate, "video-bitrate", "", "Video bitrate (videochannel only)")
fs.StringVar(&cfg.videoHW, "video-hw", "", "Hardware acceleration (none, nvenc)")
fs.IntVar(&cfg.videoQRSize, "video-qr-size", 0, "Video QR code fragment size (videochannel only)")
fs.StringVar(&cfg.videoQRRecovery, "video-qr-recovery", "low",
"QR error correction: low (7%), medium (15%), high (25%), highest (30%)")
fs.StringVar(&cfg.videoCodec, "video-codec", "qrcode", "Visual codec: qrcode or tile")
fs.IntVar(&cfg.videoTileModule, "video-tile-module", 0,
"Tile module size in pixels 1..270 (videochannel tile only, default 4)")
fs.IntVar(&cfg.videoTileRS, "video-tile-rs", 0,
"Tile Reed-Solomon parity percent 0..200 (videochannel tile only, default 20)")
fs.IntVar(&cfg.vp8FPS, "vp8-fps", 0, "VP8 frames per second (vp8channel only, default 25)")
fs.IntVar(&cfg.vp8BatchSize, "vp8-batch", 0, "VP8 frames per tick (vp8channel only, default 1)")
fs.IntVar(&cfg.seiFPS, "fps", 0, "Frames per second for transports that use video timing (seichannel)")
fs.IntVar(&cfg.seiBatchSize, "batch", 0, "Transport frames per tick for batched transports (seichannel)")
fs.IntVar(&cfg.seiFragmentSize, "frag", 0, "Fragment size in bytes for fragmented transports (seichannel)")
fs.IntVar(&cfg.seiAckTimeoutMS, "ack-ms", 0, "ACK timeout in milliseconds for reliable visual transports (seichannel)")
fs.IntVar(&cfg.amount, "amount", 0, "Number of rooms to generate (gen mode only)")
fs.StringVar(&cfg.ffmpegPath, "ffmpeg", "ffmpeg", "Path to ffmpeg executable")
if err := fs.Parse(args); err != nil {
return cfg, fmt.Errorf("parse flags: %w", err)
}
return cfg, nil
}
// noisyPrefixes lists log prefixes from third-party libs that spam via std log.
var noisyPrefixes = [][]byte{ //nolint:gochecknoglobals // package-level filter list
[]byte("turnc"), []byte("[turn]"), []byte("Fail to refresh permissions"),
@@ -309,10 +196,8 @@ func configureLogging(debug bool) {
logger.SetVerbose(true)
return
}
// Suppress noisy LiveKit/pion logs unless debug is enabled.
_ = os.Setenv("PION_LOG_DISABLE", "all")
lksdk.SetLogger(protoLogger.GetDiscardLogger())
// turnc logs via std log directly — filter it out.
log.SetOutput(filteredWriter{w: os.Stderr})
}
@@ -339,45 +224,6 @@ func loadNames(dataDir string) error {
return nil
}
func toSessionConfig(cfg config) session.Config {
return session.Config{
Mode: cfg.mode,
Link: cfg.link,
Transport: cfg.transport,
Auth: cfg.auth,
Engine: cfg.engine,
URL: cfg.url,
Token: cfg.token,
RoomID: cfg.roomID,
ClientID: cfg.clientID,
KeyHex: cfg.keyHex,
SOCKSHost: cfg.socksHost,
SOCKSPort: cfg.socksPort,
SOCKSUser: cfg.socksUser,
SOCKSPass: cfg.socksPass,
DNSServer: cfg.dnsServer,
SOCKSProxyAddr: cfg.socksProxyAddr,
SOCKSProxyPort: cfg.socksProxyPort,
VideoWidth: cfg.videoWidth,
VideoHeight: cfg.videoHeight,
VideoFPS: cfg.videoFPS,
VideoBitrate: cfg.videoBitrate,
VideoHW: cfg.videoHW,
VideoQRSize: cfg.videoQRSize,
VideoQRRecovery: cfg.videoQRRecovery,
VideoCodec: cfg.videoCodec,
VideoTileModule: cfg.videoTileModule,
VideoTileRS: cfg.videoTileRS,
VP8FPS: cfg.vp8FPS,
VP8BatchSize: cfg.vp8BatchSize,
SEIFPS: cfg.seiFPS,
SEIBatchSize: cfg.seiBatchSize,
SEIFragmentSize: cfg.seiFragmentSize,
SEIAckTimeoutMS: cfg.seiAckTimeoutMS,
Amount: cfg.amount,
}
}
func waitForShutdown(errCh <-chan error) error {
done := make(chan error, 1)
go func() {

View File

@@ -3,7 +3,6 @@ package main
import (
"context"
"errors"
"flag"
"os"
"path/filepath"
"testing"
@@ -14,110 +13,40 @@ import (
var errBoom = errors.New("boom")
//nolint:cyclop // table-driven test naturally has many branches
func TestToSessionConfig(t *testing.T) {
cfg := config{
mode: "cnc",
link: "direct", //nolint:goconst // test literal, repetition is intentional
transport: "vp8channel",
auth: "jazz", //nolint:goconst // test literal, repetition is intentional
roomID: "room", //nolint:goconst // test literal, repetition is intentional
clientID: "client", //nolint:goconst // test literal, repetition is intentional
keyHex: "key", //nolint:goconst // test literal, repetition is intentional
socksHost: "127.0.0.1",
socksPort: 1080,
dnsServer: "1.1.1.1:53", //nolint:goconst // test literal, repetition is intentional
socksProxyAddr: "proxy",
socksProxyPort: 1081,
videoWidth: 640,
videoHeight: 480,
videoFPS: 30,
videoBitrate: "1M",
videoHW: "none",
videoQRSize: 4,
videoQRRecovery: "low",
videoCodec: "qrcode",
videoTileModule: 4,
videoTileRS: 20,
vp8FPS: 25,
vp8BatchSize: 8,
seiFPS: 40,
seiBatchSize: 3,
seiFragmentSize: 512,
seiAckTimeoutMS: 1500,
amount: 5,
}
got := toSessionConfig(cfg)
if got.Mode != cfg.mode || got.Auth != "jazz" || got.SOCKSPort != cfg.socksPort ||
got.VideoTileRS != cfg.videoTileRS || got.VP8BatchSize != cfg.vp8BatchSize ||
got.SEIFPS != cfg.seiFPS || got.SEIBatchSize != cfg.seiBatchSize ||
got.SEIFragmentSize != cfg.seiFragmentSize || got.SEIAckTimeoutMS != cfg.seiAckTimeoutMS ||
got.Amount != cfg.amount {
t.Fatalf("toSessionConfig() = %+v", got)
func writeYAML(t *testing.T, body string) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "olcrtc.yaml")
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
return path
}
//nolint:cyclop // table-driven test naturally has many branches
func TestParseFlagsFrom(t *testing.T) {
cfg, err := parseFlagsFrom([]string{
"-mode", "srv", //nolint:goconst // test literal, repetition is intentional
"-link", "direct",
"-transport", "vp8channel",
"-auth", "telemost",
"-id", "room",
"-client-id", "client",
"-socks-port", "1080",
"-socks-host", "127.0.0.1",
"-key", "key",
"-debug",
"-data", "data",
"-dns", "9.9.9.9:53",
"-socks-proxy", "proxy",
"-socks-proxy-port", "1081",
"-video-w", "640",
"-video-h", "480",
"-video-fps", "30",
"-video-bitrate", "1M",
"-video-hw", "none",
"-video-qr-size", "128",
"-video-qr-recovery", "high",
"-video-codec", "tile",
"-video-tile-module", "6",
"-video-tile-rs", "40",
"-vp8-fps", "24",
"-vp8-batch", "3",
"-fps", "40",
"-batch", "4",
"-frag", "512",
"-ack-ms", "1500",
"-amount", "7",
}, flag.ContinueOnError)
if err != nil {
t.Fatalf("parseFlagsFrom() error = %v", err)
func TestRunWithArgsRequiresConfig(t *testing.T) {
session.RegisterDefaults()
if err := runWithArgs(nil); !errors.Is(err, ErrConfigPathRequired) {
t.Fatalf("runWithArgs(nil) = %v, want %v", err, ErrConfigPathRequired)
}
if cfg.mode != "srv" || cfg.auth != "telemost" || cfg.roomID != "room" ||
cfg.debug != true || cfg.videoCodec != "tile" || cfg.videoTileRS != 40 ||
cfg.vp8FPS != 24 || cfg.vp8BatchSize != 3 || cfg.seiFPS != 40 ||
cfg.seiBatchSize != 4 || cfg.seiFragmentSize != 512 || cfg.seiAckTimeoutMS != 1500 ||
cfg.amount != 7 {
t.Fatalf("parseFlagsFrom() = %+v", cfg)
if err := runWithArgs([]string{"-h"}); !errors.Is(err, ErrConfigPathRequired) {
t.Fatalf("runWithArgs(-h) = %v, want %v", err, ErrConfigPathRequired)
}
_, err = parseFlagsFrom([]string{"-bad"}, flag.ContinueOnError)
if err == nil {
t.Fatal("parseFlagsFrom(bad flag) error = nil")
if err := runWithArgs([]string{"a.yaml", "b.yaml"}); !errors.Is(err, ErrConfigPathRequired) {
t.Fatalf("runWithArgs(two args) = %v, want %v", err, ErrConfigPathRequired)
}
}
func TestRunGenModeValidationErrors(t *testing.T) {
session.RegisterDefaults()
if err := runWithConfig(config{mode: "gen"}); err == nil { //nolint:goconst // test literal, repetition is intentional
if err := runWithConfig(loadedConfig{scfg: session.Config{Mode: "gen"}}); err == nil {
t.Fatal("runWithConfig(gen, no carrier) error = nil")
}
if err := runWithConfig(config{mode: "gen", auth: "wbstream", dnsServer: "1.1.1.1:53"}); err == nil { //nolint:goconst,lll // test literal, repetition is intentional
cfg := loadedConfig{scfg: session.Config{
Mode: "gen", Auth: "wbstream", DNSServer: "1.1.1.1:53",
}}
if err := runWithConfig(cfg); err == nil {
t.Fatal("runWithConfig(gen, amount=0) error = nil")
}
}
@@ -128,16 +57,18 @@ func TestRunGenModeCallsGen(t *testing.T) {
var collected []string
oldRunGen := runGen
t.Cleanup(func() { runGen = oldRunGen })
runGen = func(cfg config) error {
if cfg.auth != "wbstream" || cfg.dnsServer != "1.1.1.1:53" || cfg.amount != 3 {
t.Fatalf("runGen cfg = %+v", cfg)
runGen = func(scfg session.Config) error {
if scfg.Auth != "wbstream" || scfg.DNSServer != "1.1.1.1:53" || scfg.Amount != 3 {
t.Fatalf("runGen scfg = %+v", scfg)
}
collected = append(collected, "ok")
return nil
}
err := runWithConfig(config{mode: "gen", auth: "wbstream", dnsServer: "1.1.1.1:53", amount: 3})
if err != nil {
cfg := loadedConfig{scfg: session.Config{
Mode: "gen", Auth: "wbstream", DNSServer: "1.1.1.1:53", Amount: 3,
}}
if err := runWithConfig(cfg); err != nil {
t.Fatalf("runWithConfig(gen) error = %v", err)
}
if len(collected) != 1 {
@@ -147,22 +78,21 @@ func TestRunGenModeCallsGen(t *testing.T) {
func TestRunWithConfigValidationAndDataDirErrors(t *testing.T) {
session.RegisterDefaults()
cfg := config{
mode: "srv",
link: "direct",
transport: "datachannel",
auth: "jazz",
clientID: "client",
keyHex: "key",
dnsServer: "1.1.1.1:53",
videoCodec: "qrcode",
scfg := session.Config{
Mode: "srv",
Link: "direct",
Transport: "datachannel",
Auth: "jazz",
ClientID: "client",
KeyHex: "key",
DNSServer: "1.1.1.1:53",
}
if err := runWithConfig(cfg); !errors.Is(err, ErrDataDirRequired) {
if err := runWithConfig(loadedConfig{scfg: scfg}); !errors.Is(err, ErrDataDirRequired) {
t.Fatalf("runWithConfig(no data dir) = %v, want %v", err, ErrDataDirRequired)
}
cfg.mode = ""
if err := runWithConfig(cfg); err == nil {
scfg.Mode = ""
if err := runWithConfig(loadedConfig{scfg: scfg}); err == nil {
t.Fatal("runWithConfig(invalid config) error = nil")
}
}
@@ -194,17 +124,22 @@ func TestRunWithArgsSuccessfulSessionReturn(t *testing.T) {
return nil
}
err := runWithArgs([]string{
"-mode", "srv",
"-link", "direct",
"-transport", "datachannel",
"-auth", "jazz",
"-client-id", "client",
"-key", "key",
"-dns", "1.1.1.1:53",
"-data", dir,
})
if err != nil {
yamlPath := writeYAML(t, `
mode: srv
link: direct
auth:
provider: jazz
room:
client_id: client
crypto:
key: key
net:
transport: datachannel
dns: 1.1.1.1:53
data: `+dir+`
`)
if err := runWithArgs([]string{yamlPath}); err != nil {
t.Fatalf("runWithArgs() error = %v", err)
}
if !called {

View File

@@ -5,7 +5,6 @@
mode: cnc
link: direct
carrier: ""
auth:
provider: wbstream # must match the server

View File

@@ -1,17 +1,10 @@
# Configuration
olcrtc accepts the same settings via CLI flags or a YAML file. Use whichever
fits your deployment:
olcrtc reads its entire runtime configuration from a single YAML file.
There are no other CLI flags.
```bash
# CLI flags (existing behaviour)
olcrtc -mode srv -auth wbstream -id room123 -key $(openssl rand -hex 32) ...
# YAML file
olcrtc -config /etc/olcrtc/server.yaml
# YAML file plus CLI overrides — any flag wins over the corresponding YAML field
olcrtc -config /etc/olcrtc/server.yaml -id room999
olcrtc /etc/olcrtc/server.yaml
```
Examples:
@@ -21,31 +14,24 @@ Examples:
## Schema
| YAML path | CLI flag | Notes |
|----------------------------|----------------------|-----------------------------------------------|
| `mode` | `-mode` | `srv`, `cnc`, or `gen` |
| `link` | `-link` | `direct` |
| `auth.provider` | `-auth` | `telemost`, `jazz`, `wbstream`, `none` |
| `room.id` | `-id` | conference room id |
| `room.client_id` | `-client-id` | deprecated, will be removed |
| `crypto.key` | `-key` | 64-char hex (32 bytes) |
| `net.transport` | `-transport` | `datachannel`, `videochannel`, `seichannel`, `vp8channel` |
| `net.dns` | `-dns` | resolver `host:port` |
| `socks.host` / `.port` | `-socks-host` / `-socks-port` | client-side listener |
| `socks.user` / `.pass` | `-socks-user` / `-socks-pass` | optional client-side auth |
| `socks.proxy_addr` / `.proxy_port` | `-socks-proxy` / `-socks-proxy-port` | server-side egress proxy |
| `engine.name` / `.url` / `.token` | `-engine` / `-url` / `-token` | only when `auth.provider: none` |
| `video.*` | `-video-*` | videochannel tuning |
| `vp8.*` | `-vp8-*` | vp8channel tuning |
| `sei.fps` / `.batch_size` / `.fragment_size` / `.ack_timeout_ms` | `-fps` / `-batch` / `-frag` / `-ack-ms` | seichannel tuning |
| `gen.amount` | `-amount` | gen mode: number of rooms to create |
| `data` | `-data` | path to data directory |
| `debug` | `-debug` | verbose logging |
| `ffmpeg` | `-ffmpeg` | path to ffmpeg binary |
## Precedence
`CLI flag (non-zero) > YAML value > zero value`.
A CLI flag with its zero value (e.g. `-socks-port 0`) does NOT override a YAML
value — pass an explicit non-zero value to override.
| YAML path | Notes |
|------------------------------------------------------------------|-----------------------------------------------------------|
| `mode` | `srv`, `cnc`, or `gen` |
| `link` | `direct` |
| `auth.provider` | `telemost`, `jazz`, `wbstream`, `none` |
| `room.id` | conference room id |
| `room.client_id` | deprecated, will be removed |
| `crypto.key` | 64-char hex (32 bytes) |
| `net.transport` | `datachannel`, `videochannel`, `seichannel`, `vp8channel` |
| `net.dns` | resolver `host:port` |
| `socks.host` / `.port` | client-side listener |
| `socks.user` / `.pass` | optional client-side auth |
| `socks.proxy_addr` / `.proxy_port` | server-side egress proxy |
| `engine.name` / `.url` / `.token` | only when `auth.provider: none` |
| `video.*` | videochannel tuning |
| `vp8.*` | vp8channel tuning |
| `sei.fps` / `.batch_size` / `.fragment_size` / `.ack_timeout_ms` | seichannel tuning |
| `gen.amount` | gen mode: number of rooms to create |
| `data` | path to data directory |
| `debug` | verbose logging |
| `ffmpeg` | path to ffmpeg binary |

View File

@@ -6,7 +6,6 @@ mode: srv
# Connection topology
link: direct # p2p link type
carrier: "" # leave empty for default selection from auth provider
auth:
provider: wbstream # telemost | jazz | wbstream | none

View File

@@ -23,7 +23,6 @@ var ErrConfigNotFound = errors.New("config file not found")
type File struct {
Mode string `yaml:"mode"`
Link string `yaml:"link"`
Carrier string `yaml:"carrier"`
Auth Auth `yaml:"auth"`
Room Room `yaml:"room"`
Crypto Crypto `yaml:"crypto"`