2020-04-04 23:07:25 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2021-01-19 05:20:58 +00:00
|
|
|
"io"
|
2021-07-15 21:37:38 +00:00
|
|
|
"io/fs"
|
2021-01-10 01:22:39 +00:00
|
|
|
"os"
|
2021-09-01 15:54:41 +00:00
|
|
|
"time"
|
2021-01-10 01:22:39 +00:00
|
|
|
|
2020-12-16 05:56:53 +00:00
|
|
|
"emperror.dev/errors"
|
2020-06-13 17:40:26 +00:00
|
|
|
"github.com/apex/log"
|
2021-01-31 02:43:35 +00:00
|
|
|
"github.com/docker/docker/client"
|
2021-07-15 21:37:38 +00:00
|
|
|
|
2021-01-31 02:43:35 +00:00
|
|
|
"github.com/pterodactyl/wings/environment"
|
2021-02-02 05:28:46 +00:00
|
|
|
"github.com/pterodactyl/wings/remote"
|
2020-04-14 05:01:07 +00:00
|
|
|
"github.com/pterodactyl/wings/server/backup"
|
2020-04-04 23:07:25 +00:00
|
|
|
)
|
|
|
|
|
2020-04-17 20:46:36 +00:00
|
|
|
// Notifies the panel of a backup's state and returns an error if one is encountered
|
|
|
|
// while performing this action.
|
|
|
|
func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
|
2021-02-02 05:28:46 +00:00
|
|
|
if err := s.client.SetBackupStatus(s.Context(), uuid, ad.ToRequest(successful)); err != nil {
|
|
|
|
if !remote.IsRequestError(err) {
|
2020-06-13 17:40:26 +00:00
|
|
|
s.Log().WithFields(log.Fields{
|
|
|
|
"backup": uuid,
|
|
|
|
"error": err,
|
2020-08-28 02:35:57 +00:00
|
|
|
}).Error("failed to notify panel of backup status due to wings error")
|
2020-04-17 20:46:36 +00:00
|
|
|
|
2020-11-28 23:57:10 +00:00
|
|
|
return err
|
2020-04-17 20:46:36 +00:00
|
|
|
}
|
|
|
|
|
2020-10-31 17:04:20 +00:00
|
|
|
return errors.New(err.Error())
|
2020-04-17 20:46:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-26 23:43:18 +00:00
|
|
|
// Get all of the ignored files for a server based on its .pteroignore file in the root.
|
2020-12-25 19:52:57 +00:00
|
|
|
func (s *Server) getServerwideIgnoredFiles() (string, error) {
|
2020-12-25 22:00:01 +00:00
|
|
|
f, st, err := s.Filesystem().File(".pteroignore")
|
2020-12-25 21:40:53 +00:00
|
|
|
if err != nil {
|
2020-12-25 22:00:01 +00:00
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
return "", nil
|
2020-12-25 21:40:53 +00:00
|
|
|
}
|
2020-12-25 22:00:01 +00:00
|
|
|
return "", err
|
2020-12-25 22:01:25 +00:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
if st.Mode()&os.ModeSymlink != 0 || st.Size() > 32*1024 {
|
2020-12-25 22:00:01 +00:00
|
|
|
// Don't read a symlinked ignore file, or a file larger than 32KiB in size.
|
2020-12-25 19:52:57 +00:00
|
|
|
return "", nil
|
2020-04-26 23:43:18 +00:00
|
|
|
}
|
2021-11-15 17:24:52 +00:00
|
|
|
b, err := io.ReadAll(f)
|
2020-12-25 19:52:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2020-04-20 02:52:19 +00:00
|
|
|
}
|
2020-12-25 19:52:57 +00:00
|
|
|
return string(b), nil
|
2020-04-26 23:43:18 +00:00
|
|
|
}
|
|
|
|
|
2021-01-19 05:20:58 +00:00
|
|
|
// Backup performs a server backup and then emits the event over the server
|
|
|
|
// websocket. We let the actual backup system handle notifying the panel of the
|
|
|
|
// status, but that won't emit a websocket event.
|
2020-05-02 22:02:02 +00:00
|
|
|
func (s *Server) Backup(b backup.BackupInterface) error {
|
2020-12-25 19:52:57 +00:00
|
|
|
ignored := b.Ignored()
|
|
|
|
if b.Ignored() == "" {
|
|
|
|
if i, err := s.getServerwideIgnoredFiles(); err != nil {
|
2021-08-02 21:07:00 +00:00
|
|
|
log.WithField("server", s.ID()).WithField("error", err).Warn("failed to get server-wide ignored files")
|
2020-12-25 19:52:57 +00:00
|
|
|
} else {
|
|
|
|
ignored = i
|
|
|
|
}
|
2020-04-19 06:26:23 +00:00
|
|
|
}
|
|
|
|
|
2021-05-02 19:28:36 +00:00
|
|
|
ad, err := b.Generate(s.Context(), s.Filesystem().Path(), ignored)
|
2020-05-10 02:24:30 +00:00
|
|
|
if err != nil {
|
2020-12-27 20:55:58 +00:00
|
|
|
if err := s.notifyPanelOfBackup(b.Identifier(), &backup.ArchiveDetails{}, false); err != nil {
|
2020-06-13 17:40:26 +00:00
|
|
|
s.Log().WithFields(log.Fields{
|
|
|
|
"backup": b.Identifier(),
|
2020-12-27 20:55:58 +00:00
|
|
|
"error": err,
|
2020-06-13 17:40:26 +00:00
|
|
|
}).Warn("failed to notify panel of failed backup state")
|
2020-11-28 23:57:10 +00:00
|
|
|
} else {
|
2020-12-27 20:55:58 +00:00
|
|
|
s.Log().WithField("backup", b.Identifier()).Info("notified panel of failed backup state")
|
2020-04-04 23:07:25 +00:00
|
|
|
}
|
|
|
|
|
2022-01-18 03:23:29 +00:00
|
|
|
s.Events().Publish(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
|
2020-08-21 04:28:06 +00:00
|
|
|
"uuid": b.Identifier(),
|
|
|
|
"is_successful": false,
|
2020-08-24 01:06:17 +00:00
|
|
|
"checksum": "",
|
|
|
|
"checksum_type": "sha1",
|
2020-08-21 04:28:06 +00:00
|
|
|
"file_size": 0,
|
|
|
|
})
|
|
|
|
|
2020-12-27 20:55:58 +00:00
|
|
|
return errors.WrapIf(err, "backup: error while generating server backup")
|
2020-04-04 23:07:25 +00:00
|
|
|
}
|
|
|
|
|
2020-04-14 05:01:07 +00:00
|
|
|
// Try to notify the panel about the status of this backup. If for some reason this request
|
|
|
|
// fails, delete the archive from the daemon and return that error up the chain to the caller.
|
2020-04-17 20:46:36 +00:00
|
|
|
if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil {
|
2020-12-25 19:52:57 +00:00
|
|
|
_ = b.Remove()
|
|
|
|
|
2020-11-28 23:57:10 +00:00
|
|
|
s.Log().WithField("error", notifyError).Info("failed to notify panel of successful backup state")
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
s.Log().WithField("backup", b.Identifier()).Info("notified panel of successful backup state")
|
2020-04-04 23:07:25 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 04:03:50 +00:00
|
|
|
// Emit an event over the socket so we can update the backup in realtime on
|
|
|
|
// the frontend for the server.
|
2022-01-18 03:23:29 +00:00
|
|
|
s.Events().Publish(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
|
2020-08-21 04:28:06 +00:00
|
|
|
"uuid": b.Identifier(),
|
|
|
|
"is_successful": true,
|
2020-08-24 01:06:17 +00:00
|
|
|
"checksum": ad.Checksum,
|
|
|
|
"checksum_type": "sha1",
|
2020-08-21 04:28:06 +00:00
|
|
|
"file_size": ad.Size,
|
2020-04-07 04:03:50 +00:00
|
|
|
})
|
|
|
|
|
2020-04-04 23:07:25 +00:00
|
|
|
return nil
|
2020-06-13 17:40:26 +00:00
|
|
|
}
|
2021-01-19 05:20:58 +00:00
|
|
|
|
|
|
|
// RestoreBackup calls the Restore function on the provided backup. Once this
|
|
|
|
// restoration is completed an event is emitted to the websocket to notify the
|
|
|
|
// Panel that is has been completed.
|
|
|
|
//
|
|
|
|
// In addition to the websocket event an API call is triggered to notify the
|
|
|
|
// Panel of the new state.
|
|
|
|
func (s *Server) RestoreBackup(b backup.BackupInterface, reader io.ReadCloser) (err error) {
|
|
|
|
s.Config().SetSuspended(true)
|
|
|
|
// Local backups will not pass a reader through to this function, so check first
|
|
|
|
// to make sure it is a valid reader before trying to close it.
|
|
|
|
defer func() {
|
|
|
|
s.Config().SetSuspended(false)
|
|
|
|
if reader != nil {
|
|
|
|
reader.Close()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
// Send an API call to the Panel as soon as this function is done running so that
|
|
|
|
// the Panel is informed of the restoration status of this backup.
|
|
|
|
defer func() {
|
2021-02-02 05:28:46 +00:00
|
|
|
if rerr := s.client.SendRestorationStatus(s.Context(), b.Identifier(), err == nil); rerr != nil {
|
2021-01-21 04:03:14 +00:00
|
|
|
s.Log().WithField("error", rerr).WithField("backup", b.Identifier()).Error("failed to notify Panel of backup restoration status")
|
2021-01-19 05:20:58 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-01-31 02:43:35 +00:00
|
|
|
// Don't try to restore the server until we have completely stopped the running
|
|
|
|
// instance, otherwise you'll likely hit all types of write errors due to the
|
|
|
|
// server being suspended.
|
|
|
|
if s.Environment.State() != environment.ProcessOfflineState {
|
2022-02-01 00:09:08 +00:00
|
|
|
if err = s.Environment.WaitForStop(s.Context(), time.Minute*2, false); err != nil {
|
2021-01-31 02:43:35 +00:00
|
|
|
if !client.IsErrNotFound(err) {
|
|
|
|
return errors.WrapIf(err, "server/backup: restore: failed to wait for container stop")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-19 05:20:58 +00:00
|
|
|
// Attempt to restore the backup to the server by running through each entry
|
|
|
|
// in the file one at a time and writing them to the disk.
|
2021-01-31 02:43:35 +00:00
|
|
|
s.Log().Debug("starting file writing process for backup restoration")
|
2021-09-01 15:54:41 +00:00
|
|
|
err = b.Restore(s.Context(), reader, func(file string, r io.Reader, mode fs.FileMode, atime, mtime time.Time) error {
|
2021-01-31 02:49:07 +00:00
|
|
|
s.Events().Publish(DaemonMessageEvent, "(restoring): "+file)
|
2021-07-15 21:37:38 +00:00
|
|
|
if err := s.Filesystem().Writefile(file, r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-09-01 15:54:41 +00:00
|
|
|
if err := s.Filesystem().Chmod(file, mode); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return s.Filesystem().Chtimes(file, atime, mtime)
|
2021-01-19 05:20:58 +00:00
|
|
|
})
|
|
|
|
|
2021-01-19 05:22:37 +00:00
|
|
|
return errors.WithStackIf(err)
|
2021-01-19 05:20:58 +00:00
|
|
|
}
|