Compare commits
3 Commits
release/v1
...
fix/arm64-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a672b9ad2e | ||
|
|
56a9693767 | ||
|
|
447f1c31aa |
@@ -1,13 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 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
|
## v1.6.4
|
||||||
### Fixed
|
### Fixed
|
||||||
* Fixes a bug causing CPU limiting to not be properly applied to servers.
|
* Fixes a bug causing CPU limiting to not be properly applied to servers.
|
||||||
|
|||||||
@@ -20,10 +20,9 @@ I would like to extend my sincere thanks to the following sponsors for helping f
|
|||||||
| ------- | ----- |
|
| ------- | ----- |
|
||||||
| [**WISP**](https://wisp.gg) | Extra features. |
|
| [**WISP**](https://wisp.gg) | Extra features. |
|
||||||
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
||||||
| [**Fragnet**](https://fragnet.net) | Providing low latency, high-end game hosting solutions to gamers, game studios and eSports platforms. |
|
|
||||||
| [**Tempest**](https://tempest.net/) | Tempest Hosting is a subsidiary of Path Network, Inc. offering unmetered DDoS protected 10Gbps dedicated servers, starting at just $80/month. Full anycast, tons of filters. |
|
| [**Tempest**](https://tempest.net/) | Tempest Hosting is a subsidiary of Path Network, Inc. offering unmetered DDoS protected 10Gbps dedicated servers, starting at just $80/month. Full anycast, tons of filters. |
|
||||||
| [**Bloom.host**](https://bloom.host) | Bloom.host offers dedicated core VPS and Minecraft hosting with Ryzen 9 processors. With owned-hardware, we offer truly unbeatable prices on high-performance hosting. |
|
| [**Bloom.host**](https://bloom.host) | Bloom.host offers dedicated core VPS and Minecraft hosting with Ryzen 9 processors. With owned-hardware, we offer truly unbeatable prices on high-performance hosting. |
|
||||||
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. |
|
||||||
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
||||||
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
||||||
| [**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. |
|
| [**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. |
|
||||||
|
|||||||
13
cmd/root.go
13
cmd/root.go
@@ -5,8 +5,6 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pterodactyl/wings/internal/cron"
|
|
||||||
"github.com/pterodactyl/wings/internal/database"
|
|
||||||
log2 "log"
|
log2 "log"
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
@@ -132,10 +130,6 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := database.Initialize(); err != nil {
|
|
||||||
log.WithField("error", err).Fatal("failed to initialize database")
|
|
||||||
}
|
|
||||||
|
|
||||||
manager, err := server.NewManager(cmd.Context(), pclient)
|
manager, err := server.NewManager(cmd.Context(), pclient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithField("error", err).Fatal("failed to load server configurations")
|
log.WithField("error", err).Fatal("failed to load server configurations")
|
||||||
@@ -265,13 +259,6 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if s, err := cron.Scheduler(cmd.Context(), manager); err != nil {
|
|
||||||
log.WithField("error", err).Fatal("failed to initialize cron system")
|
|
||||||
} else {
|
|
||||||
log.WithField("subsystem", "cron").Info("starting cron processes")
|
|
||||||
s.StartAsync()
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// Run the SFTP server.
|
// Run the SFTP server.
|
||||||
if err := sftp.New(manager).Run(); err != nil {
|
if err := sftp.New(manager).Run(); err != nil {
|
||||||
|
|||||||
@@ -163,15 +163,6 @@ type SystemConfiguration struct {
|
|||||||
// disk usage is not a concern.
|
// disk usage is not a concern.
|
||||||
DiskCheckInterval int64 `default:"150" yaml:"disk_check_interval"`
|
DiskCheckInterval int64 `default:"150" yaml:"disk_check_interval"`
|
||||||
|
|
||||||
// ActivitySendInterval is the amount of time that should ellapse between aggregated server activity
|
|
||||||
// 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 int `default:"60" yaml:"activity_send_interval"`
|
|
||||||
|
|
||||||
// ActivitySendCount is the number of activity events to send per batch.
|
|
||||||
ActivitySendCount int `default:"100" yaml:"activity_send_count"`
|
|
||||||
|
|
||||||
// If set to true, file permissions for a server will be checked when the process is
|
// 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
|
// booted. This can cause boot delays if the server has a large amount of files. In most
|
||||||
// cases disabling this should not have any major impact unless external processes are
|
// cases disabling this should not have any major impact unless external processes are
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ type DockerNetworkConfiguration struct {
|
|||||||
Mode string `default:"pterodactyl_nw" yaml:"network_mode"`
|
Mode string `default:"pterodactyl_nw" yaml:"network_mode"`
|
||||||
IsInternal bool `default:"false" yaml:"is_internal"`
|
IsInternal bool `default:"false" yaml:"is_internal"`
|
||||||
EnableICC bool `default:"true" yaml:"enable_icc"`
|
EnableICC bool `default:"true" yaml:"enable_icc"`
|
||||||
NetworkMTU int64 `default:"1500" yaml:"network_mtu"`
|
|
||||||
Interfaces dockerNetworkInterfaces `yaml:"interfaces"`
|
Interfaces dockerNetworkInterfaces `yaml:"interfaces"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func createDockerNetwork(ctx context.Context, cli *client.Client) error {
|
|||||||
"com.docker.network.bridge.enable_ip_masquerade": "true",
|
"com.docker.network.bridge.enable_ip_masquerade": "true",
|
||||||
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
|
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
|
||||||
"com.docker.network.bridge.name": "pterodactyl0",
|
"com.docker.network.bridge.name": "pterodactyl0",
|
||||||
"com.docker.network.driver.mtu": strconv.FormatInt(nw.NetworkMTU, 10),
|
"com.docker.network.driver.mtu": "1500",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
24
go.mod
24
go.mod
@@ -37,22 +37,16 @@ require (
|
|||||||
github.com/pkg/sftp v1.13.4
|
github.com/pkg/sftp v1.13.4
|
||||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
|
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
|
||||||
github.com/spf13/cobra v1.4.0
|
github.com/spf13/cobra v1.4.0
|
||||||
github.com/stretchr/testify v1.7.5
|
github.com/stretchr/testify v1.7.0
|
||||||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
|
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
gopkg.in/ini.v1 v1.66.4
|
gopkg.in/ini.v1 v1.66.4
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require github.com/goccy/go-json v0.9.6
|
||||||
github.com/glebarez/sqlite v1.4.6
|
|
||||||
github.com/go-co-op/gocron v1.15.0
|
|
||||||
github.com/goccy/go-json v0.9.6
|
|
||||||
github.com/klauspost/compress v1.15.1
|
|
||||||
gorm.io/gorm v1.23.8
|
|
||||||
)
|
|
||||||
|
|
||||||
require golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
require golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
@@ -71,7 +65,6 @@ require (
|
|||||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||||
github.com/gammazero/deque v0.1.1 // indirect
|
github.com/gammazero/deque v0.1.1 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.17.3 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.10.1 // indirect
|
github.com/go-playground/validator/v10 v10.10.1 // indirect
|
||||||
@@ -80,10 +73,9 @@ require (
|
|||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/gorilla/mux v1.7.4 // indirect
|
github.com/gorilla/mux v1.7.4 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
|
github.com/klauspost/compress v1.15.1 // indirect
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
github.com/magefile/mage v1.13.0 // indirect
|
github.com/magefile/mage v1.13.0 // indirect
|
||||||
@@ -104,8 +96,6 @@ require (
|
|||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
github.com/prometheus/common v0.32.1 // indirect
|
github.com/prometheus/common v0.32.1 // indirect
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||||
@@ -121,9 +111,5 @@ require (
|
|||||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb // indirect
|
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb // indirect
|
||||||
google.golang.org/grpc v1.45.0 // indirect
|
google.golang.org/grpc v1.45.0 // indirect
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
modernc.org/libc v1.16.17 // indirect
|
|
||||||
modernc.org/mathutil v1.4.1 // indirect
|
|
||||||
modernc.org/memory v1.1.1 // indirect
|
|
||||||
modernc.org/sqlite v1.17.3 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
60
go.sum
60
go.sum
@@ -400,12 +400,6 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
|||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||||
github.com/glebarez/go-sqlite v1.17.3 h1:Rji9ROVSTTfjuWD6j5B+8DtkNvPILoUC3xRhkQzGxvk=
|
|
||||||
github.com/glebarez/go-sqlite v1.17.3/go.mod h1:Hg+PQuhUy98XCxWEJEaWob8x7lhJzhNYF1nZbUiRGIY=
|
|
||||||
github.com/glebarez/sqlite v1.4.6 h1:D5uxD2f6UJ82cHnVtO2TZ9pqsLyto3fpDKHIk2OsR8A=
|
|
||||||
github.com/glebarez/sqlite v1.4.6/go.mod h1:WYEtEFjhADPaPJqL/PGlbQQGINBA3eUAfDNbKFJf/zA=
|
|
||||||
github.com/go-co-op/gocron v1.15.0 h1:XmiPazahD9aq0/QdK5toCVHfgTXfrZ/s83RpAgzr6SM=
|
|
||||||
github.com/go-co-op/gocron v1.15.0/go.mod h1:On9zUZTv7FBeuj9D/cdYyAWcPUiLqqAx7nsPHd0EmKM=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@@ -613,11 +607,6 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
|
|||||||
github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ=
|
github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ=
|
||||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||||
github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=
|
github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
|
||||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
@@ -708,7 +697,6 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
|
|||||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
@@ -880,10 +868,6 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
|||||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
@@ -948,17 +932,14 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q=
|
|
||||||
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
@@ -1309,14 +1290,12 @@ golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -1400,7 +1379,6 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
|
|||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||||
golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
@@ -1577,11 +1555,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE=
|
|
||||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
|
||||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
@@ -1637,33 +1612,6 @@ k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
|||||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
||||||
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
|
||||||
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
|
|
||||||
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
|
|
||||||
modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
|
|
||||||
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
|
|
||||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
|
||||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
|
||||||
modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
|
|
||||||
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
|
|
||||||
modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
|
|
||||||
modernc.org/libc v1.16.7/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
|
|
||||||
modernc.org/libc v1.16.8/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
|
|
||||||
modernc.org/libc v1.16.17 h1:rXo8IZJvP+QSN1KrlV23dtkM3XfGYXjx3RbLLzBtndM=
|
|
||||||
modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
|
|
||||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
|
||||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU=
|
|
||||||
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
|
||||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
|
||||||
modernc.org/sqlite v1.17.3 h1:iE+coC5g17LtByDYDWKpR6m2Z9022YrSh3bumwOnIrI=
|
|
||||||
modernc.org/sqlite v1.17.3/go.mod h1:10hPVYar9C0kfXuTWGz8s0XtB8uAGymUy51ZzStYe3k=
|
|
||||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
|
||||||
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
|
|
||||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
||||||
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
package cron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/pterodactyl/wings/internal/database"
|
|
||||||
"github.com/pterodactyl/wings/internal/models"
|
|
||||||
"github.com/pterodactyl/wings/server"
|
|
||||||
"github.com/pterodactyl/wings/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
type activityCron struct {
|
|
||||||
mu *system.AtomicBool
|
|
||||||
manager *server.Manager
|
|
||||||
max int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run executes the cronjob and ensures we fetch and send all of 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
|
|
||||||
// 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
|
|
||||||
// go ahead and mark it as no longer running.
|
|
||||||
if !ac.mu.SwapIf(true) {
|
|
||||||
return errors.WithStack(ErrCronRunning)
|
|
||||||
}
|
|
||||||
defer ac.mu.Store(false)
|
|
||||||
|
|
||||||
var activity []models.Activity
|
|
||||||
tx := database.Instance().WithContext(ctx).
|
|
||||||
Where("event NOT LIKE ?", "server:sftp.%").
|
|
||||||
Limit(ac.max).
|
|
||||||
Find(&activity)
|
|
||||||
|
|
||||||
if tx.Error != nil {
|
|
||||||
return errors.WithStack(tx.Error)
|
|
||||||
}
|
|
||||||
if len(activity) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ac.manager.Client().SendActivityLogs(ctx, activity); 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
tx = database.Instance().WithContext(ctx).Where("id IN ?", ids).Delete(&models.Activity{})
|
|
||||||
if tx.Error != nil {
|
|
||||||
return errors.WithStack(tx.Error)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package cron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"emperror.dev/errors"
|
|
||||||
log2 "github.com/apex/log"
|
|
||||||
"github.com/go-co-op/gocron"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/server"
|
|
||||||
"github.com/pterodactyl/wings/system"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ErrCronRunning = errors.Sentinel("cron: job already running")
|
|
||||||
|
|
||||||
var o system.AtomicBool
|
|
||||||
|
|
||||||
// Scheduler configures the internal cronjob system for Wings and returns the scheduler
|
|
||||||
// instance to the caller. This should only be called once per application lifecycle, additional
|
|
||||||
// calls will result in an error being returned.
|
|
||||||
func Scheduler(ctx context.Context, m *server.Manager) (*gocron.Scheduler, error) {
|
|
||||||
if !o.SwapIf(true) {
|
|
||||||
return nil, errors.New("cron: cannot call scheduler more than once in application lifecycle")
|
|
||||||
}
|
|
||||||
l, err := time.LoadLocation(config.Get().System.Timezone)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cron: failed to parse configured system timezone")
|
|
||||||
}
|
|
||||||
|
|
||||||
activity := activityCron{
|
|
||||||
mu: system.NewAtomicBool(false),
|
|
||||||
manager: m,
|
|
||||||
max: config.Get().System.ActivitySendCount,
|
|
||||||
}
|
|
||||||
|
|
||||||
sftp := sftpCron{
|
|
||||||
mu: system.NewAtomicBool(false),
|
|
||||||
manager: m,
|
|
||||||
max: config.Get().System.ActivitySendCount,
|
|
||||||
}
|
|
||||||
|
|
||||||
s := gocron.NewScheduler(l)
|
|
||||||
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...")
|
|
||||||
} else {
|
|
||||||
log.WithField("cron", "activity").WithField("error", err).Error("activity process failed to execute")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
_, _ = 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...")
|
|
||||||
} else {
|
|
||||||
log.WithField("cron", "sftp").WithField("error", err).Error("sftp events process failed to execute")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
package cron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/pterodactyl/wings/internal/database"
|
|
||||||
"github.com/pterodactyl/wings/internal/models"
|
|
||||||
"github.com/pterodactyl/wings/server"
|
|
||||||
"github.com/pterodactyl/wings/system"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sftpCron struct {
|
|
||||||
mu *system.AtomicBool
|
|
||||||
manager *server.Manager
|
|
||||||
max int
|
|
||||||
}
|
|
||||||
|
|
||||||
type mapKey struct {
|
|
||||||
User string
|
|
||||||
Server string
|
|
||||||
IP string
|
|
||||||
Event models.Event
|
|
||||||
Timestamp string
|
|
||||||
}
|
|
||||||
|
|
||||||
type eventMap struct {
|
|
||||||
max int
|
|
||||||
ids []int
|
|
||||||
m map[mapKey]*models.Activity
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run executes the SFTP reconciliation cron. This job will pull all of the SFTP specific events
|
|
||||||
// and merge them together across user, server, ip, and event. This allows a SFTP event that deletes
|
|
||||||
// tens or hundreds of files to be tracked as a single "deletion" event so long as they all occur
|
|
||||||
// within the same one minute period of time (starting at the first timestamp for the group). Without
|
|
||||||
// this we'd end up flooding the Panel event log with excessive data that is of no use to end users.
|
|
||||||
func (sc *sftpCron) Run(ctx context.Context) error {
|
|
||||||
if !sc.mu.SwapIf(true) {
|
|
||||||
return errors.WithStack(ErrCronRunning)
|
|
||||||
}
|
|
||||||
defer sc.mu.Store(false)
|
|
||||||
|
|
||||||
var o int
|
|
||||||
activity, err := sc.fetchRecords(ctx, o)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o += len(activity)
|
|
||||||
|
|
||||||
events := &eventMap{
|
|
||||||
m: map[mapKey]*models.Activity{},
|
|
||||||
ids: []int{},
|
|
||||||
max: sc.max,
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
if len(activity) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
slen := len(events.ids)
|
|
||||||
for _, a := range activity {
|
|
||||||
events.Push(a)
|
|
||||||
}
|
|
||||||
if len(events.ids) > slen {
|
|
||||||
// Execute the query again, we found some events so we want to continue
|
|
||||||
// with this. Start at the next offset.
|
|
||||||
activity, err = sc.fetchRecords(ctx, o)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
o += len(activity)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(events.m) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := sc.manager.Client().SendActivityLogs(ctx, events.Elements()); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to send sftp activity logs to Panel")
|
|
||||||
}
|
|
||||||
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
|
|
||||||
// since we might need to make multiple database queries to select enough events to properly
|
|
||||||
// fill up our request to the given maximum. This is due to the fact that this cron merges any
|
|
||||||
// activity that line up across user, server, ip, and event into a single activity record when
|
|
||||||
// sending the data to the Panel.
|
|
||||||
func (sc *sftpCron) fetchRecords(ctx context.Context, offset int) (activity []models.Activity, err error) {
|
|
||||||
tx := database.Instance().WithContext(ctx).
|
|
||||||
Where("event LIKE ?", "server:sftp.%").
|
|
||||||
Order("event DESC").
|
|
||||||
Offset(offset).
|
|
||||||
Limit(sc.max).
|
|
||||||
Find(&activity)
|
|
||||||
if tx.Error != nil {
|
|
||||||
err = errors.WithStack(tx.Error)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push adds an activity to the event mapping, or de-duplicates it and merges the files metadata
|
|
||||||
// into the existing entity that exists.
|
|
||||||
func (em *eventMap) Push(a models.Activity) {
|
|
||||||
m := em.forActivity(a)
|
|
||||||
// If no activity entity is returned we've hit the cap for the number of events to
|
|
||||||
// send along to the Panel. Just skip over this record and we'll account for it in
|
|
||||||
// the next iteration.
|
|
||||||
if m == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
em.ids = append(em.ids, a.ID)
|
|
||||||
// Always reduce this to the first timestamp that was recorded for the set
|
|
||||||
// of events, and not
|
|
||||||
if a.Timestamp.Before(m.Timestamp) {
|
|
||||||
m.Timestamp = a.Timestamp
|
|
||||||
}
|
|
||||||
list := m.Metadata["files"].([]interface{})
|
|
||||||
if s, ok := a.Metadata["files"]; ok {
|
|
||||||
v := reflect.ValueOf(s)
|
|
||||||
if v.Kind() != reflect.Slice || v.IsNil() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
list = append(list, v.Index(i).Interface())
|
|
||||||
}
|
|
||||||
// You must set it again at the end of the process, otherwise you've only updated the file
|
|
||||||
// slice in this one loop since it isn't passed by reference. This is just shorter than having
|
|
||||||
// to explicitly keep casting it to the slice.
|
|
||||||
m.Metadata["files"] = list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Elements returns the finalized activity models.
|
|
||||||
func (em *eventMap) Elements() (out []models.Activity) {
|
|
||||||
for _, v := range em.m {
|
|
||||||
out = append(out, *v)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// forActivity returns an event entity from our map which allows existing matches to be
|
|
||||||
// updated with additional files.
|
|
||||||
func (em *eventMap) forActivity(a models.Activity) *models.Activity {
|
|
||||||
key := mapKey{
|
|
||||||
User: a.User.String,
|
|
||||||
Server: a.Server,
|
|
||||||
IP: a.IP,
|
|
||||||
Event: a.Event,
|
|
||||||
// We group by the minute, don't care about the seconds for this logic.
|
|
||||||
Timestamp: a.Timestamp.Format("2006-01-02_15:04"),
|
|
||||||
}
|
|
||||||
if v, ok := em.m[key]; ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
// Cap the size of the events map at the defined maximum events to send to the Panel. Just
|
|
||||||
// return nil and let the caller handle it.
|
|
||||||
if len(em.m) >= em.max {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Doesn't exist in our map yet, create a copy of the activity passed into this
|
|
||||||
// function and then assign it into the map with an empty metadata value.
|
|
||||||
v := a
|
|
||||||
v.Metadata = models.ActivityMeta{
|
|
||||||
"files": make([]interface{}, 0),
|
|
||||||
}
|
|
||||||
em.m[key] = &v
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/glebarez/sqlite"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"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
|
|
||||||
var db *gorm.DB
|
|
||||||
|
|
||||||
// Initialize configures the local SQLite database for Wings and ensures that the models have
|
|
||||||
// been fully migrated.
|
|
||||||
func Initialize() error {
|
|
||||||
if !o.SwapIf(true) {
|
|
||||||
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{
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instance returns the gorm database instance that was configured when the application was
|
|
||||||
// booted.
|
|
||||||
func Instance() *gorm.DB {
|
|
||||||
if db == nil {
|
|
||||||
panic("database: attempt to access instance before initialized")
|
|
||||||
}
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pterodactyl/wings/system"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Event string
|
|
||||||
|
|
||||||
type ActivityMeta map[string]interface{}
|
|
||||||
|
|
||||||
// Activity defines an activity log event for a server entity performed by a user. This is
|
|
||||||
// used for tracking commands, power actions, and SFTP events so that they can be reconciled
|
|
||||||
// and sent back to the Panel instance to be displayed to the user.
|
|
||||||
type Activity struct {
|
|
||||||
ID int `gorm:"primaryKey;not null" json:"-"`
|
|
||||||
// User is UUID of the user that triggered this event, or an empty string if the event
|
|
||||||
// cannot be tied to a specific user, in which case we will assume it was the system
|
|
||||||
// user.
|
|
||||||
User JsonNullString `gorm:"type:uuid" json:"user"`
|
|
||||||
// Server is the UUID of the server this event is associated with.
|
|
||||||
Server string `gorm:"type:uuid;not null" json:"server"`
|
|
||||||
// Event is a string that describes what occurred, and is used by the Panel instance to
|
|
||||||
// properly associate this event in the activity logs.
|
|
||||||
Event Event `gorm:"index;not null" json:"event"`
|
|
||||||
// Metadata is either a null value, string, or a JSON blob with additional event specific
|
|
||||||
// metadata that can be provided.
|
|
||||||
Metadata ActivityMeta `gorm:"serializer:json" json:"metadata"`
|
|
||||||
// IP is the IP address that triggered this event, or an empty string if it cannot be
|
|
||||||
// determined properly. This should be the connecting user's IP address, and not the
|
|
||||||
// internal system IP.
|
|
||||||
IP string `gorm:"not null" json:"ip"`
|
|
||||||
Timestamp time.Time `gorm:"not null" json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUser sets the current user that performed the action. If an empty string is provided
|
|
||||||
// it is cast into a null value when stored.
|
|
||||||
func (a Activity) SetUser(u string) *Activity {
|
|
||||||
var ns JsonNullString
|
|
||||||
if u == "" {
|
|
||||||
if err := ns.Scan(nil); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := ns.Scan(u); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.User = ns
|
|
||||||
return &a
|
|
||||||
}
|
|
||||||
|
|
||||||
// BeforeCreate executes before we create any activity entry to ensure the IP address
|
|
||||||
// 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 a.Timestamp.IsZero() {
|
|
||||||
a.Timestamp = time.Now()
|
|
||||||
}
|
|
||||||
a.Timestamp = a.Timestamp.UTC()
|
|
||||||
if a.Metadata == nil {
|
|
||||||
a.Metadata = ActivityMeta{}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/goccy/go-json"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JsonNullString struct {
|
|
||||||
sql.NullString
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v JsonNullString) MarshalJSON() ([]byte, error) {
|
|
||||||
if v.Valid {
|
|
||||||
return json.Marshal(v.String)
|
|
||||||
} else {
|
|
||||||
return json.Marshal(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *JsonNullString) UnmarshalJSON(data []byte) error {
|
|
||||||
var s *string
|
|
||||||
if err := json.Unmarshal(data, &s); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
if s != nil {
|
|
||||||
v.String = *s
|
|
||||||
}
|
|
||||||
v.Valid = s != nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pterodactyl/wings/internal/models"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -31,7 +30,6 @@ type Client interface {
|
|||||||
SetInstallationStatus(ctx context.Context, uuid string, successful bool) error
|
SetInstallationStatus(ctx context.Context, uuid string, successful bool) error
|
||||||
SetTransferStatus(ctx context.Context, uuid string, successful bool) error
|
SetTransferStatus(ctx context.Context, uuid string, successful bool) error
|
||||||
ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error)
|
ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error)
|
||||||
SendActivityLogs(ctx context.Context, activity []models.Activity) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
@@ -130,19 +128,10 @@ func (c *client) requestOnce(ctx context.Context, method, path string, body io.R
|
|||||||
// and adds the required authentication headers to the request that is being
|
// and adds the required authentication headers to the request that is being
|
||||||
// created. Errors returned will be of the RequestError type if there was some
|
// created. Errors returned will be of the RequestError type if there was some
|
||||||
// type of response from the API that can be parsed.
|
// type of response from the API that can be parsed.
|
||||||
func (c *client) request(ctx context.Context, method, path string, body *bytes.Buffer, opts ...func(r *http.Request)) (*Response, error) {
|
func (c *client) request(ctx context.Context, method, path string, body io.Reader, opts ...func(r *http.Request)) (*Response, error) {
|
||||||
var res *Response
|
var res *Response
|
||||||
err := backoff.Retry(func() error {
|
err := backoff.Retry(func() error {
|
||||||
var b bytes.Buffer
|
r, err := c.requestOnce(ctx, method, path, body, opts...)
|
||||||
if body != nil {
|
|
||||||
// We have to create a copy of the body, otherwise attempting this request again will
|
|
||||||
// send no data if there was initially a body since the "requestOnce" method will read
|
|
||||||
// the whole buffer, thus leaving it empty at the end.
|
|
||||||
if _, err := b.Write(body.Bytes()); err != nil {
|
|
||||||
return backoff.Permanent(errors.Wrap(err, "http: failed to copy body buffer"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r, err := c.requestOnce(ctx, method, path, &b, opts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||||
return backoff.Permanent(err)
|
return backoff.Permanent(err)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package remote
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pterodactyl/wings/internal/models"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -179,16 +178,6 @@ func (c *client) SendRestorationStatus(ctx context.Context, backup string, succe
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendActivityLogs sends activity logs back to the Panel for processing.
|
|
||||||
func (c *client) SendActivityLogs(ctx context.Context, activity []models.Activity) error {
|
|
||||||
resp, err := c.Post(ctx, "/activity", d{"data": activity})
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStackIf(err)
|
|
||||||
}
|
|
||||||
_ = resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getServersPaged returns a subset of servers from the Panel API using the
|
// getServersPaged returns a subset of servers from the Panel API using the
|
||||||
// pagination query parameters.
|
// pagination query parameters.
|
||||||
func (c *client) getServersPaged(ctx context.Context, page, limit int) ([]RawServerData, Pagination, error) {
|
func (c *client) getServersPaged(ctx context.Context, page, limit int) ([]RawServerData, Pagination, error) {
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package remote
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/apex/log"
|
|
||||||
"github.com/goccy/go-json"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/parser"
|
"github.com/pterodactyl/wings/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,7 +87,6 @@ type SftpAuthRequest struct {
|
|||||||
// user for the SFTP subsystem.
|
// user for the SFTP subsystem.
|
||||||
type SftpAuthResponse struct {
|
type SftpAuthResponse struct {
|
||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
User string `json:"user"`
|
|
||||||
Permissions []string `json:"permissions"`
|
Permissions []string `json:"permissions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package router
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"github.com/pterodactyl/wings/internal/models"
|
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -601,11 +600,6 @@ func postServerUploadFiles(c *gin.Context) {
|
|||||||
if err := handleFileUpload(p, s, header); err != nil {
|
if err := handleFileUpload(p, s, header); err != nil {
|
||||||
NewServerError(err, s).Abort(c)
|
NewServerError(err, s).Abort(c)
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
s.SaveActivity(s.NewRequestActivity(token.UserUuid, c.Request.RemoteAddr), server.ActivityFileUploaded, models.ActivityMeta{
|
|
||||||
"file": header.Filename,
|
|
||||||
"directory": filepath.Clean(directory),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -623,5 +617,6 @@ func handleFileUpload(p string, s *server.Server, header *multipart.FileHeader)
|
|||||||
if err := s.Filesystem().Writefile(p, file); err != nil {
|
if err := s.Filesystem().Writefile(p, file); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ type UploadPayload struct {
|
|||||||
jwt.Payload
|
jwt.Payload
|
||||||
|
|
||||||
ServerUuid string `json:"server_uuid"`
|
ServerUuid string `json:"server_uuid"`
|
||||||
UserUuid string `json:"user_uuid"`
|
|
||||||
UniqueId string `json:"unique_id"`
|
UniqueId string `json:"unique_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/gbrlsnchs/jwt/v3"
|
"github.com/gbrlsnchs/jwt/v3"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The time at which Wings was booted. No JWT's created before this time are allowed to
|
// The time at which Wings was booted. No JWT's created before this time are allowed to
|
||||||
@@ -34,13 +35,13 @@ func DenyJTI(jti string) {
|
|||||||
denylist.Store(jti, time.Now())
|
denylist.Store(jti, time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebsocketPayload defines the JWT payload for a websocket connection. This JWT is passed along to
|
// A JWT payload for Websocket connections. This JWT is passed along to the Websocket after
|
||||||
// the websocket after it has been connected to by sending an "auth" event.
|
// it has been connected to by sending an "auth" event.
|
||||||
type WebsocketPayload struct {
|
type WebsocketPayload struct {
|
||||||
jwt.Payload
|
jwt.Payload
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
UserUUID string `json:"user_uuid"`
|
UserID json.Number `json:"user_id"`
|
||||||
ServerUUID string `json:"server_uuid"`
|
ServerUUID string `json:"server_uuid"`
|
||||||
Permissions []string `json:"permissions"`
|
Permissions []string `json:"permissions"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package websocket
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pterodactyl/wings/internal/models"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -41,7 +40,6 @@ type Handler struct {
|
|||||||
Connection *websocket.Conn `json:"-"`
|
Connection *websocket.Conn `json:"-"`
|
||||||
jwt *tokens.WebsocketPayload
|
jwt *tokens.WebsocketPayload
|
||||||
server *server.Server
|
server *server.Server
|
||||||
ra server.RequestActivity
|
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +109,6 @@ func GetHandler(s *server.Server, w http.ResponseWriter, r *http.Request) (*Hand
|
|||||||
Connection: conn,
|
Connection: conn,
|
||||||
jwt: nil,
|
jwt: nil,
|
||||||
server: s,
|
server: s,
|
||||||
ra: s.NewRequestActivity("", r.RemoteAddr),
|
|
||||||
uuid: u,
|
uuid: u,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -267,7 +264,6 @@ func (h *Handler) GetJwt() *tokens.WebsocketPayload {
|
|||||||
// setJwt sets the JWT for the websocket in a race-safe manner.
|
// setJwt sets the JWT for the websocket in a race-safe manner.
|
||||||
func (h *Handler) setJwt(token *tokens.WebsocketPayload) {
|
func (h *Handler) setJwt(token *tokens.WebsocketPayload) {
|
||||||
h.Lock()
|
h.Lock()
|
||||||
h.ra = h.ra.SetUser(token.UserUUID)
|
|
||||||
h.jwt = token
|
h.jwt = token
|
||||||
h.Unlock()
|
h.Unlock()
|
||||||
}
|
}
|
||||||
@@ -369,10 +365,6 @@ func (h *Handler) HandleInbound(ctx context.Context, m Message) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
h.server.SaveActivity(h.ra, models.Event(server.ActivityPowerPrefix+action), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case SendServerLogsEvent:
|
case SendServerLogsEvent:
|
||||||
@@ -429,13 +421,7 @@ func (h *Handler) HandleInbound(ctx context.Context, m Message) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.server.Environment.SendCommand(strings.Join(m.Args, "")); err != nil {
|
return h.server.Environment.SendCommand(strings.Join(m.Args, ""))
|
||||||
return err
|
|
||||||
}
|
|
||||||
h.server.SaveActivity(h.ra, server.ActivityConsoleCommand, models.ActivityMeta{
|
|
||||||
"command": strings.Join(m.Args, ""),
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/pterodactyl/wings/internal/database"
|
|
||||||
"github.com/pterodactyl/wings/internal/models"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ActivityPowerPrefix = "server:power."
|
|
||||||
|
|
||||||
const (
|
|
||||||
ActivityConsoleCommand = models.Event("server:console.command")
|
|
||||||
ActivitySftpWrite = models.Event("server:sftp.write")
|
|
||||||
ActivitySftpCreate = models.Event("server:sftp.create")
|
|
||||||
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
|
|
||||||
// specific metadata including the specific user and IP address associated with all subsequent
|
|
||||||
// events. The internal logged event structure can be extracted by calling RequestEvent.Event().
|
|
||||||
type RequestActivity struct {
|
|
||||||
server string
|
|
||||||
user string
|
|
||||||
ip string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event returns the underlying logged event from the RequestEvent instance and sets the
|
|
||||||
// specific event and metadata on it.
|
|
||||||
func (ra RequestActivity) Event(event models.Event, metadata models.ActivityMeta) *models.Activity {
|
|
||||||
a := models.Activity{Server: ra.server, IP: ra.ip, Event: event, Metadata: metadata}
|
|
||||||
|
|
||||||
return a.SetUser(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 {
|
|
||||||
c := ra
|
|
||||||
c.user = u
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
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,11 +16,6 @@ type EggConfiguration struct {
|
|||||||
FileDenylist []string `json:"file_denylist"`
|
FileDenylist []string `json:"file_denylist"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigurationMeta struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
@@ -29,8 +24,6 @@ type Configuration struct {
|
|||||||
// docker containers as well as in log output.
|
// docker containers as well as in log output.
|
||||||
Uuid string `json:"uuid"`
|
Uuid string `json:"uuid"`
|
||||||
|
|
||||||
Meta ConfigurationMeta `json:"meta"`
|
|
||||||
|
|
||||||
// Whether or not the server is in a suspended state. Suspended servers cannot
|
// 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.
|
// be started or modified except in certain scenarios by an admin user.
|
||||||
Suspended bool `json:"suspended"`
|
Suspended bool `json:"suspended"`
|
||||||
|
|||||||
@@ -52,24 +52,6 @@ func (m *Manager) Client() remote.Client {
|
|||||||
return m.client
|
return m.client
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the count of servers stored in the manager instance.
|
|
||||||
func (m *Manager) Len() int {
|
|
||||||
m.mu.RLock()
|
|
||||||
defer m.mu.RUnlock()
|
|
||||||
return len(m.servers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys returns all of the server UUIDs stored in the manager set.
|
|
||||||
func (m *Manager) Keys() []string {
|
|
||||||
m.mu.RLock()
|
|
||||||
defer m.mu.RUnlock()
|
|
||||||
keys := make([]string, len(m.servers))
|
|
||||||
for i, s := range m.servers {
|
|
||||||
keys[i] = s.ID()
|
|
||||||
}
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put replaces all the current values in the collection with the value that
|
// Put replaces all the current values in the collection with the value that
|
||||||
// is passed through.
|
// is passed through.
|
||||||
func (m *Manager) Put(s []*Server) {
|
func (m *Manager) Put(s []*Server) {
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
package sftp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
|
||||||
"github.com/pterodactyl/wings/internal/database"
|
|
||||||
"github.com/pterodactyl/wings/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type eventHandler struct {
|
|
||||||
ip string
|
|
||||||
user string
|
|
||||||
server string
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileAction struct {
|
|
||||||
// Entity is the targeted file or directory (depending on the event) that the action
|
|
||||||
// is being performed _against_, such as "/foo/test.txt". This will always be the full
|
|
||||||
// path to the element.
|
|
||||||
Entity string
|
|
||||||
// Target is an optional (often blank) field that only has a value in it when the event
|
|
||||||
// is specifically modifying the entity, such as a rename or move event. In that case
|
|
||||||
// the Target field will be the final value, such as "/bar/new.txt"
|
|
||||||
Target string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log parses a SFTP specific file activity event and then passes it off to be stored
|
|
||||||
// in the normal activity database.
|
|
||||||
func (eh *eventHandler) Log(e models.Event, fa FileAction) error {
|
|
||||||
metadata := map[string]interface{}{
|
|
||||||
"files": []string{fa.Entity},
|
|
||||||
}
|
|
||||||
if fa.Target != "" {
|
|
||||||
metadata["files"] = []map[string]string{
|
|
||||||
{"from": fa.Entity, "to": fa.Target},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a := models.Activity{
|
|
||||||
Server: eh.server,
|
|
||||||
Event: e,
|
|
||||||
Metadata: metadata,
|
|
||||||
IP: eh.ip,
|
|
||||||
}
|
|
||||||
|
|
||||||
if tx := database.Instance().Create(a.SetUser(eh.user)); tx.Error != nil {
|
|
||||||
return errors.WithStack(tx.Error)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustLog is a wrapper around log that will trigger a fatal error and exit the application
|
|
||||||
// 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", errors.WithStack(err)).WithField("event", e).Error("sftp: failed to log event")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,39 +28,31 @@ const (
|
|||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
|
permissions []string
|
||||||
server *server.Server
|
server *server.Server
|
||||||
fs *filesystem.Filesystem
|
fs *filesystem.Filesystem
|
||||||
events *eventHandler
|
|
||||||
permissions []string
|
|
||||||
logger *log.Entry
|
logger *log.Entry
|
||||||
ro bool
|
ro bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler returns a new connection handler for the SFTP server. This allows a given user
|
// Returns a new connection handler for the SFTP server. This allows a given user
|
||||||
// to access the underlying filesystem.
|
// to access the underlying filesystem.
|
||||||
func NewHandler(sc *ssh.ServerConn, srv *server.Server) (*Handler, error) {
|
func NewHandler(sc *ssh.ServerConn, srv *server.Server) *Handler {
|
||||||
uuid, ok := sc.Permissions.Extensions["user"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("sftp: mismatched Wings and Panel versions — Panel 1.10 is required for this version of Wings.")
|
|
||||||
}
|
|
||||||
|
|
||||||
events := eventHandler{
|
|
||||||
ip: sc.RemoteAddr().String(),
|
|
||||||
user: uuid,
|
|
||||||
server: srv.ID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Handler{
|
return &Handler{
|
||||||
permissions: strings.Split(sc.Permissions.Extensions["permissions"], ","),
|
permissions: strings.Split(sc.Permissions.Extensions["permissions"], ","),
|
||||||
server: srv,
|
server: srv,
|
||||||
fs: srv.Filesystem(),
|
fs: srv.Filesystem(),
|
||||||
events: &events,
|
|
||||||
ro: config.Get().System.Sftp.ReadOnly,
|
ro: config.Get().System.Sftp.ReadOnly,
|
||||||
logger: log.WithFields(log.Fields{"subsystem": "sftp", "user": uuid, "ip": sc.RemoteAddr()}),
|
logger: log.WithFields(log.Fields{
|
||||||
}, nil
|
"subsystem": "sftp",
|
||||||
|
"username": sc.User(),
|
||||||
|
"ip": sc.RemoteAddr(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handlers returns the sftp.Handlers for this struct.
|
// Returns the sftp.Handlers for this struct.
|
||||||
func (h *Handler) Handlers() sftp.Handlers {
|
func (h *Handler) Handlers() sftp.Handlers {
|
||||||
return sftp.Handlers{
|
return sftp.Handlers{
|
||||||
FileGet: h,
|
FileGet: h,
|
||||||
@@ -129,12 +121,7 @@ func (h *Handler) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
|||||||
}
|
}
|
||||||
// Chown may or may not have been called in the touch function, so always do
|
// Chown may or may not have been called in the touch function, so always do
|
||||||
// it at this point to avoid the file being improperly owned.
|
// it at this point to avoid the file being improperly owned.
|
||||||
_ = h.fs.Chown(request.Filepath)
|
_ = h.server.Filesystem().Chown(request.Filepath)
|
||||||
event := server.ActivitySftpWrite
|
|
||||||
if permission == PermissionFileCreate {
|
|
||||||
event = server.ActivitySftpCreate
|
|
||||||
}
|
|
||||||
h.events.MustLog(event, FileAction{Entity: request.Filepath})
|
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +172,6 @@ func (h *Handler) Filecmd(request *sftp.Request) error {
|
|||||||
l.WithField("error", err).Error("failed to rename file")
|
l.WithField("error", err).Error("failed to rename file")
|
||||||
return sftp.ErrSSHFxFailure
|
return sftp.ErrSSHFxFailure
|
||||||
}
|
}
|
||||||
h.events.MustLog(server.ActivitySftpRename, FileAction{Entity: request.Filepath, Target: request.Target})
|
|
||||||
break
|
break
|
||||||
// Handle deletion of a directory. This will properly delete all of the files and
|
// Handle deletion of a directory. This will properly delete all of the files and
|
||||||
// folders within that directory if it is not already empty (unlike a lot of SFTP
|
// folders within that directory if it is not already empty (unlike a lot of SFTP
|
||||||
@@ -194,12 +180,10 @@ func (h *Handler) Filecmd(request *sftp.Request) error {
|
|||||||
if !h.can(PermissionFileDelete) {
|
if !h.can(PermissionFileDelete) {
|
||||||
return sftp.ErrSSHFxPermissionDenied
|
return sftp.ErrSSHFxPermissionDenied
|
||||||
}
|
}
|
||||||
p := filepath.Clean(request.Filepath)
|
if err := h.fs.Delete(request.Filepath); err != nil {
|
||||||
if err := h.fs.Delete(p); err != nil {
|
|
||||||
l.WithField("error", err).Error("failed to remove directory")
|
l.WithField("error", err).Error("failed to remove directory")
|
||||||
return sftp.ErrSSHFxFailure
|
return sftp.ErrSSHFxFailure
|
||||||
}
|
}
|
||||||
h.events.MustLog(server.ActivitySftpDelete, FileAction{Entity: request.Filepath})
|
|
||||||
return sftp.ErrSSHFxOk
|
return sftp.ErrSSHFxOk
|
||||||
// Handle requests to create a new Directory.
|
// Handle requests to create a new Directory.
|
||||||
case "Mkdir":
|
case "Mkdir":
|
||||||
@@ -207,12 +191,11 @@ func (h *Handler) Filecmd(request *sftp.Request) error {
|
|||||||
return sftp.ErrSSHFxPermissionDenied
|
return sftp.ErrSSHFxPermissionDenied
|
||||||
}
|
}
|
||||||
name := strings.Split(filepath.Clean(request.Filepath), "/")
|
name := strings.Split(filepath.Clean(request.Filepath), "/")
|
||||||
p := strings.Join(name[0:len(name)-1], "/")
|
err := h.fs.CreateDirectory(name[len(name)-1], strings.Join(name[0:len(name)-1], "/"))
|
||||||
if err := h.fs.CreateDirectory(name[len(name)-1], p); err != nil {
|
if err != nil {
|
||||||
l.WithField("error", err).Error("failed to create directory")
|
l.WithField("error", err).Error("failed to create directory")
|
||||||
return sftp.ErrSSHFxFailure
|
return sftp.ErrSSHFxFailure
|
||||||
}
|
}
|
||||||
h.events.MustLog(server.ActivitySftpCreateDirectory, FileAction{Entity: request.Filepath})
|
|
||||||
break
|
break
|
||||||
// Support creating symlinks between files. The source and target must resolve within
|
// Support creating symlinks between files. The source and target must resolve within
|
||||||
// the server home directory.
|
// the server home directory.
|
||||||
@@ -245,7 +228,6 @@ func (h *Handler) Filecmd(request *sftp.Request) error {
|
|||||||
l.WithField("error", err).Error("failed to remove a file")
|
l.WithField("error", err).Error("failed to remove a file")
|
||||||
return sftp.ErrSSHFxFailure
|
return sftp.ErrSSHFxFailure
|
||||||
}
|
}
|
||||||
h.events.MustLog(server.ActivitySftpDelete, FileAction{Entity: request.Filepath})
|
|
||||||
return sftp.ErrSSHFxOk
|
return sftp.ErrSSHFxOk
|
||||||
default:
|
default:
|
||||||
return sftp.ErrSSHFxOpUnsupported
|
return sftp.ErrSSHFxOpUnsupported
|
||||||
@@ -305,6 +287,7 @@ func (h *Handler) can(permission string) bool {
|
|||||||
if h.server.IsSuspended() {
|
if h.server.IsSuspended() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range h.permissions {
|
for _, p := range h.permissions {
|
||||||
// If we match the permission specifically, or the user has been granted the "*"
|
// If we match the permission specifically, or the user has been granted the "*"
|
||||||
// permission because they're an admin, let them through.
|
// permission because they're an admin, let them through.
|
||||||
|
|||||||
@@ -91,21 +91,19 @@ func (c *SFTPServer) Run() error {
|
|||||||
if conn, _ := listener.Accept(); conn != nil {
|
if conn, _ := listener.Accept(); conn != nil {
|
||||||
go func(conn net.Conn) {
|
go func(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
if err := c.AcceptInbound(conn, conf); err != nil {
|
c.AcceptInbound(conn, conf)
|
||||||
log.WithField("error", err).Error("sftp: failed to accept inbound connection")
|
|
||||||
}
|
|
||||||
}(conn)
|
}(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptInbound handles an inbound connection to the instance and determines if we should
|
// Handles an inbound connection to the instance and determines if we should serve the
|
||||||
// serve the request or not.
|
// request or not.
|
||||||
func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) error {
|
func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) {
|
||||||
// Before beginning a handshake must be performed on the incoming net.Conn
|
// Before beginning a handshake must be performed on the incoming net.Conn
|
||||||
sconn, chans, reqs, err := ssh.NewServerConn(conn, config)
|
sconn, chans, reqs, err := ssh.NewServerConn(conn, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return
|
||||||
}
|
}
|
||||||
defer sconn.Close()
|
defer sconn.Close()
|
||||||
go ssh.DiscardRequests(reqs)
|
go ssh.DiscardRequests(reqs)
|
||||||
@@ -151,17 +149,11 @@ func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) erro
|
|||||||
|
|
||||||
// Spin up a SFTP server instance for the authenticated user's server allowing
|
// Spin up a SFTP server instance for the authenticated user's server allowing
|
||||||
// them access to the underlying filesystem.
|
// them access to the underlying filesystem.
|
||||||
handler, err := NewHandler(sconn, srv)
|
handler := sftp.NewRequestServer(channel, NewHandler(sconn, srv).Handlers())
|
||||||
if err != nil {
|
if err := handler.Serve(); err == io.EOF {
|
||||||
return errors.WithStackIf(err)
|
handler.Close()
|
||||||
}
|
|
||||||
rs := sftp.NewRequestServer(channel, handler.Handlers())
|
|
||||||
if err := rs.Serve(); err == io.EOF {
|
|
||||||
_ = rs.Close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates a new ED25519 private key that is used for host authentication when
|
// Generates a new ED25519 private key that is used for host authentication when
|
||||||
@@ -221,9 +213,8 @@ func (c *SFTPServer) makeCredentialsRequest(conn ssh.ConnMetadata, t remote.Sftp
|
|||||||
logger.WithField("server", resp.Server).Debug("credentials validated and matched to server instance")
|
logger.WithField("server", resp.Server).Debug("credentials validated and matched to server instance")
|
||||||
permissions := ssh.Permissions{
|
permissions := ssh.Permissions{
|
||||||
Extensions: map[string]string{
|
Extensions: map[string]string{
|
||||||
"ip": conn.RemoteAddr().String(),
|
|
||||||
"uuid": resp.Server,
|
"uuid": resp.Server,
|
||||||
"user": resp.User,
|
"user": conn.User(),
|
||||||
"permissions": strings.Join(resp.Permissions, ","),
|
"permissions": strings.Join(resp.Permissions, ","),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
var Version = "1.7.0"
|
var Version = "develop"
|
||||||
|
|||||||
@@ -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, "")
|
|
||||||
}
|
|
||||||
8
wings.go
8
wings.go
@@ -2,16 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pterodactyl/wings/cmd"
|
"github.com/pterodactyl/wings/cmd"
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Since we make use of the math/rand package in the code, especially for generating
|
|
||||||
// non-cryptographically secure random strings we need to seed the RNG. Just make use
|
|
||||||
// of the current time for this.
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
// Execute the main binary code.
|
|
||||||
cmd.Execute()
|
cmd.Execute()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user