mirror of
https://github.com/m1k1o/neko-rooms.git
synced 2026-11-02 22:51:34 -06:00
add container creation.
This commit is contained in:
@@ -10,9 +10,9 @@ import (
|
||||
)
|
||||
|
||||
type ApiManagerCtx struct {
|
||||
logger zerolog.Logger
|
||||
rooms types.RoomManager
|
||||
conf *config.API
|
||||
logger zerolog.Logger
|
||||
rooms types.RoomManager
|
||||
conf *config.API
|
||||
}
|
||||
|
||||
func New(roomManager types.RoomManager, conf *config.API) *ApiManagerCtx {
|
||||
|
||||
@@ -22,7 +22,7 @@ func (manager *ApiManagerCtx) roomsList(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
func (manager *ApiManagerCtx) roomCreate(w http.ResponseWriter, r *http.Request) {
|
||||
request := types.RoomSettings{}
|
||||
if err := json.NewDecoder(r.Body).Decode(request); err != nil {
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func (manager *ApiManagerCtx) roomUpdate(w http.ResponseWriter, r *http.Request)
|
||||
roomId := chi.URLParam(r, "roomId")
|
||||
|
||||
request := types.RoomSettings{}
|
||||
if err := json.NewDecoder(r.Body).Decode(request); err != nil {
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,17 +1,97 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"m1k1o/neko_rooms/internal/utils"
|
||||
)
|
||||
|
||||
type Room struct {
|
||||
NAT1To1IPs []string
|
||||
EphemeralMin uint16
|
||||
EphemeralMax uint16
|
||||
|
||||
TraefikDomain string
|
||||
TraefikEntrypoint string
|
||||
TraefikCertresolver string
|
||||
TraefikNetwork string
|
||||
}
|
||||
|
||||
func (Room) Init(cmd *cobra.Command) error {
|
||||
cmd.PersistentFlags().String("epr", "59000-59999", "limits the pool of ephemeral ports that ICE UDP connections can allocate from")
|
||||
if err := viper.BindPFlag("epr", cmd.PersistentFlags().Lookup("epr")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringSlice("nat1to1", []string{}, "sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP address is used")
|
||||
if err := viper.BindPFlag("nat1to1", cmd.PersistentFlags().Lookup("nat1to1")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("traefik_domain", "neko.lan", "traefik: domain on which will be container hosted")
|
||||
if err := viper.BindPFlag("traefik_domain", cmd.PersistentFlags().Lookup("traefik_domain")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("traefik_entrypoint", "web-secure", "traefik: router entrypoint")
|
||||
if err := viper.BindPFlag("traefik_entrypoint", cmd.PersistentFlags().Lookup("traefik_entrypoint")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("traefik_certresolver", "lets-encrypt", "traefik: certificate resolver for router")
|
||||
if err := viper.BindPFlag("traefik_certresolver", cmd.PersistentFlags().Lookup("traefik_certresolver")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("traefik_network", "traefik", "traefik: docker network name")
|
||||
if err := viper.BindPFlag("traefik_network", cmd.PersistentFlags().Lookup("traefik_network")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Room) Set() {
|
||||
s.NAT1To1IPs = viper.GetStringSlice("nat1to1")
|
||||
|
||||
// if not specified, get public
|
||||
if len(s.NAT1To1IPs) == 0 {
|
||||
ip, err := utils.GetIP()
|
||||
if err == nil {
|
||||
s.NAT1To1IPs = append(s.NAT1To1IPs, ip)
|
||||
}
|
||||
}
|
||||
|
||||
min := uint16(59000)
|
||||
max := uint16(59100)
|
||||
epr := viper.GetString("epr")
|
||||
ports := strings.SplitN(epr, "-", -1)
|
||||
if len(ports) > 1 {
|
||||
start, err := strconv.ParseUint(ports[0], 10, 16)
|
||||
if err == nil {
|
||||
min = uint16(start)
|
||||
}
|
||||
|
||||
end, err := strconv.ParseUint(ports[1], 10, 16)
|
||||
if err == nil {
|
||||
max = uint16(end)
|
||||
}
|
||||
}
|
||||
|
||||
if min > max {
|
||||
s.EphemeralMin = max
|
||||
s.EphemeralMax = min
|
||||
} else {
|
||||
s.EphemeralMin = min
|
||||
s.EphemeralMax = max
|
||||
}
|
||||
|
||||
s.TraefikDomain = viper.GetString("traefik_domain")
|
||||
s.TraefikEntrypoint = viper.GetString("traefik_entrypoint")
|
||||
s.TraefikCertresolver = viper.GetString("traefik_certresolver")
|
||||
s.TraefikNetwork = viper.GetString("traefik_network")
|
||||
}
|
||||
|
||||
+130
-6
@@ -2,14 +2,25 @@ package room
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
network "github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
|
||||
"m1k1o/neko_rooms/internal/types"
|
||||
"m1k1o/neko_rooms/internal/config"
|
||||
"m1k1o/neko_rooms/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
nekoImage = "m1k1o/neko:latest"
|
||||
containerPrefix = "neko-room-"
|
||||
frontendPort = 8080
|
||||
)
|
||||
|
||||
func New(config *config.Room) *RoomManagerCtx {
|
||||
@@ -36,7 +47,9 @@ type RoomManagerCtx struct {
|
||||
}
|
||||
|
||||
func (manager *RoomManagerCtx) List() (*[]types.RoomData, error) {
|
||||
containers, err := manager.client.ContainerList(context.Background(), dockerTypes.ContainerListOptions{})
|
||||
containers, err := manager.client.ContainerList(context.Background(), dockerTypes.ContainerListOptions{
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -52,15 +65,126 @@ func (manager *RoomManagerCtx) List() (*[]types.RoomData, error) {
|
||||
}
|
||||
|
||||
func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (*types.RoomData, error) {
|
||||
// configs
|
||||
pathName := "foobar"
|
||||
eprStart := uint(52135)
|
||||
eprEnd := uint(52145)
|
||||
|
||||
portBindings := nat.PortMap{}
|
||||
exposedPorts := nat.PortSet{
|
||||
nat.Port(fmt.Sprintf("%d/udp", frontendPort)): struct{}{},
|
||||
}
|
||||
|
||||
for port := eprStart; port <= eprEnd; port++ {
|
||||
portKey := nat.Port(fmt.Sprintf("%d/udp", port))
|
||||
|
||||
portBindings[portKey] = []nat.PortBinding{
|
||||
{
|
||||
HostIP: "0.0.0.0",
|
||||
HostPort: fmt.Sprintf("%d", port),
|
||||
},
|
||||
}
|
||||
|
||||
exposedPorts[portKey] = struct{}{}
|
||||
}
|
||||
|
||||
containerName := containerPrefix + pathName
|
||||
|
||||
labels := map[string]string{
|
||||
// Set internal labels
|
||||
"m1k1o.neko_rooms": "true",
|
||||
"m1k1o.neko_rooms.epr": fmt.Sprintf("%d-%d", eprStart, eprEnd),
|
||||
|
||||
// Set traefik labels
|
||||
"traefik.enable": "true",
|
||||
"traefik.http.services." + containerName + "-frontend.loadbalancer.server.port": fmt.Sprintf("%d", frontendPort),
|
||||
"traefik.http.routers." + containerName + ".entrypoints": manager.config.TraefikEntrypoint,
|
||||
"traefik.http.routers." + containerName + ".rule": "Host(`" + manager.config.TraefikDomain + "`) && PathPrefix(`/" + pathName + "`)",
|
||||
"traefik.http.middlewares." + containerName + "-rdr.redirectregex.regex": "/" + pathName + "$$",
|
||||
"traefik.http.middlewares." + containerName + "-rdr.redirectregex.replacement": "/" + pathName + "/",
|
||||
"traefik.http.middlewares." + containerName + "-prf.stripprefix.prefixes": "/" + pathName + "/",
|
||||
"traefik.http.routers." + containerName + ".middlewares": containerName + "-rdr," + containerName + "-prf",
|
||||
}
|
||||
|
||||
// optional HTTPS
|
||||
if manager.config.TraefikCertresolver != "" {
|
||||
labels["traefik.http.routers."+containerName+".tls"] = "true"
|
||||
labels["traefik.http.routers."+containerName+".tls.certresolver"] = manager.config.TraefikCertresolver
|
||||
}
|
||||
|
||||
config := &container.Config{
|
||||
// Hostname
|
||||
Hostname: containerName,
|
||||
// Domainname
|
||||
Domainname: containerName,
|
||||
// List of exposed ports
|
||||
ExposedPorts: exposedPorts,
|
||||
// List of environment variable to set in the container
|
||||
Env: append([]string{
|
||||
fmt.Sprintf("NEKO_BIND=%d", frontendPort),
|
||||
}, settings.Env(eprStart, eprEnd, manager.config.NAT1To1IPs)...),
|
||||
// Name of the image as it was passed by the operator (e.g. could be symbolic)
|
||||
Image: nekoImage,
|
||||
// List of labels set to this container
|
||||
Labels: labels,
|
||||
}
|
||||
|
||||
hostConfig := &container.HostConfig{
|
||||
// Port mapping between the exposed port (container) and the host
|
||||
PortBindings: portBindings,
|
||||
// Configuration of the logs for this container
|
||||
LogConfig: container.LogConfig{
|
||||
Type: "json-file",
|
||||
Config: map[string]string{},
|
||||
},
|
||||
// Restart policy to be used for the container
|
||||
RestartPolicy: container.RestartPolicy{
|
||||
Name: "always",
|
||||
},
|
||||
// List of kernel capabilities to add to the container
|
||||
CapAdd: strslice.StrSlice{
|
||||
"SYS_ADMIN",
|
||||
},
|
||||
// Total shm memory usage
|
||||
ShmSize: 2 * 10e9,
|
||||
}
|
||||
|
||||
networkingConfig := &network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
manager.config.TraefikNetwork: &network.EndpointSettings{},
|
||||
},
|
||||
}
|
||||
|
||||
// Creating the actual container
|
||||
cont, err := manager.client.ContainerCreate(
|
||||
context.Background(),
|
||||
config,
|
||||
hostConfig,
|
||||
networkingConfig,
|
||||
nil,
|
||||
containerName,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Run the actual container
|
||||
err = manager.client.ContainerStart(context.Background(), cont.ID, dockerTypes.ContainerStartOptions{})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.RoomData{
|
||||
ID: "foo",
|
||||
ID: cont.ID,
|
||||
RoomSettings: settings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (manager *RoomManagerCtx) Get(id string) (*types.RoomData, error) {
|
||||
return &types.RoomData{
|
||||
ID: "foo",
|
||||
ID: "foo",
|
||||
RoomSettings: types.RoomSettings{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
+48
-1
@@ -1,5 +1,10 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RoomSettings struct {
|
||||
MaxConnections uint `json:"max_connections"`
|
||||
UserPass string `json:"user_pass"`
|
||||
@@ -18,8 +23,50 @@ type RoomSettings struct {
|
||||
AudioPipeline string `json:"audio_pipeline"`
|
||||
}
|
||||
|
||||
func (settings *RoomSettings) Env(epr_start uint, epr_end uint, nat1to1 []string) []string {
|
||||
env := []string{
|
||||
fmt.Sprintf("NEKO_EPR=%d-%d", epr_start, epr_end),
|
||||
fmt.Sprintf("NEKO_NAT1TO1=%s", strings.Join(nat1to1, ",")),
|
||||
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 settings.BroadcastPipeline != "" {
|
||||
env = append(env, fmt.Sprintf("NEKO_BROADCAST_PIPELINE=%s", settings.BroadcastPipeline))
|
||||
}
|
||||
|
||||
if settings.VideoCodec != "" {
|
||||
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 != "" {
|
||||
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))
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
type RoomData struct {
|
||||
ID string `json:"id"`
|
||||
ID string `json:"id"`
|
||||
|
||||
RoomSettings
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user