mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-08 09:22:34 +00:00
Plugins base (#613)
* wip * wip * cleanup * notification channels * phpstan * services * remove server types * refactoring * refactoring
This commit is contained in:
40
app/Services/AbstractService.php
Normal file
40
app/Services/AbstractService.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Service;
|
||||
|
||||
abstract class AbstractService implements ServiceInterface
|
||||
{
|
||||
public function __construct(protected Service $service) {}
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function creationData(array $input): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
366
app/Services/Database/AbstractDatabase.php
Executable file
366
app/Services/Database/AbstractDatabase.php
Executable file
@ -0,0 +1,366 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Database;
|
||||
|
||||
use App\Actions\Database\SyncDatabases;
|
||||
use App\Enums\BackupStatus;
|
||||
use App\Exceptions\ServiceInstallationFailed;
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Models\BackupFile;
|
||||
use App\Services\AbstractService;
|
||||
use Closure;
|
||||
|
||||
abstract class AbstractDatabase extends AbstractService implements Database
|
||||
{
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
protected array $systemDbs = [];
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
protected array $systemUsers = [];
|
||||
|
||||
protected string $defaultCharset;
|
||||
|
||||
protected string $separator = "\t";
|
||||
|
||||
protected int $headerLines = 1;
|
||||
|
||||
protected bool $removeLastRow = false;
|
||||
|
||||
/**
|
||||
* @phpstan-return view-string
|
||||
*/
|
||||
protected function getScriptView(string $script): string
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
return 'ssh.services.database.'.$this->service->name.'.'.$script;
|
||||
}
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$databaseExists = $this->service->server->database();
|
||||
if ($databaseExists) {
|
||||
$fail('You already have a database service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServiceInstallationFailed
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$version = str_replace('.', '', $this->service->version);
|
||||
$command = view($this->getScriptView('install-'.$version));
|
||||
$this->service->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version);
|
||||
$status = $this->service->server->systemd()->status($this->unit());
|
||||
$this->service->validateInstall($status);
|
||||
$this->service->server->os()->cleanup();
|
||||
/** @TODO implement post-install for services and move it there */
|
||||
app(SyncDatabases::class)->sync($this->service->server);
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$hasDatabase = $this->service->server->databases()->exists();
|
||||
if ($hasDatabase) {
|
||||
$fail('You have database(s) on the server.');
|
||||
}
|
||||
$hasDatabaseUser = $this->service->server->databaseUsers()->exists();
|
||||
if ($hasDatabaseUser) {
|
||||
$fail('You have database user(s) on the server.');
|
||||
}
|
||||
$hasRunningBackup = $this->service->server->backups()
|
||||
->where('status', BackupStatus::RUNNING)
|
||||
->exists();
|
||||
if ($hasRunningBackup) {
|
||||
$fail('You have database backup(s) on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function uninstall(): void
|
||||
{
|
||||
$version = $this->service->version;
|
||||
$command = view($this->getScriptView('uninstall'));
|
||||
$this->service->server->ssh()->exec($command, 'uninstall-'.$this->service->name.'-'.$version);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function create(string $name, string $charset, string $collation): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view($this->getScriptView('create'), [
|
||||
'name' => $name,
|
||||
'charset' => $charset,
|
||||
'collation' => $collation,
|
||||
]),
|
||||
'create-database'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function delete(string $name): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view($this->getScriptView('delete'), [
|
||||
'name' => $name,
|
||||
]),
|
||||
'delete-database'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function createUser(string $username, string $password, string $host): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view($this->getScriptView('create-user'), [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'host' => $host,
|
||||
]),
|
||||
'create-user'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function deleteUser(string $username, string $host): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view($this->getScriptView('delete-user'), [
|
||||
'username' => $username,
|
||||
'host' => $host,
|
||||
]),
|
||||
'delete-user'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function link(string $username, string $host, array $databases): void
|
||||
{
|
||||
$ssh = $this->service->server->ssh();
|
||||
$version = $this->service->version;
|
||||
|
||||
foreach ($databases as $database) {
|
||||
$ssh->exec(
|
||||
view($this->getScriptView('link'), [
|
||||
'username' => $username,
|
||||
'host' => $host,
|
||||
'database' => $database,
|
||||
'version' => $version,
|
||||
]),
|
||||
'link-user-to-database'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function unlink(string $username, string $host): void
|
||||
{
|
||||
$version = $this->service->version;
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
view($this->getScriptView('unlink'), [
|
||||
'username' => $username,
|
||||
'host' => $host,
|
||||
'version' => $version,
|
||||
]),
|
||||
'unlink-user-from-databases'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function runBackup(BackupFile $backupFile): void
|
||||
{
|
||||
// backup
|
||||
$this->service->server->ssh()->exec(
|
||||
view($this->getScriptView('backup'), [
|
||||
'file' => $backupFile->name,
|
||||
'database' => $backupFile->backup->database->name,
|
||||
]),
|
||||
'backup-database'
|
||||
);
|
||||
|
||||
// upload to storage
|
||||
$upload = $backupFile->backup->storage->provider()->ssh($this->service->server)->upload(
|
||||
$backupFile->tempPath(),
|
||||
$backupFile->path(),
|
||||
);
|
||||
|
||||
// cleanup
|
||||
$this->service->server->ssh()->exec('rm '.$backupFile->tempPath());
|
||||
|
||||
$backupFile->size = $upload['size'];
|
||||
$backupFile->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function restoreBackup(BackupFile $backupFile, string $database): void
|
||||
{
|
||||
// download
|
||||
$backupFile->backup->storage->provider()->ssh($this->service->server)->download(
|
||||
$backupFile->path(),
|
||||
$backupFile->tempPath(),
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
view($this->getScriptView('restore'), [
|
||||
'database' => $database,
|
||||
'file' => rtrim($backupFile->tempPath(), '.zip'),
|
||||
]),
|
||||
'restore-database'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function getCharsets(): array
|
||||
{
|
||||
$data = $this->service->server->ssh()->exec(
|
||||
view($this->getScriptView('get-charsets')),
|
||||
'get-database-charsets'
|
||||
);
|
||||
|
||||
$charsets = $this->tableToArray($data);
|
||||
|
||||
$results = [];
|
||||
$charsetCollations = [];
|
||||
|
||||
foreach ($charsets as $key => $charset) {
|
||||
if (empty($charsetCollations[$charset[1]])) {
|
||||
$charsetCollations[$charset[1]] = [];
|
||||
}
|
||||
|
||||
$charsetCollations[$charset[1]][] = $charset[0];
|
||||
|
||||
if ($charset[3] === 'Yes') {
|
||||
$results[$charset[1]] = [
|
||||
'default' => $charset[0],
|
||||
'list' => [],
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key == count($charsets) - 1) {
|
||||
$results[$charset[1]] = [
|
||||
'default' => null,
|
||||
'list' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_keys($results) as $charset) {
|
||||
$results[$charset]['list'] = $charsetCollations[$charset];
|
||||
}
|
||||
|
||||
ksort($results);
|
||||
|
||||
return [
|
||||
'charsets' => $results,
|
||||
'defaultCharset' => $this->defaultCharset,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function getDatabases(): array
|
||||
{
|
||||
$data = $this->service->server->ssh()->exec(
|
||||
view($this->getScriptView('get-db-list')),
|
||||
'get-db-list'
|
||||
);
|
||||
|
||||
$databases = $this->tableToArray($data);
|
||||
|
||||
return array_values(array_filter($databases, fn ($database): bool => ! in_array($database[0], $this->systemDbs)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function getUsers(): array
|
||||
{
|
||||
$data = $this->service->server->ssh()->exec(
|
||||
view($this->getScriptView('get-users-list')),
|
||||
'get-users-list'
|
||||
);
|
||||
|
||||
$users = $this->tableToArray($data);
|
||||
|
||||
$users = array_values(array_filter($users, fn ($users): bool => ! in_array($users[0], $this->systemUsers)));
|
||||
|
||||
foreach ($users as $key => $user) {
|
||||
$databases = explode(',', $user[2]);
|
||||
$databases = array_values(array_filter($databases, fn ($database): bool => ! in_array($database, $this->systemDbs)));
|
||||
$users[$key][2] = implode(',', $databases);
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array<string>>
|
||||
*/
|
||||
protected function tableToArray(string $data, bool $keepHeader = false): array
|
||||
{
|
||||
$lines = explode("\n", trim($data));
|
||||
|
||||
if (! $keepHeader) {
|
||||
for ($i = 0; $i < $this->headerLines; $i++) {
|
||||
array_shift($lines);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->removeLastRow) {
|
||||
array_pop($lines);
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
foreach ($lines as $line) {
|
||||
$separator = $this->separator === '' || $this->separator === '0' ? "\t" : $this->separator;
|
||||
$row = explode($separator, $line);
|
||||
$row = array_map('trim', $row);
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
}
|
43
app/Services/Database/Database.php
Executable file
43
app/Services/Database/Database.php
Executable file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Database;
|
||||
|
||||
use App\Models\BackupFile;
|
||||
use App\Services\ServiceInterface;
|
||||
|
||||
interface Database extends ServiceInterface
|
||||
{
|
||||
public function create(string $name, string $charset, string $collation): void;
|
||||
|
||||
public function delete(string $name): void;
|
||||
|
||||
public function createUser(string $username, string $password, string $host): void;
|
||||
|
||||
public function deleteUser(string $username, string $host): void;
|
||||
|
||||
/**
|
||||
* @param array<string> $databases
|
||||
*/
|
||||
public function link(string $username, string $host, array $databases): void;
|
||||
|
||||
public function unlink(string $username, string $host): void;
|
||||
|
||||
public function runBackup(BackupFile $backupFile): void;
|
||||
|
||||
public function restoreBackup(BackupFile $backupFile, string $database): void;
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getCharsets(): array;
|
||||
|
||||
/**
|
||||
* @return array<int, array<string>>
|
||||
*/
|
||||
public function getDatabases(): array;
|
||||
|
||||
/**
|
||||
* @return array<int, array<string>>
|
||||
*/
|
||||
public function getUsers(): array;
|
||||
}
|
31
app/Services/Database/Mariadb.php
Normal file
31
app/Services/Database/Mariadb.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Database;
|
||||
|
||||
class Mariadb extends AbstractDatabase
|
||||
{
|
||||
protected array $systemDbs = ['information_schema', 'performance_schema', 'mysql', 'sys'];
|
||||
|
||||
protected array $systemUsers = [
|
||||
'root',
|
||||
'mysql',
|
||||
'mariadb.sys',
|
||||
];
|
||||
|
||||
protected string $defaultCharset = 'utf8mb3';
|
||||
|
||||
public static function id(): string
|
||||
{
|
||||
return 'mariadb';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'database';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return 'mariadb';
|
||||
}
|
||||
}
|
32
app/Services/Database/Mysql.php
Executable file
32
app/Services/Database/Mysql.php
Executable file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Database;
|
||||
|
||||
class Mysql extends AbstractDatabase
|
||||
{
|
||||
protected array $systemDbs = ['information_schema', 'performance_schema', 'mysql', 'sys'];
|
||||
|
||||
protected array $systemUsers = [
|
||||
'root',
|
||||
'mysql.session',
|
||||
'mysql.sys',
|
||||
'mysql.infoschema',
|
||||
];
|
||||
|
||||
protected string $defaultCharset = 'utf8mb3';
|
||||
|
||||
public static function id(): string
|
||||
{
|
||||
return 'mysql';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'database';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return 'mysql';
|
||||
}
|
||||
}
|
36
app/Services/Database/Postgresql.php
Normal file
36
app/Services/Database/Postgresql.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Database;
|
||||
|
||||
class Postgresql extends AbstractDatabase
|
||||
{
|
||||
protected array $systemDbs = ['template0', 'template1', 'postgres'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $systemUsers = ['postgres'];
|
||||
|
||||
protected string $defaultCharset = 'UTF8';
|
||||
|
||||
protected int $headerLines = 2;
|
||||
|
||||
protected string $separator = '|';
|
||||
|
||||
protected bool $removeLastRow = true;
|
||||
|
||||
public static function id(): string
|
||||
{
|
||||
return 'postgresql';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'database';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return 'postgresql';
|
||||
}
|
||||
}
|
7
app/Services/Firewall/AbstractFirewall.php
Executable file
7
app/Services/Firewall/AbstractFirewall.php
Executable file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Firewall;
|
||||
|
||||
use App\Services\AbstractService;
|
||||
|
||||
abstract class AbstractFirewall extends AbstractService implements Firewall {}
|
10
app/Services/Firewall/Firewall.php
Executable file
10
app/Services/Firewall/Firewall.php
Executable file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Firewall;
|
||||
|
||||
use App\Services\ServiceInterface;
|
||||
|
||||
interface Firewall extends ServiceInterface
|
||||
{
|
||||
public function applyRules(): void;
|
||||
}
|
57
app/Services/Firewall/Ufw.php
Executable file
57
app/Services/Firewall/Ufw.php
Executable file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Firewall;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use App\Exceptions\SSHError;
|
||||
|
||||
class Ufw extends AbstractFirewall
|
||||
{
|
||||
public static function id(): string
|
||||
{
|
||||
return 'ufw';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'firewall';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return 'ufw';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.firewall.ufw.install-ufw'),
|
||||
'install-ufw'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function applyRules(): void
|
||||
{
|
||||
$rules = $this->service->server
|
||||
->firewallRules()
|
||||
->where('status', '!=', FirewallRuleStatus::DELETING)
|
||||
->get();
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.firewall.ufw.apply-rules', ['rules' => $rules]),
|
||||
'apply-rules'
|
||||
);
|
||||
}
|
||||
}
|
68
app/Services/Monitoring/RemoteMonitor/RemoteMonitor.php
Normal file
68
app/Services/Monitoring/RemoteMonitor/RemoteMonitor.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Monitoring\RemoteMonitor;
|
||||
|
||||
use App\Models\Metric;
|
||||
use App\Services\AbstractService;
|
||||
use Closure;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class RemoteMonitor extends AbstractService
|
||||
{
|
||||
public static function id(): string
|
||||
{
|
||||
return 'remote-monitor';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'monitoring';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$monitoringExists = $this->service->server->monitoring();
|
||||
if ($monitoringExists) {
|
||||
$fail('You already have a monitoring service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
'version' => [
|
||||
'required',
|
||||
Rule::in(['latest']),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function creationData(array $input): array
|
||||
{
|
||||
return [
|
||||
'data_retention' => 10,
|
||||
];
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
'data_retention' => $this->service->type_data['data_retention'] ?? 10,
|
||||
];
|
||||
}
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function uninstall(): void
|
||||
{
|
||||
Metric::query()->where('server_id', $this->service->server_id)->delete();
|
||||
}
|
||||
}
|
116
app/Services/Monitoring/VitoAgent/VitoAgent.php
Normal file
116
app/Services/Monitoring/VitoAgent/VitoAgent.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Monitoring\VitoAgent;
|
||||
|
||||
use App\Exceptions\ServiceInstallationFailed;
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Models\Metric;
|
||||
use App\Services\AbstractService;
|
||||
use Closure;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
class VitoAgent extends AbstractService
|
||||
{
|
||||
const string TAGS_URL = 'https://api.github.com/repos/vitodeploy/agent/tags';
|
||||
|
||||
const string DOWNLOAD_URL = 'https://github.com/vitodeploy/agent/releases/download/%s';
|
||||
|
||||
public static function id(): string
|
||||
{
|
||||
return 'vito-agent';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'monitoring';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return 'vito-agent';
|
||||
}
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$monitoringExists = $this->service->server->monitoring();
|
||||
if ($monitoringExists) {
|
||||
$fail('You already have a monitoring service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
'version' => [
|
||||
'required',
|
||||
Rule::in(['latest']),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function creationData(array $input): array
|
||||
{
|
||||
return [
|
||||
'url' => '',
|
||||
'secret' => Uuid::uuid4()->toString(),
|
||||
'data_retention' => 10,
|
||||
];
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
'url' => $this->service->type_data['url'] ?? null,
|
||||
'secret' => $this->service->type_data['secret'] ?? null,
|
||||
'data_retention' => $this->service->type_data['data_retention'] ?? 10,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
* @throws ServiceInstallationFailed
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$tags = Http::get(self::TAGS_URL)->json();
|
||||
if (empty($tags)) {
|
||||
throw new ServiceInstallationFailed('Failed to fetch tags');
|
||||
}
|
||||
$this->service->version = $tags[0]['name'];
|
||||
$this->service->save();
|
||||
$downloadUrl = sprintf(self::DOWNLOAD_URL, $this->service->version);
|
||||
|
||||
$data = $this->data();
|
||||
$data['url'] = route('api.servers.agent', [$this->service->server, $this->service->id]);
|
||||
$this->service->type_data = $data;
|
||||
$this->service->save();
|
||||
$this->service->refresh();
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.monitoring.vito-agent.install', [
|
||||
'downloadUrl' => $downloadUrl,
|
||||
'configUrl' => $this->data()['url'],
|
||||
'configSecret' => $this->data()['secret'],
|
||||
]),
|
||||
'install-vito-agent'
|
||||
);
|
||||
$status = $this->service->server->systemd()->status($this->unit());
|
||||
$this->service->validateInstall($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.monitoring.vito-agent.uninstall'),
|
||||
'uninstall-vito-agent'
|
||||
);
|
||||
Metric::query()->where('server_id', $this->service->server_id)->delete();
|
||||
}
|
||||
}
|
98
app/Services/NodeJS/NodeJS.php
Normal file
98
app/Services/NodeJS/NodeJS.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\NodeJS;
|
||||
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Services\AbstractService;
|
||||
use Closure;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class NodeJS extends AbstractService
|
||||
{
|
||||
public static function id(): string
|
||||
{
|
||||
return 'nodejs';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'nodejs';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'version' => [
|
||||
'required',
|
||||
Rule::in(config('service.services.nodejs.versions')),
|
||||
Rule::unique('services', 'version')
|
||||
->where('type', 'nodejs')
|
||||
->where('server_id', $this->service->server_id),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$hasSite = $this->service->server->sites()
|
||||
->where('nodejs_version', $this->service->version)
|
||||
->exists();
|
||||
if ($hasSite) {
|
||||
$fail('Some sites are using this NodeJS version.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$server = $this->service->server;
|
||||
$server->ssh()->exec(
|
||||
view('ssh.services.nodejs.install-nodejs', [
|
||||
'version' => $this->service->version,
|
||||
]),
|
||||
'install-nodejs-'.$this->service->version
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.nodejs.uninstall-nodejs', [
|
||||
'version' => $this->service->version,
|
||||
'default' => $this->service->is_default,
|
||||
]),
|
||||
'uninstall-nodejs-'.$this->service->version
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function setDefaultCli(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.nodejs.change-default-nodejs', [
|
||||
'version' => $this->service->version,
|
||||
]),
|
||||
'change-default-nodejs'
|
||||
);
|
||||
}
|
||||
}
|
176
app/Services/PHP/PHP.php
Normal file
176
app/Services/PHP/PHP.php
Normal file
@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\PHP;
|
||||
|
||||
use App\Exceptions\SSHCommandError;
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Services\AbstractService;
|
||||
use Closure;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PHP extends AbstractService
|
||||
{
|
||||
public static function id(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return 'php'.$this->service->version.'-fpm';
|
||||
}
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'version' => [
|
||||
'required',
|
||||
Rule::in(config('service.services.php.versions')),
|
||||
Rule::unique('services', 'version')
|
||||
->where('type', 'php')
|
||||
->where('server_id', $this->service->server_id),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$hasSite = $this->service->server->sites()
|
||||
->where('php_version', $this->service->version)
|
||||
->exists();
|
||||
if ($hasSite) {
|
||||
$fail('Some sites are using this PHP version.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$server = $this->service->server;
|
||||
$server->ssh()->exec(
|
||||
view('ssh.services.php.install-php', [
|
||||
'version' => $this->service->version,
|
||||
'user' => $server->getSshUser(),
|
||||
]),
|
||||
'install-php-'.$this->service->version
|
||||
);
|
||||
$this->installComposer();
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.php.uninstall-php', [
|
||||
'version' => $this->service->version,
|
||||
]),
|
||||
'uninstall-php-'.$this->service->version
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function setDefaultCli(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.php.change-default-php', [
|
||||
'version' => $this->service->version,
|
||||
]),
|
||||
'change-default-php'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function installExtension(string $name): void
|
||||
{
|
||||
$result = $this->service->server->ssh()->exec(
|
||||
view('ssh.services.php.install-php-extension', [
|
||||
'version' => $this->service->version,
|
||||
'name' => $name,
|
||||
]),
|
||||
'install-php-extension-'.$name
|
||||
);
|
||||
$pos = strpos($result, '[PHP Modules]');
|
||||
if ($pos === false) {
|
||||
throw new SSHCommandError('Failed to install extension');
|
||||
}
|
||||
$result = Str::substr($result, $pos);
|
||||
if (! Str::contains($result, $name)) {
|
||||
throw new SSHCommandError('Failed to install extension');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function installComposer(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.php.install-composer'),
|
||||
'install-composer'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function getPHPIni(string $type): string
|
||||
{
|
||||
return $this->service->server->os()->readFile(
|
||||
sprintf('/etc/php/%s/%s/php.ini', $this->service->version, $type)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function createFpmPool(string $user, string $version): void
|
||||
{
|
||||
$this->service->server->ssh()->write(
|
||||
"/etc/php/{$version}/fpm/pool.d/{$user}.conf",
|
||||
view('ssh.services.php.fpm-pool', [
|
||||
'user' => $user,
|
||||
'version' => $version,
|
||||
]),
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->systemd()->restart($this->unit());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function removeFpmPool(string $user, string $version, ?int $siteId): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.php.remove-fpm-pool', [
|
||||
'user' => $user,
|
||||
'version' => $version,
|
||||
]),
|
||||
"remove-{$version}fpm-pool-{$user}",
|
||||
$siteId
|
||||
);
|
||||
}
|
||||
}
|
38
app/Services/ProcessManager/AbstractProcessManager.php
Normal file
38
app/Services/ProcessManager/AbstractProcessManager.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\ProcessManager;
|
||||
|
||||
use App\Services\AbstractService;
|
||||
use Closure;
|
||||
|
||||
abstract class AbstractProcessManager extends AbstractService implements ProcessManager
|
||||
{
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$processManagerExists = $this->service->server->processManager();
|
||||
if ($processManagerExists) {
|
||||
$fail('You already have a process manager service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$hasWorker = $this->service->server->workers()->exists();
|
||||
if ($hasWorker) {
|
||||
$fail('You have worker(s) on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
29
app/Services/ProcessManager/ProcessManager.php
Executable file
29
app/Services/ProcessManager/ProcessManager.php
Executable file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\ProcessManager;
|
||||
|
||||
use App\Services\ServiceInterface;
|
||||
|
||||
interface ProcessManager extends ServiceInterface
|
||||
{
|
||||
public function create(
|
||||
int $id,
|
||||
string $command,
|
||||
string $user,
|
||||
bool $autoStart,
|
||||
bool $autoRestart,
|
||||
int $numprocs,
|
||||
string $logFile,
|
||||
?int $siteId = null
|
||||
): void;
|
||||
|
||||
public function delete(int $id, ?int $siteId = null): void;
|
||||
|
||||
public function restart(int $id, ?int $siteId = null): void;
|
||||
|
||||
public function stop(int $id, ?int $siteId = null): void;
|
||||
|
||||
public function start(int $id, ?int $siteId = null): void;
|
||||
|
||||
public function getLogs(string $user, string $logPath): string;
|
||||
}
|
152
app/Services/ProcessManager/Supervisor.php
Normal file
152
app/Services/ProcessManager/Supervisor.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\ProcessManager;
|
||||
|
||||
use App\Exceptions\SSHError;
|
||||
use Throwable;
|
||||
|
||||
class Supervisor extends AbstractProcessManager
|
||||
{
|
||||
public static function id(): string
|
||||
{
|
||||
return 'supervisor';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'process_manager';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return 'supervisor';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.process-manager.supervisor.install-supervisor'),
|
||||
'install-supervisor'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.process-manager.supervisor.uninstall-supervisor'),
|
||||
'uninstall-supervisor'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function create(
|
||||
int $id,
|
||||
string $command,
|
||||
string $user,
|
||||
bool $autoStart,
|
||||
bool $autoRestart,
|
||||
int $numprocs,
|
||||
string $logFile,
|
||||
?int $siteId = null
|
||||
): void {
|
||||
$this->service->server->ssh()->write(
|
||||
"/etc/supervisor/conf.d/$id.conf",
|
||||
view('ssh.services.process-manager.supervisor.worker', [
|
||||
'name' => (string) $id,
|
||||
'command' => $command,
|
||||
'user' => $user,
|
||||
'autoStart' => var_export($autoStart, true),
|
||||
'autoRestart' => var_export($autoRestart, true),
|
||||
'numprocs' => (string) $numprocs,
|
||||
'logFile' => $logFile,
|
||||
]),
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.process-manager.supervisor.create-worker', [
|
||||
'id' => $id,
|
||||
'logFile' => $logFile,
|
||||
'user' => $user,
|
||||
]),
|
||||
'create-worker',
|
||||
$siteId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function delete(int $id, ?int $siteId = null): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.process-manager.supervisor.delete-worker', [
|
||||
'id' => $id,
|
||||
]),
|
||||
'delete-worker',
|
||||
$siteId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function restart(int $id, ?int $siteId = null): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.process-manager.supervisor.restart-worker', [
|
||||
'id' => $id,
|
||||
]),
|
||||
'restart-worker',
|
||||
$siteId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function stop(int $id, ?int $siteId = null): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.process-manager.supervisor.stop-worker', [
|
||||
'id' => $id,
|
||||
]),
|
||||
'stop-worker',
|
||||
$siteId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function start(int $id, ?int $siteId = null): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.process-manager.supervisor.start-worker', [
|
||||
'id' => $id,
|
||||
]),
|
||||
'start-worker',
|
||||
$siteId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function getLogs(string $user, string $logPath): string
|
||||
{
|
||||
return $this->service->server->ssh($user)->exec(
|
||||
"tail -100 $logPath"
|
||||
);
|
||||
}
|
||||
}
|
68
app/Services/Redis/Redis.php
Normal file
68
app/Services/Redis/Redis.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Redis;
|
||||
|
||||
use App\Exceptions\ServiceInstallationFailed;
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Services\AbstractService;
|
||||
use Closure;
|
||||
|
||||
class Redis extends AbstractService
|
||||
{
|
||||
public static function id(): string
|
||||
{
|
||||
return 'redis';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'memory_database';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return 'redis';
|
||||
}
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$redisExists = $this->service->server->memoryDatabase();
|
||||
if ($redisExists) {
|
||||
$fail('You already have a Redis service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServiceInstallationFailed
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.redis.install'),
|
||||
'install-redis'
|
||||
);
|
||||
$status = $this->service->server->systemd()->status($this->unit());
|
||||
$this->service->validateInstall($status);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.redis.uninstall'),
|
||||
'uninstall-redis'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
}
|
38
app/Services/ServiceInterface.php
Normal file
38
app/Services/ServiceInterface.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
interface ServiceInterface
|
||||
{
|
||||
public static function id(): string;
|
||||
|
||||
public static function type(): string;
|
||||
|
||||
public function unit(): string;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $input
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function creationRules(array $input): array;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $input
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function creationData(array $input): array;
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function deletionRules(): array;
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function data(): array;
|
||||
|
||||
public function install(): void;
|
||||
|
||||
public function uninstall(): void;
|
||||
}
|
101
app/Services/Webserver/AbstractWebserver.php
Executable file
101
app/Services/Webserver/AbstractWebserver.php
Executable file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Webserver;
|
||||
|
||||
use App\Models\Site;
|
||||
use App\Services\AbstractService;
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
|
||||
abstract class AbstractWebserver extends AbstractService implements Webserver
|
||||
{
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$webserverExists = $this->service->server->webserver();
|
||||
if ($webserverExists) {
|
||||
$fail('You already have a webserver service on the server.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function deletionRules(): array
|
||||
{
|
||||
return [
|
||||
'service' => [
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$hasSite = $this->service->server->sites()
|
||||
->exists();
|
||||
if ($hasSite) {
|
||||
$fail('Cannot uninstall webserver while you have websites using it.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $replace replace blocks
|
||||
* @param array<int, string> $regenerate regenerates the blocks
|
||||
* @param array<string, string> $append appends to the blocks
|
||||
*/
|
||||
protected function getUpdatedVHost(Site $site, string $vhost, array $replace = [], array $regenerate = [], array $append = []): string
|
||||
{
|
||||
foreach ($replace as $block => $replacement) {
|
||||
$vhost = preg_replace(
|
||||
'/#\['.$block.'](.*?)#\[\/'.$block.']/s',
|
||||
$replacement,
|
||||
$vhost
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($regenerate as $block) {
|
||||
$vhost = preg_replace(
|
||||
'/#\['.$block.'](.*?)#\[\/'.$block.']/s',
|
||||
$this->generateVhost($site, $block),
|
||||
$vhost
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($append as $block => $content) {
|
||||
/**
|
||||
* #[block]
|
||||
* content
|
||||
* content
|
||||
* content
|
||||
* content
|
||||
* --append-here--
|
||||
* #[/block]
|
||||
*/
|
||||
$vhost = preg_replace(
|
||||
'/(#\['.$block.'](.*?)\n)(?=#\[\/'.$block.'])/s',
|
||||
"\$1$content\n",
|
||||
$vhost
|
||||
);
|
||||
}
|
||||
|
||||
return $vhost;
|
||||
}
|
||||
|
||||
protected function generateVhost(Site $site, ?string $block = null): string
|
||||
{
|
||||
$viewPath = 'ssh.services.webserver.'.$this::id().'.vhost-blocks.'.$block;
|
||||
if ($block) {
|
||||
if (! view()->exists($viewPath)) {
|
||||
throw new InvalidArgumentException("View for block '{$block}' does not exist.");
|
||||
}
|
||||
$vhost = view($viewPath, [
|
||||
'site' => $site,
|
||||
]);
|
||||
} else {
|
||||
$vhost = $site->type()->vhost($this::id());
|
||||
}
|
||||
|
||||
return format_nginx_config($vhost);
|
||||
}
|
||||
}
|
209
app/Services/Webserver/Caddy.php
Executable file
209
app/Services/Webserver/Caddy.php
Executable file
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Webserver;
|
||||
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Exceptions\SSLCreationException;
|
||||
use App\Models\Site;
|
||||
use App\Models\Ssl;
|
||||
use Throwable;
|
||||
|
||||
class Caddy extends AbstractWebserver
|
||||
{
|
||||
public static function id(): string
|
||||
{
|
||||
return 'caddy';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'webserver';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return 'caddy';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.caddy.install-caddy'),
|
||||
'install-caddy'
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->write(
|
||||
'/etc/caddy/Caddyfile',
|
||||
view('ssh.services.webserver.caddy.caddy'),
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->write(
|
||||
'/etc/systemd/system/caddy.service',
|
||||
view('ssh.services.webserver.caddy.caddy-systemd'),
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->systemd()->reload();
|
||||
|
||||
$this->service->server->systemd()->restart('caddy');
|
||||
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.caddy.uninstall-caddy'),
|
||||
'uninstall-caddy'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function createVHost(Site $site): void
|
||||
{
|
||||
// We need to get the isolated user first, if the site is isolated
|
||||
// otherwise, use the default ssh user
|
||||
$ssh = $this->service->server->ssh($site->user);
|
||||
|
||||
$ssh->exec(
|
||||
view('ssh.services.webserver.caddy.create-path', [
|
||||
'path' => $site->path,
|
||||
]),
|
||||
'create-path',
|
||||
$site->id
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->write(
|
||||
'/etc/caddy/sites-available/'.$site->domain,
|
||||
$this->generateVhost($site),
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.caddy.create-vhost', [
|
||||
'domain' => $site->domain,
|
||||
]),
|
||||
'create-vhost',
|
||||
$site->id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function updateVHost(Site $site, ?string $vhost = null, array $replace = [], array $regenerate = [], array $append = []): void
|
||||
{
|
||||
if (! $vhost) {
|
||||
$vhost = $this->getVHost($site);
|
||||
}
|
||||
|
||||
if (! $vhost || ! preg_match('/#\[main]/', $vhost)) {
|
||||
$vhost = $this->generateVhost($site);
|
||||
}
|
||||
|
||||
$this->service->server->ssh()->write(
|
||||
'/etc/caddy/sites-available/'.$site->domain,
|
||||
format_nginx_config($this->getUpdatedVHost($site, $vhost, $replace, $regenerate, $append)),
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->systemd()->restart('caddy');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function getVHost(Site $site): string
|
||||
{
|
||||
return $this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.caddy.get-vhost', [
|
||||
'domain' => $site->domain,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function deleteSite(Site $site): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.caddy.delete-site', [
|
||||
'domain' => $site->domain,
|
||||
'path' => $site->path,
|
||||
]),
|
||||
'delete-vhost',
|
||||
$site->id
|
||||
);
|
||||
$this->service->restart();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function changePHPVersion(Site $site, string $version): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.caddy.change-php-version', [
|
||||
'domain' => $site->domain,
|
||||
'oldVersion' => $site->php_version,
|
||||
'newVersion' => $version,
|
||||
]),
|
||||
'change-php-version',
|
||||
$site->id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function setupSSL(Ssl $ssl): void
|
||||
{
|
||||
if ($ssl->type == 'custom') {
|
||||
$ssl->certificate_path = '/etc/ssl/'.$ssl->id.'/cert.pem';
|
||||
$ssl->pk_path = '/etc/ssl/'.$ssl->id.'/privkey.pem';
|
||||
$ssl->save();
|
||||
$command = view('ssh.services.webserver.caddy.create-custom-ssl', [
|
||||
'path' => dirname($ssl->certificate_path),
|
||||
'certificate' => $ssl->certificate,
|
||||
'pk' => $ssl->pk,
|
||||
'certificatePath' => $ssl->certificate_path,
|
||||
'pkPath' => $ssl->pk_path,
|
||||
]);
|
||||
$result = $this->service->server->ssh()->setLog($ssl->log)->exec(
|
||||
$command,
|
||||
'create-ssl',
|
||||
$ssl->site_id
|
||||
);
|
||||
if (! $ssl->validateSetup($result)) {
|
||||
throw new SSLCreationException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function removeSSL(Ssl $ssl): void
|
||||
{
|
||||
if ($ssl->certificate_path) {
|
||||
$this->service->server->ssh()->exec(
|
||||
'sudo rm -rf '.dirname($ssl->certificate_path),
|
||||
'remove-ssl',
|
||||
$ssl->site_id
|
||||
);
|
||||
}
|
||||
|
||||
$this->updateVHost($ssl->site);
|
||||
}
|
||||
}
|
213
app/Services/Webserver/Nginx.php
Executable file
213
app/Services/Webserver/Nginx.php
Executable file
@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Webserver;
|
||||
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Exceptions\SSLCreationException;
|
||||
use App\Models\Site;
|
||||
use App\Models\Ssl;
|
||||
use Throwable;
|
||||
|
||||
class Nginx extends AbstractWebserver
|
||||
{
|
||||
public static function id(): string
|
||||
{
|
||||
return 'nginx';
|
||||
}
|
||||
|
||||
public static function type(): string
|
||||
{
|
||||
return 'webserver';
|
||||
}
|
||||
|
||||
public function unit(): string
|
||||
{
|
||||
return 'nginx';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.nginx.install-nginx'),
|
||||
'install-nginx'
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->write(
|
||||
'/etc/nginx/nginx.conf',
|
||||
view('ssh.services.webserver.nginx.nginx', [
|
||||
'user' => $this->service->server->getSshUser(),
|
||||
]),
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->systemd()->restart('nginx');
|
||||
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.nginx.uninstall-nginx'),
|
||||
'uninstall-nginx'
|
||||
);
|
||||
$this->service->server->os()->cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function createVHost(Site $site): void
|
||||
{
|
||||
// We need to get the isolated user first, if the site is isolated
|
||||
// otherwise, use the default ssh user
|
||||
$ssh = $this->service->server->ssh($site->user);
|
||||
|
||||
$ssh->exec(
|
||||
view('ssh.services.webserver.nginx.create-path', [
|
||||
'path' => $site->path,
|
||||
]),
|
||||
'create-path',
|
||||
$site->id
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->write(
|
||||
'/etc/nginx/sites-available/'.$site->domain,
|
||||
$this->generateVhost($site),
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.nginx.create-vhost', [
|
||||
'domain' => $site->domain,
|
||||
'vhost' => $this->generateVhost($site),
|
||||
]),
|
||||
'create-vhost',
|
||||
$site->id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function updateVHost(Site $site, ?string $vhost = null, array $replace = [], array $regenerate = [], array $append = []): void
|
||||
{
|
||||
if (! $vhost) {
|
||||
$vhost = $this->getVHost($site);
|
||||
}
|
||||
|
||||
if (! $vhost || ! preg_match('/#\[header]/', $vhost) || ! preg_match('/#\[main]/', $vhost) || ! preg_match('/#\[footer]/', $vhost)) {
|
||||
$vhost = $this->generateVhost($site);
|
||||
}
|
||||
|
||||
$this->service->server->ssh()->write(
|
||||
'/etc/nginx/sites-available/'.$site->domain,
|
||||
format_nginx_config($this->getUpdatedVHost($site, $vhost, $replace, $regenerate, $append)),
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->systemd()->restart('nginx');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function getVHost(Site $site): string
|
||||
{
|
||||
return $this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.nginx.get-vhost', [
|
||||
'domain' => $site->domain,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function deleteSite(Site $site): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.nginx.delete-site', [
|
||||
'domain' => $site->domain,
|
||||
'path' => $site->path,
|
||||
]),
|
||||
'delete-vhost',
|
||||
$site->id
|
||||
);
|
||||
$this->service->restart();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function changePHPVersion(Site $site, string $version): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.nginx.change-php-version', [
|
||||
'domain' => $site->domain,
|
||||
'oldVersion' => $site->php_version,
|
||||
'newVersion' => $version,
|
||||
]),
|
||||
'change-php-version',
|
||||
$site->id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function setupSSL(Ssl $ssl): void
|
||||
{
|
||||
$domains = '';
|
||||
foreach ($ssl->getDomains() as $domain) {
|
||||
$domains .= ' -d '.$domain;
|
||||
}
|
||||
$command = view('ssh.services.webserver.nginx.create-letsencrypt-ssl', [
|
||||
'email' => $ssl->email,
|
||||
'name' => $ssl->id,
|
||||
'domains' => $domains,
|
||||
]);
|
||||
if ($ssl->type == 'custom') {
|
||||
$ssl->certificate_path = '/etc/ssl/'.$ssl->id.'/cert.pem';
|
||||
$ssl->pk_path = '/etc/ssl/'.$ssl->id.'/privkey.pem';
|
||||
$ssl->save();
|
||||
$command = view('ssh.services.webserver.nginx.create-custom-ssl', [
|
||||
'path' => dirname($ssl->certificate_path),
|
||||
'certificate' => $ssl->certificate,
|
||||
'pk' => $ssl->pk,
|
||||
'certificatePath' => $ssl->certificate_path,
|
||||
'pkPath' => $ssl->pk_path,
|
||||
]);
|
||||
}
|
||||
$result = $this->service->server->ssh()->setLog($ssl->log)->exec(
|
||||
$command,
|
||||
'create-ssl',
|
||||
$ssl->site_id
|
||||
);
|
||||
if (! $ssl->validateSetup($result)) {
|
||||
throw new SSLCreationException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function removeSSL(Ssl $ssl): void
|
||||
{
|
||||
if ($ssl->certificate_path) {
|
||||
$this->service->server->ssh()->exec(
|
||||
'sudo rm -rf '.dirname($ssl->certificate_path),
|
||||
'remove-ssl',
|
||||
$ssl->site_id
|
||||
);
|
||||
}
|
||||
|
||||
$this->updateVHost($ssl->site);
|
||||
}
|
||||
}
|
29
app/Services/Webserver/Webserver.php
Executable file
29
app/Services/Webserver/Webserver.php
Executable file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Webserver;
|
||||
|
||||
use App\Models\Site;
|
||||
use App\Models\Ssl;
|
||||
use App\Services\ServiceInterface;
|
||||
|
||||
interface Webserver extends ServiceInterface
|
||||
{
|
||||
public function createVHost(Site $site): void;
|
||||
|
||||
/**
|
||||
* @param array<string, string> $replace replace blocks
|
||||
* @param array<int, string> $regenerate regenerates the blocks
|
||||
* @param array<string, string> $append appends to the blocks
|
||||
*/
|
||||
public function updateVHost(Site $site, ?string $vhost = null, array $replace = [], array $regenerate = [], array $append = []): void;
|
||||
|
||||
public function getVHost(Site $site): string;
|
||||
|
||||
public function deleteSite(Site $site): void;
|
||||
|
||||
public function changePHPVersion(Site $site, string $version): void;
|
||||
|
||||
public function setupSSL(Ssl $ssl): void;
|
||||
|
||||
public function removeSSL(Ssl $ssl): void;
|
||||
}
|
Reference in New Issue
Block a user