package cmd import ( "context" "os" "os/exec" "strings" "emperror.dev/errors" "github.com/apex/log" "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/internal/vhd" "github.com/pterodactyl/wings/loggers/cli" "github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server/filesystem" "github.com/spf13/cobra" ) type MigrateVHDCommand struct { manager *server.Manager } func newMigrateVHDCommand() *cobra.Command { return &cobra.Command{ Use: "migrate-vhd", Short: "migrates existing data from a directory tree into virtual hard-disks", PreRun: func(cmd *cobra.Command, args []string) { log.SetLevel(log.DebugLevel) log.SetHandler(cli.Default) }, Run: func(cmd *cobra.Command, args []string) { client := remote.NewFromConfig(config.Get()) manager, err := server.NewManager(cmd.Context(), client, true) if err != nil { log.WithField("error", err).Fatal("failed to create new server manager") } c := &MigrateVHDCommand{ manager: manager, } if err := c.Run(cmd.Context()); err != nil { log.WithField("error", err).Fatal("failed to execute command") } }, } } // Run executes the migration command. func (m *MigrateVHDCommand) Run(ctx context.Context) error { if !config.Get().System.UseVirtualDisks { return errors.New("cannot migrate to vhd: configuration file \"system.use_virtual_disks\" value is set to \"false\"") } for _, s := range m.manager.All() { s.Log().Debug("starting migration of server contents to virtual disk...") v := vhd.New(s.DiskSpace(), filesystem.VirtualDiskPath(s.Id()), s.Filesystem().Path()) if err := v.Allocate(ctx); err != nil { return errors.WithStackIf(err) } if err := v.MakeFilesystem(ctx); err != nil { // If the filesystem already exists no worries, just move on with our // day here. if !errors.Is(err, vhd.ErrFilesystemExists) { return errors.WithStack(err) } } bak := strings.TrimSuffix(s.Filesystem().Path(), "/") + "_bak" // Create a backup directory of the server files if one does not already exist // at that location. If one does exists we'll just assume it is good to go and // rely on it to provide the files we'll need. if _, err := os.Lstat(bak); os.IsNotExist(err) { if err := os.Rename(s.Filesystem().Path(), bak); err != nil { return errors.Wrap(err, "failed to rename existing data directory for backup") } } else if err != nil { return errors.WithStack(err) } if err := os.RemoveAll(s.Filesystem().Path()); err != nil && !os.IsNotExist(err) { return errors.Wrap(err, "failed to remove base server files path") } // Attempt to mount the disk at the expected path now that we've created // a backup of the server files. if err := v.Mount(ctx); err != nil && !errors.Is(err, vhd.ErrFilesystemMounted) { return errors.WithStackIf(err) } // Copy over the files from the backup for this server. cmd := exec.CommandContext(ctx, "cp", "-a", bak + "/.", s.Filesystem().Path()) if err := cmd.Run(); err != nil { return errors.WithStack(err) } s.Log().Info("finished migration to virtual disk...") } return nil }