|
4 | 4 |
|
5 | 5 | use Illuminate\Http\Request; |
6 | 6 | use Pterodactyl\Models\Server; |
| 7 | +use Pterodactyl\Models\AuditLog; |
7 | 8 | use Illuminate\Http\JsonResponse; |
| 9 | +use Illuminate\Database\Query\Builder; |
| 10 | +use Illuminate\Database\Query\JoinClause; |
8 | 11 | use Pterodactyl\Http\Controllers\Controller; |
9 | 12 | use Pterodactyl\Repositories\Eloquent\NodeRepository; |
10 | 13 | use Pterodactyl\Services\Eggs\EggConfigurationService; |
@@ -83,4 +86,67 @@ public function list(Request $request) |
83 | 86 |
|
84 | 87 | return new ServerConfigurationCollection($servers); |
85 | 88 | } |
| 89 | + |
| 90 | + /** |
| 91 | + * Resets the state of all servers on the node to be normal. This is triggered |
| 92 | + * when Wings restarts and is useful for ensuring that any servers on the node |
| 93 | + * do not get incorrectly stuck in installing/restoring from backup states since |
| 94 | + * a Wings reboot would completely stop those processes. |
| 95 | + * |
| 96 | + * @param \Illuminate\Http\Request $request |
| 97 | + * @return \Illuminate\Http\JsonResponse |
| 98 | + * |
| 99 | + * @throws \Throwable |
| 100 | + */ |
| 101 | + public function resetState(Request $request) |
| 102 | + { |
| 103 | + $node = $request->attributes->get('node'); |
| 104 | + |
| 105 | + // Get all of the servers that are currently marked as restoring from a backup |
| 106 | + // on this node that do not have a failed backup tracked in the audit logs table |
| 107 | + // as well. |
| 108 | + // |
| 109 | + // For each of those servers we'll track a new audit log entry to mark them as |
| 110 | + // failed and then update them all to be in a valid state. |
| 111 | + /** @var \Pterodactyl\Models\Server[] $servers */ |
| 112 | + $servers = Server::query() |
| 113 | + ->select('servers.*') |
| 114 | + ->selectRaw('started.metadata->>"$.backup_uuid" as backup_uuid') |
| 115 | + ->leftJoinSub(function (Builder $builder) { |
| 116 | + $builder->select('*')->from('audit_logs') |
| 117 | + ->where('action', AuditLog::SERVER__BACKUP_RESTORE_STARTED) |
| 118 | + ->orderByDesc('created_at') |
| 119 | + ->limit(1); |
| 120 | + }, 'started', 'started.server_id', '=', 'servers.id') |
| 121 | + ->leftJoin('audit_logs as completed', function (JoinClause $clause) { |
| 122 | + $clause->whereColumn('completed.created_at', '>', 'started.created_at') |
| 123 | + ->whereIn('completed.action', [ |
| 124 | + AuditLog::SERVER__BACKUP_RESTORE_COMPLETED, |
| 125 | + AuditLog::SERVER__BACKUP_RESTORE_FAILED, |
| 126 | + ]); |
| 127 | + }) |
| 128 | + ->whereNotNull('started.id') |
| 129 | + ->whereNull('completed.id') |
| 130 | + ->where('servers.node_id', $node->id) |
| 131 | + ->where('servers.status', Server::STATUS_RESTORING_BACKUP) |
| 132 | + ->get(); |
| 133 | + |
| 134 | + foreach ($servers as $server) { |
| 135 | + // Just create a new audit entry for this event and update the server state |
| 136 | + // so that power actions, file management, and backups can resume as normal. |
| 137 | + $server->audit(AuditLog::SERVER__BACKUP_RESTORE_FAILED, function (AuditLog $audit, Server $server) { |
| 138 | + $audit->is_system = true; |
| 139 | + $audit->metadata = ['backup_uuid' => $server->getAttribute('backup_uuid')]; |
| 140 | + $server->update(['status' => null]); |
| 141 | + }); |
| 142 | + } |
| 143 | + |
| 144 | + // Update any server marked as installing or restoring as being in a normal state |
| 145 | + // at this point in the process. |
| 146 | + Server::query()->where('node_id', $node->id) |
| 147 | + ->whereIn('status', [Server::STATUS_INSTALLING, Server::STATUS_RESTORING_BACKUP]) |
| 148 | + ->update(['status' => null]); |
| 149 | + |
| 150 | + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); |
| 151 | + } |
86 | 152 | } |
0 commit comments