working state

This commit is contained in:
ACoolName 2025-03-20 18:39:49 +02:00
parent 5f01b6b27c
commit 2e36a019cd
12 changed files with 177 additions and 62 deletions

View File

@ -2,6 +2,7 @@ package auth
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
@ -158,27 +159,33 @@ func (con AuthApi) Verify(ctx *gin.Context) {
claimsPointer, exists := ctx.Get("claims") claimsPointer, exists := ctx.Get("claims")
if !exists { if !exists {
ctx.Status(403) ctx.Status(403)
ctx.Error(errors.New("Failed to get claims, not logged in"))
return return
} }
claims := claimsPointer.(*AuthClaims) claims, ok := claimsPointer.(*AuthClaims)
if !ok {
ctx.Error(errors.New("Failed to convert claims"))
ctx.Status(500)
return
}
forwarded_host := ctx.Request.Header.Get("x-forwarded-host") forwardedUri := ctx.Request.Header.Get("x-forwarded-uri")
log.Printf("Checking auth of %s", forwarded_host)
domainSegments := strings.Split(forwarded_host, ".") pathSegments := strings.Split(forwardedUri, "/")
serverId, service := domainSegments[0], domainSegments[1] serverId, service := pathSegments[2], pathSegments[1]
switch service { switch service {
case "browsers": case "browsers":
fmt.Printf("%#v %s", claims, serverId)
serverPermissions, err := con.serverAuthDbHandler.GetPermissions(ctx, claims.Username, serverId) serverPermissions, err := con.serverAuthDbHandler.GetPermissions(ctx, claims.Username, serverId)
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
} }
if (claims.Permissions|serverPermissions)&models.Admin == models.Admin { if (claims.Permissions|serverPermissions)&models.Admin == models.Admin {
ctx.Header("X-Username", claims.Username) ctx.Header("X-Auth-Username", claims.Username)
log.Printf("Set header X-Username %s", claims.Username) log.Printf("Set header X-Username %s", claims.Username)
ctx.Status(200) ctx.Status(200)
return return
@ -186,7 +193,7 @@ func (con AuthApi) Verify(ctx *gin.Context) {
case "cloud": case "cloud":
if claims.Permissions&models.Cloud == models.Cloud || claims.Permissions&models.Admin == models.Admin { if claims.Permissions&models.Cloud == models.Cloud || claims.Permissions&models.Admin == models.Admin {
log.Printf("Set header X-Username %s", claims.Username) log.Printf("Set header X-Username %s", claims.Username)
ctx.Header("X-Username", claims.Username) ctx.Header("X-Auth-Username", claims.Username)
ctx.Status(200) ctx.Status(200)
return return
} }
@ -201,6 +208,11 @@ func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) gin.HandlerFu
panic(err) panic(err)
} }
serverAuthDbHandler, err := factories.GetServersAuthorizationDbHandler(config.ServersAuthorizationDatabase)
if err != nil {
panic(err)
}
inviteHandler, err := factories.GetInviteTokenDbHandler(config.Authentication.UserPass.InviteTokenDatabase) inviteHandler, err := factories.GetInviteTokenDbHandler(config.Authentication.UserPass.InviteTokenDatabase)
if err != nil { if err != nil {
panic(err) panic(err)
@ -208,13 +220,14 @@ func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) gin.HandlerFu
connection := AuthApi{ connection := AuthApi{
userAuthDbHandler: userAuthHandler, userAuthDbHandler: userAuthHandler,
serverAuthDbHandler: serverAuthDbHandler,
tokenHandler: inviteHandler, tokenHandler: inviteHandler,
config: config, config: config,
} }
group.POST("/signin", connection.signIn) group.POST("/signin", connection.signIn)
group.POST("/signup", connection.signUp) group.POST("/signup", connection.signUp)
group.Any("/verify", connection.Verify) group.Any("/verify", connection.LoggedIn, connection.Verify)
return connection.LoggedIn return connection.LoggedIn
} }

View File

@ -40,9 +40,7 @@ authentication:
instancemanager: instancemanager:
type: "docker" type: "docker"
docker: docker:
games_domain: "games.acooldomain.co" browsers_sub_domain: browsers
browsers_domain: "browsers.acooldomain.co"
certificate_resolver: "letsencrypt"
file_browser: file_browser:
image: image:
registry: "filebrowser/filebrowser" registry: "filebrowser/filebrowser"

View File

@ -126,6 +126,7 @@ func (self *ServersAuthorizationDbHandler) SetPermissions(ctx context.Context, u
func (self *ServersAuthorizationDbHandler) GetPermissions(ctx context.Context, username string, serverId string) (models.Permission, error) { func (self *ServersAuthorizationDbHandler) GetPermissions(ctx context.Context, username string, serverId string) (models.Permission, error) {
var serverPermissions ServerPermissions var serverPermissions ServerPermissions
err := self.collection.FindOne( err := self.collection.FindOne(
ctx, ctx,
bson.M{ bson.M{

View File

@ -26,7 +26,7 @@ type UserPassAuthenticationDbHandler struct {
} }
func (self *UserPassAuthenticationDbHandler) ListUsers(ctx context.Context) ([]models.User, error) { func (self *UserPassAuthenticationDbHandler) ListUsers(ctx context.Context) ([]models.User, error) {
cursor, err := self.collection.Find(ctx, nil) cursor, err := self.collection.Find(ctx, bson.M{})
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -14,11 +14,11 @@ var (
instanceManagerMutex sync.Mutex instanceManagerMutex sync.Mutex
) )
func getDockerCacheKey(config *models.DockerInstanceManagerConfig) string { func getDockerCacheKey(config *models.DockerInstanceManagerConfig, siteDomain string) string {
return "Docker" return "Docker/" + siteDomain
} }
func GetInstanceManager(config models.InstanceManagerConfig) (instancemanager.InstanceManager, error) { func GetInstanceManager(config models.InstanceManagerConfig, siteDomain string) (instancemanager.InstanceManager, error) {
var key string var key string
var handler instancemanager.InstanceManager var handler instancemanager.InstanceManager
var err error var err error
@ -29,7 +29,7 @@ func GetInstanceManager(config models.InstanceManagerConfig) (instancemanager.In
if config.Docker == nil { if config.Docker == nil {
return nil, errors.New("missing Docker configuration") return nil, errors.New("missing Docker configuration")
} }
key = getDockerCacheKey(config.Docker) key = getDockerCacheKey(config.Docker, siteDomain)
default: default:
return nil, errors.New("unsupported database type") return nil, errors.New("unsupported database type")
} }
@ -43,7 +43,7 @@ func GetInstanceManager(config models.InstanceManagerConfig) (instancemanager.In
switch config.Type { switch config.Type {
case models.DOCKER: case models.DOCKER:
handler, err = docker.NewInstanceManager(*config.Docker) handler, err = docker.NewInstanceManager(*config.Docker, siteDomain)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"maps" "maps"
"net" "net"
"strings" "strings"
@ -25,6 +26,8 @@ type InstanceManager struct {
instancemanager.InstanceManager instancemanager.InstanceManager
client *client.Client client *client.Client
config models.DockerInstanceManagerConfig config models.DockerInstanceManagerConfig
browsersSubDomain string
siteDomain string
} }
func (self *InstanceManager) containerList(ctx context.Context, labels ContainerLabels, all bool) ([]container.Summary, error) { func (self *InstanceManager) containerList(ctx context.Context, labels ContainerLabels, all bool) ([]container.Summary, error) {
@ -129,7 +132,7 @@ func (self *InstanceManager) GetServer(ctx context.Context, serverId string) (*i
RunningCommand: "", RunningCommand: "",
RunningImage: nil, RunningImage: nil,
Ports: nil, Ports: nil,
Domain: self.config.GamesDomain, Domain: self.siteDomain,
}, nil }, nil
} }
@ -146,7 +149,7 @@ func (self *InstanceManager) GetServer(ctx context.Context, serverId string) (*i
RunningCommand: runningCommand, RunningCommand: runningCommand,
Ports: convertContainerPortsToPorts(serverContainer.Ports), Ports: convertContainerPortsToPorts(serverContainer.Ports),
RunningImage: &image, RunningImage: &image,
Domain: self.config.GamesDomain, Domain: self.siteDomain,
}, nil }, nil
} }
@ -170,7 +173,7 @@ func (self *InstanceManager) ListServers(ctx context.Context) ([]instancemanager
RunningCommand: "", RunningCommand: "",
Ports: nil, Ports: nil,
RunningImage: nil, RunningImage: nil,
Domain: self.config.GamesDomain, Domain: self.siteDomain,
} }
} }
@ -328,18 +331,22 @@ func (self *InstanceManager) StopServer(ctx context.Context, serverId string) er
return nil return nil
} }
func (self *InstanceManager) CreateServer(ctx context.Context) (*instancemanager.Server, error) { func (self *InstanceManager) createVolume(ctx context.Context, volumeLabels VolumeLabels) (volume.Volume, error) {
labels, err := convertLabelsToMap(VolumeLabels{ labels, err := convertLabelsToMap(volumeLabels)
Type: Game,
})
if err != nil { if err != nil {
return nil, err return volume.Volume{}, err
} }
volume, err := self.client.VolumeCreate(ctx, volume.CreateOptions{ volume, err := self.client.VolumeCreate(ctx, volume.CreateOptions{
Labels: *labels, Labels: *labels,
}) })
return volume, err
}
func (self *InstanceManager) CreateServer(ctx context.Context) (*instancemanager.Server, error) {
volume, err := self.createVolume(ctx, VolumeLabels{Type: Game})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -349,7 +356,7 @@ func (self *InstanceManager) CreateServer(ctx context.Context) (*instancemanager
Running: false, Running: false,
RunningImage: nil, RunningImage: nil,
RunningCommand: "", RunningCommand: "",
Domain: self.config.GamesDomain, Domain: self.siteDomain,
}, nil }, nil
} }
@ -455,7 +462,8 @@ func (self *InstanceManager) GetFileBrowser(ctx context.Context, serverId string
} }
return &models.FileBrowser{ return &models.FileBrowser{
Url: containerLabels.VolumeId[:12] + "." + self.config.BrowsersDomain, Url: fmt.Sprintf("%s/browsers/%s/", self.siteDomain, containerLabels.VolumeId),
ServerId: containerLabels.VolumeId,
Id: rawContainer.ID, Id: rawContainer.ID,
}, nil }, nil
} }
@ -475,7 +483,8 @@ func (self *InstanceManager) ListFileBrowsers(ctx context.Context) ([]models.Fil
} }
fileBrowsers[i] = models.FileBrowser{ fileBrowsers[i] = models.FileBrowser{
Url: containerLabels.VolumeId[:12] + "." + self.config.BrowsersDomain, Url: fmt.Sprintf("%s/browsers/%s/", self.siteDomain, containerLabels.VolumeId),
ServerId: containerLabels.VolumeId,
Id: rawContainer.ID, Id: rawContainer.ID,
} }
@ -492,20 +501,71 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri
return nil, err return nil, err
} }
labelId := serverId[:12] labelId := server.Id
volumeLabels := VolumeLabels{
Type: FileBrowser,
VolumeId: server.Id,
}
containers, err := self.containerList(ctx, ContainerLabels{VolumeId: serverId, Type: FileBrowser}, false)
if len(containers) > 0 {
return &models.FileBrowser{
Url: fmt.Sprintf("%s/browsers/%s/", self.siteDomain, server.Id),
ServerId: server.Id,
Id: containers[0].ID,
}, nil
}
volume, err := self.createVolume(ctx, volumeLabels)
containerConfig, err := convertLabelsToMap(ContainerLabels{VolumeId: serverId, Type: FileBrowser})
if err != nil {
return nil, err
}
PrepareDatabaseContainer, err := self.client.ContainerCreate(
ctx,
&container.Config{
Entrypoint: []string{"/bin/sh"},
Cmd: []string{"-c", "/filebrowser config init -d /tmp/database/database.db && /filebrowser config set -d /tmp/database/database.db --auth.method=proxy --auth.header=X-Auth-Username"},
Image: fmt.Sprintf("%s:%s", self.config.FileBrowser.Image.Registry, self.config.FileBrowser.Image.Tag),
Labels: *containerConfig,
},
&container.HostConfig{
Mounts: []mount.Mount{{Source: volume.Name, Target: "/tmp/database", Type: mount.TypeVolume}},
AutoRemove: true,
},
&network.NetworkingConfig{},
&v1.Platform{},
"",
)
if err != nil {
return nil, err
}
err = self.client.ContainerStart(ctx, PrepareDatabaseContainer.ID, container.StartOptions{})
if err != nil {
log.Printf("Failed to wait %s", err)
}
responseChan, _ := self.client.ContainerWait(ctx, PrepareDatabaseContainer.ID, container.WaitConditionNotRunning)
response := <-responseChan
log.Printf("%#v\n", response)
browserLabels := make(map[string]string) browserLabels := make(map[string]string)
browserLabels["traefik.enable"] = "true" browserLabels["traefik.enable"] = "true"
browserLabels[fmt.Sprintf("traefik.http.routers.%s.rule", labelId)] = fmt.Sprintf("Host(`%s.browsers.%s`)", labelId, self.config.BrowsersDomain) browserLabels[fmt.Sprintf("traefik.http.routers.%s.rule", labelId)] = fmt.Sprintf("Host(`%s`) && PathPrefix(`/browsers/%s/`)", self.siteDomain, labelId)
browserLabels[fmt.Sprintf("traefik.http.routers.%s.entrypoints", labelId)] = self.config.FileBrowser.ReverseProxy.Entrypoint browserLabels[fmt.Sprintf("traefik.http.routers.%s.entrypoints", labelId)] = self.config.FileBrowser.ReverseProxy.Entrypoint
browserLabels[fmt.Sprintf("traefik.http.routers.%s.middlewares", labelId)] = strings.Join(self.config.FileBrowser.ReverseProxy.Middlewares, ",") browserLabels[fmt.Sprintf("traefik.http.routers.%s.middlewares", labelId)] = strings.Join(self.config.FileBrowser.ReverseProxy.Middlewares, ",")
if self.config.FileBrowser.ReverseProxy.Tls { if self.config.FileBrowser.ReverseProxy.Tls {
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].main", labelId)] = self.config.BrowsersDomain
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].sans", labelId)] = fmt.Sprintf("*.%s", self.config.BrowsersDomain)
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", labelId)] = self.config.FileBrowser.ReverseProxy.TlsResolver browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", labelId)] = self.config.FileBrowser.ReverseProxy.TlsResolver
} }
containerConfig, err := convertLabelsToMap(ContainerLabels{VolumeId: serverId, Type: FileBrowser})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -514,9 +574,7 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri
command := self.config.FileBrowser.Command command := self.config.FileBrowser.Command
if command == "" { command += fmt.Sprintf("-d /tmp/database/database.db -r /tmp/data -b /browsers/%s/", labelId)
command = "--noauth -r /tmp/data"
}
splitCommand, err := shellwords.Split(command) splitCommand, err := shellwords.Split(command)
if err != nil { if err != nil {
@ -524,7 +582,7 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri
} }
ContainerResponse, err := self.client.ContainerCreate( ContainerResponse, err := self.client.ContainerCreate(
context.TODO(), ctx,
&container.Config{ &container.Config{
Cmd: splitCommand, Cmd: splitCommand,
Image: fmt.Sprintf("%s:%s", self.config.FileBrowser.Image.Registry, self.config.FileBrowser.Image.Tag), Image: fmt.Sprintf("%s:%s", self.config.FileBrowser.Image.Registry, self.config.FileBrowser.Image.Tag),
@ -532,7 +590,7 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri
Tty: true, Tty: true,
}, },
&container.HostConfig{ &container.HostConfig{
Mounts: []mount.Mount{{Source: server.Id, Target: "/tmp/data", Type: "volume"}}, Mounts: []mount.Mount{{Source: server.Id, Target: "/tmp/data", Type: "volume"}, {Source: volume.Name, Target: "/tmp/database/", Type: mount.TypeVolume}},
AutoRemove: true, AutoRemove: true,
ConsoleSize: [2]uint{1000, 1000}, ConsoleSize: [2]uint{1000, 1000},
}, },
@ -548,14 +606,15 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri
return nil, err return nil, err
} }
err = self.client.ContainerStart(context.TODO(), ContainerResponse.ID, container.StartOptions{}) err = self.client.ContainerStart(ctx, ContainerResponse.ID, container.StartOptions{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &models.FileBrowser{ return &models.FileBrowser{
Url: serverId[:12] + "." + self.config.BrowsersDomain, Url: fmt.Sprintf("%s/browsers/%s/", self.siteDomain, server.Id),
Id: ContainerResponse.ID, ServerId: server.Id,
Id: volume.Name,
}, nil }, nil
} }
@ -566,16 +625,26 @@ func (self *InstanceManager) StopFileBrowser(ctx context.Context, serverId strin
} }
for _, rawContainer := range containers { for _, rawContainer := range containers {
stopChan, _ := self.client.ContainerWait(ctx, rawContainer.ID, container.WaitConditionRemoved)
err := self.client.ContainerStop(ctx, rawContainer.ID, container.StopOptions{}) err := self.client.ContainerStop(ctx, rawContainer.ID, container.StopOptions{})
if err != nil { if err != nil {
return err return err
} }
<-stopChan
for _, mount := range rawContainer.Mounts {
if mount.Destination == "/tmp/database" {
err := self.client.VolumeRemove(ctx, mount.Name, true)
if err != nil {
return err
}
}
}
} }
return nil return nil
} }
func NewInstanceManager(config models.DockerInstanceManagerConfig) (*InstanceManager, error) { func NewInstanceManager(config models.DockerInstanceManagerConfig, siteDomain string) (*InstanceManager, error) {
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil { if err != nil {
return nil, err return nil, err
@ -586,5 +655,6 @@ func NewInstanceManager(config models.DockerInstanceManagerConfig) (*InstanceMan
return &InstanceManager{ return &InstanceManager{
config: config, config: config,
client: apiClient, client: apiClient,
siteDomain: siteDomain,
}, nil }, nil
} }

View File

@ -20,6 +20,7 @@ type BrowserLabels struct {
type VolumeLabels struct { type VolumeLabels struct {
Type ContainerType `json:"type,omitempty"` Type ContainerType `json:"type,omitempty"`
VolumeId string `json:"volume_id,omitempty"`
} }
type ImageLabels struct { type ImageLabels struct {

View File

@ -65,9 +65,6 @@ type FileBrowserConfig struct {
} }
type DockerInstanceManagerConfig struct { type DockerInstanceManagerConfig struct {
GamesDomain string `yaml:"games_domain"`
BrowsersDomain string `yaml:"browsers_domain"`
CertificateResolver string `yaml:"certificate_resolver"`
FileBrowser FileBrowserConfig `yaml:"file_browser"` FileBrowser FileBrowserConfig `yaml:"file_browser"`
} }

View File

@ -21,4 +21,5 @@ type Image struct {
type FileBrowser struct { type FileBrowser struct {
Id string Id string
Url string Url string
ServerId string
} }

View File

@ -7,6 +7,12 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type FileBrowser struct {
Id string `json:"Id"`
Url string `json:"Url"`
ConnectedTo ServerInfo `json:"ConnectedTo"`
}
func (con ServersApi) GetBrowsers(ctx *gin.Context) { func (con ServersApi) GetBrowsers(ctx *gin.Context) {
fileBrowsers, err := con.InstanceManager.ListFileBrowsers(ctx) fileBrowsers, err := con.InstanceManager.ListFileBrowsers(ctx)
@ -15,7 +21,34 @@ func (con ServersApi) GetBrowsers(ctx *gin.Context) {
return return
} }
ctx.JSON(200, fileBrowsers) fileBrowserInfos := make([]FileBrowserInfo, len(fileBrowsers))
for i, browser := range fileBrowsers {
server, err := con.ServersDbHandler.GetServer(ctx, browser.ServerId)
if err != nil {
ctx.Error(err)
continue
}
serverInfo := ServerInfo{
Id: server.Id,
Image: ImageInfo{
Name: server.Image.Registry,
Version: server.Image.Tag,
},
OwnerId: server.Owner,
DefaultCommand: server.Command,
Nickname: server.Nickname,
}
fileBrowserInfos[i] = FileBrowserInfo{
ConnectedTo: serverInfo,
Url: browser.Url,
Id: browser.Id,
}
}
ctx.JSON(200, fileBrowserInfos)
} }
func (con ServersApi) StopBrowser(ctx *gin.Context) { func (con ServersApi) StopBrowser(ctx *gin.Context) {
@ -40,7 +73,7 @@ func LoadBrowsersGroup(group *gin.RouterGroup, config models.GlobalConfig) {
panic(err) panic(err)
} }
instanceManager, err := factories.GetInstanceManager(config.InstanceManager) instanceManager, err := factories.GetInstanceManager(config.InstanceManager, config.Domain)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -49,7 +49,7 @@ func (con ServersApi) GetImages(ctx *gin.Context) {
} }
func LoadeImagesGroup(group *gin.RouterGroup, config models.GlobalConfig) { func LoadeImagesGroup(group *gin.RouterGroup, config models.GlobalConfig) {
instanceManager, err := factories.GetInstanceManager(config.InstanceManager) instanceManager, err := factories.GetInstanceManager(config.InstanceManager, config.Domain)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -31,6 +31,7 @@ type ServersApi struct {
ServersDbHandler dbhandler.ServersDbHandler ServersDbHandler dbhandler.ServersDbHandler
InstanceManager instancemanager.InstanceManager InstanceManager instancemanager.InstanceManager
ServerAuthorization dbhandler.ServersAuthorizationDbHandler ServerAuthorization dbhandler.ServersAuthorizationDbHandler
config models.GlobalConfig
} }
type ImageInfo struct { type ImageInfo struct {
@ -513,7 +514,7 @@ func (con ServersApi) SetServerUserPermissions(ctx *gin.Context) {
} }
func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) { func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) {
instanceManager, err := factories.GetInstanceManager(config.InstanceManager) instanceManager, err := factories.GetInstanceManager(config.InstanceManager, config.Domain)
if err != nil { if err != nil {
panic(err) panic(err)
} }