func (e *Environment) Readlog(lines int) ([]string, error) {
	r, err := e.client.ContainerLogs(context.Background(), e.Id, types.ContainerLogsOptions{
		ShowStdout: true,
		ShowStderr: true,
		Tail:       strconv.Itoa(lines),
	})
	if err != nil {
		return nil, errors.WithStack(err)
	}
	defer r.Close()
	var out []string
	scanner := bufio.NewScanner(r)
	for scanner.Scan() {
		out = append(out, scanner.Text())
	}
	return out, nil
}
Readlog reads the log file for the server. This does not care if the server
is running or not, it will simply try to read the last X bytes of the file
and return them.
package docker
import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"strconv"
	"strings"
	"time"
	"emperror.dev/errors"
	"github.com/apex/log"
	"github.com/docker/docker/api/types"
	"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"
	"github.com/pterodactyl/wings/config"
	"github.com/pterodactyl/wings/environment"
	"github.com/pterodactyl/wings/system"
)
var ErrNotAttached = errors.Sentinel("not attached to instance")
type noopWriter struct{}
var _ io.Writer = noopWriter{}
func (nw noopWriter) Write(b []byte) (int, error) {
	return len(b), nil
}
func (e *Environment) Attach(ctx context.Context) error {
	if e.IsAttached() {
		return nil
	}
	if err := e.followOutput(); err != nil {
		return err
	}
	opts := types.ContainerAttachOptions{
		Stdin:  true,
		Stdout: true,
		Stderr: true,
		Stream: true,
	}
	if st, err := e.client.ContainerAttach(ctx, e.Id, opts); err != nil {
		return err
	} else {
		e.SetStream(&st)
	}
	go func() {
		pollCtx, cancel := context.WithCancel(context.Background())
		defer cancel()
		defer e.stream.Close()
		defer func() {
			e.SetState(environment.ProcessOfflineState)
			e.SetStream(nil)
		}()
		go func() {
			if err := e.pollResources(pollCtx); err != nil {
				if !errors.Is(err, context.Canceled) {
					e.log().WithField("error", err).Error("error during environment resource polling")
				} else {
					e.log().Warn("stopping server resource polling: context canceled")
				}
			}
		}()
		c := new(noopWriter)
		if _, err := io.Copy(c, e.stream.Reader); err != nil {
			e.log().WithField("error", err).Error("could not copy from environment stream to noop writer")
		}
	}()
	return nil
}
func (e *Environment) InSituUpdate() error {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	if _, err := e.client.ContainerInspect(ctx, e.Id); err != nil {
		if client.IsErrNotFound(err) {
			return nil
		}
		return errors.Wrap(err, "environment/docker: could not inspect container")
	}
	if _, err := e.client.ContainerUpdate(ctx, e.Id, container.UpdateConfig{
		Resources: e.Configuration.Limits().AsContainerResources(),
	}); err != nil {
		return errors.Wrap(err, "environment/docker: could not update container")
	}
	return nil
}
func (e *Environment) Create() error {
	if _, err := e.client.ContainerInspect(context.Background(), e.Id); err == nil {
		return nil
	} else if !client.IsErrNotFound(err) {
		return errors.Wrap(err, "environment/docker: failed to inspect container")
	}
	if err := e.ensureImageExists(e.meta.Image); err != nil {
		return errors.WithStackIf(err)
	}
	a := e.Configuration.Allocations()
	evs := e.Configuration.EnvironmentVariables()
	for i, v := range evs {
		if v == "SERVER_IP=127.0.0.1" {
			evs[i] = "SERVER_IP=" + config.Get().Docker.Network.Interface
		}
	}
	conf := &container.Config{
		Hostname:     e.Id,
		Domainname:   config.Get().Docker.Domainname,
		User:         strconv.Itoa(config.Get().System.User.Uid),
		AttachStdin:  true,
		AttachStdout: true,
		AttachStderr: true,
		OpenStdin:    true,
		Tty:          true,
		ExposedPorts: a.Exposed(),
		Image:        strings.TrimPrefix(e.meta.Image, "~"),
		Env:          e.Configuration.EnvironmentVariables(),
		Labels: map[string]string{
			"Service":       "Pterodactyl",
			"ContainerType": "server_process",
		},
	}
	tmpfsSize := strconv.Itoa(int(config.Get().Docker.TmpfsSize))
	hostConf := &container.HostConfig{
		PortBindings: a.DockerBindings(),
		Mounts: e.convertMounts(),
		Tmpfs: map[string]string{
			"/tmp": "rw,exec,nosuid,size=" + tmpfsSize + "M",
		},
		Resources: e.Configuration.Limits().AsContainerResources(),
		DNS: config.Get().Docker.Network.Dns,
		LogConfig: container.LogConfig{
			Type: jsonfilelog.Name,
			Config: map[string]string{
				"max-size": "5m",
				"max-file": "1",
				"compress": "false",
			},
		},
		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: container.NetworkMode(config.Get().Docker.Network.Mode),
	}
	if _, err := e.client.ContainerCreate(context.Background(), conf, hostConf, nil, nil, e.Id); err != nil {
		return errors.Wrap(err, "environment/docker: failed to create container")
	}
	return nil
}
func (e *Environment) Destroy() error {
	e.SetState(environment.ProcessStoppingState)
	err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{
		RemoveVolumes: true,
		RemoveLinks:   false,
		Force:         true,
	})
	e.SetState(environment.ProcessOfflineState)
	if err != nil && client.IsErrNotFound(err) {
		return nil
	}
	return err
}
func (e *Environment) SendCommand(c string) error {
	if !e.IsAttached() {
		return errors.Wrap(ErrNotAttached, "environment/docker: cannot send command to container")
	}
	e.mu.RLock()
	defer e.mu.RUnlock()
	if e.meta.Stop.Type == "command" && c == e.meta.Stop.Value {
		e.SetState(environment.ProcessStoppingState)
	}
	_, err := e.stream.Conn.Write([]byte(c + "\n"))
	return errors.Wrap(err, "environment/docker: could not write to container stream")
}