diff --git a/config/config.go b/config/config.go index b5ef9bc..8ba2976 100644 --- a/config/config.go +++ b/config/config.go @@ -108,29 +108,48 @@ type SftpConfiguration struct { ReadOnly bool `default:"false" yaml:"read_only"` } +type dockerNetworkInterfaces struct { + V4 struct { + Subnet string `default:"172.18.0.0/16"` + Gateway string `default:"172.18.0.1"` + } + + V6 struct { + Subnet string `default:"fdba:17c8:6c94::/64"` + Gateway string `default:"fdba:17c8:6c94::1011"` + } +} + +type DockerNetworkConfiguration struct { + // The interface that should be used to create the network. Must not conflict + // with any other interfaces in use by Docker or on the system. + Interface string `default:"172.18.0.1"` + + // The name of the network to use. If this network already exists it will not + // be created. If it is not found, a new network will be created using the interface + // defined. + Name string `default:"pterodactyl_nw"` + ISPN bool `default:"false" yaml:"ispn"` + Driver string `default:"bridge"` + IsInternal bool `default:"false" yaml:"is_internal"` + EnableICC bool `default:"true" yaml:"enable_icc"` + Interfaces *dockerNetworkInterfaces `yaml:"interfaces"` +} + // Defines the docker configuration used by the daemon when interacting with // containers and networks on the system. type DockerConfiguration struct { // Network configuration that should be used when creating a new network // for containers run through the daemon. - Network struct { - // The interface that should be used to create the network. Must not conflict - // with any other interfaces in use by Docker or on the system. - Interface string - - // The name of the network to use. If this network already exists it will not - // be created. If it is not found, a new network will be created using the interface - // defined. - Name string - } + Network *DockerNetworkConfiguration `yaml:"network"` // If true, container images will be updated when a server starts if there // is an update available. If false the daemon will not attempt updates and will // defer to the host system to manage image updates. - UpdateImages bool `yaml:"update_images"` + UpdateImages bool `default:"true" yaml:"update_images"` // The location of the Docker socket. - Socket string + Socket string `default:"/var/run/docker.sock"` // Defines the location of the timezone file on the host system that should // be mounted into the created containers so that they all use the same time. @@ -167,8 +186,11 @@ func (c *Configuration) SetDefaults() { Username: "pterodactyl", Data: "/srv/daemon-data", TimezonePath: "/etc/timezone", + Sftp: &SftpConfiguration{}, } + defaults.SetDefaults(c.System.Sftp) + // By default the internal webserver should bind to all interfaces and // be served on port 8080. c.Api = &ApiConfiguration{ @@ -195,11 +217,14 @@ func (c *Configuration) SetDefaults() { c.Throttles.CheckInterval = 100 // Configure the defaults for Docker connection and networks. - c.Docker = &DockerConfiguration{} - c.Docker.UpdateImages = true - c.Docker.Socket = "/var/run/docker.sock" - c.Docker.Network.Name = "pterodactyl_nw" - c.Docker.Network.Interface = "172.18.0.1" + c.Docker = &DockerConfiguration{ + Network: &DockerNetworkConfiguration{ + Interfaces: &dockerNetworkInterfaces{}, + }, + } + defaults.SetDefaults(c.Docker) + defaults.SetDefaults(c.Docker.Network) + defaults.SetDefaults(c.Docker.Network.Interfaces) } // Reads the configuration from the provided file and returns the configuration @@ -210,13 +235,8 @@ func ReadConfiguration(path string) (*Configuration, error) { return nil, err } - sftp :=new(SftpConfiguration) - defaults.SetDefaults(sftp) - c := new(Configuration) - c.System = new(SystemConfiguration) - c.System.Sftp = sftp - defaults.SetDefaults(c) + c.SetDefaults() // Replace environment variables within the configuration file with their // values from the host system. diff --git a/environment.go b/environment.go new file mode 100644 index 0000000..d72c1a0 --- /dev/null +++ b/environment.go @@ -0,0 +1,95 @@ +package main + +import ( + "context" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + "github.com/pterodactyl/wings/config" + "go.uber.org/zap" +) + +// Configures the required network for the docker environment. +func ConfigureDockerEnvironment(c *config.DockerConfiguration) error { + // Ensure the required docker network exists on the system. + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return err + } + + resource, err := cli.NetworkInspect(context.Background(), c.Network.Name, types.NetworkInspectOptions{}) + if err != nil && client.IsErrNotFound(err) { + zap.S().Infow("creating missing pterodactyl0 interface, this could take a few seconds...") + return createDockerNetwork(cli, c) + } else if err != nil { + zap.S().Fatalw("failed to create required docker network for containers", zap.Error(err)) + } + + switch resource.Driver { + case "host": + c.Network.Interface = "127.0.0.1" + c.Network.ISPN = false + return nil + case "overlay": + case "weavemesh": + c.Network.Interface = "" + c.Network.ISPN = true + return nil + default: + c.Network.ISPN = false + } + + return nil +} + +// Creates a new network on the machine if one does not exist already. +func createDockerNetwork(cli *client.Client, c *config.DockerConfiguration) error { + _, err := cli.NetworkCreate(context.Background(), c.Network.Name, types.NetworkCreate{ + Driver: c.Network.Driver, + EnableIPv6: true, + Internal: c.Network.IsInternal, + IPAM: &network.IPAM{ + Config: []network.IPAMConfig{ + { + Subnet: c.Network.Interfaces.V4.Subnet, + Gateway: c.Network.Interfaces.V4.Gateway, + }, + { + Subnet: c.Network.Interfaces.V6.Subnet, + Gateway: c.Network.Interfaces.V6.Gateway, + }, + }, + }, + Options: map[string]string{ + "encryption": "false", + "com.docker.network.bridge.default_bridge": "false", + "com.docker.network.bridge.enable_icc": "true", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", + "com.docker.network.bridge.name": "pterodactyl0", + "com.docker.network.driver.mtu": "1500", + }, + }) + + if err != nil { + return err + } + + switch c.Network.Driver { + case "host": + c.Network.Interface = "127.0.0.1" + c.Network.ISPN = false + break + case "overlay": + case "weavemesh": + c.Network.Interface = "" + c.Network.ISPN = true + break + default: + c.Network.Interface = c.Network.Interfaces.V4.Gateway + c.Network.ISPN = false + break + } + + return nil +} diff --git a/wings.go b/wings.go index 987adf6..deda418 100644 --- a/wings.go +++ b/wings.go @@ -12,6 +12,7 @@ import ( "github.com/remeh/sizedwaitgroup" "go.uber.org/zap" "net/http" + "os" ) // Entrypoint for the Wings application. Configures the logger and checks any @@ -70,6 +71,15 @@ func main() { return } + if err := ConfigureDockerEnvironment(c.Docker); err != nil { + zap.S().Fatalw("failed to configure docker environment", zap.Error(errors.WithStack(err))) + os.Exit(1) + } + + if err := c.WriteToDisk(); err != nil { + zap.S().Errorw("failed to save configuration to disk", zap.Error(errors.WithStack(err))) + } + // Just for some nice log output. for _, s := range server.GetServers().All() { zap.S().Infow("loaded configuration for server", zap.String("server", s.Uuid)) @@ -143,7 +153,7 @@ func main() { } r := &Router{ - token: c.AuthenticationToken, + token: c.AuthenticationToken, upgrader: websocket.Upgrader{ // Ensure that the websocket request is originating from the Panel itself, // and not some other location.