$input */ public function create(User $creator, Project $project, array $input): Server { $server = new Server([ 'project_id' => $project->id, 'user_id' => $creator->id, 'name' => $input['name'], 'ssh_user' => config('core.server_providers_default_user')[$input['provider']][$input['os']], 'ip' => $input['ip'] ?? '', 'port' => $input['port'] ?? 22, 'os' => $input['os'], 'type' => ServerType::REGULAR, 'provider' => $input['provider'], 'authentication' => [ 'user' => config('core.ssh_user'), 'pass' => Str::random(15), 'root_pass' => Str::random(15), ], 'progress' => 0, 'progress_step' => 'Initializing', ]); try { if ($server->provider != 'custom') { $server->provider_id = $input['server_provider']; } $server->type_data = $server->type()->data($input); $server->provider_data = $server->provider()->data($input); // save $server->save(); // create firewall rules $this->createFirewallRules($server); // create instance $server->provider()->create(); // add services $server->type()->createServices($input); // install server $this->install($server); return $server; } catch (Exception $e) { $server->delete(); throw ValidationException::withMessages([ 'provider' => $e->getMessage(), ]); } } private function install(Server $server): void { dispatch(function () use ($server): void { $maxWait = 180; while ($maxWait > 0) { sleep(10); $maxWait -= 10; if (! $server->provider()->isRunning()) { continue; } try { $server->ssh()->connect(); break; } catch (SSHConnectionError) { // ignore } } $server->type()->install(); $server->update([ 'status' => ServerStatus::READY, ]); Notifier::send($server, new ServerInstallationSucceed($server)); }) ->catch(function (Throwable $e) use ($server): void { $server->update([ 'status' => ServerStatus::INSTALLATION_FAILED, ]); Notifier::send($server, new ServerInstallationFailed($server)); Log::error('server-installation-error', [ 'error' => (string) $e, ]); }) ->onConnection('ssh'); } /** * @param array $input * @return array */ public static function rules(Project $project, array $input): array { $rules = [ 'provider' => [ 'required', Rule::in(config('core.server_providers')), ], 'name' => [ 'required', ], 'os' => [ 'required', Rule::in(config('core.operating_systems')), ], 'server_provider' => [ Rule::when(fn (): bool => isset($input['provider']) && $input['provider'] != ServerProvider::CUSTOM, [ 'required', Rule::exists('server_providers', 'id')->where(function (Builder $query) use ($project): void { $query->where('project_id', $project->id) ->orWhereNull('project_id'); }), ]), ], 'ip' => [ Rule::when(fn (): bool => isset($input['provider']) && $input['provider'] == ServerProvider::CUSTOM, [ 'required', new RestrictedIPAddressesRule, ]), ], 'port' => [ Rule::when(fn (): bool => isset($input['provider']) && $input['provider'] == ServerProvider::CUSTOM, [ 'required', 'numeric', 'min:1', 'max:65535', ]), ], ]; return array_merge($rules, self::typeRules($input), self::providerRules($input)); } /** * @param array $input * @return array> */ private static function typeRules(array $input): array { if (! isset($input['type']) || ! in_array($input['type'], config('core.server_types'))) { return []; } $server = new Server(['type' => $input['type']]); return $server->type()->createRules($input); } /** * @param array $input * @return array> */ private static function providerRules(array $input): array { if ( ! isset($input['provider']) || ! isset($input['server_provider']) || ! in_array($input['provider'], config('core.server_providers')) || $input['provider'] == ServerProvider::CUSTOM ) { return []; } $server = new Server([ 'provider' => $input['provider'], 'provider_id' => $input['server_provider'], ]); return $server->provider()->createRules($input); } public function createFirewallRules(Server $server): void { $server->firewallRules()->createMany([ [ 'type' => 'allow', 'name' => 'SSH', 'protocol' => 'tcp', 'port' => 22, 'source' => null, 'mask' => null, 'status' => FirewallRuleStatus::READY, ], [ 'type' => 'allow', 'name' => 'HTTP', 'protocol' => 'tcp', 'port' => 80, 'source' => null, 'mask' => null, 'status' => FirewallRuleStatus::READY, ], [ 'type' => 'allow', 'name' => 'HTTPS', 'protocol' => 'tcp', 'port' => 443, 'source' => null, 'mask' => null, 'status' => FirewallRuleStatus::READY, ], ]); } }