Compare commits
17 Commits
release/v1
...
v1.11.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
774c0af0b0 | ||
|
|
71fbd9271e | ||
|
|
2d640209e5 | ||
|
|
304fd91283 | ||
|
|
18de96d7b8 | ||
|
|
a36cab1783 | ||
|
|
6e0c095bb8 | ||
|
|
14eea3b1e4 | ||
|
|
1bc77dc969 | ||
|
|
b8715d1d4f | ||
|
|
13d3490bcf | ||
|
|
e9b8b11fec | ||
|
|
43b7aa2536 | ||
|
|
9b8b3c90fb | ||
|
|
e74d8e3501 | ||
|
|
4b3bd2ff47 | ||
|
|
e652d2df84 |
6
.github/workflows/docker.yaml
vendored
6
.github/workflows/docker.yaml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/push.yaml
vendored
6
.github/workflows/push.yaml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -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
|
||||
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## v1.11.2
|
||||
### Fixed
|
||||
* Backups being restored from remote storage (s3) erroring out due to a closed stream.
|
||||
* Fix IP validation logic for activity logs filtering out valid IPs instead of invalid IPs
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ip := net.ParseIP(v.IP); ip == 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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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, "")
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user