Compare commits
15 Commits
dane/sqlit
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a76d84ea96 | ||
|
|
02cbf2df5b | ||
|
|
b6edf3acf9 | ||
|
|
c686992e85 | ||
|
|
c736c24118 | ||
|
|
9dfc651a91 | ||
|
|
ad26022c30 | ||
|
|
83861a6dec | ||
|
|
231e24aa33 | ||
|
|
e3ab241d7f | ||
|
|
c18e844689 | ||
|
|
8cee18a92b | ||
|
|
f952efd9c7 | ||
|
|
21cf66b2b4 | ||
|
|
251f91a08e |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## v1.7.1
|
||||
### Fixed
|
||||
* YAML parser has been updated to fix some strange issues
|
||||
|
||||
### Added
|
||||
* Added `Force Outgoing IP` option for servers to ensure outgoing traffic uses the server's IP address
|
||||
* Adds an option to control the level of gzip compression for backups
|
||||
|
||||
## v1.7.0
|
||||
### Fixed
|
||||
* Fixes multi-platform support for Wings' Docker image.
|
||||
|
||||
### Added
|
||||
* Adds support for tracking of SFTP actions, power actions, server commands, and file uploads by utilizing a local SQLite database and processing events before sending them to the Panel.
|
||||
* Adds support for configuring the MTU on the `pterodactyl0` network.
|
||||
|
||||
## v1.6.4
|
||||
### Fixed
|
||||
* Fixes a bug causing CPU limiting to not be properly applied to servers.
|
||||
|
||||
@@ -58,7 +58,7 @@ func newDiagnosticsCommand() *cobra.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
// diagnosticsCmdRun collects diagnostics about wings, it's configuration and the node.
|
||||
// diagnosticsCmdRun collects diagnostics about wings, its configuration and the node.
|
||||
// We collect:
|
||||
// - wings and docker versions
|
||||
// - relevant parts of daemon configuration
|
||||
|
||||
@@ -81,7 +81,7 @@ func init() {
|
||||
rootCommand.Flags().Bool("pprof", false, "if the pprof profiler should be enabled. The profiler will bind to localhost:6060 by default")
|
||||
rootCommand.Flags().Int("pprof-block-rate", 0, "enables block profile support, may have performance impacts")
|
||||
rootCommand.Flags().Int("pprof-port", 6060, "If provided with --pprof, the port it will run on")
|
||||
rootCommand.Flags().Bool("auto-tls", false, "pass in order to have wings generate and manage it's own SSL certificates using Let's Encrypt")
|
||||
rootCommand.Flags().Bool("auto-tls", false, "pass in order to have wings generate and manage its own SSL certificates using Let's Encrypt")
|
||||
rootCommand.Flags().String("tls-hostname", "", "required with --auto-tls, the FQDN for the generated SSL certificate")
|
||||
rootCommand.Flags().Bool("ignore-certificate-errors", false, "ignore certificate verification errors when executing API calls")
|
||||
|
||||
@@ -162,7 +162,7 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
// Every minute, write the current server states to the disk to allow for a more
|
||||
// seamless hard-reboot process in which wings will re-sync server states based
|
||||
// on it's last tracked state.
|
||||
// on its last tracked state.
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -167,10 +167,10 @@ type SystemConfiguration struct {
|
||||
// being sent to the Panel. By default this will send activity collected over the last minute. Keep
|
||||
// in mind that only a fixed number of activity log entries, defined by ActivitySendCount, will be sent
|
||||
// in each run.
|
||||
ActivitySendInterval int64 `default:"60" yaml:"activity_send_interval"`
|
||||
ActivitySendInterval int `default:"60" yaml:"activity_send_interval"`
|
||||
|
||||
// ActivitySendCount is the number of activity events to send per batch.
|
||||
ActivitySendCount int64 `default:"100" yaml:"activity_send_count"`
|
||||
ActivitySendCount int `default:"100" yaml:"activity_send_count"`
|
||||
|
||||
// If set to true, file permissions for a server will be checked when the process is
|
||||
// booted. This can cause boot delays if the server has a large amount of files. In most
|
||||
@@ -219,6 +219,15 @@ type Backups struct {
|
||||
//
|
||||
// Defaults to 0 (unlimited)
|
||||
WriteLimit int `default:"0" yaml:"write_limit"`
|
||||
|
||||
// CompressionLevel determines how much backups created by wings should be compressed.
|
||||
//
|
||||
// "none" -> no compression will be applied
|
||||
// "best_speed" -> uses gzip level 1 for fast speed
|
||||
// "best_compression" -> uses gzip level 9 for minimal disk space useage
|
||||
//
|
||||
// Defaults to "best_speed" (level 1)
|
||||
CompressionLevel string `default:"best_speed" yaml:"compression_level"`
|
||||
}
|
||||
|
||||
type Transfers struct {
|
||||
|
||||
@@ -12,6 +12,11 @@ import (
|
||||
// Defines the allocations available for a given server. When using the Docker environment
|
||||
// driver these correspond to mappings for the container that allow external connections.
|
||||
type Allocations struct {
|
||||
// ForceOutgoingIP causes a dedicated bridge network to be created for the
|
||||
// server with a special option, causing Docker to SNAT outgoing traffic to
|
||||
// the DefaultMapping's IP. This is important to servers which rely on external
|
||||
// services that check the IP of the server (Source Engine servers, for example).
|
||||
ForceOutgoingIP bool `json:"force_outgoing_ip"`
|
||||
// Defines the default allocation that should be used for this server. This is
|
||||
// what will be used for {SERVER_IP} and {SERVER_PORT} when modifying configuration
|
||||
// files or the startup arguments for a server.
|
||||
|
||||
@@ -41,12 +41,12 @@ func ConfigureDocker(ctx context.Context) error {
|
||||
nw := config.Get().Docker.Network
|
||||
resource, err := cli.NetworkInspect(ctx, nw.Name, types.NetworkInspectOptions{})
|
||||
if err != nil {
|
||||
if client.IsErrNotFound(err) {
|
||||
log.Info("creating missing pterodactyl0 interface, this could take a few seconds...")
|
||||
if err := createDockerNetwork(ctx, cli); err != nil {
|
||||
if !client.IsErrNotFound(err) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
log.Info("creating missing pterodactyl0 interface, this could take a few seconds...")
|
||||
if err := createDockerNetwork(ctx, cli); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,10 +147,12 @@ func (e *Environment) InSituUpdate() error {
|
||||
// currently available for it. If the container already exists it will be
|
||||
// returned.
|
||||
func (e *Environment) Create() error {
|
||||
ctx := context.Background()
|
||||
|
||||
// If the container already exists don't hit the user with an error, just return
|
||||
// the current information about it which is what we would do when creating the
|
||||
// container anyways.
|
||||
if _, err := e.ContainerInspect(context.Background()); err == nil {
|
||||
if _, err := e.ContainerInspect(ctx); err == nil {
|
||||
return nil
|
||||
} else if !client.IsErrNotFound(err) {
|
||||
return errors.Wrap(err, "environment/docker: failed to inspect container")
|
||||
@@ -190,7 +192,34 @@ func (e *Environment) Create() error {
|
||||
},
|
||||
}
|
||||
|
||||
tmpfsSize := strconv.Itoa(int(config.Get().Docker.TmpfsSize))
|
||||
networkMode := container.NetworkMode(config.Get().Docker.Network.Mode)
|
||||
if a.ForceOutgoingIP {
|
||||
e.log().Debug("environment/docker: forcing outgoing IP address")
|
||||
networkName := strings.ReplaceAll(e.Id, "-", "")
|
||||
networkMode = container.NetworkMode(networkName)
|
||||
|
||||
if _, err := e.client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{}); err != nil {
|
||||
if !client.IsErrNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := e.client.NetworkCreate(ctx, networkName, types.NetworkCreate{
|
||||
Driver: "bridge",
|
||||
EnableIPv6: false,
|
||||
Internal: false,
|
||||
Attachable: false,
|
||||
Ingress: false,
|
||||
ConfigOnly: false,
|
||||
Options: map[string]string{
|
||||
"encryption": "false",
|
||||
"com.docker.network.bridge.default_bridge": "false",
|
||||
"com.docker.network.host_ipv4": a.DefaultMapping.Ip,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hostConf := &container.HostConfig{
|
||||
PortBindings: a.DockerBindings(),
|
||||
@@ -202,7 +231,7 @@ func (e *Environment) Create() error {
|
||||
// Configure the /tmp folder mapping in containers. This is necessary for some
|
||||
// games that need to make use of it for downloads and other installation processes.
|
||||
Tmpfs: map[string]string{
|
||||
"/tmp": "rw,exec,nosuid,size=" + tmpfsSize + "M",
|
||||
"/tmp": "rw,exec,nosuid,size=" + strconv.Itoa(int(config.Get().Docker.TmpfsSize)) + "M",
|
||||
},
|
||||
|
||||
// Define resource limits for the container based on the data passed through
|
||||
@@ -231,10 +260,10 @@ func (e *Environment) Create() error {
|
||||
"setpcap", "mknod", "audit_write", "net_raw", "dac_override",
|
||||
"fowner", "fsetid", "net_bind_service", "sys_chroot", "setfcap",
|
||||
},
|
||||
NetworkMode: container.NetworkMode(config.Get().Docker.Network.Mode),
|
||||
NetworkMode: networkMode,
|
||||
}
|
||||
|
||||
if _, err := e.client.ContainerCreate(context.Background(), conf, hostConf, nil, nil, e.Id); err != nil {
|
||||
if _, err := e.client.ContainerCreate(ctx, conf, hostConf, nil, nil, e.Id); err != nil {
|
||||
return errors.Wrap(err, "environment/docker: failed to create container")
|
||||
}
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -49,6 +49,7 @@ require (
|
||||
github.com/go-co-op/gocron v1.15.0
|
||||
github.com/goccy/go-json v0.9.6
|
||||
github.com/klauspost/compress v1.15.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.23.8
|
||||
)
|
||||
|
||||
@@ -121,7 +122,6 @@ require (
|
||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb // indirect
|
||||
google.golang.org/grpc v1.45.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.16.17 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.1.1 // indirect
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
type activityCron struct {
|
||||
mu *system.AtomicBool
|
||||
manager *server.Manager
|
||||
max int64
|
||||
max int
|
||||
}
|
||||
|
||||
// Run executes the cronjob and ensures we fetch and send all of the stored activity to the
|
||||
@@ -30,7 +30,7 @@ func (ac *activityCron) Run(ctx context.Context) error {
|
||||
var activity []models.Activity
|
||||
tx := database.Instance().WithContext(ctx).
|
||||
Where("event NOT LIKE ?", "server:sftp.%").
|
||||
Limit(int(ac.max)).
|
||||
Limit(ac.max).
|
||||
Find(&activity)
|
||||
|
||||
if tx.Error != nil {
|
||||
|
||||
@@ -3,7 +3,7 @@ package cron
|
||||
import (
|
||||
"context"
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
log2 "github.com/apex/log"
|
||||
"github.com/go-co-op/gocron"
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/server"
|
||||
@@ -40,7 +40,13 @@ func Scheduler(ctx context.Context, m *server.Manager) (*gocron.Scheduler, error
|
||||
}
|
||||
|
||||
s := gocron.NewScheduler(l)
|
||||
_, _ = s.Tag("activity").Every(config.Get().System.ActivitySendInterval).Seconds().Do(func() {
|
||||
log := log2.WithField("subsystem", "cron")
|
||||
|
||||
interval := time.Duration(config.Get().System.ActivitySendInterval) * time.Second
|
||||
log.WithField("interval", interval).Info("configuring system crons")
|
||||
|
||||
_, _ = s.Tag("activity").Every(interval).Do(func() {
|
||||
log.WithField("cron", "activity").Debug("sending internal activity events to Panel")
|
||||
if err := activity.Run(ctx); err != nil {
|
||||
if errors.Is(err, ErrCronRunning) {
|
||||
log.WithField("cron", "activity").Warn("activity process is already running, skipping...")
|
||||
@@ -50,7 +56,8 @@ func Scheduler(ctx context.Context, m *server.Manager) (*gocron.Scheduler, error
|
||||
}
|
||||
})
|
||||
|
||||
_, _ = s.Tag("sftp").Every(config.Get().System.ActivitySendInterval).Seconds().Do(func() {
|
||||
_, _ = s.Tag("sftp").Every(interval).Do(func() {
|
||||
log.WithField("cron", "sftp").Debug("sending sftp events to Panel")
|
||||
if err := sftp.Run(ctx); err != nil {
|
||||
if errors.Is(err, ErrCronRunning) {
|
||||
log.WithField("cron", "sftp").Warn("sftp events process already running, skipping...")
|
||||
|
||||
@@ -7,14 +7,13 @@ import (
|
||||
"github.com/pterodactyl/wings/internal/models"
|
||||
"github.com/pterodactyl/wings/server"
|
||||
"github.com/pterodactyl/wings/system"
|
||||
"gorm.io/gorm"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type sftpCron struct {
|
||||
mu *system.AtomicBool
|
||||
manager *server.Manager
|
||||
max int64
|
||||
max int
|
||||
}
|
||||
|
||||
type mapKey struct {
|
||||
@@ -52,7 +51,7 @@ func (sc *sftpCron) Run(ctx context.Context) error {
|
||||
events := &eventMap{
|
||||
m: map[mapKey]*models.Activity{},
|
||||
ids: []int{},
|
||||
max: int(sc.max),
|
||||
max: sc.max,
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -79,17 +78,13 @@ func (sc *sftpCron) Run(ctx context.Context) error {
|
||||
if len(events.m) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = database.Instance().Transaction(func(tx *gorm.DB) error {
|
||||
tx.Where("id IN ?", events.ids).Delete(&models.Activity{})
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
if err := sc.manager.Client().SendActivityLogs(ctx, events.Elements()); err != nil {
|
||||
return errors.Wrap(err, "failed to send sftp activity logs to Panel")
|
||||
}
|
||||
|
||||
return sc.manager.Client().SendActivityLogs(ctx, events.Elements())
|
||||
})
|
||||
|
||||
return errors.WithStack(err)
|
||||
if tx := database.Instance().Where("id IN ?", events.ids).Delete(&models.Activity{}); tx.Error != nil {
|
||||
return errors.WithStack(tx.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchRecords returns a group of activity events starting at the given offset. This is used
|
||||
@@ -102,7 +97,7 @@ func (sc *sftpCron) fetchRecords(ctx context.Context, offset int) (activity []mo
|
||||
Where("event LIKE ?", "server:sftp.%").
|
||||
Order("event DESC").
|
||||
Offset(offset).
|
||||
Limit(int(sc.max)).
|
||||
Limit(sc.max).
|
||||
Find(&activity)
|
||||
if tx.Error != nil {
|
||||
err = errors.WithStack(tx.Error)
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"github.com/pterodactyl/wings/internal/models"
|
||||
"github.com/pterodactyl/wings/system"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
var o system.AtomicBool
|
||||
@@ -20,11 +22,25 @@ func Initialize() error {
|
||||
panic("database: attempt to initialize more than once during application lifecycle")
|
||||
}
|
||||
p := filepath.Join(config.Get().System.RootDirectory, "wings.db")
|
||||
instance, err := gorm.Open(sqlite.Open(p), &gorm.Config{})
|
||||
instance, err := gorm.Open(sqlite.Open(p), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "database: could not open database file")
|
||||
}
|
||||
db = instance
|
||||
if sql, err := db.DB(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
} else {
|
||||
sql.SetMaxOpenConns(1)
|
||||
sql.SetConnMaxLifetime(time.Hour)
|
||||
}
|
||||
if tx := db.Exec("PRAGMA synchronous = OFF"); tx.Error != nil {
|
||||
return errors.WithStack(tx.Error)
|
||||
}
|
||||
if tx := db.Exec("PRAGMA journal_mode = MEMORY"); tx.Error != nil {
|
||||
return errors.WithStack(tx.Error)
|
||||
}
|
||||
if err := db.AutoMigrate(&models.Activity{}); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
@@ -60,5 +60,8 @@ func (a *Activity) BeforeCreate(_ *gorm.DB) error {
|
||||
a.Timestamp = time.Now()
|
||||
}
|
||||
a.Timestamp = a.Timestamp.UTC()
|
||||
if a.Metadata == nil {
|
||||
a.Metadata = ActivityMeta{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/icza/dyno"
|
||||
"github.com/magiconair/properties"
|
||||
"gopkg.in/ini.v1"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
)
|
||||
|
||||
@@ -180,7 +180,7 @@ func postServerReinstall(c *gin.Context) {
|
||||
c.Status(http.StatusAccepted)
|
||||
}
|
||||
|
||||
// Deletes a server from the wings daemon and dissociate it's objects.
|
||||
// Deletes a server from the wings daemon and dissociate its objects.
|
||||
func deleteServer(c *gin.Context) {
|
||||
s := middleware.ExtractServer(c)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package router
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"github.com/pterodactyl/wings/internal/models"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
@@ -600,6 +601,11 @@ func postServerUploadFiles(c *gin.Context) {
|
||||
if err := handleFileUpload(p, s, header); err != nil {
|
||||
NewServerError(err, s).Abort(c)
|
||||
return
|
||||
} else {
|
||||
s.SaveActivity(s.NewRequestActivity(token.UserUuid, c.Request.RemoteAddr), server.ActivityFileUploaded, models.ActivityMeta{
|
||||
"file": header.Filename,
|
||||
"directory": filepath.Clean(directory),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -617,6 +623,5 @@ func handleFileUpload(p string, s *server.Server, header *multipart.FileHeader)
|
||||
if err := s.Filesystem().Writefile(p, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ type UploadPayload struct {
|
||||
jwt.Payload
|
||||
|
||||
ServerUuid string `json:"server_uuid"`
|
||||
UserUuid string `json:"user_uuid"`
|
||||
UniqueId string `json:"unique_id"`
|
||||
}
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ func (h *Handler) HandleInbound(ctx context.Context, m Message) error {
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
_ = h.ra.Save(h.server, models.Event(server.ActivityPowerPrefix+action), nil)
|
||||
h.server.SaveActivity(h.ra, models.Event(server.ActivityPowerPrefix+action), nil)
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -429,11 +429,13 @@ func (h *Handler) HandleInbound(ctx context.Context, m Message) error {
|
||||
}
|
||||
}
|
||||
|
||||
_ = h.ra.Save(h.server, server.ActivityConsoleCommand, models.ActivityMeta{
|
||||
if err := h.server.Environment.SendCommand(strings.Join(m.Args, "")); err != nil {
|
||||
return err
|
||||
}
|
||||
h.server.SaveActivity(h.ra, server.ActivityConsoleCommand, models.ActivityMeta{
|
||||
"command": strings.Join(m.Args, ""),
|
||||
})
|
||||
|
||||
return h.server.Environment.SendCommand(strings.Join(m.Args, ""))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: ptero-wings
|
||||
Version: 1.5.3
|
||||
Version: 1.7.0
|
||||
Release: 1%{?dist}
|
||||
Summary: The server control plane for Pterodactyl Panel. Written from the ground-up with security, speed, and stability in mind.
|
||||
BuildArch: x86_64
|
||||
@@ -91,6 +91,9 @@ rm -rf /var/log/pterodactyl
|
||||
wings --version
|
||||
|
||||
%changelog
|
||||
* Wed Sep 14 2022 Chance Callahan <ccallaha@redhat.com> - 1.7.0-1
|
||||
- Updating specfile to match stable release.
|
||||
|
||||
* Wed Oct 27 2021 Capitol Hosting Solutions Systems Engineering <syseng@chs.gg> - 1.5.3-1
|
||||
- specfile by Capitol Hosting Solutions, Upstream by Pterodactyl
|
||||
- Rebased for https://github.com/pterodactyl/wings/releases/tag/v1.5.3
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"emperror.dev/errors"
|
||||
"github.com/pterodactyl/wings/internal/database"
|
||||
"github.com/pterodactyl/wings/internal/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
const ActivityPowerPrefix = "server:power."
|
||||
@@ -15,6 +17,7 @@ const (
|
||||
ActivitySftpCreateDirectory = models.Event("server:sftp.create-directory")
|
||||
ActivitySftpRename = models.Event("server:sftp.rename")
|
||||
ActivitySftpDelete = models.Event("server:sftp.delete")
|
||||
ActivityFileUploaded = models.Event("server:file.uploaded")
|
||||
)
|
||||
|
||||
// RequestActivity is a wrapper around a LoggedEvent that is able to track additional request
|
||||
@@ -34,29 +37,6 @@ func (ra RequestActivity) Event(event models.Event, metadata models.ActivityMeta
|
||||
return a.SetUser(ra.user)
|
||||
}
|
||||
|
||||
// Save creates a new event instance and saves it. If an error is encountered it is automatically
|
||||
// logged to the provided server's error logging output. The error is also returned to the caller
|
||||
// but can be ignored.
|
||||
func (ra RequestActivity) Save(s *Server, event models.Event, metadata models.ActivityMeta) error {
|
||||
if tx := database.Instance().Create(ra.Event(event, metadata)); tx.Error != nil {
|
||||
err := errors.WithStackIf(tx.Error)
|
||||
|
||||
s.Log().WithField("error", err).WithField("event", event).Error("activity: failed to save event")
|
||||
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IP returns the IP address associated with this entry.
|
||||
func (ra RequestActivity) IP() string {
|
||||
return ra.ip
|
||||
}
|
||||
|
||||
func (ra *RequestActivity) User() string {
|
||||
return ra.user
|
||||
}
|
||||
|
||||
// SetUser clones the RequestActivity struct and sets a new user value on the copy
|
||||
// before returning it.
|
||||
func (ra RequestActivity) SetUser(u string) RequestActivity {
|
||||
@@ -68,3 +48,17 @@ func (ra RequestActivity) SetUser(u string) RequestActivity {
|
||||
func (s *Server) NewRequestActivity(user string, ip string) RequestActivity {
|
||||
return RequestActivity{server: s.ID(), user: user, ip: ip}
|
||||
}
|
||||
|
||||
// SaveActivity saves an activity entry to the database in a background routine. If an error is
|
||||
// encountered it is logged but not returned to the caller.
|
||||
func (s *Server) SaveActivity(a RequestActivity, event models.Event, metadata models.ActivityMeta) {
|
||||
ctx, cancel := context.WithTimeout(s.Context(), time.Second*3)
|
||||
go func() {
|
||||
defer cancel()
|
||||
if tx := database.Instance().WithContext(ctx).Create(a.Event(event, metadata)); tx.Error != nil {
|
||||
s.Log().WithField("error", errors.WithStack(tx.Error)).
|
||||
WithField("event", event).
|
||||
Error("activity: failed to save event")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@ type EggConfiguration struct {
|
||||
FileDenylist []string `json:"file_denylist"`
|
||||
}
|
||||
|
||||
type ConfigurationMeta struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type Configuration struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
@@ -24,6 +29,8 @@ type Configuration struct {
|
||||
// docker containers as well as in log output.
|
||||
Uuid string `json:"uuid"`
|
||||
|
||||
Meta ConfigurationMeta `json:"meta"`
|
||||
|
||||
// Whether or not the server is in a suspended state. Suspended servers cannot
|
||||
// be started or modified except in certain scenarios by an admin user.
|
||||
Suspended bool `json:"suspended"`
|
||||
|
||||
@@ -62,8 +62,21 @@ func (a *Archive) Create(dst string) error {
|
||||
writer = f
|
||||
}
|
||||
|
||||
// The default compression level is BestSpeed
|
||||
var cl = pgzip.BestSpeed
|
||||
|
||||
// Choose which compression level to use based on the compression_level configuration option
|
||||
switch config.Get().System.Backups.CompressionLevel {
|
||||
case "none":
|
||||
cl = pgzip.NoCompression
|
||||
case "best_speed":
|
||||
cl = pgzip.BestSpeed
|
||||
case "best_compression":
|
||||
cl = pgzip.BestCompression
|
||||
}
|
||||
|
||||
// Create a new gzip writer around the file.
|
||||
gw, _ := pgzip.NewWriterLevel(writer, pgzip.BestSpeed)
|
||||
gw, _ := pgzip.NewWriterLevel(writer, cl)
|
||||
_ = gw.SetConcurrency(1<<20, 1)
|
||||
defer gw.Close()
|
||||
|
||||
@@ -148,7 +161,7 @@ func (a *Archive) withFilesCallback(tw *tar.Writer) func(path string, de *godirw
|
||||
// Adds a given file path to the final archive being created.
|
||||
func (a *Archive) addToArchive(p string, rp string, w *tar.Writer) error {
|
||||
// Lstat the file, this will give us the same information as Stat except that it will not
|
||||
// follow a symlink to it's target automatically. This is important to avoid including
|
||||
// follow a symlink to its target automatically. This is important to avoid including
|
||||
// files that exist outside the server root unintentionally in the backup.
|
||||
s, err := os.Lstat(p)
|
||||
if err != nil {
|
||||
|
||||
@@ -71,7 +71,7 @@ func (fs *Filesystem) HasSpaceAvailable(allowStaleValue bool) bool {
|
||||
// If space is -1 or 0 just return true, means they're allowed unlimited.
|
||||
//
|
||||
// Technically we could skip disk space calculation because we don't need to check if the
|
||||
// server exceeds it's limit but because this method caches the disk usage it would be best
|
||||
// server exceeds its limit but because this method caches the disk usage it would be best
|
||||
// to calculate the disk usage and always return true.
|
||||
if fs.MaxDisk() == 0 {
|
||||
return true
|
||||
|
||||
@@ -44,7 +44,7 @@ func (eh *eventHandler) Log(e models.Event, fa FileAction) error {
|
||||
}
|
||||
|
||||
if tx := database.Instance().Create(a.SetUser(eh.user)); tx.Error != nil {
|
||||
return errors.Wrap(tx.Error, "sftp: failed to save event to database")
|
||||
return errors.WithStack(tx.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -53,6 +53,6 @@ func (eh *eventHandler) Log(e models.Event, fa FileAction) error {
|
||||
// if an error is encountered during the logging of the event.
|
||||
func (eh *eventHandler) MustLog(e models.Event, fa FileAction) {
|
||||
if err := eh.Log(e, fa); err != nil {
|
||||
log.WithField("error", err).Fatal("sftp: failed to log event")
|
||||
log.WithField("error", errors.WithStack(err)).WithField("event", e).Error("sftp: failed to log event")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package system
|
||||
|
||||
var Version = "develop"
|
||||
var Version = "1.7.1"
|
||||
|
||||
@@ -23,7 +23,7 @@ type SinkPool struct {
|
||||
}
|
||||
|
||||
// NewSinkPool returns a new empty SinkPool. A sink pool generally lives with a
|
||||
// server instance for it's full lifetime.
|
||||
// server instance for its full lifetime.
|
||||
func NewSinkPool() *SinkPool {
|
||||
return &SinkPool{}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user