mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 05:56:16 +00:00
Compare commits
58 Commits
Author | SHA1 | Date | |
---|---|---|---|
e2dd9177f7 | |||
5a9e8d6799 | |||
868b70f530 | |||
d07e9bcad2 | |||
0cd815cce6 | |||
5ab6617b5d | |||
72b37c56fd | |||
8a4ef66946 | |||
4517ca7d2a | |||
75aed62d75 | |||
aaef73d89d | |||
f03a029e36 | |||
52d195710b | |||
ddacc32e64 | |||
2ae9a14d02 | |||
3019c3d213 | |||
c43869d255 | |||
18748f77ac | |||
052e28d2e3 | |||
87ec0af697 | |||
e9016737d4 | |||
f34d5eb82b | |||
12c500e125 | |||
2d566b853f | |||
ca93b521ec | |||
bce05d3171 | |||
929dd1dbaa | |||
2bcd145bea | |||
c0f903d4ca | |||
cca4ab7ae3 | |||
51e7325d3d | |||
ce085879c1 | |||
8a49003e9e | |||
dcc4276f09 | |||
f089779045 | |||
f1efb9a6c8 | |||
a7d472fb45 | |||
d01d406d3d | |||
c66c50835a | |||
b6179d6693 | |||
9244e69fd8 | |||
a7ba095919 | |||
807ae01646 | |||
cc896d82e9 | |||
d16d3c1385 | |||
3946cf6b34 | |||
165212fed2 | |||
f6b36dfefc | |||
33594f2dba | |||
f68d6c7ca2 | |||
d504588f95 | |||
a0af4e3e9d | |||
ca0e33be2f | |||
4d051330d6 | |||
ab2d6f64f3 | |||
d9a56f95dd | |||
884f18db63 | |||
536df65fc6 |
@ -12,5 +12,5 @@ MAIL_PORT=
|
|||||||
MAIL_USERNAME=null
|
MAIL_USERNAME=null
|
||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
MAIL_FROM_ADDRESS=null
|
MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
@ -12,5 +12,5 @@ MAIL_PORT=
|
|||||||
MAIL_USERNAME=null
|
MAIL_USERNAME=null
|
||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
MAIL_FROM_ADDRESS=null
|
MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
@ -13,5 +13,5 @@ MAIL_PORT=
|
|||||||
MAIL_USERNAME=null
|
MAIL_USERNAME=null
|
||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
MAIL_FROM_ADDRESS=null
|
MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
35
.github/workflows/docker-1x.yml
vendored
Normal file
35
.github/workflows/docker-1x.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: Build and push Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 1.x
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
run: |
|
||||||
|
docker buildx build . \
|
||||||
|
-f docker/Dockerfile \
|
||||||
|
-t vitodeploy/vito:1.x \
|
||||||
|
--build-arg="RELEASE=0" \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--no-cache \
|
||||||
|
--push
|
35
.github/workflows/docker-release.yml
vendored
Normal file
35
.github/workflows/docker-release.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: Build and push Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
run: |
|
||||||
|
docker buildx build . \
|
||||||
|
-f docker/Dockerfile \
|
||||||
|
-t vitodeploy/vito:${{ github.event.release.tag_name }} \
|
||||||
|
-t vitodeploy/vito:latest \
|
||||||
|
--build-arg="RELEASE=0" \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--no-cache \
|
||||||
|
--push
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,6 +7,8 @@
|
|||||||
/storage/test-key
|
/storage/test-key
|
||||||
/storage/test-key.pub
|
/storage/test-key.pub
|
||||||
/vendor
|
/vendor
|
||||||
|
/storage/database.sqlite
|
||||||
|
/storage/database-test.sqlite
|
||||||
.env
|
.env
|
||||||
.env.backup
|
.env.backup
|
||||||
.env.production
|
.env.production
|
||||||
|
@ -1,23 +1,5 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
Thank you for your interest in contributing! There are a couple of contribution guidelines that make it easier to apply the incoming suggestions.
|
|
||||||
|
|
||||||
If you want to contribute please start with the issues. Issues labeled with "Bug" are the higher priorities.
|
Please read the contribution guide on the website
|
||||||
|
|
||||||
## Issues
|
https://vitodeploy.com/introduction/contribution-guide.html
|
||||||
1. Issues are the best place to propose a new feature.
|
|
||||||
2. If you are adding a feature that there is no issue for yet, please first open an issue and label it as "feature" and lets discuss it before you implement it.
|
|
||||||
3. Search the issues before proposing a feature to see if it is already under discussion. Referencing existing issues is a good way to increase the priority of your own.
|
|
||||||
4. We don't have an issue template yet, but the more detailed your explanation, the more quickly we'll be able to evaluate it.
|
|
||||||
5. Search for the issue that you also have. Give it a reaction (and comment, if you have something to add). We note that!
|
|
||||||
|
|
||||||
## Pull Requests
|
|
||||||
1. Open PRs represent issues that we're actively thinking about merging (at a pace we can manage). If we think a proposal needs more discussion, or that the existing code would require a lot of back-and-forth to merge, we might close it and suggest you make an issue.
|
|
||||||
2. All PRs should be made against the `main` branch. This can be changed in the future.
|
|
||||||
3. If you are making changes to the front-end layer, Please build the assets via `npm run build` and push it with the other changes.
|
|
||||||
4. Write tests for your code. Tests can be Unit or Feature.
|
|
||||||
5. Code refactors will be closed. For the architectural refactors open an issue first.
|
|
||||||
6. Use `./vendor/bin/pint` to style your code before opening a PR otherwise the actions will fail.
|
|
||||||
7. Typo fixes in documentation are welcome, but if it's at all debatable we might just close it.
|
|
||||||
|
|
||||||
## Misc
|
|
||||||
1. If you think we closed something incorrectly, feel free to (politely) tell us why! We're human and make mistakes.
|
|
||||||
|
@ -36,8 +36,8 @@ ## Useful Links
|
|||||||
- [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)
|
- [Feedbacks](https://vitodeploy.featurebase.app)
|
||||||
- [Roadmap](https://vitodeploy.featurebase.app/roadmap)
|
- [Roadmap](https://vitodeploy.featurebase.app/roadmap)
|
||||||
- [Video Demo](https://youtu.be/rLRHIyEfON8)
|
- [Video Demo](https://youtu.be/AbmUOBDOc28)
|
||||||
- [Discord](https://discord.gg/dcUWA5DV)
|
- [Discord](https://discord.gg/uZeeHZZnm5)
|
||||||
- [Contribution](/CONTRIBUTING.md)
|
- [Contribution](/CONTRIBUTING.md)
|
||||||
- [Security](/SECURITY.md)
|
- [Security](/SECURITY.md)
|
||||||
|
|
||||||
@ -54,3 +54,4 @@ ## Credits
|
|||||||
- Prettier
|
- Prettier
|
||||||
- Postcss
|
- Postcss
|
||||||
- Flowbite
|
- Flowbite
|
||||||
|
- svgrepo.com
|
||||||
|
150
app/Actions/Monitoring/GetMetrics.php
Normal file
150
app/Actions/Monitoring/GetMetrics.php
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Monitoring;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Contracts\Database\Query\Expression;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class GetMetrics
|
||||||
|
{
|
||||||
|
public function filter(Server $server, array $input): array
|
||||||
|
{
|
||||||
|
if (isset($input['from']) && isset($input['to']) && $input['from'] === $input['to']) {
|
||||||
|
$input['from'] = Carbon::parse($input['from'])->format('Y-m-d').' 00:00:00';
|
||||||
|
$input['to'] = Carbon::parse($input['to'])->format('Y-m-d').' 23:59:59';
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultInput = [
|
||||||
|
'period' => '10m',
|
||||||
|
];
|
||||||
|
|
||||||
|
$input = array_merge($defaultInput, $input);
|
||||||
|
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
return $this->metrics(
|
||||||
|
server: $server,
|
||||||
|
fromDate: $this->getFromDate($input),
|
||||||
|
toDate: $this->getToDate($input),
|
||||||
|
interval: $this->getInterval($input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function metrics(
|
||||||
|
Server $server,
|
||||||
|
Carbon $fromDate,
|
||||||
|
Carbon $toDate,
|
||||||
|
?Expression $interval = null
|
||||||
|
): array {
|
||||||
|
$metrics = DB::table('metrics')
|
||||||
|
->where('server_id', $server->id)
|
||||||
|
->whereBetween('created_at', [$fromDate->format('Y-m-d H:i:s'), $toDate->format('Y-m-d H:i:s')])
|
||||||
|
->select(
|
||||||
|
[
|
||||||
|
DB::raw('created_at as date'),
|
||||||
|
DB::raw('ROUND(AVG(load), 2) as load'),
|
||||||
|
DB::raw('ROUND(AVG(memory_total), 2) as memory_total'),
|
||||||
|
DB::raw('ROUND(AVG(memory_used), 2) as memory_used'),
|
||||||
|
DB::raw('ROUND(AVG(memory_free), 2) as memory_free'),
|
||||||
|
DB::raw('ROUND(AVG(disk_total), 2) as disk_total'),
|
||||||
|
DB::raw('ROUND(AVG(disk_used), 2) as disk_used'),
|
||||||
|
DB::raw('ROUND(AVG(disk_free), 2) as disk_free'),
|
||||||
|
$interval,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
->groupByRaw('date_interval')
|
||||||
|
->orderBy('date_interval')
|
||||||
|
->get()
|
||||||
|
->map(function ($item) {
|
||||||
|
$item->date = Carbon::parse($item->date)->format('Y-m-d H:i');
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
'metrics' => $metrics,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFromDate(array $input): Carbon
|
||||||
|
{
|
||||||
|
if ($input['period'] === 'custom') {
|
||||||
|
return new Carbon($input['from']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Carbon::parse('-'.convert_time_format($input['period']));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getToDate(array $input): Carbon
|
||||||
|
{
|
||||||
|
if ($input['period'] === 'custom') {
|
||||||
|
return new Carbon($input['to']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Carbon::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getInterval(array $input): Expression
|
||||||
|
{
|
||||||
|
if ($input['period'] === 'custom') {
|
||||||
|
$from = new Carbon($input['from']);
|
||||||
|
$to = new Carbon($input['to']);
|
||||||
|
$periodInHours = $from->diffInHours($to);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($periodInHours)) {
|
||||||
|
$periodInHours = Carbon::parse(
|
||||||
|
convert_time_format($input['period'])
|
||||||
|
)->diffInHours();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($periodInHours <= 1) {
|
||||||
|
return DB::raw("strftime('%Y-%m-%d %H:%M:00', created_at) as date_interval");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($periodInHours <= 24) {
|
||||||
|
return DB::raw("strftime('%Y-%m-%d %H:00:00', created_at) as date_interval");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($periodInHours > 24) {
|
||||||
|
return DB::raw("strftime('%Y-%m-%d 00:00:00', created_at) as date_interval");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'period' => [
|
||||||
|
'required',
|
||||||
|
Rule::in([
|
||||||
|
'10m',
|
||||||
|
'30m',
|
||||||
|
'1h',
|
||||||
|
'12h',
|
||||||
|
'1d',
|
||||||
|
'7d',
|
||||||
|
'custom',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
|
||||||
|
if ($input['period'] === 'custom') {
|
||||||
|
Validator::make($input, [
|
||||||
|
'from' => [
|
||||||
|
'required',
|
||||||
|
'date',
|
||||||
|
'before:to',
|
||||||
|
],
|
||||||
|
'to' => [
|
||||||
|
'required',
|
||||||
|
'date',
|
||||||
|
'after:from',
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
app/Actions/Monitoring/UpdateMetricSettings.php
Normal file
32
app/Actions/Monitoring/UpdateMetricSettings.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Monitoring;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class UpdateMetricSettings
|
||||||
|
{
|
||||||
|
public function update(Server $server, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$service = $server->monitoring();
|
||||||
|
|
||||||
|
$data = $service->handler()->data();
|
||||||
|
$data['data_retention'] = $input['data_retention'];
|
||||||
|
$service->type_data = $data;
|
||||||
|
$service->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'data_retention' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(config('core.metrics_data_retention')),
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Enums\ServiceStatus;
|
use App\Enums\ServiceStatus;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\SSH\Services\PHP\PHP;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
class ChangeDefaultCli
|
class ChangeDefaultCli
|
||||||
@ -12,7 +13,9 @@ public function change(Server $server, array $input): void
|
|||||||
{
|
{
|
||||||
$this->validate($server, $input);
|
$this->validate($server, $input);
|
||||||
$service = $server->php($input['version']);
|
$service = $server->php($input['version']);
|
||||||
$service->handler()->setDefaultCli();
|
/** @var PHP $handler */
|
||||||
|
$handler = $service->handler();
|
||||||
|
$handler->setDefaultCli();
|
||||||
$server->defaultService('php')->update(['is_default' => 0]);
|
$server->defaultService('php')->update(['is_default' => 0]);
|
||||||
$service->update(['is_default' => 1]);
|
$service->update(['is_default' => 1]);
|
||||||
$service->update(['status' => ServiceStatus::READY]);
|
$service->update(['status' => ServiceStatus::READY]);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Actions\PHP;
|
namespace App\Actions\PHP;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\SSH\Services\PHP\PHP;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
class GetPHPIni
|
class GetPHPIni
|
||||||
@ -14,7 +15,10 @@ public function getIni(Server $server, array $input): string
|
|||||||
$php = $server->php($input['version']);
|
$php = $server->php($input['version']);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return $php->handler()->getPHPIni();
|
/** @var PHP $handler */
|
||||||
|
$handler = $php->handler();
|
||||||
|
|
||||||
|
return $handler->getPHPIni();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
throw ValidationException::withMessages(
|
throw ValidationException::withMessages(
|
||||||
['ini' => $e->getMessage()]
|
['ini' => $e->getMessage()]
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\SSH\Services\PHP\PHP;
|
||||||
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;
|
||||||
@ -23,7 +24,9 @@ public function install(Server $server, array $input): Service
|
|||||||
$service->save();
|
$service->save();
|
||||||
|
|
||||||
dispatch(function () use ($service, $input) {
|
dispatch(function () use ($service, $input) {
|
||||||
$service->handler()->installExtension($input['extension']);
|
/** @var PHP $handler */
|
||||||
|
$handler = $service->handler();
|
||||||
|
$handler->installExtension($input['extension']);
|
||||||
})->catch(function () use ($service, $input) {
|
})->catch(function () use ($service, $input) {
|
||||||
$service->refresh();
|
$service->refresh();
|
||||||
$typeData = $service->type_data;
|
$typeData = $service->type_data;
|
||||||
|
@ -34,8 +34,6 @@ public function create(Site $site, array $input): void
|
|||||||
$ssl->status = SslStatus::CREATED;
|
$ssl->status = SslStatus::CREATED;
|
||||||
$ssl->save();
|
$ssl->save();
|
||||||
$site->type()->edit();
|
$site->type()->edit();
|
||||||
})->catch(function () use ($ssl) {
|
|
||||||
$ssl->delete();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
app/Actions/Server/CreateServerLog.php
Executable file
35
app/Actions/Server/CreateServerLog.php
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class CreateServerLog
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
public function create(Server $server, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$server->logs()->create([
|
||||||
|
'is_remote' => true,
|
||||||
|
'name' => $input['path'],
|
||||||
|
'type' => 'remote',
|
||||||
|
'disk' => 'ssh',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
protected function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'path' => 'required',
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
57
app/Actions/Service/Install.php
Normal file
57
app/Actions/Service/Install.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use App\Enums\ServiceStatus;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class Install
|
||||||
|
{
|
||||||
|
public function install(Server $server, array $input): Service
|
||||||
|
{
|
||||||
|
$this->validate($server, $input);
|
||||||
|
|
||||||
|
$service = new Service([
|
||||||
|
'server_id' => $server->id,
|
||||||
|
'name' => $input['name'],
|
||||||
|
'type' => $input['type'],
|
||||||
|
'version' => $input['version'],
|
||||||
|
'status' => ServiceStatus::INSTALLING,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Validator::make($input, $service->handler()->creationRules($input))->validate();
|
||||||
|
|
||||||
|
$service->type_data = $service->handler()->creationData($input);
|
||||||
|
|
||||||
|
$service->save();
|
||||||
|
|
||||||
|
dispatch(function () use ($service) {
|
||||||
|
$service->handler()->install();
|
||||||
|
$service->status = ServiceStatus::READY;
|
||||||
|
$service->save();
|
||||||
|
})->catch(function () use ($service) {
|
||||||
|
$service->status = ServiceStatus::INSTALLATION_FAILED;
|
||||||
|
$service->save();
|
||||||
|
})->onConnection('ssh');
|
||||||
|
|
||||||
|
return $service;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(Server $server, array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'type' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(config('core.service_types')),
|
||||||
|
],
|
||||||
|
'name' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(array_keys(config('core.service_types'))),
|
||||||
|
],
|
||||||
|
'version' => 'required',
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
28
app/Actions/Service/Uninstall.php
Normal file
28
app/Actions/Service/Uninstall.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use App\Enums\ServiceStatus;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class Uninstall
|
||||||
|
{
|
||||||
|
public function uninstall(Service $service): void
|
||||||
|
{
|
||||||
|
Validator::make([
|
||||||
|
'service' => $service->id,
|
||||||
|
], $service->handler()->deletionRules())->validate();
|
||||||
|
|
||||||
|
$service->status = ServiceStatus::UNINSTALLING;
|
||||||
|
$service->save();
|
||||||
|
|
||||||
|
dispatch(function () use ($service) {
|
||||||
|
$service->handler()->uninstall();
|
||||||
|
$service->delete();
|
||||||
|
})->catch(function () use ($service) {
|
||||||
|
$service->status = ServiceStatus::FAILED;
|
||||||
|
$service->save();
|
||||||
|
})->onConnection('ssh');
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@
|
|||||||
namespace App\Actions\Site;
|
namespace App\Actions\Site;
|
||||||
|
|
||||||
use App\Enums\SiteStatus;
|
use App\Enums\SiteStatus;
|
||||||
|
use App\Exceptions\RepositoryNotFound;
|
||||||
|
use App\Exceptions\RepositoryPermissionDenied;
|
||||||
use App\Exceptions\SourceControlIsNotConnected;
|
use App\Exceptions\SourceControlIsNotConnected;
|
||||||
use App\Facades\Notifier;
|
use App\Facades\Notifier;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@ -19,7 +21,6 @@
|
|||||||
class CreateSite
|
class CreateSite
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @throws SourceControlIsNotConnected
|
|
||||||
* @throws ValidationException
|
* @throws ValidationException
|
||||||
*/
|
*/
|
||||||
public function create(Server $server, array $input): Site
|
public function create(Server $server, array $input): Site
|
||||||
@ -47,7 +48,15 @@ public function create(Server $server, array $input): Site
|
|||||||
}
|
}
|
||||||
} catch (SourceControlIsNotConnected) {
|
} catch (SourceControlIsNotConnected) {
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
'source_control' => __('Source control is not connected'),
|
'source_control' => 'Source control is not connected',
|
||||||
|
]);
|
||||||
|
} catch (RepositoryPermissionDenied) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'repository' => 'You do not have permission to access this repository',
|
||||||
|
]);
|
||||||
|
} catch (RepositoryNotFound) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'repository' => 'Repository not found',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
app/Actions/Site/UpdateSourceControl.php
Normal file
49
app/Actions/Site/UpdateSourceControl.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Site;
|
||||||
|
|
||||||
|
use App\Exceptions\RepositoryNotFound;
|
||||||
|
use App\Exceptions\RepositoryPermissionDenied;
|
||||||
|
use App\Exceptions\SourceControlIsNotConnected;
|
||||||
|
use App\Models\Site;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class UpdateSourceControl
|
||||||
|
{
|
||||||
|
public function update(Site $site, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
$site->source_control_id = $input['source_control'];
|
||||||
|
try {
|
||||||
|
if ($site->sourceControl()) {
|
||||||
|
$site->sourceControl()->getRepo($site->repository);
|
||||||
|
}
|
||||||
|
} catch (SourceControlIsNotConnected) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'source_control' => 'Source control is not connected',
|
||||||
|
]);
|
||||||
|
} catch (RepositoryPermissionDenied) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'repository' => 'You do not have permission to access this repository',
|
||||||
|
]);
|
||||||
|
} catch (RepositoryNotFound) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'repository' => 'Repository not found',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$site->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'source_control' => [
|
||||||
|
'required',
|
||||||
|
Rule::exists('source_controls', 'id'),
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
28
app/Console/Commands/DeleteOlderMetricsCommand.php
Normal file
28
app/Console/Commands/DeleteOlderMetricsCommand.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class DeleteOlderMetricsCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'metrics:delete-older-metrics';
|
||||||
|
|
||||||
|
protected $description = 'Delete older metrics from database';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
Service::query()->where('type', 'monitoring')->chunk(100, function ($services) {
|
||||||
|
$services->each(function ($service) {
|
||||||
|
$this->info("Deleting older metrics for service {$service->server->name}");
|
||||||
|
$service
|
||||||
|
->server
|
||||||
|
->metrics()
|
||||||
|
->where('created_at', '<', now()->subDays($service->handler()->data()['data_retention']))
|
||||||
|
->delete();
|
||||||
|
$this->info('Metrics deleted');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
31
app/Console/Commands/GetMetricsCommand.php
Normal file
31
app/Console/Commands/GetMetricsCommand.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
class GetMetricsCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'metrics:get';
|
||||||
|
|
||||||
|
protected $description = 'Get server metrics';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$checkedMetrics = 0;
|
||||||
|
Server::query()->whereHas('services', function (Builder $query) {
|
||||||
|
$query->where('type', 'monitoring')
|
||||||
|
->where('name', 'remote-monitor');
|
||||||
|
})->chunk(10, function ($servers) use (&$checkedMetrics) {
|
||||||
|
/** @var Server $server */
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
$info = $server->os()->resourceInfo();
|
||||||
|
$server->metrics()->create(array_merge($info, ['server_id' => $server->id]));
|
||||||
|
$checkedMetrics++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$this->info("Checked $checkedMetrics metrics");
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,8 @@ protected function schedule(Schedule $schedule): void
|
|||||||
$schedule->command('backups:run "0 0 * * *"')->daily();
|
$schedule->command('backups:run "0 0 * * *"')->daily();
|
||||||
$schedule->command('backups:run "0 0 * * 0"')->weekly();
|
$schedule->command('backups:run "0 0 * * 0"')->weekly();
|
||||||
$schedule->command('backups:run "0 0 1 * *"')->monthly();
|
$schedule->command('backups:run "0 0 1 * *"')->monthly();
|
||||||
|
$schedule->command('metrics:delete-older-metrics')->daily();
|
||||||
|
$schedule->command('metrics:get')->everyMinute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,7 +10,9 @@ final class Database
|
|||||||
|
|
||||||
const MYSQL80 = 'mysql80';
|
const MYSQL80 = 'mysql80';
|
||||||
|
|
||||||
const MARIADB = 'mariadb';
|
const MARIADB103 = 'mariadb103';
|
||||||
|
|
||||||
|
const MARIADB104 = 'mariadb104';
|
||||||
|
|
||||||
const POSTGRESQL12 = 'postgresql12';
|
const POSTGRESQL12 = 'postgresql12';
|
||||||
|
|
||||||
|
@ -11,4 +11,6 @@ final class SiteType
|
|||||||
const LARAVEL = 'laravel';
|
const LARAVEL = 'laravel';
|
||||||
|
|
||||||
const WORDPRESS = 'wordpress';
|
const WORDPRESS = 'wordpress';
|
||||||
|
|
||||||
|
const PHPMYADMIN = 'phpmyadmin';
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
use Exception;
|
class SSHAuthenticationError extends SSHError
|
||||||
|
|
||||||
class SSHAuthenticationError extends Exception
|
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
use Exception;
|
class SSLCreationException extends SSHError
|
||||||
|
|
||||||
class SSLCreationException extends Exception
|
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
* @method static init(Server $server, string $asUser = null)
|
* @method static init(Server $server, string $asUser = null)
|
||||||
* @method static setLog(string $logType, int $siteId = null)
|
* @method static setLog(string $logType, int $siteId = null)
|
||||||
* @method static connect()
|
* @method static connect()
|
||||||
* @method static string exec(string $command, string $log = '', int $siteId = null)
|
* @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 disconnect()
|
* @method static disconnect()
|
||||||
|
@ -96,7 +96,7 @@ public function connect(bool $sftp = false): void
|
|||||||
* @throws SSHCommandError
|
* @throws SSHCommandError
|
||||||
* @throws SSHConnectionError
|
* @throws SSHConnectionError
|
||||||
*/
|
*/
|
||||||
public function exec(string|array $commands, string $log = '', ?int $siteId = null): string
|
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
|
||||||
{
|
{
|
||||||
if ($log) {
|
if ($log) {
|
||||||
$this->setLog($log, $siteId);
|
$this->setLog($log, $siteId);
|
||||||
@ -112,18 +112,34 @@ public function exec(string|array $commands, string $log = '', ?int $siteId = nu
|
|||||||
throw new SSHConnectionError($e->getMessage());
|
throw new SSHConnectionError($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! is_array($commands)) {
|
|
||||||
$commands = [$commands];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$result = '';
|
if ($this->asUser) {
|
||||||
foreach ($commands as $command) {
|
$command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"';
|
||||||
$result .= $this->executeCommand($command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
$this->connection->setTimeout(0);
|
||||||
|
if ($stream) {
|
||||||
|
$this->connection->exec($command, function ($output) {
|
||||||
|
$this->log?->write($output);
|
||||||
|
echo $output;
|
||||||
|
ob_flush();
|
||||||
|
flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
$output = $this->connection->exec($command);
|
||||||
|
|
||||||
|
$this->log?->write($output);
|
||||||
|
|
||||||
|
if (Str::contains($output, 'VITO_SSH_ERROR')) {
|
||||||
|
throw new SSHCommandError('SSH command failed with an error');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
|
throw $e;
|
||||||
throw new SSHCommandError($e->getMessage());
|
throw new SSHCommandError($e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,28 +157,6 @@ public function upload(string $local, string $remote): void
|
|||||||
$this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
|
$this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
protected function executeCommand(string $command): string
|
|
||||||
{
|
|
||||||
if ($this->asUser) {
|
|
||||||
$command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->connection->setTimeout(0);
|
|
||||||
|
|
||||||
$output = $this->connection->exec($command);
|
|
||||||
|
|
||||||
$this->log?->write($output);
|
|
||||||
|
|
||||||
if (Str::contains($output, 'VITO_SSH_ERROR')) {
|
|
||||||
throw new Exception('SSH command failed with an error');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
|
36
app/Http/Controllers/API/AgentController.php
Normal file
36
app/Http/Controllers/API/AgentController.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class AgentController extends Controller
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Server $server, int $id): JsonResponse
|
||||||
|
{
|
||||||
|
$validated = $this->validate($request, [
|
||||||
|
'load' => 'required|numeric',
|
||||||
|
'memory_total' => 'required|numeric',
|
||||||
|
'memory_used' => 'required|numeric',
|
||||||
|
'memory_free' => 'required|numeric',
|
||||||
|
'disk_total' => 'required|numeric',
|
||||||
|
'disk_used' => 'required|numeric',
|
||||||
|
'disk_free' => 'required|numeric',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var Service $service */
|
||||||
|
$service = $server->services()->findOrFail($id);
|
||||||
|
|
||||||
|
if ($request->header('secret') !== $service->handler()->data()['secret']) {
|
||||||
|
return response()->json(['error' => 'Unauthorized'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$server->metrics()->create(array_merge($validated, ['server_id' => $server->id]));
|
||||||
|
|
||||||
|
return response()->json();
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Actions\Site\Deploy;
|
use App\Actions\Site\Deploy;
|
||||||
use App\Exceptions\SourceControlIsNotConnected;
|
use App\Exceptions\SourceControlIsNotConnected;
|
||||||
use App\Facades\Notifier;
|
use App\Facades\Notifier;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\GitHook;
|
use App\Models\GitHook;
|
||||||
use App\Models\ServerLog;
|
use App\Models\ServerLog;
|
||||||
use App\Notifications\SourceControlDisconnected;
|
use App\Notifications\SourceControlDisconnected;
|
16
app/Http/Controllers/API/HealthController.php
Normal file
16
app/Http/Controllers/API/HealthController.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
|
class HealthController extends Controller
|
||||||
|
{
|
||||||
|
public function __invoke()
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'version' => vito_version(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,8 @@
|
|||||||
use App\Actions\Site\UpdateDeploymentScript;
|
use App\Actions\Site\UpdateDeploymentScript;
|
||||||
use App\Actions\Site\UpdateEnv;
|
use App\Actions\Site\UpdateEnv;
|
||||||
use App\Exceptions\DeploymentScriptIsEmptyException;
|
use App\Exceptions\DeploymentScriptIsEmptyException;
|
||||||
|
use App\Exceptions\RepositoryNotFound;
|
||||||
|
use App\Exceptions\RepositoryPermissionDenied;
|
||||||
use App\Exceptions\SourceControlIsNotConnected;
|
use App\Exceptions\SourceControlIsNotConnected;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
@ -24,12 +26,14 @@ public function deploy(Server $server, Site $site): HtmxResponse
|
|||||||
app(Deploy::class)->run($site);
|
app(Deploy::class)->run($site);
|
||||||
|
|
||||||
Toast::success('Deployment started!');
|
Toast::success('Deployment started!');
|
||||||
} catch (SourceControlIsNotConnected $e) {
|
} catch (SourceControlIsNotConnected) {
|
||||||
Toast::error($e->getMessage());
|
Toast::error('Source control is not connected. Check site\'s settings.');
|
||||||
|
|
||||||
return htmx()->redirect(route('source-controls'));
|
|
||||||
} catch (DeploymentScriptIsEmptyException) {
|
} catch (DeploymentScriptIsEmptyException) {
|
||||||
Toast::error('Deployment script is empty!');
|
Toast::error('Deployment script is empty!');
|
||||||
|
} catch (RepositoryPermissionDenied) {
|
||||||
|
Toast::error('You do not have permission to access this repository!');
|
||||||
|
} catch (RepositoryNotFound) {
|
||||||
|
Toast::error('Repository not found!');
|
||||||
}
|
}
|
||||||
|
|
||||||
return htmx()->back();
|
return htmx()->back();
|
||||||
@ -83,6 +87,12 @@ public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
|
|||||||
Toast::success('Auto deployment has been enabled.');
|
Toast::success('Auto deployment has been enabled.');
|
||||||
} catch (SourceControlIsNotConnected) {
|
} catch (SourceControlIsNotConnected) {
|
||||||
Toast::error('Source control is not connected. Check site\'s settings.');
|
Toast::error('Source control is not connected. Check site\'s settings.');
|
||||||
|
} catch (DeploymentScriptIsEmptyException) {
|
||||||
|
Toast::error('Deployment script is empty!');
|
||||||
|
} catch (RepositoryPermissionDenied) {
|
||||||
|
Toast::error('You do not have permission to access this repository!');
|
||||||
|
} catch (RepositoryNotFound) {
|
||||||
|
Toast::error('Repository not found!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
43
app/Http/Controllers/ConsoleController.php
Normal file
43
app/Http/Controllers/ConsoleController.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ConsoleController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Server $server): View
|
||||||
|
{
|
||||||
|
return view('console.index', [
|
||||||
|
'server' => $server,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(Server $server, Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'user' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(['root', $server->ssh_user]),
|
||||||
|
],
|
||||||
|
'command' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->stream(
|
||||||
|
function () use ($server, $request) {
|
||||||
|
$ssh = $server->ssh($request->user);
|
||||||
|
$log = 'console-'.time();
|
||||||
|
$ssh->exec(command: $request->command, log: $log, stream: true);
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
[
|
||||||
|
'Cache-Control' => 'no-cache',
|
||||||
|
'X-Accel-Buffering' => 'no',
|
||||||
|
'Content-Type' => 'text/event-stream',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
44
app/Http/Controllers/MetricController.php
Normal file
44
app/Http/Controllers/MetricController.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Monitoring\GetMetrics;
|
||||||
|
use App\Actions\Monitoring\UpdateMetricSettings;
|
||||||
|
use App\Facades\Toast;
|
||||||
|
use App\Helpers\HtmxResponse;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class MetricController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Server $server, Request $request): View|RedirectResponse
|
||||||
|
{
|
||||||
|
$this->checkIfMonitoringServiceInstalled($server);
|
||||||
|
|
||||||
|
return view('metrics.index', [
|
||||||
|
'server' => $server,
|
||||||
|
'data' => app(GetMetrics::class)->filter($server, $request->input()),
|
||||||
|
'lastMetric' => $server->metrics()->latest()->first(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function settings(Server $server, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
$this->checkIfMonitoringServiceInstalled($server);
|
||||||
|
|
||||||
|
app(UpdateMetricSettings::class)->update($server, $request->input());
|
||||||
|
|
||||||
|
Toast::success('Metric settings updated successfully');
|
||||||
|
|
||||||
|
return htmx()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkIfMonitoringServiceInstalled(Server $server): void
|
||||||
|
{
|
||||||
|
if (! $server->monitoring()) {
|
||||||
|
abort(404, 'Monitoring service is not installed on this server');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -71,7 +71,9 @@ public function updateIni(Server $server, Request $request): RedirectResponse
|
|||||||
|
|
||||||
Toast::success('PHP ini updated!');
|
Toast::success('PHP ini updated!');
|
||||||
|
|
||||||
return back();
|
return back()->with([
|
||||||
|
'ini' => $request->input('ini'),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function uninstall(Server $server, Request $request): RedirectResponse
|
public function uninstall(Server $server, Request $request): RedirectResponse
|
||||||
|
@ -54,7 +54,6 @@ public function show(Server $server): View
|
|||||||
{
|
{
|
||||||
return view('servers.show', [
|
return view('servers.show', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'logs' => $server->logs()->latest()->limit(10)->get(),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Server\CreateServerLog;
|
||||||
|
use App\Facades\Toast;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\ServerLog;
|
use App\Models\ServerLog;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class ServerLogController extends Controller
|
class ServerLogController extends Controller
|
||||||
{
|
{
|
||||||
@ -13,6 +16,7 @@ public function index(Server $server): View
|
|||||||
{
|
{
|
||||||
return view('server-logs.index', [
|
return view('server-logs.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
|
'pageTitle' => __('Vito Logs'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,4 +30,31 @@ public function show(Server $server, ServerLog $serverLog): RedirectResponse
|
|||||||
'content' => $serverLog->getContent(),
|
'content' => $serverLog->getContent(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function remote(Server $server): View
|
||||||
|
{
|
||||||
|
return view('server-logs.remote-logs', [
|
||||||
|
'server' => $server,
|
||||||
|
'remote' => true,
|
||||||
|
'pageTitle' => __('Remote Logs'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Server $server, Request $request): \App\Helpers\HtmxResponse
|
||||||
|
{
|
||||||
|
app(CreateServerLog::class)->create($server, $request->input());
|
||||||
|
|
||||||
|
Toast::success('Log added successfully.');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('servers.logs.remote', ['server' => $server]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Server $server, ServerLog $serverLog): RedirectResponse
|
||||||
|
{
|
||||||
|
$serverLog->delete();
|
||||||
|
|
||||||
|
Toast::success('Remote log deleted successfully.');
|
||||||
|
|
||||||
|
return redirect()->route('servers.logs.remote', ['server' => $server]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Service\Install;
|
||||||
|
use App\Actions\Service\Uninstall;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
|
use App\Helpers\HtmxResponse;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class ServiceController extends Controller
|
class ServiceController extends Controller
|
||||||
{
|
{
|
||||||
@ -62,4 +66,22 @@ public function disable(Server $server, Service $service): RedirectResponse
|
|||||||
|
|
||||||
return back();
|
return back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function install(Server $server, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
app(Install::class)->install($server, $request->input());
|
||||||
|
|
||||||
|
Toast::success('Service is being uninstalled!');
|
||||||
|
|
||||||
|
return htmx()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(Server $server, Service $service): HtmxResponse
|
||||||
|
{
|
||||||
|
app(Uninstall::class)->uninstall($service);
|
||||||
|
|
||||||
|
Toast::success('Service is being uninstalled!');
|
||||||
|
|
||||||
|
return htmx()->back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Actions\Site\CreateSite;
|
use App\Actions\Site\CreateSite;
|
||||||
use App\Actions\Site\DeleteSite;
|
use App\Actions\Site\DeleteSite;
|
||||||
|
use App\Enums\SiteStatus;
|
||||||
use App\Enums\SiteType;
|
use App\Enums\SiteType;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
@ -42,14 +43,38 @@ public function create(Server $server): View
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Server $server, Site $site): View
|
public function show(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
||||||
{
|
{
|
||||||
|
if (in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
||||||
|
if ($request->hasHeader('HX-Request')) {
|
||||||
|
return htmx()->redirect(route('servers.sites.installing', [$server, $site]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('servers.sites.installing', [$server, $site]);
|
||||||
|
}
|
||||||
|
|
||||||
return view('sites.show', [
|
return view('sites.show', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'site' => $site,
|
'site' => $site,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function installing(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
||||||
|
{
|
||||||
|
if (! in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
||||||
|
if ($request->hasHeader('HX-Request')) {
|
||||||
|
return htmx()->redirect(route('servers.sites.show', [$server, $site]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('servers.sites.show', [$server, $site]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('sites.installing', [
|
||||||
|
'server' => $server,
|
||||||
|
'site' => $site,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function destroy(Server $server, Site $site): RedirectResponse
|
public function destroy(Server $server, Site $site): RedirectResponse
|
||||||
{
|
{
|
||||||
app(DeleteSite::class)->delete($site);
|
app(DeleteSite::class)->delete($site);
|
||||||
|
@ -13,6 +13,7 @@ public function index(Server $server, Site $site): View
|
|||||||
return view('site-logs.index', [
|
return view('site-logs.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'site' => $site,
|
'site' => $site,
|
||||||
|
'pageTitle' => __('Vito Logs'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Site\UpdateSourceControl;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Helpers\HtmxResponse;
|
use App\Helpers\HtmxResponse;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Site;
|
use App\Models\Site;
|
||||||
|
use App\SSH\Services\Webserver\Webserver;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -24,7 +26,10 @@ public function index(Server $server, Site $site): View
|
|||||||
|
|
||||||
public function getVhost(Server $server, Site $site): RedirectResponse
|
public function getVhost(Server $server, Site $site): RedirectResponse
|
||||||
{
|
{
|
||||||
return back()->with('vhost', $server->webserver()->handler()->getVHost($site));
|
/** @var Webserver $handler */
|
||||||
|
$handler = $server->webserver()->handler();
|
||||||
|
|
||||||
|
return back()->with('vhost', $handler->getVHost($site));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse
|
public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse
|
||||||
@ -34,7 +39,9 @@ public function updateVhost(Server $server, Site $site, Request $request): Redir
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$server->webserver()->handler()->updateVHost($site, false, $request->input('vhost'));
|
/** @var Webserver $handler */
|
||||||
|
$handler = $server->webserver()->handler();
|
||||||
|
$handler->updateVHost($site, false, $request->input('vhost'));
|
||||||
|
|
||||||
Toast::success('VHost updated successfully!');
|
Toast::success('VHost updated successfully!');
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
@ -63,4 +70,13 @@ public function updatePHPVersion(Server $server, Site $site, Request $request):
|
|||||||
|
|
||||||
return htmx()->back();
|
return htmx()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateSourceControl(Server $server, Site $site, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
$site = app(UpdateSourceControl::class)->update($site, $request->input());
|
||||||
|
|
||||||
|
Toast::success('Source control updated successfully!');
|
||||||
|
|
||||||
|
return htmx()->back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,14 @@
|
|||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
|
||||||
class HandleSSHErrors
|
class HandleSSHErrors
|
||||||
{
|
{
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
{
|
{
|
||||||
$res = $next($request);
|
$res = $next($request);
|
||||||
if ($res->exception) {
|
if ($res instanceof Response && $res->exception) {
|
||||||
if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) {
|
if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) {
|
||||||
Toast::error($res->exception->getMessage());
|
Toast::error($res->exception->getMessage());
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class TrustProxies extends Middleware
|
|||||||
*
|
*
|
||||||
* @var array<int, string>|string|null
|
* @var array<int, string>|string|null
|
||||||
*/
|
*/
|
||||||
protected $proxies;
|
protected $proxies = '*';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The headers that should be used to detect proxies.
|
* The headers that should be used to detect proxies.
|
||||||
@ -20,7 +20,7 @@ class TrustProxies extends Middleware
|
|||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
protected $headers =
|
protected $headers =
|
||||||
Request::HEADER_X_FORWARDED_FOR |
|
Request::HEADER_X_FORWARDED_FOR |
|
||||||
Request::HEADER_X_FORWARDED_HOST |
|
Request::HEADER_X_FORWARDED_HOST |
|
||||||
Request::HEADER_X_FORWARDED_PORT |
|
Request::HEADER_X_FORWARDED_PORT |
|
||||||
Request::HEADER_X_FORWARDED_PROTO |
|
Request::HEADER_X_FORWARDED_PROTO |
|
||||||
|
53
app/Models/Metric.php
Normal file
53
app/Models/Metric.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property int $server_id
|
||||||
|
* @property float $load
|
||||||
|
* @property float $memory_total
|
||||||
|
* @property float $memory_used
|
||||||
|
* @property float $memory_free
|
||||||
|
* @property float $disk_total
|
||||||
|
* @property float $disk_used
|
||||||
|
* @property float $disk_free
|
||||||
|
* @property Server $server
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
*/
|
||||||
|
class Metric extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'server_id',
|
||||||
|
'load',
|
||||||
|
'memory_total',
|
||||||
|
'memory_used',
|
||||||
|
'memory_free',
|
||||||
|
'disk_total',
|
||||||
|
'disk_used',
|
||||||
|
'disk_free',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'server_id' => 'integer',
|
||||||
|
'load' => 'float',
|
||||||
|
'memory_total' => 'float',
|
||||||
|
'memory_used' => 'float',
|
||||||
|
'memory_free' => 'float',
|
||||||
|
'disk_total' => 'float',
|
||||||
|
'disk_used' => 'float',
|
||||||
|
'disk_free' => 'float',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function server(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Server::class);
|
||||||
|
}
|
||||||
|
}
|
@ -195,6 +195,11 @@ public function daemons(): HasMany
|
|||||||
return $this->queues()->whereNull('site_id');
|
return $this->queues()->whereNull('site_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function metrics(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Metric::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function sshKeys(): BelongsToMany
|
public function sshKeys(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(SshKey::class, 'server_ssh_keys')
|
return $this->belongsToMany(SshKey::class, 'server_ssh_keys')
|
||||||
@ -325,6 +330,24 @@ public function php(?string $version = null): ?Service
|
|||||||
return $this->service('php', $version);
|
return $this->service('php', $version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function memoryDatabase(?string $version = null): ?Service
|
||||||
|
{
|
||||||
|
if (! $version) {
|
||||||
|
return $this->defaultService('memory_database');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->service('memory_database', $version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function monitoring(?string $version = null): ?Service
|
||||||
|
{
|
||||||
|
if (! $version) {
|
||||||
|
return $this->defaultService('monitoring');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->service('monitoring', $version);
|
||||||
|
}
|
||||||
|
|
||||||
public function sshKey(): array
|
public function sshKey(): array
|
||||||
{
|
{
|
||||||
/** @var FilesystemAdapter $storageDisk */
|
/** @var FilesystemAdapter $storageDisk */
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
* @property string $disk
|
* @property string $disk
|
||||||
* @property Server $server
|
* @property Server $server
|
||||||
* @property ?Site $site
|
* @property ?Site $site
|
||||||
|
* @property bool $is_remote
|
||||||
*/
|
*/
|
||||||
class ServerLog extends AbstractModel
|
class ServerLog extends AbstractModel
|
||||||
{
|
{
|
||||||
@ -27,11 +28,13 @@ class ServerLog extends AbstractModel
|
|||||||
'type',
|
'type',
|
||||||
'name',
|
'name',
|
||||||
'disk',
|
'disk',
|
||||||
|
'is_remote',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'server_id' => 'integer',
|
'server_id' => 'integer',
|
||||||
'site_id' => 'integer',
|
'site_id' => 'integer',
|
||||||
|
'is_remote' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
public static function boot(): void
|
public static function boot(): void
|
||||||
@ -64,6 +67,17 @@ public function site(): BelongsTo
|
|||||||
return $this->belongsTo(Site::class);
|
return $this->belongsTo(Site::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getRemote($query, bool $active = true, ?Site $site = null)
|
||||||
|
{
|
||||||
|
$query->where('is_remote', $active);
|
||||||
|
|
||||||
|
if ($site) {
|
||||||
|
$query->where('name', 'like', $site->path.'%');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
public function write($buf): void
|
public function write($buf): void
|
||||||
{
|
{
|
||||||
if (Str::contains($buf, 'VITO_SSH_ERROR')) {
|
if (Str::contains($buf, 'VITO_SSH_ERROR')) {
|
||||||
@ -78,6 +92,10 @@ public function write($buf): void
|
|||||||
|
|
||||||
public function getContent(): ?string
|
public function getContent(): ?string
|
||||||
{
|
{
|
||||||
|
if ($this->is_remote) {
|
||||||
|
return $this->server->os()->tail($this->name, 150);
|
||||||
|
}
|
||||||
|
|
||||||
if (Storage::disk($this->disk)->exists($this->name)) {
|
if (Storage::disk($this->disk)->exists($this->name)) {
|
||||||
return Storage::disk($this->disk)->get($this->name);
|
return Storage::disk($this->disk)->get($this->name);
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,7 @@
|
|||||||
|
|
||||||
use App\Actions\Service\Manage;
|
use App\Actions\Service\Manage;
|
||||||
use App\Exceptions\ServiceInstallationFailed;
|
use App\Exceptions\ServiceInstallationFailed;
|
||||||
use App\SSH\Services\Database\Database as DatabaseHandler;
|
use App\SSH\Services\ServiceInterface;
|
||||||
use App\SSH\Services\Firewall\Firewall as FirewallHandler;
|
|
||||||
use App\SSH\Services\PHP\PHP as PHPHandler;
|
|
||||||
use App\SSH\Services\ProcessManager\ProcessManager as ProcessManagerHandler;
|
|
||||||
use App\SSH\Services\Redis\Redis as RedisHandler;
|
|
||||||
use App\SSH\Services\Webserver\Webserver as WebserverHandler;
|
|
||||||
use 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\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@ -53,7 +48,9 @@ public static function boot(): void
|
|||||||
parent::boot();
|
parent::boot();
|
||||||
|
|
||||||
static::creating(function (Service $service) {
|
static::creating(function (Service $service) {
|
||||||
$service->unit = config('core.service_units')[$service->name][$service->server->os][$service->version];
|
if (array_key_exists($service->name, config('core.service_units'))) {
|
||||||
|
$service->unit = config('core.service_units')[$service->name][$service->server->os][$service->version];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,8 +59,8 @@ public function server(): BelongsTo
|
|||||||
return $this->belongsTo(Server::class);
|
return $this->belongsTo(Server::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handler(
|
public function handler(): ServiceInterface
|
||||||
): PHPHandler|WebserverHandler|DatabaseHandler|FirewallHandler|ProcessManagerHandler|RedisHandler {
|
{
|
||||||
$handler = config('core.service_handlers')[$this->name];
|
$handler = config('core.service_handlers')[$this->name];
|
||||||
|
|
||||||
return new $handler($this);
|
return new $handler($this);
|
||||||
@ -81,26 +78,26 @@ public function validateInstall($result): void
|
|||||||
|
|
||||||
public function start(): void
|
public function start(): void
|
||||||
{
|
{
|
||||||
app(Manage::class)->start($this);
|
$this->unit && app(Manage::class)->start($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function stop(): void
|
public function stop(): void
|
||||||
{
|
{
|
||||||
app(Manage::class)->stop($this);
|
$this->unit && app(Manage::class)->stop($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function restart(): void
|
public function restart(): void
|
||||||
{
|
{
|
||||||
app(Manage::class)->restart($this);
|
$this->unit && app(Manage::class)->restart($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function enable(): void
|
public function enable(): void
|
||||||
{
|
{
|
||||||
app(Manage::class)->enable($this);
|
$this->unit && app(Manage::class)->enable($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function disable(): void
|
public function disable(): void
|
||||||
{
|
{
|
||||||
app(Manage::class)->disable($this);
|
$this->unit && app(Manage::class)->disable($this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Exceptions\SourceControlIsNotConnected;
|
use App\Exceptions\SourceControlIsNotConnected;
|
||||||
use App\SiteTypes\SiteType;
|
use App\SiteTypes\SiteType;
|
||||||
|
use App\SSH\Services\Webserver\Webserver;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
@ -185,7 +186,9 @@ public function php(): ?Service
|
|||||||
|
|
||||||
public function changePHPVersion($version): void
|
public function changePHPVersion($version): void
|
||||||
{
|
{
|
||||||
$this->server->webserver()->handler()->changePHPVersion($this, $version);
|
/** @var Webserver $handler */
|
||||||
|
$handler = $this->server->webserver()->handler();
|
||||||
|
$handler->changePHPVersion($this, $version);
|
||||||
$this->php_version = $version;
|
$this->php_version = $version;
|
||||||
$this->save();
|
$this->save();
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
use App\Helpers\Toast;
|
use App\Helpers\Toast;
|
||||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
use Illuminate\Support\Facades\URL;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
@ -37,9 +36,5 @@ public function boot(): void
|
|||||||
$this->app->bind('toast', function () {
|
$this->app->bind('toast', function () {
|
||||||
return new Toast;
|
return new Toast;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (str(config('app.url'))->startsWith('https://')) {
|
|
||||||
URL::forceScheme('https');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,12 +98,12 @@ public function reboot(): void
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function editFile(string $path, string $content): void
|
public function editFile(string $path, ?string $content = null): void
|
||||||
{
|
{
|
||||||
$this->server->ssh()->exec(
|
$this->server->ssh()->exec(
|
||||||
$this->getScript('edit-file.sh', [
|
$this->getScript('edit-file.sh', [
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
'content' => $content,
|
'content' => $content ?? '',
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -117,6 +117,16 @@ public function readFile(string $path): string
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tail(string $path, int $lines): string
|
||||||
|
{
|
||||||
|
return $this->server->ssh()->exec(
|
||||||
|
$this->getScript('tail.sh', [
|
||||||
|
'path' => $path,
|
||||||
|
'lines' => $lines,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function runScript(string $path, string $script, ?int $siteId = null): ServerLog
|
public function runScript(string $path, string $script, ?int $siteId = null): ServerLog
|
||||||
{
|
{
|
||||||
$ssh = $this->server->ssh();
|
$ssh = $this->server->ssh();
|
||||||
@ -131,4 +141,46 @@ public function runScript(string $path, string $script, ?int $siteId = null): Se
|
|||||||
|
|
||||||
return $ssh->log;
|
return $ssh->log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function download(string $url, string $path): string
|
||||||
|
{
|
||||||
|
return $this->server->ssh()->exec(
|
||||||
|
$this->getScript('download.sh', [
|
||||||
|
'url' => $url,
|
||||||
|
'path' => $path,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unzip(string $path): string
|
||||||
|
{
|
||||||
|
return $this->server->ssh()->exec(
|
||||||
|
'unzip '.$path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cleanup(): void
|
||||||
|
{
|
||||||
|
$this->server->ssh()->exec(
|
||||||
|
$this->getScript('cleanup.sh'),
|
||||||
|
'cleanup'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resourceInfo(): array
|
||||||
|
{
|
||||||
|
$info = $this->server->ssh()->exec(
|
||||||
|
$this->getScript('resource-info.sh'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'load' => str($info)->after('load:')->before(PHP_EOL)->toString(),
|
||||||
|
'memory_total' => str($info)->after('memory_total:')->before(PHP_EOL)->toString(),
|
||||||
|
'memory_used' => str($info)->after('memory_used:')->before(PHP_EOL)->toString(),
|
||||||
|
'memory_free' => str($info)->after('memory_free:')->before(PHP_EOL)->toString(),
|
||||||
|
'disk_total' => str($info)->after('disk_total:')->before(PHP_EOL)->toString(),
|
||||||
|
'disk_used' => str($info)->after('disk_used:')->before(PHP_EOL)->toString(),
|
||||||
|
'disk_free' => str($info)->after('disk_free:')->before(PHP_EOL)->toString(),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
19
app/SSH/OS/scripts/cleanup.sh
Normal file
19
app/SSH/OS/scripts/cleanup.sh
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Update package lists
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get update -y
|
||||||
|
|
||||||
|
# Remove unnecessary dependencies
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove --purge -y
|
||||||
|
|
||||||
|
# Clear package cache
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get clean -y
|
||||||
|
|
||||||
|
# Remove old configuration files
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get purge -y $(dpkg -l | grep '^rc' | awk '{print $2}')
|
||||||
|
|
||||||
|
# Clear temporary files
|
||||||
|
sudo rm -rf /tmp/*
|
||||||
|
|
||||||
|
# Clear journal logs
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive journalctl --vacuum-time=1d
|
||||||
|
|
||||||
|
echo "Cleanup completed."
|
3
app/SSH/OS/scripts/download.sh
Normal file
3
app/SSH/OS/scripts/download.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if ! wget __url__ -O __path__; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
@ -1 +1 @@
|
|||||||
[ -f __path__ ] && cat __path__
|
[ -f __path__ ] && sudo cat __path__
|
||||||
|
7
app/SSH/OS/scripts/resource-info.sh
Normal file
7
app/SSH/OS/scripts/resource-info.sh
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
echo "load:$(uptime | awk -F'load average:' '{print $2}' | awk -F, '{print $1}' | tr -d ' ')"
|
||||||
|
echo "memory_total:$(free -k | awk 'NR==2{print $2}')"
|
||||||
|
echo "memory_used:$(free -k | awk 'NR==2{print $3}')"
|
||||||
|
echo "memory_free:$(free -k | awk 'NR==2{print $7}')"
|
||||||
|
echo "disk_total:$(df -BM / | awk 'NR==2{print $2}' | sed 's/M//')"
|
||||||
|
echo "disk_used:$(df -BM / | awk 'NR==2{print $3}' | sed 's/M//')"
|
||||||
|
echo "disk_free:$(df -BM / | awk 'NR==2{print $4}' | sed 's/M//')"
|
1
app/SSH/OS/scripts/tail.sh
Normal file
1
app/SSH/OS/scripts/tail.sh
Normal file
@ -0,0 +1 @@
|
|||||||
|
sudo tail -n __lines__ __path__
|
23
app/SSH/PHPMyAdmin/PHPMyAdmin.php
Normal file
23
app/SSH/PHPMyAdmin/PHPMyAdmin.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\SSH\PHPMyAdmin;
|
||||||
|
|
||||||
|
use App\Models\Site;
|
||||||
|
use App\SSH\HasScripts;
|
||||||
|
|
||||||
|
class PHPMyAdmin
|
||||||
|
{
|
||||||
|
use HasScripts;
|
||||||
|
|
||||||
|
public function install(Site $site): void
|
||||||
|
{
|
||||||
|
$site->server->ssh()->exec(
|
||||||
|
$this->getScript('install.sh', [
|
||||||
|
'version' => $site->type_data['version'],
|
||||||
|
'path' => $site->path,
|
||||||
|
]),
|
||||||
|
'install-phpmyadmin',
|
||||||
|
$site->id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
25
app/SSH/PHPMyAdmin/scripts/install.sh
Executable file
25
app/SSH/PHPMyAdmin/scripts/install.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
sudo rm -rf phpmyadmin
|
||||||
|
|
||||||
|
sudo rm -rf __path__
|
||||||
|
|
||||||
|
if ! wget https://files.phpmyadmin.net/phpMyAdmin/__version__/phpMyAdmin-__version__-all-languages.zip; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! unzip phpMyAdmin-__version__-all-languages.zip; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! rm -rf phpMyAdmin-__version__-all-languages.zip; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv phpMyAdmin-__version__-all-languages __path__; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv __path__/config.sample.inc.php __path__/config.inc.php; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "PHPMyAdmin installed!"
|
42
app/SSH/Services/AbstractService.php
Normal file
42
app/SSH/Services/AbstractService.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\SSH\Services;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
|
||||||
|
abstract class AbstractService implements ServiceInterface
|
||||||
|
{
|
||||||
|
public function __construct(protected Service $service)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function creationRules(array $input): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function creationData(array $input): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deletionRules(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function data(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
@ -2,40 +2,78 @@
|
|||||||
|
|
||||||
namespace App\SSH\Services\Database;
|
namespace App\SSH\Services\Database;
|
||||||
|
|
||||||
|
use App\Enums\BackupStatus;
|
||||||
use App\Models\BackupFile;
|
use App\Models\BackupFile;
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\Service;
|
|
||||||
use App\SSH\HasScripts;
|
use App\SSH\HasScripts;
|
||||||
use App\SSH\Services\ServiceInterface;
|
use App\SSH\Services\AbstractService;
|
||||||
|
use Closure;
|
||||||
|
|
||||||
abstract class AbstractDatabase implements Database, ServiceInterface
|
abstract class AbstractDatabase extends AbstractService implements Database
|
||||||
{
|
{
|
||||||
use HasScripts;
|
use HasScripts;
|
||||||
|
|
||||||
protected Service $service;
|
|
||||||
|
|
||||||
protected Server $server;
|
|
||||||
|
|
||||||
abstract protected function getScriptsDir(): string;
|
abstract protected function getScriptsDir(): string;
|
||||||
|
|
||||||
public function __construct(Service $service)
|
public function creationRules(array $input): array
|
||||||
{
|
{
|
||||||
$this->service = $service;
|
return [
|
||||||
$this->server = $service->server;
|
'type' => [
|
||||||
|
'required',
|
||||||
|
function (string $attribute, mixed $value, Closure $fail) {
|
||||||
|
$databaseExists = $this->service->server->database();
|
||||||
|
if ($databaseExists) {
|
||||||
|
$fail('You already have a database service on the server.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function install(): void
|
public function install(): void
|
||||||
{
|
{
|
||||||
$version = $this->service->version;
|
$version = $this->service->version;
|
||||||
$command = $this->getScript($this->service->name.'/install-'.$version.'.sh');
|
$command = $this->getScript($this->service->name.'/install-'.$version.'.sh');
|
||||||
$this->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version);
|
$this->service->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version);
|
||||||
$status = $this->server->systemd()->status($this->service->unit);
|
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||||
$this->service->validateInstall($status);
|
$this->service->validateInstall($status);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deletionRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'service' => [
|
||||||
|
function (string $attribute, mixed $value, Closure $fail) {
|
||||||
|
$hasDatabase = $this->service->server->databases()->exists();
|
||||||
|
if ($hasDatabase) {
|
||||||
|
$fail('You have database(s) on the server.');
|
||||||
|
}
|
||||||
|
$hasDatabaseUser = $this->service->server->databaseUsers()->exists();
|
||||||
|
if ($hasDatabaseUser) {
|
||||||
|
$fail('You have database user(s) on the server.');
|
||||||
|
}
|
||||||
|
$hasRunningBackup = $this->service->server->backups()
|
||||||
|
->where('status', BackupStatus::RUNNING)
|
||||||
|
->exists();
|
||||||
|
if ($hasRunningBackup) {
|
||||||
|
$fail('You have database backup(s) on the server.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(): void
|
||||||
|
{
|
||||||
|
$version = $this->service->version;
|
||||||
|
$command = $this->getScript($this->service->name.'/uninstall.sh');
|
||||||
|
$this->service->server->ssh()->exec($command, 'uninstall-'.$this->service->name.'-'.$version);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(string $name): void
|
public function create(string $name): void
|
||||||
{
|
{
|
||||||
$this->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
$this->getScript($this->getScriptsDir().'/create.sh', [
|
$this->getScript($this->getScriptsDir().'/create.sh', [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
]),
|
]),
|
||||||
@ -45,7 +83,7 @@ public function create(string $name): void
|
|||||||
|
|
||||||
public function delete(string $name): void
|
public function delete(string $name): void
|
||||||
{
|
{
|
||||||
$this->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
$this->getScript($this->getScriptsDir().'/delete.sh', [
|
$this->getScript($this->getScriptsDir().'/delete.sh', [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
]),
|
]),
|
||||||
@ -55,7 +93,7 @@ public function delete(string $name): void
|
|||||||
|
|
||||||
public function createUser(string $username, string $password, string $host): void
|
public function createUser(string $username, string $password, string $host): void
|
||||||
{
|
{
|
||||||
$this->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
$this->getScript($this->getScriptsDir().'/create-user.sh', [
|
$this->getScript($this->getScriptsDir().'/create-user.sh', [
|
||||||
'username' => $username,
|
'username' => $username,
|
||||||
'password' => $password,
|
'password' => $password,
|
||||||
@ -67,7 +105,7 @@ public function createUser(string $username, string $password, string $host): vo
|
|||||||
|
|
||||||
public function deleteUser(string $username, string $host): void
|
public function deleteUser(string $username, string $host): void
|
||||||
{
|
{
|
||||||
$this->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
$this->getScript($this->getScriptsDir().'/delete-user.sh', [
|
$this->getScript($this->getScriptsDir().'/delete-user.sh', [
|
||||||
'username' => $username,
|
'username' => $username,
|
||||||
'host' => $host,
|
'host' => $host,
|
||||||
@ -78,7 +116,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->server->ssh();
|
$ssh = $this->service->server->ssh();
|
||||||
|
|
||||||
foreach ($databases as $database) {
|
foreach ($databases as $database) {
|
||||||
$ssh->exec(
|
$ssh->exec(
|
||||||
@ -94,7 +132,7 @@ 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
|
||||||
{
|
{
|
||||||
$this->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,
|
||||||
@ -106,7 +144,7 @@ public function unlink(string $username, string $host): void
|
|||||||
public function runBackup(BackupFile $backupFile): void
|
public function runBackup(BackupFile $backupFile): void
|
||||||
{
|
{
|
||||||
// backup
|
// backup
|
||||||
$this->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
$this->getScript($this->getScriptsDir().'/backup.sh', [
|
$this->getScript($this->getScriptsDir().'/backup.sh', [
|
||||||
'file' => $backupFile->name,
|
'file' => $backupFile->name,
|
||||||
'database' => $backupFile->backup->database->name,
|
'database' => $backupFile->backup->database->name,
|
||||||
@ -115,13 +153,13 @@ public function runBackup(BackupFile $backupFile): void
|
|||||||
);
|
);
|
||||||
|
|
||||||
// upload to storage
|
// upload to storage
|
||||||
$upload = $backupFile->backup->storage->provider()->ssh($this->server)->upload(
|
$upload = $backupFile->backup->storage->provider()->ssh($this->service->server)->upload(
|
||||||
$backupFile->path(),
|
$backupFile->path(),
|
||||||
$backupFile->storagePath(),
|
$backupFile->storagePath(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
$this->server->ssh()->exec('rm '.$backupFile->name.'.zip');
|
$this->service->server->ssh()->exec('rm '.$backupFile->name.'.zip');
|
||||||
|
|
||||||
$backupFile->size = $upload['size'];
|
$backupFile->size = $upload['size'];
|
||||||
$backupFile->save();
|
$backupFile->save();
|
||||||
@ -130,12 +168,12 @@ public function runBackup(BackupFile $backupFile): void
|
|||||||
public function restoreBackup(BackupFile $backupFile, string $database): void
|
public function restoreBackup(BackupFile $backupFile, string $database): void
|
||||||
{
|
{
|
||||||
// download
|
// download
|
||||||
$backupFile->backup->storage->provider()->ssh($this->server)->download(
|
$backupFile->backup->storage->provider()->ssh($this->service->server)->download(
|
||||||
$backupFile->storagePath(),
|
$backupFile->storagePath(),
|
||||||
$backupFile->name.'.zip',
|
$backupFile->name.'.zip',
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
$this->getScript($this->getScriptsDir().'/restore.sh', [
|
$this->getScript($this->getScriptsDir().'/restore.sh', [
|
||||||
'database' => $database,
|
'database' => $database,
|
||||||
'file' => $backupFile->name,
|
'file' => $backupFile->name,
|
||||||
|
@ -9,4 +9,6 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
|||||||
|
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
||||||
|
|
||||||
|
sudo systemctl unmask mysql.service
|
||||||
|
|
||||||
sudo service mysql start
|
sudo service mysql start
|
||||||
|
@ -9,4 +9,6 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
|||||||
|
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
||||||
|
|
||||||
|
sudo systemctl unmask mysql.service
|
||||||
|
|
||||||
sudo service mysql start
|
sudo service mysql start
|
||||||
|
9
app/SSH/Services/Database/scripts/mariadb/uninstall.sh
Normal file
9
app/SSH/Services/Database/scripts/mariadb/uninstall.sh
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
sudo service mysql stop
|
||||||
|
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get remove mariadb-server mariadb-backup -y
|
||||||
|
|
||||||
|
sudo rm -rf /etc/mysql
|
||||||
|
sudo rm -rf /var/lib/mysql
|
||||||
|
sudo rm -rf /var/log/mysql
|
||||||
|
sudo rm -rf /var/run/mysqld
|
||||||
|
sudo rm -rf /var/run/mysqld/mysqld.sock
|
@ -1,5 +1,7 @@
|
|||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
|
||||||
|
|
||||||
|
sudo systemctl unmask mysql.service
|
||||||
|
|
||||||
sudo service mysql enable
|
sudo service mysql enable
|
||||||
|
|
||||||
sudo service mysql start
|
sudo service mysql start
|
||||||
|
@ -6,6 +6,8 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
|||||||
|
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install mysql-server -y
|
||||||
|
|
||||||
|
sudo systemctl unmask mysql.service
|
||||||
|
|
||||||
sudo service mysql enable
|
sudo service mysql enable
|
||||||
|
|
||||||
sudo service mysql start
|
sudo service mysql start
|
||||||
|
9
app/SSH/Services/Database/scripts/mysql/uninstall.sh
Executable file
9
app/SSH/Services/Database/scripts/mysql/uninstall.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
sudo service mysql stop
|
||||||
|
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get remove mysql-server -y
|
||||||
|
|
||||||
|
sudo rm -rf /etc/mysql
|
||||||
|
sudo rm -rf /var/lib/mysql
|
||||||
|
sudo rm -rf /var/log/mysql
|
||||||
|
sudo rm -rf /var/run/mysqld
|
||||||
|
sudo rm -rf /var/run/mysqld/mysqld.sock
|
11
app/SSH/Services/Database/scripts/postgresql/uninstall.sh
Normal file
11
app/SSH/Services/Database/scripts/postgresql/uninstall.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
sudo service postgresql stop
|
||||||
|
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get remove postgresql-* -y
|
||||||
|
|
||||||
|
sudo rm -rf /etc/postgresql
|
||||||
|
sudo rm -rf /var/lib/postgresql
|
||||||
|
sudo rm -rf /var/log/postgresql
|
||||||
|
sudo rm -rf /var/run/postgresql
|
||||||
|
sudo rm -rf /var/run/postgresql/postmaster.pid
|
||||||
|
sudo rm -rf /var/run/postgresql/.s.PGSQL.5432
|
||||||
|
sudo rm -rf /var/run/postgresql/.s.PGSQL.5432.lock
|
@ -2,15 +2,8 @@
|
|||||||
|
|
||||||
namespace App\SSH\Services\Firewall;
|
namespace App\SSH\Services\Firewall;
|
||||||
|
|
||||||
use App\Models\Service;
|
use App\SSH\Services\AbstractService;
|
||||||
use App\SSH\Services\ServiceInterface;
|
|
||||||
|
|
||||||
abstract class AbstractFirewall implements Firewall, ServiceInterface
|
abstract class AbstractFirewall extends AbstractService implements Firewall
|
||||||
{
|
{
|
||||||
protected Service $service;
|
|
||||||
|
|
||||||
public function __construct(Service $service)
|
|
||||||
{
|
|
||||||
$this->service = $service;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,12 @@ public function install(): void
|
|||||||
$this->getScript('ufw/install-ufw.sh'),
|
$this->getScript('ufw/install-ufw.sh'),
|
||||||
'install-ufw'
|
'install-ufw'
|
||||||
);
|
);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void
|
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void
|
||||||
|
53
app/SSH/Services/Monitoring/RemoteMonitor/RemoteMonitor.php
Normal file
53
app/SSH/Services/Monitoring/RemoteMonitor/RemoteMonitor.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\SSH\Services\Monitoring\RemoteMonitor;
|
||||||
|
|
||||||
|
use App\Models\Metric;
|
||||||
|
use App\SSH\Services\AbstractService;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class RemoteMonitor extends AbstractService
|
||||||
|
{
|
||||||
|
public function creationRules(array $input): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => [
|
||||||
|
function (string $attribute, mixed $value, Closure $fail) {
|
||||||
|
$monitoringExists = $this->service->server->monitoring();
|
||||||
|
if ($monitoringExists) {
|
||||||
|
$fail('You already have a monitoring service on the server.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'version' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(['latest']),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function creationData(array $input): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data_retention' => 10,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function data(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data_retention' => $this->service->type_data['data_retention'] ?? 10,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(): void
|
||||||
|
{
|
||||||
|
Metric::where('server_id', $this->service->server_id)->delete();
|
||||||
|
}
|
||||||
|
}
|
87
app/SSH/Services/Monitoring/VitoAgent/VitoAgent.php
Normal file
87
app/SSH/Services/Monitoring/VitoAgent/VitoAgent.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\SSH\Services\Monitoring\VitoAgent;
|
||||||
|
|
||||||
|
use App\Models\Metric;
|
||||||
|
use App\SSH\HasScripts;
|
||||||
|
use App\SSH\Services\AbstractService;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
|
||||||
|
class VitoAgent extends AbstractService
|
||||||
|
{
|
||||||
|
use HasScripts;
|
||||||
|
|
||||||
|
const TAGS_URL = 'https://api.github.com/repos/vitodeploy/agent/tags';
|
||||||
|
|
||||||
|
const DOWNLOAD_URL = 'https://github.com/vitodeploy/agent/releases/download/%s';
|
||||||
|
|
||||||
|
public function creationRules(array $input): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => [
|
||||||
|
Rule::unique('services', 'type')->where('server_id', $this->service->server_id),
|
||||||
|
],
|
||||||
|
'version' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(['latest']),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function creationData(array $input): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'url' => '',
|
||||||
|
'secret' => Uuid::uuid4()->toString(),
|
||||||
|
'data_retention' => 10,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function data(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'url' => $this->service->type_data['url'] ?? null,
|
||||||
|
'secret' => $this->service->type_data['secret'] ?? null,
|
||||||
|
'data_retention' => $this->service->type_data['data_retention'] ?? 10,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install(): void
|
||||||
|
{
|
||||||
|
$tags = Http::get(self::TAGS_URL)->json();
|
||||||
|
if (empty($tags)) {
|
||||||
|
throw new \Exception('Failed to fetch tags');
|
||||||
|
}
|
||||||
|
$this->service->version = $tags[0]['name'];
|
||||||
|
$this->service->save();
|
||||||
|
$downloadUrl = sprintf(self::DOWNLOAD_URL, $this->service->version);
|
||||||
|
|
||||||
|
$data = $this->data();
|
||||||
|
$data['url'] = route('api.servers.agent', [$this->service->server, $this->service->id]);
|
||||||
|
$this->service->type_data = $data;
|
||||||
|
$this->service->save();
|
||||||
|
$this->service->refresh();
|
||||||
|
|
||||||
|
$this->service->server->ssh()->exec(
|
||||||
|
$this->getScript('install.sh', [
|
||||||
|
'download_url' => $downloadUrl,
|
||||||
|
'config_url' => $this->data()['url'],
|
||||||
|
'config_secret' => $this->data()['secret'],
|
||||||
|
]),
|
||||||
|
'install-vito-agent'
|
||||||
|
);
|
||||||
|
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||||
|
$this->service->validateInstall($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(): void
|
||||||
|
{
|
||||||
|
$this->service->server->ssh()->exec(
|
||||||
|
$this->getScript('uninstall.sh'),
|
||||||
|
'uninstall-vito-agent'
|
||||||
|
);
|
||||||
|
Metric::where('server_id', $this->service->server_id)->delete();
|
||||||
|
}
|
||||||
|
}
|
53
app/SSH/Services/Monitoring/VitoAgent/scripts/install.sh
Normal file
53
app/SSH/Services/Monitoring/VitoAgent/scripts/install.sh
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
arch=$(uname -m)
|
||||||
|
|
||||||
|
if [ "$arch" == "x86_64" ]; then
|
||||||
|
executable="vitoagent-linux-amd64"
|
||||||
|
elif [ "$arch" == "i686" ]; then
|
||||||
|
executable="vitoagent-linux-amd"
|
||||||
|
elif [ "$arch" == "armv7l" ]; then
|
||||||
|
executable="vitoagent-linux-arm"
|
||||||
|
elif [ "$arch" == "aarch64" ]; then
|
||||||
|
executable="vitoagent-linux-arm64"
|
||||||
|
else
|
||||||
|
executable="vitoagent-linux-amd64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
wget __download_url__/$executable
|
||||||
|
|
||||||
|
chmod +x ./$executable
|
||||||
|
|
||||||
|
sudo mv ./$executable /usr/local/bin/vito-agent
|
||||||
|
|
||||||
|
# create service
|
||||||
|
export VITO_AGENT_SERVICE="
|
||||||
|
[Unit]
|
||||||
|
Description=Vito Agent
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
ExecStart=/usr/local/bin/vito-agent
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
"
|
||||||
|
echo "${VITO_AGENT_SERVICE}" | sudo tee /etc/systemd/system/vito-agent.service
|
||||||
|
|
||||||
|
sudo mkdir -p /etc/vito-agent
|
||||||
|
|
||||||
|
export VITO_AGENT_CONFIG="
|
||||||
|
{
|
||||||
|
\"url\": \"__config_url__\",
|
||||||
|
\"secret\": \"__config_secret__\"
|
||||||
|
}
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "${VITO_AGENT_CONFIG}" | sudo tee /etc/vito-agent/config.json
|
||||||
|
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable vito-agent
|
||||||
|
sudo systemctl start vito-agent
|
||||||
|
|
||||||
|
echo "Vito Agent installed successfully"
|
13
app/SSH/Services/Monitoring/VitoAgent/scripts/uninstall.sh
Normal file
13
app/SSH/Services/Monitoring/VitoAgent/scripts/uninstall.sh
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
sudo service vito-agent stop
|
||||||
|
|
||||||
|
sudo systemctl disable vito-agent
|
||||||
|
|
||||||
|
sudo rm -f /usr/local/bin/vito-agent
|
||||||
|
|
||||||
|
sudo rm -f /etc/systemd/system/vito-agent.service
|
||||||
|
|
||||||
|
sudo rm -rf /etc/vito-agent
|
||||||
|
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
echo "Vito Agent uninstalled successfully"
|
@ -3,20 +3,43 @@
|
|||||||
namespace App\SSH\Services\PHP;
|
namespace App\SSH\Services\PHP;
|
||||||
|
|
||||||
use App\Exceptions\SSHCommandError;
|
use App\Exceptions\SSHCommandError;
|
||||||
use App\Models\Service;
|
|
||||||
use App\SSH\HasScripts;
|
use App\SSH\HasScripts;
|
||||||
use App\SSH\Services\ServiceInterface;
|
use App\SSH\Services\AbstractService;
|
||||||
|
use Closure;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class PHP implements ServiceInterface
|
class PHP extends AbstractService
|
||||||
{
|
{
|
||||||
use HasScripts;
|
use HasScripts;
|
||||||
|
|
||||||
protected Service $service;
|
public function creationRules(array $input): array
|
||||||
|
|
||||||
public function __construct(Service $service)
|
|
||||||
{
|
{
|
||||||
$this->service = $service;
|
return [
|
||||||
|
'version' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(config('core.php_versions')),
|
||||||
|
Rule::unique('services', 'version')
|
||||||
|
->where('type', 'php')
|
||||||
|
->where('server_id', $this->service->server_id),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deletionRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'service' => [
|
||||||
|
function (string $attribute, mixed $value, Closure $fail) {
|
||||||
|
$hasSite = $this->service->server->sites()
|
||||||
|
->where('php_version', $this->service->version)
|
||||||
|
->exists();
|
||||||
|
if ($hasSite) {
|
||||||
|
$fail('Some sites are using this PHP version.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function install(): void
|
public function install(): void
|
||||||
@ -29,6 +52,7 @@ public function install(): void
|
|||||||
]),
|
]),
|
||||||
'install-php-'.$this->service->version
|
'install-php-'.$this->service->version
|
||||||
);
|
);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function uninstall(): void
|
public function uninstall(): void
|
||||||
@ -39,6 +63,7 @@ public function uninstall(): void
|
|||||||
]),
|
]),
|
||||||
'uninstall-php-'.$this->service->version
|
'uninstall-php-'.$this->service->version
|
||||||
);
|
);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDefaultCli(): void
|
public function setDefaultCli(): void
|
||||||
|
@ -2,15 +2,37 @@
|
|||||||
|
|
||||||
namespace App\SSH\Services\ProcessManager;
|
namespace App\SSH\Services\ProcessManager;
|
||||||
|
|
||||||
use App\Models\Service;
|
use App\SSH\Services\AbstractService;
|
||||||
use App\SSH\Services\ServiceInterface;
|
use Closure;
|
||||||
|
|
||||||
abstract class AbstractProcessManager implements ProcessManager, ServiceInterface
|
abstract class AbstractProcessManager extends AbstractService implements ProcessManager
|
||||||
{
|
{
|
||||||
protected Service $service;
|
public function creationRules(array $input): array
|
||||||
|
|
||||||
public function __construct(Service $service)
|
|
||||||
{
|
{
|
||||||
$this->service = $service;
|
return [
|
||||||
|
'type' => [
|
||||||
|
'required',
|
||||||
|
function (string $attribute, mixed $value, Closure $fail) {
|
||||||
|
$processManagerExists = $this->service->server->processManager();
|
||||||
|
if ($processManagerExists) {
|
||||||
|
$fail('You already have a process manager service on the server.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deletionRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'service' => [
|
||||||
|
function (string $attribute, mixed $value, Closure $fail) {
|
||||||
|
$hasQueue = $this->service->server->queues()->exists();
|
||||||
|
if ($hasQueue) {
|
||||||
|
$fail('You have queue(s) on the server.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,18 @@ public function install(): void
|
|||||||
$this->getScript('supervisor/install-supervisor.sh'),
|
$this->getScript('supervisor/install-supervisor.sh'),
|
||||||
'install-supervisor'
|
'install-supervisor'
|
||||||
);
|
);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(): void
|
||||||
|
{
|
||||||
|
$this->service->server->ssh()->exec(
|
||||||
|
$this->getScript('supervisor/uninstall-supervisor.sh'),
|
||||||
|
'uninstall-supervisor'
|
||||||
|
);
|
||||||
|
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||||
|
$this->service->validateInstall($status);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
sudo service supervisor stop
|
||||||
|
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get remove supervisor -y
|
||||||
|
|
||||||
|
sudo rm -rf /etc/supervisor
|
||||||
|
sudo rm -rf /var/log/supervisor
|
||||||
|
sudo rm -rf /var/run/supervisor
|
||||||
|
sudo rm -rf /var/run/supervisor/supervisor.sock
|
@ -2,16 +2,27 @@
|
|||||||
|
|
||||||
namespace App\SSH\Services\Redis;
|
namespace App\SSH\Services\Redis;
|
||||||
|
|
||||||
use App\Models\Service;
|
|
||||||
use App\SSH\HasScripts;
|
use App\SSH\HasScripts;
|
||||||
use App\SSH\Services\ServiceInterface;
|
use App\SSH\Services\AbstractService;
|
||||||
|
use Closure;
|
||||||
|
|
||||||
class Redis implements ServiceInterface
|
class Redis extends AbstractService
|
||||||
{
|
{
|
||||||
use HasScripts;
|
use HasScripts;
|
||||||
|
|
||||||
public function __construct(protected Service $service)
|
public function creationRules(array $input): array
|
||||||
{
|
{
|
||||||
|
return [
|
||||||
|
'type' => [
|
||||||
|
'required',
|
||||||
|
function (string $attribute, mixed $value, Closure $fail) {
|
||||||
|
$redisExists = $this->service->server->memoryDatabase();
|
||||||
|
if ($redisExists) {
|
||||||
|
$fail('You already have a Redis service on the server.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function install(): void
|
public function install(): void
|
||||||
@ -20,5 +31,17 @@ public function install(): void
|
|||||||
$this->getScript('install.sh'),
|
$this->getScript('install.sh'),
|
||||||
'install-redis'
|
'install-redis'
|
||||||
);
|
);
|
||||||
|
$status = $this->service->server->systemd()->status($this->service->unit);
|
||||||
|
$this->service->validateInstall($status);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(): void
|
||||||
|
{
|
||||||
|
$this->service->server->ssh()->exec(
|
||||||
|
$this->getScript('uninstall.sh'),
|
||||||
|
'uninstall-redis'
|
||||||
|
);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
app/SSH/Services/Redis/scripts/uninstall.sh
Executable file
15
app/SSH/Services/Redis/scripts/uninstall.sh
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
sudo service redis stop
|
||||||
|
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get remove redis-server -y
|
||||||
|
|
||||||
|
sudo rm -rf /etc/redis
|
||||||
|
sudo rm -rf /var/lib/redis
|
||||||
|
sudo rm -rf /var/log/redis
|
||||||
|
sudo rm -rf /var/run/redis
|
||||||
|
sudo rm -rf /var/run/redis/redis-server.pid
|
||||||
|
sudo rm -rf /var/run/redis/redis-server.sock
|
||||||
|
sudo rm -rf /var/run/redis/redis-server.sock
|
||||||
|
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive sudo apt-get autoremove -y
|
||||||
|
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive sudo apt-get autoclean -y
|
@ -4,5 +4,15 @@
|
|||||||
|
|
||||||
interface ServiceInterface
|
interface ServiceInterface
|
||||||
{
|
{
|
||||||
|
public function creationRules(array $input): array;
|
||||||
|
|
||||||
|
public function creationData(array $input): array;
|
||||||
|
|
||||||
|
public function deletionRules(): array;
|
||||||
|
|
||||||
|
public function data(): array;
|
||||||
|
|
||||||
public function install(): void;
|
public function install(): void;
|
||||||
|
|
||||||
|
public function uninstall(): void;
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,8 @@
|
|||||||
|
|
||||||
namespace App\SSH\Services\Webserver;
|
namespace App\SSH\Services\Webserver;
|
||||||
|
|
||||||
use App\Models\Service;
|
use App\SSH\Services\AbstractService;
|
||||||
use App\SSH\Services\ServiceInterface;
|
|
||||||
|
|
||||||
abstract class AbstractWebserver implements ServiceInterface, Webserver
|
abstract class AbstractWebserver extends AbstractService implements Webserver
|
||||||
{
|
{
|
||||||
public function __construct(protected Service $service)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
use App\Models\Site;
|
use App\Models\Site;
|
||||||
use App\Models\Ssl;
|
use App\Models\Ssl;
|
||||||
use App\SSH\HasScripts;
|
use App\SSH\HasScripts;
|
||||||
|
use Closure;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
@ -23,6 +24,31 @@ public function install(): void
|
|||||||
]),
|
]),
|
||||||
'install-nginx'
|
'install-nginx'
|
||||||
);
|
);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deletionRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'service' => [
|
||||||
|
function (string $attribute, mixed $value, Closure $fail) {
|
||||||
|
$hasSite = $this->service->server->sites()
|
||||||
|
->exists();
|
||||||
|
if ($hasSite) {
|
||||||
|
$fail('Cannot uninstall webserver while you have websites using it.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(): void
|
||||||
|
{
|
||||||
|
$this->service->server->ssh()->exec(
|
||||||
|
$this->getScript('nginx/uninstall-nginx.sh'),
|
||||||
|
'uninstall-nginx'
|
||||||
|
);
|
||||||
|
$this->service->server->os()->cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createVHost(Site $site): void
|
public function createVHost(Site $site): void
|
||||||
@ -44,7 +70,7 @@ public function updateVHost(Site $site, bool $noSSL = false, ?string $vhost = nu
|
|||||||
$this->getScript('nginx/update-vhost.sh', [
|
$this->getScript('nginx/update-vhost.sh', [
|
||||||
'domain' => $site->domain,
|
'domain' => $site->domain,
|
||||||
'path' => $site->path,
|
'path' => $site->path,
|
||||||
'vhost' => $this->generateVhost($site, $noSSL),
|
'vhost' => $vhost ?? $this->generateVhost($site, $noSSL),
|
||||||
]),
|
]),
|
||||||
'update-vhost',
|
'update-vhost',
|
||||||
$site->id
|
$site->id
|
||||||
|
12
app/SSH/Services/Webserver/scripts/nginx/uninstall-nginx.sh
Executable file
12
app/SSH/Services/Webserver/scripts/nginx/uninstall-nginx.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
sudo service nginx stop
|
||||||
|
|
||||||
|
sudo DEBIAN_FRONTEND=noninteractive apt-get purge nginx nginx-common nginx-full -y
|
||||||
|
|
||||||
|
sudo rm -rf /etc/nginx
|
||||||
|
sudo rm -rf /var/log/nginx
|
||||||
|
sudo rm -rf /var/lib/nginx
|
||||||
|
sudo rm -rf /var/cache/nginx
|
||||||
|
sudo rm -rf /usr/share/nginx
|
||||||
|
sudo rm -rf /etc/systemd/system/nginx.service
|
||||||
|
|
||||||
|
sudo systemctl daemon-reload
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Enums\ServiceStatus;
|
use App\Enums\ServiceStatus;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\SSH\Services\PHP\PHP;
|
||||||
|
|
||||||
abstract class AbstractType implements ServerType
|
abstract class AbstractType implements ServerType
|
||||||
{
|
{
|
||||||
@ -31,7 +32,9 @@ public function install(): void
|
|||||||
$service->update(['status' => ServiceStatus::READY]);
|
$service->update(['status' => ServiceStatus::READY]);
|
||||||
if ($service->type == 'php') {
|
if ($service->type == 'php') {
|
||||||
$this->progress($currentProgress, 'installing-composer');
|
$this->progress($currentProgress, 'installing-composer');
|
||||||
$service->handler()->installComposer();
|
/** @var PHP $handler */
|
||||||
|
$handler = $service->handler();
|
||||||
|
$handler->installComposer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->progress(100, 'finishing');
|
$this->progress(100, 'finishing');
|
||||||
|
@ -13,7 +13,7 @@ public function createRules(array $input): array
|
|||||||
],
|
],
|
||||||
'php' => [
|
'php' => [
|
||||||
'required',
|
'required',
|
||||||
'in:'.implode(',', config('core.php_versions')),
|
'in:none,'.implode(',', config('core.php_versions')),
|
||||||
],
|
],
|
||||||
'database' => [
|
'database' => [
|
||||||
'required',
|
'required',
|
||||||
|
49
app/SiteTypes/PHPMyAdmin.php
Executable file
49
app/SiteTypes/PHPMyAdmin.php
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\SiteTypes;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class PHPMyAdmin extends PHPSite
|
||||||
|
{
|
||||||
|
public function supportedFeatures(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createRules(array $input): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'php_version' => [
|
||||||
|
'required',
|
||||||
|
Rule::in($this->site->server->installedPHPVersions()),
|
||||||
|
],
|
||||||
|
'version' => 'required|string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFields(array $input): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'web_directory' => '',
|
||||||
|
'php_version' => $input['php_version'] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function data(array $input): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'version' => $input['version'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install(): void
|
||||||
|
{
|
||||||
|
$this->site->server->webserver()->handler()->createVHost($this->site);
|
||||||
|
$this->progress(30);
|
||||||
|
app(\App\SSH\PHPMyAdmin\PHPMyAdmin::class)->install($this->site);
|
||||||
|
$this->progress(65);
|
||||||
|
}
|
||||||
|
}
|
@ -34,7 +34,7 @@ public function connect(bool $sftp = false): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exec(string|array $commands, string $log = '', ?int $siteId = null): string
|
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
|
||||||
{
|
{
|
||||||
if ($log) {
|
if ($log) {
|
||||||
$this->setLog($log, $siteId);
|
$this->setLog($log, $siteId);
|
||||||
@ -42,21 +42,19 @@ public function exec(string|array $commands, string $log = '', ?int $siteId = nu
|
|||||||
$this->log = null;
|
$this->log = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! is_array($commands)) {
|
$this->commands[] = $command;
|
||||||
$commands = [$commands];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($commands as $command) {
|
|
||||||
if (is_string($command)) {
|
|
||||||
$this->commands[] = $command;
|
|
||||||
} else {
|
|
||||||
$this->commands[] = get_class($command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$output = $this->output ?? 'fake output';
|
$output = $this->output ?? 'fake output';
|
||||||
$this->log?->write($output);
|
$this->log?->write($output);
|
||||||
|
|
||||||
|
if ($stream) {
|
||||||
|
echo $output;
|
||||||
|
ob_flush();
|
||||||
|
flush();
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,3 +29,22 @@ function htmx(): HtmxResponse
|
|||||||
{
|
{
|
||||||
return new HtmxResponse();
|
return new HtmxResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vito_version(): string
|
||||||
|
{
|
||||||
|
$version = exec('git describe --tags');
|
||||||
|
if (str($version)->contains('-')) {
|
||||||
|
return str($version)->before('-').' (dev)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convert_time_format($string): string
|
||||||
|
{
|
||||||
|
$string = preg_replace('/(\d+)m/', '$1 minutes', $string);
|
||||||
|
$string = preg_replace('/(\d+)s/', '$1 seconds', $string);
|
||||||
|
$string = preg_replace('/(\d+)d/', '$1 days', $string);
|
||||||
|
|
||||||
|
return preg_replace('/(\d+)h/', '$1 hours', $string);
|
||||||
|
}
|
||||||
|
221
config/core.php
221
config/core.php
@ -1,34 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Enums\OperatingSystem;
|
|
||||||
use App\Enums\StorageProvider;
|
|
||||||
use App\NotificationChannels\Discord;
|
|
||||||
use App\NotificationChannels\Email;
|
|
||||||
use App\NotificationChannels\Slack;
|
|
||||||
use App\NotificationChannels\Telegram;
|
|
||||||
use App\ServerProviders\AWS;
|
|
||||||
use App\ServerProviders\DigitalOcean;
|
|
||||||
use App\ServerProviders\Hetzner;
|
|
||||||
use App\ServerProviders\Linode;
|
|
||||||
use App\ServerProviders\Vultr;
|
|
||||||
use App\SiteTypes\Laravel;
|
|
||||||
use App\SiteTypes\PHPBlank;
|
|
||||||
use App\SiteTypes\PHPSite;
|
|
||||||
use App\SiteTypes\Wordpress;
|
|
||||||
use App\SourceControlProviders\Bitbucket;
|
|
||||||
use App\SourceControlProviders\Github;
|
|
||||||
use App\SourceControlProviders\Gitlab;
|
|
||||||
use App\SSH\Services\Database\Mariadb;
|
|
||||||
use App\SSH\Services\Database\Mysql;
|
|
||||||
use App\SSH\Services\Database\Postgresql;
|
|
||||||
use App\SSH\Services\Firewall\Ufw;
|
|
||||||
use App\SSH\Services\PHP\PHP;
|
|
||||||
use App\SSH\Services\ProcessManager\Supervisor;
|
|
||||||
use App\SSH\Services\Redis\Redis;
|
|
||||||
use App\SSH\Services\Webserver\Nginx;
|
|
||||||
use App\StorageProviders\Dropbox;
|
|
||||||
use App\StorageProviders\FTP;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
/*
|
/*
|
||||||
* SSH
|
* SSH
|
||||||
@ -43,12 +14,12 @@
|
|||||||
* General
|
* General
|
||||||
*/
|
*/
|
||||||
'operating_systems' => [
|
'operating_systems' => [
|
||||||
OperatingSystem::UBUNTU20,
|
\App\Enums\OperatingSystem::UBUNTU20,
|
||||||
OperatingSystem::UBUNTU22,
|
\App\Enums\OperatingSystem::UBUNTU22,
|
||||||
],
|
],
|
||||||
'webservers' => ['none', 'nginx'],
|
'webservers' => ['none', 'nginx'],
|
||||||
'php_versions' => [
|
'php_versions' => [
|
||||||
'none',
|
// 'none',
|
||||||
// '5.6',
|
// '5.6',
|
||||||
'7.0',
|
'7.0',
|
||||||
'7.1',
|
'7.1',
|
||||||
@ -125,114 +96,104 @@
|
|||||||
],
|
],
|
||||||
'server_providers_class' => [
|
'server_providers_class' => [
|
||||||
\App\Enums\ServerProvider::CUSTOM => \App\ServerProviders\Custom::class,
|
\App\Enums\ServerProvider::CUSTOM => \App\ServerProviders\Custom::class,
|
||||||
\App\Enums\ServerProvider::AWS => AWS::class,
|
\App\Enums\ServerProvider::AWS => \App\ServerProviders\AWS::class,
|
||||||
\App\Enums\ServerProvider::LINODE => Linode::class,
|
\App\Enums\ServerProvider::LINODE => \App\ServerProviders\Linode::class,
|
||||||
\App\Enums\ServerProvider::DIGITALOCEAN => DigitalOcean::class,
|
\App\Enums\ServerProvider::DIGITALOCEAN => \App\ServerProviders\DigitalOcean::class,
|
||||||
\App\Enums\ServerProvider::VULTR => Vultr::class,
|
\App\Enums\ServerProvider::VULTR => \App\ServerProviders\Vultr::class,
|
||||||
\App\Enums\ServerProvider::HETZNER => Hetzner::class,
|
\App\Enums\ServerProvider::HETZNER => \App\ServerProviders\Hetzner::class,
|
||||||
],
|
],
|
||||||
'server_providers_default_user' => [
|
'server_providers_default_user' => [
|
||||||
'custom' => [
|
'custom' => [
|
||||||
'ubuntu_18' => 'root',
|
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||||
'ubuntu_20' => 'root',
|
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||||
'ubuntu_22' => 'root',
|
|
||||||
],
|
],
|
||||||
'aws' => [
|
'aws' => [
|
||||||
'ubuntu_18' => 'ubuntu',
|
\App\Enums\OperatingSystem::UBUNTU20 => 'ubuntu',
|
||||||
'ubuntu_20' => 'ubuntu',
|
\App\Enums\OperatingSystem::UBUNTU22 => 'ubuntu',
|
||||||
'ubuntu_22' => 'ubuntu',
|
|
||||||
],
|
],
|
||||||
'linode' => [
|
'linode' => [
|
||||||
'ubuntu_18' => 'root',
|
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||||
'ubuntu_20' => 'root',
|
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||||
'ubuntu_22' => 'root',
|
|
||||||
],
|
],
|
||||||
'digitalocean' => [
|
'digitalocean' => [
|
||||||
'ubuntu_18' => 'root',
|
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||||
'ubuntu_20' => 'root',
|
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||||
'ubuntu_22' => 'root',
|
|
||||||
],
|
],
|
||||||
'vultr' => [
|
'vultr' => [
|
||||||
'ubuntu_18' => 'root',
|
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||||
'ubuntu_20' => 'root',
|
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||||
'ubuntu_22' => 'root',
|
|
||||||
],
|
],
|
||||||
'hetzner' => [
|
'hetzner' => [
|
||||||
'ubuntu_18' => 'root',
|
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||||
'ubuntu_20' => 'root',
|
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||||
'ubuntu_22' => 'root',
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Service
|
* Service
|
||||||
*/
|
*/
|
||||||
|
'service_types' => [
|
||||||
|
'nginx' => 'webserver',
|
||||||
|
'mysql' => 'database',
|
||||||
|
'mariadb' => 'database',
|
||||||
|
'postgresql' => 'database',
|
||||||
|
'redis' => 'memory_database',
|
||||||
|
'php' => 'php',
|
||||||
|
'ufw' => 'firewall',
|
||||||
|
'supervisor' => 'process_manager',
|
||||||
|
'vito-agent' => 'monitoring',
|
||||||
|
'remote-monitor' => 'monitoring',
|
||||||
|
],
|
||||||
'service_handlers' => [
|
'service_handlers' => [
|
||||||
'nginx' => Nginx::class,
|
'nginx' => \App\SSH\Services\Webserver\Nginx::class,
|
||||||
'mysql' => Mysql::class,
|
'mysql' => \App\SSH\Services\Database\Mysql::class,
|
||||||
'mariadb' => Mariadb::class,
|
'mariadb' => \App\SSH\Services\Database\Mariadb::class,
|
||||||
'postgresql' => Postgresql::class,
|
'postgresql' => \App\SSH\Services\Database\Postgresql::class,
|
||||||
'redis' => Redis::class,
|
'redis' => \App\SSH\Services\Redis\Redis::class,
|
||||||
'php' => PHP::class,
|
'php' => \App\SSH\Services\PHP\PHP::class,
|
||||||
'ufw' => Ufw::class,
|
'ufw' => \App\SSH\Services\Firewall\Ufw::class,
|
||||||
'supervisor' => Supervisor::class,
|
'supervisor' => \App\SSH\Services\ProcessManager\Supervisor::class,
|
||||||
|
'vito-agent' => \App\SSH\Services\Monitoring\VitoAgent\VitoAgent::class,
|
||||||
|
'remote-monitor' => \App\SSH\Services\Monitoring\RemoteMonitor\RemoteMonitor::class,
|
||||||
],
|
],
|
||||||
'service_units' => [
|
'service_units' => [
|
||||||
'nginx' => [
|
'nginx' => [
|
||||||
'ubuntu_18' => [
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
'latest' => 'nginx',
|
'latest' => 'nginx',
|
||||||
],
|
],
|
||||||
'ubuntu_20' => [
|
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||||
'latest' => 'nginx',
|
|
||||||
],
|
|
||||||
'ubuntu_22' => [
|
|
||||||
'latest' => 'nginx',
|
'latest' => 'nginx',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'mysql' => [
|
'mysql' => [
|
||||||
'ubuntu_18' => [
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
'5.7' => 'mysql',
|
'5.7' => 'mysql',
|
||||||
'8.0' => 'mysql',
|
'8.0' => 'mysql',
|
||||||
],
|
],
|
||||||
'ubuntu_20' => [
|
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||||
'5.7' => 'mysql',
|
|
||||||
'8.0' => 'mysql',
|
|
||||||
],
|
|
||||||
'ubuntu_22' => [
|
|
||||||
'5.7' => 'mysql',
|
'5.7' => 'mysql',
|
||||||
'8.0' => 'mysql',
|
'8.0' => 'mysql',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'mariadb' => [
|
'mariadb' => [
|
||||||
'ubuntu_18' => [
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
'10.3' => 'mariadb',
|
'10.3' => 'mariadb',
|
||||||
'10.4' => 'mariadb',
|
'10.4' => 'mariadb',
|
||||||
],
|
],
|
||||||
'ubuntu_20' => [
|
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||||
'10.3' => 'mariadb',
|
|
||||||
'10.4' => 'mariadb',
|
|
||||||
],
|
|
||||||
'ubuntu_22' => [
|
|
||||||
'10.3' => 'mariadb',
|
'10.3' => 'mariadb',
|
||||||
'10.4' => 'mariadb',
|
'10.4' => 'mariadb',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'postgresql' => [
|
'postgresql' => [
|
||||||
'ubuntu_18' => [
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
'12' => 'postgresql',
|
'12' => 'postgresql',
|
||||||
'13' => 'postgresql',
|
'13' => 'postgresql',
|
||||||
'14' => 'postgresql',
|
'14' => 'postgresql',
|
||||||
'15' => 'postgresql',
|
'15' => 'postgresql',
|
||||||
'16' => 'postgresql',
|
'16' => 'postgresql',
|
||||||
],
|
],
|
||||||
'ubuntu_20' => [
|
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||||
'12' => 'postgresql',
|
|
||||||
'13' => 'postgresql',
|
|
||||||
'14' => 'postgresql',
|
|
||||||
'15' => 'postgresql',
|
|
||||||
'16' => 'postgresql',
|
|
||||||
],
|
|
||||||
'ubuntu_22' => [
|
|
||||||
'12' => 'postgresql',
|
'12' => 'postgresql',
|
||||||
'13' => 'postgresql',
|
'13' => 'postgresql',
|
||||||
'14' => 'postgresql',
|
'14' => 'postgresql',
|
||||||
@ -241,19 +202,7 @@
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'php' => [
|
'php' => [
|
||||||
'ubuntu_18' => [
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
'5.6' => 'php5.6-fpm',
|
|
||||||
'7.0' => 'php7.0-fpm',
|
|
||||||
'7.1' => 'php7.1-fpm',
|
|
||||||
'7.2' => 'php7.2-fpm',
|
|
||||||
'7.3' => 'php7.3-fpm',
|
|
||||||
'7.4' => 'php7.4-fpm',
|
|
||||||
'8.0' => 'php8.0-fpm',
|
|
||||||
'8.1' => 'php8.1-fpm',
|
|
||||||
'8.2' => 'php8.2-fpm',
|
|
||||||
'8.3' => 'php8.3-fpm',
|
|
||||||
],
|
|
||||||
'ubuntu_20' => [
|
|
||||||
'5.6' => 'php5.6-fpm',
|
'5.6' => 'php5.6-fpm',
|
||||||
'7.0' => 'php7.0-fpm',
|
'7.0' => 'php7.0-fpm',
|
||||||
'7.1' => 'php7.1-fpm',
|
'7.1' => 'php7.1-fpm',
|
||||||
@ -264,7 +213,7 @@
|
|||||||
'8.1' => 'php8.1-fpm',
|
'8.1' => 'php8.1-fpm',
|
||||||
'8.3' => 'php8.3-fpm',
|
'8.3' => 'php8.3-fpm',
|
||||||
],
|
],
|
||||||
'ubuntu_22' => [
|
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||||
'5.6' => 'php5.6-fpm',
|
'5.6' => 'php5.6-fpm',
|
||||||
'7.0' => 'php7.0-fpm',
|
'7.0' => 'php7.0-fpm',
|
||||||
'7.1' => 'php7.1-fpm',
|
'7.1' => 'php7.1-fpm',
|
||||||
@ -278,36 +227,35 @@
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'redis' => [
|
'redis' => [
|
||||||
'ubuntu_18' => [
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
'latest' => 'redis',
|
'latest' => 'redis',
|
||||||
],
|
],
|
||||||
'ubuntu_20' => [
|
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||||
'latest' => 'redis',
|
|
||||||
],
|
|
||||||
'ubuntu_22' => [
|
|
||||||
'latest' => 'redis',
|
'latest' => 'redis',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'supervisor' => [
|
'supervisor' => [
|
||||||
'ubuntu_18' => [
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
'latest' => 'supervisor',
|
'latest' => 'supervisor',
|
||||||
],
|
],
|
||||||
'ubuntu_20' => [
|
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||||
'latest' => 'supervisor',
|
|
||||||
],
|
|
||||||
'ubuntu_22' => [
|
|
||||||
'latest' => 'supervisor',
|
'latest' => 'supervisor',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'ufw' => [
|
'ufw' => [
|
||||||
'ubuntu_18' => [
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
'latest' => 'ufw',
|
'latest' => 'ufw',
|
||||||
],
|
],
|
||||||
'ubuntu_20' => [
|
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||||
'latest' => 'ufw',
|
'latest' => 'ufw',
|
||||||
],
|
],
|
||||||
'ubuntu_22' => [
|
],
|
||||||
'latest' => 'ufw',
|
'vito-agent' => [
|
||||||
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
|
'latest' => 'vito-agent',
|
||||||
|
],
|
||||||
|
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||||
|
'latest' => 'vito-agent',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -320,12 +268,14 @@
|
|||||||
\App\Enums\SiteType::PHP_BLANK,
|
\App\Enums\SiteType::PHP_BLANK,
|
||||||
\App\Enums\SiteType::LARAVEL,
|
\App\Enums\SiteType::LARAVEL,
|
||||||
\App\Enums\SiteType::WORDPRESS,
|
\App\Enums\SiteType::WORDPRESS,
|
||||||
|
\App\Enums\SiteType::PHPMYADMIN,
|
||||||
],
|
],
|
||||||
'site_types_class' => [
|
'site_types_class' => [
|
||||||
\App\Enums\SiteType::PHP => PHPSite::class,
|
\App\Enums\SiteType::PHP => \App\SiteTypes\PHPSite::class,
|
||||||
\App\Enums\SiteType::PHP_BLANK => PHPBlank::class,
|
\App\Enums\SiteType::PHP_BLANK => \App\SiteTypes\PHPBlank::class,
|
||||||
\App\Enums\SiteType::LARAVEL => Laravel::class,
|
\App\Enums\SiteType::LARAVEL => \App\SiteTypes\Laravel::class,
|
||||||
\App\Enums\SiteType::WORDPRESS => Wordpress::class,
|
\App\Enums\SiteType::WORDPRESS => \App\SiteTypes\Wordpress::class,
|
||||||
|
\App\Enums\SiteType::PHPMYADMIN => \App\SiteTypes\PHPMyAdmin::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -337,9 +287,9 @@
|
|||||||
'bitbucket',
|
'bitbucket',
|
||||||
],
|
],
|
||||||
'source_control_providers_class' => [
|
'source_control_providers_class' => [
|
||||||
'github' => Github::class,
|
'github' => \App\SourceControlProviders\Github::class,
|
||||||
'gitlab' => Gitlab::class,
|
'gitlab' => \App\SourceControlProviders\Gitlab::class,
|
||||||
'bitbucket' => Bitbucket::class,
|
'bitbucket' => \App\SourceControlProviders\Bitbucket::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -397,26 +347,33 @@
|
|||||||
\App\Enums\NotificationChannel::TELEGRAM,
|
\App\Enums\NotificationChannel::TELEGRAM,
|
||||||
],
|
],
|
||||||
'notification_channels_providers_class' => [
|
'notification_channels_providers_class' => [
|
||||||
\App\Enums\NotificationChannel::SLACK => Slack::class,
|
\App\Enums\NotificationChannel::SLACK => \App\NotificationChannels\Slack::class,
|
||||||
\App\Enums\NotificationChannel::DISCORD => Discord::class,
|
\App\Enums\NotificationChannel::DISCORD => \App\NotificationChannels\Discord::class,
|
||||||
\App\Enums\NotificationChannel::EMAIL => Email::class,
|
\App\Enums\NotificationChannel::EMAIL => \App\NotificationChannels\Email::class,
|
||||||
\App\Enums\NotificationChannel::TELEGRAM => Telegram::class,
|
\App\Enums\NotificationChannel::TELEGRAM => \App\NotificationChannels\Telegram::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* storage providers
|
* storage providers
|
||||||
*/
|
*/
|
||||||
'storage_providers' => [
|
'storage_providers' => [
|
||||||
StorageProvider::DROPBOX,
|
\App\Enums\StorageProvider::DROPBOX,
|
||||||
StorageProvider::FTP,
|
\App\Enums\StorageProvider::FTP,
|
||||||
],
|
],
|
||||||
'storage_providers_class' => [
|
'storage_providers_class' => [
|
||||||
'dropbox' => Dropbox::class,
|
'dropbox' => \App\StorageProviders\Dropbox::class,
|
||||||
'ftp' => FTP::class,
|
'ftp' => \App\StorageProviders\Ftp::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'ssl_types' => [
|
'ssl_types' => [
|
||||||
\App\Enums\SslType::LETSENCRYPT,
|
\App\Enums\SslType::LETSENCRYPT,
|
||||||
\App\Enums\SslType::CUSTOM,
|
\App\Enums\SslType::CUSTOM,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'metrics_data_retention' => [
|
||||||
|
7,
|
||||||
|
14,
|
||||||
|
30,
|
||||||
|
90,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
22
database/factories/MetricFactory.php
Normal file
22
database/factories/MetricFactory.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class MetricFactory extends Factory
|
||||||
|
{
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'server_id' => 1,
|
||||||
|
'load' => $this->faker->randomFloat(2, 0, 100),
|
||||||
|
'memory_total' => $this->faker->randomFloat(0, 0, 100),
|
||||||
|
'memory_used' => $this->faker->randomFloat(0, 0, 100),
|
||||||
|
'memory_free' => $this->faker->randomFloat(0, 0, 100),
|
||||||
|
'disk_total' => $this->faker->randomFloat(0, 0, 100),
|
||||||
|
'disk_used' => $this->faker->randomFloat(0, 0, 100),
|
||||||
|
'disk_free' => $this->faker->randomFloat(0, 0, 100),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_logs', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_remote')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_logs', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_remote');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('metrics', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('server_id');
|
||||||
|
$table->decimal('load', 5, 2);
|
||||||
|
$table->decimal('memory_total', 15, 0);
|
||||||
|
$table->decimal('memory_used', 15, 0);
|
||||||
|
$table->decimal('memory_free', 15, 0);
|
||||||
|
$table->decimal('disk_total', 15, 0);
|
||||||
|
$table->decimal('disk_used', 15, 0);
|
||||||
|
$table->decimal('disk_free', 15, 0);
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->index(['server_id', 'created_at']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('metrics');
|
||||||
|
}
|
||||||
|
};
|
32
database/seeders/MetricsSeeder.php
Normal file
32
database/seeders/MetricsSeeder.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\Metric;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Carbon\CarbonPeriod;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class MetricsSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
Metric::query()->delete();
|
||||||
|
|
||||||
|
$monitoring = Service::query()
|
||||||
|
->where('type', 'monitoring')
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$range = CarbonPeriod::create(Carbon::now()->subDays(7), '1 minute', Carbon::now());
|
||||||
|
foreach ($range as $date) {
|
||||||
|
Metric::factory()->create([
|
||||||
|
'server_id' => $monitoring->server_id,
|
||||||
|
'created_at' => $date,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,29 @@
|
|||||||
|
ARG RELEASE=1
|
||||||
|
|
||||||
FROM ubuntu:22.04
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
ENV RELEASE_ARG=$RELEASE
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
# upgrade
|
# upgrade
|
||||||
RUN apt clean && apt update && apt update && apt upgrade -y && apt autoremove -y
|
RUN apt-get clean && apt-get update && apt-get update && apt-get upgrade -y && apt-get autoremove -y
|
||||||
|
|
||||||
# requirements
|
# requirements
|
||||||
RUN apt install -y software-properties-common curl zip unzip git gcc
|
RUN apt-get install -y software-properties-common curl zip unzip git gcc
|
||||||
|
|
||||||
# nginx
|
# nginx
|
||||||
RUN apt install -y nginx
|
RUN apt-get install -y nginx
|
||||||
|
|
||||||
# php
|
# php
|
||||||
RUN apt update \
|
RUN apt-get update \
|
||||||
&& apt install -y gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \
|
&& apt-get install -y cron gnupg gosu curl ca-certificates zip unzip git supervisor libcap2-bin libpng-dev \
|
||||||
python2 dnsutils librsvg2-bin fswatch wget \
|
python2 dnsutils librsvg2-bin fswatch wget \
|
||||||
&& add-apt-repository ppa:ondrej/php -y \
|
&& add-apt-repository ppa:ondrej/php -y \
|
||||||
&& apt update \
|
&& apt-get update \
|
||||||
&& apt install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \
|
&& apt-get install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \
|
||||||
php8.2-curl php8.2-gettext php8.2-zip php8.2-bcmath php8.2-soap php8.2-redis php8.2-sqlite3
|
php8.2-curl php8.2-gettext php8.2-zip php8.2-bcmath php8.2-soap php8.2-redis php8.2-sqlite3
|
||||||
COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
|
COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
|
||||||
|
|
||||||
@ -27,9 +31,9 @@ COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
|
|||||||
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
|
|
||||||
# app
|
# app
|
||||||
COPY . /var/www/html
|
RUN rm -rf /var/www/html
|
||||||
RUN rm -rf /var/www/html/vendor
|
RUN git clone -b 1.x https://github.com/vitodeploy/vito.git /var/www/html
|
||||||
RUN rm -rf /var/www/html/.env
|
RUN [ "$RELEASE_ARG" = "1" ] && git checkout $(git tag -l --merged 1.x --sort=-v:refname | head -n 1) || true
|
||||||
RUN composer install --no-dev --prefer-dist
|
RUN composer install --no-dev --prefer-dist
|
||||||
RUN chown -R www-data:www-data /var/www/html \
|
RUN chown -R www-data:www-data /var/www/html \
|
||||||
&& chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache
|
&& chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache
|
||||||
@ -40,6 +44,8 @@ RUN rm /etc/nginx/sites-enabled/default
|
|||||||
COPY docker/nginx.conf /etc/nginx/sites-available/default
|
COPY docker/nginx.conf /etc/nginx/sites-available/default
|
||||||
RUN ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
|
RUN ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
|
||||||
|
|
||||||
|
RUN echo "* * * * * cd /var/www/html && php artisan schedule:run >> /var/log/cron.log 2>&1" | crontab -
|
||||||
|
|
||||||
# supervisord
|
# supervisord
|
||||||
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
|
||||||
|
@ -37,6 +37,8 @@ php /var/www/html/artisan view:cache
|
|||||||
|
|
||||||
php /var/www/html/artisan user:create "$NAME" "$EMAIL" "$PASSWORD"
|
php /var/www/html/artisan user:create "$NAME" "$EMAIL" "$PASSWORD"
|
||||||
|
|
||||||
|
cron
|
||||||
|
|
||||||
echo "Vito is running! 🚀"
|
echo "Vito is running! 🚀"
|
||||||
|
|
||||||
/usr/bin/supervisord
|
/usr/bin/supervisord
|
||||||
|
239
package-lock.json
generated
239
package-lock.json
generated
@ -8,8 +8,10 @@
|
|||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"alpinejs": "^3.4.2",
|
"alpinejs": "^3.4.2",
|
||||||
|
"apexcharts": "^3.44.2",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
"flowbite": "^2.3.0",
|
"flowbite": "^2.3.0",
|
||||||
|
"flowbite-datepicker": "^1.2.6",
|
||||||
"htmx.org": "^1.9.10",
|
"htmx.org": "^1.9.10",
|
||||||
"laravel-echo": "^1.15.0",
|
"laravel-echo": "^1.15.0",
|
||||||
"laravel-vite-plugin": "^0.7.2",
|
"laravel-vite-plugin": "^0.7.2",
|
||||||
@ -20,7 +22,7 @@
|
|||||||
"tailwindcss": "^3.1.0",
|
"tailwindcss": "^3.1.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"toastr": "^2.1.4",
|
"toastr": "^2.1.4",
|
||||||
"vite": "^4.5.2"
|
"vite": "^4.5.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
@ -529,6 +531,12 @@
|
|||||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@yr/monotone-cubic-spline": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/alpinejs": {
|
"node_modules/alpinejs": {
|
||||||
"version": "3.12.0",
|
"version": "3.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz",
|
||||||
@ -557,6 +565,21 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/apexcharts": {
|
||||||
|
"version": "3.48.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.48.0.tgz",
|
||||||
|
"integrity": "sha512-Lhpj1Ij6lKlrUke8gf+P+SE6uGUn+Pe1TnCJ+zqrY0YMvbqM3LMb1lY+eybbTczUyk0RmMZomlTa2NgX2EUs4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@yr/monotone-cubic-spline": "^1.0.3",
|
||||||
|
"svg.draggable.js": "^2.2.2",
|
||||||
|
"svg.easing.js": "^2.0.0",
|
||||||
|
"svg.filter.js": "^2.0.2",
|
||||||
|
"svg.pathmorphing.js": "^0.1.3",
|
||||||
|
"svg.resize.js": "^1.4.3",
|
||||||
|
"svg.select.js": "^3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/arg": {
|
"node_modules/arg": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
@ -885,6 +908,15 @@
|
|||||||
"mini-svg-data-uri": "^1.4.3"
|
"mini-svg-data-uri": "^1.4.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/flowbite-datepicker": {
|
||||||
|
"version": "1.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.2.6.tgz",
|
||||||
|
"integrity": "sha512-UbU/xXs9HFiwWfL4M1vpwIo8EpS0NUQSOvYnp0Z9u3N118nU7lPFGoUOq7su9d0aOJy9FssXzx1SZwN8MXhE1g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"flowbite": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fraction.js": {
|
"node_modules/fraction.js": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||||
@ -1680,6 +1712,97 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/svg.draggable.js": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"svg.js": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/svg.easing.js": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"svg.js": ">=2.3.x"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/svg.filter.js": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"svg.js": "^2.2.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/svg.js": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/svg.pathmorphing.js": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"svg.js": "^2.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/svg.resize.js": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"svg.js": "^2.6.5",
|
||||||
|
"svg.select.js": "^2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/svg.resize.js/node_modules/svg.select.js": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"svg.js": "^2.2.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/svg.select.js": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"svg.js": "^2.6.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
|
||||||
@ -1812,9 +1935,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "4.5.2",
|
"version": "4.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.18.10",
|
"esbuild": "^0.18.10",
|
||||||
@ -2177,6 +2300,12 @@
|
|||||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@yr/monotone-cubic-spline": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"alpinejs": {
|
"alpinejs": {
|
||||||
"version": "3.12.0",
|
"version": "3.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.0.tgz",
|
||||||
@ -2202,6 +2331,21 @@
|
|||||||
"picomatch": "^2.0.4"
|
"picomatch": "^2.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"apexcharts": {
|
||||||
|
"version": "3.48.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.48.0.tgz",
|
||||||
|
"integrity": "sha512-Lhpj1Ij6lKlrUke8gf+P+SE6uGUn+Pe1TnCJ+zqrY0YMvbqM3LMb1lY+eybbTczUyk0RmMZomlTa2NgX2EUs4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@yr/monotone-cubic-spline": "^1.0.3",
|
||||||
|
"svg.draggable.js": "^2.2.2",
|
||||||
|
"svg.easing.js": "^2.0.0",
|
||||||
|
"svg.filter.js": "^2.0.2",
|
||||||
|
"svg.pathmorphing.js": "^0.1.3",
|
||||||
|
"svg.resize.js": "^1.4.3",
|
||||||
|
"svg.select.js": "^3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"arg": {
|
"arg": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
@ -2434,6 +2578,15 @@
|
|||||||
"mini-svg-data-uri": "^1.4.3"
|
"mini-svg-data-uri": "^1.4.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flowbite-datepicker": {
|
||||||
|
"version": "1.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.2.6.tgz",
|
||||||
|
"integrity": "sha512-UbU/xXs9HFiwWfL4M1vpwIo8EpS0NUQSOvYnp0Z9u3N118nU7lPFGoUOq7su9d0aOJy9FssXzx1SZwN8MXhE1g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"flowbite": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"fraction.js": {
|
"fraction.js": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||||
@ -2911,6 +3064,78 @@
|
|||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"svg.draggable.js": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"svg.js": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"svg.easing.js": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"svg.js": ">=2.3.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"svg.filter.js": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"svg.js": "^2.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"svg.js": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"svg.pathmorphing.js": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"svg.js": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"svg.resize.js": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"svg.js": "^2.6.5",
|
||||||
|
"svg.select.js": "^2.1.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"svg.select.js": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"svg.js": "^2.2.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"svg.select.js": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"svg.js": "^2.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tailwindcss": {
|
"tailwindcss": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
|
||||||
@ -3011,9 +3236,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"vite": {
|
"vite": {
|
||||||
"version": "4.5.2",
|
"version": "4.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"esbuild": "^0.18.10",
|
"esbuild": "^0.18.10",
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
"tailwindcss": "^3.1.0",
|
"tailwindcss": "^3.1.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"toastr": "^2.1.4",
|
"toastr": "^2.1.4",
|
||||||
"vite": "^4.5.2"
|
"vite": "^4.5.3",
|
||||||
|
"apexcharts": "^3.44.2",
|
||||||
|
"flowbite-datepicker": "^1.2.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
747
public/build/assets/app-66009dff.js
Normal file
747
public/build/assets/app-66009dff.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
public/build/assets/app-f65997bb.css
Normal file
1
public/build/assets/app-f65997bb.css
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user