Skip to content

Commit a5a1ea3

Browse files
committed
Add ability to change service implementation for a server.
1 parent e095841 commit a5a1ea3

File tree

5 files changed

+378
-80
lines changed

5 files changed

+378
-80
lines changed

app/Http/Controllers/Admin/ServersController.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,24 @@ public function viewStartup(Request $request, $id)
198198
return $item;
199199
});
200200

201-
return view('admin.servers.view.startup', ['server' => $server]);
201+
$services = Models\Service::with('options.packs', 'options.variables')->get();
202+
Javascript::put([
203+
'services' => $services->map(function ($item) {
204+
return array_merge($item->toArray(), [
205+
'options' => $item->options->keyBy('id')->toArray(),
206+
]);
207+
})->keyBy('id'),
208+
'server_variables' => $server->variables->mapWithKeys(function ($item) {
209+
return ['env_' . $item->variable_id => [
210+
'value' => $item->variable_value,
211+
]];
212+
})->toArray(),
213+
]);
214+
215+
return view('admin.servers.view.startup', [
216+
'server' => $server,
217+
'services' => $services,
218+
]);
202219
}
203220

204221
/**
@@ -479,9 +496,13 @@ public function saveStartup(Request $request, $id)
479496
$repo = new ServerRepository;
480497

481498
try {
482-
$repo->updateStartup($id, $request->except('_token'), true);
499+
if ($repo->updateStartup($id, $request->except('_token'), true)) {
500+
Alert::success('Service configuration successfully modfied for this server, reinstalling now.')->flash();
483501

484-
Alert::success('Startup variables were successfully modified and assigned for this server.')->flash();
502+
return redirect()->route('admin.servers.view', $id);
503+
} else {
504+
Alert::success('Startup variables were successfully modified and assigned for this server.')->flash();
505+
}
485506
} catch (DisplayValidationException $ex) {
486507
return redirect()->route('admin.servers.view.startup', $id)->withErrors(json_decode($ex->getMessage()));
487508
} catch (DisplayException $ex) {

app/Models/Server.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class Server extends Model
7272
*/
7373
protected $casts = [
7474
'node_id' => 'integer',
75+
'skip_scripts' => 'boolean',
7576
'suspended' => 'integer',
7677
'owner_id' => 'integer',
7778
'memory' => 'integer',

app/Repositories/ServerRepository.php

Lines changed: 168 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public function create(array $data)
103103
'startup' => 'string',
104104
'auto_deploy' => 'sometimes|required|accepted',
105105
'custom_id' => 'sometimes|required|numeric|unique:servers,id',
106-
'skip_scripting' => 'sometimes|required|boolean',
106+
'skip_scripts' => 'sometimes|required|boolean',
107107
]);
108108

109109
$validator->sometimes('node_id', 'required|numeric|min:1|exists:nodes,id', function ($input) {
@@ -250,14 +250,15 @@ public function create(array $data)
250250
'node_id' => $node->id,
251251
'name' => $data['name'],
252252
'description' => $data['description'],
253-
'suspended' => 0,
253+
'skip_scripts' => isset($data['skip_scripts']),
254+
'suspended' => false,
254255
'owner_id' => $user->id,
255256
'memory' => $data['memory'],
256257
'swap' => $data['swap'],
257258
'disk' => $data['disk'],
258259
'io' => $data['io'],
259260
'cpu' => $data['cpu'],
260-
'oom_disabled' => (isset($data['oom_disabled'])) ? true : false,
261+
'oom_disabled' => isset($data['oom_disabled']),
261262
'allocation_id' => $allocation->id,
262263
'service_id' => $data['service_id'],
263264
'option_id' => $data['option_id'],
@@ -327,7 +328,7 @@ public function create(array $data)
327328
'type' => $service->folder,
328329
'option' => $option->tag,
329330
'pack' => (isset($pack)) ? $pack->uuid : null,
330-
'skip_scripting' => isset($data['skip_scripting']),
331+
'skip_scripts' => $server->skip_scripts,
331332
],
332333
'keys' => [
333334
(string) $server->daemonSecret => $this->daemonPermissions,
@@ -621,13 +622,93 @@ public function changeBuild($id, array $data)
621622
}
622623
}
623624

625+
/**
626+
* Update the service configuration for a server.
627+
*
628+
* @param int $id
629+
* @param array $data
630+
* @return void
631+
*
632+
* @throws \GuzzleHttp\Exception\RequestException
633+
* @throws \Pterodactyl\Exceptions\DisplayException
634+
* @throws \Pterodactyl\Exceptions\DisplayValidationException
635+
*/
636+
protected function changeService($id, array $data)
637+
{
638+
639+
}
640+
641+
protected function processVariables(Models\Server $server, $data, $admin = false)
642+
{
643+
$server->load('option.variables');
644+
645+
if ($admin) {
646+
$server->startup = $data['startup'];
647+
$server->save();
648+
}
649+
650+
if ($server->option->variables) {
651+
foreach ($server->option->variables as &$variable) {
652+
$set = isset($data['env_' . $variable->id]);
653+
654+
// If user is not an admin and are trying to edit a non-editable field
655+
// or an invisible field just silently skip the variable.
656+
if (! $admin && (! $variable->user_editable || ! $variable->user_viewable)) {
657+
continue;
658+
}
659+
660+
// Perform Field Validation
661+
$validator = Validator::make([
662+
'variable_value' => ($set) ? $data['env_' . $variable->id] : null,
663+
], [
664+
'variable_value' => $variable->rules,
665+
]);
666+
667+
if ($validator->fails()) {
668+
throw new DisplayValidationException(json_encode(
669+
collect([
670+
'notice' => ['There was a validation error with the `' . $variable->name . '` variable.'],
671+
])->merge($validator->errors()->toArray())
672+
));
673+
}
674+
675+
$svar = Models\ServerVariable::firstOrNew([
676+
'server_id' => $server->id,
677+
'variable_id' => $variable->id,
678+
]);
679+
680+
// Set the value; if one was not passed set it to the default value
681+
if ($set) {
682+
$svar->variable_value = $data['env_' . $variable->id];
683+
684+
// Not passed, check if this record exists if so keep value, otherwise set default
685+
} else {
686+
$svar->variable_value = ($svar->exists) ? $svar->variable_value : $variable->default_value;
687+
}
688+
689+
$svar->save();
690+
}
691+
}
692+
693+
// Reload Variables
694+
$server->load('variables');
695+
return $server->option->variables->map(function ($item, $key) use ($server) {
696+
$display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first();
697+
698+
return [
699+
'variable' => $item->env_variable,
700+
'value' => (! is_null($display)) ? $display : $item->default_value,
701+
];
702+
});
703+
}
704+
624705
/**
625706
* Update the startup details for a server.
626707
*
627708
* @param int $id
628709
* @param array $data
629710
* @param bool $admin
630-
* @return void
711+
* @return bool
631712
*
632713
* @throws \GuzzleHttp\Exception\RequestException
633714
* @throws \Pterodactyl\Exceptions\DisplayException
@@ -636,78 +717,110 @@ public function changeBuild($id, array $data)
636717
public function updateStartup($id, array $data, $admin = false)
637718
{
638719
$server = Models\Server::with('variables', 'option.variables')->findOrFail($id);
720+
$hasServiceChanges = false;
721+
722+
if ($admin) {
723+
// User is an admin, lots of things to do here.
724+
$validator = Validator::make($data, [
725+
'startup' => 'required|string',
726+
'skip_scripts' => 'sometimes|required|boolean',
727+
'service_id' => 'required|numeric|min:1|exists:services,id',
728+
'option_id' => 'required|numeric|min:1|exists:service_options,id',
729+
'pack_id' => 'sometimes|nullable|numeric|min:0',
730+
]);
639731

640-
DB::transaction(function () use ($admin, $data, $server) {
641-
if (isset($data['startup']) && $admin) {
642-
$server->startup = $data['startup'];
643-
$server->save();
732+
if ((int) $data['pack_id'] < 1) {
733+
$data['pack_id'] = null;
644734
}
645735

646-
if ($server->option->variables) {
647-
foreach ($server->option->variables as &$variable) {
648-
$set = isset($data['env_' . $variable->id]);
649-
650-
// If user is not an admin and are trying to edit a non-editable field
651-
// or an invisible field just silently skip the variable.
652-
if (! $admin && (! $variable->user_editable || ! $variable->user_viewable)) {
653-
continue;
654-
}
736+
if ($validator->fails()) {
737+
throw new DisplayValidationException(json_encode($validator->errors()));
738+
}
655739

656-
// Perform Field Validation
657-
$validator = Validator::make([
658-
'variable_value' => ($set) ? $data['env_' . $variable->id] : null,
659-
], [
660-
'variable_value' => $variable->rules,
661-
]);
740+
if (
741+
$server->service_id != $data['service_id'] ||
742+
$server->option_id != $data['option_id'] ||
743+
$server->pack_id != $data['pack_id']
744+
) {
745+
$hasServiceChanges = true;
746+
}
747+
}
662748

663-
if ($validator->fails()) {
664-
throw new DisplayValidationException(json_encode(
665-
collect([
666-
'notice' => ['There was a validation error with the `' . $variable->name . '` variable.'],
667-
])->merge($validator->errors()->toArray())
668-
));
669-
}
749+
// If user isn't an administrator, this function is being access from the front-end
750+
// Just try to update specific variables.
751+
if (! $admin || ! $hasServiceChanges) {
752+
return DB::transaction(function () use ($admin, $data, $server) {
753+
$environment = $this->processVariables($server, $data, $admin);
670754

671-
$svar = Models\ServerVariable::firstOrNew([
672-
'server_id' => $server->id,
673-
'variable_id' => $variable->id,
674-
]);
755+
$server->node->guzzleClient([
756+
'X-Access-Server' => $server->uuid,
757+
'X-Access-Token' => $server->node->daemonSecret,
758+
])->request('PATCH', '/server', [
759+
'json' => [
760+
'build' => [
761+
'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup])->toArray(),
762+
],
763+
],
764+
]);
675765

676-
// Set the value; if one was not passed set it to the default value
677-
if ($set) {
678-
$svar->variable_value = $data['env_' . $variable->id];
766+
return false;
767+
});
768+
}
679769

680-
// Not passed, check if this record exists if so keep value, otherwise set default
681-
} else {
682-
$svar->variable_value = ($svar->exists) ? $svar->variable_value : $variable->default_value;
683-
}
770+
// Validate those Service Option Variables
771+
// We know the service and option exists because of the validation.
772+
// We need to verify that the option exists for the service, and then check for
773+
// any required variable fields. (fields are labeled env_<env_variable>)
774+
$option = Models\ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first();
775+
if (! $option) {
776+
throw new DisplayException('The requested service option does not exist for the specified service.');
777+
}
684778

685-
$svar->save();
686-
}
779+
// Validate the Pack
780+
if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) {
781+
$data['pack_id'] = null;
782+
} else {
783+
$pack = Models\Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first();
784+
if (! $pack) {
785+
throw new DisplayException('The requested service pack does not seem to exist for this combination.');
687786
}
787+
}
688788

689-
// Reload Variables
690-
$server->load('variables');
691-
$environment = $server->option->variables->map(function ($item, $key) use ($server) {
692-
$display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first();
789+
return DB::transaction(function () use ($admin, $data, $server) {
790+
$server->installed = 0;
791+
$server->service_id = $data['service_id'];
792+
$server->option_id = $data['option_id'];
793+
$server->pack_id = $data['pack_id'];
794+
$server->skip_scripts = isset($data['skip_scripts']);
795+
$server->save();
693796

694-
return [
695-
'variable' => $item->env_variable,
696-
'value' => (! is_null($display)) ? $display : $item->default_value,
697-
];
698-
});
797+
$server->variables->each->delete();
798+
799+
$server->load('service', 'pack');
800+
801+
// Send New Environment
802+
$environment = $this->processVariables($server, $data, $admin);
699803

700804
$server->node->guzzleClient([
701805
'X-Access-Server' => $server->uuid,
702806
'X-Access-Token' => $server->node->daemonSecret,
703-
])->request('PATCH', '/server', [
807+
])->request('POST', '/server/reinstall', [
704808
'json' => [
705809
'build' => [
706-
'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup]),
810+
'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup])->toArray(),
811+
],
812+
'service' => [
813+
'type' => $server->option->service->folder,
814+
'option' => $server->option->tag,
815+
'pack' => (! is_null($server->pack_id)) ? $server->pack->uuid : null,
816+
'skip_scripts' => $server->skip_scripts,
707817
],
708818
],
709819
]);
820+
821+
return true;
710822
});
823+
711824
}
712825

713826
/**
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use Illuminate\Support\Facades\Schema;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Database\Migrations\Migration;
6+
7+
class AddServiceScriptTrackingToServers extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*
12+
* @return void
13+
*/
14+
public function up()
15+
{
16+
Schema::table('servers', function (Blueprint $table) {
17+
$table->boolean('skip_scripts')->default(false)->after('description');
18+
});
19+
}
20+
21+
/**
22+
* Reverse the migrations.
23+
*
24+
* @return void
25+
*/
26+
public function down()
27+
{
28+
Schema::table('servers', function (Blueprint $table) {
29+
$table->dropColumn('skip_scripts');
30+
});
31+
}
32+
}

0 commit comments

Comments
 (0)