Merge pull request #5 from pterodactyl/feature/docker-environment
Feature/docker environment
This commit is contained in:
commit
4ef7bef4fb
|
@ -1,10 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "Provisioning development environment for Pterodactyl go daemon."
|
||||
cp /home/ubuntu/go/github.com/pterodactyl/wings.go/.dev/vagrant/motd.txt /etc/motd
|
||||
cp /home/vagrant/go/github.com/pterodactyl/wings.go/.dev/vagrant/motd.txt /etc/motd
|
||||
|
||||
chown -R ubuntu:ubuntu /home/ubuntu/go
|
||||
chown -R ubuntu:ubuntu /srv
|
||||
chown -R vagrant:vagrant /home/vagrant/go
|
||||
chown -R vagrant:vagrant /srv
|
||||
|
||||
echo "Update apt repositories"
|
||||
sudo add-apt-repository ppa:longsleep/golang-backports
|
||||
|
@ -13,24 +13,28 @@ apt-get update > /dev/null
|
|||
echo "Install docker"
|
||||
curl -sSL https://get.docker.com/ | sh
|
||||
systemctl enable docker
|
||||
usermod -aG docker ubuntu
|
||||
usermod -aG docker vagrant
|
||||
|
||||
echo "Install go"
|
||||
apt-get install -y golang-go
|
||||
echo "export GOPATH=/home/ubuntu/go" >> /home/ubuntu/.profile
|
||||
echo "export GOPATH=/home/vagrant/go" >> /home/vagrant/.profile
|
||||
export GOPATH=/go
|
||||
echo 'export PATH=$PATH:$GOPATH/bin' >> /home/ubuntu/.profile
|
||||
echo 'export PATH=$PATH:$GOPATH/bin' >> /home/vagrant/.profile
|
||||
|
||||
echo "Install go dep"
|
||||
sudo -H -u ubuntu bash -c 'go get -u github.com/golang/dep/cmd/dep'
|
||||
sudo -H -u vagrant bash -c 'go get -u github.com/golang/dep/cmd/dep'
|
||||
|
||||
echo "Install delve for debugging"
|
||||
sudo -H -u ubuntu bash -c 'go get -u github.com/derekparker/delve/cmd/dlv'
|
||||
sudo -H -u vagrant bash -c 'go get -u github.com/derekparker/delve/cmd/dlv'
|
||||
|
||||
echo "Install additional dependencies"
|
||||
apt-get -y install mercurial #tar unzip make gcc g++ python > /dev/null
|
||||
|
||||
echo "Install ctop for fancy container monitoring"
|
||||
wget https://github.com/bcicen/ctop/releases/download/v0.7.1/ctop-0.7.1-linux-amd64 -O /usr/local/bin/ctop
|
||||
chmod +x /usr/local/bin/ctop
|
||||
|
||||
echo " ------------"
|
||||
echo "Gopath is /home/ubuntu/go"
|
||||
echo "The project is mounted to /home/ubuntu/go/src/github.com/pterodactyl/wings.go"
|
||||
echo "Gopath is /home/vagrant/go"
|
||||
echo "The project is mounted to /home/vagrant/go/src/github.com/pterodactyl/wings"
|
||||
echo "Provisioning is completed."
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -39,6 +39,8 @@ wings.exe
|
|||
# IDE/Editor files (VS Code)
|
||||
/.vscode
|
||||
|
||||
# test files
|
||||
test_*/
|
||||
|
||||
# Keep all gitkeep files (This needs to stay at the bottom)
|
||||
!.gitkeep
|
||||
|
|
14
.travis.yml
14
.travis.yml
|
@ -6,23 +6,17 @@ go:
|
|||
services:
|
||||
- docker
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'ppa:masterminds/glide'
|
||||
packages:
|
||||
- glide
|
||||
|
||||
install:
|
||||
- mkdir -p $GOPATH/bin
|
||||
|
||||
# Install other tools
|
||||
# Install used tools
|
||||
- go get github.com/golang/dep/cmd/dep
|
||||
- go get github.com/mitchellh/gox
|
||||
- go get github.com/haya14busa/goverage
|
||||
- go get github.com/schrej/godacov
|
||||
|
||||
# Install project dependencies with glide
|
||||
- glide install
|
||||
# Install project dependencies with dep
|
||||
- dep ensure
|
||||
|
||||
script:
|
||||
- make cross-build
|
||||
|
|
156
Gopkg.lock
generated
156
Gopkg.lock
generated
|
@ -1,15 +1,6 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/Azure/go-ansiterm"
|
||||
packages = [
|
||||
".",
|
||||
"winterm"
|
||||
]
|
||||
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/Microsoft/go-winio"
|
||||
packages = ["."]
|
||||
|
@ -17,22 +8,10 @@
|
|||
version = "v0.4.7"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/Nvveen/Gotty"
|
||||
packages = ["."]
|
||||
revision = "cd527374f1e5bff4938207604a14f2e38a9cf512"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/StackExchange/wmi"
|
||||
packages = ["."]
|
||||
revision = "5d049714c4a64225c3c79a7cf7d02f7fb5b96338"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/containerd/continuity"
|
||||
packages = ["pathdriver"]
|
||||
revision = "d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371"
|
||||
version = "1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
|
@ -40,40 +19,45 @@
|
|||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/distribution"
|
||||
packages = [
|
||||
"digestset",
|
||||
"reference"
|
||||
]
|
||||
revision = "83389a148052d74ac602f5f1d62f86ff2f3c4aa5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/docker"
|
||||
packages = [
|
||||
"api",
|
||||
"api/types",
|
||||
"api/types/blkiodev",
|
||||
"api/types/container",
|
||||
"api/types/events",
|
||||
"api/types/filters",
|
||||
"api/types/image",
|
||||
"api/types/mount",
|
||||
"api/types/network",
|
||||
"api/types/registry",
|
||||
"api/types/strslice",
|
||||
"api/types/swarm",
|
||||
"api/types/swarm/runtime",
|
||||
"api/types/time",
|
||||
"api/types/versions",
|
||||
"opts",
|
||||
"pkg/archive",
|
||||
"pkg/fileutils",
|
||||
"pkg/homedir",
|
||||
"pkg/idtools",
|
||||
"pkg/ioutils",
|
||||
"pkg/jsonmessage",
|
||||
"pkg/longpath",
|
||||
"pkg/mount",
|
||||
"pkg/pools",
|
||||
"pkg/stdcopy",
|
||||
"pkg/system",
|
||||
"pkg/term",
|
||||
"pkg/term/windows"
|
||||
"api/types/volume",
|
||||
"client"
|
||||
]
|
||||
revision = "fe8aac6f5ae413a967adb0adad0b54abdfb825c4"
|
||||
revision = "e3831a62a3052472d7252049bc59835d5d7dc8bd"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/go-connections"
|
||||
packages = ["nat"]
|
||||
packages = [
|
||||
"nat",
|
||||
"sockets",
|
||||
"tlsconfig"
|
||||
]
|
||||
revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d"
|
||||
version = "v0.3.0"
|
||||
|
||||
|
@ -89,12 +73,6 @@
|
|||
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
||||
version = "v1.4.7"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fsouza/go-dockerclient"
|
||||
packages = ["."]
|
||||
revision = "2ff310040c161b75fa19fb9b287a90a6e03c0012"
|
||||
version = "1.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/gin-contrib/sse"
|
||||
|
@ -132,6 +110,18 @@
|
|||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/google/jsonapi"
|
||||
packages = ["."]
|
||||
revision = "46d3ced0434461be12e555852e2f1a9ed382e139"
|
||||
version = "1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
|
@ -139,6 +129,7 @@
|
|||
".",
|
||||
"hcl/ast",
|
||||
"hcl/parser",
|
||||
"hcl/printer",
|
||||
"hcl/scanner",
|
||||
"hcl/strconv",
|
||||
"hcl/token",
|
||||
|
@ -146,7 +137,7 @@
|
|||
"json/scanner",
|
||||
"json/token"
|
||||
]
|
||||
revision = "23c074d0eceb2b8a5bfdbb271ab780cde70f05a8"
|
||||
revision = "f40e974e75af4e271d97ce0fc917af5898ae7bda"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
|
@ -154,6 +145,18 @@
|
|||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/lestrrat-go/file-rotatelogs"
|
||||
packages = ["."]
|
||||
revision = "9df8b44f21785240553882138c5df2e9cc1db910"
|
||||
version = "v2.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/lestrrat/go-strftime"
|
||||
packages = ["."]
|
||||
revision = "ba3bf9c1d0421aa146564a632931730344f1f9f1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/magiconair/properties"
|
||||
packages = ["."]
|
||||
|
@ -170,7 +173,7 @@
|
|||
branch = "master"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
packages = ["."]
|
||||
revision = "a4e142e9c047c904fa2f1e144d9a84e6133024bc"
|
||||
revision = "00c29f56e2386353d58c599509e8dc3801b0d716"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/opencontainers/go-digest"
|
||||
|
@ -187,15 +190,6 @@
|
|||
revision = "d60099175f88c47cd379c4738d158884749ed235"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/opencontainers/runc"
|
||||
packages = [
|
||||
"libcontainer/system",
|
||||
"libcontainer/user"
|
||||
]
|
||||
revision = "baf6536d6259209c3edfa2b22237af82942d3dfa"
|
||||
version = "v0.1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pelletier/go-toml"
|
||||
packages = ["."]
|
||||
|
@ -214,29 +208,26 @@
|
|||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/rifflock/lfshook"
|
||||
packages = ["."]
|
||||
revision = "bf539943797a1f34c1f502d07de419b5238ae6c6"
|
||||
version = "v2.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/shirou/gopsutil"
|
||||
packages = [
|
||||
"cpu",
|
||||
"host",
|
||||
"internal/common",
|
||||
"mem",
|
||||
"net",
|
||||
"process"
|
||||
"internal/common"
|
||||
]
|
||||
revision = "543a05cce094293c7747322720256bee15d88a12"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/shirou/w32"
|
||||
packages = ["."]
|
||||
revision = "bb4de0191aa41b5507caa14b0650cdbddcd9280b"
|
||||
revision = "5776ff9c7c5d063d574ef53d740f75c68b448e53"
|
||||
version = "v2.18.02"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
packages = ["."]
|
||||
revision = "d682213848ed68c0a260ca37d6dd5ace8423f5ba"
|
||||
version = "v1.0.4"
|
||||
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
|
||||
version = "v1.0.5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/afero"
|
||||
|
@ -256,8 +247,8 @@
|
|||
[[projects]]
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
|
||||
version = "v0.0.1"
|
||||
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -274,8 +265,8 @@
|
|||
[[projects]]
|
||||
name = "github.com/spf13/viper"
|
||||
packages = ["."]
|
||||
revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
|
||||
version = "v1.0.0"
|
||||
revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
|
@ -293,16 +284,17 @@
|
|||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["ssh/terminal"]
|
||||
revision = "432090b8f568c018896cd8a0fb0345872bbac6ce"
|
||||
revision = "12892e8c234f4fe6f6803f052061de9057903bb2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp"
|
||||
"context/ctxhttp",
|
||||
"proxy"
|
||||
]
|
||||
revision = "cbe0f9307d0156177f9dd5dc85da1a31abc5f2fb"
|
||||
revision = "b68f30494add4df6bd8ef5e82803f308e7f7c59c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -311,10 +303,9 @@
|
|||
"unix",
|
||||
"windows"
|
||||
]
|
||||
revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
|
||||
revision = "378d26f46672a356c46195c28f61bdb4c0a781dd"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"internal/gen",
|
||||
|
@ -324,7 +315,8 @@
|
|||
"unicode/cldr",
|
||||
"unicode/norm"
|
||||
]
|
||||
revision = "4e4a3210bb54bb31f6ab2cdca2edcc0b50c420c1"
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/go-playground/validator.v8"
|
||||
|
@ -333,14 +325,14 @@
|
|||
version = "v8.18.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
|
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "3f1bdf5882e27f292b13d4b95c9f51eb1a7609af61ccda0fae50a806b0a2ba4f"
|
||||
inputs-digest = "8e17495db05ff2c85d228a20157c41c223e428d28217e14f89ab7b764a8706dd"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
41
Gopkg.toml
41
Gopkg.toml
|
@ -1,6 +1,6 @@
|
|||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
|
@ -25,33 +25,38 @@
|
|||
# unused-packages = true
|
||||
|
||||
|
||||
#[[constraint]]
|
||||
# branch = "develop"
|
||||
# name = "github.com/pterodactyl/wings"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/google/jsonapi"
|
||||
version = "~1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
version = "~1.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/shirou/gopsutil"
|
||||
version = "2.17.6"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
version = "~1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gin-gonic/gin"
|
||||
version = "1.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/lestrrat/go-file-rotatelogs"
|
||||
name = "github.com/lestrrat-go/file-rotatelogs"
|
||||
version = "2.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/rifflock/lfshook"
|
||||
version = "2.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/shirou/gopsutil"
|
||||
revision = "543a05cce094293c7747322720256bee15d88a12"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
version = "1.0.4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/viper"
|
||||
|
@ -64,3 +69,11 @@
|
|||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/docker/docker"
|
||||
revision = "e3831a62a3052472d7252049bc59835d5d7dc8bd"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/docker/distribution"
|
||||
branch = "master"
|
||||
|
|
4
Vagrantfile
vendored
4
Vagrantfile
vendored
|
@ -1,7 +1,7 @@
|
|||
Vagrant.configure("2") do |cfg|
|
||||
cfg.vm.box = "ubuntu/xenial64"
|
||||
cfg.vm.box = "bento/ubuntu-16.04"
|
||||
|
||||
cfg.vm.synced_folder "./", "/home/ubuntu/go/src/github.com/pterodactyl/wings"
|
||||
cfg.vm.synced_folder "./", "/home/vagrant/go/src/github.com/pterodactyl/wings"
|
||||
|
||||
cfg.vm.provision :shell, path: ".dev/vagrant/provision.sh"
|
||||
|
||||
|
|
14
api/api.go
14
api/api.go
|
@ -2,7 +2,6 @@ package api
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pterodactyl/wings/config"
|
||||
|
@ -41,8 +40,7 @@ func (api *InternalAPI) Listen() {
|
|||
l := fmt.Sprintf("%s:%d", viper.GetString(config.APIHost), viper.GetInt(config.APIPort))
|
||||
api.router.Run(l)
|
||||
|
||||
logrus.Info("Now listening on %s", l)
|
||||
logrus.Fatal(http.ListenAndServe(l, nil))
|
||||
logrus.Info("API Server is now listening on %s", l)
|
||||
}
|
||||
|
||||
// Register routes for v1 of the API. This API should be fully backwards compatable with
|
||||
|
@ -53,7 +51,7 @@ func (api *InternalAPI) Listen() {
|
|||
func (api *InternalAPI) register() {
|
||||
v1 := api.router.Group("/api/v1")
|
||||
{
|
||||
v1.GET("/", AuthHandler(""), GetIndex)
|
||||
v1.GET("", AuthHandler(""), GetIndex)
|
||||
//v1.PATCH("/config", AuthHandler("c:config"), PatchConfiguration)
|
||||
|
||||
v1.GET("/servers", AuthHandler("c:list"), handleGetServers)
|
||||
|
@ -61,15 +59,15 @@ func (api *InternalAPI) register() {
|
|||
|
||||
v1ServerRoutes := v1.Group("/servers/:server")
|
||||
{
|
||||
v1ServerRoutes.GET("/", AuthHandler("s:get"), handleGetServer)
|
||||
v1ServerRoutes.PATCH("/", AuthHandler("s:config"), handlePatchServer)
|
||||
v1ServerRoutes.DELETE("/", AuthHandler("g:server:delete"), handleDeleteServer)
|
||||
v1ServerRoutes.GET("", AuthHandler("s:get"), handleGetServer)
|
||||
v1ServerRoutes.PATCH("", AuthHandler("s:config"), handlePatchServer)
|
||||
v1ServerRoutes.DELETE("", AuthHandler("g:server:delete"), handleDeleteServer)
|
||||
v1ServerRoutes.POST("/reinstall", AuthHandler("s:install-server"), handlePostServerReinstall)
|
||||
v1ServerRoutes.POST("/rebuild", AuthHandler("g:server:rebuild"), handlePostServerRebuild)
|
||||
v1ServerRoutes.POST("/password", AuthHandler(""), handlePostServerPassword)
|
||||
v1ServerRoutes.POST("/power", AuthHandler("s:power"), handlePostServerPower)
|
||||
v1ServerRoutes.POST("/command", AuthHandler("s:command"), handlePostServerCommand)
|
||||
v1ServerRoutes.GET("/log", AuthHandler("s:console"), handleGetServerLog)
|
||||
v1ServerRoutes.GET("/log", AuthHandler("s:console"), handleGetConsole)
|
||||
v1ServerRoutes.POST("/suspend", AuthHandler(""), handlePostServerSuspend)
|
||||
v1ServerRoutes.POST("/unsuspend", AuthHandler(""), handlePostServerUnsuspend)
|
||||
}
|
||||
|
|
33
api/auth.go
33
api/auth.go
|
@ -2,8 +2,12 @@ package api
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/jsonapi"
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/control"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -62,15 +66,25 @@ func (a *authorizationManager) HasPermission(permission string) bool {
|
|||
|
||||
// AuthHandler returns a HandlerFunc that checks request authentication
|
||||
// permission is a permission string describing the required permission to access the route
|
||||
//
|
||||
// The AuthHandler looks for an access token header (defined in accessTokenHeader)
|
||||
// or a `token` request parameter
|
||||
func AuthHandler(permission string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requestToken := c.Request.Header.Get(accessTokenHeader)
|
||||
if requestToken != "" && strings.HasPrefix(requestToken, "Baerer ") {
|
||||
requestToken = requestToken[7:]
|
||||
} else {
|
||||
requestToken = c.Query("token")
|
||||
}
|
||||
requestServer := c.Param("server")
|
||||
var server control.Server
|
||||
|
||||
if requestToken == "" && permission != "" {
|
||||
log.Debug("Token missing in request.")
|
||||
c.JSON(http.StatusBadRequest, responseError{"Missing required " + accessTokenHeader + " header."})
|
||||
sendErrors(c, http.StatusUnauthorized, &jsonapi.ErrorObject{
|
||||
Title: "Missing required " + accessTokenHeader + " header or token param.",
|
||||
Status: strconv.Itoa(http.StatusUnauthorized),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
@ -90,7 +104,7 @@ func AuthHandler(permission string) gin.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusForbidden, responseError{"You do not have permission to perform this action."})
|
||||
sendForbidden(c)
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
|
@ -107,16 +121,3 @@ func GetContextAuthManager(c *gin.Context) AuthorizationManager {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetContextServer returns a control.Server contained in a gin.Context
|
||||
// or null
|
||||
func GetContextServer(c *gin.Context) control.Server {
|
||||
server, exists := c.Get(contextVarAuth)
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
if server, ok := server.(control.Server); ok {
|
||||
return server
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,9 +6,6 @@ import (
|
|||
//"runtime"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
//"github.com/pterodactyl/wings/constants"
|
||||
//"github.com/shirou/gopsutil/host"
|
||||
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
//"github.com/shirou/gopsutil/host"
|
||||
//"github.com/shirou/gopsutil/mem"
|
||||
|
@ -128,7 +125,7 @@ type incomingConfiguration struct {
|
|||
}
|
||||
|
||||
// handlePatchConfig handles PATCH /config
|
||||
func handlePatchConfig(c *gin.Context) {
|
||||
func PatchConfiguration(c *gin.Context) {
|
||||
// reqBody, err := ioutil.ReadAll(c.Request.Body)
|
||||
// if err != nil {
|
||||
// log.WithError(err).Error("Failed to read input.")
|
||||
|
|
|
@ -3,25 +3,29 @@ package api
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/jsonapi"
|
||||
"github.com/pterodactyl/wings/control"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GET /servers
|
||||
// TODO: make jsonapi compliant
|
||||
func handleGetServers(c *gin.Context) {
|
||||
servers := control.GetServers()
|
||||
c.JSON(http.StatusOK, servers)
|
||||
sendData(c, servers)
|
||||
}
|
||||
|
||||
// POST /servers
|
||||
// TODO: make jsonapi compliant
|
||||
func handlePostServers(c *gin.Context) {
|
||||
server := control.ServerStruct{}
|
||||
if err := c.BindJSON(&server); err != nil {
|
||||
log.WithField("server", server).WithError(err).Error("Failed to parse server request.")
|
||||
c.Status(http.StatusBadRequest)
|
||||
sendErrors(c, http.StatusBadRequest, &jsonapi.ErrorObject{
|
||||
Status: strconv.Itoa(http.StatusBadRequest),
|
||||
Title: "The passed server object is invalid.",
|
||||
})
|
||||
return
|
||||
}
|
||||
var srv control.Server
|
||||
|
@ -30,10 +34,14 @@ func handlePostServers(c *gin.Context) {
|
|||
if _, ok := err.(control.ErrServerExists); ok {
|
||||
log.WithError(err).Error("Cannot create server, it already exists.")
|
||||
c.Status(http.StatusBadRequest)
|
||||
sendErrors(c, http.StatusConflict, &jsonapi.ErrorObject{
|
||||
Status: strconv.Itoa(http.StatusConflict),
|
||||
Title: "A server with this ID already exists.",
|
||||
})
|
||||
return
|
||||
}
|
||||
log.WithField("server", server).WithError(err).Error("Failed to create server.")
|
||||
c.Status(http.StatusInternalServerError)
|
||||
sendInternalError(c, "Failed to create the server", "")
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
|
@ -43,19 +51,22 @@ func handlePostServers(c *gin.Context) {
|
|||
}
|
||||
env.Create()
|
||||
}()
|
||||
c.JSON(http.StatusOK, srv)
|
||||
sendDataStatus(c, http.StatusCreated, srv)
|
||||
}
|
||||
|
||||
// GET /servers/:server
|
||||
// TODO: make jsonapi compliant
|
||||
func handleGetServer(c *gin.Context) {
|
||||
id := c.Param("server")
|
||||
server := control.GetServer(id)
|
||||
if server == nil {
|
||||
c.Status(http.StatusNotFound)
|
||||
sendErrors(c, http.StatusNotFound, &jsonapi.ErrorObject{
|
||||
Code: strconv.Itoa(http.StatusNotFound),
|
||||
Title: "Server not found.",
|
||||
Detail: "The requested Server with the id " + id + " couldn't be found.",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, server)
|
||||
sendData(c, server)
|
||||
}
|
||||
|
||||
// PATCH /servers/:server
|
||||
|
@ -64,7 +75,6 @@ func handlePatchServer(c *gin.Context) {
|
|||
}
|
||||
|
||||
// DELETE /servers/:server
|
||||
// TODO: make jsonapi compliant
|
||||
func handleDeleteServer(c *gin.Context) {
|
||||
id := c.Param("server")
|
||||
server := control.GetServer(id)
|
||||
|
@ -75,18 +85,21 @@ func handleDeleteServer(c *gin.Context) {
|
|||
|
||||
env, err := server.Environment()
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("server", server).Error("Failed to delete server.")
|
||||
sendInternalError(c, "The server could not be deleted.", "")
|
||||
return
|
||||
}
|
||||
if err := env.Destroy(); err != nil {
|
||||
log.WithError(err).Error("Failed to delete server, the environment couldn't be destroyed.")
|
||||
sendInternalError(c, "The server could not be deleted.", "The server environment couldn't be destroyed.")
|
||||
return
|
||||
}
|
||||
|
||||
if err := control.DeleteServer(id); err != nil {
|
||||
log.WithError(err).Error("Failed to delete server.")
|
||||
c.Status(http.StatusInternalServerError)
|
||||
sendInternalError(c, "The server could not be deleted.", "")
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func handlePostServerReinstall(c *gin.Context) {
|
||||
|
@ -102,7 +115,6 @@ func handlePostServerRebuild(c *gin.Context) {
|
|||
}
|
||||
|
||||
// POST /servers/:server/power
|
||||
// TODO: make jsonapi compliant
|
||||
func handlePostServerPower(c *gin.Context) {
|
||||
server := getServerFromContext(c)
|
||||
if server == nil {
|
||||
|
@ -112,7 +124,7 @@ func handlePostServerPower(c *gin.Context) {
|
|||
|
||||
auth := GetContextAuthManager(c)
|
||||
if auth == nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
sendInternalError(c, "An internal error occured.", "")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -120,7 +132,7 @@ func handlePostServerPower(c *gin.Context) {
|
|||
case "start":
|
||||
{
|
||||
if !auth.HasPermission("s:power:start") {
|
||||
c.Status(http.StatusForbidden)
|
||||
sendForbidden(c)
|
||||
return
|
||||
}
|
||||
server.Start()
|
||||
|
@ -128,7 +140,7 @@ func handlePostServerPower(c *gin.Context) {
|
|||
case "stop":
|
||||
{
|
||||
if !auth.HasPermission("s:power:stop") {
|
||||
c.Status(http.StatusForbidden)
|
||||
sendForbidden(c)
|
||||
return
|
||||
}
|
||||
server.Stop()
|
||||
|
@ -136,7 +148,7 @@ func handlePostServerPower(c *gin.Context) {
|
|||
case "restart":
|
||||
{
|
||||
if !auth.HasPermission("s:power:restart") {
|
||||
c.Status(http.StatusForbidden)
|
||||
sendForbidden(c)
|
||||
return
|
||||
}
|
||||
server.Restart()
|
||||
|
@ -144,7 +156,7 @@ func handlePostServerPower(c *gin.Context) {
|
|||
case "kill":
|
||||
{
|
||||
if !auth.HasPermission("s:power:kill") {
|
||||
c.Status(http.StatusForbidden)
|
||||
sendForbidden(c)
|
||||
return
|
||||
}
|
||||
server.Kill()
|
||||
|
@ -157,15 +169,16 @@ func handlePostServerPower(c *gin.Context) {
|
|||
}
|
||||
|
||||
// POST /servers/:server/command
|
||||
// TODO: make jsonapi compliant
|
||||
func handlePostServerCommand(c *gin.Context) {
|
||||
server := getServerFromContext(c)
|
||||
cmd := c.Query("command")
|
||||
server.Exec(cmd)
|
||||
c.Status(204)
|
||||
}
|
||||
|
||||
func handleGetServerLog(c *gin.Context) {
|
||||
|
||||
func handleGetConsole(c *gin.Context) {
|
||||
server := getServerFromContext(c)
|
||||
server.Websockets().Upgrade(c.Writer, c.Request)
|
||||
}
|
||||
|
||||
func handlePostServerSuspend(c *gin.Context) {
|
||||
|
|
50
api/routes.go
Normal file
50
api/routes.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package api
|
||||
|
||||
func (api *InternalAPI) RegisterRoutes() {
|
||||
// Register routes for v1 of the API. This API should be fully backwards compatable with
|
||||
// the existing Nodejs Daemon API.
|
||||
v1 := api.router.Group("/v1")
|
||||
{
|
||||
v1.GET("", AuthHandler(""), GetIndex)
|
||||
v1.PATCH("/config", AuthHandler("c:config"), PatchConfiguration)
|
||||
|
||||
v1.GET("/servers", AuthHandler("c:list"), handleGetServers)
|
||||
v1.POST("/servers", AuthHandler("c:create"), handlePostServers)
|
||||
|
||||
v1ServerRoutes := v1.Group("/servers/:server")
|
||||
{
|
||||
v1ServerRoutes.GET("", AuthHandler("s:get"), handleGetServer)
|
||||
v1ServerRoutes.PATCH("", AuthHandler("s:config"), handlePatchServer)
|
||||
v1ServerRoutes.DELETE("", AuthHandler("g:server:delete"), handleDeleteServer)
|
||||
v1ServerRoutes.POST("/reinstall", AuthHandler("s:install-server"), handlePostServerReinstall)
|
||||
v1ServerRoutes.POST("/rebuild", AuthHandler("g:server:rebuild"), handlePostServerRebuild)
|
||||
v1ServerRoutes.POST("/password", AuthHandler(""), handlePostServerPassword)
|
||||
v1ServerRoutes.POST("/power", AuthHandler("s:power"), handlePostServerPower)
|
||||
v1ServerRoutes.POST("/command", AuthHandler("s:command"), handlePostServerCommand)
|
||||
v1ServerRoutes.GET("/console", AuthHandler("s:console"), handleGetConsole)
|
||||
v1ServerRoutes.GET("/log", AuthHandler("s:console"), handleGetServerLog)
|
||||
v1ServerRoutes.POST("/suspend", AuthHandler(""), handlePostServerSuspend)
|
||||
v1ServerRoutes.POST("/unsuspend", AuthHandler(""), handlePostServerUnsuspend)
|
||||
}
|
||||
|
||||
//v1ServerFileRoutes := v1.Group("/servers/:server/files")
|
||||
//{
|
||||
// v1ServerFileRoutes.GET("/file/:file", AuthHandler("s:files:read"), handleGetFile)
|
||||
// v1ServerFileRoutes.GET("/stat/:file", AuthHandler("s:files:"), handleGetFileStat)
|
||||
// v1ServerFileRoutes.GET("/dir/:directory", AuthHandler("s:files:get"), handleGetDirectory)
|
||||
//
|
||||
// v1ServerFileRoutes.POST("/dir/:directory", AuthHandler("s:files:create"), handlePostFilesFolder)
|
||||
// v1ServerFileRoutes.POST("/file/:file", AuthHandler("s:files:post"), handlePostFile)
|
||||
//
|
||||
// v1ServerFileRoutes.POST("/copy/:file", AuthHandler("s:files:copy"), handlePostFileCopy)
|
||||
// v1ServerFileRoutes.POST("/move/:file", AuthHandler("s:files:move"), handlePostFileMove)
|
||||
// v1ServerFileRoutes.POST("/rename/:file", AuthHandler("s:files:move"), handlePostFileMove)
|
||||
// v1ServerFileRoutes.POST("/compress/:file", AuthHandler("s:files:compress"), handlePostFileCompress)
|
||||
// v1ServerFileRoutes.POST("/decompress/:file", AuthHandler("s:files:decompress"), handlePostFileDecompress)
|
||||
//
|
||||
// v1ServerFileRoutes.DELETE("/file/:file", AuthHandler("s:files:delete"), handleDeleteFile)
|
||||
//
|
||||
// v1ServerFileRoutes.GET("/download/:token", handleGetDownloadFile)
|
||||
//}
|
||||
}
|
||||
}
|
35
api/utils.go
35
api/utils.go
|
@ -1,10 +1,45 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/jsonapi"
|
||||
"github.com/pterodactyl/wings/control"
|
||||
)
|
||||
|
||||
func getServerFromContext(context *gin.Context) control.Server {
|
||||
return control.GetServer(context.Param("server"))
|
||||
}
|
||||
|
||||
func sendErrors(c *gin.Context, s int, err ...*jsonapi.ErrorObject) {
|
||||
c.Status(s)
|
||||
c.Header("Content-Type", "application/json")
|
||||
jsonapi.MarshalErrors(c.Writer, err)
|
||||
}
|
||||
|
||||
func sendInternalError(c *gin.Context, title string, detail string) {
|
||||
sendErrors(c, http.StatusInternalServerError, &jsonapi.ErrorObject{
|
||||
Status: strconv.Itoa(http.StatusInternalServerError),
|
||||
Title: title,
|
||||
Detail: detail,
|
||||
})
|
||||
}
|
||||
|
||||
func sendForbidden(c *gin.Context) {
|
||||
sendErrors(c, http.StatusForbidden, &jsonapi.ErrorObject{
|
||||
Title: "The provided token has insufficient permissions to perform this action.",
|
||||
Status: strconv.Itoa(http.StatusForbidden),
|
||||
})
|
||||
}
|
||||
|
||||
func sendData(c *gin.Context, payload interface{}) {
|
||||
sendDataStatus(c, http.StatusOK, payload)
|
||||
}
|
||||
|
||||
func sendDataStatus(c *gin.Context, status int, payload interface{}) {
|
||||
c.Status(status)
|
||||
c.Header("Content-Type", "application/json")
|
||||
jsonapi.MarshalPayload(c.Writer, payload)
|
||||
}
|
||||
|
|
120
api/websockets/collection.go
Normal file
120
api/websockets/collection.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
package websockets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
writeWait = 10 * time.Second
|
||||
pongWait = 60 * time.Second
|
||||
|
||||
pingPeriod = pongWait * 9 / 10
|
||||
|
||||
maxMessageSize = 512
|
||||
)
|
||||
|
||||
var wsupgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type websocketMap map[*Socket]bool
|
||||
|
||||
type Collection struct {
|
||||
sockets websocketMap
|
||||
|
||||
Broadcast chan Message
|
||||
|
||||
register chan *Socket
|
||||
unregister chan *Socket
|
||||
close chan bool
|
||||
}
|
||||
|
||||
//var _ io.Writer = &Collection{}
|
||||
|
||||
func NewCollection() *Collection {
|
||||
return &Collection{
|
||||
Broadcast: make(chan Message),
|
||||
register: make(chan *Socket),
|
||||
unregister: make(chan *Socket),
|
||||
close: make(chan bool),
|
||||
sockets: make(websocketMap),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collection) Upgrade(w http.ResponseWriter, r *http.Request) {
|
||||
socket, err := wsupgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to upgrade to websocket")
|
||||
return
|
||||
}
|
||||
s := &Socket{
|
||||
collection: c,
|
||||
conn: socket,
|
||||
send: make(chan []byte, 256),
|
||||
}
|
||||
c.register <- s
|
||||
|
||||
go s.readPump()
|
||||
go s.writePump()
|
||||
}
|
||||
|
||||
func (c *Collection) Add(s *Socket) {
|
||||
c.register <- s
|
||||
}
|
||||
|
||||
func (c *Collection) Remove(s *Socket) {
|
||||
c.unregister <- s
|
||||
}
|
||||
|
||||
func (c *Collection) Run() {
|
||||
defer func() {
|
||||
for s := range c.sockets {
|
||||
close(s.send)
|
||||
delete(c.sockets, s)
|
||||
}
|
||||
close(c.register)
|
||||
close(c.unregister)
|
||||
close(c.Broadcast)
|
||||
close(c.close)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case s := <-c.register:
|
||||
c.sockets[s] = true
|
||||
case s := <-c.unregister:
|
||||
if _, ok := c.sockets[s]; ok {
|
||||
delete(c.sockets, s)
|
||||
close(s.send)
|
||||
}
|
||||
case m := <-c.Broadcast:
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to encode websocket message.")
|
||||
continue
|
||||
}
|
||||
for s := range c.sockets {
|
||||
select {
|
||||
case s.send <- b:
|
||||
default:
|
||||
close(s.send)
|
||||
delete(c.sockets, s)
|
||||
}
|
||||
}
|
||||
case <-c.close:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collection) Close() {
|
||||
c.close <- true
|
||||
}
|
59
api/websockets/message.go
Normal file
59
api/websockets/message.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package websockets
|
||||
|
||||
type MessageType string
|
||||
|
||||
const (
|
||||
MessageTypeProc MessageType = "proc"
|
||||
MessageTypeConsole MessageType = "console"
|
||||
MessageTypeStatus MessageType = "status"
|
||||
)
|
||||
|
||||
// Message is a message that can be sent using a websocket in JSON format
|
||||
type Message struct {
|
||||
// Type is the type of a websocket message
|
||||
Type MessageType `json:"type"`
|
||||
// Payload is the payload of the message
|
||||
// The payload needs to support encoding in JSON
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
|
||||
type ProcPayload struct {
|
||||
Memory int `json:"memory"`
|
||||
CPUCores []int `json:"cpu_cores"`
|
||||
CPUTotal int `json:"cpu_total"`
|
||||
Disk int `json:"disk"`
|
||||
}
|
||||
|
||||
type ConsoleSource string
|
||||
type ConsoleLevel string
|
||||
|
||||
const (
|
||||
ConsoleSourceWings ConsoleSource = "wings"
|
||||
ConsoleSourceServer ConsoleSource = "server"
|
||||
|
||||
ConsoleLevelPlain ConsoleLevel = "plain"
|
||||
ConsoleLevelInfo ConsoleLevel = "info"
|
||||
ConsoleLevelWarn ConsoleLevel = "warn"
|
||||
ConsoleLevelError ConsoleLevel = "error"
|
||||
)
|
||||
|
||||
type ConsolePayload struct {
|
||||
// Source is the source of the console line, either ConsoleSourceWings or ConsoleSourceServer
|
||||
Source ConsoleSource `json:"source"`
|
||||
// Level is the level of the message.
|
||||
// Use one of plain, info, warn or error. If omitted the default is plain.
|
||||
Level ConsoleLevel `json:"level,omitempty"`
|
||||
// Line is the actual line to print to the console.
|
||||
Line string `json:"line"`
|
||||
}
|
||||
|
||||
func (c *Collection) Log(l ConsoleLevel, m string) {
|
||||
c.Broadcast <- Message{
|
||||
Type: MessageTypeConsole,
|
||||
Payload: ConsolePayload{
|
||||
Source: ConsoleSourceWings,
|
||||
Level: l,
|
||||
Line: m,
|
||||
},
|
||||
}
|
||||
}
|
81
api/websockets/socket.go
Normal file
81
api/websockets/socket.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package websockets
|
||||
|
||||
import "github.com/gorilla/websocket"
|
||||
import (
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Socket struct {
|
||||
collection *Collection
|
||||
|
||||
conn *websocket.Conn
|
||||
|
||||
send chan []byte
|
||||
}
|
||||
|
||||
func (s *Socket) readPump() {
|
||||
defer func() {
|
||||
s.collection.unregister <- s
|
||||
s.conn.Close()
|
||||
}()
|
||||
s.conn.SetReadLimit(maxMessageSize)
|
||||
s.conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||
s.conn.SetPongHandler(func(string) error {
|
||||
s.conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||
return nil
|
||||
})
|
||||
for {
|
||||
t, m, err := s.conn.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||
log.WithError(err).Debug("Websocket closed unexpectedly.")
|
||||
}
|
||||
return
|
||||
}
|
||||
// Handle websocket responses somehow
|
||||
if t == websocket.TextMessage {
|
||||
log.Debug("Received websocket message: " + string(m))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Socket) writePump() {
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
s.conn.Close()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case m, ok := <-s.send:
|
||||
s.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if !ok {
|
||||
// The collection closed the channel
|
||||
s.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
w, err := s.conn.NextWriter(websocket.TextMessage)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
w.Write([]byte{'['})
|
||||
w.Write(m)
|
||||
n := len(s.send) - 1
|
||||
for i := 0; i < n; i++ {
|
||||
w.Write([]byte{','})
|
||||
w.Write(<-s.send)
|
||||
}
|
||||
w.Write([]byte{']'})
|
||||
if err := w.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
case <-ticker.C:
|
||||
s.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if err := s.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
68
command/root.go
Normal file
68
command/root.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/pterodactyl/wings/api"
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/constants"
|
||||
"github.com/pterodactyl/wings/control"
|
||||
"github.com/pterodactyl/wings/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// RootCommand is the root command of wings
|
||||
var RootCommand = &cobra.Command{
|
||||
Use: "wings",
|
||||
Short: "Wings is the next generation server control daemon for Pterodactyl",
|
||||
Long: "Wings is the next generation server control daemon for Pterodactyl",
|
||||
Run: run,
|
||||
Version: constants.Version,
|
||||
}
|
||||
|
||||
var configPath string
|
||||
|
||||
func init() {
|
||||
RootCommand.Flags().StringVarP(&configPath, "config", "c", "./config.yml", "Allows to set the path of the configuration file.")
|
||||
}
|
||||
|
||||
// Execute registers the RootCommand
|
||||
func Execute() {
|
||||
RootCommand.Execute()
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) {
|
||||
utils.InitLogging()
|
||||
log.Info("Loading configuration...")
|
||||
if err := config.LoadConfiguration(configPath); err != nil {
|
||||
log.WithError(err).Fatal("Could not locate a suitable config.yml file. Aborting startup.")
|
||||
log.Exit(1)
|
||||
}
|
||||
utils.ConfigureLogging()
|
||||
|
||||
log.Info(` ____`)
|
||||
log.Info(`__ Pterodactyl _____/___/_______ _______ ______`)
|
||||
log.Info(`\_____\ \/\/ / / / __ / ___/`)
|
||||
log.Info(` \___\ / / / / /_/ /___ /`)
|
||||
log.Info(` \___/\___/___/___/___/___ /______/`)
|
||||
log.Info(` /_______/ v` + constants.Version)
|
||||
log.Info()
|
||||
|
||||
log.Info("Configuration loaded successfully.")
|
||||
|
||||
log.Info("Loading configured servers.")
|
||||
if err := control.LoadServerConfigurations(filepath.Join(viper.GetString(config.DataPath), constants.ServersPath)); err != nil {
|
||||
log.WithError(err).Error("Failed to load configured servers.")
|
||||
}
|
||||
if amount := len(control.GetServers()); amount > 0 {
|
||||
log.Info("Loaded " + strconv.Itoa(amount) + " server(s).")
|
||||
}
|
||||
|
||||
log.Info("Starting API server.")
|
||||
api := &api.InternalAPI{}
|
||||
api.Listen()
|
||||
}
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const configFile = "../config.yml.example"
|
||||
const configFile = "../config.example.yml"
|
||||
|
||||
func TestLoadConfiguraiton(t *testing.T) {
|
||||
err := LoadConfiguration(configFile)
|
||||
|
|
|
@ -6,7 +6,7 @@ const (
|
|||
|
||||
// DataPath is a string containing the path where data should
|
||||
// be stored on the system
|
||||
DataPath = "datapath"
|
||||
DataPath = "data"
|
||||
|
||||
// APIHost is a string containing the interface ip address
|
||||
// on what the api should listen on
|
||||
|
|
|
@ -26,3 +26,10 @@ const ServerDataPath = "data"
|
|||
|
||||
// JSONIndent is the indent to use with the json.MarshalIndent() function.
|
||||
const JSONIndent = " "
|
||||
|
||||
// DockerContainerPrefix is the prefix used for naming Docker containers.
|
||||
// It's also used to prefix the hostnames of the docker containers.
|
||||
const DockerContainerPrefix = "ptdl-"
|
||||
|
||||
// WSMaxMessages is the maximum number of messages that are sent in one transfer.
|
||||
const WSMaxMessages = 10
|
||||
|
|
33
control/console_handler.go
Normal file
33
control/console_handler.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/pterodactyl/wings/api/websockets"
|
||||
)
|
||||
|
||||
type ConsoleHandler struct {
|
||||
Websockets *websockets.Collection
|
||||
HandlerFunc *func(string)
|
||||
}
|
||||
|
||||
var _ io.Writer = ConsoleHandler{}
|
||||
|
||||
func (c ConsoleHandler) Write(b []byte) (n int, e error) {
|
||||
l := make([]byte, len(b))
|
||||
copy(l, b)
|
||||
line := string(l)
|
||||
m := websockets.Message{
|
||||
Type: websockets.MessageTypeConsole,
|
||||
Payload: websockets.ConsolePayload{
|
||||
Line: line,
|
||||
Level: websockets.ConsoleLevelPlain,
|
||||
Source: websockets.ConsoleSourceServer,
|
||||
},
|
||||
}
|
||||
c.Websockets.Broadcast <- m
|
||||
if c.HandlerFunc != nil {
|
||||
(*c.HandlerFunc)(line)
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
|
@ -1,220 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pterodactyl/wings/constants"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type dockerEnvironment struct {
|
||||
baseEnvironment
|
||||
|
||||
client *docker.Client
|
||||
container *docker.Container
|
||||
context context.Context
|
||||
|
||||
containerInput io.Writer
|
||||
containerOutput io.Writer
|
||||
closeWaiter docker.CloseWaiter
|
||||
|
||||
server *ServerStruct
|
||||
}
|
||||
|
||||
// Ensure DockerEnvironment implements Environment
|
||||
var _ Environment = &dockerEnvironment{}
|
||||
|
||||
// NewDockerEnvironment creates a new docker enviornment
|
||||
// instance and connects to the docker client on the host system
|
||||
// If the container is already running it will try to reattach
|
||||
// to the running container
|
||||
func NewDockerEnvironment(server *ServerStruct) (Environment, error) {
|
||||
env := dockerEnvironment{}
|
||||
|
||||
env.server = server
|
||||
|
||||
client, err := docker.NewClient("unix:///var/run/docker.sock")
|
||||
env.client = client
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Failed to connect to docker.")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if env.server.DockerContainer.ID != "" {
|
||||
if err := env.checkContainerExists(); err != nil {
|
||||
log.WithError(err).Error("Failed to find the container with stored id, removing id.")
|
||||
env.server.DockerContainer.ID = ""
|
||||
env.server.Save()
|
||||
}
|
||||
}
|
||||
|
||||
return &env, nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) checkContainerExists() error {
|
||||
container, err := env.client.InspectContainer(env.server.DockerContainer.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env.container = container
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) attach() error {
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
success := make(chan struct{})
|
||||
w, err := env.client.AttachToContainerNonBlocking(docker.AttachToContainerOptions{
|
||||
Container: env.server.DockerContainer.ID,
|
||||
InputStream: pr,
|
||||
OutputStream: os.Stdout,
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stream: true,
|
||||
Success: success,
|
||||
})
|
||||
env.closeWaiter = w
|
||||
env.containerInput = pw
|
||||
|
||||
<-success
|
||||
close(success)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create creates the docker container for the environment and applies all
|
||||
// settings to it
|
||||
func (env *dockerEnvironment) Create() error {
|
||||
log.WithField("server", env.server.ID).Debug("Creating docker environment")
|
||||
// Split image repository and tag to feed it to the library
|
||||
imageParts := strings.Split(env.server.Service().DockerImage, ":")
|
||||
imageRepoParts := strings.Split(imageParts[0], "/")
|
||||
if len(imageRepoParts) >= 3 {
|
||||
// Handle possibly required authentication
|
||||
}
|
||||
|
||||
// Pull docker image
|
||||
var pullImageOpts = docker.PullImageOptions{
|
||||
Repository: imageParts[0],
|
||||
}
|
||||
if len(imageParts) >= 2 {
|
||||
pullImageOpts.Tag = imageParts[1]
|
||||
}
|
||||
log.WithField("image", env.server.service.DockerImage).Debug("Pulling docker image")
|
||||
err := env.client.PullImage(pullImageOpts, docker.AuthConfiguration{})
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("server", env.server.ID).Error("Failed to create docker environment")
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(env.server.dataPath(), constants.DefaultFolderPerms); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create docker container
|
||||
// TODO: apply cpu, io, disk limits.
|
||||
containerConfig := &docker.Config{
|
||||
Image: env.server.Service().DockerImage,
|
||||
Cmd: strings.Split(env.server.StartupCommand, " "),
|
||||
OpenStdin: true,
|
||||
}
|
||||
containerHostConfig := &docker.HostConfig{
|
||||
Memory: env.server.Settings.Memory,
|
||||
MemorySwap: env.server.Settings.Swap,
|
||||
Binds: []string{env.server.dataPath() + ":/home/container"},
|
||||
}
|
||||
createContainerOpts := docker.CreateContainerOptions{
|
||||
Name: "ptdl-" + env.server.UUIDShort(),
|
||||
Config: containerConfig,
|
||||
HostConfig: containerHostConfig,
|
||||
Context: env.context,
|
||||
}
|
||||
container, err := env.client.CreateContainer(createContainerOpts)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("server", env.server.ID).Error("Failed to create docker container")
|
||||
return err
|
||||
}
|
||||
env.server.DockerContainer.ID = container.ID
|
||||
env.server.Save()
|
||||
env.container = container
|
||||
|
||||
log.WithField("server", env.server.ID).Debug("Docker environment created")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy removes the environment's docker container
|
||||
func (env *dockerEnvironment) Destroy() error {
|
||||
log.WithField("server", env.server.ID).Debug("Destroying docker environment")
|
||||
if _, err := env.client.InspectContainer(env.server.DockerContainer.ID); err != nil {
|
||||
if _, ok := err.(*docker.NoSuchContainer); ok {
|
||||
log.WithField("server", env.server.ID).Debug("Container not found, docker environment destroyed already.")
|
||||
return nil
|
||||
}
|
||||
log.WithError(err).WithField("server", env.server.ID).Error("Could not destroy docker environment")
|
||||
return err
|
||||
}
|
||||
err := env.client.RemoveContainer(docker.RemoveContainerOptions{
|
||||
ID: env.server.DockerContainer.ID,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("server", env.server.ID).Error("Failed to destroy docker environment")
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithField("server", env.server.ID).Debug("Docker environment destroyed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) Exists() bool {
|
||||
if env.container != nil {
|
||||
return true
|
||||
}
|
||||
env.checkContainerExists()
|
||||
return env.container != nil
|
||||
}
|
||||
|
||||
// Start starts the environment's docker container
|
||||
func (env *dockerEnvironment) Start() error {
|
||||
log.WithField("server", env.server.ID).Debug("Starting service in docker environment")
|
||||
if err := env.attach(); err != nil {
|
||||
log.WithError(err).Error("Failed to attach to docker container")
|
||||
}
|
||||
if err := env.client.StartContainer(env.container.ID, nil); err != nil {
|
||||
log.WithError(err).Error("Failed to start docker container")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the environment's docker container
|
||||
func (env *dockerEnvironment) Stop() error {
|
||||
log.WithField("server", env.server.ID).Debug("Stopping service in docker environment")
|
||||
if err := env.client.StopContainer(env.container.ID, 20000); err != nil {
|
||||
log.WithError(err).Error("Failed to stop docker container")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) Kill() error {
|
||||
log.WithField("server", env.server.ID).Debug("Killing service in docker environment")
|
||||
if err := env.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: env.container.ID,
|
||||
}); err != nil {
|
||||
log.WithError(err).Error("Failed to kill docker container")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exec sends commands to the standard input of the docker container
|
||||
func (env *dockerEnvironment) Exec(command string) error {
|
||||
log.Debug("Command: " + command)
|
||||
_, err := env.containerInput.Write([]byte(command + "\n"))
|
||||
return err
|
||||
}
|
245
control/environment_docker.go
Normal file
245
control/environment_docker.go
Normal file
|
@ -0,0 +1,245 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pterodactyl/wings/constants"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type dockerEnvironment struct {
|
||||
baseEnvironment
|
||||
|
||||
client *client.Client
|
||||
hires types.HijackedResponse
|
||||
attached bool
|
||||
|
||||
server *ServerStruct
|
||||
}
|
||||
|
||||
// Ensure DockerEnvironment implements Environment
|
||||
var _ Environment = &dockerEnvironment{}
|
||||
|
||||
// NewDockerEnvironment creates a new docker enviornment
|
||||
// instance and connects to the docker client on the host system
|
||||
// If the container is already running it will try to reattach
|
||||
// to the running container
|
||||
func NewDockerEnvironment(server *ServerStruct) (Environment, error) {
|
||||
env := dockerEnvironment{}
|
||||
|
||||
env.server = server
|
||||
env.attached = false
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
env.client = cli
|
||||
ctx := context.TODO()
|
||||
cli.NegotiateAPIVersion(ctx)
|
||||
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Failed to connect to docker.")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if env.server.DockerContainer.ID != "" {
|
||||
if err := env.inspectContainer(ctx); err != nil {
|
||||
log.WithError(err).Error("Failed to find the container with stored id, removing id.")
|
||||
env.server.DockerContainer.ID = ""
|
||||
env.server.Save()
|
||||
}
|
||||
}
|
||||
|
||||
return &env, nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) inspectContainer(ctx context.Context) error {
|
||||
_, err := env.client.ContainerInspect(ctx, env.server.DockerContainer.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) attach() error {
|
||||
if env.attached {
|
||||
return nil
|
||||
}
|
||||
|
||||
cw := ConsoleHandler{
|
||||
Websockets: env.server.websockets,
|
||||
}
|
||||
|
||||
var err error
|
||||
env.hires, err = env.client.ContainerAttach(context.TODO(), env.server.DockerContainer.ID,
|
||||
types.ContainerAttachOptions{
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
Stream: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.WithField("server", env.server.ID).WithError(err).Error("Failed to attach to docker container.")
|
||||
return err
|
||||
}
|
||||
env.attached = true
|
||||
|
||||
go func() {
|
||||
defer env.hires.Close()
|
||||
defer func() {
|
||||
env.attached = false
|
||||
}()
|
||||
io.Copy(cw, env.hires.Reader)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create creates the docker container for the environment and applies all
|
||||
// settings to it
|
||||
func (env *dockerEnvironment) Create() error {
|
||||
log.WithField("server", env.server.ID).Debug("Creating docker environment")
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
if err := env.pullImage(ctx); err != nil {
|
||||
log.WithError(err).WithField("image", env.server.GetService().DockerImage).WithField("server", env.server.ID).Error("Failed to pull docker image.")
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(env.server.dataPath(), constants.DefaultFolderPerms); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create docker container
|
||||
// TODO: apply cpu, io, disk limits.
|
||||
|
||||
containerConfig := &container.Config{
|
||||
Image: env.server.GetService().DockerImage,
|
||||
Cmd: strings.Split(env.server.StartupCommand, " "),
|
||||
AttachStdin: true,
|
||||
OpenStdin: true,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Tty: true,
|
||||
Hostname: constants.DockerContainerPrefix + env.server.UUIDShort(),
|
||||
}
|
||||
|
||||
containerHostConfig := &container.HostConfig{
|
||||
Resources: container.Resources{
|
||||
Memory: env.server.Settings.Memory,
|
||||
MemorySwap: env.server.Settings.Swap,
|
||||
},
|
||||
// TODO: Allow custom binds via some kind of settings in the service
|
||||
Binds: []string{env.server.dataPath() + ":/home/container"},
|
||||
// TODO: Add port bindings
|
||||
}
|
||||
|
||||
containerHostConfig.Memory = 0
|
||||
|
||||
container, err := env.client.ContainerCreate(ctx, containerConfig, containerHostConfig, nil, constants.DockerContainerPrefix+env.server.UUIDShort())
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("server", env.server.ID).Error("Failed to create docker container")
|
||||
return err
|
||||
}
|
||||
|
||||
env.server.DockerContainer.ID = container.ID
|
||||
env.server.Save()
|
||||
|
||||
log.WithField("server", env.server.ID).Debug("Docker environment created")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy removes the environment's docker container
|
||||
func (env *dockerEnvironment) Destroy() error {
|
||||
log.WithField("server", env.server.ID).Debug("Destroying docker environment")
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
if err := env.inspectContainer(ctx); err != nil {
|
||||
log.WithError(err).Debug("Container not found error")
|
||||
log.WithField("server", env.server.ID).Debug("Container not found, docker environment destroyed already.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := env.client.ContainerRemove(ctx, env.server.DockerContainer.ID, types.ContainerRemoveOptions{}); err != nil {
|
||||
log.WithError(err).WithField("server", env.server.ID).Error("Failed to destroy docker environment")
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithField("server", env.server.ID).Debug("Docker environment destroyed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) Exists() bool {
|
||||
if err := env.inspectContainer(context.TODO()); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Start starts the environment's docker container
|
||||
func (env *dockerEnvironment) Start() error {
|
||||
log.WithField("server", env.server.ID).Debug("Starting service in docker environment")
|
||||
if err := env.attach(); err != nil {
|
||||
log.WithError(err).Error("Failed to attach to docker container")
|
||||
}
|
||||
|
||||
if err := env.client.ContainerStart(context.TODO(), env.server.DockerContainer.ID, types.ContainerStartOptions{}); err != nil {
|
||||
log.WithError(err).Error("Failed to start docker container")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the environment's docker container
|
||||
func (env *dockerEnvironment) Stop() error {
|
||||
log.WithField("server", env.server.ID).Debug("Stopping service in docker environment")
|
||||
|
||||
// TODO: Decide after what timeout to kill the container, currently 30 seconds
|
||||
timeout := 30 * time.Second
|
||||
if err := env.client.ContainerStop(context.TODO(), env.server.DockerContainer.ID, &timeout); err != nil {
|
||||
log.WithError(err).Error("Failed to stop docker container")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) Kill() error {
|
||||
log.WithField("server", env.server.ID).Debug("Killing service in docker environment")
|
||||
|
||||
if err := env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL"); err != nil {
|
||||
log.WithError(err).Error("Failed to kill docker container")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exec sends commands to the standard input of the docker container
|
||||
func (env *dockerEnvironment) Exec(command string) error {
|
||||
log.Debug("Command: " + command)
|
||||
_, err := env.hires.Conn.Write([]byte(command + "\n"))
|
||||
return err
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) pullImage(ctx context.Context) error {
|
||||
// Split image repository and tag
|
||||
//imageParts := strings.Split(env.server.GetService().DockerImage, ":")
|
||||
//imageRepoParts := strings.Split(imageParts[0], "/")
|
||||
//if len(imageRepoParts) >= 3 {
|
||||
// TODO: Handle possibly required authentication
|
||||
//}
|
||||
|
||||
// Pull docker image
|
||||
log.WithField("image", env.server.GetService().DockerImage).Debug("Pulling docker image")
|
||||
|
||||
rc, err := env.client.ImagePull(ctx, env.server.GetService().DockerImage, types.ImagePullOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
return nil
|
||||
}
|
|
@ -1,19 +1,28 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
"github.com/pterodactyl/wings/api/websockets"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testServer() *ServerStruct {
|
||||
viper.SetDefault(config.DataPath, "./test_data")
|
||||
return &ServerStruct{
|
||||
ID: "testuuid-something-something",
|
||||
service: &service{
|
||||
Service: &Service{
|
||||
DockerImage: "alpine:latest",
|
||||
},
|
||||
StartupCommand: "/bin/ash echo hello && sleep 100",
|
||||
websockets: websockets.NewCollection(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +42,7 @@ func TestNewDockerEnvironmentExisting(t *testing.T) {
|
|||
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, env)
|
||||
assert.NotNil(t, env.container)
|
||||
assert.NotNil(t, env.server.DockerContainer)
|
||||
|
||||
eenv.Destroy()
|
||||
}
|
||||
|
@ -45,12 +54,9 @@ func TestCreateDockerEnvironment(t *testing.T) {
|
|||
|
||||
a := assert.New(t)
|
||||
a.Nil(err)
|
||||
a.NotNil(env.container)
|
||||
a.Equal(env.container.Name, "ptdl_testuuid")
|
||||
a.NotNil(env.server.DockerContainer.ID)
|
||||
|
||||
if err := env.client.RemoveContainer(docker.RemoveContainerOptions{
|
||||
ID: env.container.ID,
|
||||
}); err != nil {
|
||||
if err := env.client.ContainerRemove(context.TODO(), env.server.DockerContainer.ID, types.ContainerRemoveOptions{}); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
@ -61,10 +67,10 @@ func TestDestroyDockerEnvironment(t *testing.T) {
|
|||
|
||||
err := env.Destroy()
|
||||
|
||||
_, ierr := env.client.InspectContainer(env.container.ID)
|
||||
_, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.IsType(t, ierr, &docker.NoSuchContainer{})
|
||||
assert.True(t, client.IsErrNotFound(ierr))
|
||||
}
|
||||
|
||||
func TestStartDockerEnvironment(t *testing.T) {
|
||||
|
@ -72,15 +78,13 @@ func TestStartDockerEnvironment(t *testing.T) {
|
|||
env.Create()
|
||||
err := env.Start()
|
||||
|
||||
i, ierr := env.client.InspectContainer(env.container.ID)
|
||||
i, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, ierr)
|
||||
assert.True(t, i.State.Running)
|
||||
|
||||
env.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: env.container.ID,
|
||||
})
|
||||
env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL")
|
||||
env.Destroy()
|
||||
}
|
||||
|
||||
|
@ -90,15 +94,13 @@ func TestStopDockerEnvironment(t *testing.T) {
|
|||
env.Start()
|
||||
err := env.Stop()
|
||||
|
||||
i, ierr := env.client.InspectContainer(env.container.ID)
|
||||
i, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, ierr)
|
||||
assert.False(t, i.State.Running)
|
||||
|
||||
env.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: env.container.ID,
|
||||
})
|
||||
env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL")
|
||||
env.Destroy()
|
||||
}
|
||||
|
||||
|
@ -108,15 +110,13 @@ func TestKillDockerEnvironment(t *testing.T) {
|
|||
env.Start()
|
||||
err := env.Kill()
|
||||
|
||||
i, ierr := env.client.InspectContainer(env.container.ID)
|
||||
i, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, ierr)
|
||||
assert.False(t, i.State.Running)
|
||||
|
||||
env.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: env.container.ID,
|
||||
})
|
||||
env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL")
|
||||
env.Destroy()
|
||||
}
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/constants"
|
||||
"github.com/pterodactyl/wings/api/websockets"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusStopped Status = "stopped"
|
||||
StatusStarting Status = "starting"
|
||||
StatusRunning Status = "running"
|
||||
StatusStopping Status = "stopping"
|
||||
)
|
||||
|
||||
// ErrServerExists is returned when a server already exists on creation.
|
||||
|
@ -35,6 +37,7 @@ type Server interface {
|
|||
Save() error
|
||||
|
||||
Environment() (Environment, error)
|
||||
Websockets() *websockets.Collection
|
||||
|
||||
HasPermission(string, string) bool
|
||||
}
|
||||
|
@ -42,32 +45,36 @@ type Server interface {
|
|||
// ServerStruct is a single instance of a Service managed by the panel
|
||||
type ServerStruct struct {
|
||||
// ID is the unique identifier of the server
|
||||
ID string `json:"uuid"`
|
||||
ID string `json:"uuid" jsonapi:"primary,server"`
|
||||
|
||||
// ServiceName is the name of the service. It is mainly used to allow storing the service
|
||||
// in the config
|
||||
ServiceName string `json:"serviceName"`
|
||||
service *Service
|
||||
ServiceName string `json:"serviceName"`
|
||||
Service *Service `json:"-" jsonapi:"relation,service"`
|
||||
environment Environment
|
||||
|
||||
// StartupCommand is the command executed in the environment to start the server
|
||||
StartupCommand string `json:"startupCommand"`
|
||||
StartupCommand string `json:"startupCommand" jsonapi:"attr,startup_command"`
|
||||
|
||||
// DockerContainer holds information regarding the docker container when the server
|
||||
// is running in a docker environment
|
||||
DockerContainer dockerContainer `json:"dockerContainer"`
|
||||
DockerContainer dockerContainer `json:"dockerContainer" jsonapi:"attr,docker_container"`
|
||||
|
||||
// EnvironmentVariables are set in the Environment the server is running in
|
||||
EnvironmentVariables map[string]string `json:"env"`
|
||||
EnvironmentVariables map[string]string `json:"environmentVariables" jsonapi:"attr,environment_variables"`
|
||||
|
||||
// Allocations contains the ports and ip addresses assigned to the server
|
||||
Allocations allocations `json:"allocation"`
|
||||
Allocations allocations `json:"allocation" jsonapi:"attr,allocations"`
|
||||
|
||||
// Settings are the environment settings and limitations for the server
|
||||
Settings settings `json:"settings"`
|
||||
Settings settings `json:"settings" jsonapi:"attr,settings"`
|
||||
|
||||
// Keys are some auth keys we will hopefully replace by something better.
|
||||
// TODO remove
|
||||
Keys map[string][]string `json:"keys"`
|
||||
|
||||
websockets *websockets.Collection
|
||||
status Status
|
||||
}
|
||||
|
||||
type allocations struct {
|
||||
|
@ -99,73 +106,6 @@ type serversMap map[string]*ServerStruct
|
|||
|
||||
var servers = make(serversMap)
|
||||
|
||||
// LoadServerConfigurations loads the configured servers from a specified path
|
||||
func LoadServerConfigurations(path string) error {
|
||||
serverFiles, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
servers = make(serversMap)
|
||||
|
||||
for _, file := range serverFiles {
|
||||
if file.IsDir() {
|
||||
server, err := loadServerConfiguration(filepath.Join(path, file.Name(), constants.ServerConfigFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
servers[server.ID] = server
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadServerConfiguration(path string) (*ServerStruct, error) {
|
||||
file, err := ioutil.ReadFile(path)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
server := &ServerStruct{}
|
||||
if err := json.Unmarshal(file, server); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func storeServerConfiguration(server *ServerStruct) error {
|
||||
serverJSON, err := json.MarshalIndent(server, "", constants.JSONIndent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(server.path(), constants.DefaultFolderPerms); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(server.configFilePath(), serverJSON, constants.DefaultFilePerms); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeServerConfigurations() error {
|
||||
for _, s := range servers {
|
||||
if err := storeServerConfiguration(s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteServerFolder(id string) error {
|
||||
path := filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, id)
|
||||
folder, err := os.Stat(path)
|
||||
if os.IsNotExist(err) || !folder.IsDir() {
|
||||
return err
|
||||
}
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
// GetServers returns an array of all servers the daemon manages
|
||||
func GetServers() []Server {
|
||||
serverArray := make([]Server, len(servers))
|
||||
|
@ -193,6 +133,11 @@ func CreateServer(server *ServerStruct) (Server, error) {
|
|||
}
|
||||
servers[server.ID] = server
|
||||
if err := server.Save(); err != nil {
|
||||
delete(servers, server.ID)
|
||||
return nil, err
|
||||
}
|
||||
if err := server.init(); err != nil {
|
||||
DeleteServer(server.ID)
|
||||
return nil, err
|
||||
}
|
||||
return server, nil
|
||||
|
@ -208,13 +153,40 @@ func DeleteServer(id string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerStruct) init() error {
|
||||
// TODO: Properly use the correct service, mock for now.
|
||||
s.Service = &Service{
|
||||
DockerImage: "quay.io/pterodactyl/core:java",
|
||||
EnvironmentName: "docker",
|
||||
}
|
||||
s.status = StatusStopped
|
||||
|
||||
s.websockets = websockets.NewCollection()
|
||||
go s.websockets.Run()
|
||||
|
||||
var err error
|
||||
if s.environment == nil {
|
||||
switch s.GetService().EnvironmentName {
|
||||
case "docker":
|
||||
s.environment, err = NewDockerEnvironment(s)
|
||||
default:
|
||||
log.WithField("service", s.ServiceName).Error("Invalid environment name")
|
||||
return errors.New("Invalid environment name")
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ServerStruct) Start() error {
|
||||
s.SetStatus(StatusStarting)
|
||||
env, err := s.Environment()
|
||||
if err != nil {
|
||||
s.SetStatus(StatusStopped)
|
||||
return err
|
||||
}
|
||||
if !env.Exists() {
|
||||
if err := env.Create(); err != nil {
|
||||
s.SetStatus(StatusStopped)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -222,8 +194,10 @@ func (s *ServerStruct) Start() error {
|
|||
}
|
||||
|
||||
func (s *ServerStruct) Stop() error {
|
||||
s.SetStatus(StatusStopping)
|
||||
env, err := s.Environment()
|
||||
if err != nil {
|
||||
s.SetStatus(StatusRunning)
|
||||
return err
|
||||
}
|
||||
return env.Stop()
|
||||
|
@ -259,70 +233,3 @@ func (s *ServerStruct) Rebuild() error {
|
|||
}
|
||||
return env.ReCreate()
|
||||
}
|
||||
|
||||
// Service returns the server's service configuration
|
||||
func (s *ServerStruct) Service() *Service {
|
||||
if s.service == nil {
|
||||
// TODO: Properly use the correct service, mock for now.
|
||||
s.service = &Service{
|
||||
DockerImage: "quay.io/pterodactyl/core:java",
|
||||
EnvironmentName: "docker",
|
||||
}
|
||||
}
|
||||
return s.service
|
||||
}
|
||||
|
||||
// UUIDShort returns the first block of the UUID
|
||||
func (s *ServerStruct) UUIDShort() string {
|
||||
return s.ID[0:strings.Index(s.ID, "-")]
|
||||
}
|
||||
|
||||
// Environment returns the servers environment
|
||||
func (s *ServerStruct) Environment() (Environment, error) {
|
||||
var err error
|
||||
if s.environment == nil {
|
||||
switch s.Service().EnvironmentName {
|
||||
case "docker":
|
||||
s.environment, err = NewDockerEnvironment(s)
|
||||
default:
|
||||
log.WithField("service", s.ServiceName).Error("Invalid environment name")
|
||||
return nil, errors.New("Invalid environment name")
|
||||
}
|
||||
}
|
||||
return s.environment, err
|
||||
}
|
||||
|
||||
// HasPermission checks wether a provided token has a specific permission
|
||||
func (s *ServerStruct) HasPermission(token string, permission string) bool {
|
||||
for key, perms := range s.Keys {
|
||||
if key == token {
|
||||
for _, perm := range perms {
|
||||
if perm == permission || perm == "s:*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *ServerStruct) Save() error {
|
||||
if err := storeServerConfiguration(s); err != nil {
|
||||
log.WithField("server", s.ID).WithError(err).Error("Failed to store server configuration.")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerStruct) path() string {
|
||||
return filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, s.ID)
|
||||
}
|
||||
|
||||
func (s *ServerStruct) dataPath() string {
|
||||
return filepath.Join(s.path(), constants.ServerDataPath)
|
||||
}
|
||||
|
||||
func (s *ServerStruct) configFilePath() string {
|
||||
return filepath.Join(s.path(), constants.ServerConfigFile)
|
||||
}
|
||||
|
|
108
control/server_persistence.go
Normal file
108
control/server_persistence.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/constants"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// LoadServerConfigurations loads the configured servers from a specified path
|
||||
func LoadServerConfigurations(path string) error {
|
||||
serverFiles, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
servers = make(serversMap)
|
||||
|
||||
for _, file := range serverFiles {
|
||||
if file.IsDir() {
|
||||
server, err := loadServerConfiguration(filepath.Join(path, file.Name(), constants.ServerConfigFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
servers[server.ID] = server
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadServerConfiguration(path string) (*ServerStruct, error) {
|
||||
file, err := ioutil.ReadFile(path)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
server := &ServerStruct{}
|
||||
if err := json.Unmarshal(file, server); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := server.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func storeServerConfiguration(server *ServerStruct) error {
|
||||
serverJSON, err := json.MarshalIndent(server, "", constants.JSONIndent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(server.path(), constants.DefaultFolderPerms); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(server.configFilePath(), serverJSON, constants.DefaultFilePerms); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeServerConfigurations() error {
|
||||
for _, s := range servers {
|
||||
if err := storeServerConfiguration(s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteServerFolder(id string) error {
|
||||
path := filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, id)
|
||||
folder, err := os.Stat(path)
|
||||
if os.IsNotExist(err) || !folder.IsDir() {
|
||||
return err
|
||||
}
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
func (s *ServerStruct) Save() error {
|
||||
if err := storeServerConfiguration(s); err != nil {
|
||||
log.WithField("server", s.ID).WithError(err).Error("Failed to store server configuration.")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerStruct) path() string {
|
||||
p, err := filepath.Abs(viper.GetString(config.DataPath))
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("server", s.ID).Error("Failed to get absolute data path for server.")
|
||||
p = viper.GetString(config.DataPath)
|
||||
}
|
||||
return filepath.Join(p, constants.ServersPath, s.ID)
|
||||
}
|
||||
|
||||
func (s *ServerStruct) dataPath() string {
|
||||
return filepath.Join(s.path(), constants.ServerDataPath)
|
||||
}
|
||||
|
||||
func (s *ServerStruct) configFilePath() string {
|
||||
return filepath.Join(s.path(), constants.ServerConfigFile)
|
||||
}
|
49
control/server_util.go
Normal file
49
control/server_util.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pterodactyl/wings/api/websockets"
|
||||
)
|
||||
|
||||
func (s *ServerStruct) SetStatus(st Status) {
|
||||
s.status = st
|
||||
s.websockets.Broadcast <- websockets.Message{
|
||||
Type: websockets.MessageTypeStatus,
|
||||
Payload: s.status,
|
||||
}
|
||||
}
|
||||
|
||||
// Service returns the server's service configuration
|
||||
func (s *ServerStruct) GetService() *Service {
|
||||
return s.Service
|
||||
}
|
||||
|
||||
// UUIDShort returns the first block of the UUID
|
||||
func (s *ServerStruct) UUIDShort() string {
|
||||
return s.ID[0:strings.Index(s.ID, "-")]
|
||||
}
|
||||
|
||||
// Environment returns the servers environment
|
||||
func (s *ServerStruct) Environment() (Environment, error) {
|
||||
return s.environment, nil
|
||||
}
|
||||
|
||||
func (s *ServerStruct) Websockets() *websockets.Collection {
|
||||
return s.websockets
|
||||
}
|
||||
|
||||
// HasPermission checks wether a provided token has a specific permission
|
||||
func (s *ServerStruct) HasPermission(token string, permission string) bool {
|
||||
for key, perms := range s.Keys {
|
||||
if key == token {
|
||||
for _, perm := range perms {
|
||||
if perm == permission || perm == "s:*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -4,7 +4,7 @@ type Service struct {
|
|||
server *Server
|
||||
|
||||
// EnvironmentName is the name of the environment used by the service
|
||||
EnvironmentName string `json:"environmentName"`
|
||||
EnvironmentName string `json:"environmentName" jsonapi:"primary,service"`
|
||||
|
||||
DockerImage string `json:"dockerImage"`
|
||||
DockerImage string `json:"dockerImage" jsonapi:"attr,docker_image"`
|
||||
}
|
||||
|
|
177
glide.lock
generated
Normal file
177
glide.lock
generated
Normal file
|
@ -0,0 +1,177 @@
|
|||
hash: 59375e40229965f33d35e16d1ce13db87188dc5a70f89cf54c2921eb9475ba4f
|
||||
updated: 2017-11-03T18:09:41.337408376+01:00
|
||||
imports:
|
||||
- name: bitbucket.org/tebeka/strftime
|
||||
version: 2194253a23c090a4d5953b152a3c63fb5da4f5a4
|
||||
- name: github.com/Azure/go-ansiterm
|
||||
version: 19f72df4d05d31cbe1c56bfc8045c96babff6c7e
|
||||
subpackages:
|
||||
- winterm
|
||||
- name: github.com/docker/docker
|
||||
version: 90d35abf7b3535c1c319c872900fbd76374e521c
|
||||
subpackages:
|
||||
- api/types
|
||||
- api/types/blkiodev
|
||||
- api/types/container
|
||||
- api/types/filters
|
||||
- api/types/mount
|
||||
- api/types/network
|
||||
- api/types/registry
|
||||
- api/types/strslice
|
||||
- api/types/swarm
|
||||
- api/types/versions
|
||||
- opts
|
||||
- pkg/archive
|
||||
- pkg/fileutils
|
||||
- pkg/homedir
|
||||
- pkg/idtools
|
||||
- pkg/ioutils
|
||||
- pkg/jsonlog
|
||||
- pkg/jsonmessage
|
||||
- pkg/longpath
|
||||
- pkg/pools
|
||||
- pkg/promise
|
||||
- pkg/stdcopy
|
||||
- pkg/system
|
||||
- pkg/term
|
||||
- pkg/term/windows
|
||||
- name: github.com/docker/go-connections
|
||||
version: 3ede32e2033de7505e6500d6c868c2b9ed9f169d
|
||||
subpackages:
|
||||
- nat
|
||||
- name: github.com/docker/go-units
|
||||
version: 0dadbb0345b35ec7ef35e228dabb8de89a65bf52
|
||||
- name: github.com/fsnotify/fsnotify
|
||||
version: 4da3e2cfbabc9f751898f250b49f2439785783a1
|
||||
- name: github.com/fsouza/go-dockerclient
|
||||
version: 4df4873b288c855e4186534280c3a3a1af403e67
|
||||
- name: github.com/gin-gonic/gin
|
||||
version: e2212d40c62a98b388a5eb48ecbdcf88534688ba
|
||||
subpackages:
|
||||
- binding
|
||||
- render
|
||||
- name: github.com/go-ole/go-ole
|
||||
version: 085abb85892dc1949567b726dff00fa226c60c45
|
||||
subpackages:
|
||||
- oleutil
|
||||
- name: github.com/golang/protobuf
|
||||
version: 2402d76f3d41f928c7902a765dfc872356dd3aad
|
||||
subpackages:
|
||||
- proto
|
||||
- name: github.com/google/jsonapi
|
||||
version: 46d3ced0434461be12e555852e2f1a9ed382e139
|
||||
- name: github.com/gorilla/websocket
|
||||
version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b
|
||||
- name: github.com/hashicorp/go-cleanhttp
|
||||
version: 3573b8b52aa7b37b9358d966a898feb387f62437
|
||||
- name: github.com/hashicorp/hcl
|
||||
version: 392dba7d905ed5d04a5794ba89f558b27e2ba1ca
|
||||
subpackages:
|
||||
- hcl/ast
|
||||
- hcl/parser
|
||||
- hcl/scanner
|
||||
- hcl/strconv
|
||||
- hcl/token
|
||||
- json/parser
|
||||
- json/scanner
|
||||
- json/token
|
||||
- name: github.com/inconshreveable/mousetrap
|
||||
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
- name: github.com/lestrrat/go-file-rotatelogs
|
||||
version: ab335c655133cea61d8164853c1ed0e97d6c77cb
|
||||
- name: github.com/magiconair/properties
|
||||
version: 51463bfca2576e06c62a8504b5c0f06d61312647
|
||||
- name: github.com/manucorporat/sse
|
||||
version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d
|
||||
- name: github.com/mattn/go-isatty
|
||||
version: fc9e8d8ef48496124e79ae0df75490096eccf6fe
|
||||
- name: github.com/Microsoft/go-winio
|
||||
version: c4dc1301f1dc0307acd38e611aa375a64dfe0642
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: d0303fe809921458f417bcf828397a65db30a7e4
|
||||
- name: github.com/moby/moby
|
||||
version: 90d35abf7b3535c1c319c872900fbd76374e521c
|
||||
subpackages:
|
||||
- client
|
||||
- name: github.com/Nvveen/Gotty
|
||||
version: cd527374f1e5bff4938207604a14f2e38a9cf512
|
||||
- name: github.com/opencontainers/runc
|
||||
version: 6ca8b741bb67839b7170d96257dde5c246f8b784
|
||||
subpackages:
|
||||
- libcontainer/system
|
||||
- libcontainer/user
|
||||
- name: github.com/pelletier/go-buffruneio
|
||||
version: c37440a7cf42ac63b919c752ca73a85067e05992
|
||||
- name: github.com/pelletier/go-toml
|
||||
version: 4a000a21a414d139727f616a8bb97f847b1b310b
|
||||
- name: github.com/rifflock/lfshook
|
||||
version: 6844c808343cb8fa357d7f141b1b990e05d24e41
|
||||
- name: github.com/shirou/gopsutil
|
||||
version: 6e221c482653ef05c9f6a7bf71ddceea0e40bac5
|
||||
subpackages:
|
||||
- cpu
|
||||
- host
|
||||
- internal/common
|
||||
- mem
|
||||
- net
|
||||
- process
|
||||
- name: github.com/shirou/w32
|
||||
version: bb4de0191aa41b5507caa14b0650cdbddcd9280b
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: f006c2ac4710855cf0f916dd6b77acf6b048dc6e
|
||||
repo: https://github.com/sirupsen/logrus.git
|
||||
vcs: git
|
||||
- name: github.com/sirupsen/logrus
|
||||
version: f006c2ac4710855cf0f916dd6b77acf6b048dc6e
|
||||
- name: github.com/spf13/afero
|
||||
version: 9be650865eab0c12963d8753212f4f9c66cdcf12
|
||||
subpackages:
|
||||
- mem
|
||||
- name: github.com/spf13/cast
|
||||
version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4
|
||||
- name: github.com/spf13/cobra
|
||||
version: 90fc11bbc0a789c29272c21b5ff9e93db183f8dc
|
||||
- name: github.com/spf13/jwalterweatherman
|
||||
version: 0efa5202c04663c757d84f90f5219c1250baf94f
|
||||
- name: github.com/spf13/pflag
|
||||
version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
|
||||
- name: github.com/spf13/viper
|
||||
version: c1de95864d73a5465492829d7cb2dd422b19ac96
|
||||
- name: github.com/StackExchange/wmi
|
||||
version: ea383cf3ba6ec950874b8486cd72356d007c768f
|
||||
- name: golang.org/x/crypto
|
||||
version: bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8
|
||||
subpackages:
|
||||
- ssh/terminal
|
||||
- name: golang.org/x/net
|
||||
version: f315505cf3349909cdf013ea56690da34e96a451
|
||||
subpackages:
|
||||
- context
|
||||
- context/ctxhttp
|
||||
- name: golang.org/x/sys
|
||||
version: f7928cfef4d09d1b080aa2b6fd3ca9ba1567c733
|
||||
subpackages:
|
||||
- unix
|
||||
- windows
|
||||
- name: golang.org/x/text
|
||||
version: 5a2c30c33799f1e813f7f3259000d594a5ed493a
|
||||
subpackages:
|
||||
- transform
|
||||
- unicode/norm
|
||||
- name: gopkg.in/go-playground/validator.v8
|
||||
version: c193cecd124b5cc722d7ee5538e945bdb3348435
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b
|
||||
testImports:
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: 792786c7400a136282c1664665ae0a8db921c6c2
|
||||
subpackages:
|
||||
- difflib
|
||||
- name: github.com/stretchr/testify
|
||||
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
|
||||
subpackages:
|
||||
- assert
|
33
glide.yaml
Normal file
33
glide.yaml
Normal file
|
@ -0,0 +1,33 @@
|
|||
package: github.com/pterodactyl/wings
|
||||
import:
|
||||
- package: github.com/gin-gonic/gin
|
||||
version: ~1.1.4
|
||||
- package: github.com/lestrrat/go-file-rotatelogs
|
||||
version: ~2.0.0
|
||||
- package: github.com/rifflock/lfshook
|
||||
version: ~1.7.0
|
||||
- package: github.com/sirupsen/logrus
|
||||
version: ~1.0.0
|
||||
- package: github.com/Sirupsen/logrus
|
||||
version: ~1.0.0
|
||||
repo: https://github.com/sirupsen/logrus.git
|
||||
vcs: git
|
||||
- package: github.com/spf13/viper
|
||||
- package: github.com/spf13/cobra
|
||||
- package: github.com/shirou/gopsutil
|
||||
version: ^2.17.6
|
||||
- package: github.com/moby/moby
|
||||
version: ~17.5.0-ce-rc3
|
||||
subpackages:
|
||||
- client
|
||||
- package: github.com/fsouza/go-dockerclient
|
||||
- package: github.com/hashicorp/go-cleanhttp
|
||||
- package: github.com/gorilla/websocket
|
||||
version: ~1.2.0
|
||||
- package: github.com/google/jsonapi
|
||||
version: ~1.0.0
|
||||
testImport:
|
||||
- package: github.com/stretchr/testify
|
||||
version: ~1.1.4
|
||||
subpackages:
|
||||
- assert
|
71
main.go
71
main.go
|
@ -1,71 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/pterodactyl/wings/api"
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/constants"
|
||||
"github.com/pterodactyl/wings/control"
|
||||
"github.com/pterodactyl/wings/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var configPath string
|
||||
var RootCommand = &cobra.Command{
|
||||
Use: "wings",
|
||||
Short: "Wings is the next generation server control daemon for Pterodactyl",
|
||||
Long: "Wings is the next generation server control daemon for Pterodactyl",
|
||||
Run: run,
|
||||
}
|
||||
|
||||
// Entrypoint of the application. Currently just boots up the cobra command
|
||||
// and lets that handle everything else.
|
||||
func main() {
|
||||
RootCommand.Flags().StringVarP(&configPath, "config", "c", "./config.yml", "Allows to set the path of the configuration file.")
|
||||
|
||||
if err := RootCommand.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstraps the application and beging the process of running the API and
|
||||
// server instances.
|
||||
func run(cmd *cobra.Command, args []string) {
|
||||
utils.InitLogging()
|
||||
|
||||
logrus.Info("Booting configuration file...")
|
||||
if err := config.LoadConfiguration(configPath); err != nil {
|
||||
logrus.WithError(err).Fatal("Could not locate a suitable config.yml file for this Daemon.")
|
||||
}
|
||||
|
||||
logrus.Info("Configuration successfully loaded, booting application.")
|
||||
utils.ConfigureLogging()
|
||||
|
||||
logrus.Info(` ____`)
|
||||
logrus.Info(`__ Pterodactyl _____/___/_______ _______ ______`)
|
||||
logrus.Info(`\_____\ \/\/ / / / __ / ___/`)
|
||||
logrus.Info(` \___\ / / / / /_/ /___ /`)
|
||||
logrus.Info(` \___/\___/___/___/___/___ /______/`)
|
||||
logrus.Info(` /_______/ v` + constants.Version)
|
||||
logrus.Info()
|
||||
|
||||
logrus.Info("Loading configured servers.")
|
||||
if err := control.LoadServerConfigurations(filepath.Join(viper.GetString(config.DataPath), constants.ServersPath)); err != nil {
|
||||
logrus.WithError(err).Error("Failed to load configured servers.")
|
||||
}
|
||||
|
||||
if amount := len(control.GetServers()); amount == 1 {
|
||||
logrus.Info("Found and loaded " + strconv.Itoa(amount) + " server(s).")
|
||||
}
|
||||
|
||||
logrus.Info("Registering API server and booting.")
|
||||
a := api.InternalAPI{}
|
||||
a.Listen()
|
||||
}
|
|
@ -3,12 +3,13 @@ package utils
|
|||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
//"time"
|
||||
|
||||
"github.com/pterodactyl/wings/constants"
|
||||
|
||||
//"github.com/lestrrat/go-file-rotatelogs"
|
||||
//"github.com/rifflock/lfshook"
|
||||
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||
"github.com/rifflock/lfshook"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
|
@ -30,20 +31,23 @@ func ConfigureLogging() error {
|
|||
if err := os.MkdirAll(path, constants.DefaultFolderPerms); err != nil {
|
||||
return err
|
||||
}
|
||||
//writer := rotatelogs.New(
|
||||
// path+"/wings.%Y%m%d-%H%M.log",
|
||||
// rotatelogs.WithLinkName(path),
|
||||
// rotatelogs.WithMaxAge(time.Duration(viper.GetInt(config.LogDeleteAfterDays))*time.Hour*24),
|
||||
// rotatelogs.WithRotationTime(time.Duration(604800)*time.Second),
|
||||
//)
|
||||
//
|
||||
//log.AddHook(lfshook.NewHook(lfshook.WriterMap{
|
||||
// log.DebugLevel: writer,
|
||||
// log.InfoLevel: writer,
|
||||
// log.WarnLevel: writer,
|
||||
// log.ErrorLevel: writer,
|
||||
// log.FatalLevel: writer,
|
||||
//}))
|
||||
writer, err := rotatelogs.New(
|
||||
path+"/wings.%Y%m%d-%H%M.log",
|
||||
rotatelogs.WithLinkName(path),
|
||||
rotatelogs.WithMaxAge(time.Duration(viper.GetInt(config.LogDeleteAfterDays))*time.Hour*24),
|
||||
rotatelogs.WithRotationTime(time.Hour*24),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.AddHook(lfshook.NewHook(lfshook.WriterMap{
|
||||
log.DebugLevel: writer,
|
||||
log.InfoLevel: writer,
|
||||
log.WarnLevel: writer,
|
||||
log.ErrorLevel: writer,
|
||||
log.FatalLevel: writer,
|
||||
}, &log.JSONFormatter{}))
|
||||
|
||||
level := viper.GetString(config.LogLevel)
|
||||
|
||||
|
|
BIN
wings-api.paw
Normal file
BIN
wings-api.paw
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user