environment(docker): fix podman compatibility (#151)
This commit is contained in:
parent
f577f5521f
commit
3337362955
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -23,6 +23,8 @@
|
|||
# ignore configuration file
|
||||
/config.yml
|
||||
/config*.yml
|
||||
/config.yaml
|
||||
/config*.yaml
|
||||
|
||||
# Ignore Vagrant stuff
|
||||
/.vagrant
|
||||
|
|
|
@ -65,7 +65,7 @@ func newDiagnosticsCommand() *cobra.Command {
|
|||
// - the docker debug output
|
||||
// - running docker containers
|
||||
// - logs
|
||||
func diagnosticsCmdRun(cmd *cobra.Command, args []string) {
|
||||
func diagnosticsCmdRun(*cobra.Command, []string) {
|
||||
questions := []*survey.Question{
|
||||
{
|
||||
Name: "IncludeEndpoints",
|
||||
|
|
|
@ -16,9 +16,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pterodactyl/wings/internal/cron"
|
||||
"github.com/pterodactyl/wings/internal/database"
|
||||
|
||||
"github.com/NYTimes/logrotate"
|
||||
"github.com/apex/log"
|
||||
"github.com/apex/log/handlers/multi"
|
||||
|
@ -31,6 +28,8 @@ import (
|
|||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/environment"
|
||||
"github.com/pterodactyl/wings/internal/cron"
|
||||
"github.com/pterodactyl/wings/internal/database"
|
||||
"github.com/pterodactyl/wings/loggers/cli"
|
||||
"github.com/pterodactyl/wings/remote"
|
||||
"github.com/pterodactyl/wings/router"
|
||||
|
@ -111,7 +110,6 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
|||
log.WithField("error", err).Fatal("failed to configure system directories for pterodactyl")
|
||||
return
|
||||
}
|
||||
log.WithField("username", config.Get().System.User).Info("checking for pterodactyl system user")
|
||||
if err := config.EnsurePterodactylUser(); err != nil {
|
||||
log.WithField("error", err).Fatal("failed to create pterodactyl system user")
|
||||
}
|
||||
|
@ -364,7 +362,7 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
|||
return
|
||||
}
|
||||
|
||||
// Check if main http server should run with TLS. Otherwise reset the TLS
|
||||
// Check if main http server should run with TLS. Otherwise, reset the TLS
|
||||
// config on the server and then serve it over normal HTTP.
|
||||
if api.Ssl.Enabled {
|
||||
if err := s.ListenAndServeTLS(api.Ssl.CertificateFile, api.Ssl.KeyFile); err != nil {
|
||||
|
|
|
@ -152,9 +152,23 @@ type SystemConfiguration struct {
|
|||
// Definitions for the user that gets created to ensure that we can quickly access
|
||||
// this information without constantly having to do a system lookup.
|
||||
User struct {
|
||||
Uid int
|
||||
Gid int
|
||||
}
|
||||
// Rootless controls settings related to rootless container daemons.
|
||||
Rootless struct {
|
||||
// Enabled controls whether rootless containers are enabled.
|
||||
Enabled bool `yaml:"enabled" default:"false"`
|
||||
// ContainerUID controls the UID of the user inside the container.
|
||||
// This should likely be set to 0 so the container runs as the user
|
||||
// running Wings.
|
||||
ContainerUID int `yaml:"container_uid" default:"0"`
|
||||
// ContainerGID controls the GID of the user inside the container.
|
||||
// This should likely be set to 0 so the container runs as the user
|
||||
// running Wings.
|
||||
ContainerGID int `yaml:"container_gid" default:"0"`
|
||||
} `yaml:"rootless"`
|
||||
|
||||
Uid int `yaml:"uid"`
|
||||
Gid int `yaml:"gid"`
|
||||
} `yaml:"user"`
|
||||
|
||||
// The amount of time in seconds that can elapse before a server's disk space calculation is
|
||||
// considered stale and a re-check should occur. DANGER: setting this value too low can seriously
|
||||
|
@ -425,6 +439,19 @@ func EnsurePterodactylUser() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
if _config.System.User.Rootless.Enabled {
|
||||
log.Info("rootless mode is enabled, skipping user creation...")
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_config.System.Username = u.Username
|
||||
_config.System.User.Uid = system.MustInt(u.Uid)
|
||||
_config.System.User.Gid = system.MustInt(u.Gid)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.WithField("username", _config.System.Username).Info("checking for pterodactyl system user")
|
||||
u, err := user.Lookup(_config.System.Username)
|
||||
// If an error is returned but it isn't the unknown user error just abort
|
||||
// the process entirely. If we did find a user, return it immediately.
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"sort"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
|
@ -86,6 +87,22 @@ type DockerConfiguration struct {
|
|||
// if the value is "host", then the pterodactyl containers are started with user namespace
|
||||
// remapping disabled
|
||||
UsernsMode string `default:"" json:"userns_mode" yaml:"userns_mode"`
|
||||
|
||||
LogConfig struct {
|
||||
Type string `default:"local" json:"type" yaml:"type"`
|
||||
Config map[string]string `default:"{\"max-size\":\"5m\",\"max-file\":\"1\",\"compress\":\"false\",\"mode\":\"non-blocking\"}" json:"config" yaml:"config"`
|
||||
} `json:"log_config" yaml:"log_config"`
|
||||
}
|
||||
|
||||
func (c DockerConfiguration) ContainerLogConfig() container.LogConfig {
|
||||
if c.LogConfig.Type == "" {
|
||||
return container.LogConfig{}
|
||||
}
|
||||
|
||||
return container.LogConfig{
|
||||
Type: c.LogConfig.Type,
|
||||
Config: c.LogConfig.Config,
|
||||
}
|
||||
}
|
||||
|
||||
// RegistryConfiguration defines the authentication credentials for a given
|
||||
|
|
|
@ -16,7 +16,6 @@ import (
|
|||
"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/local"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/environment"
|
||||
|
@ -43,17 +42,13 @@ func (nw noopWriter) Write(b []byte) (int, error) {
|
|||
//
|
||||
// Calling this function will poll resources for the container in the background
|
||||
// until the container is stopped. The context provided to this function is used
|
||||
// for the purposes of attaching to the container, a seecond context is created
|
||||
// for the purposes of attaching to the container, a second context is created
|
||||
// within the function for managing polling.
|
||||
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,
|
||||
|
@ -90,20 +85,13 @@ func (e *Environment) Attach(ctx context.Context) error {
|
|||
}
|
||||
}()
|
||||
|
||||
// Block the completion of this routine until the container is no longer running. This allows
|
||||
// the pollResources function to run until it needs to be stopped. Because the container
|
||||
// can be polled for resource usage, even when stopped, we need to have this logic present
|
||||
// in order to cancel the context and therefore stop the routine that is spawned.
|
||||
//
|
||||
// For now, DO NOT use client#ContainerWait from the Docker package. There is a nasty
|
||||
// bug causing containers to hang on deletion and cause servers to lock up on the system.
|
||||
//
|
||||
// This weird code isn't intuitive, but it keeps the function from ending until the container
|
||||
// is stopped and therefore the stream reader ends up closed.
|
||||
// @see https://github.com/moby/moby/issues/41827
|
||||
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")
|
||||
if err := system.ScanReader(e.stream.Reader, func(v []byte) {
|
||||
e.logCallbackMx.Lock()
|
||||
defer e.logCallbackMx.Unlock()
|
||||
e.logCallback(v)
|
||||
}); err != nil && err != io.EOF {
|
||||
log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output")
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -163,14 +151,14 @@ func (e *Environment) Create() error {
|
|||
return errors.WithStackIf(err)
|
||||
}
|
||||
|
||||
cfg := config.Get()
|
||||
a := e.Configuration.Allocations()
|
||||
|
||||
evs := e.Configuration.EnvironmentVariables()
|
||||
for i, v := range evs {
|
||||
// Convert 127.0.0.1 to the pterodactyl0 network interface if the environment is Docker
|
||||
// so that the server operates as expected.
|
||||
if v == "SERVER_IP=127.0.0.1" {
|
||||
evs[i] = "SERVER_IP=" + config.Get().Docker.Network.Interface
|
||||
evs[i] = "SERVER_IP=" + cfg.Docker.Network.Interface
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,8 +174,7 @@ func (e *Environment) Create() error {
|
|||
|
||||
conf := &container.Config{
|
||||
Hostname: e.Id,
|
||||
Domainname: config.Get().Docker.Domainname,
|
||||
User: strconv.Itoa(config.Get().System.User.Uid) + ":" + strconv.Itoa(config.Get().System.User.Gid),
|
||||
Domainname: cfg.Docker.Domainname,
|
||||
AttachStdin: true,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
|
@ -199,7 +186,14 @@ func (e *Environment) Create() error {
|
|||
Labels: labels,
|
||||
}
|
||||
|
||||
networkMode := container.NetworkMode(config.Get().Docker.Network.Mode)
|
||||
// Set the user running the container properly depending on what mode we are operating in.
|
||||
if cfg.System.User.Rootless.Enabled {
|
||||
conf.User = fmt.Sprintf("%d:%d", cfg.System.User.Rootless.ContainerUID, cfg.System.User.Rootless.ContainerGID)
|
||||
} else {
|
||||
conf.User = strconv.Itoa(cfg.System.User.Uid) + ":" + strconv.Itoa(cfg.System.User.Gid)
|
||||
}
|
||||
|
||||
networkMode := container.NetworkMode(cfg.Docker.Network.Mode)
|
||||
if a.ForceOutgoingIP {
|
||||
e.log().Debug("environment/docker: forcing outgoing IP address")
|
||||
networkName := strings.ReplaceAll(e.Id, "-", "")
|
||||
|
@ -238,28 +232,20 @@ func (e *Environment) Create() error {
|
|||
// 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=" + strconv.Itoa(int(config.Get().Docker.TmpfsSize)) + "M",
|
||||
"/tmp": "rw,exec,nosuid,size=" + strconv.Itoa(int(cfg.Docker.TmpfsSize)) + "M",
|
||||
},
|
||||
|
||||
// Define resource limits for the container based on the data passed through
|
||||
// from the Panel.
|
||||
Resources: e.Configuration.Limits().AsContainerResources(),
|
||||
|
||||
DNS: config.Get().Docker.Network.Dns,
|
||||
DNS: cfg.Docker.Network.Dns,
|
||||
|
||||
// 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: local.Name,
|
||||
Config: map[string]string{
|
||||
"max-size": "5m",
|
||||
"max-file": "1",
|
||||
"compress": "false",
|
||||
"mode": "non-blocking",
|
||||
},
|
||||
},
|
||||
LogConfig: cfg.Docker.ContainerLogConfig(),
|
||||
|
||||
SecurityOpt: []string{"no-new-privileges"},
|
||||
ReadonlyRootfs: true,
|
||||
|
@ -268,7 +254,7 @@ func (e *Environment) Create() error {
|
|||
"fowner", "fsetid", "net_bind_service", "sys_chroot", "setfcap",
|
||||
},
|
||||
NetworkMode: networkMode,
|
||||
UsernsMode: container.UsernsMode(config.Get().Docker.UsernsMode),
|
||||
UsernsMode: container.UsernsMode(cfg.Docker.UsernsMode),
|
||||
}
|
||||
|
||||
if _, err := e.client.ContainerCreate(ctx, conf, hostConf, nil, nil, e.Id); err != nil {
|
||||
|
@ -349,59 +335,6 @@ func (e *Environment) Readlog(lines int) ([]string, error) {
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// Attaches to the log for the container. This avoids us missing crucial output
|
||||
// that happens in the split seconds before the code moves from 'Starting' to
|
||||
// 'Attaching' on the process.
|
||||
func (e *Environment) followOutput() error {
|
||||
if exists, err := e.Exists(); !exists {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(fmt.Sprintf("no such container: %s", e.Id))
|
||||
}
|
||||
|
||||
opts := types.ContainerLogsOptions{
|
||||
ShowStderr: true,
|
||||
ShowStdout: true,
|
||||
Follow: true,
|
||||
Since: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
reader, err := e.client.ContainerLogs(context.Background(), e.Id, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go e.scanOutput(reader)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Environment) scanOutput(reader io.ReadCloser) {
|
||||
defer reader.Close()
|
||||
|
||||
if err := system.ScanReader(reader, func(v []byte) {
|
||||
e.logCallbackMx.Lock()
|
||||
defer e.logCallbackMx.Unlock()
|
||||
e.logCallback(v)
|
||||
}); err != nil && err != io.EOF {
|
||||
log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output")
|
||||
return
|
||||
}
|
||||
|
||||
// Return here if the server is offline or currently stopping.
|
||||
if e.State() == environment.ProcessStoppingState || e.State() == environment.ProcessOfflineState {
|
||||
return
|
||||
}
|
||||
|
||||
// Close the current reader before starting a new one, the defer will still run,
|
||||
// but it will do nothing if we already closed the stream.
|
||||
_ = reader.Close()
|
||||
|
||||
// Start following the output of the server again.
|
||||
go e.followOutput()
|
||||
}
|
||||
|
||||
// Pulls the image from Docker. If there is an error while pulling the image
|
||||
// from the source but the image already exists locally, we will report that
|
||||
// error to the logger but continue with the process.
|
||||
|
|
|
@ -96,7 +96,7 @@ func (e *Environment) SetStream(s *types.HijackedResponse) {
|
|||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
// IsAttached determine if the this process is currently attached to the
|
||||
// IsAttached determines if this process is currently attached to the
|
||||
// container instance by checking if the stream is nil or not.
|
||||
func (e *Environment) IsAttached() bool {
|
||||
e.mu.RLock()
|
||||
|
|
|
@ -39,7 +39,7 @@ func (e *Environment) OnBeforeStart(ctx context.Context) error {
|
|||
//
|
||||
// This won't actually run an installation process however, it is just here to ensure the
|
||||
// environment gets created properly if it is missing and the server is started. We're making
|
||||
// an assumption that all of the files will still exist at this point.
|
||||
// an assumption that all the files will still exist at this point.
|
||||
if err := e.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ func (e *Environment) Start(ctx context.Context) error {
|
|||
}
|
||||
|
||||
// If we cannot start & attach to the container in 30 seconds something has gone
|
||||
// quite sideways and we should stop trying to avoid a hanging situation.
|
||||
// quite sideways, and we should stop trying to avoid a hanging situation.
|
||||
actx, cancel := context.WithTimeout(ctx, time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
|
|
|
@ -134,7 +134,11 @@ func calculateDockerAbsoluteCpu(pStats types.CPUStats, stats types.CPUStats) flo
|
|||
|
||||
percent := 0.0
|
||||
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
||||
percent = (cpuDelta / systemDelta) * cpus * 100.0
|
||||
percent = (cpuDelta / systemDelta) * 100.0
|
||||
|
||||
if cpus > 0 {
|
||||
percent *= cpus
|
||||
}
|
||||
}
|
||||
|
||||
return math.Round(percent*1000) / 1000
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -419,7 +420,12 @@ func (ip *InstallationProcess) Execute() (string, error) {
|
|||
},
|
||||
}
|
||||
|
||||
tmpfsSize := strconv.Itoa(int(config.Get().Docker.TmpfsSize))
|
||||
cfg := config.Get()
|
||||
if cfg.System.User.Rootless.Enabled {
|
||||
conf.User = fmt.Sprintf("%d:%d", cfg.System.User.Rootless.ContainerUID, cfg.System.User.Rootless.ContainerGID)
|
||||
}
|
||||
|
||||
tmpfsSize := strconv.Itoa(int(cfg.Docker.TmpfsSize))
|
||||
hostConf := &container.HostConfig{
|
||||
Mounts: []mount.Mount{
|
||||
{
|
||||
|
@ -439,18 +445,11 @@ func (ip *InstallationProcess) Execute() (string, error) {
|
|||
Tmpfs: map[string]string{
|
||||
"/tmp": "rw,exec,nosuid,size=" + tmpfsSize + "M",
|
||||
},
|
||||
DNS: config.Get().Docker.Network.Dns,
|
||||
LogConfig: container.LogConfig{
|
||||
Type: "local",
|
||||
Config: map[string]string{
|
||||
"max-size": "5m",
|
||||
"max-file": "1",
|
||||
"compress": "false",
|
||||
},
|
||||
},
|
||||
DNS: cfg.Docker.Network.Dns,
|
||||
LogConfig: cfg.Docker.ContainerLogConfig(),
|
||||
Privileged: true,
|
||||
NetworkMode: container.NetworkMode(config.Get().Docker.Network.Mode),
|
||||
UsernsMode: container.UsernsMode(config.Get().Docker.UsernsMode),
|
||||
NetworkMode: container.NetworkMode(cfg.Docker.Network.Mode),
|
||||
UsernsMode: container.UsernsMode(cfg.Docker.UsernsMode),
|
||||
}
|
||||
|
||||
// Ensure the root directory for the server exists properly before attempting
|
||||
|
|
Loading…
Reference in New Issue
Block a user