Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02734292a0 | ||
|
|
7899a7abdf | ||
|
|
6f9783f164 | ||
|
|
d9ebf693e0 | ||
|
|
0cfd72e1d1 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## v1.5.2
|
||||
### Fixed
|
||||
* Fixes servers not properly re-syncing with the Panel if they are already running causing them to be hard-stopped when terminated, rather than stopped with the configured action.
|
||||
|
||||
### Changed
|
||||
* Changes SFTP server implementation to use ED25519 server keys rather than deprecated SHA1 RSA keys.
|
||||
|
||||
### Added
|
||||
* Adds server uptime output in the stats event emitted to the websocket.
|
||||
|
||||
## v1.5.1
|
||||
### Added
|
||||
* Global configuration option for toggling server crash detection (`system.crash_detection.enabled`)
|
||||
|
||||
@@ -30,7 +30,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f
|
||||
| [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims for inexpensive services on quality servers. Premium i9-9900K processors will run your game like a dream. |
|
||||
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
||||
| [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. |
|
||||
| [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! |
|
||||
| [**Capitol Hosting Solutions**](https://chs.gg/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! |
|
||||
| [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! |
|
||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.|
|
||||
|
||||
13
cmd/root.go
13
cmd/root.go
@@ -255,6 +255,13 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||
// state being tracked.
|
||||
s.Environment.SetState(environment.ProcessOfflineState)
|
||||
}
|
||||
|
||||
if state := s.Environment.State(); state == environment.ProcessStartingState || state == environment.ProcessRunningState {
|
||||
s.Log().Debug("re-syncing server configuration for already running server")
|
||||
if err := s.Sync(); err != nil {
|
||||
s.Log().WithError(err).Error("failed to re-sync server configuration")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -287,12 +294,12 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||
|
||||
sys := config.Get().System
|
||||
// Ensure the archive directory exists.
|
||||
if err := os.MkdirAll(sys.ArchiveDirectory, 0755); err != nil {
|
||||
if err := os.MkdirAll(sys.ArchiveDirectory, 0o755); err != nil {
|
||||
log.WithField("error", err).Error("failed to create archive directory")
|
||||
}
|
||||
|
||||
// Ensure the backup directory exists.
|
||||
if err := os.MkdirAll(sys.BackupDirectory, 0755); err != nil {
|
||||
if err := os.MkdirAll(sys.BackupDirectory, 0o755); err != nil {
|
||||
log.WithField("error", err).Error("failed to create backup directory")
|
||||
}
|
||||
|
||||
@@ -385,7 +392,7 @@ func initConfig() {
|
||||
// in the code without having to pass around a logger instance.
|
||||
func initLogging() {
|
||||
dir := config.Get().System.LogDirectory
|
||||
if err := os.MkdirAll(path.Join(dir, "/install"), 0700); err != nil {
|
||||
if err := os.MkdirAll(path.Join(dir, "/install"), 0o700); err != nil {
|
||||
log2.Fatalf("cmd/root: failed to create install directory path: %s", err)
|
||||
}
|
||||
p := filepath.Join(dir, "/wings.log")
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -12,6 +13,23 @@ import (
|
||||
"github.com/pterodactyl/wings/environment"
|
||||
)
|
||||
|
||||
// Uptime returns the current uptime of the container in milliseconds. If the
|
||||
// container is not currently running this will return 0.
|
||||
func (e *Environment) Uptime(ctx context.Context) (int64, error) {
|
||||
ins, err := e.client.ContainerInspect(ctx, e.Id)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "environment: could not inspect container")
|
||||
}
|
||||
if !ins.State.Running {
|
||||
return 0, nil
|
||||
}
|
||||
started, err := time.Parse(time.RFC3339, ins.State.StartedAt)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "environment: failed to parse container start time")
|
||||
}
|
||||
return time.Since(started).Milliseconds(), nil
|
||||
}
|
||||
|
||||
// Attach to the instance and then automatically emit an event whenever the resource usage for the
|
||||
// server process changes.
|
||||
func (e *Environment) pollResources(ctx context.Context) error {
|
||||
@@ -28,6 +46,11 @@ func (e *Environment) pollResources(ctx context.Context) error {
|
||||
}
|
||||
defer stats.Body.Close()
|
||||
|
||||
uptime, err := e.Uptime(ctx)
|
||||
if err != nil {
|
||||
e.log().WithField("error", err).Warn("failed to calculate container uptime")
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(stats.Body)
|
||||
for {
|
||||
select {
|
||||
@@ -50,7 +73,12 @@ func (e *Environment) pollResources(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !v.PreRead.IsZero() {
|
||||
uptime = uptime + v.Read.Sub(v.PreRead).Milliseconds()
|
||||
}
|
||||
|
||||
st := environment.Stats{
|
||||
Uptime: uptime,
|
||||
Memory: calculateDockerMemory(v.MemoryStats),
|
||||
MemoryLimit: v.MemoryStats.Limit,
|
||||
CpuAbsolute: calculateDockerAbsoluteCpu(v.PreCPUStats, v.CPUStats),
|
||||
|
||||
@@ -104,4 +104,8 @@ type ProcessEnvironment interface {
|
||||
// handle this itself, but there are some scenarios where it is helpful for the server
|
||||
// to update the state externally (e.g. starting -> started).
|
||||
SetState(string)
|
||||
|
||||
// Uptime returns the current environment uptime in milliseconds. This is
|
||||
// the time that has passed since it was last started.
|
||||
Uptime(ctx context.Context) (int64, error)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package environment
|
||||
|
||||
// Defines the current resource usage for a given server instance. If a server is offline you
|
||||
// should obviously expect memory and CPU usage to be 0. However, disk will always be returned
|
||||
// since that is not dependent on the server being running to collect that data.
|
||||
// Stats defines the current resource usage for a given server instance.
|
||||
type Stats struct {
|
||||
// The total amount of memory, in bytes, that this server instance is consuming. This is
|
||||
// calculated slightly differently than just using the raw Memory field that the stats
|
||||
@@ -19,12 +17,11 @@ type Stats struct {
|
||||
// does not take into account any limits on the server process itself.
|
||||
CpuAbsolute float64 `json:"cpu_absolute"`
|
||||
|
||||
// The current disk space being used by the server. This is cached to prevent slow lookup
|
||||
// issues on frequent refreshes.
|
||||
// Disk int64 `json:"disk_bytes"`
|
||||
|
||||
// Current network transmit in & out for a container.
|
||||
Network NetworkStats `json:"network"`
|
||||
|
||||
// The current uptime of the container, in milliseconds.
|
||||
Uptime int64 `json:"uptime"`
|
||||
}
|
||||
|
||||
type NetworkStats struct {
|
||||
|
||||
1
go.mod
1
go.mod
@@ -33,7 +33,6 @@ require (
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/icza/dyno v0.0.0-20210726202311-f1bafe5d9996
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/juju/ratelimit v1.0.1
|
||||
github.com/karrick/godirwalk v1.16.1
|
||||
github.com/klauspost/compress v1.13.2 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -513,8 +513,6 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||
|
||||
@@ -46,6 +46,7 @@ func (ru *ResourceUsage) Reset() {
|
||||
|
||||
ru.Memory = 0
|
||||
ru.CpuAbsolute = 0
|
||||
ru.Uptime = 0
|
||||
ru.Network.TxBytes = 0
|
||||
ru.Network.RxBytes = 0
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package sftp
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
@@ -48,18 +48,20 @@ func New(m *server.Manager) *SFTPServer {
|
||||
}
|
||||
}
|
||||
|
||||
// Starts the SFTP server and add a persistent listener to handle inbound SFTP connections.
|
||||
// Run starts the SFTP server and add a persistent listener to handle inbound
|
||||
// SFTP connections. This will automatically generate an ED25519 key if one does
|
||||
// not already exist on the system for host key verification purposes.
|
||||
func (c *SFTPServer) Run() error {
|
||||
if _, err := os.Stat(path.Join(c.BasePath, ".sftp/id_rsa")); os.IsNotExist(err) {
|
||||
if err := c.generatePrivateKey(); err != nil {
|
||||
if _, err := os.Stat(c.PrivateKeyPath()); os.IsNotExist(err) {
|
||||
if err := c.generateED25519PrivateKey(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "sftp/server: could not stat private key file")
|
||||
return errors.Wrap(err, "sftp: could not stat private key file")
|
||||
}
|
||||
pb, err := ioutil.ReadFile(path.Join(c.BasePath, ".sftp/id_rsa"))
|
||||
pb, err := ioutil.ReadFile(c.PrivateKeyPath())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "sftp/server: could not read private key file")
|
||||
return errors.Wrap(err, "sftp: could not read private key file")
|
||||
}
|
||||
private, err := ssh.ParsePrivateKey(pb)
|
||||
if err != nil {
|
||||
@@ -78,7 +80,9 @@ func (c *SFTPServer) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithField("listen", c.Listen).Info("sftp server listening for connections")
|
||||
public := string(ssh.MarshalAuthorizedKey(private.PublicKey()))
|
||||
log.WithField("listen", c.Listen).WithField("public_key", strings.Trim(public, "\n")).Info("sftp server listening for connections")
|
||||
|
||||
for {
|
||||
if conn, _ := listener.Accept(); conn != nil {
|
||||
go func(conn net.Conn) {
|
||||
@@ -148,26 +152,30 @@ func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a private key that will be used by the SFTP server.
|
||||
func (c *SFTPServer) generatePrivateKey() error {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
// Generates a new ED25519 private key that is used for host authentication when
|
||||
// a user connects to the SFTP server.
|
||||
func (c *SFTPServer) generateED25519PrivateKey() error {
|
||||
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
return errors.Wrap(err, "sftp: failed to generate ED25519 private key")
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(c.BasePath, ".sftp"), 0755); err != nil {
|
||||
return errors.Wrap(err, "sftp/server: could not create .sftp directory")
|
||||
if err := os.MkdirAll(path.Dir(c.PrivateKeyPath()), 0755); err != nil {
|
||||
return errors.Wrap(err, "sftp: could not create internal sftp data directory")
|
||||
}
|
||||
o, err := os.OpenFile(path.Join(c.BasePath, ".sftp/id_rsa"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
o, err := os.OpenFile(c.PrivateKeyPath(), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer o.Close()
|
||||
|
||||
err = pem.Encode(o, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
})
|
||||
return errors.WithStack(err)
|
||||
b, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "sftp: failed to marshal private key into bytes")
|
||||
}
|
||||
if err := pem.Encode(o, &pem.Block{Type: "PRIVATE KEY", Bytes: b}); err != nil {
|
||||
return errors.Wrap(err, "sftp: failed to write ED25519 private key to disk")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A function capable of validating user credentials with the Panel API.
|
||||
@@ -209,3 +217,8 @@ func (c *SFTPServer) passwordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.
|
||||
|
||||
return sshPerm, nil
|
||||
}
|
||||
|
||||
// PrivateKeyPath returns the path the host private key for this server instance.
|
||||
func (c *SFTPServer) PrivateKeyPath() string {
|
||||
return path.Join(c.BasePath, ".sftp/id_ed25519")
|
||||
}
|
||||
Reference in New Issue
Block a user