Monitoring & Service Management (#163)

Monitoring & Service Management
This commit is contained in:
Saeed Vaziry
2024-04-13 11:47:56 +02:00
committed by GitHub
parent 87ec0af697
commit 052e28d2e3
95 changed files with 2423 additions and 341 deletions

View File

@ -0,0 +1,150 @@
<?php
namespace App\Actions\Monitoring;
use App\Models\Server;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Query\Expression;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class GetMetrics
{
public function filter(Server $server, array $input): array
{
if (isset($input['from']) && isset($input['to']) && $input['from'] === $input['to']) {
$input['from'] = Carbon::parse($input['from'])->format('Y-m-d').' 00:00:00';
$input['to'] = Carbon::parse($input['to'])->format('Y-m-d').' 23:59:59';
}
$defaultInput = [
'period' => '10m',
];
$input = array_merge($defaultInput, $input);
$this->validate($input);
return $this->metrics(
server: $server,
fromDate: $this->getFromDate($input),
toDate: $this->getToDate($input),
interval: $this->getInterval($input)
);
}
private function metrics(
Server $server,
Carbon $fromDate,
Carbon $toDate,
?Expression $interval = null
): array {
$metrics = DB::table('metrics')
->where('server_id', $server->id)
->whereBetween('created_at', [$fromDate->format('Y-m-d H:i:s'), $toDate->format('Y-m-d H:i:s')])
->select(
[
DB::raw('created_at as date'),
DB::raw('AVG(load) as load'),
DB::raw('AVG(memory_total) as memory_total'),
DB::raw('AVG(memory_used) as memory_used'),
DB::raw('AVG(memory_free) as memory_free'),
DB::raw('AVG(disk_total) as disk_total'),
DB::raw('AVG(disk_used) as disk_used'),
DB::raw('AVG(disk_free) as disk_free'),
$interval,
],
)
->groupByRaw('date_interval')
->orderBy('date_interval')
->get()
->map(function ($item) {
$item->date = Carbon::parse($item->date)->format('Y-m-d H:i');
return $item;
});
return [
'metrics' => $metrics,
];
}
private function getFromDate(array $input): Carbon
{
if ($input['period'] === 'custom') {
return new Carbon($input['from']);
}
return Carbon::parse('-'.convert_time_format($input['period']));
}
private function getToDate(array $input): Carbon
{
if ($input['period'] === 'custom') {
return new Carbon($input['to']);
}
return Carbon::now();
}
private function getInterval(array $input): Expression
{
if ($input['period'] === 'custom') {
$from = new Carbon($input['from']);
$to = new Carbon($input['to']);
$periodInHours = $from->diffInHours($to);
}
if (! isset($periodInHours)) {
$periodInHours = Carbon::parse(
convert_time_format($input['period'])
)->diffInHours();
}
if ($periodInHours <= 1) {
return DB::raw("strftime('%Y-%m-%d %H:%M:00', created_at) as date_interval");
}
if ($periodInHours <= 24) {
return DB::raw("strftime('%Y-%m-%d %H:00:00', created_at) as date_interval");
}
if ($periodInHours > 24) {
return DB::raw("strftime('%Y-%m-%d 00:00:00', created_at) as date_interval");
}
}
private function validate(array $input): void
{
Validator::make($input, [
'period' => [
'required',
Rule::in([
'10m',
'30m',
'1h',
'12h',
'1d',
'7d',
'custom',
]),
],
])->validate();
if ($input['period'] === 'custom') {
Validator::make($input, [
'from' => [
'required',
'date',
'before:to',
],
'to' => [
'required',
'date',
'after:from',
],
])->validate();
}
}
}

View File

@ -4,6 +4,7 @@
use App\Enums\ServiceStatus;
use App\Models\Server;
use App\SSH\Services\PHP\PHP;
use Illuminate\Validation\ValidationException;
class ChangeDefaultCli
@ -12,7 +13,9 @@ public function change(Server $server, array $input): void
{
$this->validate($server, $input);
$service = $server->php($input['version']);
$service->handler()->setDefaultCli();
/** @var PHP $handler */
$handler = $service->handler();
$handler->setDefaultCli();
$server->defaultService('php')->update(['is_default' => 0]);
$service->update(['is_default' => 1]);
$service->update(['status' => ServiceStatus::READY]);

View File

@ -3,6 +3,7 @@
namespace App\Actions\PHP;
use App\Models\Server;
use App\SSH\Services\PHP\PHP;
use Illuminate\Validation\ValidationException;
class GetPHPIni
@ -14,7 +15,10 @@ public function getIni(Server $server, array $input): string
$php = $server->php($input['version']);
try {
return $php->handler()->getPHPIni();
/** @var PHP $handler */
$handler = $php->handler();
return $handler->getPHPIni();
} catch (\Throwable $e) {
throw ValidationException::withMessages(
['ini' => $e->getMessage()]

View File

@ -4,6 +4,7 @@
use App\Models\Server;
use App\Models\Service;
use App\SSH\Services\PHP\PHP;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
@ -23,7 +24,9 @@ public function install(Server $server, array $input): Service
$service->save();
dispatch(function () use ($service, $input) {
$service->handler()->installExtension($input['extension']);
/** @var PHP $handler */
$handler = $service->handler();
$handler->installExtension($input['extension']);
})->catch(function () use ($service, $input) {
$service->refresh();
$typeData = $service->type_data;

View File

@ -8,14 +8,15 @@
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class Create
class Install
{
public function create(Server $server, array $input): Service
public function install(Server $server, array $input): Service
{
$this->validate($server, $input);
$service = new Service([
'name' => $input['type'],
'server_id' => $server->id,
'name' => $input['name'],
'type' => $input['type'],
'version' => $input['version'],
'status' => ServiceStatus::INSTALLING,
@ -27,15 +28,13 @@ public function create(Server $server, array $input): Service
$service->save();
$service->handler()->create();
dispatch(function () use ($service) {
$service->handler()->install();
$service->status = ServiceStatus::READY;
$service->save();
})->catch(function () use ($service) {
$service->handler()->delete();
$service->delete();
$service->status = ServiceStatus::INSTALLATION_FAILED;
$service->save();
})->onConnection('ssh');
return $service;
@ -46,8 +45,11 @@ private function validate(Server $server, array $input): void
Validator::make($input, [
'type' => [
'required',
Rule::in(config('core.add_on_services')),
Rule::unique('services', 'type')->where('server_id', $server->id),
Rule::in(config('core.service_types')),
],
'name' => [
'required',
Rule::in(array_keys(config('core.service_types'))),
],
'version' => 'required',
])->validate();

View File

@ -0,0 +1,28 @@
<?php
namespace App\Actions\Service;
use App\Enums\ServiceStatus;
use App\Models\Service;
use Illuminate\Support\Facades\Validator;
class Uninstall
{
public function uninstall(Service $service): void
{
Validator::make([
'service' => $service->id,
], $service->handler()->deletionRules())->validate();
$service->status = ServiceStatus::UNINSTALLING;
$service->save();
dispatch(function () use ($service) {
$service->handler()->uninstall();
$service->delete();
})->catch(function () use ($service) {
$service->status = ServiceStatus::FAILED;
$service->save();
})->onConnection('ssh');
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class AgentController extends Controller
{
public function __invoke(Request $request, Server $server, int $id): JsonResponse
{
$validated = $this->validate($request, [
'load' => 'required|numeric',
'memory_total' => 'required|numeric',
'memory_used' => 'required|numeric',
'memory_free' => 'required|numeric',
'disk_total' => 'required|numeric',
'disk_used' => 'required|numeric',
'disk_free' => 'required|numeric',
]);
/** @var Service $service */
$service = $server->services()->findOrFail($id);
if ($request->header('secret') !== $service->handler()->data()['secret']) {
return response()->json(['error' => 'Unauthorized'], 401);
}
$server->metrics()->create(array_merge($validated, ['server_id' => $server->id]));
return response()->json();
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Monitoring\GetMetrics;
use App\Facades\Toast;
use App\Models\Server;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class MetricController extends Controller
{
public function index(Server $server, Request $request): View|RedirectResponse
{
if (! $server->service('monitoring')) {
Toast::error('You need to install monitoring service first');
return redirect()->route('servers.services', $server);
}
return view('metrics.index', [
'server' => $server,
'data' => app(GetMetrics::class)->filter($server, $request->input()),
]);
}
}

View File

@ -2,7 +2,8 @@
namespace App\Http\Controllers;
use App\Actions\Service\Create;
use App\Actions\Service\Install;
use App\Actions\Service\Uninstall;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Models\Server;
@ -68,7 +69,16 @@ public function disable(Server $server, Service $service): RedirectResponse
public function install(Server $server, Request $request): HtmxResponse
{
app(Create::class)->create($server, $request->input());
app(Install::class)->install($server, $request->input());
Toast::success('Service is being uninstalled!');
return htmx()->back();
}
public function uninstall(Server $server, Service $service): HtmxResponse
{
app(Uninstall::class)->uninstall($service);
Toast::success('Service is being uninstalled!');

53
app/Models/Metric.php Normal file
View File

@ -0,0 +1,53 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
* @property int $server_id
* @property float $load
* @property float $memory_total
* @property float $memory_used
* @property float $memory_free
* @property float $disk_total
* @property float $disk_used
* @property float $disk_free
* @property Server $server
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
*/
class Metric extends Model
{
use HasFactory;
protected $fillable = [
'server_id',
'load',
'memory_total',
'memory_used',
'memory_free',
'disk_total',
'disk_used',
'disk_free',
];
protected $casts = [
'server_id' => 'integer',
'load' => 'float',
'memory_total' => 'float',
'memory_used' => 'float',
'memory_free' => 'float',
'disk_total' => 'float',
'disk_used' => 'float',
'disk_free' => 'float',
];
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
}

View File

@ -195,6 +195,11 @@ public function daemons(): HasMany
return $this->queues()->whereNull('site_id');
}
public function metrics(): HasMany
{
return $this->hasMany(Metric::class);
}
public function sshKeys(): BelongsToMany
{
return $this->belongsToMany(SshKey::class, 'server_ssh_keys')
@ -325,6 +330,24 @@ public function php(?string $version = null): ?Service
return $this->service('php', $version);
}
public function memoryDatabase(?string $version = null): ?Service
{
if (! $version) {
return $this->defaultService('memory_database');
}
return $this->service('memory_database', $version);
}
public function monitoring(?string $version = null): ?Service
{
if (! $version) {
return $this->defaultService('monitoring');
}
return $this->service('monitoring', $version);
}
public function sshKey(): array
{
/** @var FilesystemAdapter $storageDisk */

View File

@ -4,13 +4,7 @@
use App\Actions\Service\Manage;
use App\Exceptions\ServiceInstallationFailed;
use App\SSH\Services\AddOnServices\AbstractAddOnService;
use App\SSH\Services\Database\Database as DatabaseHandler;
use App\SSH\Services\Firewall\Firewall as FirewallHandler;
use App\SSH\Services\PHP\PHP as PHPHandler;
use App\SSH\Services\ProcessManager\ProcessManager as ProcessManagerHandler;
use App\SSH\Services\Redis\Redis as RedisHandler;
use App\SSH\Services\Webserver\Webserver as WebserverHandler;
use App\SSH\Services\ServiceInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
@ -65,8 +59,8 @@ public function server(): BelongsTo
return $this->belongsTo(Server::class);
}
public function handler(
): PHPHandler|WebserverHandler|DatabaseHandler|FirewallHandler|ProcessManagerHandler|RedisHandler|AbstractAddOnService {
public function handler(): ServiceInterface
{
$handler = config('core.service_handlers')[$this->name];
return new $handler($this);

View File

@ -148,4 +148,12 @@ public function unzip(string $path): string
'unzip '.$path
);
}
public function cleanup(): void
{
$this->server->ssh()->exec(
$this->getScript('cleanup.sh'),
'cleanup'
);
}
}

View File

@ -0,0 +1,19 @@
# Update package lists
sudo DEBIAN_FRONTEND=noninteractive apt-get update -y
# Remove unnecessary dependencies
sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove --purge -y
# Clear package cache
sudo DEBIAN_FRONTEND=noninteractive apt-get clean -y
# Remove old configuration files
sudo DEBIAN_FRONTEND=noninteractive apt-get purge -y $(dpkg -l | grep '^rc' | awk '{print $2}')
# Clear temporary files
sudo rm -rf /tmp/*
# Clear journal logs
sudo DEBIAN_FRONTEND=noninteractive journalctl --vacuum-time=1d
echo "Cleanup completed."

View File

@ -0,0 +1,42 @@
<?php
namespace App\SSH\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
{
//
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace App\SSH\Services\AddOnServices;
use App\SSH\Services\ServiceInterface;
abstract class AbstractAddOnService implements ServiceInterface
{
abstract public function creationRules(array $input): array;
abstract public function creationData(array $input): array;
abstract public function create(): void;
abstract public function delete(): void;
abstract public function data(): array;
}

View File

@ -2,40 +2,78 @@
namespace App\SSH\Services\Database;
use App\Enums\BackupStatus;
use App\Models\BackupFile;
use App\Models\Server;
use App\Models\Service;
use App\SSH\HasScripts;
use App\SSH\Services\ServiceInterface;
use App\SSH\Services\AbstractService;
use Closure;
abstract class AbstractDatabase implements Database, ServiceInterface
abstract class AbstractDatabase extends AbstractService implements Database
{
use HasScripts;
protected Service $service;
protected Server $server;
abstract protected function getScriptsDir(): string;
public function __construct(Service $service)
public function creationRules(array $input): array
{
$this->service = $service;
$this->server = $service->server;
return [
'type' => [
'required',
function (string $attribute, mixed $value, Closure $fail) {
$databaseExists = $this->service->server->database();
if ($databaseExists) {
$fail('You already have a database service on the server.');
}
},
],
];
}
public function install(): void
{
$version = $this->service->version;
$command = $this->getScript($this->service->name.'/install-'.$version.'.sh');
$this->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version);
$status = $this->server->systemd()->status($this->service->unit);
$this->service->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version);
$status = $this->service->server->systemd()->status($this->service->unit);
$this->service->validateInstall($status);
$this->service->server->os()->cleanup();
}
public function deletionRules(): array
{
return [
'service' => [
function (string $attribute, mixed $value, Closure $fail) {
$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.');
}
},
],
];
}
public function uninstall(): void
{
$version = $this->service->version;
$command = $this->getScript($this->service->name.'/uninstall.sh');
$this->service->server->ssh()->exec($command, 'uninstall-'.$this->service->name.'-'.$version);
$this->service->server->os()->cleanup();
}
public function create(string $name): void
{
$this->server->ssh()->exec(
$this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/create.sh', [
'name' => $name,
]),
@ -45,7 +83,7 @@ public function create(string $name): void
public function delete(string $name): void
{
$this->server->ssh()->exec(
$this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/delete.sh', [
'name' => $name,
]),
@ -55,7 +93,7 @@ public function delete(string $name): void
public function createUser(string $username, string $password, string $host): void
{
$this->server->ssh()->exec(
$this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/create-user.sh', [
'username' => $username,
'password' => $password,
@ -67,7 +105,7 @@ public function createUser(string $username, string $password, string $host): vo
public function deleteUser(string $username, string $host): void
{
$this->server->ssh()->exec(
$this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/delete-user.sh', [
'username' => $username,
'host' => $host,
@ -78,7 +116,7 @@ public function deleteUser(string $username, string $host): void
public function link(string $username, string $host, array $databases): void
{
$ssh = $this->server->ssh();
$ssh = $this->service->server->ssh();
foreach ($databases as $database) {
$ssh->exec(
@ -94,7 +132,7 @@ public function link(string $username, string $host, array $databases): void
public function unlink(string $username, string $host): void
{
$this->server->ssh()->exec(
$this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/unlink.sh', [
'username' => $username,
'host' => $host,
@ -106,7 +144,7 @@ public function unlink(string $username, string $host): void
public function runBackup(BackupFile $backupFile): void
{
// backup
$this->server->ssh()->exec(
$this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/backup.sh', [
'file' => $backupFile->name,
'database' => $backupFile->backup->database->name,
@ -115,13 +153,13 @@ public function runBackup(BackupFile $backupFile): void
);
// upload to storage
$upload = $backupFile->backup->storage->provider()->ssh($this->server)->upload(
$upload = $backupFile->backup->storage->provider()->ssh($this->service->server)->upload(
$backupFile->path(),
$backupFile->storagePath(),
);
// cleanup
$this->server->ssh()->exec('rm '.$backupFile->name.'.zip');
$this->service->server->ssh()->exec('rm '.$backupFile->name.'.zip');
$backupFile->size = $upload['size'];
$backupFile->save();
@ -130,12 +168,12 @@ public function runBackup(BackupFile $backupFile): void
public function restoreBackup(BackupFile $backupFile, string $database): void
{
// download
$backupFile->backup->storage->provider()->ssh($this->server)->download(
$backupFile->backup->storage->provider()->ssh($this->service->server)->download(
$backupFile->storagePath(),
$backupFile->name.'.zip',
);
$this->server->ssh()->exec(
$this->service->server->ssh()->exec(
$this->getScript($this->getScriptsDir().'/restore.sh', [
'database' => $database,
'file' => $backupFile->name,

View File

@ -9,4 +9,6 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
sudo systemctl unmask mysql.service
sudo service mysql start

View File

@ -9,4 +9,6 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
sudo systemctl unmask mysql.service
sudo service mysql start

View File

@ -0,0 +1,9 @@
sudo service mysql stop
sudo DEBIAN_FRONTEND=noninteractive apt-get remove mariadb-server mariadb-backup -y
sudo rm -rf /etc/mysql
sudo rm -rf /var/lib/mysql
sudo rm -rf /var/log/mysql
sudo rm -rf /var/run/mysqld
sudo rm -rf /var/run/mysqld/mysqld.sock

View File

@ -1,5 +1,7 @@
sudo DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
sudo systemctl unmask mysql.service
sudo service mysql enable
sudo service mysql start

View File

@ -6,6 +6,8 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
sudo systemctl unmask mysql.service
sudo service mysql enable
sudo service mysql start

View File

@ -0,0 +1,9 @@
sudo service mysql stop
sudo DEBIAN_FRONTEND=noninteractive apt-get remove mysql-server -y
sudo rm -rf /etc/mysql
sudo rm -rf /var/lib/mysql
sudo rm -rf /var/log/mysql
sudo rm -rf /var/run/mysqld
sudo rm -rf /var/run/mysqld/mysqld.sock

View File

@ -0,0 +1,11 @@
sudo service postgresql stop
sudo DEBIAN_FRONTEND=noninteractive apt-get remove postgresql-* -y
sudo rm -rf /etc/postgresql
sudo rm -rf /var/lib/postgresql
sudo rm -rf /var/log/postgresql
sudo rm -rf /var/run/postgresql
sudo rm -rf /var/run/postgresql/postmaster.pid
sudo rm -rf /var/run/postgresql/.s.PGSQL.5432
sudo rm -rf /var/run/postgresql/.s.PGSQL.5432.lock

View File

@ -2,15 +2,8 @@
namespace App\SSH\Services\Firewall;
use App\Models\Service;
use App\SSH\Services\ServiceInterface;
use App\SSH\Services\AbstractService;
abstract class AbstractFirewall implements Firewall, ServiceInterface
abstract class AbstractFirewall extends AbstractService implements Firewall
{
protected Service $service;
public function __construct(Service $service)
{
$this->service = $service;
}
}

View File

@ -14,6 +14,12 @@ public function install(): void
$this->getScript('ufw/install-ufw.sh'),
'install-ufw'
);
$this->service->server->os()->cleanup();
}
public function uninstall(): void
{
//
}
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void

View File

@ -3,20 +3,43 @@
namespace App\SSH\Services\PHP;
use App\Exceptions\SSHCommandError;
use App\Models\Service;
use App\SSH\HasScripts;
use App\SSH\Services\ServiceInterface;
use App\SSH\Services\AbstractService;
use Closure;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class PHP implements ServiceInterface
class PHP extends AbstractService
{
use HasScripts;
protected Service $service;
public function __construct(Service $service)
public function creationRules(array $input): array
{
$this->service = $service;
return [
'version' => [
'required',
Rule::in(config('core.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) {
$hasSite = $this->service->server->sites()
->where('php_version', $this->service->version)
->exists();
if ($hasSite) {
$fail('Some sites are using this PHP version.');
}
},
],
];
}
public function install(): void
@ -29,6 +52,7 @@ public function install(): void
]),
'install-php-'.$this->service->version
);
$this->service->server->os()->cleanup();
}
public function uninstall(): void
@ -39,6 +63,7 @@ public function uninstall(): void
]),
'uninstall-php-'.$this->service->version
);
$this->service->server->os()->cleanup();
}
public function setDefaultCli(): void

View File

@ -2,15 +2,37 @@
namespace App\SSH\Services\ProcessManager;
use App\Models\Service;
use App\SSH\Services\ServiceInterface;
use App\SSH\Services\AbstractService;
use Closure;
abstract class AbstractProcessManager implements ProcessManager, ServiceInterface
abstract class AbstractProcessManager extends AbstractService implements ProcessManager
{
protected Service $service;
public function __construct(Service $service)
public function creationRules(array $input): array
{
$this->service = $service;
return [
'type' => [
'required',
function (string $attribute, mixed $value, Closure $fail) {
$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) {
$hasQueue = $this->service->server->queues()->exists();
if ($hasQueue) {
$fail('You have queue(s) on the server.');
}
},
],
];
}
}

View File

@ -15,6 +15,18 @@ public function install(): void
$this->getScript('supervisor/install-supervisor.sh'),
'install-supervisor'
);
$this->service->server->os()->cleanup();
}
public function uninstall(): void
{
$this->service->server->ssh()->exec(
$this->getScript('supervisor/uninstall-supervisor.sh'),
'uninstall-supervisor'
);
$status = $this->service->server->systemd()->status($this->service->unit);
$this->service->validateInstall($status);
$this->service->server->os()->cleanup();
}
/**

View File

@ -0,0 +1,8 @@
sudo service supervisor stop
sudo DEBIAN_FRONTEND=noninteractive apt-get remove supervisor -y
sudo rm -rf /etc/supervisor
sudo rm -rf /var/log/supervisor
sudo rm -rf /var/run/supervisor
sudo rm -rf /var/run/supervisor/supervisor.sock

View File

@ -2,16 +2,27 @@
namespace App\SSH\Services\Redis;
use App\Models\Service;
use App\SSH\HasScripts;
use App\SSH\Services\ServiceInterface;
use App\SSH\Services\AbstractService;
use Closure;
class Redis implements ServiceInterface
class Redis extends AbstractService
{
use HasScripts;
public function __construct(protected Service $service)
public function creationRules(array $input): array
{
return [
'type' => [
'required',
function (string $attribute, mixed $value, Closure $fail) {
$redisExists = $this->service->server->memoryDatabase();
if ($redisExists) {
$fail('You already have a Redis service on the server.');
}
},
],
];
}
public function install(): void
@ -20,5 +31,17 @@ public function install(): void
$this->getScript('install.sh'),
'install-redis'
);
$status = $this->service->server->systemd()->status($this->service->unit);
$this->service->validateInstall($status);
$this->service->server->os()->cleanup();
}
public function uninstall(): void
{
$this->service->server->ssh()->exec(
$this->getScript('uninstall.sh'),
'uninstall-redis'
);
$this->service->server->os()->cleanup();
}
}

View File

@ -0,0 +1,15 @@
sudo service redis stop
sudo DEBIAN_FRONTEND=noninteractive apt-get remove redis-server -y
sudo rm -rf /etc/redis
sudo rm -rf /var/lib/redis
sudo rm -rf /var/log/redis
sudo rm -rf /var/run/redis
sudo rm -rf /var/run/redis/redis-server.pid
sudo rm -rf /var/run/redis/redis-server.sock
sudo rm -rf /var/run/redis/redis-server.sock
sudo DEBIAN_FRONTEND=noninteractive sudo apt-get autoremove -y
sudo DEBIAN_FRONTEND=noninteractive sudo apt-get autoclean -y

View File

@ -4,5 +4,15 @@
interface ServiceInterface
{
public function creationRules(array $input): array;
public function creationData(array $input): array;
public function deletionRules(): array;
public function data(): array;
public function install(): void;
public function uninstall(): void;
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\SSH\Services\VitoAgent;
use App\Models\Metric;
use App\SSH\HasScripts;
use App\SSH\Services\AbstractService;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\Rule;
use Ramsey\Uuid\Uuid;
class VitoAgent extends AbstractService
{
use HasScripts;
const TAGS_URL = 'https://api.github.com/repos/vitodeploy/agent/tags';
const DOWNLOAD_URL = 'https://github.com/vitodeploy/agent/releases/download/%s';
public function creationRules(array $input): array
{
return [
'type' => [
Rule::unique('services', 'type')->where('server_id', $this->service->server_id),
],
'version' => [
'required',
Rule::in(['latest']),
],
];
}
public function creationData(array $input): array
{
return [
'url' => '',
'secret' => Uuid::uuid4()->toString(),
];
}
public function data(): array
{
return [
'url' => $this->service->type_data['url'] ?? null,
'secret' => $this->service->type_data['secret'] ?? null,
];
}
public function install(): void
{
$tags = Http::get(self::TAGS_URL)->json();
if (empty($tags)) {
throw new \Exception('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(
$this->getScript('install.sh', [
'download_url' => $downloadUrl,
'config_url' => $this->data()['url'],
'config_secret' => $this->data()['secret'],
]),
'install-vito-agent'
);
$status = $this->service->server->systemd()->status($this->service->unit);
$this->service->validateInstall($status);
}
public function uninstall(): void
{
$this->service->server->ssh()->exec(
$this->getScript('uninstall.sh'),
'uninstall-vito-agent'
);
Metric::where('server_id', $this->service->server_id)->delete();
}
}

View File

@ -0,0 +1,53 @@
arch=$(uname -m)
if [ "$arch" == "x86_64" ]; then
executable="vitoagent-linux-amd64"
elif [ "$arch" == "i686" ]; then
executable="vitoagent-linux-amd"
elif [ "$arch" == "armv7l" ]; then
executable="vitoagent-linux-arm"
elif [ "$arch" == "aarch64" ]; then
executable="vitoagent-linux-arm64"
else
executable="vitoagent-linux-amd64"
fi
wget __download_url__/$executable
chmod +x ./$executable
sudo mv ./$executable /usr/local/bin/vito-agent
# create service
export VITO_AGENT_SERVICE="
[Unit]
Description=Vito Agent
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/vito-agent
Restart=on-failure
[Install]
WantedBy=multi-user.target
"
echo "${VITO_AGENT_SERVICE}" | sudo tee /etc/systemd/system/vito-agent.service
sudo mkdir -p /etc/vito-agent
export VITO_AGENT_CONFIG="
{
\"url\": \"__config_url__\",
\"secret\": \"__config_secret__\"
}
"
echo "${VITO_AGENT_CONFIG}" | sudo tee /etc/vito-agent/config.json
sudo systemctl daemon-reload
sudo systemctl enable vito-agent
sudo systemctl start vito-agent
echo "Vito Agent installed successfully"

View File

@ -0,0 +1,13 @@
sudo service vito-agent stop
sudo systemctl disable vito-agent
sudo rm -f /usr/local/bin/vito-agent
sudo rm -f /etc/systemd/system/vito-agent.service
sudo rm -rf /etc/vito-agent
sudo systemctl daemon-reload
echo "Vito Agent uninstalled successfully"

View File

@ -2,12 +2,8 @@
namespace App\SSH\Services\Webserver;
use App\Models\Service;
use App\SSH\Services\ServiceInterface;
use App\SSH\Services\AbstractService;
abstract class AbstractWebserver implements ServiceInterface, Webserver
abstract class AbstractWebserver extends AbstractService implements Webserver
{
public function __construct(protected Service $service)
{
}
}

View File

@ -6,6 +6,7 @@
use App\Models\Site;
use App\Models\Ssl;
use App\SSH\HasScripts;
use Closure;
use Illuminate\Support\Str;
use Throwable;
@ -23,6 +24,31 @@ public function install(): void
]),
'install-nginx'
);
$this->service->server->os()->cleanup();
}
public function deletionRules(): array
{
return [
'service' => [
function (string $attribute, mixed $value, Closure $fail) {
$hasSite = $this->service->server->sites()
->exists();
if ($hasSite) {
$fail('Cannot uninstall webserver while you have websites using it.');
}
},
],
];
}
public function uninstall(): void
{
$this->service->server->ssh()->exec(
$this->getScript('nginx/uninstall-nginx.sh'),
'uninstall-nginx'
);
$this->service->server->os()->cleanup();
}
public function createVHost(Site $site): void

View File

@ -0,0 +1,12 @@
sudo service nginx stop
sudo DEBIAN_FRONTEND=noninteractive apt-get purge nginx nginx-common nginx-full -y
sudo rm -rf /etc/nginx
sudo rm -rf /var/log/nginx
sudo rm -rf /var/lib/nginx
sudo rm -rf /var/cache/nginx
sudo rm -rf /usr/share/nginx
sudo rm -rf /etc/systemd/system/nginx.service
sudo systemctl daemon-reload

View File

@ -4,6 +4,7 @@
use App\Enums\ServiceStatus;
use App\Models\Server;
use App\SSH\Services\PHP\PHP;
abstract class AbstractType implements ServerType
{
@ -31,7 +32,9 @@ public function install(): void
$service->update(['status' => ServiceStatus::READY]);
if ($service->type == 'php') {
$this->progress($currentProgress, 'installing-composer');
$service->handler()->installComposer();
/** @var PHP $handler */
$handler = $service->handler();
$handler->installComposer();
}
}
$this->progress(100, 'finishing');

View File

@ -13,7 +13,7 @@ public function createRules(array $input): array
],
'php' => [
'required',
'in:'.implode(',', config('core.php_versions')),
'in:none,'.implode(',', config('core.php_versions')),
],
'database' => [
'required',

View File

@ -34,3 +34,12 @@ function vito_version(): string
{
return exec('git describe --tags');
}
function convert_time_format($string): string
{
$string = preg_replace('/(\d+)m/', '$1 minutes', $string);
$string = preg_replace('/(\d+)s/', '$1 seconds', $string);
$string = preg_replace('/(\d+)d/', '$1 days', $string);
return preg_replace('/(\d+)h/', '$1 hours', $string);
}