mirror of
https://github.com/vitodeploy/vito.git
synced 2025-04-20 02:11:36 +00:00
Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
fb651ab5ce | ||
|
c2625a7352 | ||
|
b72a2ddb1c | ||
|
0e8e6ef56f | ||
|
39fa25aee7 | ||
|
945c2e75c0 | ||
|
82933e29ff | ||
|
82c1f36ef6 | ||
|
e06d23b31a | ||
|
f0e7faa0e7 | ||
|
319fdb44e7 | ||
|
b62c40c97d | ||
|
e39e8c17a2 | ||
|
1391eb32d8 | ||
|
7f5e68e131 | ||
|
431da1b728 | ||
|
8c487a64fa | ||
|
a67e586a5d | ||
|
960db714b7 | ||
|
7da0221ccb | ||
|
9ac5f9ebb3 | ||
|
ed8965b92b | ||
|
9473d198e1 | ||
|
55269dbcde | ||
|
3d67153912 | ||
|
11e3b167cc | ||
|
ad027eb033 | ||
|
e031bafba5 | ||
|
b5c8d99ef8 | ||
|
109d644ad8 | ||
|
5ccbab74b1 | ||
|
7d367465ff | ||
|
eec83f577c | ||
|
fd77368cf3 | ||
|
a862a603f2 | ||
|
3b42f93654 | ||
|
661292df5e | ||
|
0cfb938320 | ||
|
dd4a3d30c0 | ||
|
2b849c888e | ||
|
d9a791755e | ||
|
e3ea8f975f | ||
|
de468ae1ba | ||
|
30ef8ad5eb | ||
|
88223a61f9 | ||
|
1067a5fd33 | ||
|
4361305206 | ||
|
fe331fd2b3 | ||
|
bbe3ca802d | ||
|
765ac21916 | ||
|
016886f307 | ||
|
179aefefac |
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Feature request
|
||||||
|
url: https://github.com/vitodeploy/vito/discussions/new?category=ideas
|
||||||
|
about: Share ideas for new features
|
||||||
|
- name: Support
|
||||||
|
url: https://github.com/vitodeploy/vito/discussions/new?category=q-a
|
||||||
|
about: Ask the community for help
|
||||||
|
- name: Discord
|
||||||
|
url: https://discord.gg/uZeeHZZnm5
|
||||||
|
about: Join the community
|
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: feature
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
To request a feature or suggest an idea please add it to the feedback boards
|
|
||||||
|
|
||||||
https://vitodeploy.featurebase.app/
|
|
@ -34,8 +34,7 @@ ## Useful Links
|
|||||||
- [Documentation](https://vitodeploy.com)
|
- [Documentation](https://vitodeploy.com)
|
||||||
- [Install on Server](https://vitodeploy.com/introduction/installation.html#install-on-vps-recommended)
|
- [Install on Server](https://vitodeploy.com/introduction/installation.html#install-on-vps-recommended)
|
||||||
- [Install via Docker](https://vitodeploy.com/introduction/installation.html#install-via-docker)
|
- [Install via Docker](https://vitodeploy.com/introduction/installation.html#install-via-docker)
|
||||||
- [Feedbacks](https://vitodeploy.featurebase.app)
|
- [Roadmap](https://github.com/orgs/vitodeploy/projects/5)
|
||||||
- [Roadmap](https://vitodeploy.featurebase.app/roadmap)
|
|
||||||
- [Video Demo](https://youtu.be/AbmUOBDOc28)
|
- [Video Demo](https://youtu.be/AbmUOBDOc28)
|
||||||
- [Discord](https://discord.gg/uZeeHZZnm5)
|
- [Discord](https://discord.gg/uZeeHZZnm5)
|
||||||
- [Contribution](/CONTRIBUTING.md)
|
- [Contribution](/CONTRIBUTING.md)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\CronJob;
|
namespace App\Actions\CronJob;
|
||||||
|
|
||||||
|
use App\Enums\CronjobStatus;
|
||||||
use App\Models\CronJob;
|
use App\Models\CronJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
|
||||||
@ -10,7 +11,9 @@ class DeleteCronJob
|
|||||||
public function delete(Server $server, CronJob $cronJob): void
|
public function delete(Server $server, CronJob $cronJob): void
|
||||||
{
|
{
|
||||||
$user = $cronJob->user;
|
$user = $cronJob->user;
|
||||||
$cronJob->delete();
|
$cronJob->status = CronjobStatus::DELETING;
|
||||||
|
$cronJob->save();
|
||||||
$server->cron()->update($cronJob->user, CronJob::crontab($server, $user));
|
$server->cron()->update($cronJob->user, CronJob::crontab($server, $user));
|
||||||
|
$cronJob->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
app/Actions/CronJob/DisableCronJob.php
Executable file
20
app/Actions/CronJob/DisableCronJob.php
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\CronJob;
|
||||||
|
|
||||||
|
use App\Enums\CronjobStatus;
|
||||||
|
use App\Models\CronJob;
|
||||||
|
use App\Models\Server;
|
||||||
|
|
||||||
|
class DisableCronJob
|
||||||
|
{
|
||||||
|
public function disable(Server $server, CronJob $cronJob): void
|
||||||
|
{
|
||||||
|
$cronJob->status = CronjobStatus::DISABLING;
|
||||||
|
$cronJob->save();
|
||||||
|
|
||||||
|
$server->cron()->update($cronJob->user, CronJob::crontab($server, $cronJob->user));
|
||||||
|
$cronJob->status = CronjobStatus::DISABLED;
|
||||||
|
$cronJob->save();
|
||||||
|
}
|
||||||
|
}
|
20
app/Actions/CronJob/EnableCronJob.php
Executable file
20
app/Actions/CronJob/EnableCronJob.php
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\CronJob;
|
||||||
|
|
||||||
|
use App\Enums\CronjobStatus;
|
||||||
|
use App\Models\CronJob;
|
||||||
|
use App\Models\Server;
|
||||||
|
|
||||||
|
class EnableCronJob
|
||||||
|
{
|
||||||
|
public function enable(Server $server, CronJob $cronJob): void
|
||||||
|
{
|
||||||
|
$cronJob->status = CronjobStatus::ENABLING;
|
||||||
|
$cronJob->save();
|
||||||
|
|
||||||
|
$server->cron()->update($cronJob->user, CronJob::crontab($server, $cronJob->user));
|
||||||
|
$cronJob->status = CronjobStatus::READY;
|
||||||
|
$cronJob->save();
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,9 @@ public function create(Server $server, array $input): Database
|
|||||||
'server_id' => $server->id,
|
'server_id' => $server->id,
|
||||||
'name' => $input['name'],
|
'name' => $input['name'],
|
||||||
]);
|
]);
|
||||||
$server->database()->handler()->create($database->name);
|
/** @var \App\SSH\Services\Database\Database */
|
||||||
|
$databaseHandler = $server->database()->handler();
|
||||||
|
$databaseHandler->create($database->name);
|
||||||
$database->status = DatabaseStatus::READY;
|
$database->status = DatabaseStatus::READY;
|
||||||
$database->save();
|
$database->save();
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ private function getInterval(array $input): Expression
|
|||||||
)->diffInHours();
|
)->diffInHours();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($periodInHours <= 1) {
|
if (abs($periodInHours) <= 1) {
|
||||||
return DB::raw("strftime('%Y-%m-%d %H:%M:00', created_at) as date_interval");
|
return DB::raw("strftime('%Y-%m-%d %H:%M:00', created_at) as date_interval");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ public function add(User $user, array $input): void
|
|||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'provider' => $input['provider'],
|
'provider' => $input['provider'],
|
||||||
'label' => $input['label'],
|
'label' => $input['label'],
|
||||||
|
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
|
||||||
]);
|
]);
|
||||||
$this->validateType($channel, $input);
|
$this->validateType($channel, $input);
|
||||||
$channel->data = $channel->provider()->createData($input);
|
$channel->data = $channel->provider()->createData($input);
|
||||||
|
34
app/Actions/NotificationChannels/EditChannel.php
Normal file
34
app/Actions/NotificationChannels/EditChannel.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\NotificationChannels;
|
||||||
|
|
||||||
|
use App\Models\NotificationChannel;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class EditChannel
|
||||||
|
{
|
||||||
|
public function edit(NotificationChannel $notificationChannel, User $user, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$notificationChannel->label = $input['label'];
|
||||||
|
$notificationChannel->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||||
|
|
||||||
|
$notificationChannel->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'label' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
Validator::make($input, $rules)->validate();
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Actions\PHP;
|
namespace App\Actions\PHP;
|
||||||
|
|
||||||
|
use App\Enums\PHPIniType;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\SSH\Services\PHP\PHP;
|
use App\SSH\Services\PHP\PHP;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
class GetPHPIni
|
class GetPHPIni
|
||||||
@ -18,7 +21,7 @@ public function getIni(Server $server, array $input): string
|
|||||||
/** @var PHP $handler */
|
/** @var PHP $handler */
|
||||||
$handler = $php->handler();
|
$handler = $php->handler();
|
||||||
|
|
||||||
return $handler->getPHPIni();
|
return $handler->getPHPIni($input['type']);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
throw ValidationException::withMessages(
|
throw ValidationException::withMessages(
|
||||||
['ini' => $e->getMessage()]
|
['ini' => $e->getMessage()]
|
||||||
@ -28,6 +31,13 @@ public function getIni(Server $server, array $input): string
|
|||||||
|
|
||||||
public function validate(Server $server, array $input): void
|
public function validate(Server $server, array $input): void
|
||||||
{
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'type' => [
|
||||||
|
'required',
|
||||||
|
Rule::in([PHPIniType::CLI, PHPIniType::FPM]),
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
|
||||||
if (! isset($input['version']) || ! in_array($input['version'], $server->installedPHPVersions())) {
|
if (! isset($input['version']) || ! in_array($input['version'], $server->installedPHPVersions())) {
|
||||||
throw ValidationException::withMessages(
|
throw ValidationException::withMessages(
|
||||||
['version' => __('This version is not installed')]
|
['version' => __('This version is not installed')]
|
||||||
|
@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Actions\PHP;
|
namespace App\Actions\PHP;
|
||||||
|
|
||||||
|
use App\Enums\PHPIniType;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Filesystem\FilesystemAdapter;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
@ -22,19 +25,19 @@ public function update(Server $server, array $input): void
|
|||||||
|
|
||||||
$tmpName = Str::random(10).strtotime('now');
|
$tmpName = Str::random(10).strtotime('now');
|
||||||
try {
|
try {
|
||||||
/** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */
|
/** @var FilesystemAdapter $storageDisk */
|
||||||
$storageDisk = Storage::disk('local');
|
$storageDisk = Storage::disk('local');
|
||||||
|
|
||||||
$storageDisk->put($tmpName, $input['ini']);
|
$storageDisk->put($tmpName, $input['ini']);
|
||||||
$service->server->ssh('root')->upload(
|
$service->server->ssh('root')->upload(
|
||||||
$storageDisk->path($tmpName),
|
$storageDisk->path($tmpName),
|
||||||
"/etc/php/$service->version/cli/php.ini"
|
sprintf('/etc/php/%s/%s/php.ini', $service->version, $input['type'])
|
||||||
);
|
);
|
||||||
$this->deleteTempFile($tmpName);
|
$this->deleteTempFile($tmpName);
|
||||||
} catch (Throwable) {
|
} catch (Throwable) {
|
||||||
$this->deleteTempFile($tmpName);
|
$this->deleteTempFile($tmpName);
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
'ini' => __("Couldn't update php.ini file!"),
|
'ini' => __("Couldn't update php.ini (:type) file!", ['type' => $input['type']]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +59,10 @@ public function validate(Server $server, array $input): void
|
|||||||
'string',
|
'string',
|
||||||
],
|
],
|
||||||
'version' => 'required|string',
|
'version' => 'required|string',
|
||||||
|
'type' => [
|
||||||
|
'required',
|
||||||
|
Rule::in([PHPIniType::CLI, PHPIniType::FPM]),
|
||||||
|
],
|
||||||
])->validate();
|
])->validate();
|
||||||
|
|
||||||
if (! in_array($input['version'], $server->installedPHPVersions())) {
|
if (! in_array($input['version'], $server->installedPHPVersions())) {
|
||||||
|
@ -14,7 +14,7 @@ public function create(User $user, array $input): Project
|
|||||||
$input['name'] = strtolower($input['name']);
|
$input['name'] = strtolower($input['name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->validate($user, $input);
|
$this->validate($input);
|
||||||
|
|
||||||
$project = new Project([
|
$project = new Project([
|
||||||
'name' => $input['name'],
|
'name' => $input['name'],
|
||||||
@ -22,10 +22,12 @@ public function create(User $user, array $input): Project
|
|||||||
|
|
||||||
$project->save();
|
$project->save();
|
||||||
|
|
||||||
|
$project->users()->attach($user);
|
||||||
|
|
||||||
return $project;
|
return $project;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function validate(User $user, array $input): void
|
private function validate(array $input): void
|
||||||
{
|
{
|
||||||
Validator::make($input, [
|
Validator::make($input, [
|
||||||
'name' => [
|
'name' => [
|
||||||
|
@ -17,11 +17,15 @@ public function delete(User $user, Project $project): void
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($user->current_project_id == $project->id) {
|
if ($user->current_project_id == $project->id) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'project' => __('Cannot delete your current project.'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/** @var Project $randomProject */
|
/** @var Project $randomProject */
|
||||||
$randomProject = $user->projects()->where('id', '!=', $project->id)->first();
|
$randomProject = $user->projects()->where('project_id', '!=', $project->id)->first();
|
||||||
$user->current_project_id = $randomProject->id;
|
$user->current_project_id = $randomProject->id;
|
||||||
$user->save();
|
$user->save();
|
||||||
}
|
|
||||||
|
|
||||||
$project->delete();
|
$project->delete();
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
use App\Enums\SslType;
|
use App\Enums\SslType;
|
||||||
use App\Models\Site;
|
use App\Models\Site;
|
||||||
use App\Models\Ssl;
|
use App\Models\Ssl;
|
||||||
|
use App\SSH\Services\Webserver\Webserver;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
@ -27,14 +28,23 @@ public function create(Site $site, array $input): void
|
|||||||
'expires_at' => $input['type'] === SslType::LETSENCRYPT ? now()->addMonths(3) : $input['expires_at'],
|
'expires_at' => $input['type'] === SslType::LETSENCRYPT ? now()->addMonths(3) : $input['expires_at'],
|
||||||
'status' => SslStatus::CREATING,
|
'status' => SslStatus::CREATING,
|
||||||
]);
|
]);
|
||||||
|
$ssl->domains = [$site->domain];
|
||||||
|
if (isset($input['aliases']) && $input['aliases']) {
|
||||||
|
$ssl->domains = array_merge($ssl->domains, $site->aliases);
|
||||||
|
}
|
||||||
$ssl->save();
|
$ssl->save();
|
||||||
|
|
||||||
dispatch(function () use ($site, $ssl) {
|
dispatch(function () use ($site, $ssl) {
|
||||||
$site->server->webserver()->handler()->setupSSL($ssl);
|
/** @var Webserver $webserver */
|
||||||
|
$webserver = $site->server->webserver()->handler();
|
||||||
|
$webserver->setupSSL($ssl);
|
||||||
$ssl->status = SslStatus::CREATED;
|
$ssl->status = SslStatus::CREATED;
|
||||||
$ssl->save();
|
$ssl->save();
|
||||||
$site->type()->edit();
|
$site->type()->edit();
|
||||||
});
|
})->catch(function () use ($ssl) {
|
||||||
|
$ssl->status = SslStatus::FAILED;
|
||||||
|
$ssl->save();
|
||||||
|
})->onConnection('ssh');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
32
app/Actions/Script/CreateScript.php
Normal file
32
app/Actions/Script/CreateScript.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Script;
|
||||||
|
|
||||||
|
use App\Models\Script;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class CreateScript
|
||||||
|
{
|
||||||
|
public function create(User $user, array $input): Script
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$script = new Script([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'name' => $input['name'],
|
||||||
|
'content' => $input['content'],
|
||||||
|
]);
|
||||||
|
$script->save();
|
||||||
|
|
||||||
|
return $script;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'content' => ['required', 'string'],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
28
app/Actions/Script/EditScript.php
Normal file
28
app/Actions/Script/EditScript.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Script;
|
||||||
|
|
||||||
|
use App\Models\Script;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class EditScript
|
||||||
|
{
|
||||||
|
public function edit(Script $script, array $input): Script
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$script->name = $input['name'];
|
||||||
|
$script->content = $input['content'];
|
||||||
|
$script->save();
|
||||||
|
|
||||||
|
return $script;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'content' => ['required', 'string'],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
62
app/Actions/Script/ExecuteScript.php
Normal file
62
app/Actions/Script/ExecuteScript.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Script;
|
||||||
|
|
||||||
|
use App\Enums\ScriptExecutionStatus;
|
||||||
|
use App\Models\Script;
|
||||||
|
use App\Models\ScriptExecution;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\ServerLog;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ExecuteScript
|
||||||
|
{
|
||||||
|
public function execute(Script $script, Server $server, array $input): ScriptExecution
|
||||||
|
{
|
||||||
|
$this->validate($server, $input);
|
||||||
|
|
||||||
|
$execution = new ScriptExecution([
|
||||||
|
'script_id' => $script->id,
|
||||||
|
'user' => $input['user'],
|
||||||
|
'variables' => $input['variables'] ?? [],
|
||||||
|
'status' => ScriptExecutionStatus::EXECUTING,
|
||||||
|
]);
|
||||||
|
$execution->save();
|
||||||
|
|
||||||
|
dispatch(function () use ($execution, $server, $script) {
|
||||||
|
$content = $execution->getContent();
|
||||||
|
$log = ServerLog::make($server, 'script-'.$script->id.'-'.strtotime('now'));
|
||||||
|
$log->save();
|
||||||
|
$execution->server_log_id = $log->id;
|
||||||
|
$execution->save();
|
||||||
|
$server->os()->runScript('~/', $content, $log, $execution->user);
|
||||||
|
$execution->status = ScriptExecutionStatus::COMPLETED;
|
||||||
|
$execution->save();
|
||||||
|
})->catch(function () use ($execution) {
|
||||||
|
$execution->status = ScriptExecutionStatus::FAILED;
|
||||||
|
$execution->save();
|
||||||
|
})->onConnection('ssh');
|
||||||
|
|
||||||
|
return $execution;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(Server $server, array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'user' => [
|
||||||
|
'required',
|
||||||
|
Rule::in([
|
||||||
|
'root',
|
||||||
|
$server->ssh_user,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
'variables' => 'array',
|
||||||
|
'variables.*' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:255',
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Enums\ServerStatus;
|
||||||
use App\Facades\Notifier;
|
use App\Facades\Notifier;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\ServerDisconnected;
|
use App\Notifications\ServerDisconnected;
|
||||||
@ -15,12 +16,12 @@ public function check(Server $server): Server
|
|||||||
try {
|
try {
|
||||||
$server->ssh()->connect();
|
$server->ssh()->connect();
|
||||||
$server->refresh();
|
$server->refresh();
|
||||||
if ($status == 'disconnected') {
|
if (in_array($status, [ServerStatus::DISCONNECTED, ServerStatus::UPDATING])) {
|
||||||
$server->status = 'ready';
|
$server->status = ServerStatus::READY;
|
||||||
$server->save();
|
$server->save();
|
||||||
}
|
}
|
||||||
} catch (Throwable) {
|
} catch (Throwable) {
|
||||||
$server->status = 'disconnected';
|
$server->status = ServerStatus::DISCONNECTED;
|
||||||
$server->save();
|
$server->save();
|
||||||
Notifier::send($server, new ServerDisconnected($server));
|
Notifier::send($server, new ServerDisconnected($server));
|
||||||
}
|
}
|
||||||
|
25
app/Actions/Server/Update.php
Normal file
25
app/Actions/Server/Update.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Enums\ServerStatus;
|
||||||
|
use App\Facades\Notifier;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Notifications\ServerUpdateFailed;
|
||||||
|
|
||||||
|
class Update
|
||||||
|
{
|
||||||
|
public function update(Server $server): void
|
||||||
|
{
|
||||||
|
$server->status = ServerStatus::UPDATING;
|
||||||
|
$server->save();
|
||||||
|
dispatch(function () use ($server) {
|
||||||
|
$server->os()->upgrade();
|
||||||
|
$server->checkConnection();
|
||||||
|
$server->checkForUpdates();
|
||||||
|
})->catch(function () use ($server) {
|
||||||
|
Notifier::send($server, new ServerUpdateFailed($server));
|
||||||
|
$server->checkConnection();
|
||||||
|
})->onConnection('ssh');
|
||||||
|
}
|
||||||
|
}
|
@ -38,6 +38,7 @@ public function create(User $user, array $input): ServerProvider
|
|||||||
$serverProvider->profile = $input['name'];
|
$serverProvider->profile = $input['name'];
|
||||||
$serverProvider->provider = $input['provider'];
|
$serverProvider->provider = $input['provider'];
|
||||||
$serverProvider->credentials = $provider->credentialData($input);
|
$serverProvider->credentials = $provider->credentialData($input);
|
||||||
|
$serverProvider->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||||
$serverProvider->save();
|
$serverProvider->save();
|
||||||
|
|
||||||
return $serverProvider;
|
return $serverProvider;
|
||||||
|
34
app/Actions/ServerProvider/EditServerProvider.php
Normal file
34
app/Actions/ServerProvider/EditServerProvider.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\ServerProvider;
|
||||||
|
|
||||||
|
use App\Models\ServerProvider;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class EditServerProvider
|
||||||
|
{
|
||||||
|
public function edit(ServerProvider $serverProvider, User $user, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$serverProvider->profile = $input['name'];
|
||||||
|
$serverProvider->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||||
|
|
||||||
|
$serverProvider->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'name' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
Validator::make($input, $rules)->validate();
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@
|
|||||||
class CreateSite
|
class CreateSite
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @throws ValidationException
|
* @throws SourceControlIsNotConnected
|
||||||
*/
|
*/
|
||||||
public function create(Server $server, array $input): Site
|
public function create(Server $server, array $input): Site
|
||||||
{
|
{
|
||||||
@ -33,7 +33,7 @@ public function create(Server $server, array $input): Site
|
|||||||
'server_id' => $server->id,
|
'server_id' => $server->id,
|
||||||
'type' => $input['type'],
|
'type' => $input['type'],
|
||||||
'domain' => $input['domain'],
|
'domain' => $input['domain'],
|
||||||
'aliases' => isset($input['alias']) ? [$input['alias']] : [],
|
'aliases' => $input['aliases'] ?? [],
|
||||||
'path' => '/home/'.$server->getSshUser().'/'.$input['domain'],
|
'path' => '/home/'.$server->getSshUser().'/'.$input['domain'],
|
||||||
'status' => SiteStatus::INSTALLING,
|
'status' => SiteStatus::INSTALLING,
|
||||||
]);
|
]);
|
||||||
@ -115,7 +115,7 @@ private function validateInputs(Server $server, array $input): void
|
|||||||
return $query->where('server_id', $server->id);
|
return $query->where('server_id', $server->id);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
'alias' => [
|
'aliases.*' => [
|
||||||
new DomainRule(),
|
new DomainRule(),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -30,7 +30,7 @@ public function run(Site $site): Deployment
|
|||||||
'deployment_script_id' => $site->deploymentScript->id,
|
'deployment_script_id' => $site->deploymentScript->id,
|
||||||
'status' => DeploymentStatus::DEPLOYING,
|
'status' => DeploymentStatus::DEPLOYING,
|
||||||
]);
|
]);
|
||||||
$lastCommit = $site->sourceControl()->provider()->getLastCommit($site->repository, $site->branch);
|
$lastCommit = $site->sourceControl()?->provider()?->getLastCommit($site->repository, $site->branch);
|
||||||
if ($lastCommit) {
|
if ($lastCommit) {
|
||||||
$deployment->commit_id = $lastCommit['commit_id'];
|
$deployment->commit_id = $lastCommit['commit_id'];
|
||||||
$deployment->commit_data = $lastCommit['commit_data'];
|
$deployment->commit_data = $lastCommit['commit_data'];
|
||||||
@ -44,7 +44,12 @@ public function run(Site $site): Deployment
|
|||||||
$log->save();
|
$log->save();
|
||||||
$deployment->log_id = $log->id;
|
$deployment->log_id = $log->id;
|
||||||
$deployment->save();
|
$deployment->save();
|
||||||
$site->server->os()->runScript($site->path, $site->deploymentScript->content, $log);
|
$site->server->os()->runScript(
|
||||||
|
path: $site->path,
|
||||||
|
script: $site->deploymentScript->content,
|
||||||
|
serverLog: $log,
|
||||||
|
variables: $site->environmentVariables($deployment)
|
||||||
|
);
|
||||||
$deployment->status = DeploymentStatus::FINISHED;
|
$deployment->status = DeploymentStatus::FINISHED;
|
||||||
$deployment->save();
|
$deployment->save();
|
||||||
})->catch(function () use ($deployment) {
|
})->catch(function () use ($deployment) {
|
||||||
|
33
app/Actions/Site/UpdateAliases.php
Normal file
33
app/Actions/Site/UpdateAliases.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Site;
|
||||||
|
|
||||||
|
use App\Models\Site;
|
||||||
|
use App\SSH\Services\Webserver\Webserver;
|
||||||
|
use App\ValidationRules\DomainRule;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class UpdateAliases
|
||||||
|
{
|
||||||
|
public function update(Site $site, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$site->aliases = $input['aliases'] ?? [];
|
||||||
|
|
||||||
|
/** @var Webserver $webserver */
|
||||||
|
$webserver = $site->server->webserver()->handler();
|
||||||
|
$webserver->updateVHost($site, ! $site->hasSSL());
|
||||||
|
|
||||||
|
$site->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'aliases.*' => [
|
||||||
|
new DomainRule(),
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Actions\Site;
|
namespace App\Actions\Site;
|
||||||
|
|
||||||
|
use App\Exceptions\SSHUploadFailed;
|
||||||
use App\Models\Site;
|
use App\Models\Site;
|
||||||
|
|
||||||
class UpdateEnv
|
class UpdateEnv
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @throws SSHUploadFailed
|
||||||
|
*/
|
||||||
public function update(Site $site, array $input): void
|
public function update(Site $site, array $input): void
|
||||||
{
|
{
|
||||||
$site->server->os()->editFile(
|
$site->server->os()->editFile(
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Actions\SourceControl;
|
namespace App\Actions\SourceControl;
|
||||||
|
|
||||||
use App\Models\SourceControl;
|
use App\Models\SourceControl;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
@ -10,7 +11,7 @@
|
|||||||
|
|
||||||
class ConnectSourceControl
|
class ConnectSourceControl
|
||||||
{
|
{
|
||||||
public function connect(array $input): void
|
public function connect(User $user, array $input): void
|
||||||
{
|
{
|
||||||
$this->validate($input);
|
$this->validate($input);
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ public function connect(array $input): void
|
|||||||
'provider' => $input['provider'],
|
'provider' => $input['provider'],
|
||||||
'profile' => $input['name'],
|
'profile' => $input['name'],
|
||||||
'url' => Arr::has($input, 'url') ? $input['url'] : null,
|
'url' => Arr::has($input, 'url') ? $input['url'] : null,
|
||||||
|
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->validateProvider($sourceControl, $input);
|
$this->validateProvider($sourceControl, $input);
|
||||||
|
54
app/Actions/SourceControl/EditSourceControl.php
Normal file
54
app/Actions/SourceControl/EditSourceControl.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\SourceControl;
|
||||||
|
|
||||||
|
use App\Models\SourceControl;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class EditSourceControl
|
||||||
|
{
|
||||||
|
public function edit(SourceControl $sourceControl, User $user, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$sourceControl->profile = $input['name'];
|
||||||
|
$sourceControl->url = $input['url'] ?? null;
|
||||||
|
$sourceControl->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||||
|
|
||||||
|
$this->validateProvider($sourceControl, $input);
|
||||||
|
|
||||||
|
$sourceControl->provider_data = $sourceControl->provider()->createData($input);
|
||||||
|
|
||||||
|
if (! $sourceControl->provider()->connect()) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'token' => __('Cannot connect to :provider or invalid token!', ['provider' => $sourceControl->provider]
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sourceControl->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'name' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
Validator::make($input, $rules)->validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
private function validateProvider(SourceControl $sourceControl, array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, $sourceControl->provider()->createRules($input))->validate();
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ public function create(User $user, array $input): void
|
|||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'provider' => $input['provider'],
|
'provider' => $input['provider'],
|
||||||
'profile' => $input['name'],
|
'profile' => $input['name'],
|
||||||
|
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->validateProvider($input, $storageProvider->provider()->validationRules());
|
$this->validateProvider($input, $storageProvider->provider()->validationRules());
|
||||||
|
34
app/Actions/StorageProvider/EditStorageProvider.php
Normal file
34
app/Actions/StorageProvider/EditStorageProvider.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\StorageProvider;
|
||||||
|
|
||||||
|
use App\Models\StorageProvider;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class EditStorageProvider
|
||||||
|
{
|
||||||
|
public function edit(StorageProvider $storageProvider, User $user, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$storageProvider->profile = $input['name'];
|
||||||
|
$storageProvider->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||||
|
|
||||||
|
$storageProvider->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'name' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
Validator::make($input, $rules)->validate();
|
||||||
|
}
|
||||||
|
}
|
58
app/Actions/Tag/AttachTag.php
Normal file
58
app/Actions/Tag/AttachTag.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Tag;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Site;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class AttachTag
|
||||||
|
{
|
||||||
|
public function attach(User $user, array $input): Tag
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
/** @var Server|Site $taggable */
|
||||||
|
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);
|
||||||
|
|
||||||
|
$tag = Tag::query()->where('name', $input['name'])->first();
|
||||||
|
if ($tag) {
|
||||||
|
if (! $taggable->tags->contains($tag->id)) {
|
||||||
|
$taggable->tags()->attach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tag = new Tag([
|
||||||
|
'project_id' => $user->currentProject->id,
|
||||||
|
'name' => $input['name'],
|
||||||
|
'color' => config('core.tag_colors')[array_rand(config('core.tag_colors'))],
|
||||||
|
]);
|
||||||
|
$tag->save();
|
||||||
|
|
||||||
|
$taggable->tags()->attach($tag->id);
|
||||||
|
|
||||||
|
return $tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'name' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
|
'taggable_id' => [
|
||||||
|
'required',
|
||||||
|
'integer',
|
||||||
|
],
|
||||||
|
'taggable_type' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(config('core.taggable_types')),
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
49
app/Actions/Tag/CreateTag.php
Normal file
49
app/Actions/Tag/CreateTag.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Tag;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class CreateTag
|
||||||
|
{
|
||||||
|
public function create(User $user, array $input): Tag
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$tag = Tag::query()
|
||||||
|
->where('project_id', $user->current_project_id)
|
||||||
|
->where('name', $input['name'])
|
||||||
|
->first();
|
||||||
|
if ($tag) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'name' => ['Tag with this name already exists.'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tag = new Tag([
|
||||||
|
'project_id' => $user->currentProject->id,
|
||||||
|
'name' => $input['name'],
|
||||||
|
'color' => $input['color'],
|
||||||
|
]);
|
||||||
|
$tag->save();
|
||||||
|
|
||||||
|
return $tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'name' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
|
'color' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(config('core.tag_colors')),
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
15
app/Actions/Tag/DeleteTag.php
Normal file
15
app/Actions/Tag/DeleteTag.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Tag;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class DeleteTag
|
||||||
|
{
|
||||||
|
public function delete(Tag $tag): void
|
||||||
|
{
|
||||||
|
DB::table('taggables')->where('tag_id', $tag->id)->delete();
|
||||||
|
$tag->delete();
|
||||||
|
}
|
||||||
|
}
|
36
app/Actions/Tag/DetachTag.php
Normal file
36
app/Actions/Tag/DetachTag.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Tag;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Site;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class DetachTag
|
||||||
|
{
|
||||||
|
public function detach(Tag $tag, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
/** @var Server|Site $taggable */
|
||||||
|
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);
|
||||||
|
|
||||||
|
$taggable->tags()->detach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'taggable_id' => [
|
||||||
|
'required',
|
||||||
|
'integer',
|
||||||
|
],
|
||||||
|
'taggable_type' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(config('core.taggable_types')),
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
38
app/Actions/Tag/EditTag.php
Normal file
38
app/Actions/Tag/EditTag.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Tag;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class EditTag
|
||||||
|
{
|
||||||
|
public function edit(Tag $tag, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$tag->name = $input['name'];
|
||||||
|
$tag->color = $input['color'];
|
||||||
|
|
||||||
|
$tag->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'name' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
|
'color' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(config('core.tag_colors')),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
Validator::make($input, $rules)->validate();
|
||||||
|
}
|
||||||
|
}
|
@ -9,4 +9,10 @@ final class CronjobStatus
|
|||||||
const READY = 'ready';
|
const READY = 'ready';
|
||||||
|
|
||||||
const DELETING = 'deleting';
|
const DELETING = 'deleting';
|
||||||
|
|
||||||
|
const ENABLING = 'enabling';
|
||||||
|
|
||||||
|
const DISABLING = 'disabling';
|
||||||
|
|
||||||
|
const DISABLED = 'disabled';
|
||||||
}
|
}
|
||||||
|
@ -9,4 +9,6 @@ final class OperatingSystem
|
|||||||
const UBUNTU20 = 'ubuntu_20';
|
const UBUNTU20 = 'ubuntu_20';
|
||||||
|
|
||||||
const UBUNTU22 = 'ubuntu_22';
|
const UBUNTU22 = 'ubuntu_22';
|
||||||
|
|
||||||
|
const UBUNTU24 = 'ubuntu_24';
|
||||||
}
|
}
|
||||||
|
10
app/Enums/PHPIniType.php
Normal file
10
app/Enums/PHPIniType.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
final class PHPIniType
|
||||||
|
{
|
||||||
|
const CLI = 'cli';
|
||||||
|
|
||||||
|
const FPM = 'fpm';
|
||||||
|
}
|
12
app/Enums/ScriptExecutionStatus.php
Normal file
12
app/Enums/ScriptExecutionStatus.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
final class ScriptExecutionStatus
|
||||||
|
{
|
||||||
|
const EXECUTING = 'executing';
|
||||||
|
|
||||||
|
const COMPLETED = 'completed';
|
||||||
|
|
||||||
|
const FAILED = 'failed';
|
||||||
|
}
|
@ -11,4 +11,6 @@ final class ServerStatus
|
|||||||
const INSTALLATION_FAILED = 'installation_failed';
|
const INSTALLATION_FAILED = 'installation_failed';
|
||||||
|
|
||||||
const DISCONNECTED = 'disconnected';
|
const DISCONNECTED = 'disconnected';
|
||||||
|
|
||||||
|
const UPDATING = 'updating';
|
||||||
}
|
}
|
||||||
|
@ -9,4 +9,8 @@ final class StorageProvider
|
|||||||
const FTP = 'ftp';
|
const FTP = 'ftp';
|
||||||
|
|
||||||
const LOCAL = 'local';
|
const LOCAL = 'local';
|
||||||
|
|
||||||
|
const S3 = 's3';
|
||||||
|
|
||||||
|
const WASABI = 'wasabi';
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,4 @@
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
class DeploymentScriptIsEmptyException extends Exception
|
class DeploymentScriptIsEmptyException extends Exception {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
8
app/Exceptions/SSHUploadFailed.php
Executable file
8
app/Exceptions/SSHUploadFailed.php
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
class SSHUploadFailed extends SSHError
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
@ -4,6 +4,4 @@
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
class SourceControlIsNotConnected extends Exception
|
class SourceControlIsNotConnected extends Exception {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
30
app/Facades/FTP.php
Normal file
30
app/Facades/FTP.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Facades;
|
||||||
|
|
||||||
|
use App\Support\Testing\FTPFake;
|
||||||
|
use FTP\Connection;
|
||||||
|
use Illuminate\Support\Facades\Facade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method static bool|Connection connect(string $host, string $port, bool $ssl = false)
|
||||||
|
* @method static bool login(string $username, string $password, bool|Connection $connection)
|
||||||
|
* @method static void close(bool|Connection $connection)
|
||||||
|
* @method static bool passive(bool|Connection $connection, bool $passive)
|
||||||
|
* @method static bool delete(bool|Connection $connection, string $path)
|
||||||
|
* @method static void assertConnected(string $host)
|
||||||
|
*/
|
||||||
|
class FTP extends Facade
|
||||||
|
{
|
||||||
|
protected static function getFacadeAccessor(): string
|
||||||
|
{
|
||||||
|
return 'ftp';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fake(): FTPFake
|
||||||
|
{
|
||||||
|
static::swap($fake = new FTPFake());
|
||||||
|
|
||||||
|
return $fake;
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,8 @@
|
|||||||
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false)
|
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false)
|
||||||
* @method static string assertExecuted(array|string $commands)
|
* @method static string assertExecuted(array|string $commands)
|
||||||
* @method static string assertExecutedContains(string $command)
|
* @method static string assertExecutedContains(string $command)
|
||||||
|
* @method static string assertFileUploaded(string $toPath, ?string $content = null)
|
||||||
|
* @method static string getUploadedLocalPath()
|
||||||
* @method static disconnect()
|
* @method static disconnect()
|
||||||
*/
|
*/
|
||||||
class SSH extends FacadeAlias
|
class SSH extends FacadeAlias
|
||||||
|
37
app/Helpers/FTP.php
Normal file
37
app/Helpers/FTP.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use FTP\Connection;
|
||||||
|
|
||||||
|
class FTP
|
||||||
|
{
|
||||||
|
public function connect(string $host, string $port, bool $ssl = false): bool|Connection
|
||||||
|
{
|
||||||
|
if ($ssl) {
|
||||||
|
return ftp_ssl_connect($host, $port, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ftp_connect($host, $port, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function login(string $username, string $password, bool|Connection $connection): bool
|
||||||
|
{
|
||||||
|
return ftp_login($connection, $username, $password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close(bool|Connection $connection): void
|
||||||
|
{
|
||||||
|
ftp_close($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function passive(bool|Connection $connection, bool $passive): bool
|
||||||
|
{
|
||||||
|
return ftp_pasv($connection, $passive);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(bool|Connection $connection, string $path): bool
|
||||||
|
{
|
||||||
|
return ftp_delete($connection, $path);
|
||||||
|
}
|
||||||
|
}
|
@ -94,12 +94,6 @@ public function connect(bool $sftp = false): void
|
|||||||
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
|
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
|
||||||
{
|
{
|
||||||
if (! $this->log && $log) {
|
if (! $this->log && $log) {
|
||||||
$this->log = $this->server->logs()->create([
|
|
||||||
'site_id' => $siteId,
|
|
||||||
'name' => $this->server->id.'-'.strtotime('now').'-'.$log.'.log',
|
|
||||||
'type' => $log,
|
|
||||||
'disk' => config('core.logs_disk'),
|
|
||||||
]);
|
|
||||||
$this->log = ServerLog::make($this->server, $log);
|
$this->log = ServerLog::make($this->server, $log);
|
||||||
if ($siteId) {
|
if ($siteId) {
|
||||||
$this->log->forSite($siteId);
|
$this->log->forSite($siteId);
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
use App\Exceptions\RepositoryNotFound;
|
use App\Exceptions\RepositoryNotFound;
|
||||||
use App\Exceptions\RepositoryPermissionDenied;
|
use App\Exceptions\RepositoryPermissionDenied;
|
||||||
use App\Exceptions\SourceControlIsNotConnected;
|
use App\Exceptions\SourceControlIsNotConnected;
|
||||||
|
use App\Exceptions\SSHUploadFailed;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
use App\Models\Deployment;
|
use App\Models\Deployment;
|
||||||
@ -81,9 +82,12 @@ public function updateEnv(Server $server, Site $site, Request $request): Redirec
|
|||||||
{
|
{
|
||||||
$this->authorize('manage', $server);
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
|
try {
|
||||||
app(UpdateEnv::class)->update($site, $request->input());
|
app(UpdateEnv::class)->update($site, $request->input());
|
||||||
|
|
||||||
Toast::success('Env updated!');
|
Toast::success('Env updated!');
|
||||||
|
} catch (SSHUploadFailed) {
|
||||||
|
Toast::error('Failed to update .env file!');
|
||||||
|
}
|
||||||
|
|
||||||
return back();
|
return back();
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
use App\Actions\CronJob\CreateCronJob;
|
use App\Actions\CronJob\CreateCronJob;
|
||||||
use App\Actions\CronJob\DeleteCronJob;
|
use App\Actions\CronJob\DeleteCronJob;
|
||||||
|
use App\Actions\CronJob\DisableCronJob;
|
||||||
|
use App\Actions\CronJob\EnableCronJob;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
use App\Models\CronJob;
|
use App\Models\CronJob;
|
||||||
@ -45,4 +47,26 @@ public function destroy(Server $server, CronJob $cronJob): RedirectResponse
|
|||||||
|
|
||||||
return back();
|
return back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function enable(Server $server, CronJob $cronJob): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
|
app(EnableCronJob::class)->enable($server, $cronJob);
|
||||||
|
|
||||||
|
Toast::success('Cronjob enabled successfully.');
|
||||||
|
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function disable(Server $server, CronJob $cronJob): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
|
app(DisableCronJob::class)->disable($server, $cronJob);
|
||||||
|
|
||||||
|
Toast::success('Cronjob disabled successfully.');
|
||||||
|
|
||||||
|
return back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ public function updateIni(Server $server, Request $request): RedirectResponse
|
|||||||
|
|
||||||
app(UpdatePHPIni::class)->update($server, $request->input());
|
app(UpdatePHPIni::class)->update($server, $request->input());
|
||||||
|
|
||||||
Toast::success('PHP ini updated!');
|
Toast::success(__('PHP ini (:type) updated!', ['type' => $request->input('type')]));
|
||||||
|
|
||||||
return back()->with([
|
return back()->with([
|
||||||
'ini' => $request->input('ini'),
|
'ini' => $request->input('ini'),
|
||||||
|
@ -29,7 +29,7 @@ public function store(Server $server, Request $request): HtmxResponse
|
|||||||
{
|
{
|
||||||
$this->authorize('manage', $server);
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
/** @var \App\Models\SshKey $key */
|
/** @var SshKey $key */
|
||||||
$key = app(CreateSshKey::class)->create(
|
$key = app(CreateSshKey::class)->create(
|
||||||
$request->user(),
|
$request->user(),
|
||||||
$request->input()
|
$request->input()
|
||||||
|
111
app/Http/Controllers/ScriptController.php
Normal file
111
app/Http/Controllers/ScriptController.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Script\CreateScript;
|
||||||
|
use App\Actions\Script\EditScript;
|
||||||
|
use App\Actions\Script\ExecuteScript;
|
||||||
|
use App\Facades\Toast;
|
||||||
|
use App\Helpers\HtmxResponse;
|
||||||
|
use App\Models\Script;
|
||||||
|
use App\Models\ScriptExecution;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ScriptController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request): View
|
||||||
|
{
|
||||||
|
$this->authorize('viewAny', Script::class);
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'scripts' => $user->scripts,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->has('edit')) {
|
||||||
|
$data['editScript'] = $user->scripts()->findOrFail($request->input('edit'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('execute')) {
|
||||||
|
$data['executeScript'] = $user->scripts()->findOrFail($request->input('execute'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('scripts.index', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Script $script): View
|
||||||
|
{
|
||||||
|
$this->authorize('view', $script);
|
||||||
|
|
||||||
|
return view('scripts.show', [
|
||||||
|
'script' => $script,
|
||||||
|
'executions' => $script->executions()->latest()->paginate(20),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
$this->authorize('create', Script::class);
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
app(CreateScript::class)->create($user, $request->input());
|
||||||
|
|
||||||
|
Toast::success('Script created.');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('scripts.index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(Request $request, Script $script): HtmxResponse
|
||||||
|
{
|
||||||
|
$this->authorize('update', $script);
|
||||||
|
|
||||||
|
app(EditScript::class)->edit($script, $request->input());
|
||||||
|
|
||||||
|
Toast::success('Script updated.');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('scripts.index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(Script $script, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'server' => 'required|exists:servers,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server = Server::findOrFail($request->input('server'));
|
||||||
|
|
||||||
|
$this->authorize('execute', [$script, $server]);
|
||||||
|
|
||||||
|
app(ExecuteScript::class)->execute($script, $server, $request->input());
|
||||||
|
|
||||||
|
Toast::success('Executing the script...');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('scripts.show', $script));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Script $script): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->authorize('delete', $script);
|
||||||
|
|
||||||
|
$script->delete();
|
||||||
|
|
||||||
|
Toast::success('Script deleted.');
|
||||||
|
|
||||||
|
return redirect()->route('scripts.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function log(Script $script, ScriptExecution $execution): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->authorize('view', $script);
|
||||||
|
|
||||||
|
return back()->with('content', $execution->serverLog?->getContent());
|
||||||
|
}
|
||||||
|
}
|
@ -35,7 +35,9 @@ public function create(Request $request): View
|
|||||||
$this->authorize('create', [Server::class, $user->currentProject]);
|
$this->authorize('create', [Server::class, $user->currentProject]);
|
||||||
|
|
||||||
$provider = $request->query('provider', old('provider', \App\Enums\ServerProvider::CUSTOM));
|
$provider = $request->query('provider', old('provider', \App\Enums\ServerProvider::CUSTOM));
|
||||||
$serverProviders = ServerProvider::query()->where('provider', $provider)->get();
|
$serverProviders = ServerProvider::getByProjectId(auth()->user()->current_project_id)
|
||||||
|
->where('provider', $provider)
|
||||||
|
->get();
|
||||||
|
|
||||||
return view('servers.create', [
|
return view('servers.create', [
|
||||||
'serverProviders' => $serverProviders,
|
'serverProviders' => $serverProviders,
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Actions\Server\EditServer;
|
use App\Actions\Server\EditServer;
|
||||||
use App\Actions\Server\RebootServer;
|
use App\Actions\Server\RebootServer;
|
||||||
|
use App\Actions\Server\Update;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@ -64,4 +65,24 @@ public function edit(Request $request, Server $server): RedirectResponse
|
|||||||
|
|
||||||
return back();
|
return back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function checkUpdates(Server $server): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
|
$server->checkForUpdates();
|
||||||
|
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Server $server): HtmxResponse
|
||||||
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
|
app(Update::class)->update($server);
|
||||||
|
|
||||||
|
Toast::info('Updating server. This may take a few minutes.');
|
||||||
|
|
||||||
|
return htmx()->back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Controllers\Settings;
|
namespace App\Http\Controllers\Settings;
|
||||||
|
|
||||||
use App\Actions\NotificationChannels\AddChannel;
|
use App\Actions\NotificationChannels\AddChannel;
|
||||||
|
use App\Actions\NotificationChannels\EditChannel;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@ -13,11 +14,17 @@
|
|||||||
|
|
||||||
class NotificationChannelController extends Controller
|
class NotificationChannelController extends Controller
|
||||||
{
|
{
|
||||||
public function index(): View
|
public function index(Request $request): View
|
||||||
{
|
{
|
||||||
return view('settings.notification-channels.index', [
|
$data = [
|
||||||
'channels' => NotificationChannel::query()->latest()->get(),
|
'channels' => NotificationChannel::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
if ($request->has('edit')) {
|
||||||
|
$data['editChannel'] = NotificationChannel::find($request->input('edit'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('settings.notification-channels.index', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function add(Request $request): HtmxResponse
|
public function add(Request $request): HtmxResponse
|
||||||
@ -32,6 +39,19 @@ public function add(Request $request): HtmxResponse
|
|||||||
return htmx()->redirect(route('settings.notification-channels'));
|
return htmx()->redirect(route('settings.notification-channels'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function update(NotificationChannel $notificationChannel, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
app(EditChannel::class)->edit(
|
||||||
|
$notificationChannel,
|
||||||
|
$request->user(),
|
||||||
|
$request->input(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Toast::success('Channel updated.');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('settings.notification-channels'));
|
||||||
|
}
|
||||||
|
|
||||||
public function delete(int $id): RedirectResponse
|
public function delete(int $id): RedirectResponse
|
||||||
{
|
{
|
||||||
$channel = NotificationChannel::query()->findOrFail($id);
|
$channel = NotificationChannel::query()->findOrFail($id);
|
||||||
|
@ -19,6 +19,8 @@ class ProjectController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(): View
|
public function index(): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('viewAny', Project::class);
|
||||||
|
|
||||||
return view('settings.projects.index', [
|
return view('settings.projects.index', [
|
||||||
'projects' => Project::all(),
|
'projects' => Project::all(),
|
||||||
]);
|
]);
|
||||||
@ -26,6 +28,8 @@ public function index(): View
|
|||||||
|
|
||||||
public function create(Request $request): HtmxResponse
|
public function create(Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('create', Project::class);
|
||||||
|
|
||||||
app(CreateProject::class)->create($request->user(), $request->input());
|
app(CreateProject::class)->create($request->user(), $request->input());
|
||||||
|
|
||||||
Toast::success('Project created.');
|
Toast::success('Project created.');
|
||||||
@ -35,8 +39,7 @@ public function create(Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function update(Request $request, Project $project): HtmxResponse
|
public function update(Request $request, Project $project): HtmxResponse
|
||||||
{
|
{
|
||||||
/** @var Project $project */
|
$this->authorize('update', $project);
|
||||||
$project = $request->user()->projects()->findOrFail($project->id);
|
|
||||||
|
|
||||||
app(UpdateProject::class)->update($project, $request->input());
|
app(UpdateProject::class)->update($project, $request->input());
|
||||||
|
|
||||||
@ -47,12 +50,11 @@ public function update(Request $request, Project $project): HtmxResponse
|
|||||||
|
|
||||||
public function delete(Project $project): RedirectResponse
|
public function delete(Project $project): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('delete', $project);
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
|
||||||
/** @var Project $project */
|
|
||||||
$project = $user->projects()->findOrFail($project->id);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
app(DeleteProject::class)->delete($user, $project);
|
app(DeleteProject::class)->delete($user, $project);
|
||||||
} catch (ValidationException $e) {
|
} catch (ValidationException $e) {
|
||||||
@ -66,7 +68,7 @@ public function delete(Project $project): RedirectResponse
|
|||||||
return back();
|
return back();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function switch($projectId): RedirectResponse
|
public function switch(Request $request, $projectId): RedirectResponse
|
||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
@ -79,6 +81,11 @@ public function switch($projectId): RedirectResponse
|
|||||||
$user->current_project_id = $project->id;
|
$user->current_project_id = $project->id;
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
|
// check if the referer is settings/*
|
||||||
|
if (str_contains($request->headers->get('referer'), 'settings')) {
|
||||||
|
return redirect()->to($request->headers->get('referer'));
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('servers');
|
return redirect()->route('servers');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Actions\ServerProvider\CreateServerProvider;
|
use App\Actions\ServerProvider\CreateServerProvider;
|
||||||
use App\Actions\ServerProvider\DeleteServerProvider;
|
use App\Actions\ServerProvider\DeleteServerProvider;
|
||||||
|
use App\Actions\ServerProvider\EditServerProvider;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@ -14,11 +15,17 @@
|
|||||||
|
|
||||||
class ServerProviderController extends Controller
|
class ServerProviderController extends Controller
|
||||||
{
|
{
|
||||||
public function index(): View
|
public function index(Request $request): View
|
||||||
{
|
{
|
||||||
return view('settings.server-providers.index', [
|
$data = [
|
||||||
'providers' => auth()->user()->serverProviders,
|
'providers' => ServerProvider::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
if ($request->has('edit')) {
|
||||||
|
$data['editProvider'] = ServerProvider::find($request->input('edit'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('settings.server-providers.index', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connect(Request $request): HtmxResponse
|
public function connect(Request $request): HtmxResponse
|
||||||
@ -33,6 +40,19 @@ public function connect(Request $request): HtmxResponse
|
|||||||
return htmx()->redirect(route('settings.server-providers'));
|
return htmx()->redirect(route('settings.server-providers'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function update(ServerProvider $serverProvider, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
app(EditServerProvider::class)->edit(
|
||||||
|
$serverProvider,
|
||||||
|
$request->user(),
|
||||||
|
$request->input(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Toast::success('Provider updated.');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('settings.server-providers'));
|
||||||
|
}
|
||||||
|
|
||||||
public function delete(ServerProvider $serverProvider): RedirectResponse
|
public function delete(ServerProvider $serverProvider): RedirectResponse
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Actions\SourceControl\ConnectSourceControl;
|
use App\Actions\SourceControl\ConnectSourceControl;
|
||||||
use App\Actions\SourceControl\DeleteSourceControl;
|
use App\Actions\SourceControl\DeleteSourceControl;
|
||||||
|
use App\Actions\SourceControl\EditSourceControl;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@ -14,16 +15,23 @@
|
|||||||
|
|
||||||
class SourceControlController extends Controller
|
class SourceControlController extends Controller
|
||||||
{
|
{
|
||||||
public function index(): View
|
public function index(Request $request): View
|
||||||
{
|
{
|
||||||
return view('settings.source-controls.index', [
|
$data = [
|
||||||
'sourceControls' => SourceControl::query()->orderByDesc('id')->get(),
|
'sourceControls' => SourceControl::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
if ($request->has('edit')) {
|
||||||
|
$data['editSourceControl'] = SourceControl::find($request->input('edit'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('settings.source-controls.index', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connect(Request $request): HtmxResponse
|
public function connect(Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
app(ConnectSourceControl::class)->connect(
|
app(ConnectSourceControl::class)->connect(
|
||||||
|
$request->user(),
|
||||||
$request->input(),
|
$request->input(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -32,6 +40,19 @@ public function connect(Request $request): HtmxResponse
|
|||||||
return htmx()->redirect(route('settings.source-controls'));
|
return htmx()->redirect(route('settings.source-controls'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function update(SourceControl $sourceControl, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
app(EditSourceControl::class)->edit(
|
||||||
|
$sourceControl,
|
||||||
|
$request->user(),
|
||||||
|
$request->input(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Toast::success('Source control updated.');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('settings.source-controls'));
|
||||||
|
}
|
||||||
|
|
||||||
public function delete(SourceControl $sourceControl): RedirectResponse
|
public function delete(SourceControl $sourceControl): RedirectResponse
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Actions\StorageProvider\CreateStorageProvider;
|
use App\Actions\StorageProvider\CreateStorageProvider;
|
||||||
use App\Actions\StorageProvider\DeleteStorageProvider;
|
use App\Actions\StorageProvider\DeleteStorageProvider;
|
||||||
|
use App\Actions\StorageProvider\EditStorageProvider;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@ -14,11 +15,17 @@
|
|||||||
|
|
||||||
class StorageProviderController extends Controller
|
class StorageProviderController extends Controller
|
||||||
{
|
{
|
||||||
public function index(): View
|
public function index(Request $request): View
|
||||||
{
|
{
|
||||||
return view('settings.storage-providers.index', [
|
$data = [
|
||||||
'providers' => auth()->user()->storageProviders,
|
'providers' => StorageProvider::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
if ($request->has('edit')) {
|
||||||
|
$data['editProvider'] = StorageProvider::find($request->input('edit'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('settings.storage-providers.index', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connect(Request $request): HtmxResponse
|
public function connect(Request $request): HtmxResponse
|
||||||
@ -33,6 +40,19 @@ public function connect(Request $request): HtmxResponse
|
|||||||
return htmx()->redirect(route('settings.storage-providers'));
|
return htmx()->redirect(route('settings.storage-providers'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function update(StorageProvider $storageProvider, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
app(EditStorageProvider::class)->edit(
|
||||||
|
$storageProvider,
|
||||||
|
$request->user(),
|
||||||
|
$request->input(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Toast::success('Provider updated.');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('settings.storage-providers'));
|
||||||
|
}
|
||||||
|
|
||||||
public function delete(StorageProvider $storageProvider): RedirectResponse
|
public function delete(StorageProvider $storageProvider): RedirectResponse
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
90
app/Http/Controllers/Settings/TagController.php
Normal file
90
app/Http/Controllers/Settings/TagController.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Settings;
|
||||||
|
|
||||||
|
use App\Actions\Tag\AttachTag;
|
||||||
|
use App\Actions\Tag\CreateTag;
|
||||||
|
use App\Actions\Tag\DeleteTag;
|
||||||
|
use App\Actions\Tag\DetachTag;
|
||||||
|
use App\Actions\Tag\EditTag;
|
||||||
|
use App\Facades\Toast;
|
||||||
|
use App\Helpers\HtmxResponse;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TagController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request): View
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'tags' => Tag::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->has('edit')) {
|
||||||
|
$data['editTag'] = Tag::find($request->input('edit'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('settings.tags.index', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
app(CreateTag::class)->create(
|
||||||
|
$user,
|
||||||
|
$request->input(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Toast::success('Tag created.');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('settings.tags'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Tag $tag, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
app(EditTag::class)->edit(
|
||||||
|
$tag,
|
||||||
|
$request->input(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Toast::success('Tag updated.');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('settings.tags'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attach(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
app(AttachTag::class)->attach($user, $request->input());
|
||||||
|
|
||||||
|
return back()->with([
|
||||||
|
'status' => 'tag-created',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detach(Request $request, Tag $tag): RedirectResponse
|
||||||
|
{
|
||||||
|
app(DetachTag::class)->detach($tag, $request->input());
|
||||||
|
|
||||||
|
return back()->with([
|
||||||
|
'status' => 'tag-detached',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Tag $tag): RedirectResponse
|
||||||
|
{
|
||||||
|
app(DeleteTag::class)->delete($tag);
|
||||||
|
|
||||||
|
Toast::success('Tag deleted.');
|
||||||
|
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Project;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
@ -56,6 +57,18 @@ public function updateProjects(User $user, Request $request): HtmxResponse
|
|||||||
|
|
||||||
$user->projects()->sync($request->projects);
|
$user->projects()->sync($request->projects);
|
||||||
|
|
||||||
|
if ($user->currentProject && ! $user->projects->contains($user->currentProject)) {
|
||||||
|
$user->current_project_id = null;
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Project $firstProject */
|
||||||
|
$firstProject = $user->projects->first();
|
||||||
|
if (! $user->currentProject && $firstProject) {
|
||||||
|
$user->current_project_id = $firstProject->id;
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
|
|
||||||
Toast::success('Projects updated successfully');
|
Toast::success('Projects updated successfully');
|
||||||
|
|
||||||
return htmx()->redirect(route('settings.users.show', $user));
|
return htmx()->redirect(route('settings.users.show', $user));
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Site\UpdateAliases;
|
||||||
use App\Actions\Site\UpdateSourceControl;
|
use App\Actions\Site\UpdateSourceControl;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
@ -83,10 +84,21 @@ public function updateSourceControl(Server $server, Site $site, Request $request
|
|||||||
{
|
{
|
||||||
$this->authorize('manage', $server);
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$site = app(UpdateSourceControl::class)->update($site, $request->input());
|
app(UpdateSourceControl::class)->update($site, $request->input());
|
||||||
|
|
||||||
Toast::success('Source control updated successfully!');
|
Toast::success('Source control updated successfully!');
|
||||||
|
|
||||||
return htmx()->back();
|
return htmx()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateAliases(Server $server, Site $site, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
|
app(UpdateAliases::class)->update($site, $request->input());
|
||||||
|
|
||||||
|
Toast::success('Aliases updated successfully!');
|
||||||
|
|
||||||
|
return htmx()->back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,5 +69,6 @@ class Kernel extends HttpKernel
|
|||||||
'handle-ssh-errors' => HandleSSHErrors::class,
|
'handle-ssh-errors' => HandleSSHErrors::class,
|
||||||
'select-current-project' => SelectCurrentProject::class,
|
'select-current-project' => SelectCurrentProject::class,
|
||||||
'is-admin' => \App\Http\Middleware\IsAdmin::class,
|
'is-admin' => \App\Http\Middleware\IsAdmin::class,
|
||||||
|
'must-have-current-project' => \App\Http\Middleware\MustHaveCurrentProject::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
31
app/Http/Middleware/MustHaveCurrentProject.php
Normal file
31
app/Http/Middleware/MustHaveCurrentProject.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Facades\Toast;
|
||||||
|
use App\Models\User;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class MustHaveCurrentProject
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (! $user->currentProject) {
|
||||||
|
Toast::warning('Please select a project to continue');
|
||||||
|
|
||||||
|
return redirect()->route('profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ public function handle(Request $request, Closure $next): Response
|
|||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if ($server->project_id != $user->current_project_id) {
|
if ($server->project_id != $user->current_project_id && $user->can('view', $server)) {
|
||||||
$user->current_project_id = $server->project_id;
|
$user->current_project_id = $server->project_id;
|
||||||
$user->save();
|
$user->save();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\CronjobStatus;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
@ -41,7 +42,14 @@ public function server(): BelongsTo
|
|||||||
public static function crontab(Server $server, string $user): string
|
public static function crontab(Server $server, string $user): string
|
||||||
{
|
{
|
||||||
$data = '';
|
$data = '';
|
||||||
$cronJobs = $server->cronJobs()->where('user', $user)->get();
|
$cronJobs = $server->cronJobs()
|
||||||
|
->where('user', $user)
|
||||||
|
->whereIn('status', [
|
||||||
|
CronjobStatus::READY,
|
||||||
|
CronjobStatus::CREATING,
|
||||||
|
CronjobStatus::ENABLING,
|
||||||
|
])
|
||||||
|
->get();
|
||||||
foreach ($cronJobs as $key => $cronJob) {
|
foreach ($cronJobs as $key => $cronJob) {
|
||||||
$data .= $cronJob->frequency.' '.$cronJob->command;
|
$data .= $cronJob->frequency.' '.$cronJob->command;
|
||||||
if ($key != count($cronJobs) - 1) {
|
if ($key != count($cronJobs) - 1) {
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Notifications\NotificationInterface;
|
use App\Notifications\NotificationInterface;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -12,6 +14,7 @@
|
|||||||
* @property array data
|
* @property array data
|
||||||
* @property string label
|
* @property string label
|
||||||
* @property bool connected
|
* @property bool connected
|
||||||
|
* @property int $project_id
|
||||||
*/
|
*/
|
||||||
class NotificationChannel extends AbstractModel
|
class NotificationChannel extends AbstractModel
|
||||||
{
|
{
|
||||||
@ -24,6 +27,7 @@ class NotificationChannel extends AbstractModel
|
|||||||
'data',
|
'data',
|
||||||
'connected',
|
'connected',
|
||||||
'is_default',
|
'is_default',
|
||||||
|
'project_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
@ -47,4 +51,16 @@ public static function notifyAll(NotificationInterface $notification): void
|
|||||||
$channel->notify($notification);
|
$channel->notify($notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function project(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getByProjectId(int $projectId): Builder
|
||||||
|
{
|
||||||
|
return self::query()
|
||||||
|
->where('project_id', $projectId)
|
||||||
|
->orWhereNull('project_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
* @property User $user
|
* @property User $user
|
||||||
* @property Collection<Server> $servers
|
* @property Collection<Server> $servers
|
||||||
* @property Collection<NotificationChannel> $notificationChannels
|
* @property Collection<NotificationChannel> $notificationChannels
|
||||||
|
* @property Collection<SourceControl> $sourceControls
|
||||||
*/
|
*/
|
||||||
class Project extends Model
|
class Project extends Model
|
||||||
{
|
{
|
||||||
@ -59,4 +60,14 @@ public function users(): BelongsToMany
|
|||||||
{
|
{
|
||||||
return $this->belongsToMany(User::class, 'user_project')->withTimestamps();
|
return $this->belongsToMany(User::class, 'user_project')->withTimestamps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sourceControls(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(SourceControl::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tags(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Tag::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
66
app/Models/Script.php
Normal file
66
app/Models/Script.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property int $user_id
|
||||||
|
* @property string $name
|
||||||
|
* @property string $content
|
||||||
|
* @property Carbon $created_at
|
||||||
|
* @property Carbon $updated_at
|
||||||
|
* @property Collection<ScriptExecution> $executions
|
||||||
|
* @property ?ScriptExecution $lastExecution
|
||||||
|
*/
|
||||||
|
class Script extends AbstractModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'name',
|
||||||
|
'content',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function boot(): void
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
static::deleting(function (Script $script) {
|
||||||
|
$script->executions()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVariables(): array
|
||||||
|
{
|
||||||
|
$variables = [];
|
||||||
|
preg_match_all('/\${(.*?)}/', $this->content, $matches);
|
||||||
|
foreach ($matches[1] as $match) {
|
||||||
|
$variables[] = $match;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_unique($variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function executions(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ScriptExecution::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lastExecution(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(ScriptExecution::class)->latest();
|
||||||
|
}
|
||||||
|
}
|
61
app/Models/ScriptExecution.php
Normal file
61
app/Models/ScriptExecution.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property int $script_id
|
||||||
|
* @property int $server_log_id
|
||||||
|
* @property string $user
|
||||||
|
* @property array $variables
|
||||||
|
* @property string $status
|
||||||
|
* @property Carbon $created_at
|
||||||
|
* @property Carbon $updated_at
|
||||||
|
* @property Script $script
|
||||||
|
* @property ?ServerLog $serverLog
|
||||||
|
*/
|
||||||
|
class ScriptExecution extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'script_id',
|
||||||
|
'server_log_id',
|
||||||
|
'user',
|
||||||
|
'variables',
|
||||||
|
'status',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'script_id' => 'integer',
|
||||||
|
'server_log_id' => 'integer',
|
||||||
|
'variables' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function script(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Script::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
$content = $this->script->content;
|
||||||
|
foreach ($this->variables as $variable => $value) {
|
||||||
|
if (is_string($value) && ! empty($value)) {
|
||||||
|
$content = str_replace('${'.$variable.'}', $value, $content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function serverLog(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ServerLog::class);
|
||||||
|
}
|
||||||
|
}
|
@ -9,10 +9,12 @@
|
|||||||
use App\SSH\Cron\Cron;
|
use App\SSH\Cron\Cron;
|
||||||
use App\SSH\OS\OS;
|
use App\SSH\OS\OS;
|
||||||
use App\SSH\Systemd\Systemd;
|
use App\SSH\Systemd\Systemd;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
use Illuminate\Filesystem\FilesystemAdapter;
|
use Illuminate\Filesystem\FilesystemAdapter;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
@ -55,6 +57,8 @@
|
|||||||
* @property Queue[] $daemons
|
* @property Queue[] $daemons
|
||||||
* @property SshKey[] $sshKeys
|
* @property SshKey[] $sshKeys
|
||||||
* @property string $hostname
|
* @property string $hostname
|
||||||
|
* @property int $updates
|
||||||
|
* @property Carbon $last_update_check
|
||||||
*/
|
*/
|
||||||
class Server extends AbstractModel
|
class Server extends AbstractModel
|
||||||
{
|
{
|
||||||
@ -82,6 +86,8 @@ class Server extends AbstractModel
|
|||||||
'security_updates',
|
'security_updates',
|
||||||
'progress',
|
'progress',
|
||||||
'progress_step',
|
'progress_step',
|
||||||
|
'updates',
|
||||||
|
'last_update_check',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
@ -95,6 +101,8 @@ class Server extends AbstractModel
|
|||||||
'available_updates' => 'integer',
|
'available_updates' => 'integer',
|
||||||
'security_updates' => 'integer',
|
'security_updates' => 'integer',
|
||||||
'progress' => 'integer',
|
'progress' => 'integer',
|
||||||
|
'updates' => 'integer',
|
||||||
|
'last_update_check' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
@ -207,6 +215,11 @@ public function sshKeys(): BelongsToMany
|
|||||||
->withTimestamps();
|
->withTimestamps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
public function getSshUser(): string
|
public function getSshUser(): string
|
||||||
{
|
{
|
||||||
if ($this->ssh_user) {
|
if ($this->ssh_user) {
|
||||||
@ -384,4 +397,11 @@ public function cron(): Cron
|
|||||||
{
|
{
|
||||||
return new Cron($this);
|
return new Cron($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function checkForUpdates(): void
|
||||||
|
{
|
||||||
|
$this->updates = $this->os()->availableUpdates();
|
||||||
|
$this->last_update_check = now();
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
@ -13,6 +14,7 @@
|
|||||||
* @property array $credentials
|
* @property array $credentials
|
||||||
* @property bool $connected
|
* @property bool $connected
|
||||||
* @property User $user
|
* @property User $user
|
||||||
|
* @property ?int $project_id
|
||||||
*/
|
*/
|
||||||
class ServerProvider extends AbstractModel
|
class ServerProvider extends AbstractModel
|
||||||
{
|
{
|
||||||
@ -24,12 +26,14 @@ class ServerProvider extends AbstractModel
|
|||||||
'provider',
|
'provider',
|
||||||
'credentials',
|
'credentials',
|
||||||
'connected',
|
'connected',
|
||||||
|
'project_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'user_id' => 'integer',
|
'user_id' => 'integer',
|
||||||
'credentials' => 'encrypted:array',
|
'credentials' => 'encrypted:array',
|
||||||
'connected' => 'boolean',
|
'connected' => 'boolean',
|
||||||
|
'project_id' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
@ -46,4 +50,16 @@ public function servers(): HasMany
|
|||||||
{
|
{
|
||||||
return $this->hasMany(Server::class, 'provider_id');
|
return $this->hasMany(Server::class, 'provider_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function project(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getByProjectId(int $projectId): Builder
|
||||||
|
{
|
||||||
|
return self::query()
|
||||||
|
->where('project_id', $projectId)
|
||||||
|
->orWhereNull('project_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,6 +127,11 @@ public function ssls(): HasMany
|
|||||||
return $this->hasMany(Ssl::class);
|
return $this->hasMany(Ssl::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws SourceControlIsNotConnected
|
* @throws SourceControlIsNotConnected
|
||||||
*/
|
*/
|
||||||
@ -278,4 +284,22 @@ public function getEnv(): string
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasSSL(): bool
|
||||||
|
{
|
||||||
|
return $this->ssls->isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environmentVariables(?Deployment $deployment = null): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'SITE_PATH' => $this->path,
|
||||||
|
'DOMAIN' => $this->domain,
|
||||||
|
'BRANCH' => $this->branch ?? '',
|
||||||
|
'REPOSITORY' => $this->repository ?? '',
|
||||||
|
'COMMIT_ID' => $deployment?->commit_id ?? '',
|
||||||
|
'PHP_VERSION' => $this->php_version,
|
||||||
|
'PHP_PATH' => '/usr/bin/php'.$this->php_version,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\SourceControlProviders\SourceControlProvider;
|
use App\SourceControlProviders\SourceControlProvider;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -12,6 +14,7 @@
|
|||||||
* @property ?string $profile
|
* @property ?string $profile
|
||||||
* @property ?string $url
|
* @property ?string $url
|
||||||
* @property string $access_token
|
* @property string $access_token
|
||||||
|
* @property ?int $project_id
|
||||||
*/
|
*/
|
||||||
class SourceControl extends AbstractModel
|
class SourceControl extends AbstractModel
|
||||||
{
|
{
|
||||||
@ -23,11 +26,13 @@ class SourceControl extends AbstractModel
|
|||||||
'profile',
|
'profile',
|
||||||
'url',
|
'url',
|
||||||
'access_token',
|
'access_token',
|
||||||
|
'project_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'access_token' => 'encrypted',
|
'access_token' => 'encrypted',
|
||||||
'provider_data' => 'encrypted:array',
|
'provider_data' => 'encrypted:array',
|
||||||
|
'project_id' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function provider(): SourceControlProvider
|
public function provider(): SourceControlProvider
|
||||||
@ -46,4 +51,16 @@ public function sites(): HasMany
|
|||||||
{
|
{
|
||||||
return $this->hasMany(Site::class);
|
return $this->hasMany(Site::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function project(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getByProjectId(int $projectId): Builder
|
||||||
|
{
|
||||||
|
return self::query()
|
||||||
|
->where('project_id', $projectId)
|
||||||
|
->orWhereNull('project_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
* @property string $status
|
* @property string $status
|
||||||
* @property Site $site
|
* @property Site $site
|
||||||
* @property string $ca_path
|
* @property string $ca_path
|
||||||
|
* @property ?array $domains
|
||||||
*/
|
*/
|
||||||
class Ssl extends AbstractModel
|
class Ssl extends AbstractModel
|
||||||
{
|
{
|
||||||
@ -30,6 +31,7 @@ class Ssl extends AbstractModel
|
|||||||
'ca',
|
'ca',
|
||||||
'expires_at',
|
'expires_at',
|
||||||
'status',
|
'status',
|
||||||
|
'domains',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
@ -38,6 +40,7 @@ class Ssl extends AbstractModel
|
|||||||
'pk' => 'encrypted',
|
'pk' => 'encrypted',
|
||||||
'ca' => 'encrypted',
|
'ca' => 'encrypted',
|
||||||
'expires_at' => 'datetime',
|
'expires_at' => 'datetime',
|
||||||
|
'domains' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function site(): BelongsTo
|
public function site(): BelongsTo
|
||||||
@ -111,4 +114,16 @@ public function validateSetup(string $result): bool
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDomains(): array
|
||||||
|
{
|
||||||
|
if (! empty($this->domains) && is_array($this->domains)) {
|
||||||
|
return $this->domains;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->domains = [$this->site->domain];
|
||||||
|
$this->save();
|
||||||
|
|
||||||
|
return $this->domains;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
@ -12,6 +13,7 @@
|
|||||||
* @property string $provider
|
* @property string $provider
|
||||||
* @property array $credentials
|
* @property array $credentials
|
||||||
* @property User $user
|
* @property User $user
|
||||||
|
* @property int $project_id
|
||||||
*/
|
*/
|
||||||
class StorageProvider extends AbstractModel
|
class StorageProvider extends AbstractModel
|
||||||
{
|
{
|
||||||
@ -22,11 +24,13 @@ class StorageProvider extends AbstractModel
|
|||||||
'profile',
|
'profile',
|
||||||
'provider',
|
'provider',
|
||||||
'credentials',
|
'credentials',
|
||||||
|
'project_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'user_id' => 'integer',
|
'user_id' => 'integer',
|
||||||
'credentials' => 'encrypted:array',
|
'credentials' => 'encrypted:array',
|
||||||
|
'project_id' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
@ -45,4 +49,16 @@ public function backups(): HasMany
|
|||||||
{
|
{
|
||||||
return $this->hasMany(Backup::class, 'storage_id');
|
return $this->hasMany(Backup::class, 'storage_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function project(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getByProjectId(int $projectId): Builder
|
||||||
|
{
|
||||||
|
return self::query()
|
||||||
|
->where('project_id', $projectId)
|
||||||
|
->orWhereNull('project_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
55
app/Models/Tag.php
Normal file
55
app/Models/Tag.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property int $project_id
|
||||||
|
* @property string $name
|
||||||
|
* @property string $color
|
||||||
|
* @property Carbon $created_at
|
||||||
|
* @property Carbon $updated_at
|
||||||
|
*/
|
||||||
|
class Tag extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'project_id',
|
||||||
|
'name',
|
||||||
|
'color',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'project_id' => 'int',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function project(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function servers(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Server::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sites(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Site::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getByProjectId(int $projectId): Builder
|
||||||
|
{
|
||||||
|
return self::query()
|
||||||
|
->where('project_id', $projectId)
|
||||||
|
->orWhereNull('project_id');
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Enums\UserRole;
|
use App\Enums\UserRole;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
@ -105,24 +106,6 @@ public function storageProvider(string $provider): HasOne
|
|||||||
return $this->hasOne(StorageProvider::class)->where('provider', $provider);
|
return $this->hasOne(StorageProvider::class)->where('provider', $provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connectedStorageProviders(): HasMany
|
|
||||||
{
|
|
||||||
return $this->storageProviders()->where('connected', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function connectedSourceControls(): array
|
|
||||||
{
|
|
||||||
$connectedSourceControls = [];
|
|
||||||
$sourceControls = $this->sourceControls()
|
|
||||||
->where('connected', 1)
|
|
||||||
->get(['provider']);
|
|
||||||
foreach ($sourceControls as $sourceControl) {
|
|
||||||
$connectedSourceControls[] = $sourceControl->provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $connectedSourceControls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function projects(): BelongsToMany
|
public function projects(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Project::class, 'user_project')->withTimestamps();
|
return $this->belongsToMany(Project::class, 'user_project')->withTimestamps();
|
||||||
@ -133,11 +116,6 @@ public function currentProject(): HasOne
|
|||||||
return $this->HasOne(Project::class, 'id', 'current_project_id');
|
return $this->HasOne(Project::class, 'id', 'current_project_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isMemberOfProject(Project $project): bool
|
|
||||||
{
|
|
||||||
return $project->user_id === $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createDefaultProject(): Project
|
public function createDefaultProject(): Project
|
||||||
{
|
{
|
||||||
$project = $this->projects()->first();
|
$project = $this->projects()->first();
|
||||||
@ -160,4 +138,18 @@ public function isAdmin(): bool
|
|||||||
{
|
{
|
||||||
return $this->role === UserRole::ADMIN;
|
return $this->role === UserRole::ADMIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function scripts(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Script::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function allServers(): Builder
|
||||||
|
{
|
||||||
|
return Server::query()->whereHas('project', function (Builder $query) {
|
||||||
|
$query->whereHas('users', function ($query) {
|
||||||
|
$query->where('user_id', $this->id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,5 @@
|
|||||||
|
|
||||||
abstract class AbstractNotificationChannel implements NotificationChannelInterface
|
abstract class AbstractNotificationChannel implements NotificationChannelInterface
|
||||||
{
|
{
|
||||||
public function __construct(protected NotificationChannel $notificationChannel)
|
public function __construct(protected NotificationChannel $notificationChannel) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ private function checkConnection(string $subject, string $text): bool
|
|||||||
'content' => '*'.$subject.'*'."\n".$text,
|
'content' => '*'.$subject.'*'."\n".$text,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $connect->ok();
|
return $connect->successful();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function send(object $notifiable, NotificationInterface $notification): void
|
public function send(object $notifiable, NotificationInterface $notification): void
|
||||||
|
@ -26,6 +26,6 @@ public function toEmail(object $notifiable): MailMessage
|
|||||||
return (new MailMessage)
|
return (new MailMessage)
|
||||||
->subject(__('Server disconnected!'))
|
->subject(__('Server disconnected!'))
|
||||||
->line("We've disconnected from your server [".$this->server->name.'].')
|
->line("We've disconnected from your server [".$this->server->name.'].')
|
||||||
->line('Please check your sever is online and make sure that has our public keys in it');
|
->line('Please check your server is online and make sure that has our public keys in it');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
app/Notifications/ServerUpdateFailed.php
Normal file
33
app/Notifications/ServerUpdateFailed.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
|
class ServerUpdateFailed extends AbstractNotification
|
||||||
|
{
|
||||||
|
protected Server $server;
|
||||||
|
|
||||||
|
public function __construct(Server $server)
|
||||||
|
{
|
||||||
|
$this->server = $server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rawText(): string
|
||||||
|
{
|
||||||
|
return __("Update failed for server [:server] \nCheck your server's logs \n:logs", [
|
||||||
|
'server' => $this->server->name,
|
||||||
|
'logs' => url('/servers/'.$this->server->id.'/logs'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toEmail(object $notifiable): MailMessage
|
||||||
|
{
|
||||||
|
return (new MailMessage)
|
||||||
|
->subject(__('Server update failed!'))
|
||||||
|
->line('Your server ['.$this->server->name.'] update has been failed.')
|
||||||
|
->line('Check your server logs')
|
||||||
|
->action('View Logs', url('/servers/'.$this->server->id.'/logs'));
|
||||||
|
}
|
||||||
|
}
|
@ -7,9 +7,7 @@
|
|||||||
|
|
||||||
class SiteInstallationFailed extends AbstractNotification
|
class SiteInstallationFailed extends AbstractNotification
|
||||||
{
|
{
|
||||||
public function __construct(protected Site $site)
|
public function __construct(protected Site $site) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function rawText(): string
|
public function rawText(): string
|
||||||
{
|
{
|
||||||
|
@ -7,9 +7,7 @@
|
|||||||
|
|
||||||
class SiteInstallationSucceed extends AbstractNotification
|
class SiteInstallationSucceed extends AbstractNotification
|
||||||
{
|
{
|
||||||
public function __construct(protected Site $site)
|
public function __construct(protected Site $site) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function rawText(): string
|
public function rawText(): string
|
||||||
{
|
{
|
||||||
|
@ -7,9 +7,7 @@
|
|||||||
|
|
||||||
class SourceControlDisconnected extends AbstractNotification
|
class SourceControlDisconnected extends AbstractNotification
|
||||||
{
|
{
|
||||||
public function __construct(protected SourceControl $sourceControl)
|
public function __construct(protected SourceControl $sourceControl) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function rawText(): string
|
public function rawText(): string
|
||||||
{
|
{
|
||||||
|
43
app/Policies/ScriptPolicy.php
Normal file
43
app/Policies/ScriptPolicy.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\Script;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||||
|
|
||||||
|
class ScriptPolicy
|
||||||
|
{
|
||||||
|
use HandlesAuthorization;
|
||||||
|
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Script $script): bool
|
||||||
|
{
|
||||||
|
return $user->id === $script->user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Script $script): bool
|
||||||
|
{
|
||||||
|
return $user->id === $script->user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(User $user, Script $script, Server $server): bool
|
||||||
|
{
|
||||||
|
return $user->id === $script->user_id && $server->project->users->contains($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, Script $script): bool
|
||||||
|
{
|
||||||
|
return $user->id === $script->user_id;
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Helpers\FTP;
|
||||||
use App\Helpers\Notifier;
|
use App\Helpers\Notifier;
|
||||||
use App\Helpers\SSH;
|
use App\Helpers\SSH;
|
||||||
use App\Helpers\Toast;
|
use App\Helpers\Toast;
|
||||||
@ -36,5 +37,8 @@ public function boot(): void
|
|||||||
$this->app->bind('toast', function () {
|
$this->app->bind('toast', function () {
|
||||||
return new Toast;
|
return new Toast;
|
||||||
});
|
});
|
||||||
|
$this->app->bind('ftp', function () {
|
||||||
|
return new FTP;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ public function installDependencies(Site $site): void
|
|||||||
$site->server->ssh()->exec(
|
$site->server->ssh()->exec(
|
||||||
$this->getScript('composer-install.sh', [
|
$this->getScript('composer-install.sh', [
|
||||||
'path' => $site->path,
|
'path' => $site->path,
|
||||||
|
'php_version' => $site->php_version,
|
||||||
]),
|
]),
|
||||||
'composer-install',
|
'composer-install',
|
||||||
$site->id
|
$site->id
|
||||||
|
@ -2,6 +2,6 @@ if ! cd __path__; then
|
|||||||
echo 'VITO_SSH_ERROR' && exit 1
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev; then
|
if ! php__php_version__ /usr/local/bin/composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev; then
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
fi
|
fi
|
||||||
|
@ -6,9 +6,7 @@
|
|||||||
|
|
||||||
class Cron
|
class Cron
|
||||||
{
|
{
|
||||||
public function __construct(protected Server $server)
|
public function __construct(protected Server $server) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(string $user, string $cron): void
|
public function update(string $user, string $cron): void
|
||||||
{
|
{
|
||||||
|
20
app/SSH/HasS3Storage.php
Normal file
20
app/SSH/HasS3Storage.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\SSH;
|
||||||
|
|
||||||
|
trait HasS3Storage
|
||||||
|
{
|
||||||
|
private function prepareS3Path(string $path, string $prefix = ''): string
|
||||||
|
{
|
||||||
|
$path = trim($path);
|
||||||
|
$path = ltrim($path, '/');
|
||||||
|
$path = preg_replace('/[^a-zA-Z0-9\-_\.\/]/', '_', $path);
|
||||||
|
$path = preg_replace('/\/+/', '/', $path);
|
||||||
|
|
||||||
|
if ($prefix) {
|
||||||
|
$path = trim($prefix, '/').'/'.$path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
@ -2,17 +2,20 @@
|
|||||||
|
|
||||||
namespace App\SSH\OS;
|
namespace App\SSH\OS;
|
||||||
|
|
||||||
|
use App\Exceptions\SSHUploadFailed;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\ServerLog;
|
use App\Models\ServerLog;
|
||||||
use App\SSH\HasScripts;
|
use App\SSH\HasScripts;
|
||||||
|
use Illuminate\Filesystem\FilesystemAdapter;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class OS
|
class OS
|
||||||
{
|
{
|
||||||
use HasScripts;
|
use HasScripts;
|
||||||
|
|
||||||
public function __construct(protected Server $server)
|
public function __construct(protected Server $server) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function installDependencies(): void
|
public function installDependencies(): void
|
||||||
{
|
{
|
||||||
@ -30,6 +33,19 @@ public function upgrade(): void
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function availableUpdates(): int
|
||||||
|
{
|
||||||
|
$result = $this->server->ssh()->exec(
|
||||||
|
$this->getScript('available-updates.sh'),
|
||||||
|
'check-available-updates'
|
||||||
|
);
|
||||||
|
|
||||||
|
// -1 because the first line is not a package
|
||||||
|
$availableUpdates = str($result)->after('Available updates:')->trim()->toInteger() - 1;
|
||||||
|
|
||||||
|
return max($availableUpdates, 0);
|
||||||
|
}
|
||||||
|
|
||||||
public function createUser(string $user, string $password, string $key): void
|
public function createUser(string $user, string $password, string $key): void
|
||||||
{
|
{
|
||||||
$this->server->ssh()->exec(
|
$this->server->ssh()->exec(
|
||||||
@ -98,14 +114,25 @@ public function reboot(): void
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SSHUploadFailed
|
||||||
|
*/
|
||||||
public function editFile(string $path, ?string $content = null): void
|
public function editFile(string $path, ?string $content = null): void
|
||||||
{
|
{
|
||||||
$this->server->ssh()->exec(
|
$tmpName = Str::random(10).strtotime('now');
|
||||||
$this->getScript('edit-file.sh', [
|
try {
|
||||||
'path' => $path,
|
/** @var FilesystemAdapter $storageDisk */
|
||||||
'content' => $content ?? '',
|
$storageDisk = Storage::disk('local');
|
||||||
]),
|
$storageDisk->put($tmpName, $content);
|
||||||
|
$this->server->ssh()->upload(
|
||||||
|
$storageDisk->path($tmpName),
|
||||||
|
$path
|
||||||
);
|
);
|
||||||
|
} catch (Throwable) {
|
||||||
|
throw new SSHUploadFailed();
|
||||||
|
} finally {
|
||||||
|
$this->deleteTempFile($tmpName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function readFile(string $path): string
|
public function readFile(string $path): string
|
||||||
@ -127,19 +154,23 @@ public function tail(string $path, int $lines): string
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function runScript(string $path, string $script, ?ServerLog $serverLog): ServerLog
|
public function runScript(string $path, string $script, ?ServerLog $serverLog, ?string $user = null, ?array $variables = []): ServerLog
|
||||||
{
|
{
|
||||||
$ssh = $this->server->ssh();
|
$ssh = $this->server->ssh($user);
|
||||||
if ($serverLog) {
|
if ($serverLog) {
|
||||||
$ssh->setLog($serverLog);
|
$ssh->setLog($serverLog);
|
||||||
}
|
}
|
||||||
$ssh->exec(
|
$command = '';
|
||||||
$this->getScript('run-script.sh', [
|
foreach ($variables as $key => $variable) {
|
||||||
|
$command .= "$key=$variable".PHP_EOL;
|
||||||
|
}
|
||||||
|
$command .= $this->getScript('run-script.sh', [
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
'script' => $script,
|
'script' => $script,
|
||||||
]),
|
]);
|
||||||
'run-script'
|
$ssh->exec($command, 'run-script');
|
||||||
);
|
|
||||||
|
info($command);
|
||||||
|
|
||||||
return $ssh->log;
|
return $ssh->log;
|
||||||
}
|
}
|
||||||
@ -185,4 +216,11 @@ public function resourceInfo(): array
|
|||||||
'disk_free' => str($info)->after('disk_free:')->before(PHP_EOL)->toString(),
|
'disk_free' => str($info)->after('disk_free:')->before(PHP_EOL)->toString(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function deleteTempFile(string $name): void
|
||||||
|
{
|
||||||
|
if (Storage::disk('local')->exists($name)) {
|
||||||
|
Storage::disk('local')->delete($name);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
5
app/SSH/OS/scripts/available-updates.sh
Normal file
5
app/SSH/OS/scripts/available-updates.sh
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||||
|
|
||||||
|
AVAILABLE_UPDATES=$(sudo DEBIAN_FRONTEND=noninteractive apt list --upgradable | wc -l)
|
||||||
|
|
||||||
|
echo "Available updates:$AVAILABLE_UPDATES"
|
@ -1,3 +0,0 @@
|
|||||||
if ! echo "__content__" | tee __path__; then
|
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
|
||||||
fi
|
|
@ -1,3 +1,8 @@
|
|||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common curl zip unzip git gcc openssl
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common curl zip unzip git gcc openssl ufw
|
||||||
git config --global user.email "__email__"
|
git config --global user.email "__email__"
|
||||||
git config --global user.name "__name__"
|
git config --global user.name "__name__"
|
||||||
|
|
||||||
|
# Install Node.js
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install nodejs -y
|
||||||
|
@ -2,8 +2,3 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get clean
|
|||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
|
sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove -y
|
sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove -y
|
||||||
|
|
||||||
# Install Node.js
|
|
||||||
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -;
|
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install nodejs -y
|
|
||||||
|
@ -6,9 +6,7 @@
|
|||||||
|
|
||||||
abstract class AbstractService implements ServiceInterface
|
abstract class AbstractService implements ServiceInterface
|
||||||
{
|
{
|
||||||
public function __construct(protected Service $service)
|
public function __construct(protected Service $service) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function creationRules(array $input): array
|
public function creationRules(array $input): array
|
||||||
{
|
{
|
||||||
|
@ -117,6 +117,7 @@ public function deleteUser(string $username, string $host): void
|
|||||||
public function link(string $username, string $host, array $databases): void
|
public function link(string $username, string $host, array $databases): void
|
||||||
{
|
{
|
||||||
$ssh = $this->service->server->ssh();
|
$ssh = $this->service->server->ssh();
|
||||||
|
$version = $this->service->version;
|
||||||
|
|
||||||
foreach ($databases as $database) {
|
foreach ($databases as $database) {
|
||||||
$ssh->exec(
|
$ssh->exec(
|
||||||
@ -124,6 +125,7 @@ public function link(string $username, string $host, array $databases): void
|
|||||||
'username' => $username,
|
'username' => $username,
|
||||||
'host' => $host,
|
'host' => $host,
|
||||||
'database' => $database,
|
'database' => $database,
|
||||||
|
'version' => $version,
|
||||||
]),
|
]),
|
||||||
'link-user-to-database'
|
'link-user-to-database'
|
||||||
);
|
);
|
||||||
@ -132,10 +134,13 @@ public function link(string $username, string $host, array $databases): void
|
|||||||
|
|
||||||
public function unlink(string $username, string $host): void
|
public function unlink(string $username, string $host): void
|
||||||
{
|
{
|
||||||
|
$version = $this->service->version;
|
||||||
|
|
||||||
$this->service->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
$this->getScript($this->getScriptsDir().'/unlink.sh', [
|
$this->getScript($this->getScriptsDir().'/unlink.sh', [
|
||||||
'username' => $username,
|
'username' => $username,
|
||||||
'host' => $host,
|
'host' => $host,
|
||||||
|
'version' => $version,
|
||||||
]),
|
]),
|
||||||
'unlink-user-from-databases'
|
'unlink-user-from-databases'
|
||||||
);
|
);
|
||||||
|
14
app/SSH/Services/Database/scripts/mariadb/install-10.11.sh
Executable file
14
app/SSH/Services/Database/scripts/mariadb/install-10.11.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
wget https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
|
||||||
|
|
||||||
|
chmod +x mariadb_repo_setup
|
||||||
|
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive ./mariadb_repo_setup \
|
||||||
|
--mariadb-server-version="mariadb-10.11"
|
||||||
|
|
||||||
|
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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user