mirror of
https://github.com/m1k1o/neko-rooms.git
synced 2026-11-02 22:51:34 -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:
|
||||
type: object
|
||||
properties:
|
||||
api_version:
|
||||
type: number
|
||||
description: if not set, version is taken from neko_image
|
||||
name:
|
||||
type: string
|
||||
example: foobar
|
||||
|
||||
@@ -394,6 +394,12 @@ export interface RoomResources {
|
||||
* @interface RoomSettings
|
||||
*/
|
||||
export interface RoomSettings {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof RoomSettings
|
||||
*/
|
||||
'api_version'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
||||
@@ -20,6 +20,7 @@ type Server struct {
|
||||
Key string
|
||||
Bind string
|
||||
Proxy bool
|
||||
CORS bool
|
||||
PProf bool
|
||||
|
||||
Admin Admin
|
||||
@@ -46,6 +47,11 @@ func (Server) Init(cmd *cobra.Command) error {
|
||||
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")
|
||||
if err := viper.BindPFlag("pprof", cmd.PersistentFlags().Lookup("pprof")); err != nil {
|
||||
return err
|
||||
@@ -86,6 +92,7 @@ func (s *Server) Set() {
|
||||
s.Key = viper.GetString("key")
|
||||
s.Bind = viper.GetString("bind")
|
||||
s.Proxy = viper.GetBool("proxy")
|
||||
s.CORS = viper.GetBool("cors")
|
||||
s.PProf = viper.GetBool("pprof")
|
||||
|
||||
s.Admin.Static = viper.GetString("admin.static")
|
||||
|
||||
+25
-6
@@ -16,7 +16,9 @@ type RoomLabels struct {
|
||||
URL string
|
||||
Mux bool
|
||||
Epr EprPorts
|
||||
|
||||
NekoImage string
|
||||
ApiVersion int
|
||||
|
||||
BrowserPolicy *BrowserPolicyLabels
|
||||
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")
|
||||
}
|
||||
|
||||
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 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
|
||||
if val, ok := labels["m1k1o.neko_rooms.browser_policy"]; ok && val == "true" {
|
||||
policyType, ok := labels["m1k1o.neko_rooms.browser_policy.type"]
|
||||
@@ -117,10 +129,12 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
|
||||
return &RoomLabels{
|
||||
Name: name,
|
||||
URL: url,
|
||||
NekoImage: nekoImage,
|
||||
Mux: mux,
|
||||
Epr: epr,
|
||||
|
||||
NekoImage: nekoImage,
|
||||
ApiVersion: apiVersion,
|
||||
|
||||
BrowserPolicy: browserPolicy,
|
||||
UserDefined: userDefined,
|
||||
}, nil
|
||||
@@ -134,6 +148,11 @@ func (manager *RoomManagerCtx) serializeLabels(labels RoomLabels) map[string]str
|
||||
"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 {
|
||||
labelsMap["m1k1o.neko_rooms.mux"] = fmt.Sprintf("%d", labels.Epr.Min)
|
||||
} else {
|
||||
|
||||
+116
-5
@@ -10,7 +10,9 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/opts"
|
||||
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)
|
||||
|
||||
// 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.
|
||||
roomName := settings.Name
|
||||
if roomName == "" {
|
||||
@@ -318,7 +354,9 @@ func (manager *RoomManagerCtx) Create(ctx context.Context, settings types.RoomSe
|
||||
Name: roomName,
|
||||
Mux: manager.config.Mux,
|
||||
Epr: epr,
|
||||
|
||||
NekoImage: settings.NekoImage,
|
||||
ApiVersion: settings.ApiVersion,
|
||||
|
||||
BrowserPolicy: browserPolicyLabels,
|
||||
UserDefined: settings.Labels,
|
||||
@@ -394,11 +432,16 @@ func (manager *RoomManagerCtx) Create(ctx context.Context, settings types.RoomSe
|
||||
// Set environment variables
|
||||
//
|
||||
|
||||
env := settings.ToEnv(manager.config, types.PortSettings{
|
||||
env, err := settings.ToEnv(
|
||||
manager.config,
|
||||
types.PortSettings{
|
||||
FrontendPort: frontendPort,
|
||||
EprMin: epr.Min,
|
||||
EprMax: epr.Max,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//
|
||||
// Set browser policies
|
||||
@@ -830,7 +873,7 @@ func (manager *RoomManagerCtx) GetSettings(ctx context.Context, id string) (*typ
|
||||
settings.MaxConnections = 0
|
||||
}
|
||||
|
||||
err = settings.FromEnv(container.Config.Env)
|
||||
err = settings.FromEnv(labels.ApiVersion, container.Config.Env)
|
||||
return &settings, err
|
||||
}
|
||||
|
||||
@@ -840,12 +883,20 @@ func (manager *RoomManagerCtx) GetStats(ctx context.Context, id string) (*types.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
settings := types.RoomSettings{}
|
||||
err = settings.FromEnv(container.Config.Env)
|
||||
labels, err := manager.extractLabels(container.Config.Labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
settings := types.RoomSettings{}
|
||||
err = settings.FromEnv(labels.ApiVersion, container.Config.Env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var stats types.RoomStats
|
||||
switch labels.ApiVersion {
|
||||
case 2:
|
||||
output, err := manager.containerExec(ctx, id, []string{
|
||||
"wget", "-q", "-O-", "http://127.0.0.1:8080/stats?pwd=" + url.QueryEscape(settings.AdminPass),
|
||||
})
|
||||
@@ -853,10 +904,70 @@ func (manager *RoomManagerCtx) GetStats(ctx context.Context, id string) (*types.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var stats types.RoomStats
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
// Basic CORS
|
||||
if config.CORS {
|
||||
// for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing
|
||||
router.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowOriginFunc: func(r *http.Request, origin string) bool {
|
||||
return true
|
||||
},
|
||||
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
|
||||
if config.PProf {
|
||||
|
||||
+24
-170
@@ -3,31 +3,11 @@ package types
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
Connections uint16 `json:"connections"`
|
||||
NekoImages []string `json:"neko_images"`
|
||||
@@ -73,6 +53,8 @@ type RoomResources struct {
|
||||
}
|
||||
|
||||
type RoomSettings struct {
|
||||
ApiVersion int `json:"api_version"`
|
||||
|
||||
Name string `json:"name"`
|
||||
NekoImage string `json:"neko_image"`
|
||||
MaxConnections uint16 `json:"max_connections"` // 0 when using mux
|
||||
@@ -106,161 +88,33 @@ type RoomSettings struct {
|
||||
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 {
|
||||
FrontendPort 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 {
|
||||
Connections uint32 `json:"connections"`
|
||||
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