mirror of
https://github.com/m1k1o/neko-rooms.git
synced 2026-11-03 07:01:35 -06:00
Allow API versioning (#85)
* add api v3 partial support. * fix codecs in env. * fix default codecs. * split files. * add session stats for api v3. * fix API token env. * fix null array in json. * add CORS. * save api version to labels. * irgnore api token env. * remove duplicate api version. * move api version to settings.
This commit is contained in:
@@ -467,6 +467,9 @@ components:
|
|||||||
RoomSettings:
|
RoomSettings:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
api_version:
|
||||||
|
type: number
|
||||||
|
description: if not set, version is taken from neko_image
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
example: foobar
|
example: foobar
|
||||||
|
|||||||
@@ -394,6 +394,12 @@ export interface RoomResources {
|
|||||||
* @interface RoomSettings
|
* @interface RoomSettings
|
||||||
*/
|
*/
|
||||||
export interface RoomSettings {
|
export interface RoomSettings {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof RoomSettings
|
||||||
|
*/
|
||||||
|
'api_version'?: number;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type Server struct {
|
|||||||
Key string
|
Key string
|
||||||
Bind string
|
Bind string
|
||||||
Proxy bool
|
Proxy bool
|
||||||
|
CORS bool
|
||||||
PProf bool
|
PProf bool
|
||||||
|
|
||||||
Admin Admin
|
Admin Admin
|
||||||
@@ -46,6 +47,11 @@ func (Server) Init(cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().Bool("cors", false, "enable CORS")
|
||||||
|
if err := viper.BindPFlag("cors", cmd.PersistentFlags().Lookup("cors")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
cmd.PersistentFlags().Bool("pprof", false, "enable pprof endpoint available at /debug/pprof")
|
cmd.PersistentFlags().Bool("pprof", false, "enable pprof endpoint available at /debug/pprof")
|
||||||
if err := viper.BindPFlag("pprof", cmd.PersistentFlags().Lookup("pprof")); err != nil {
|
if err := viper.BindPFlag("pprof", cmd.PersistentFlags().Lookup("pprof")); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -86,6 +92,7 @@ func (s *Server) Set() {
|
|||||||
s.Key = viper.GetString("key")
|
s.Key = viper.GetString("key")
|
||||||
s.Bind = viper.GetString("bind")
|
s.Bind = viper.GetString("bind")
|
||||||
s.Proxy = viper.GetBool("proxy")
|
s.Proxy = viper.GetBool("proxy")
|
||||||
|
s.CORS = viper.GetBool("cors")
|
||||||
s.PProf = viper.GetBool("pprof")
|
s.PProf = viper.GetBool("pprof")
|
||||||
|
|
||||||
s.Admin.Static = viper.GetString("admin.static")
|
s.Admin.Static = viper.GetString("admin.static")
|
||||||
|
|||||||
+34
-15
@@ -12,11 +12,13 @@ import (
|
|||||||
var labelRegex = regexp.MustCompile(`^[a-z0-9.-]+$`)
|
var labelRegex = regexp.MustCompile(`^[a-z0-9.-]+$`)
|
||||||
|
|
||||||
type RoomLabels struct {
|
type RoomLabels struct {
|
||||||
Name string
|
Name string
|
||||||
URL string
|
URL string
|
||||||
Mux bool
|
Mux bool
|
||||||
Epr EprPorts
|
Epr EprPorts
|
||||||
NekoImage string
|
|
||||||
|
NekoImage string
|
||||||
|
ApiVersion int
|
||||||
|
|
||||||
BrowserPolicy *BrowserPolicyLabels
|
BrowserPolicy *BrowserPolicyLabels
|
||||||
UserDefined map[string]string
|
UserDefined map[string]string
|
||||||
@@ -40,11 +42,6 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
|
|||||||
//return nil, fmt.Errorf("damaged container labels: url not found")
|
//return nil, fmt.Errorf("damaged container labels: url not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
nekoImage, ok := labels["m1k1o.neko_rooms.neko_image"]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("damaged container labels: neko_image not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
var mux bool
|
var mux bool
|
||||||
var epr EprPorts
|
var epr EprPorts
|
||||||
|
|
||||||
@@ -88,6 +85,21 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nekoImage, ok := labels["m1k1o.neko_rooms.neko_image"]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("damaged container labels: neko_image not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
apiVersion := 2 // default, prior to api versioning
|
||||||
|
apiVersionStr, ok := labels["m1k1o.neko_rooms.api_version"]
|
||||||
|
if ok {
|
||||||
|
var err error
|
||||||
|
apiVersion, err = strconv.Atoi(apiVersionStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var browserPolicy *BrowserPolicyLabels
|
var browserPolicy *BrowserPolicyLabels
|
||||||
if val, ok := labels["m1k1o.neko_rooms.browser_policy"]; ok && val == "true" {
|
if val, ok := labels["m1k1o.neko_rooms.browser_policy"]; ok && val == "true" {
|
||||||
policyType, ok := labels["m1k1o.neko_rooms.browser_policy.type"]
|
policyType, ok := labels["m1k1o.neko_rooms.browser_policy.type"]
|
||||||
@@ -115,11 +127,13 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &RoomLabels{
|
return &RoomLabels{
|
||||||
Name: name,
|
Name: name,
|
||||||
URL: url,
|
URL: url,
|
||||||
NekoImage: nekoImage,
|
Mux: mux,
|
||||||
Mux: mux,
|
Epr: epr,
|
||||||
Epr: epr,
|
|
||||||
|
NekoImage: nekoImage,
|
||||||
|
ApiVersion: apiVersion,
|
||||||
|
|
||||||
BrowserPolicy: browserPolicy,
|
BrowserPolicy: browserPolicy,
|
||||||
UserDefined: userDefined,
|
UserDefined: userDefined,
|
||||||
@@ -134,6 +148,11 @@ func (manager *RoomManagerCtx) serializeLabels(labels RoomLabels) map[string]str
|
|||||||
"m1k1o.neko_rooms.neko_image": labels.NekoImage,
|
"m1k1o.neko_rooms.neko_image": labels.NekoImage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// api version 2 is currently default
|
||||||
|
if labels.ApiVersion != 2 {
|
||||||
|
labelsMap["m1k1o.neko_rooms.api_version"] = fmt.Sprintf("%d", labels.ApiVersion)
|
||||||
|
}
|
||||||
|
|
||||||
if labels.Mux && labels.Epr.Min == labels.Epr.Max {
|
if labels.Mux && labels.Epr.Min == labels.Epr.Max {
|
||||||
labelsMap["m1k1o.neko_rooms.mux"] = fmt.Sprintf("%d", labels.Epr.Min)
|
labelsMap["m1k1o.neko_rooms.mux"] = fmt.Sprintf("%d", labels.Epr.Min)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+128
-17
@@ -10,7 +10,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
dockerTypes "github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
@@ -248,6 +250,40 @@ func (manager *RoomManagerCtx) Create(ctx context.Context, settings types.RoomSe
|
|||||||
|
|
||||||
isPrivilegedImage, _ := utils.ArrayIn(settings.NekoImage, manager.config.NekoPrivilegedImages)
|
isPrivilegedImage, _ := utils.ArrayIn(settings.NekoImage, manager.config.NekoPrivilegedImages)
|
||||||
|
|
||||||
|
// if api version is not set, try to detect it
|
||||||
|
if settings.ApiVersion == 0 {
|
||||||
|
inspect, _, err := manager.client.ImageInspectWithRaw(ctx, settings.NekoImage)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on image label
|
||||||
|
if val, ok := inspect.Config.Labels["m1k1o.neko_rooms.api_version"]; ok {
|
||||||
|
var err error
|
||||||
|
settings.ApiVersion, err = strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
|
||||||
|
// based on opencontainers image url label
|
||||||
|
if val, ok := inspect.Config.Labels["org.opencontainers.image.url"]; ok {
|
||||||
|
switch val {
|
||||||
|
case "https://github.com/m1k1o/neko":
|
||||||
|
settings.ApiVersion = 2
|
||||||
|
case "https://github.com/demodesk/neko":
|
||||||
|
settings.ApiVersion = 3
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
|
||||||
|
// unable to detect api version
|
||||||
|
{
|
||||||
|
// TODO: this should be removed in future, but since we have a lot of v2 images, we need to support it
|
||||||
|
log.Warn().Str("image", settings.NekoImage).Msg("unable to detect api version, fallback to v2")
|
||||||
|
settings.ApiVersion = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Check if path name exists.
|
// TODO: Check if path name exists.
|
||||||
roomName := settings.Name
|
roomName := settings.Name
|
||||||
if roomName == "" {
|
if roomName == "" {
|
||||||
@@ -315,10 +351,12 @@ func (manager *RoomManagerCtx) Create(ctx context.Context, settings types.RoomSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
labels := manager.serializeLabels(RoomLabels{
|
labels := manager.serializeLabels(RoomLabels{
|
||||||
Name: roomName,
|
Name: roomName,
|
||||||
Mux: manager.config.Mux,
|
Mux: manager.config.Mux,
|
||||||
Epr: epr,
|
Epr: epr,
|
||||||
NekoImage: settings.NekoImage,
|
|
||||||
|
NekoImage: settings.NekoImage,
|
||||||
|
ApiVersion: settings.ApiVersion,
|
||||||
|
|
||||||
BrowserPolicy: browserPolicyLabels,
|
BrowserPolicy: browserPolicyLabels,
|
||||||
UserDefined: settings.Labels,
|
UserDefined: settings.Labels,
|
||||||
@@ -394,11 +432,16 @@ func (manager *RoomManagerCtx) Create(ctx context.Context, settings types.RoomSe
|
|||||||
// Set environment variables
|
// Set environment variables
|
||||||
//
|
//
|
||||||
|
|
||||||
env := settings.ToEnv(manager.config, types.PortSettings{
|
env, err := settings.ToEnv(
|
||||||
FrontendPort: frontendPort,
|
manager.config,
|
||||||
EprMin: epr.Min,
|
types.PortSettings{
|
||||||
EprMax: epr.Max,
|
FrontendPort: frontendPort,
|
||||||
})
|
EprMin: epr.Min,
|
||||||
|
EprMax: epr.Max,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Set browser policies
|
// Set browser policies
|
||||||
@@ -830,7 +873,7 @@ func (manager *RoomManagerCtx) GetSettings(ctx context.Context, id string) (*typ
|
|||||||
settings.MaxConnections = 0
|
settings.MaxConnections = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
err = settings.FromEnv(container.Config.Env)
|
err = settings.FromEnv(labels.ApiVersion, container.Config.Env)
|
||||||
return &settings, err
|
return &settings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,22 +883,90 @@ func (manager *RoomManagerCtx) GetStats(ctx context.Context, id string) (*types.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
settings := types.RoomSettings{}
|
labels, err := manager.extractLabels(container.Config.Labels)
|
||||||
err = settings.FromEnv(container.Config.Env)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := manager.containerExec(ctx, id, []string{
|
settings := types.RoomSettings{}
|
||||||
"wget", "-q", "-O-", "http://127.0.0.1:8080/stats?pwd=" + url.QueryEscape(settings.AdminPass),
|
err = settings.FromEnv(labels.ApiVersion, container.Config.Env)
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var stats types.RoomStats
|
var stats types.RoomStats
|
||||||
if err := json.Unmarshal([]byte(output), &stats); err != nil {
|
switch labels.ApiVersion {
|
||||||
return nil, err
|
case 2:
|
||||||
|
output, err := manager.containerExec(ctx, id, []string{
|
||||||
|
"wget", "-q", "-O-", "http://127.0.0.1:8080/stats?pwd=" + url.QueryEscape(settings.AdminPass),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(output), &stats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
output, err := manager.containerExec(ctx, id, []string{
|
||||||
|
"wget", "-q", "-O-", "http://127.0.0.1:8080/api/sessions?token=" + url.QueryEscape(settings.AdminPass),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessions []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Profile struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
} `json:"profile"`
|
||||||
|
State struct {
|
||||||
|
IsConnected bool `json:"is_connected"`
|
||||||
|
NotConnectedSince *time.Time `json:"not_connected_since"`
|
||||||
|
} `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(output), &sessions); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create empty array so that it's not null in json
|
||||||
|
stats.Members = []*types.RoomMember{}
|
||||||
|
|
||||||
|
for _, session := range sessions {
|
||||||
|
if session.State.IsConnected {
|
||||||
|
stats.Connections++
|
||||||
|
// append members
|
||||||
|
stats.Members = append(stats.Members, &types.RoomMember{
|
||||||
|
ID: session.ID,
|
||||||
|
Name: session.Profile.Name,
|
||||||
|
Admin: session.Profile.IsAdmin,
|
||||||
|
Muted: false, // not supported
|
||||||
|
})
|
||||||
|
} else if session.State.NotConnectedSince != nil {
|
||||||
|
// populate last admin left time
|
||||||
|
if session.Profile.IsAdmin && (stats.LastAdminLeftAt == nil || (*session.State.NotConnectedSince).After(*stats.LastAdminLeftAt)) {
|
||||||
|
stats.LastAdminLeftAt = session.State.NotConnectedSince
|
||||||
|
}
|
||||||
|
// populate last user left time
|
||||||
|
if !session.Profile.IsAdmin && (stats.LastUserLeftAt == nil || (*session.State.NotConnectedSince).After(*stats.LastUserLeftAt)) {
|
||||||
|
stats.LastUserLeftAt = session.State.NotConnectedSince
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse started time
|
||||||
|
if container.State.StartedAt != "" {
|
||||||
|
stats.ServerStartedAt, err = time.Parse(time.RFC3339, container.State.StartedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: settings & host
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported API version: %d", labels.ApiVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &stats, nil
|
return &stats, nil
|
||||||
|
|||||||
@@ -45,14 +45,18 @@ func New(ApiManager types.ApiManager, roomConfig *config.Room, config *config.Se
|
|||||||
router.Use(middleware.Recoverer) // Recover from panics without crashing server
|
router.Use(middleware.Recoverer) // Recover from panics without crashing server
|
||||||
|
|
||||||
// Basic CORS
|
// Basic CORS
|
||||||
// for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing
|
if config.CORS {
|
||||||
router.Use(cors.Handler(cors.Options{
|
// for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing
|
||||||
AllowedOrigins: []string{"*"},
|
router.Use(cors.Handler(cors.Options{
|
||||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
AllowOriginFunc: func(r *http.Request, origin string) bool {
|
||||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
return true
|
||||||
AllowCredentials: true,
|
},
|
||||||
MaxAge: 300, // Maximum value not ignored by any of major browsers
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||||
}))
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||||
|
AllowCredentials: true,
|
||||||
|
MaxAge: 300, // Maximum value not ignored by any of major browsers
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
// mount pprof endpoint
|
// mount pprof endpoint
|
||||||
if config.PProf {
|
if config.PProf {
|
||||||
|
|||||||
+24
-170
@@ -3,31 +3,11 @@ package types
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/m1k1o/neko-rooms/internal/config"
|
"github.com/m1k1o/neko-rooms/internal/config"
|
||||||
"github.com/m1k1o/neko-rooms/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var blacklistedEnvs = []string{
|
|
||||||
// ignore bunch of default envs
|
|
||||||
"DEBIAN_FRONTEND",
|
|
||||||
"PULSE_SERVER",
|
|
||||||
"DISPLAY",
|
|
||||||
"USER",
|
|
||||||
"PATH",
|
|
||||||
|
|
||||||
// ignore bunch of envs managed by neko-rooms
|
|
||||||
"NEKO_BIND",
|
|
||||||
"NEKO_EPR",
|
|
||||||
"NEKO_UDPMUX",
|
|
||||||
"NEKO_TCPMUX",
|
|
||||||
"NEKO_NAT1TO1",
|
|
||||||
"NEKO_ICELITE",
|
|
||||||
}
|
|
||||||
|
|
||||||
type RoomsConfig struct {
|
type RoomsConfig struct {
|
||||||
Connections uint16 `json:"connections"`
|
Connections uint16 `json:"connections"`
|
||||||
NekoImages []string `json:"neko_images"`
|
NekoImages []string `json:"neko_images"`
|
||||||
@@ -73,6 +53,8 @@ type RoomResources struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RoomSettings struct {
|
type RoomSettings struct {
|
||||||
|
ApiVersion int `json:"api_version"`
|
||||||
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
NekoImage string `json:"neko_image"`
|
NekoImage string `json:"neko_image"`
|
||||||
MaxConnections uint16 `json:"max_connections"` // 0 when using mux
|
MaxConnections uint16 `json:"max_connections"` // 0 when using mux
|
||||||
@@ -106,161 +88,33 @@ type RoomSettings struct {
|
|||||||
BrowserPolicy *BrowserPolicy `json:"browser_policy,omitempty"`
|
BrowserPolicy *BrowserPolicy `json:"browser_policy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (settings *RoomSettings) ToEnv(config *config.Room, ports PortSettings) ([]string, error) {
|
||||||
|
switch settings.ApiVersion {
|
||||||
|
case 2:
|
||||||
|
return settings.toEnvV2(config, ports), nil
|
||||||
|
case 3:
|
||||||
|
return settings.toEnvV3(config, ports), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported API version: %d", settings.ApiVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings *RoomSettings) FromEnv(apiVersion int, envs []string) error {
|
||||||
|
switch apiVersion {
|
||||||
|
case 2:
|
||||||
|
return settings.fromEnvV2(envs)
|
||||||
|
case 3:
|
||||||
|
return settings.fromEnvV3(envs)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported API version: %d", apiVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type PortSettings struct {
|
type PortSettings struct {
|
||||||
FrontendPort uint16
|
FrontendPort uint16
|
||||||
EprMin, EprMax uint16
|
EprMin, EprMax uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func (settings *RoomSettings) ToEnv(config *config.Room, ports PortSettings) []string {
|
|
||||||
env := []string{
|
|
||||||
fmt.Sprintf("NEKO_BIND=:%d", ports.FrontendPort),
|
|
||||||
"NEKO_ICELITE=true",
|
|
||||||
|
|
||||||
// from settings
|
|
||||||
fmt.Sprintf("NEKO_PASSWORD=%s", settings.UserPass),
|
|
||||||
fmt.Sprintf("NEKO_PASSWORD_ADMIN=%s", settings.AdminPass),
|
|
||||||
fmt.Sprintf("NEKO_SCREEN=%s", settings.Screen),
|
|
||||||
fmt.Sprintf("NEKO_MAX_FPS=%d", settings.VideoMaxFPS),
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Mux {
|
|
||||||
env = append(env,
|
|
||||||
fmt.Sprintf("NEKO_UDPMUX=%d", ports.EprMin),
|
|
||||||
fmt.Sprintf("NEKO_TCPMUX=%d", ports.EprMin),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
env = append(env,
|
|
||||||
fmt.Sprintf("NEKO_EPR=%d-%d", ports.EprMin, ports.EprMax),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// optional nat mapping
|
|
||||||
if len(config.NAT1To1IPs) > 0 {
|
|
||||||
env = append(env, fmt.Sprintf("NEKO_NAT1TO1=%s", strings.Join(config.NAT1To1IPs, ",")))
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.ControlProtection {
|
|
||||||
env = append(env, "NEKO_CONTROL_PROTECTION=true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.ImplicitControl {
|
|
||||||
env = append(env, "NEKO_IMPLICIT_CONTROL=true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.VideoCodec == "VP8" || settings.VideoCodec == "VP9" || settings.VideoCodec == "H264" {
|
|
||||||
env = append(env, fmt.Sprintf("NEKO_%s=true", strings.ToUpper(settings.VideoCodec)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.VideoBitrate != 0 {
|
|
||||||
env = append(env, fmt.Sprintf("NEKO_VIDEO_BITRATE=%d", settings.VideoBitrate))
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.VideoPipeline != "" {
|
|
||||||
env = append(env, fmt.Sprintf("NEKO_VIDEO=%s", settings.VideoPipeline))
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.AudioCodec == "OPUS" || settings.AudioCodec == "G722" || settings.AudioCodec == "PCMU" || settings.AudioCodec == "PCMA" {
|
|
||||||
env = append(env, fmt.Sprintf("NEKO_%s=true", strings.ToUpper(settings.AudioCodec)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.AudioBitrate != 0 {
|
|
||||||
env = append(env, fmt.Sprintf("NEKO_AUDIO_BITRATE=%d", settings.AudioBitrate))
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.AudioPipeline != "" {
|
|
||||||
env = append(env, fmt.Sprintf("NEKO_AUDIO=%s", settings.AudioPipeline))
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.BroadcastPipeline != "" {
|
|
||||||
env = append(env, fmt.Sprintf("NEKO_BROADCAST_PIPELINE=%s", settings.BroadcastPipeline))
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, val := range settings.Envs {
|
|
||||||
if in, _ := utils.ArrayIn(key, blacklistedEnvs); !in {
|
|
||||||
env = append(env, fmt.Sprintf("%s=%s", key, val))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *RoomSettings) FromEnv(envs []string) error {
|
|
||||||
settings.Envs = map[string]string{}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for _, env := range envs {
|
|
||||||
r := strings.SplitN(env, "=", 2)
|
|
||||||
key, val := r[0], r[1]
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "NEKO_PASSWORD":
|
|
||||||
settings.UserPass = val
|
|
||||||
case "NEKO_PASSWORD_ADMIN":
|
|
||||||
settings.AdminPass = val
|
|
||||||
case "NEKO_CONTROL_PROTECTION":
|
|
||||||
if ok, _ := strconv.ParseBool(val); ok {
|
|
||||||
settings.ControlProtection = true
|
|
||||||
}
|
|
||||||
case "NEKO_IMPLICIT_CONTROL":
|
|
||||||
if ok, _ := strconv.ParseBool(val); ok {
|
|
||||||
settings.ImplicitControl = true
|
|
||||||
}
|
|
||||||
case "NEKO_SCREEN":
|
|
||||||
settings.Screen = val
|
|
||||||
case "NEKO_MAX_FPS":
|
|
||||||
settings.VideoMaxFPS, err = strconv.Atoi(val)
|
|
||||||
case "NEKO_BROADCAST_PIPELINE":
|
|
||||||
settings.BroadcastPipeline = val
|
|
||||||
case "NEKO_VP8":
|
|
||||||
if ok, _ := strconv.ParseBool(val); ok {
|
|
||||||
settings.VideoCodec = "VP8"
|
|
||||||
}
|
|
||||||
case "NEKO_VP9":
|
|
||||||
if ok, _ := strconv.ParseBool(val); ok {
|
|
||||||
settings.VideoCodec = "VP9"
|
|
||||||
}
|
|
||||||
case "NEKO_H264":
|
|
||||||
if ok, _ := strconv.ParseBool(val); ok {
|
|
||||||
settings.VideoCodec = "H264"
|
|
||||||
}
|
|
||||||
case "NEKO_VIDEO_BITRATE":
|
|
||||||
settings.VideoBitrate, err = strconv.Atoi(val)
|
|
||||||
case "NEKO_VIDEO":
|
|
||||||
settings.VideoPipeline = val
|
|
||||||
case "NEKO_OPUS":
|
|
||||||
if ok, _ := strconv.ParseBool(val); ok {
|
|
||||||
settings.AudioCodec = "OPUS"
|
|
||||||
}
|
|
||||||
case "NEKO_G722":
|
|
||||||
if ok, _ := strconv.ParseBool(val); ok {
|
|
||||||
settings.AudioCodec = "G722"
|
|
||||||
}
|
|
||||||
case "NEKO_PCMU":
|
|
||||||
if ok, _ := strconv.ParseBool(val); ok {
|
|
||||||
settings.AudioCodec = "PCMU"
|
|
||||||
}
|
|
||||||
case "NEKO_PCMA":
|
|
||||||
if ok, _ := strconv.ParseBool(val); ok {
|
|
||||||
settings.AudioCodec = "PCMA"
|
|
||||||
}
|
|
||||||
case "NEKO_AUDIO_BITRATE":
|
|
||||||
settings.AudioBitrate, err = strconv.Atoi(val)
|
|
||||||
case "NEKO_AUDIO":
|
|
||||||
settings.AudioPipeline = val
|
|
||||||
default:
|
|
||||||
if in, _ := utils.ArrayIn(key, blacklistedEnvs); !in {
|
|
||||||
settings.Envs[key] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type RoomStats struct {
|
type RoomStats struct {
|
||||||
Connections uint32 `json:"connections"`
|
Connections uint32 `json:"connections"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/m1k1o/neko-rooms/internal/config"
|
||||||
|
"github.com/m1k1o/neko-rooms/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
// m1k1o/neko v2 envs API
|
||||||
|
//
|
||||||
|
|
||||||
|
var blacklistedEnvsV2 = []string{
|
||||||
|
// ignore bunch of default envs
|
||||||
|
"DEBIAN_FRONTEND",
|
||||||
|
"PULSE_SERVER",
|
||||||
|
"XDG_RUNTIME_DIR",
|
||||||
|
"DISPLAY",
|
||||||
|
"USER",
|
||||||
|
"PATH",
|
||||||
|
|
||||||
|
// ignore bunch of envs managed by neko-rooms
|
||||||
|
"NEKO_BIND",
|
||||||
|
"NEKO_EPR",
|
||||||
|
"NEKO_UDPMUX",
|
||||||
|
"NEKO_TCPMUX",
|
||||||
|
"NEKO_NAT1TO1",
|
||||||
|
"NEKO_ICELITE",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings *RoomSettings) toEnvV2(config *config.Room, ports PortSettings) []string {
|
||||||
|
env := []string{
|
||||||
|
fmt.Sprintf("NEKO_BIND=:%d", ports.FrontendPort),
|
||||||
|
"NEKO_ICELITE=true",
|
||||||
|
|
||||||
|
// from settings
|
||||||
|
fmt.Sprintf("NEKO_PASSWORD=%s", settings.UserPass),
|
||||||
|
fmt.Sprintf("NEKO_PASSWORD_ADMIN=%s", settings.AdminPass),
|
||||||
|
fmt.Sprintf("NEKO_SCREEN=%s", settings.Screen),
|
||||||
|
fmt.Sprintf("NEKO_MAX_FPS=%d", settings.VideoMaxFPS),
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Mux {
|
||||||
|
env = append(env,
|
||||||
|
fmt.Sprintf("NEKO_UDPMUX=%d", ports.EprMin),
|
||||||
|
fmt.Sprintf("NEKO_TCPMUX=%d", ports.EprMin),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
env = append(env,
|
||||||
|
fmt.Sprintf("NEKO_EPR=%d-%d", ports.EprMin, ports.EprMax),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional nat mapping
|
||||||
|
if len(config.NAT1To1IPs) > 0 {
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_NAT1TO1=%s", strings.Join(config.NAT1To1IPs, ",")))
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.ControlProtection {
|
||||||
|
env = append(env, "NEKO_CONTROL_PROTECTION=true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.ImplicitControl {
|
||||||
|
env = append(env, "NEKO_IMPLICIT_CONTROL=true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.VideoCodec != "VP8" { // VP8 is default
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_VIDEO_CODEC=%s", strings.ToLower(settings.VideoCodec)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.VideoBitrate != 0 {
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_VIDEO_BITRATE=%d", settings.VideoBitrate))
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.VideoPipeline != "" {
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_VIDEO=%s", settings.VideoPipeline))
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.AudioCodec != "OPUS" { // OPUS is default
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_AUDIO_CODEC=%s", strings.ToLower(settings.AudioCodec)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.AudioBitrate != 0 {
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_AUDIO_BITRATE=%d", settings.AudioBitrate))
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.AudioPipeline != "" {
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_AUDIO=%s", settings.AudioPipeline))
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.BroadcastPipeline != "" {
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_BROADCAST_PIPELINE=%s", settings.BroadcastPipeline))
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range settings.Envs {
|
||||||
|
if in, _ := utils.ArrayIn(key, blacklistedEnvsV2); !in {
|
||||||
|
env = append(env, fmt.Sprintf("%s=%s", key, val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings *RoomSettings) fromEnvV2(envs []string) error {
|
||||||
|
settings.Envs = map[string]string{}
|
||||||
|
settings.VideoCodec = "VP8" // default
|
||||||
|
settings.AudioCodec = "OPUS" // default
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _, env := range envs {
|
||||||
|
r := strings.SplitN(env, "=", 2)
|
||||||
|
key, val := r[0], r[1]
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "NEKO_PASSWORD":
|
||||||
|
settings.UserPass = val
|
||||||
|
case "NEKO_PASSWORD_ADMIN":
|
||||||
|
settings.AdminPass = val
|
||||||
|
case "NEKO_CONTROL_PROTECTION":
|
||||||
|
if ok, _ := strconv.ParseBool(val); ok {
|
||||||
|
settings.ControlProtection = true
|
||||||
|
}
|
||||||
|
case "NEKO_IMPLICIT_CONTROL":
|
||||||
|
if ok, _ := strconv.ParseBool(val); ok {
|
||||||
|
settings.ImplicitControl = true
|
||||||
|
}
|
||||||
|
case "NEKO_SCREEN":
|
||||||
|
settings.Screen = val
|
||||||
|
case "NEKO_MAX_FPS":
|
||||||
|
settings.VideoMaxFPS, err = strconv.Atoi(val)
|
||||||
|
case "NEKO_BROADCAST_PIPELINE":
|
||||||
|
settings.BroadcastPipeline = val
|
||||||
|
case "NEKO_VIDEO_CODEC":
|
||||||
|
settings.VideoCodec = strings.ToUpper(val)
|
||||||
|
case "NEKO_VIDEO_BITRATE":
|
||||||
|
settings.VideoBitrate, err = strconv.Atoi(val)
|
||||||
|
case "NEKO_VIDEO":
|
||||||
|
settings.VideoPipeline = val
|
||||||
|
case "NEKO_AUDIO_CODEC":
|
||||||
|
settings.VideoCodec = strings.ToUpper(val)
|
||||||
|
case "NEKO_AUDIO_BITRATE":
|
||||||
|
settings.AudioBitrate, err = strconv.Atoi(val)
|
||||||
|
case "NEKO_AUDIO":
|
||||||
|
settings.AudioPipeline = val
|
||||||
|
default:
|
||||||
|
if in, _ := utils.ArrayIn(key, blacklistedEnvsV2); !in {
|
||||||
|
settings.Envs[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/m1k1o/neko-rooms/internal/config"
|
||||||
|
"github.com/m1k1o/neko-rooms/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
// demodesk/neko v3 envs API
|
||||||
|
//
|
||||||
|
|
||||||
|
var blacklistedEnvsV3 = []string{
|
||||||
|
// ignore bunch of default envs
|
||||||
|
"DEBIAN_FRONTEND",
|
||||||
|
"PULSE_SERVER",
|
||||||
|
"XDG_RUNTIME_DIR",
|
||||||
|
"DISPLAY",
|
||||||
|
"USER",
|
||||||
|
"PATH",
|
||||||
|
|
||||||
|
// ignore bunch of envs managed by neko
|
||||||
|
"NEKO_PLUGINS_ENABLED",
|
||||||
|
"NEKO_PLUGINS_DIR",
|
||||||
|
|
||||||
|
// ignore bunch of envs managed by neko-rooms
|
||||||
|
"NEKO_SERVER_BIND",
|
||||||
|
"NEKO_SESSION_API_TOKEN",
|
||||||
|
"NEKO_MEMBER_PROVIDER",
|
||||||
|
"NEKO_WEBRTC_EPR",
|
||||||
|
"NEKO_WEBRTC_UDPMUX",
|
||||||
|
"NEKO_WEBRTC_TCPMUX",
|
||||||
|
"NEKO_WEBRTC_NAT1TO1",
|
||||||
|
"NEKO_WEBRTC_ICELITE",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings *RoomSettings) toEnvV3(config *config.Room, ports PortSettings) []string {
|
||||||
|
env := []string{
|
||||||
|
fmt.Sprintf("NEKO_SERVER_BIND=:%d", ports.FrontendPort),
|
||||||
|
"NEKO_WEBRTC_ICELITE=true",
|
||||||
|
|
||||||
|
// from settings
|
||||||
|
"NEKO_MEMBER_PROVIDER=multiuser",
|
||||||
|
fmt.Sprintf("NEKO_MEMBER_MULTIUSER_USER_PASSWORD=%s", settings.UserPass),
|
||||||
|
fmt.Sprintf("NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD=%s", settings.AdminPass),
|
||||||
|
fmt.Sprintf("NEKO_SESSION_API_TOKEN=%s", settings.AdminPass), // TODO: should be random and saved somewhere
|
||||||
|
fmt.Sprintf("NEKO_DESKTOP_SCREEN=%s", settings.Screen),
|
||||||
|
//fmt.Sprintf("NEKO_MAX_FPS=%d", settings.VideoMaxFPS), // TODO: not supported yet
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Mux {
|
||||||
|
env = append(env,
|
||||||
|
fmt.Sprintf("NEKO_WEBRTC_UDPMUX=%d", ports.EprMin),
|
||||||
|
fmt.Sprintf("NEKO_WEBRTC_TCPMUX=%d", ports.EprMin),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
env = append(env,
|
||||||
|
fmt.Sprintf("NEKO_WEBRTC_EPR=%d-%d", ports.EprMin, ports.EprMax),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional nat mapping
|
||||||
|
if len(config.NAT1To1IPs) > 0 {
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_WEBRTC_NAT1TO1=%s", strings.Join(config.NAT1To1IPs, ",")))
|
||||||
|
}
|
||||||
|
|
||||||
|
//if settings.ControlProtection {
|
||||||
|
// env = append(env, "NEKO_CONTROL_PROTECTION=true") // TODO: not supported yet
|
||||||
|
//}
|
||||||
|
|
||||||
|
// implicit control - enabled by default
|
||||||
|
if !settings.ImplicitControl {
|
||||||
|
env = append(env, "NEKO_SESSION_IMPLICIT_HOSTING=false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.VideoCodec != "VP8" { // VP8 is default
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_CAPTURE_VIDEO_CODEC=%s", strings.ToLower(settings.VideoCodec)))
|
||||||
|
}
|
||||||
|
|
||||||
|
//if settings.VideoBitrate != 0 {
|
||||||
|
// env = append(env, fmt.Sprintf("NEKO_VIDEO_BITRATE=%d", settings.VideoBitrate)) // TODO: not supported yet
|
||||||
|
//}
|
||||||
|
|
||||||
|
if settings.VideoPipeline != "" {
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_CAPTURE_VIDEO_PIPELINES=%s", settings.VideoPipeline)) // TOOD: multiple pipelines, as JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.AudioCodec != "OPUS" { // OPUS is default
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_CAPTURE_AUDIO_CODEC=%s", strings.ToLower(settings.AudioCodec)))
|
||||||
|
}
|
||||||
|
|
||||||
|
//if settings.AudioBitrate != 0 {
|
||||||
|
// env = append(env, fmt.Sprintf("NEKO_AUDIO_BITRATE=%d", settings.AudioBitrate)) // TODO: not supported yet
|
||||||
|
//}
|
||||||
|
|
||||||
|
if settings.AudioPipeline != "" {
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_CAPTURE_AUDIO_PIPELINE=%s", settings.AudioPipeline))
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.BroadcastPipeline != "" {
|
||||||
|
env = append(env, fmt.Sprintf("NEKO_CAPTURE_BROADCAST_PIPELINE=%s", settings.BroadcastPipeline))
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range settings.Envs {
|
||||||
|
if in, _ := utils.ArrayIn(key, blacklistedEnvsV3); !in {
|
||||||
|
env = append(env, fmt.Sprintf("%s=%s", key, val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings *RoomSettings) fromEnvV3(envs []string) error {
|
||||||
|
settings.Envs = map[string]string{}
|
||||||
|
// enabled implicit control by default
|
||||||
|
settings.ImplicitControl = true
|
||||||
|
settings.VideoCodec = "VP8" // default
|
||||||
|
settings.AudioCodec = "OPUS" // default
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _, env := range envs {
|
||||||
|
r := strings.SplitN(env, "=", 2)
|
||||||
|
key, val := r[0], r[1]
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "NEKO_MEMBER_MULTIUSER_USER_PASSWORD":
|
||||||
|
settings.UserPass = val
|
||||||
|
case "NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD":
|
||||||
|
settings.AdminPass = val
|
||||||
|
//case "NEKO_CONTROL_PROTECTION": // TODO: not supported yet
|
||||||
|
// if ok, _ := strconv.ParseBool(val); ok {
|
||||||
|
// settings.ControlProtection = true
|
||||||
|
// }
|
||||||
|
case "NEKO_SESSION_IMPLICIT_HOSTING":
|
||||||
|
if ok, _ := strconv.ParseBool(val); !ok {
|
||||||
|
settings.ImplicitControl = false
|
||||||
|
}
|
||||||
|
case "NEKO_DESKTOP_SCREEN":
|
||||||
|
settings.Screen = val
|
||||||
|
//case "NEKO_MAX_FPS": // TODO: not supported yet
|
||||||
|
// settings.VideoMaxFPS, err = strconv.Atoi(val)
|
||||||
|
case "NEKO_CAPTURE_BROADCAST_PIPELINE":
|
||||||
|
settings.BroadcastPipeline = val
|
||||||
|
case "NEKO_CAPTURE_VIDEO_CODEC":
|
||||||
|
settings.VideoCodec = strings.ToUpper(val)
|
||||||
|
//case "NEKO_VIDEO_BITRATE": // TODO: not supported yet
|
||||||
|
// settings.VideoBitrate, err = strconv.Atoi(val)
|
||||||
|
case "NEKO_CAPTURE_VIDEO_PIPELINES": // TOOD: multiple pipelines, as JSON
|
||||||
|
settings.VideoPipeline = val
|
||||||
|
case "NEKO_CAPTURE_AUDIO_CODEC":
|
||||||
|
settings.AudioCodec = strings.ToUpper(val)
|
||||||
|
//case "NEKO_AUDIO_BITRATE": // TODO: not supported yet
|
||||||
|
// settings.AudioBitrate, err = strconv.Atoi(val)
|
||||||
|
case "NEKO_CAPTURE_AUDIO_PIPELINE":
|
||||||
|
settings.AudioPipeline = val
|
||||||
|
default:
|
||||||
|
if in, _ := utils.ArrayIn(key, blacklistedEnvsV3); !in {
|
||||||
|
settings.Envs[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user