diff --git a/server/environment_docker.go b/server/environment_docker.go index d09a9e8..dea64e4 100644 --- a/server/environment_docker.go +++ b/server/environment_docker.go @@ -3,7 +3,9 @@ package server import ( "fmt" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" "github.com/docker/docker/client" + "github.com/docker/docker/daemon/logger/jsonfilelog" "golang.org/x/net/context" "os" "strings" @@ -79,6 +81,8 @@ func (d *DockerEnvironment) Create() error { return err } + var oomDisabled = true + // If the container already exists don't hit the user with an error, just return // the current information about it which is what we would do when creating the // container anyways. @@ -87,16 +91,16 @@ func (d *DockerEnvironment) Create() error { } conf := &container.Config{ - Hostname: "container", - User: d.Configuration.Container.User, - AttachStdin: true, + Hostname: "container", + User: d.Configuration.Container.User, + AttachStdin: true, AttachStdout: true, AttachStderr: true, - OpenStdin: true, - Tty: true, + OpenStdin: true, + Tty: true, Image: d.Server.Container.Image, - Env: d.environmentVariables(), + Env: d.environmentVariables(), Labels: map[string]string{ "Service": "Pterodactyl", @@ -104,9 +108,69 @@ func (d *DockerEnvironment) Create() error { } hostConf := &container.HostConfig{ - Resources: container.Resources{ - Memory: d.Server.Build.MemoryLimit * 1000000, + // Configure the mounts for this container. First mount the server data directory + // into the container as a r/w bind. Additionally mount the host timezone data into + // the container as a readonly bind so that software running in the container uses + // the same time as the host system. + Mounts: []mount.Mount{ + { + Target: "/home/container", + Source: d.Server.Filesystem().Path(), + Type: mount.TypeBind, + ReadOnly: false, + }, + { + Target: d.Configuration.TimezonePath, + Source: d.Configuration.TimezonePath, + Type: mount.TypeBind, + ReadOnly: true, + }, }, + + // Configure the /tmp folder mapping in containers. This is necessary for some + // games that need to make use of it for downloads and other installation processes. + Tmpfs: map[string]string{ + "/tmp": "rw,exec,nosuid,size=50M", + }, + + // Define resource limits for the container based on the data passed through + // from the Panel. + Resources: container.Resources{ + // @todo memory limit should be slightly higher than the reservation + Memory: d.Server.Build.MemoryLimit * 1000000, + MemoryReservation: d.Server.Build.MemoryLimit * 1000000, + MemorySwap: d.Server.Build.ConvertedSwap(), + + CPUQuota: d.Server.Build.ConvertedCpuLimit(), + CPUPeriod: 100000, + CPUShares: 1024, + + BlkioWeight: d.Server.Build.IoWeight, + OomKillDisable: &oomDisabled, + }, + + // @todo make this configurable again + DNS: []string{"1.1.1.1", "8.8.8.8"}, + + // Configure logging for the container to make it easier on the Daemon to grab + // the server output. Ensure that we don't use too much space on the host machine + // since we only need it for the last few hundred lines of output and don't care + // about anything else in it. + LogConfig: container.LogConfig{ + Type: jsonfilelog.Name, + Config: map[string]string{ + "max-size": "5m", + "max-file": "1", + }, + }, + + SecurityOpt: []string{"no-new-privileges"}, + ReadonlyRootfs: true, + CapDrop: []string{ + "setpcap", "mknod", "audit_write", "net_raw", "dac_override", + "fowner", "fsetid", "net_bind_service", "sys_chroot", "setfcap", + }, + NetworkMode: "pterodactyl_nw", } if _, err := cli.ContainerCreate(ctx, conf, hostConf, nil, d.Server.Uuid); err != nil { @@ -135,4 +199,4 @@ func (d *DockerEnvironment) environmentVariables() []string { func (d *DockerEnvironment) volumes() map[string]struct{} { return nil -} \ No newline at end of file +} diff --git a/server/filesystem.go b/server/filesystem.go new file mode 100644 index 0000000..29fa4c7 --- /dev/null +++ b/server/filesystem.go @@ -0,0 +1,22 @@ +package server + +import "path" + +type Filesystem struct { + // The root directory where all of the server data is contained. By default + // this is going to be /srv/daemon-data but can vary depending on the system. + Root string + + // The server object associated with this Filesystem. + Server *Server +} + +// Returns the root path that contains all of a server's data. +func (fs *Filesystem) Path() string { + return path.Join(fs.Root, fs.Server.Uuid) +} + +// Returns a safe path for a server object. +func (fs *Filesystem) SafePath(p string) string { + return fs.Path() +} \ No newline at end of file diff --git a/server/server.go b/server/server.go index b4d6ad3..33cbbdd 100644 --- a/server/server.go +++ b/server/server.go @@ -40,6 +40,8 @@ type Server struct { } environment Environment + + fs *Filesystem } // The build settings for a given server that impact docker container creation and @@ -54,7 +56,7 @@ type BuildSettings struct { // The relative weight for IO operations in a container. This is relative to other // containers on the system and should be a value between 10 and 1000. - IoWeight int64 `yaml:"io"` + IoWeight uint16 `yaml:"io"` // The percentage of CPU that this instance is allowed to consume relative to // the host. A value of 200% represents complete utilization of two cores. This @@ -65,6 +67,28 @@ type BuildSettings struct { DiskSpace int64 `yaml:"disk"` } +// Converts the CPU limit for a server build into a number that can be better understood +// by the Docker environment. If there is no limit set, return -1 which will indicate to +// Docker that it has unlimited CPU quota. +func (b *BuildSettings) ConvertedCpuLimit() int64 { + if b.CpuLimit == 0 { + return -1 + } + + return b.CpuLimit * 1000 +} + +// Returns the amount of swap available as a total in bytes. This is returned as the amount +// of memory available to the server initially, PLUS the amount of additional swap to include +// which is the format used by Docker. +func (b *BuildSettings) ConvertedSwap() int64 { + if b.Swap < 0 { + return -1 + } + + return (b.Swap * 1000000) + (b.MemoryLimit * 1000000) +} + // Defines the allocations available for a given server. When using the Docker environment // driver these correspond to mappings for the container that allow external connections. type Allocations struct { @@ -156,9 +180,19 @@ func FromConfiguration(data []byte, cfg DockerConfiguration) (*Server, error) { s.environment = env + s.fs = &Filesystem{ + // @todo adjust this to be configuration provided! + Root: "/srv/daemon-data", + Server: s, + } + return s, nil } +func (s *Server) Filesystem() *Filesystem { + return s.fs +} + // Determine if the server is bootable in it's current state or not. This will not // indicate why a server is not bootable, only if it is. func (s *Server) IsBootable() bool {