package servers import ( "encoding/json" "errors" "fmt" "log" "net/http" "strconv" "strings" "time" "git.acooldomain.co/server-manager/backend/auth" "git.acooldomain.co/server-manager/backend/dbhandler" "git.acooldomain.co/server-manager/backend/factories" instancemanager "git.acooldomain.co/server-manager/backend/instancemanager" "git.acooldomain.co/server-manager/backend/models" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true }, } type ServersApi struct { ServersDbHandler dbhandler.ServersDbHandler InstanceManager instancemanager.InstanceManager ServerAuthorization dbhandler.ServersAuthorizationDbHandler config models.GlobalConfig } type ImageInfo struct { Name string `json:"Name"` Version string `json:"Version"` Ports []models.Port `json:"Ports"` } type ServerInfo struct { Id string `json:"Id"` OwnerId string `json:"OwnerId"` DefaultCommand string `json:"DefaultCommand"` Image ImageInfo `json:"Image"` On bool `json:"On"` Nickname string `json:"Nickname"` Ports []models.Port `json:"Ports"` Domain string `json:"Domain"` } type FileBrowserInfo struct { Id string `json:"Id"` OwnerId string `json:"OwnerId"` ConnectedTo ServerInfo `json:"ConnectedTo"` Url string `json:"Url"` } type CreateServerRequest struct { ImageId string `json:"ImageId"` DefaultPorts []models.Port `json:"DefaultPorts"` DefaultCommand string `json:"DefaultCommand"` Nickname string `json:"Nickname"` } func (con ServersApi) CreateServer(ctx *gin.Context) { claims, exists := ctx.Get("claims") if !exists { ctx.AbortWithStatus(500) return } serverClaims := claims.(*auth.AuthClaims) var request CreateServerRequest err := json.NewDecoder(ctx.Request.Body).Decode(&request) if err != nil { ctx.AbortWithError(400, err) return } instanceImage, err := con.InstanceManager.GetImage(ctx, request.ImageId) if err != nil { ctx.AbortWithError(500, err) return } instanceServer, err := con.InstanceManager.CreateServer(ctx) if err != nil { ctx.AbortWithError(500, err) return } err = con.ServersDbHandler.CreateServer(ctx, dbhandler.Server{ Id: instanceServer.Id, Owner: serverClaims.Username, Image: &models.Image{ Id: instanceImage.Id, Registry: instanceImage.Registry, Tag: instanceImage.Tag, }, Nickname: request.Nickname, Command: request.DefaultCommand, Ports: request.DefaultPorts, }) if err != nil { ctx.AbortWithError(500, err) return } err = con.ServerAuthorization.SetPermissions(ctx, serverClaims.Username, instanceServer.Id, models.Admin) if err != nil { ctx.AbortWithError(500, err) return } ctx.JSON(200, instanceServer.Id) } type PortMappingRequest struct { Source models.Port Destination models.Port } type StartServerRequest struct { Command string `json:"Command"` Ports []PortMappingRequest `json:"Ports"` } func (con ServersApi) StartServer(ctx *gin.Context) { serverId := ctx.Param("server_id") var request StartServerRequest err := json.NewDecoder(ctx.Request.Body).Decode(&request) if err != nil { ctx.AbortWithError(500, err) return } instanceServer, err := con.InstanceManager.GetServer(ctx, serverId) if err != nil { ctx.AbortWithError(500, err) return } if instanceServer.Running { ctx.Status(200) return } server, err := con.ServersDbHandler.GetServer(ctx, serverId) if err != nil { ctx.AbortWithError(500, err) return } err = con.InstanceManager.StartServer( ctx, instanceServer.Id, server.Image.Id, server.Command, server.Ports, ) if err != nil { ctx.AbortWithError(500, err) return } ctx.JSON(200, instanceServer.Id) } func (con ServersApi) GetServers(ctx *gin.Context) { instanceServers, err := con.InstanceManager.ListServers(ctx) if err != nil { ctx.AbortWithError(500, err) return } serverConfigs, err := con.ServersDbHandler.ListServers(ctx) if err != nil { ctx.AbortWithError(500, err) return } serverConfigsMap := make(map[string]dbhandler.Server, len(serverConfigs)) for _, serverConfig := range serverConfigs { serverConfigsMap[serverConfig.Id] = serverConfig } servers := make([]ServerInfo, len(instanceServers)) for i, instanceServer := range instanceServers { server := serverConfigsMap[instanceServer.Id] var image ImageInfo log.Printf("server=%#v\t|\tinstanceServer=%#v", server, instanceServer) if instanceServer.Running { image = ImageInfo{ Name: instanceServer.RunningImage.Registry, Version: instanceServer.RunningImage.Tag, } } else { log.Printf("serverImage:%#v", server.Image) image = ImageInfo{ Name: server.Image.Registry, Version: server.Image.Tag, } } servers[i] = ServerInfo{ Id: instanceServer.Id, Image: image, OwnerId: server.Owner, DefaultCommand: server.Command, Ports: instanceServer.Ports, On: instanceServer.Running, Nickname: server.Nickname, Domain: instanceServer.Domain, } } ctx.JSON(200, servers) } func (con ServersApi) StopServer(ctx *gin.Context) { serverId := ctx.Param("server_id") err := con.InstanceManager.StopServer(ctx, serverId) if err != nil { ctx.AbortWithError(500, err) return } ctx.Status(200) } func (con ServersApi) DeleteServer(ctx *gin.Context) { serverId := ctx.Param("server_id") err := con.InstanceManager.DeleteServer(ctx, serverId) if err != nil { ctx.AbortWithError(500, err) return } err = con.ServersDbHandler.DeleteServer(ctx, serverId) if err != nil { ctx.AbortWithError(501, err) return } ctx.JSON(200, "ok") } type RunCommandRequest struct { Command string `json:"Command"` } func (con ServersApi) 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, "\"") consolePointer, err := con.InstanceManager.InteractiveTerminal(ctx, serverId) if err != nil { ctx.AbortWithError(500, err) return } console := *consolePointer defer console.Conn.Close() _, err = console.Conn.Write([]byte(request.Command + "\n")) if err != nil { ctx.AbortWithError(500, err) return } ctx.JSON(200, "OK") } type Commands struct { CommandType string `json:"CommandType"` Arguments string `json:"Arguments"` } func (con ServersApi) AttachServer(ctx *gin.Context) { serverId := ctx.Param("server_id") stop := false var err error var hijackedPointer *instancemanager.TerminalConnection 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) }() hijackedPointer, err = con.InstanceManager.InteractiveTerminal(ctx, serverId) if err != nil { ctx.AbortWithError(500, err) return } hijacked := *hijackedPointer defer hijacked.Conn.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.Conn.Read(data) if err != nil { time.Sleep(500) hijackedPointer, err = con.InstanceManager.InteractiveTerminal(ctx, serverId) if err != nil { ctx.AbortWithError(500, err) stop = true return } hijacked = *hijackedPointer } 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)) if err != nil { log.Printf("Write to docker failed %s", errors.Unwrap(err)) stop = true break } case "close": stop = true case "resize": args := strings.Split(Command.Arguments, "x") i_width, err2 := strconv.Atoi(args[0]) if err2 != nil { break } width := uint(i_width) i_height, err2 := strconv.Atoi(args[1]) if err2 != nil { break } height := uint(i_height) err2 = hijacked.ResizerFunc(width, height) if err2 != nil { log.Printf("Failed to resize container to %dx%d: %s", width, height, 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 ServersApi) 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 } err = con.ServersDbHandler.UpdateServer(ctx, serverId, dbhandler.ServerUpdateRequest{ Ports: request.DefaultPorts, Nickname: request.Nickname, Command: request.DefaultCommand, }) if err != nil { ctx.AbortWithError(500, err) return } for user, permissions := range request.UserPermissions { err = con.ServerAuthorization.SetPermissions(ctx, user, serverId, permissions) if err != nil { log.Printf("failed to change user %s permissions for server %s due to %e", user, serverId, err) continue } } ctx.JSON(200, "OK") } func (con ServersApi) BrowseServer(ctx *gin.Context) { serverId := ctx.Param("server_id") browserInfo, err := con.InstanceManager.StartFileBrowser(ctx, serverId) if err != nil { ctx.AbortWithError(500, err) return } ctx.JSON(200, browserInfo.Url) } func (con ServersApi) GetServerUserPermissions(ctx *gin.Context) { claims, exists := ctx.Get("claims") if !exists { ctx.AbortWithStatus(500) return } authClaims := claims.(*auth.AuthClaims) serverId := ctx.Param("server_id") if serverId == "" { ctx.AbortWithStatus(500) return } permissions, err := con.ServerAuthorization.GetPermissions(ctx, authClaims.Username, serverId) if err != nil { ctx.AbortWithError(500, err) return } ctx.JSON(200, permissions) } type SetServerUserPermissionsRequest struct { Username string Permissions models.Permission } func (con ServersApi) SetServerUserPermissions(ctx *gin.Context) { serverId := ctx.Param("server_id") if serverId == "" { 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.ServerAuthorization.SetPermissions(ctx, request.Username, serverId, request.Permissions) if err != nil { ctx.AbortWithError(500, err) return } ctx.JSON(200, "OK") } func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) { instanceManager, err := factories.GetInstanceManager(config.InstanceManager, config.Domain) if err != nil { panic(err) } serversDbHandler, err := factories.GetServersDbHandler(config.ServersDatabase) if err != nil { panic(err) } serversAuthorizationHandler, err := factories.GetServersAuthorizationDbHandler(config.ServersAuthorizationDatabase) if err != nil { panic(err) } connection := ServersApi{ ServersDbHandler: serversDbHandler, ServerAuthorization: serversAuthorizationHandler, InstanceManager: instanceManager, } group.POST("/:server_id/start", auth.AuthorizedTo(models.Start), connection.ServerAuthorized(models.Start), auth.AuthorizationEnforcer(), connection.StartServer) group.POST("", auth.AuthorizedTo(models.Create), auth.AuthorizationEnforcer(), connection.CreateServer) group.GET("", connection.GetServers) group.POST("/:server_id/stop", auth.AuthorizedTo(models.Stop), connection.ServerAuthorized(models.Stop), auth.AuthorizationEnforcer(), connection.StopServer) group.DELETE("/:server_id", auth.AuthorizedTo(models.Delete), connection.ServerAuthorized(models.Delete), auth.AuthorizationEnforcer(), connection.DeleteServer) group.POST("/:server_id/run_command", auth.AuthorizedTo(models.RunCommand), connection.ServerAuthorized(models.RunCommand), auth.AuthorizationEnforcer(), connection.RunCommand) group.GET("/:server_id/attach", auth.AuthorizedTo(models.RunCommand), connection.ServerAuthorized(models.RunCommand), auth.AuthorizationEnforcer(), connection.AttachServer) group.PATCH("/:server_id", auth.AuthorizedTo(models.Admin), connection.ServerAuthorized(models.Admin), auth.AuthorizationEnforcer(), connection.UpdateServer) group.POST("/:server_id/browse", auth.AuthorizedTo(models.Browse), connection.ServerAuthorized(models.Admin), auth.AuthorizationEnforcer(), connection.BrowseServer) group.GET("/:server_id/permissions", connection.GetServerUserPermissions) group.POST("/:server_id/permissions", auth.AuthorizedTo(models.Admin), connection.ServerAuthorized(models.Admin), auth.AuthorizationEnforcer(), connection.SetServerUserPermissions) }