Compare commits

...

15 Commits

Author SHA1 Message Date
Pterodactyl CI
3879ccb4c7 bump version for release 2023-01-27 19:16:59 +00:00
Matthew Penner
304fd91283 Update CHANGELOG.md 2023-01-27 12:14:22 -07:00
Matthew Penner
18de96d7b8 activity: fix IP parsing, drop all columns with malformed ips 2023-01-24 14:36:18 -07:00
Matthew Penner
a36cab1783 router(transfer): throw error if server fails to stop 2023-01-24 12:36:02 -07:00
Matthew Penner
6e0c095bb8 router(transfer): decrease WaitForStop timeout 2023-01-24 12:34:05 -07:00
Matthew Penner
14eea3b1e4 router: close body once finished 2023-01-24 12:33:42 -07:00
Matthew Penner
1bc77dc969 system: close Docker client once finished 2023-01-24 12:33:20 -07:00
Matthew Penner
b8715d1d4f ci: update push-artifact action 2023-01-24 12:32:55 -07:00
Matthew Penner
13d3490bcf server(filesystem): fix Writefile being broken 2023-01-17 18:44:56 -07:00
Matthew Penner
e9b8b11fec Ensure files are closed after they are done being used 2023-01-17 18:34:08 -07:00
Matthew Penner
43b7aa2536 sftp: disable insecure protocols 2023-01-17 11:50:06 -07:00
Matthew Penner
9b8b3c90fb environment(docker): improve logging and stacks 2023-01-17 11:47:27 -07:00
Matthew Penner
e74d8e3501 ci: update go versions 2023-01-13 11:07:09 -07:00
Matthew Penner
4b3bd2ff47 ci(docker): fix latest tag 2022-12-04 18:37:53 -07:00
Matthew Penner
e652d2df84 ci: cleanup 2022-12-04 17:39:55 -07:00
23 changed files with 100 additions and 62 deletions

View File

@@ -18,11 +18,13 @@ jobs:
- name: Code checkout
uses: actions/checkout@v3
- name: Docker Meta
- name: Docker metadata
id: docker_meta
uses: docker/metadata-action@v4
with:
images: ghcr.io/pterodactyl/wings
flavor: |
latest=false
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.action == 'published' && github.event.release.prerelease == false }}
type=ref,event=tag
@@ -31,7 +33,7 @@ jobs:
- name: Setup QEMU
uses: docker/setup-qemu-action@v2
- name: Install buildx
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v2
- name: Login to GitHub Container Registry

View File

@@ -16,7 +16,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-20.04]
go: ["1.18.8", "1.19.3"]
go: ["1.18.10", "1.19.5"]
goos: [linux]
goarch: [amd64, arm64]
@@ -86,14 +86,14 @@ jobs:
go test -race $(go list ./...)
- name: Upload Release Artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
with:
name: wings_linux_${{ matrix.goarch }}
path: dist/wings
- name: Upload Debug Artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
with:
name: wings_linux_${{ matrix.goarch }}_debug

View File

@@ -17,7 +17,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: "1.18.8"
go-version: "1.18.10"
- name: Build release binaries
env:
@@ -62,8 +62,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: ${{ github.ref }}
tag_name: ${{ github.ref }}
draft: true
prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
body_path: ./RELEASE_CHANGELOG

View File

@@ -1,5 +1,17 @@
# Changelog
## v1.11.1
### Changed
* Release binaries are now built with Go 1.18.10
* Timeout when stopping a server before a transfer begins has been reduced to 15 seconds from 1 minute
* Removed insecure SSH protocols for use with the SFTP server
### Fixed
* Unnecessary Docker client connections being left open, causing a slow leak of file descriptors
* Files being left open in parts of the server's filesystem, causing a leak of file descriptors
* IPv6 addresses being corrupted by flawed port stripping logic for activity logs, old entries with malformed IPs will be deleted from the local SQLite database automatically
* A server that times out while being stopped at the beginning of a transfer no longer causes the server to become stuck in a transferring state
## v1.11.0
### Added (since 1.7.2)
* More detailed information returned by the `/api/system` endpoint when using the `?v=2` query parameter.

View File

@@ -58,7 +58,7 @@ func (e *Environment) Attach(ctx context.Context) error {
// Set the stream again with the container.
if st, err := e.client.ContainerAttach(ctx, e.Id, opts); err != nil {
return err
return errors.WrapIf(err, "environment/docker: error while attaching to container")
} else {
e.SetStream(&st)
}
@@ -143,7 +143,7 @@ func (e *Environment) Create() error {
if _, err := e.ContainerInspect(ctx); err == nil {
return nil
} else if !client.IsErrNotFound(err) {
return errors.Wrap(err, "environment/docker: failed to inspect container")
return errors.WrapIf(err, "environment/docker: failed to inspect container")
}
// Try to pull the requested image before creating the container.

View File

@@ -161,7 +161,7 @@ func (e *Environment) ExitState() (uint32, bool, error) {
if client.IsErrNotFound(err) {
return 1, false, nil
}
return 0, false, err
return 0, false, errors.WrapIf(err, "environment/docker: failed to inspect container")
}
return uint32(c.State.ExitCode), c.State.OOMKilled, nil
}

View File

@@ -103,7 +103,7 @@ func (e *Environment) Start(ctx context.Context) error {
// exists on the system, and rebuild the container if that is required for server booting to
// occur.
if err := e.OnBeforeStart(ctx); err != nil {
return errors.WithStackIf(err)
return errors.WrapIf(err, "environment/docker: failed to run pre-boot process")
}
// If we cannot start & attach to the container in 30 seconds something has gone
@@ -119,7 +119,7 @@ func (e *Environment) Start(ctx context.Context) error {
// By explicitly attaching to the instance before we start it, we can immediately
// react to errors/output stopping/etc. when starting.
if err := e.Attach(actx); err != nil {
return err
return errors.WrapIf(err, "environment/docker: failed to attach to container")
}
if err := e.client.ContainerStart(actx, e.Id, types.ContainerStartOptions{}); err != nil {

View File

@@ -2,6 +2,7 @@ package cron
import (
"context"
"net"
"emperror.dev/errors"
@@ -17,9 +18,9 @@ type activityCron struct {
max int
}
// Run executes the cronjob and ensures we fetch and send all of the stored activity to the
// Run executes the cronjob and ensures we fetch and send all the stored activity to the
// Panel instance. Once activity is sent it is deleted from the local database instance. Any
// SFTP specific events are not handled in this cron, they're handled seperately to account
// SFTP specific events are not handled in this cron, they're handled separately to account
// for de-duplication and event merging.
func (ac *activityCron) Run(ctx context.Context) error {
// Don't execute this cron if there is currently one running. Once this task is completed
@@ -34,7 +35,6 @@ func (ac *activityCron) Run(ctx context.Context) error {
Where("event NOT LIKE ?", "server:sftp.%").
Limit(ac.max).
Find(&activity)
if tx.Error != nil {
return errors.WithStack(tx.Error)
}
@@ -42,15 +42,42 @@ func (ac *activityCron) Run(ctx context.Context) error {
return nil
}
if err := ac.manager.Client().SendActivityLogs(ctx, activity); err != nil {
// ids to delete from the database.
ids := make([]int, 0, len(activity))
// activities to send to the panel.
activities := make([]models.Activity, 0, len(activity))
for _, v := range activity {
// Delete any activity that has an invalid IP address. This is a fix for
// a bug that truncated the last octet of an IPv6 address in the database.
if err := net.ParseIP(v.IP); err != nil {
ids = append(ids, v.ID)
continue
}
activities = append(activities, v)
}
if len(ids) > 0 {
tx = database.Instance().WithContext(ctx).Where("id IN ?", ids).Delete(&models.Activity{})
if tx.Error != nil {
return errors.WithStack(tx.Error)
}
}
if len(activities) == 0 {
return nil
}
if err := ac.manager.Client().SendActivityLogs(ctx, activities); err != nil {
return errors.WrapIf(err, "cron: failed to send activity events to Panel")
}
var ids []int
for _, v := range activity {
ids = append(ids, v.ID)
// Add all the successful activities to the list of IDs to delete.
ids = make([]int, len(activities))
for i, v := range activities {
ids[i] = v.ID
}
// Delete all the activities that were sent to the Panel (or that were invalid).
tx = database.Instance().WithContext(ctx).Where("id IN ?", ids).Delete(&models.Activity{})
if tx.Error != nil {
return errors.WithStack(tx.Error)

View File

@@ -1,11 +1,11 @@
package models
import (
"net"
"strings"
"time"
"gorm.io/gorm"
"github.com/pterodactyl/wings/system"
)
type Event string
@@ -57,7 +57,9 @@ func (a Activity) SetUser(u string) *Activity {
// is trimmed down to remove any extraneous data, and the timestamp is set to the current
// system time and then stored as UTC.
func (a *Activity) BeforeCreate(_ *gorm.DB) error {
a.IP = system.TrimIPSuffix(a.IP)
if ip, _, err := net.SplitHostPort(strings.TrimSpace(a.IP)); err == nil {
a.IP = ip
}
if a.Timestamp.IsZero() {
a.Timestamp = time.Now()
}

View File

@@ -95,6 +95,7 @@ func getDownloadFile(c *gin.Context) {
middleware.CaptureAndAbort(c, err)
return
}
defer f.Close()
c.Header("Content-Length", strconv.Itoa(int(st.Size())))
c.Header("Content-Disposition", "attachment; filename="+strconv.Quote(st.Name()))

View File

@@ -147,6 +147,7 @@ func postServerRestoreBackup(c *gin.Context) {
middleware.CaptureAndAbort(c, err)
return
}
defer res.Body.Close()
// Don't allow content types that we know are going to give us problems.
if res.Header.Get("Content-Type") == "" || !strings.Contains("application/x-gzip application/gzip", res.Header.Get("Content-Type")) {
_ = res.Body.Close()

View File

@@ -6,6 +6,7 @@ import (
"strings"
"time"
"emperror.dev/errors"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/environment"
@@ -63,11 +64,11 @@ func postServerTransfer(c *gin.Context) {
if s.Environment.State() != environment.ProcessOfflineState {
if err := s.Environment.WaitForStop(
s.Context(),
time.Minute,
time.Second*15,
false,
); err != nil && !strings.Contains(strings.ToLower(err.Error()), "no such container") {
notifyPanelOfFailure()
s.Log().WithError(err).Error("failed to stop server for transfer")
s.SetTransferring(false)
middleware.CaptureAndAbort(c, errors.Wrap(err, "failed to stop server for transfer"))
return
}
}

View File

@@ -58,7 +58,7 @@ func getServerWebsocket(c *gin.Context) {
case <-ctx.Done():
break
case <-s.Context().Done():
handler.Connection.WriteControl(ws.CloseMessage, ws.FormatCloseMessage(ws.CloseGoingAway, "server deleted"), time.Now().Add(time.Second*5))
_ = handler.Connection.WriteControl(ws.CloseMessage, ws.FormatCloseMessage(ws.CloseGoingAway, "server deleted"), time.Now().Add(time.Second*5))
break
}
}()
@@ -83,7 +83,7 @@ func getServerWebsocket(c *gin.Context) {
go func(msg websocket.Message) {
if err := handler.HandleInbound(ctx, msg); err != nil {
handler.SendErrorJson(msg, err)
_ = handler.SendErrorJson(msg, err)
}
}(j)
}

View File

@@ -85,6 +85,7 @@ func (b *LocalBackup) Restore(ctx context.Context, _ io.Reader, callback Restore
if err != nil {
return err
}
defer f.Close()
var reader io.Reader = f
// Steal the logic we use for making backups which will be applied when restoring

View File

@@ -6,6 +6,8 @@ import (
"sync"
"time"
"emperror.dev/errors"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
)
@@ -57,7 +59,7 @@ func (s *Server) handleServerCrash() error {
exitCode, oomKilled, err := s.Environment.ExitState()
if err != nil {
return err
return errors.Wrap(err, "failed to get exit state for server process")
}
// If the system is not configured to detect a clean exit code as a crash, and the
@@ -85,5 +87,5 @@ func (s *Server) handleServerCrash() error {
s.crasher.SetLastCrash(time.Now())
return s.HandlePowerAction(PowerActionStart)
return errors.Wrap(s.HandlePowerAction(PowerActionStart), "failed to start server after crash detection")
}

View File

@@ -148,7 +148,7 @@ func (fs *Filesystem) DecompressFileUnsafe(ctx context.Context, dir string, file
if err != nil {
return err
}
// TODO: defer file close?
defer f.Close()
// Identify the type of archive we are dealing with.
format, input, err := archiver.Identify(filepath.Base(file), f)

View File

@@ -183,7 +183,7 @@ func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
}
if !e.IsDir() {
syscall.Lstat(p, &st)
_ = syscall.Lstat(p, &st)
atomic.AddInt64(&size, st.Size)
}

View File

@@ -92,6 +92,9 @@ func (fs *Filesystem) Touch(p string, flag int) (*os.File, error) {
if err == nil {
return f, nil
}
if f != nil {
_ = f.Close()
}
// If the error is not because it doesn't exist then we just need to bail at this point.
if !errors.Is(err, os.ErrNotExist) {
return nil, errors.Wrap(err, "server/filesystem: touch: failed to open file handle")

View File

@@ -147,6 +147,7 @@ func (s *Server) Context() context.Context {
// server instance.
func (s *Server) GetEnvironmentVariables() []string {
out := []string{
// TODO: allow this to be overridden by the user.
fmt.Sprintf("TZ=%s", config.Get().System.Timezone),
fmt.Sprintf("STARTUP=%s", s.Config().Invocation),
fmt.Sprintf("SERVER_MEMORY=%d", s.MemoryLimit()),

View File

@@ -68,6 +68,21 @@ func (c *SFTPServer) Run() error {
}
conf := &ssh.ServerConfig{
Config: ssh.Config{
KeyExchanges: []string{
"curve25519-sha256", "curve25519-sha256@libssh.org",
"ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521",
"diffie-hellman-group14-sha256",
},
Ciphers: []string{
"aes128-gcm@openssh.com",
"chacha20-poly1305@openssh.com",
"aes128-ctr", "aes192-ctr", "aes256-ctr",
},
MACs: []string{
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256",
},
},
NoClientAuth: false,
MaxAuthTries: 6,
PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {

View File

@@ -1,3 +1,3 @@
package system
var Version = "develop"
var Version = "1.11.1"

View File

@@ -1,29 +0,0 @@
package system
import (
"math/rand"
"regexp"
"strings"
)
var ipTrimRegex = regexp.MustCompile(`(:\d*)?$`)
const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
// RandomString generates a random string of alpha-numeric characters using a
// pseudo-random number generator. The output of this function IS NOT cryptographically
// secure, it is used solely for generating random strings outside a security context.
func RandomString(n int) string {
var b strings.Builder
b.Grow(n)
for i := 0; i < n; i++ {
b.WriteByte(characters[rand.Intn(len(characters))])
}
return b.String()
}
// TrimIPSuffix removes the internal port value from an IP address to ensure we're only
// ever working directly with the IP address.
func TrimIPSuffix(s string) string {
return ipTrimRegex.ReplaceAllString(s, "")
}

View File

@@ -127,6 +127,7 @@ func GetDockerInfo(ctx context.Context) (types.Version, types.Info, error) {
if err != nil {
return types.Version{}, types.Info{}, err
}
defer c.Close()
dockerVersion, err := c.ServerVersion(ctx)
if err != nil {