diff --git a/app/Enums/ServerStatus.php b/app/Enums/ServerStatus.php index 04c4e56..71af9a2 100644 --- a/app/Enums/ServerStatus.php +++ b/app/Enums/ServerStatus.php @@ -4,8 +4,11 @@ namespace App\Enums; enum ServerStatus: string { - case PENDING = 'pending'; + case WAITING_FOR_PROVIDER = 'waiting-for-provider'; + case PROVIDER_TIMEOUT = 'provider-timeout'; + case UNPROVISIONED = 'unprovisioned'; case PROVISIONING = 'provisioning'; + case PROVISIONING_FAILED = 'provisioning-failed'; case UPDATING = 'updating'; case ACTIVE = 'active'; case DELETING = 'deleting'; diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index ce28762..04e157a 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -6,6 +6,7 @@ use App\Actions\GenerateRandomSlug; use App\Actions\GetProviderService; use App\Enums\ServerProvider; use App\Enums\ServerStatus; +use App\Jobs\Servers\WaitForServerToConnect; use App\Models\Organisation; use App\Services\ServerProviders\HetznerService; use Illuminate\Http\Request; @@ -56,6 +57,7 @@ class ServerController extends Controller public function store(Request $request) { $rootPassword = Str::random(32); + $sudoPassword = Str::random(32); $providerService = app(GetProviderService::class)->execute($request->provider); if (!$providerService) { @@ -79,13 +81,19 @@ class ServerController extends Controller 'ipv4' => $createdServer->ipv4, 'ipv6' => $createdServer->ipv6, 'provider_status' => $createdServer->status, - 'status' => ServerStatus::PENDING, + 'status' => ServerStatus::WAITING_FOR_PROVIDER, 'region' => $request->location, 'os' => $request->image, 'plan' => $request->server_type, 'user' => '', ]); + dispatch(new WaitForServerToConnect( + server: $server, + rootPassword: $rootPassword, + sudoPassword: $sudoPassword, + ))->delay(now()->addSeconds(30)); + return redirect()->route('servers.show', ['organisation' => $organisation->id, 'server' => $server->id]); } diff --git a/app/Jobs/Servers/ProvisionServer.php b/app/Jobs/Servers/ProvisionServer.php index 6ed4b6f..97a0751 100644 --- a/app/Jobs/Servers/ProvisionServer.php +++ b/app/Jobs/Servers/ProvisionServer.php @@ -2,10 +2,12 @@ namespace App\Jobs\Servers; +use App\Enums\ServerStatus; use App\Models\Server; use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Queue\Queueable; +use Spatie\Ssh\Ssh; class ProvisionServer implements ShouldQueue, ShouldBeEncrypted { @@ -14,13 +16,29 @@ class ProvisionServer implements ShouldQueue, ShouldBeEncrypted public function __construct( protected Server $server, protected string $rootPassword, - ) - { + protected string $sudoPassword, + ) { // } public function handle(): void { - // + $ssh = Ssh::create('root', $this->server->ipv4 ?? $this->server->ipv6) + ->usePassword($this->rootPassword) + ->setTimeout(10); + + $provisionScriptUrl = route('provision-script', [ + 'sudo_password' => $this->sudoPassword, + 'hostname' => $this->server->hostname, + 'server_id' => $this->server->id, + ]); + + // Download the provision script and execute it + // The script will run in the background + $ssh->execute("cd ~ && wget --quiet --output-document=provision.sh \"{$provisionScriptUrl}\" && chmod +x provision.sh && ./provision.sh &"); + + $this->server->update([ + 'status' => ServerStatus::PROVISIONING, + ]); } } diff --git a/app/Jobs/Servers/WaitForServerToConnect.php b/app/Jobs/Servers/WaitForServerToConnect.php new file mode 100644 index 0000000..ff374c2 --- /dev/null +++ b/app/Jobs/Servers/WaitForServerToConnect.php @@ -0,0 +1,49 @@ +server->ipv4 ?? $this->server->ipv6) + ->usePassword($this->rootPassword) + ->setTimeout(10) + ->execute('echo "Connected"'); + + $this->server->update([ + 'status' => ServerStatus::UNPROVISIONED, + ]); + + dispatch(new ProvisionServer($this->server, $this->rootPassword, $this->sudoPassword)); + } catch (\Throwable $e) { + return; + } + } + + public function failed(\Throwable $exception): void + { + $this->server->update([ + 'status' => ServerStatus::PROVIDER_TIMEOUT, + ]); + } +} diff --git a/provision.sh b/provision.sh index fc57b53..7f23bc2 100644 --- a/provision.sh +++ b/provision.sh @@ -1,6 +1,7 @@ #!/bin/bash -# [!server!] -# [!sudo_password!] +# [!hostname!] - server hostname +# [!sudo_password!] - the sudo password to set +# [!server_id!] - the servers id apt_wait() { while fuser /var/lib/dpkg/lock >/dev/null 2>&1; do @@ -52,8 +53,8 @@ if [ ! -d /root/.ssh ]; then fi # Set The Hostname If Necessary -echo "[!server!]" > /etc/hostname sed -i 's/127\.0\.0\.1.*localhost/127.0.0.1 [!server!].localdomain [!server!] localhost/' /etc/hosts -hostname [!server!] +echo "[!hostname!]" > /etc/hostname sed -i 's/127\.0\.0\.1.*localhost/127.0.0.1 [!hostname!].localdomain [!hostname!] localhost/' /etc/hosts +hostname [!hostname!] # Setup Keystone User useradd keystone @@ -124,4 +125,4 @@ EOF # Callback that the server is installed -curl --insecure --data "event_id=878&server_id=390&sudo_password=[!sudo_password!]&recipe_id=" https://keystone.test/provisioning/callback/app \ No newline at end of file +curl --insecure --data "server_id=[!server_id!]&sudo_password=[!sudo_password!] https://keystone.test/provisioning/callback/app \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 8108318..f740c54 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,6 +4,7 @@ use App\Http\Controllers\ApplicationController; use App\Http\Controllers\EnvironmentController; use App\Http\Controllers\OrganisationController; use App\Http\Controllers\ServerController; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use Inertia\Inertia; @@ -36,5 +37,22 @@ Route::middleware(['auth', 'verified'])->group(function () { }); }); +Route::get('/provision-script', function (Request $request) { + $validated = $request->validate([ + 'sudo_password' => ['required', 'string'], + 'hostname' => ['required', 'string'], + 'server_id' => ['required', 'integer', 'exists:servers,id'], + ]); + + $script = file_get_contents(base_path('provision.sh')); + + $script = str_replace('[!hostname!]', $validated['hostname'], $script); + $script = str_replace('[!sudo_password!]', $validated['sudo_password'], $script); + $script = str_replace('[!server_id!]', $validated['server_id'], $script); + + return response($script) + ->header('Content-Type', 'text/plain'); +})->name('provision-script'); + require __DIR__ . '/settings.php'; require __DIR__ . '/auth.php';