From 9cf74328358f01660ba205ba4e5def00d68a2541 Mon Sep 17 00:00:00 2001
From: Jamsheed Mistri <jmistri7@gmail.com>
Date: Sun, 2 Dec 2018 23:39:40 -0800
Subject: [PATCH] Adding bulk reinstall command

---
 .../Server/BulkReinstallActionCommand.php     | 113 ++++++++++++++++++
 .../Repository/ServerRepositoryInterface.php  |   9 ++
 .../Eloquent/ServerRepository.php             |  20 ++++
 resources/lang/en/command/messages.php        |   4 +
 4 files changed, 146 insertions(+)
 create mode 100644 app/Console/Commands/Server/BulkReinstallActionCommand.php

diff --git a/app/Console/Commands/Server/BulkReinstallActionCommand.php b/app/Console/Commands/Server/BulkReinstallActionCommand.php
new file mode 100644
index 00000000..86c40a9e
--- /dev/null
+++ b/app/Console/Commands/Server/BulkReinstallActionCommand.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * This software is licensed under the terms of the MIT license.
+ * https://opensource.org/licenses/MIT
+ */
+
+namespace Pterodactyl\Console\Commands\Server;
+
+use Webmozart\Assert\Assert;
+use Illuminate\Console\Command;
+use GuzzleHttp\Exception\RequestException;
+use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
+use Pterodactyl\Services\Servers\ServerConfigurationStructureService;
+use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
+
+class BulkReinstallActionCommand extends Command
+{
+    /**
+     * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService
+     */
+    protected $configurationStructureService;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
+     */
+    protected $daemonRepository;
+
+    /**
+     * @var string
+     */
+    protected $description = 'Reinstall a single server, all servers on a node, or all servers on the panel.';
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
+     */
+    protected $repository;
+
+    /**
+     * @var string
+     */
+    protected $signature = 'p:server:reinstall
+                            {server? : The ID of the server to reinstall.}
+                            {--node= : ID of the node to reinstall all servers on. Ignored if server is passed.}';
+
+    /**
+     * BulkReinstallActionCommand constructor.
+     *
+     * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository
+     * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService  $configurationStructureService
+     * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface        $repository
+     */
+    public function __construct(
+        DaemonServerRepositoryInterface $daemonRepository,
+        ServerConfigurationStructureService $configurationStructureService,
+        ServerRepositoryInterface $repository
+    ) {
+        parent::__construct();
+
+        $this->configurationStructureService = $configurationStructureService;
+        $this->daemonRepository = $daemonRepository;
+        $this->repository = $repository;
+    }
+
+    /**
+     * Handle command execution.
+     */
+    public function handle()
+    {
+    	$servers = $this->getServersToProcess();
+
+        if (! $this->confirm(trans('command/messages.server.reinstall.confirm'))) {
+            return;
+        }
+
+        $bar = $this->output->createProgressBar(count($servers));
+
+        $servers->each(function ($server) use ($bar) {
+            $bar->clear();
+
+            try {
+                $this->daemonRepository->setServer($server)->reinstall();
+            } catch (RequestException $exception) {
+                $this->output->error(trans('command/messages.server.reinstall.failed', [
+                    'name' => $server->name,
+                    'id' => $server->id,
+                    'node' => $server->node->name,
+                    'message' => $exception->getMessage(),
+                ]));
+            }
+
+            $bar->advance();
+            $bar->display();
+        });
+
+        $this->line('');
+    }
+
+    /**
+     * Return the servers to be reinstalled.
+     *
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    private function getServersToProcess()
+    {
+        Assert::nullOrIntegerish($this->argument('server'), 'Value passed in server argument must be null or an integer, received %s.');
+        Assert::nullOrIntegerish($this->option('node'), 'Value passed in node option must be null or integer, received %s.');
+
+        return $this->repository->getDataForReinstall($this->argument('server'), $this->option('node'));
+    }
+}
diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php
index f00d29d8..f3ef7daa 100644
--- a/app/Contracts/Repository/ServerRepositoryInterface.php
+++ b/app/Contracts/Repository/ServerRepositoryInterface.php
@@ -36,6 +36,15 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter
      */
     public function getDataForRebuild(int $server = null, int $node = null): Collection;
 
+    /**
+     * Return a collection of servers with their associated data for reinstall operations.
+     *
+     * @param int|null $server
+     * @param int|null $node
+     * @return \Illuminate\Support\Collection
+     */
+    public function getDataForReinstall(int $server = null, int $node = null): Collection;
+
     /**
      * Return a server model and all variables associated with the server.
      *
diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php
index 3c0248be..ccb69af5 100644
--- a/app/Repositories/Eloquent/ServerRepository.php
+++ b/app/Repositories/Eloquent/ServerRepository.php
@@ -75,6 +75,26 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
         return $instance->get($this->getColumns());
     }
 
+    /**
+     * Return a collection of servers with their associated data for reinstall operations.
+     *
+     * @param int|null $server
+     * @param int|null $node
+     * @return \Illuminate\Support\Collection
+     */
+    public function getDataForReinstall(int $server = null, int $node = null): Collection
+    {
+        $instance = $this->getBuilder()->with(['allocation', 'allocations', 'pack', 'egg', 'node']);
+
+        if (! is_null($server) && is_null($node)) {
+            $instance = $instance->where('id', '=', $server);
+        } elseif (is_null($server) && ! is_null($node)) {
+            $instance = $instance->where('node_id', '=', $node);
+        }
+
+        return $instance->get($this->getColumns());
+    }
+
     /**
      * Return a server model and all variables associated with the server.
      *
diff --git a/resources/lang/en/command/messages.php b/resources/lang/en/command/messages.php
index 8741fc42..cd10ff49 100644
--- a/resources/lang/en/command/messages.php
+++ b/resources/lang/en/command/messages.php
@@ -42,6 +42,10 @@ return [
     ],
     'server' => [
         'rebuild_failed' => 'Rebuild request for ":name" (#:id) on node ":node" failed with error: :message',
+        'reinstall' => [
+            'failed' => 'Reinstall request for ":name" (#:id) on node ":node" failed with error: :message',
+            'confirm' => 'You are about to reinstall against a group of servers. Do you wish to continue?'
+        ],
         'power' => [
             'confirm' => 'You are about to perform a :action against :count servers. Do you wish to continue?',
             'action_failed' => 'Power action request for ":name" (#:id) on node ":node" failed with error: :message',