Skip to content

Commit 67ff67a

Browse files
committed
Add endpoints to return a server's egg configuration
1 parent 5df46b2 commit 67ff67a

File tree

10 files changed

+309
-106
lines changed

10 files changed

+309
-106
lines changed

app/Http/Controllers/Api/Remote/EggRetrievalController.php

Lines changed: 0 additions & 74 deletions
This file was deleted.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Http\JsonResponse;
7+
use Pterodactyl\Http\Controllers\Controller;
8+
use Pterodactyl\Services\Eggs\EggConfigurationService;
9+
use Pterodactyl\Repositories\Eloquent\ServerRepository;
10+
11+
class ServerConfigurationController extends Controller
12+
{
13+
/**
14+
* @var \Pterodactyl\Services\Eggs\EggConfigurationService
15+
*/
16+
private $eggConfigurationService;
17+
18+
/**
19+
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
20+
*/
21+
private $repository;
22+
23+
/**
24+
* ServerConfigurationController constructor.
25+
*
26+
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
27+
* @param \Pterodactyl\Services\Eggs\EggConfigurationService $eggConfigurationService
28+
*/
29+
public function __construct(ServerRepository $repository, EggConfigurationService $eggConfigurationService)
30+
{
31+
$this->eggConfigurationService = $eggConfigurationService;
32+
$this->repository = $repository;
33+
}
34+
35+
/**
36+
* @param \Illuminate\Http\Request $request
37+
* @param $uuid
38+
* @return \Illuminate\Http\JsonResponse
39+
*
40+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
41+
*/
42+
public function __invoke(Request $request, $uuid)
43+
{
44+
$server = $this->repository->getByUuid($uuid);
45+
46+
return JsonResponse::create(
47+
$this->eggConfigurationService->handle($server)
48+
);
49+
}
50+
}

app/Models/Egg.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,43 @@
22

33
namespace Pterodactyl\Models;
44

5+
/**
6+
* @property int $id
7+
* @property string $uuid
8+
* @property int $nest_id
9+
* @property string $author
10+
* @property string $name
11+
* @property string $description
12+
* @property string $docker_image
13+
* @property string|null $config_files
14+
* @property string|null $config_startup
15+
* @property string|null $config_logs
16+
* @property string|null $config_stop
17+
* @property int|null $config_from
18+
* @property string|null $startup
19+
* @property bool $script_is_privileged
20+
* @property string|null $script_install
21+
* @property string $script_entry
22+
* @property string $script_container
23+
* @property int|null $copy_script_from
24+
* @property \Carbon\Carbon $created_at
25+
* @property \Carbon\Carbon $updated_at
26+
*
27+
* @property string|null $copy_script_install
28+
* @property string $copy_script_entry
29+
* @property string $copy_script_container
30+
* @property string|null $inherit_config_files
31+
* @property string|null $inherit_config_startup
32+
* @property string|null $inherit_config_logs
33+
* @property string|null $inherit_config_stop
34+
*
35+
* @property \Pterodactyl\Models\Nest $nest
36+
* @property \Illuminate\Support\Collection|\Pterodactyl\Models\Server[] $servers
37+
* @property \Illuminate\Support\Collection|\Pterodactyl\Models\EggVariable[] $variables
38+
* @property \Illuminate\Support\Collection|\Pterodactyl\Models\Pack[] $packs
39+
* @property \Pterodactyl\Models\Egg|null $scriptFrom
40+
* @property \Pterodactyl\Models\Egg|null $configFrom
41+
*/
542
class Egg extends Validable
643
{
744
/**

app/Services/Eggs/EggConfigurationService.php

Lines changed: 155 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,46 +9,189 @@
99

1010
namespace Pterodactyl\Services\Eggs;
1111

12+
use Illuminate\Support\Arr;
13+
use Illuminate\Support\Str;
1214
use Pterodactyl\Models\Egg;
15+
use Pterodactyl\Models\Server;
1316
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
17+
use Pterodactyl\Services\Servers\ServerConfigurationStructureService;
1418

1519
class EggConfigurationService
1620
{
1721
/**
1822
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface
1923
*/
20-
protected $repository;
24+
private $repository;
25+
26+
/**
27+
* @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService
28+
*/
29+
private $configurationStructureService;
2130

2231
/**
2332
* EggConfigurationService constructor.
2433
*
2534
* @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository
35+
* @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService
2636
*/
27-
public function __construct(EggRepositoryInterface $repository)
28-
{
37+
public function __construct(
38+
EggRepositoryInterface $repository,
39+
ServerConfigurationStructureService $configurationStructureService
40+
) {
2941
$this->repository = $repository;
42+
$this->configurationStructureService = $configurationStructureService;
3043
}
3144

3245
/**
3346
* Return an Egg file to be used by the Daemon.
3447
*
35-
* @param int|\Pterodactyl\Models\Egg $egg
48+
* @param \Pterodactyl\Models\Server $server
3649
* @return array
3750
*
3851
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
3952
*/
40-
public function handle($egg): array
53+
public function handle(Server $server): array
54+
{
55+
$configs = $this->replacePlaceholders(
56+
$server, json_decode($server->egg->inherit_config_files)
57+
);
58+
59+
return [
60+
'startup' => json_decode($server->egg->inherit_config_startup),
61+
'stop' => $this->convertStopToNewFormat($server->egg->inherit_config_stop),
62+
'configs' => $configs,
63+
];
64+
}
65+
66+
/**
67+
* Converts a legacy stop string into a new generation stop option for a server.
68+
*
69+
* For most eggs, this ends up just being a command sent to the server console, but
70+
* if the stop command is something starting with a caret (^), it will be converted
71+
* into the associated kill signal for the instance.
72+
*
73+
* @param string $stop
74+
* @return array
75+
*/
76+
protected function convertStopToNewFormat(string $stop): array
4177
{
42-
if (! $egg instanceof Egg) {
43-
$egg = $this->repository->getWithCopyAttributes($egg);
78+
if (! Str::startsWith($stop, '^')) {
79+
return [
80+
'type' => 'command',
81+
'value' => $stop,
82+
];
83+
}
84+
85+
$signal = substr($stop, 1);
86+
if (strtoupper($signal) === 'C') {
87+
return [
88+
'type' => 'stop',
89+
'value' => null,
90+
];
4491
}
4592

4693
return [
47-
'startup' => json_decode($egg->inherit_config_startup),
48-
'stop' => $egg->inherit_config_stop,
49-
'configs' => json_decode($egg->inherit_config_files),
50-
'log' => json_decode($egg->inherit_config_logs),
51-
'query' => 'none',
94+
'type' => 'signal',
95+
'value' => strtoupper($signal),
5296
];
5397
}
98+
99+
/**
100+
* @param \Pterodactyl\Models\Server $server
101+
* @param object $configs
102+
* @return array
103+
*
104+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
105+
*/
106+
protected function replacePlaceholders(Server $server, object $configs)
107+
{
108+
// Get the legacy configuration structure for the server so that we
109+
// can property map the egg placeholders to values.
110+
$structure = $this->configurationStructureService->handle($server);
111+
112+
foreach ($configs as $file => $data) {
113+
foreach ($data->find ?? [] as &$value) {
114+
preg_match('/^{{(?<key>.*)}}$/', $value, $matches);
115+
116+
if (! $key = $matches['key'] ?? null) {
117+
continue;
118+
}
119+
120+
// Matched something in {{server.X}} format, now replace that with the actual
121+
// value from the server properties.
122+
//
123+
// The Daemon supports server.X, env.X, and config.X placeholders.
124+
if (! Str::startsWith($key, ['server.', 'env.', 'config.'])) {
125+
continue;
126+
}
127+
128+
// We don't want to do anything with config keys since the Daemon will need to handle
129+
// that. For example, the Spigot egg uses "config.docker.interface" to identify the Docker
130+
// interface to proxy through, but the Panel would be unaware of that.
131+
if (Str::startsWith($key, 'config.')) {
132+
$value = "{{{$key}}}";
133+
continue;
134+
}
135+
136+
// The legacy Daemon would set SERVER_MEMORY, SERVER_IP, and SERVER_PORT with their
137+
// respective values on the Daemon side. Ensure that anything referencing those properly
138+
// replaces them with the matching config value.
139+
switch ($key) {
140+
case 'server.build.env.SERVER_MEMORY':
141+
case 'env.SERVER_MEMORY':
142+
$key = 'server.build.memory';
143+
break;
144+
case 'server.build.env.SERVER_IP':
145+
case 'env.SERVER_IP':
146+
$key = 'server.build.default.ip';
147+
break;
148+
case 'server.build.env.SERVER_PORT':
149+
case 'env.SERVER_PORT':
150+
$key = 'server.build.default.port';
151+
break;
152+
}
153+
154+
// Replace anything starting with "server." with the value out of the server configuration
155+
// array that used to be created for the old daemon.
156+
if (Str::startsWith($key, 'server.')) {
157+
$value = Arr::get(
158+
$structure, preg_replace('/^server\./', '', $key), ''
159+
);
160+
continue;
161+
}
162+
163+
// Finally, replace anything starting with env. with the expected environment
164+
// variable from the server configuration.
165+
$value = Arr::get(
166+
$structure, preg_replace('/^env\./', 'build.env.', $key), ''
167+
);
168+
}
169+
}
170+
171+
$response = [];
172+
// Normalize the output of the configuration for the new Wings Daemon to more
173+
// easily ingest, as well as make things more flexible down the road.
174+
foreach ($configs as $file => $data) {
175+
$append = ['file' => $file, 'replace' => []];
176+
177+
// I like to think I understand PHP pretty well, but if you don't pass $value
178+
// by reference here, you'll end up with a resursive array loop if the config
179+
// file has two replacements that reference the same item in the configuration
180+
// array for the server.
181+
foreach ($data as $key => &$value) {
182+
if ($key !== 'find') {
183+
$append[$key] = $value;
184+
continue;
185+
}
186+
187+
foreach ($value as $find => $replace) {
188+
$append['replace'][] = ['match' => $find, 'value' => $replace];
189+
}
190+
}
191+
192+
$response[] = $append;
193+
}
194+
195+
return $response;
196+
}
54197
}

0 commit comments

Comments
 (0)