846 lines
24 KiB
Go
846 lines
24 KiB
Go
package servers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"acooldomain.co/backend/auth"
|
|
"acooldomain.co/backend/models"
|
|
"github.com/buildkite/shellwords"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/image"
|
|
"github.com/docker/docker/api/types/mount"
|
|
"github.com/docker/docker/api/types/network"
|
|
"github.com/docker/docker/api/types/volume"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/go-connections/nat"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gorilla/websocket"
|
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
)
|
|
|
|
const FILE_BROWSER_IMAGE = "filebrowser/filebrowser:latest"
|
|
|
|
var DOMAIN string = ""
|
|
|
|
type Connection struct {
|
|
databaseConnection *mongo.Client
|
|
dockerClient *client.Client
|
|
}
|
|
|
|
type ContainerLabels struct {
|
|
OwnerId string `json:"user_id"`
|
|
ImageId string `json:"image_id"`
|
|
VolumeId string `json:"volume_id"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
type VolumeLabels struct {
|
|
OwnerId string `json:"user_id"`
|
|
ImageId string `json:"image_id"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
type ImageLabels struct {
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
var upgrader = websocket.Upgrader{
|
|
ReadBufferSize: 1024,
|
|
WriteBufferSize: 1024,
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
return true
|
|
},
|
|
}
|
|
|
|
func (con Connection) getServerInfo(volume volume.Volume) (*models.ServerInfo, error) {
|
|
var volumeLabels VolumeLabels
|
|
var serverData models.ServerData
|
|
|
|
con.databaseConnection.Database("Backend").Collection("Servers").FindOne(context.TODO(), bson.D{{Key: "Id", Value: volume.Name}}).Decode(&serverData)
|
|
jsonData, err := json.Marshal(volume.Labels)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = json.Unmarshal(jsonData, &volumeLabels)
|
|
if err != nil {
|
|
return nil, err
|
|
|
|
}
|
|
var imagePorts []models.Port
|
|
|
|
if len(serverData.DefaultPorts) == 0 {
|
|
|
|
imageList, err := con.dockerClient.ImageList(context.TODO(), image.ListOptions{Filters: filters.NewArgs(filters.Arg("reference", volumeLabels.ImageId), filters.Arg("label", "type=GAME"))})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(imageList) == 0 {
|
|
return nil, fmt.Errorf("ImageId %s does not exist", volumeLabels.ImageId)
|
|
}
|
|
imageSummery := imageList[0]
|
|
imageInspect, _, err := con.dockerClient.ImageInspectWithRaw(context.TODO(), imageSummery.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
imagePorts = make([]models.Port, len(imageInspect.Config.ExposedPorts))
|
|
i := 0
|
|
for imagePort := range imageInspect.Config.ExposedPorts {
|
|
imagePorts[i] = models.Port{Protocol: imagePort.Proto(), Number: imagePort.Int()}
|
|
i += 1
|
|
}
|
|
} else {
|
|
imagePorts = serverData.DefaultPorts
|
|
}
|
|
|
|
imageNameAndVersion := strings.Split(volumeLabels.ImageId, ":")
|
|
|
|
imageName := imageNameAndVersion[0]
|
|
imageVersion := imageNameAndVersion[1]
|
|
containers, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{
|
|
All: true,
|
|
Filters: filters.NewArgs(filters.Arg("label", "type=GAME"), filters.Arg("label", fmt.Sprintf("volume_id=%s", volume.Name))),
|
|
})
|
|
var state bool
|
|
var ports []models.Port
|
|
if err != nil || len(containers) == 0 {
|
|
state = false
|
|
ports = nil
|
|
} else {
|
|
container := containers[0]
|
|
state = container.State == "running"
|
|
ports = transformContainerPortsToModel(container.Ports)
|
|
}
|
|
|
|
serverInfo := models.ServerInfo{
|
|
Id: volume.Name,
|
|
Image: models.ImageInfo{
|
|
Name: imageName,
|
|
Version: imageVersion,
|
|
Ports: imagePorts,
|
|
},
|
|
OwnerId: volumeLabels.OwnerId,
|
|
On: state,
|
|
Ports: ports,
|
|
Nickname: serverData.Nickname,
|
|
DefaultCommand: serverData.DefaultCommand,
|
|
Domain: DOMAIN,
|
|
}
|
|
return &serverInfo, nil
|
|
|
|
}
|
|
|
|
func (con Connection) getServerInfoFromId(ServerId string) (*models.ServerInfo, error) {
|
|
volume, err := con.dockerClient.VolumeInspect(context.TODO(), ServerId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return con.getServerInfo(volume)
|
|
}
|
|
|
|
func transformContainerPortsToModel(ports []types.Port) []models.Port {
|
|
modelPorts := make([]models.Port, len(ports))
|
|
for index, port := range ports {
|
|
modelPorts[index] = models.Port{
|
|
Number: int(port.PublicPort),
|
|
Protocol: port.Type,
|
|
}
|
|
}
|
|
return modelPorts
|
|
}
|
|
|
|
func convertLabelsToMap(v any) (map[string]string, error) {
|
|
data, err := json.Marshal(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
x := map[string]string{}
|
|
json.Unmarshal(data, &x)
|
|
return x, nil
|
|
}
|
|
|
|
type CreateServerRequest struct {
|
|
ImageId string `json:"ImageId"`
|
|
DefaultPorts []models.Port `json:"DefaultPorts"`
|
|
DefaultCommand string `json:"DefaultCommand"`
|
|
Nickname string `json:"Nickname"`
|
|
}
|
|
|
|
func (con Connection) CreateServer(ctx *gin.Context) {
|
|
claims, exists := ctx.Get("claims")
|
|
if !exists {
|
|
ctx.AbortWithStatus(500)
|
|
return
|
|
}
|
|
var request CreateServerRequest
|
|
err := json.NewDecoder(ctx.Request.Body).Decode(&request)
|
|
if err != nil {
|
|
ctx.AbortWithError(400, err)
|
|
return
|
|
}
|
|
|
|
imageList, err := con.dockerClient.ImageList(context.TODO(), image.ListOptions{All: true, Filters: filters.NewArgs(filters.Arg("reference", request.ImageId), filters.Arg("label", "type=GAME"))})
|
|
if err != nil {
|
|
ctx.AbortWithError(400, err)
|
|
return
|
|
}
|
|
if len(imageList) == 0 {
|
|
ctx.AbortWithStatusJSON(404, "imageNotFound")
|
|
return
|
|
}
|
|
imageSummary := imageList[0]
|
|
labels, err := convertLabelsToMap(VolumeLabels{OwnerId: claims.(*auth.AuthClaims).Username, ImageId: imageSummary.RepoTags[0], Type: "GAME"})
|
|
if err != nil {
|
|
ctx.AbortWithError(400, err)
|
|
return
|
|
}
|
|
volumeResponse, err := con.dockerClient.VolumeCreate(context.TODO(), volume.CreateOptions{
|
|
Labels: labels,
|
|
})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
con.databaseConnection.Database("Backend").Collection("Servers").InsertOne(context.TODO(), models.ServerData{
|
|
Id: volumeResponse.Name,
|
|
OwnerId: claims.(*auth.AuthClaims).Username,
|
|
Image: imageSummary.RepoTags[0],
|
|
VolumeId: volumeResponse.Name,
|
|
DefaultPorts: request.DefaultPorts,
|
|
DefaultCommand: request.DefaultCommand,
|
|
Nickname: request.Nickname,
|
|
UserPermissions: make(map[string]models.Permission),
|
|
})
|
|
|
|
ctx.JSON(200, volumeResponse.Name)
|
|
}
|
|
|
|
type PortMappingRequest struct {
|
|
Source models.Port
|
|
Destination models.Port
|
|
}
|
|
|
|
type StartServerRequest struct {
|
|
Command string `json:"Command"`
|
|
Ports []PortMappingRequest `json:"Ports"`
|
|
}
|
|
|
|
func (con Connection) StartServer(ctx *gin.Context) {
|
|
serverId := ctx.Param("server_id")
|
|
claims, exists := ctx.Get("claims")
|
|
var request StartServerRequest
|
|
json.NewDecoder(ctx.Request.Body).Decode(&request)
|
|
|
|
if !exists {
|
|
ctx.AbortWithStatus(403)
|
|
return
|
|
}
|
|
|
|
serverInfo, err := con.getServerInfoFromId(serverId)
|
|
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
if serverInfo.On {
|
|
ctx.Status(200)
|
|
return
|
|
}
|
|
|
|
imageId := serverInfo.Image.Name + ":" + serverInfo.Image.Version
|
|
labels := ContainerLabels{
|
|
OwnerId: claims.(*auth.AuthClaims).Username,
|
|
ImageId: imageId,
|
|
VolumeId: serverInfo.Id,
|
|
Type: "GAME",
|
|
}
|
|
|
|
jsonString, err := json.Marshal(labels)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
jsonLabels := make(map[string]string)
|
|
json.Unmarshal(jsonString, &jsonLabels)
|
|
|
|
volumes := make(map[string]struct{})
|
|
|
|
image, _, err := con.dockerClient.ImageInspectWithRaw(context.TODO(), imageId)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
var portMapping nat.PortMap = make(nat.PortMap)
|
|
if len(request.Ports) == 0 {
|
|
for _, port := range serverInfo.Image.Ports {
|
|
dockerPort, err := nat.NewPort(port.Protocol, fmt.Sprint(port.Number))
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
portMapping[dockerPort] = []nat.PortBinding{{HostIP: "0.0.0.0"}}
|
|
}
|
|
} else {
|
|
for _, portCouple := range request.Ports {
|
|
portMapping[nat.Port(fmt.Sprintf("%d/%s", portCouple.Source.Number, portCouple.Source.Protocol))] = []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: fmt.Sprint(portCouple.Destination.Number)}}
|
|
}
|
|
}
|
|
command := request.Command
|
|
if command == "" {
|
|
command = serverInfo.DefaultCommand
|
|
}
|
|
|
|
words, err := shellwords.Split(command)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
if len(words) == 0 {
|
|
words = nil
|
|
}
|
|
|
|
portSet := make(nat.PortSet)
|
|
for port := range portMapping {
|
|
portSet[port] = struct{}{}
|
|
}
|
|
|
|
response, err := con.dockerClient.ContainerCreate(
|
|
context.TODO(),
|
|
&container.Config{
|
|
AttachStdin: true,
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
Tty: true,
|
|
OpenStdin: true,
|
|
StdinOnce: false,
|
|
Image: imageId,
|
|
Volumes: volumes,
|
|
Labels: jsonLabels,
|
|
Cmd: words,
|
|
ExposedPorts: portSet,
|
|
},
|
|
&container.HostConfig{
|
|
AutoRemove: true,
|
|
Mounts: []mount.Mount{{Source: serverInfo.Id, Target: image.Config.WorkingDir, Type: "volume"}},
|
|
PortBindings: portMapping,
|
|
ConsoleSize: [2]uint{1000, 1000},
|
|
},
|
|
&network.NetworkingConfig{},
|
|
&v1.Platform{},
|
|
"",
|
|
)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
if err := con.dockerClient.ContainerStart(ctx, response.ID, container.StartOptions{}); err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
UPNPPath, exists := os.LookupEnv("UPNP_PATH")
|
|
HostIP, hostIPexists := os.LookupEnv("HOST_IP")
|
|
|
|
if exists && hostIPexists {
|
|
time.Sleep(time.Millisecond * 100)
|
|
containerData, err := con.dockerClient.ContainerInspect(context.TODO(), response.ID)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
fo, err := os.OpenFile(UPNPPath, os.O_WRONLY, os.ModeAppend)
|
|
if err != nil {
|
|
ctx.AbortWithError(503, err)
|
|
con.dockerClient.ContainerStop(context.TODO(), containerData.ID, container.StopOptions{})
|
|
return
|
|
}
|
|
defer func() {
|
|
if err := fo.Close(); err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
for containerPort, portBindings := range containerData.NetworkSettings.Ports {
|
|
for _, hostPort := range portBindings {
|
|
number, proto := hostPort.HostPort, containerPort.Proto()
|
|
UPNPCommand := fmt.Sprintf("%s|%s|%s|%s\n", HostIP, number, number, strings.ToUpper(proto))
|
|
_, err := fo.Write([]byte(UPNPCommand))
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
log.Printf("Wrote command \"%s\" to %s", UPNPCommand, UPNPPath)
|
|
}
|
|
}
|
|
}
|
|
ctx.JSON(200, response.ID)
|
|
}
|
|
|
|
func (con Connection) GetServers(ctx *gin.Context) {
|
|
volumes, err := con.dockerClient.VolumeList(
|
|
context.TODO(),
|
|
volume.ListOptions{
|
|
Filters: filters.NewArgs(filters.Arg("label", "type=GAME")),
|
|
},
|
|
)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
}
|
|
var servers []models.ServerInfo = make([]models.ServerInfo, 0, len(volumes.Volumes))
|
|
for _, volume := range volumes.Volumes {
|
|
serverInfo, err := con.getServerInfo(*volume)
|
|
if err != nil {
|
|
log.Printf("failed to get server info: %s, %s", volume.Name, err)
|
|
continue
|
|
}
|
|
servers = append(servers, *serverInfo)
|
|
}
|
|
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
}
|
|
ctx.JSON(200, servers)
|
|
}
|
|
|
|
func (con Connection) StopServer(ctx *gin.Context) {
|
|
serverId := ctx.Param("server_id")
|
|
containersList, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{
|
|
Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId), filters.Arg("label", "type=GAME")),
|
|
})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
if len(containersList) == 0 {
|
|
ctx.Status(200)
|
|
return
|
|
}
|
|
|
|
for _, containerData := range containersList {
|
|
con.dockerClient.ContainerStop(context.TODO(), containerData.ID, container.StopOptions{})
|
|
}
|
|
ctx.Status(200)
|
|
}
|
|
|
|
func (con Connection) DeleteServer(ctx *gin.Context) {
|
|
serverId := ctx.Param("server_id")
|
|
containers, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{All: true, Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId))})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
for _, containerInstance := range containers {
|
|
err := con.dockerClient.ContainerRemove(context.TODO(), containerInstance.ID, container.RemoveOptions{Force: true})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
con.dockerClient.VolumeRemove(context.TODO(), serverId, false)
|
|
con.databaseConnection.Database("Backend").Collection("Servers").FindOneAndDelete(context.TODO(), bson.D{{Key: "Id", Value: serverId}})
|
|
|
|
ctx.JSON(200, "ok")
|
|
}
|
|
|
|
type RunCommandRequest struct {
|
|
Command string `json:"Command"`
|
|
}
|
|
|
|
func (con Connection) RunCommand(ctx *gin.Context) {
|
|
var request RunCommandRequest
|
|
err := json.NewDecoder(ctx.Request.Body).Decode(&request)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
}
|
|
|
|
serverId := ctx.Param("server_id")
|
|
log.Print("Writing command \"", request.Command, "\"")
|
|
containers, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId), filters.Arg("label", "type=GAME"))})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
for _, containerData := range containers {
|
|
hijacked, err := con.dockerClient.ContainerAttach(context.TODO(), containerData.ID, container.AttachOptions{Stream: true, Stdin: true})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
defer func() { hijacked.Close(); hijacked.CloseWrite() }()
|
|
number, err := hijacked.Conn.Write([]byte(request.Command + "\n"))
|
|
log.Print("Wrote ", number, " bytes")
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.JSON(200, "OK")
|
|
}
|
|
|
|
type Commands struct {
|
|
CommandType string `json:"CommandType"`
|
|
Arguments []any `json:"Arguments"`
|
|
}
|
|
|
|
func (con Connection) AttachServer(ctx *gin.Context) {
|
|
serverId := ctx.Param("server_id")
|
|
stop := false
|
|
var err error
|
|
|
|
websocketRead := make(chan Commands)
|
|
containerRead := make(chan string)
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
log.Printf("The latest error is %s", err)
|
|
}
|
|
close(websocketRead)
|
|
close(containerRead)
|
|
}()
|
|
|
|
containers, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId), filters.Arg("label", "type=GAME"))})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
if len(containers) == 0 {
|
|
ctx.AbortWithStatus(404)
|
|
return
|
|
}
|
|
hijacked, err := con.dockerClient.ContainerAttach(context.TODO(), containers[0].ID, container.AttachOptions{Stream: true, Stdin: true, Stdout: true, Stderr: true, Logs: true})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
defer hijacked.Close()
|
|
|
|
ws, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
defer ws.Close()
|
|
|
|
go func() {
|
|
data := make([]byte, 1024)
|
|
for {
|
|
if stop {
|
|
break
|
|
}
|
|
count, err := hijacked.Reader.Read(data)
|
|
if err != nil {
|
|
time.Sleep(500)
|
|
hijacked, err = con.dockerClient.ContainerAttach(context.TODO(), containers[0].ID, container.AttachOptions{Stream: true, Stdin: true, Stdout: true, Stderr: true, Logs: false})
|
|
if err != nil {
|
|
stop = true
|
|
break
|
|
}
|
|
}
|
|
if count > 0 {
|
|
containerRead <- string(data[:count])
|
|
}
|
|
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
var command Commands
|
|
for {
|
|
if stop {
|
|
break
|
|
}
|
|
|
|
err := ws.ReadJSON(&command)
|
|
if err != nil {
|
|
fmt.Printf("Failed to read from websocket: %s", err)
|
|
command.CommandType = "close"
|
|
websocketRead <- command
|
|
break
|
|
}
|
|
websocketRead <- command
|
|
}
|
|
}()
|
|
|
|
for {
|
|
if stop {
|
|
break
|
|
}
|
|
|
|
select {
|
|
case Command := <-websocketRead:
|
|
switch Command.CommandType {
|
|
case "insert":
|
|
_, err = hijacked.Conn.Write([]byte(Command.Arguments[0].(string)))
|
|
if err != nil {
|
|
log.Printf("Write to docker failed %s", errors.Unwrap(err))
|
|
|
|
stop = true
|
|
break
|
|
}
|
|
|
|
case "close":
|
|
stop = true
|
|
|
|
case "resize":
|
|
err2 := con.dockerClient.ContainerResize(context.TODO(), containers[0].ID, container.ResizeOptions{Height: Command.Arguments[1].(uint), Width: Command.Arguments[0].(uint)})
|
|
if err2 != nil {
|
|
log.Printf("Failed to resize container to %dx%d: %s", Command.Arguments[0].(uint), Command.Arguments[1].(uint), err)
|
|
}
|
|
|
|
hijacked, err2 = con.dockerClient.ContainerAttach(context.TODO(), containers[0].ID, container.AttachOptions{Stream: true, Stdin: true, Stdout: true, Stderr: true})
|
|
if err2 != nil {
|
|
log.Printf("Failed to reattach container %s", err)
|
|
}
|
|
|
|
}
|
|
case data := <-containerRead:
|
|
err := ws.WriteJSON(data)
|
|
if err != nil {
|
|
log.Printf("Write to socket failed %s", errors.Unwrap(err))
|
|
stop = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type UpdateServerRequest struct {
|
|
DefaultPorts []models.Port `json:"DefaultPorts"`
|
|
DefaultCommand string `json:"DefaultCommand"`
|
|
Nickname string `json:"Nickname"`
|
|
UserPermissions map[string][]models.Permission `json:"UserPermissions"`
|
|
}
|
|
|
|
func (con Connection) UpdateServer(ctx *gin.Context) {
|
|
serverId := ctx.Param("server_id")
|
|
var Request UpdateServerRequest
|
|
err := json.NewDecoder(ctx.Request.Body).Decode(&Request)
|
|
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
updateOperation := bson.M{}
|
|
if Request.DefaultCommand != "" {
|
|
updateOperation["DefaultCommand"] = Request.DefaultCommand
|
|
}
|
|
if Request.DefaultPorts != nil {
|
|
updateOperation["DefaultPorts"] = Request.DefaultPorts
|
|
}
|
|
if Request.Nickname != "" {
|
|
updateOperation["Nickname"] = Request.Nickname
|
|
}
|
|
|
|
_, err = con.databaseConnection.Database("Backend").Collection("Servers").UpdateOne(context.TODO(), bson.D{{Key: "Id", Value: serverId}}, bson.D{{Key: "$set", Value: updateOperation}})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
}
|
|
|
|
ctx.JSON(200, "OK")
|
|
|
|
}
|
|
|
|
func (con Connection) BrowseServer(ctx *gin.Context) {
|
|
serverID := ctx.Param("server_id")
|
|
claims, exists := ctx.Get("claims")
|
|
if !exists {
|
|
ctx.AbortWithStatus(403)
|
|
return
|
|
}
|
|
serverInfo, err := con.getServerInfoFromId(serverID)
|
|
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
}
|
|
labelId := serverInfo.Id[:12]
|
|
browserLabels := make(map[string]string)
|
|
browserLabels["traefik.enable"] = "true"
|
|
browserLabels[fmt.Sprintf("traefik.http.routers.%s.rule", labelId)] = fmt.Sprintf("Host(`%s.browsers.%s`)", labelId, DOMAIN)
|
|
browserLabels[fmt.Sprintf("traefik.http.routers.%s.entrypoints", labelId)] = "websecure"
|
|
browserLabels[fmt.Sprintf("traefik.http.routers.%s.middlewares", labelId)] = "games@docker"
|
|
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].main", labelId)] = fmt.Sprintf("%s.%s", "browsers", DOMAIN)
|
|
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].sans", labelId)] = fmt.Sprintf("*.%s.%s", "browsers", DOMAIN)
|
|
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", labelId)] = "myresolver"
|
|
|
|
containerLabels := ContainerLabels{
|
|
OwnerId: claims.(*auth.AuthClaims).Username,
|
|
ImageId: FILE_BROWSER_IMAGE,
|
|
VolumeId: serverInfo.Id,
|
|
Type: "FILE_BROWSER",
|
|
}
|
|
jsonLabels, err := json.Marshal(containerLabels)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
err = json.Unmarshal(jsonLabels, &browserLabels)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
command, err := shellwords.Split("--noauth -r /tmp/data")
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
imageRef := fmt.Sprintf("%s:%s", serverInfo.Image.Name, serverInfo.Image.Version)
|
|
images, err := con.dockerClient.ImageList(context.TODO(), image.ListOptions{Filters: filters.NewArgs(filters.Arg("label", "type=GAME"), filters.Arg("reference", imageRef))})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
if len(images) == 0 {
|
|
ctx.AbortWithError(500, fmt.Errorf("image %s no longer exists", imageRef))
|
|
return
|
|
}
|
|
|
|
browserInfo, err := con.getBrowserInfoFromServerId(serverInfo.Id)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
if browserInfo != nil {
|
|
ctx.JSON(200, browserInfo.Url)
|
|
return
|
|
}
|
|
|
|
ContainerResponse, err := con.dockerClient.ContainerCreate(
|
|
context.TODO(),
|
|
&container.Config{
|
|
Cmd: command,
|
|
Image: FILE_BROWSER_IMAGE,
|
|
Labels: browserLabels,
|
|
Tty: true,
|
|
},
|
|
&container.HostConfig{
|
|
Mounts: []mount.Mount{{Source: serverInfo.Id, Target: "/tmp/data", Type: "volume"}},
|
|
AutoRemove: true,
|
|
ConsoleSize: [2]uint{1000, 1000},
|
|
},
|
|
&network.NetworkingConfig{
|
|
EndpointsConfig: map[string]*network.EndpointSettings{"test": {
|
|
NetworkID: "exposed",
|
|
}},
|
|
},
|
|
&v1.Platform{},
|
|
"",
|
|
)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
err = con.dockerClient.ContainerStart(context.TODO(), ContainerResponse.ID, container.StartOptions{})
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
browserInfo, err = con.getBrowserInfoFromServerId(serverInfo.Id)
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
if browserInfo == nil {
|
|
ctx.AbortWithError(500, fmt.Errorf("failed to open browser for server %s", serverInfo.Id))
|
|
return
|
|
}
|
|
ctx.JSON(200, browserInfo.Url)
|
|
}
|
|
|
|
func (con Connection) GetServerUserPermissions(ctx *gin.Context) {
|
|
claims, exists := ctx.Get("claims")
|
|
if !exists {
|
|
ctx.AbortWithStatus(500)
|
|
return
|
|
}
|
|
|
|
server_id := ctx.Param("server_id")
|
|
if server_id == "" {
|
|
ctx.AbortWithStatus(500)
|
|
return
|
|
}
|
|
|
|
var serverData models.ServerData
|
|
con.databaseConnection.Database("Backend").Collection("Servers").FindOne(context.TODO(), bson.D{{Key: "Id", Value: server_id}}).Decode(&serverData)
|
|
ctx.JSON(200, serverData.UserPermissions[claims.(*auth.AuthClaims).Username])
|
|
}
|
|
|
|
type SetServerUserPermissionsRequest struct {
|
|
Username string
|
|
Permissions models.Permission
|
|
}
|
|
|
|
func (con Connection) SetServerUserPermissions(ctx *gin.Context) {
|
|
server_id := ctx.Param("server_id")
|
|
if server_id == "" {
|
|
ctx.AbortWithStatus(500)
|
|
return
|
|
}
|
|
var request SetServerUserPermissionsRequest
|
|
err := json.NewDecoder(ctx.Request.Body).Decode(&request)
|
|
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
_, err = con.databaseConnection.Database("Backend").Collection("Servers").UpdateOne(context.TODO(), bson.D{{Key: "Id", Value: server_id}}, bson.D{{Key: "$set", Value: bson.D{{Key: fmt.Sprintf("UserPermissions.%s", request.Username), Value: request.Permissions}}}})
|
|
|
|
if err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
ctx.JSON(200, "OK")
|
|
}
|
|
|
|
func LoadGroup(group *gin.RouterGroup, mongo_client *mongo.Client, config models.GlobalConfig) {
|
|
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer apiClient.Close()
|
|
|
|
DOMAIN = config.Domain
|
|
|
|
connection := Connection{databaseConnection: mongo_client, dockerClient: apiClient}
|
|
authConnection := auth.Connection{DatabaseConnection: mongo_client}
|
|
|
|
group.POST("/:server_id/start", auth.AuthorizedTo(models.Start, authConnection.ServerAuthorized(models.Start)), connection.StartServer)
|
|
group.POST("", auth.AuthorizedTo(models.Create), connection.CreateServer)
|
|
group.GET("", auth.AuthorizedTo(0), connection.GetServers)
|
|
group.POST("/:server_id/stop", auth.AuthorizedTo(models.Stop, authConnection.ServerAuthorized(models.Stop)), connection.StopServer)
|
|
group.DELETE("/:server_id", auth.AuthorizedTo(models.Delete, authConnection.ServerAuthorized(models.Delete)), connection.DeleteServer)
|
|
group.POST("/:server_id/run_command", auth.AuthorizedTo(models.RunCommand, authConnection.ServerAuthorized(models.RunCommand)), connection.RunCommand)
|
|
group.GET("/:server_id/attach", auth.AuthorizedTo(models.RunCommand, authConnection.ServerAuthorized(models.RunCommand)), connection.AttachServer)
|
|
group.PATCH("/:server_id", auth.AuthorizedTo(models.Admin, authConnection.ServerAuthorized(models.Admin)), connection.UpdateServer)
|
|
group.POST("/:server_id/browse", auth.AuthorizedTo(models.Browse, authConnection.ServerAuthorized(models.Admin)), connection.BrowseServer)
|
|
group.GET("/:server_id/permissions", auth.AuthorizedTo(models.Browse, authConnection.ServerAuthorized(models.Admin)), connection.GetServerUserPermissions)
|
|
group.POST("/:server_id/permissions", auth.AuthorizedTo(models.Browse, authConnection.ServerAuthorized(models.Admin)), connection.SetServerUserPermissions)
|
|
}
|