Compare commits

...

23 Commits
1.4.0 ... 1.6.1

Author SHA1 Message Date
2b849c888e fix update bug 2024-05-15 22:55:35 +02:00
d9a791755e fix updater and add post-update (#213) 2024-05-15 22:49:07 +02:00
e3ea8f975f fix project deletion 404 error (#208) 2024-05-15 13:41:33 +02:00
de468ae1ba Manage site aliases (#206)
* manage site aliases

* build assets

* fix tests
2024-05-15 11:23:24 +02:00
30ef8ad5eb fix letsencrypt for aliases & blank php deployment fix (#204) 2024-05-13 00:37:51 +02:00
88223a61f9 enable/disable cronjobs (#203) 2024-05-11 13:19:19 +02:00
1067a5fd33 build assets 2024-05-11 11:10:15 +02:00
4361305206 build assets 2024-05-11 11:05:53 +02:00
fe331fd2b3 server updates (#202)
* server updates

* add last update check
2024-05-11 10:09:46 +02:00
bbe3ca802d Add Ubuntu 24.04 support (#199)
* ubuntu 24
* updated aws regions and images
2024-05-10 19:10:33 +02:00
765ac21916 restart php after installing phpmyadmin (#198) 2024-05-09 12:52:28 +02:00
016886f307 fix new user bug (#197) 2024-05-09 00:55:52 +02:00
179aefefac source-controls (#193)
* edit source control
* assign project after creation
* global and project scoped source controls
2024-05-08 00:07:11 +02:00
e704a13d6b new toast notification ui (#188) 2024-05-03 10:40:01 +02:00
9936958259 local storage driver & some icon fixes (#187) 2024-05-01 22:54:44 +02:00
f81d928c66 Update README.md 2024-05-01 12:42:16 +02:00
3c4435701d fix deployment button ux (#186) 2024-05-01 09:34:01 +02:00
ebbd81348a build assets 2024-04-29 21:14:32 +02:00
5debbd4f5d update project lowercase 2024-04-29 21:11:07 +02:00
d846acaa8d User management (#185) 2024-04-29 20:58:04 +02:00
35f896eab1 Update feature_request.md
fix #179
2024-04-24 14:40:26 +02:00
25977d2ead move projects from sidebar to topbar (#170)
and fix #169
2024-04-23 21:34:39 +02:00
f0da1c6d8c Accurate deployment statuses (#168) 2024-04-21 16:26:26 +02:00
231 changed files with 5400 additions and 3517 deletions

View File

@ -9,4 +9,4 @@
To request a feature or suggest an idea please add it to the feedback boards
https://features.vitodeploy.com/
https://vitodeploy.featurebase.app/

View File

@ -1,6 +1,6 @@
<p align="center">
<img alt="srcshot 2024-02-23 at 16 26 21@2x" src="https://github.com/vitodeploy/vito/assets/61919774/9b3ae8fe-996a-4e10-b42e-74097f8e5512" alt="VitoDeploy>
<img src="https://github.com/vitodeploy/vito/assets/61919774/8060fded-58e3-4d58-b58b-5b717b0718e9" alt="VitoDeploy>
<p align="center">
<a href="https://github.com/vitodeploy/vito/actions"><img alt="GitHub Workflow Status" src="https://github.com/vitodeploy/vito/workflows/tests/badge.svg"></a>
</p>
@ -50,8 +50,6 @@ ## Credits
- Alpinejs
- HTMX
- Vite
- Toastr by CodeSeven
- Prettier
- Postcss
- Flowbite
- svgrepo.com

View File

@ -2,6 +2,7 @@
namespace App\Actions\CronJob;
use App\Enums\CronjobStatus;
use App\Models\CronJob;
use App\Models\Server;
@ -10,7 +11,9 @@ class DeleteCronJob
public function delete(Server $server, CronJob $cronJob): void
{
$user = $cronJob->user;
$cronJob->delete();
$cronJob->status = CronjobStatus::DELETING;
$cronJob->save();
$server->cron()->update($cronJob->user, CronJob::crontab($server, $user));
$cronJob->delete();
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Actions\CronJob;
use App\Enums\CronjobStatus;
use App\Models\CronJob;
use App\Models\Server;
class DisableCronJob
{
public function disable(Server $server, CronJob $cronJob): void
{
$cronJob->status = CronjobStatus::DISABLING;
$cronJob->save();
$server->cron()->update($cronJob->user, CronJob::crontab($server, $cronJob->user));
$cronJob->status = CronjobStatus::DISABLED;
$cronJob->save();
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Actions\CronJob;
use App\Enums\CronjobStatus;
use App\Models\CronJob;
use App\Models\Server;
class EnableCronJob
{
public function enable(Server $server, CronJob $cronJob): void
{
$cronJob->status = CronjobStatus::ENABLING;
$cronJob->save();
$server->cron()->update($cronJob->user, CronJob::crontab($server, $cronJob->user));
$cronJob->status = CronjobStatus::READY;
$cronJob->save();
}
}

View File

@ -10,26 +10,31 @@ class CreateProject
{
public function create(User $user, array $input): Project
{
$this->validate($user, $input);
if (isset($input['name'])) {
$input['name'] = strtolower($input['name']);
}
$this->validate($input);
$project = new Project([
'user_id' => $user->id,
'name' => $input['name'],
]);
$project->save();
$project->users()->attach($user);
return $project;
}
private function validate(User $user, array $input): void
private function validate(array $input): void
{
Validator::make($input, [
'name' => [
'required',
'string',
'max:255',
'unique:projects,name,NULL,id,user_id,'.$user->id,
'unique:projects,name',
],
])->validate();
}

View File

@ -17,12 +17,16 @@ public function delete(User $user, Project $project): void
}
if ($user->current_project_id == $project->id) {
/** @var Project $randomProject */
$randomProject = $user->projects()->where('id', '!=', $project->id)->first();
$user->current_project_id = $randomProject->id;
$user->save();
throw ValidationException::withMessages([
'project' => __('Cannot delete your current project.'),
]);
}
/** @var Project $randomProject */
$randomProject = $user->projects()->where('project_id', '!=', $project->id)->first();
$user->current_project_id = $randomProject->id;
$user->save();
$project->delete();
}
}

View File

@ -10,6 +10,10 @@ class UpdateProject
{
public function update(Project $project, array $input): Project
{
if (isset($input['name'])) {
$input['name'] = strtolower($input['name']);
}
$this->validate($project, $input);
$project->name = $input['name'];

View File

@ -6,6 +6,7 @@
use App\Enums\SslType;
use App\Models\Site;
use App\Models\Ssl;
use App\SSH\Services\Webserver\Webserver;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
@ -27,14 +28,23 @@ public function create(Site $site, array $input): void
'expires_at' => $input['type'] === SslType::LETSENCRYPT ? now()->addMonths(3) : $input['expires_at'],
'status' => SslStatus::CREATING,
]);
$ssl->domains = [$site->domain];
if (isset($input['aliases']) && $input['aliases']) {
$ssl->domains = array_merge($ssl->domains, $site->aliases);
}
$ssl->save();
dispatch(function () use ($site, $ssl) {
$site->server->webserver()->handler()->setupSSL($ssl);
/** @var Webserver $webserver */
$webserver = $site->server->webserver()->handler();
$webserver->setupSSL($ssl);
$ssl->status = SslStatus::CREATED;
$ssl->save();
$site->type()->edit();
});
})->catch(function () use ($ssl) {
$ssl->status = SslStatus::FAILED;
$ssl->save();
})->onConnection('ssh');
}
/**

View File

@ -2,6 +2,7 @@
namespace App\Actions\Server;
use App\Enums\ServerStatus;
use App\Facades\Notifier;
use App\Models\Server;
use App\Notifications\ServerDisconnected;
@ -15,12 +16,12 @@ public function check(Server $server): Server
try {
$server->ssh()->connect();
$server->refresh();
if ($status == 'disconnected') {
$server->status = 'ready';
if (in_array($status, [ServerStatus::DISCONNECTED, ServerStatus::UPDATING])) {
$server->status = ServerStatus::READY;
$server->save();
}
} catch (Throwable) {
$server->status = 'disconnected';
$server->status = ServerStatus::DISCONNECTED;
$server->save();
Notifier::send($server, new ServerDisconnected($server));
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Actions\Server;
use App\Enums\ServerStatus;
use App\Facades\Notifier;
use App\Models\Server;
use App\Notifications\ServerUpdateFailed;
class Update
{
public function update(Server $server): void
{
$server->status = ServerStatus::UPDATING;
$server->save();
dispatch(function () use ($server) {
$server->os()->upgrade();
$server->checkConnection();
$server->checkForUpdates();
})->catch(function () use ($server) {
Notifier::send($server, new ServerUpdateFailed($server));
$server->checkConnection();
})->onConnection('ssh');
}
}

View File

@ -21,7 +21,7 @@
class CreateSite
{
/**
* @throws ValidationException
* @throws SourceControlIsNotConnected
*/
public function create(Server $server, array $input): Site
{
@ -33,7 +33,7 @@ public function create(Server $server, array $input): Site
'server_id' => $server->id,
'type' => $input['type'],
'domain' => $input['domain'],
'aliases' => isset($input['alias']) ? [$input['alias']] : [],
'aliases' => $input['aliases'] ?? [],
'path' => '/home/'.$server->getSshUser().'/'.$input['domain'],
'status' => SiteStatus::INSTALLING,
]);
@ -115,7 +115,7 @@ private function validateInputs(Server $server, array $input): void
return $query->where('server_id', $server->id);
}),
],
'alias' => [
'aliases.*' => [
new DomainRule(),
],
];

View File

@ -6,6 +6,7 @@
use App\Exceptions\DeploymentScriptIsEmptyException;
use App\Exceptions\SourceControlIsNotConnected;
use App\Models\Deployment;
use App\Models\ServerLog;
use App\Models\Site;
class Deploy
@ -29,7 +30,7 @@ public function run(Site $site): Deployment
'deployment_script_id' => $site->deploymentScript->id,
'status' => DeploymentStatus::DEPLOYING,
]);
$lastCommit = $site->sourceControl()->provider()->getLastCommit($site->repository, $site->branch);
$lastCommit = $site->sourceControl()?->provider()?->getLastCommit($site->repository, $site->branch);
if ($lastCommit) {
$deployment->commit_id = $lastCommit['commit_id'];
$deployment->commit_data = $lastCommit['commit_data'];
@ -37,10 +38,15 @@ public function run(Site $site): Deployment
$deployment->save();
dispatch(function () use ($site, $deployment) {
$log = $site->server->os()->runScript($site->path, $site->deploymentScript->content, $site->id);
$deployment->status = DeploymentStatus::FINISHED;
/** @var ServerLog $log */
$log = ServerLog::make($site->server, 'deploy-'.strtotime('now'))
->forSite($site);
$log->save();
$deployment->log_id = $log->id;
$deployment->save();
$site->server->os()->runScript($site->path, $site->deploymentScript->content, $log);
$deployment->status = DeploymentStatus::FINISHED;
$deployment->save();
})->catch(function () use ($deployment) {
$deployment->status = DeploymentStatus::FAILED;
$deployment->save();

View File

@ -0,0 +1,33 @@
<?php
namespace App\Actions\Site;
use App\Models\Site;
use App\SSH\Services\Webserver\Webserver;
use App\ValidationRules\DomainRule;
use Illuminate\Support\Facades\Validator;
class UpdateAliases
{
public function update(Site $site, array $input): void
{
$this->validate($input);
$site->aliases = $input['aliases'] ?? [];
/** @var Webserver $webserver */
$webserver = $site->server->webserver()->handler();
$webserver->updateVHost($site, ! $site->hasSSL());
$site->save();
}
private function validate(array $input): void
{
Validator::make($input, [
'aliases.*' => [
new DomainRule(),
],
])->validate();
}
}

View File

@ -3,6 +3,7 @@
namespace App\Actions\SourceControl;
use App\Models\SourceControl;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
@ -10,7 +11,7 @@
class ConnectSourceControl
{
public function connect(array $input): void
public function connect(User $user, array $input): void
{
$this->validate($input);
@ -18,6 +19,7 @@ public function connect(array $input): void
'provider' => $input['provider'],
'profile' => $input['name'],
'url' => Arr::has($input, 'url') ? $input['url'] : null,
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
]);
$this->validateProvider($sourceControl, $input);

View File

@ -0,0 +1,54 @@
<?php
namespace App\Actions\SourceControl;
use App\Models\SourceControl;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class EditSourceControl
{
public function edit(SourceControl $sourceControl, User $user, array $input): void
{
$this->validate($input);
$sourceControl->profile = $input['name'];
$sourceControl->url = isset($input['url']) ? $input['url'] : null;
$sourceControl->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
$this->validateProvider($sourceControl, $input);
$sourceControl->provider_data = $sourceControl->provider()->createData($input);
if (! $sourceControl->provider()->connect()) {
throw ValidationException::withMessages([
'token' => __('Cannot connect to :provider or invalid token!', ['provider' => $sourceControl->provider]
),
]);
}
$sourceControl->save();
}
/**
* @throws ValidationException
*/
private function validate(array $input): void
{
$rules = [
'name' => [
'required',
],
];
Validator::make($input, $rules)->validate();
}
/**
* @throws ValidationException
*/
private function validateProvider(SourceControl $sourceControl, array $input): void
{
Validator::make($input, $sourceControl->provider()->createRules($input))->validate();
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Actions\User;
use App\Enums\UserRole;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class CreateUser
{
public function create(array $input): User
{
$this->validate($input);
/** @var User $user */
$user = User::query()->create([
'name' => $input['name'],
'email' => $input['email'],
'role' => $input['role'],
'password' => bcrypt($input['password']),
'timezone' => 'UTC',
]);
return $user;
}
private function validate(array $input): void
{
Validator::make($input, [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
'password' => 'required|string|min:8',
'role' => [
'required',
Rule::in([UserRole::ADMIN, UserRole::USER]),
],
])->validate();
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Actions\User;
use App\Enums\UserRole;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class UpdateUser
{
public function update(User $user, array $input): void
{
$this->validate($user, $input);
$user->name = $input['name'];
$user->email = $input['email'];
$user->timezone = $input['timezone'];
$user->role = $input['role'];
if (isset($input['password']) && $input['password'] !== null) {
$user->password = bcrypt($input['password']);
}
$user->save();
}
private function validate(User $user, array $input): void
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
'timezone' => [
'required',
Rule::in(timezone_identifiers_list()),
],
'role' => [
'required',
Rule::in([UserRole::ADMIN, UserRole::USER]),
function ($attribute, $value, $fail) use ($user) {
if ($user->is(auth()->user()) && $value !== $user->role) {
$fail('You cannot change your own role');
}
},
],
])->validate();
}
}

View File

@ -7,7 +7,7 @@
class CreateUserCommand extends Command
{
protected $signature = 'user:create {name} {email} {password}';
protected $signature = 'user:create {name} {email} {password} {--role=admin}';
protected $description = 'Create a new user';
@ -25,6 +25,7 @@ public function handle(): void
'name' => $this->argument('name'),
'email' => $this->argument('email'),
'password' => bcrypt($this->argument('password')),
'role' => $this->option('role'),
]);
$this->info('User created!');

View File

@ -9,4 +9,10 @@ final class CronjobStatus
const READY = 'ready';
const DELETING = 'deleting';
const ENABLING = 'enabling';
const DISABLING = 'disabling';
const DISABLED = 'disabled';
}

View File

@ -9,4 +9,6 @@ final class OperatingSystem
const UBUNTU20 = 'ubuntu_20';
const UBUNTU22 = 'ubuntu_22';
const UBUNTU24 = 'ubuntu_24';
}

View File

@ -11,4 +11,6 @@ final class ServerStatus
const INSTALLATION_FAILED = 'installation_failed';
const DISCONNECTED = 'disconnected';
const UPDATING = 'updating';
}

View File

@ -7,4 +7,6 @@ final class StorageProvider
const DROPBOX = 'dropbox';
const FTP = 'ftp';
const LOCAL = 'local';
}

10
app/Enums/UserRole.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace App\Enums;
final class UserRole
{
const USER = 'user';
const ADMIN = 'admin';
}

View File

@ -3,6 +3,7 @@
namespace App\Facades;
use App\Models\Server;
use App\Models\ServerLog;
use App\Support\Testing\SSHFake;
use Illuminate\Support\Facades\Facade as FacadeAlias;
@ -10,7 +11,7 @@
* Class SSH
*
* @method static init(Server $server, string $asUser = null)
* @method static setLog(string $logType, int $siteId = null)
* @method static setLog(?ServerLog $log)
* @method static connect()
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false)
* @method static string assertExecuted(array|string $commands)

View File

@ -50,14 +50,9 @@ public function init(Server $server, ?string $asUser = null): self
return $this;
}
public function setLog(string $logType, $siteId = null): self
public function setLog(ServerLog $log): self
{
$this->log = $this->server->logs()->create([
'site_id' => $siteId,
'name' => $this->server->id.'-'.strtotime('now').'-'.$logType.'.log',
'type' => $logType,
'disk' => config('core.logs_disk'),
]);
$this->log = $log;
return $this;
}
@ -98,10 +93,12 @@ public function connect(bool $sftp = false): void
*/
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
{
if ($log) {
$this->setLog($log, $siteId);
} else {
$this->log = null;
if (! $this->log && $log) {
$this->log = ServerLog::make($this->server, $log);
if ($siteId) {
$this->log->forSite($siteId);
}
$this->log->save();
}
try {
@ -132,8 +129,8 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
$this->log?->write($output);
if (Str::contains($output, 'VITO_SSH_ERROR')) {
throw new SSHCommandError('SSH command failed with an error');
if ($this->connection->getExitStatus() !== 0 || Str::contains($output, 'VITO_SSH_ERROR')) {
throw new SSHCommandError('SSH command failed with an error', $this->connection->getExitStatus());
}
return $output;

View File

@ -22,6 +22,8 @@ class ApplicationController extends Controller
{
public function deploy(Server $server, Site $site): HtmxResponse
{
$this->authorize('manage', $server);
try {
app(Deploy::class)->run($site);
@ -41,11 +43,15 @@ public function deploy(Server $server, Site $site): HtmxResponse
public function showDeploymentLog(Server $server, Site $site, Deployment $deployment): RedirectResponse
{
$this->authorize('manage', $server);
return back()->with('content', $deployment->log?->getContent());
}
public function updateDeploymentScript(Server $server, Site $site, Request $request): RedirectResponse
{
$this->authorize('manage', $server);
app(UpdateDeploymentScript::class)->update($site, $request->input());
Toast::success('Deployment script updated!');
@ -55,6 +61,8 @@ public function updateDeploymentScript(Server $server, Site $site, Request $requ
public function updateBranch(Server $server, Site $site, Request $request): RedirectResponse
{
$this->authorize('manage', $server);
app(UpdateBranch::class)->update($site, $request->input());
Toast::success('Branch updated!');
@ -64,11 +72,15 @@ public function updateBranch(Server $server, Site $site, Request $request): Redi
public function getEnv(Server $server, Site $site): RedirectResponse
{
$this->authorize('manage', $server);
return back()->with('env', $site->getEnv());
}
public function updateEnv(Server $server, Site $site, Request $request): RedirectResponse
{
$this->authorize('manage', $server);
app(UpdateEnv::class)->update($site, $request->input());
Toast::success('Env updated!');
@ -78,6 +90,8 @@ public function updateEnv(Server $server, Site $site, Request $request): Redirec
public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
{
$this->authorize('manage', $server);
if (! $site->isAutoDeployment()) {
try {
$site->enableAutoDeployment();
@ -101,6 +115,8 @@ public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
public function disableAutoDeployment(Server $server, Site $site): HtmxResponse
{
$this->authorize('manage', $server);
if ($site->isAutoDeployment()) {
try {
$site->disableAutoDeployment();

View File

@ -11,6 +11,8 @@ class ConsoleController extends Controller
{
public function index(Server $server): View
{
$this->authorize('manage', $server);
return view('console.index', [
'server' => $server,
]);
@ -18,6 +20,8 @@ public function index(Server $server): View
public function run(Server $server, Request $request)
{
$this->authorize('manage', $server);
$this->validate($request, [
'user' => [
'required',

View File

@ -4,6 +4,8 @@
use App\Actions\CronJob\CreateCronJob;
use App\Actions\CronJob\DeleteCronJob;
use App\Actions\CronJob\DisableCronJob;
use App\Actions\CronJob\EnableCronJob;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Models\CronJob;
@ -16,6 +18,8 @@ class CronjobController extends Controller
{
public function index(Server $server): View
{
$this->authorize('manage', $server);
return view('cronjobs.index', [
'server' => $server,
'cronjobs' => $server->cronJobs,
@ -24,6 +28,8 @@ public function index(Server $server): View
public function store(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(CreateCronJob::class)->create($server, $request->input());
Toast::success('Cronjob created successfully.');
@ -33,10 +39,34 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, CronJob $cronJob): RedirectResponse
{
$this->authorize('manage', $server);
app(DeleteCronJob::class)->delete($server, $cronJob);
Toast::success('Cronjob deleted successfully.');
return back();
}
public function enable(Server $server, CronJob $cronJob): RedirectResponse
{
$this->authorize('manage', $server);
app(EnableCronJob::class)->enable($server, $cronJob);
Toast::success('Cronjob enabled successfully.');
return back();
}
public function disable(Server $server, CronJob $cronJob): RedirectResponse
{
$this->authorize('manage', $server);
app(DisableCronJob::class)->disable($server, $cronJob);
Toast::success('Cronjob disabled successfully.');
return back();
}
}

View File

@ -18,6 +18,8 @@ class DatabaseBackupController extends Controller
{
public function show(Server $server, Backup $backup): View
{
$this->authorize('manage', $server);
return view('databases.backups', [
'server' => $server,
'databases' => $server->databases,
@ -28,6 +30,8 @@ public function show(Server $server, Backup $backup): View
public function run(Server $server, Backup $backup): RedirectResponse
{
$this->authorize('manage', $server);
app(RunBackup::class)->run($backup);
Toast::success('Backup is running.');
@ -37,6 +41,8 @@ public function run(Server $server, Backup $backup): RedirectResponse
public function store(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(CreateBackup::class)->create('database', $server, $request->input());
Toast::success('Backup created successfully.');
@ -46,6 +52,8 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, Backup $backup): RedirectResponse
{
$this->authorize('manage', $server);
$backup->delete();
Toast::success('Backup deleted successfully.');
@ -55,6 +63,8 @@ public function destroy(Server $server, Backup $backup): RedirectResponse
public function restore(Server $server, Backup $backup, BackupFile $backupFile, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(RestoreBackup::class)->restore($backupFile, $request->input());
Toast::success('Backup restored successfully.');
@ -64,8 +74,17 @@ public function restore(Server $server, Backup $backup, BackupFile $backupFile,
public function destroyFile(Server $server, Backup $backup, BackupFile $backupFile): RedirectResponse
{
$this->authorize('manage', $server);
$backupFile->delete();
$backupFile
->backup
->storage
->provider()
->ssh($server)
->delete($backupFile->storagePath());
Toast::success('Backup file deleted successfully.');
return back();

View File

@ -17,6 +17,8 @@ class DatabaseController extends Controller
{
public function index(Server $server): View
{
$this->authorize('manage', $server);
return view('databases.index', [
'server' => $server,
'databases' => $server->databases,
@ -27,6 +29,8 @@ public function index(Server $server): View
public function store(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
$database = app(CreateDatabase::class)->create($server, $request->input());
if ($request->input('user')) {
@ -40,6 +44,8 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, Database $database): RedirectResponse
{
$this->authorize('manage', $server);
app(DeleteDatabase::class)->delete($server, $database);
Toast::success('Database deleted successfully.');

View File

@ -16,6 +16,8 @@ class DatabaseUserController extends Controller
{
public function store(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
$database = app(CreateDatabaseUser::class)->create($server, $request->input());
if ($request->input('user')) {
@ -29,6 +31,8 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, DatabaseUser $databaseUser): RedirectResponse
{
$this->authorize('manage', $server);
app(DeleteDatabaseUser::class)->delete($server, $databaseUser);
Toast::success('User deleted successfully.');
@ -38,6 +42,8 @@ public function destroy(Server $server, DatabaseUser $databaseUser): RedirectRes
public function password(Server $server, DatabaseUser $databaseUser): RedirectResponse
{
$this->authorize('manage', $server);
return back()->with([
'password' => $databaseUser->password,
]);
@ -45,6 +51,8 @@ public function password(Server $server, DatabaseUser $databaseUser): RedirectRe
public function link(Server $server, DatabaseUser $databaseUser, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(LinkUser::class)->link($databaseUser, $request->input());
Toast::success('Database linked successfully.');

View File

@ -16,6 +16,8 @@ class FirewallController extends Controller
{
public function index(Server $server): View
{
$this->authorize('manage', $server);
return view('firewall.index', [
'server' => $server,
'rules' => $server->firewallRules,
@ -24,6 +26,8 @@ public function index(Server $server): View
public function store(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(CreateRule::class)->create($server, $request->input());
Toast::success('Firewall rule created!');
@ -33,6 +37,8 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, FirewallRule $firewallRule): RedirectResponse
{
$this->authorize('manage', $server);
app(DeleteRule::class)->delete($server, $firewallRule);
Toast::success('Firewall rule deleted!');

View File

@ -15,6 +15,8 @@ class MetricController extends Controller
{
public function index(Server $server, Request $request): View|RedirectResponse
{
$this->authorize('manage', $server);
$this->checkIfMonitoringServiceInstalled($server);
return view('metrics.index', [
@ -26,6 +28,8 @@ public function index(Server $server, Request $request): View|RedirectResponse
public function settings(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
$this->checkIfMonitoringServiceInstalled($server);
app(UpdateMetricSettings::class)->update($server, $request->input());
@ -37,6 +41,8 @@ public function settings(Server $server, Request $request): HtmxResponse
private function checkIfMonitoringServiceInstalled(Server $server): void
{
$this->authorize('manage', $server);
if (! $server->monitoring()) {
abort(404, 'Monitoring service is not installed on this server');
}

View File

@ -20,6 +20,8 @@ class PHPController extends Controller
{
public function index(Server $server): View
{
$this->authorize('manage', $server);
return view('php.index', [
'server' => $server,
'phps' => $server->services()->where('type', 'php')->get(),
@ -29,6 +31,8 @@ public function index(Server $server): View
public function install(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
try {
app(InstallNewPHP::class)->install($server, $request->input());
@ -42,6 +46,8 @@ public function install(Server $server, Request $request): HtmxResponse
public function installExtension(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(InstallPHPExtension::class)->install($server, $request->input());
Toast::success('PHP extension is being installed! Check the logs');
@ -51,6 +57,8 @@ public function installExtension(Server $server, Request $request): HtmxResponse
public function defaultCli(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(ChangeDefaultCli::class)->change($server, $request->input());
Toast::success('Default PHP CLI is being changed!');
@ -60,6 +68,8 @@ public function defaultCli(Server $server, Request $request): HtmxResponse
public function getIni(Server $server, Request $request): RedirectResponse
{
$this->authorize('manage', $server);
$ini = app(GetPHPIni::class)->getIni($server, $request->input());
return back()->with('ini', $ini);
@ -67,6 +77,8 @@ public function getIni(Server $server, Request $request): RedirectResponse
public function updateIni(Server $server, Request $request): RedirectResponse
{
$this->authorize('manage', $server);
app(UpdatePHPIni::class)->update($server, $request->input());
Toast::success('PHP ini updated!');
@ -78,6 +90,8 @@ public function updateIni(Server $server, Request $request): RedirectResponse
public function uninstall(Server $server, Request $request): RedirectResponse
{
$this->authorize('manage', $server);
app(UninstallPHP::class)->uninstall($server, $request->input());
Toast::success('PHP is being uninstalled!');

View File

@ -1,11 +1,10 @@
<?php
namespace App\Http\Controllers\Settings;
namespace App\Http\Controllers;
use App\Actions\User\UpdateUserPassword;
use App\Actions\User\UpdateUserProfileInformation;
use App\Facades\Toast;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@ -14,7 +13,7 @@ class ProfileController extends Controller
{
public function index(): View
{
return view('settings.profile.index');
return view('profile.index');
}
public function info(Request $request): RedirectResponse

View File

@ -19,6 +19,8 @@ class QueueController extends Controller
{
public function index(Server $server, Site $site): View
{
$this->authorize('manage', $server);
return view('queues.index', [
'server' => $server,
'site' => $site,
@ -28,6 +30,8 @@ public function index(Server $server, Site $site): View
public function store(Server $server, Site $site, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(CreateQueue::class)->create($site, $request->input());
Toast::success('Queue is being created.');
@ -37,6 +41,8 @@ public function store(Server $server, Site $site, Request $request): HtmxRespons
public function action(Server $server, Site $site, Queue $queue, string $action): HtmxResponse
{
$this->authorize('manage', $server);
app(ManageQueue::class)->{$action}($queue);
Toast::success('Queue is about to '.$action);
@ -46,6 +52,8 @@ public function action(Server $server, Site $site, Queue $queue, string $action)
public function destroy(Server $server, Site $site, Queue $queue): RedirectResponse
{
$this->authorize('manage', $server);
app(DeleteQueue::class)->delete($queue);
Toast::success('Queue is being deleted.');
@ -55,6 +63,8 @@ public function destroy(Server $server, Site $site, Queue $queue): RedirectRespo
public function logs(Server $server, Site $site, Queue $queue): RedirectResponse
{
$this->authorize('manage', $server);
return back()->with('content', app(GetQueueLogs::class)->getLogs($queue));
}
}

View File

@ -17,6 +17,8 @@ class SSHKeyController extends Controller
{
public function index(Server $server): View
{
$this->authorize('manage', $server);
return view('server-ssh-keys.index', [
'server' => $server,
'keys' => $server->sshKeys,
@ -25,6 +27,8 @@ public function index(Server $server): View
public function store(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
/** @var \App\Models\SshKey $key */
$key = app(CreateSshKey::class)->create(
$request->user(),
@ -38,6 +42,8 @@ public function store(Server $server, Request $request): HtmxResponse
public function destroy(Server $server, SshKey $sshKey): RedirectResponse
{
$this->authorize('manage', $server);
app(DeleteKeyFromServer::class)->delete($server, $sshKey);
Toast::success('SSH Key has been deleted.');
@ -47,6 +53,8 @@ public function destroy(Server $server, SshKey $sshKey): RedirectResponse
public function deploy(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(DeployKeyToServer::class)->deploy(
$request->user(),
$server,

View File

@ -17,6 +17,8 @@ class SSLController extends Controller
{
public function index(Server $server, Site $site): View
{
$this->authorize('manage', $server);
return view('ssls.index', [
'server' => $server,
'site' => $site,
@ -26,6 +28,8 @@ public function index(Server $server, Site $site): View
public function store(Server $server, Site $site, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(CreateSSL::class)->create($site, $request->input());
Toast::success('SSL certificate is being created.');
@ -35,6 +39,8 @@ public function store(Server $server, Site $site, Request $request): HtmxRespons
public function destroy(Server $server, Site $site, Ssl $ssl): RedirectResponse
{
$this->authorize('manage', $server);
app(DeleteSSL::class)->delete($ssl);
Toast::success('SSL certificate has been deleted.');

View File

@ -4,6 +4,7 @@
use App\Models\Server;
use App\Models\Site;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@ -23,10 +24,22 @@ public function search(Request $request): JsonResponse
$query->where('name', 'like', '%'.$request->input('q').'%')
->orWhere('ip', 'like', '%'.$request->input('q').'%');
})
->whereHas('project', function (Builder $projectQuery) {
$projectQuery->whereHas('users', function (Builder $userQuery) {
$userQuery->where('user_id', auth()->user()->id);
});
})
->get();
$sites = Site::query()
->where('domain', 'like', '%'.$request->input('q').'%')
->whereHas('server', function (Builder $serverQuery) {
$serverQuery->whereHas('project', function (Builder $projectQuery) {
$projectQuery->whereHas('users', function (Builder $userQuery) {
$userQuery->where('user_id', auth()->user()->id);
});
});
})
->get();
$result = [];

View File

@ -19,6 +19,9 @@ public function index(): View
{
/** @var User $user */
$user = auth()->user();
$this->authorize('viewAny', [Server::class, $user->currentProject]);
$servers = $user->currentProject->servers()->orderByDesc('created_at')->get();
return view('servers.index', compact('servers'));
@ -26,6 +29,11 @@ public function index(): View
public function create(Request $request): View
{
/** @var User $user */
$user = auth()->user();
$this->authorize('create', [Server::class, $user->currentProject]);
$provider = $request->query('provider', old('provider', \App\Enums\ServerProvider::CUSTOM));
$serverProviders = ServerProvider::query()->where('provider', $provider)->get();
@ -40,8 +48,13 @@ public function create(Request $request): View
*/
public function store(Request $request): HtmxResponse
{
/** @var User $user */
$user = auth()->user();
$this->authorize('create', [Server::class, $user->currentProject]);
$server = app(CreateServer::class)->create(
$request->user(),
$user,
$request->input()
);
@ -52,6 +65,8 @@ public function store(Request $request): HtmxResponse
public function show(Server $server): View
{
$this->authorize('view', $server);
return view('servers.show', [
'server' => $server,
]);
@ -59,6 +74,8 @@ public function show(Server $server): View
public function delete(Server $server): RedirectResponse
{
$this->authorize('delete', $server);
$server->delete();
Toast::success('Server deleted successfully.');

View File

@ -14,6 +14,8 @@ class ServerLogController extends Controller
{
public function index(Server $server): View
{
$this->authorize('manage', $server);
return view('server-logs.index', [
'server' => $server,
'pageTitle' => __('Vito Logs'),
@ -22,6 +24,8 @@ public function index(Server $server): View
public function show(Server $server, ServerLog $serverLog): RedirectResponse
{
$this->authorize('manage', $server);
if ($server->id != $serverLog->server_id) {
abort(404);
}
@ -33,6 +37,8 @@ public function show(Server $server, ServerLog $serverLog): RedirectResponse
public function remote(Server $server): View
{
$this->authorize('manage', $server);
return view('server-logs.remote-logs', [
'server' => $server,
'remote' => true,
@ -42,6 +48,8 @@ public function remote(Server $server): View
public function store(Server $server, Request $request): \App\Helpers\HtmxResponse
{
$this->authorize('manage', $server);
app(CreateServerLog::class)->create($server, $request->input());
Toast::success('Log added successfully.');
@ -51,6 +59,8 @@ public function store(Server $server, Request $request): \App\Helpers\HtmxRespon
public function destroy(Server $server, ServerLog $serverLog): RedirectResponse
{
$this->authorize('manage', $server);
$serverLog->delete();
Toast::success('Remote log deleted successfully.');

View File

@ -4,6 +4,7 @@
use App\Actions\Server\EditServer;
use App\Actions\Server\RebootServer;
use App\Actions\Server\Update;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Models\Server;
@ -15,11 +16,15 @@ class ServerSettingController extends Controller
{
public function index(Server $server): View
{
$this->authorize('manage', $server);
return view('server-settings.index', compact('server'));
}
public function checkConnection(Server $server): RedirectResponse|HtmxResponse
{
$this->authorize('manage', $server);
$oldStatus = $server->status;
$server = $server->checkConnection();
@ -41,6 +46,8 @@ public function checkConnection(Server $server): RedirectResponse|HtmxResponse
public function reboot(Server $server): HtmxResponse
{
$this->authorize('manage', $server);
app(RebootServer::class)->reboot($server);
Toast::info('Server is rebooting.');
@ -50,10 +57,32 @@ public function reboot(Server $server): HtmxResponse
public function edit(Request $request, Server $server): RedirectResponse
{
$this->authorize('manage', $server);
app(EditServer::class)->edit($server, $request->input());
Toast::success('Server updated.');
return back();
}
public function checkUpdates(Server $server): RedirectResponse
{
$this->authorize('manage', $server);
$server->checkForUpdates();
return back();
}
public function update(Server $server): HtmxResponse
{
$this->authorize('manage', $server);
app(Update::class)->update($server);
Toast::info('Updating server. This may take a few minutes.');
return htmx()->back();
}
}

View File

@ -16,6 +16,8 @@ class ServiceController extends Controller
{
public function index(Server $server): View
{
$this->authorize('manage', $server);
return view('services.index', [
'server' => $server,
'services' => $server->services,
@ -24,6 +26,8 @@ public function index(Server $server): View
public function start(Server $server, Service $service): RedirectResponse
{
$this->authorize('manage', $server);
$service->start();
Toast::success('Service is being started!');
@ -33,6 +37,8 @@ public function start(Server $server, Service $service): RedirectResponse
public function stop(Server $server, Service $service): RedirectResponse
{
$this->authorize('manage', $server);
$service->stop();
Toast::success('Service is being stopped!');
@ -42,6 +48,8 @@ public function stop(Server $server, Service $service): RedirectResponse
public function restart(Server $server, Service $service): RedirectResponse
{
$this->authorize('manage', $server);
$service->restart();
Toast::success('Service is being restarted!');
@ -51,6 +59,8 @@ public function restart(Server $server, Service $service): RedirectResponse
public function enable(Server $server, Service $service): RedirectResponse
{
$this->authorize('manage', $server);
$service->enable();
Toast::success('Service is being enabled!');
@ -60,6 +70,8 @@ public function enable(Server $server, Service $service): RedirectResponse
public function disable(Server $server, Service $service): RedirectResponse
{
$this->authorize('manage', $server);
$service->disable();
Toast::success('Service is being disabled!');
@ -69,15 +81,19 @@ public function disable(Server $server, Service $service): RedirectResponse
public function install(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(Install::class)->install($server, $request->input());
Toast::success('Service is being uninstalled!');
Toast::success('Service is being installed!');
return htmx()->back();
}
public function uninstall(Server $server, Service $service): HtmxResponse
{
$this->authorize('manage', $server);
app(Uninstall::class)->uninstall($service);
Toast::success('Service is being uninstalled!');

View File

@ -29,7 +29,7 @@ public function add(Request $request): HtmxResponse
Toast::success('Channel added successfully');
return htmx()->redirect(route('notification-channels'));
return htmx()->redirect(route('settings.notification-channels'));
}
public function delete(int $id): RedirectResponse
@ -40,6 +40,6 @@ public function delete(int $id): RedirectResponse
Toast::success('Channel deleted successfully');
return redirect()->route('notification-channels');
return redirect()->route('settings.notification-channels');
}
}

View File

@ -19,40 +19,42 @@ class ProjectController extends Controller
{
public function index(): View
{
$this->authorize('viewAny', Project::class);
return view('settings.projects.index', [
'projects' => auth()->user()->projects,
'projects' => Project::all(),
]);
}
public function create(Request $request): HtmxResponse
{
$this->authorize('create', Project::class);
app(CreateProject::class)->create($request->user(), $request->input());
Toast::success('Project created.');
return htmx()->redirect(route('projects'));
return htmx()->redirect(route('settings.projects'));
}
public function update(Request $request, Project $project): HtmxResponse
{
/** @var Project $project */
$project = $request->user()->projects()->findOrFail($project->id);
$this->authorize('update', $project);
app(UpdateProject::class)->update($project, $request->input());
Toast::success('Project updated.');
return htmx()->redirect(route('projects'));
return htmx()->redirect(route('settings.projects'));
}
public function delete(Project $project): RedirectResponse
{
$this->authorize('delete', $project);
/** @var User $user */
$user = auth()->user();
/** @var Project $project */
$project = $user->projects()->findOrFail($project->id);
try {
app(DeleteProject::class)->delete($user, $project);
} catch (ValidationException $e) {
@ -74,6 +76,8 @@ public function switch($projectId): RedirectResponse
/** @var Project $project */
$project = $user->projects()->findOrFail($projectId);
$this->authorize('view', $project);
$user->current_project_id = $project->id;
$user->save();

View File

@ -29,7 +29,7 @@ public function add(Request $request): HtmxResponse
Toast::success('SSH Key added');
return htmx()->redirect(route('ssh-keys'));
return htmx()->redirect(route('settings.ssh-keys'));
}
public function delete(int $id): RedirectResponse
@ -40,6 +40,6 @@ public function delete(int $id): RedirectResponse
Toast::success('SSH Key deleted');
return redirect()->route('ssh-keys');
return redirect()->route('settings.ssh-keys');
}
}

View File

@ -30,7 +30,7 @@ public function connect(Request $request): HtmxResponse
Toast::success('Server provider connected.');
return htmx()->redirect(route('server-providers'));
return htmx()->redirect(route('settings.server-providers'));
}
public function delete(ServerProvider $serverProvider): RedirectResponse

View File

@ -4,6 +4,7 @@
use App\Actions\SourceControl\ConnectSourceControl;
use App\Actions\SourceControl\DeleteSourceControl;
use App\Actions\SourceControl\EditSourceControl;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Http\Controllers\Controller;
@ -14,22 +15,42 @@
class SourceControlController extends Controller
{
public function index(): View
public function index(Request $request): View
{
return view('settings.source-controls.index', [
'sourceControls' => SourceControl::query()->orderByDesc('id')->get(),
]);
$data = [
'sourceControls' => SourceControl::getByCurrentProject(),
];
if ($request->has('edit')) {
$data['editSourceControl'] = SourceControl::find($request->input('edit'));
}
return view('settings.source-controls.index', $data);
}
public function connect(Request $request): HtmxResponse
{
app(ConnectSourceControl::class)->connect(
$request->user(),
$request->input(),
);
Toast::success('Source control connected.');
return htmx()->redirect(route('source-controls'));
return htmx()->redirect(route('settings.source-controls'));
}
public function update(SourceControl $sourceControl, Request $request): HtmxResponse
{
app(EditSourceControl::class)->edit(
$sourceControl,
$request->user(),
$request->input(),
);
Toast::success('Source control updated.');
return htmx()->redirect(route('settings.source-controls'));
}
public function delete(SourceControl $sourceControl): RedirectResponse
@ -44,6 +65,6 @@ public function delete(SourceControl $sourceControl): RedirectResponse
Toast::success('Source control deleted.');
return redirect()->route('source-controls');
return redirect()->route('settings.source-controls');
}
}

View File

@ -30,7 +30,7 @@ public function connect(Request $request): HtmxResponse
Toast::success('Storage provider connected.');
return htmx()->redirect(route('storage-providers'));
return htmx()->redirect(route('settings.storage-providers'));
}
public function delete(StorageProvider $storageProvider): RedirectResponse

View File

@ -0,0 +1,91 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Actions\User\CreateUser;
use App\Actions\User\UpdateUser;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Http\Controllers\Controller;
use App\Models\Project;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class UserController extends Controller
{
public function index(): View
{
$users = User::query()->paginate(20);
return view('settings.users.index', compact('users'));
}
public function store(Request $request): HtmxResponse
{
$user = app(CreateUser::class)->create($request->input());
return htmx()->redirect(route('settings.users.show', $user));
}
public function show(User $user): View
{
return view('settings.users.show', [
'user' => $user,
]);
}
public function update(User $user, Request $request): RedirectResponse
{
app(UpdateUser::class)->update($user, $request->input());
Toast::success('User updated successfully');
return back();
}
public function updateProjects(User $user, Request $request): HtmxResponse
{
$this->validate($request, [
'projects.*' => [
'required',
Rule::exists('projects', 'id'),
],
]);
$user->projects()->sync($request->projects);
if ($user->currentProject && ! $user->projects->contains($user->currentProject)) {
$user->current_project_id = null;
$user->save();
}
/** @var Project $firstProject */
$firstProject = $user->projects->first();
if (! $user->currentProject && $firstProject) {
$user->current_project_id = $firstProject->id;
$user->save();
}
Toast::success('Projects updated successfully');
return htmx()->redirect(route('settings.users.show', $user));
}
public function destroy(User $user): RedirectResponse
{
if ($user->is(request()->user())) {
Toast::error('You cannot delete your own account');
return back();
}
$user->delete();
Toast::success('User deleted successfully');
return redirect()->route('settings.users.index');
}
}

View File

@ -19,6 +19,8 @@ class SiteController extends Controller
{
public function index(Server $server): View
{
$this->authorize('manage', $server);
return view('sites.index', [
'server' => $server,
'sites' => $server->sites()->orderByDesc('id')->get(),
@ -27,6 +29,8 @@ public function index(Server $server): View
public function store(Server $server, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
$site = app(CreateSite::class)->create($server, $request->input());
Toast::success('Site created');
@ -36,6 +40,8 @@ public function store(Server $server, Request $request): HtmxResponse
public function create(Server $server): View
{
$this->authorize('manage', $server);
return view('sites.create', [
'server' => $server,
'type' => old('type', request()->query('type', SiteType::LARAVEL)),
@ -45,6 +51,8 @@ public function create(Server $server): View
public function show(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
{
$this->authorize('manage', $server);
if (in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
if ($request->hasHeader('HX-Request')) {
return htmx()->redirect(route('servers.sites.installing', [$server, $site]));
@ -61,6 +69,8 @@ public function show(Server $server, Site $site, Request $request): View|Redirec
public function installing(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
{
$this->authorize('manage', $server);
if (! in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
if ($request->hasHeader('HX-Request')) {
return htmx()->redirect(route('servers.sites.show', [$server, $site]));
@ -77,6 +87,8 @@ public function installing(Server $server, Site $site, Request $request): View|R
public function destroy(Server $server, Site $site): RedirectResponse
{
$this->authorize('manage', $server);
app(DeleteSite::class)->delete($site);
Toast::success('Site is being deleted');

View File

@ -10,6 +10,8 @@ class SiteLogController extends Controller
{
public function index(Server $server, Site $site): View
{
$this->authorize('manage', $server);
return view('site-logs.index', [
'server' => $server,
'site' => $site,

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Actions\Site\UpdateAliases;
use App\Actions\Site\UpdateSourceControl;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
@ -18,6 +19,8 @@ class SiteSettingController extends Controller
{
public function index(Server $server, Site $site): View
{
$this->authorize('manage', $server);
return view('site-settings.index', [
'server' => $server,
'site' => $site,
@ -26,6 +29,8 @@ public function index(Server $server, Site $site): View
public function getVhost(Server $server, Site $site): RedirectResponse
{
$this->authorize('manage', $server);
/** @var Webserver $handler */
$handler = $server->webserver()->handler();
@ -34,6 +39,8 @@ public function getVhost(Server $server, Site $site): RedirectResponse
public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse
{
$this->authorize('manage', $server);
$this->validate($request, [
'vhost' => 'required|string',
]);
@ -53,6 +60,8 @@ public function updateVhost(Server $server, Site $site, Request $request): Redir
public function updatePHPVersion(Server $server, Site $site, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
$this->validate($request, [
'version' => [
'required',
@ -73,10 +82,23 @@ public function updatePHPVersion(Server $server, Site $site, Request $request):
public function updateSourceControl(Server $server, Site $site, Request $request): HtmxResponse
{
$site = app(UpdateSourceControl::class)->update($site, $request->input());
$this->authorize('manage', $server);
app(UpdateSourceControl::class)->update($site, $request->input());
Toast::success('Source control updated successfully!');
return htmx()->back();
}
public function updateAliases(Server $server, Site $site, Request $request): HtmxResponse
{
$this->authorize('manage', $server);
app(UpdateAliases::class)->update($site, $request->input());
Toast::success('Aliases updated successfully!');
return htmx()->back();
}
}

View File

@ -68,5 +68,7 @@ class Kernel extends HttpKernel
'server-is-ready' => ServerIsReadyMiddleware::class,
'handle-ssh-errors' => HandleSSHErrors::class,
'select-current-project' => SelectCurrentProject::class,
'is-admin' => \App\Http\Middleware\IsAdmin::class,
'must-have-current-project' => \App\Http\Middleware\MustHaveCurrentProject::class,
];
}

View File

@ -14,17 +14,17 @@ class HandleSSHErrors
public function handle(Request $request, Closure $next)
{
$res = $next($request);
if ($res instanceof Response && $res->exception) {
if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) {
Toast::error($res->exception->getMessage());
// if ($res instanceof Response && $res->exception) {
// if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) {
// Toast::error($res->exception->getMessage());
if ($request->hasHeader('HX-Request')) {
return htmx()->back();
}
// if ($request->hasHeader('HX-Request')) {
// return htmx()->back();
// }
return back();
}
}
// return back();
// }
// }
return $res;
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use App\Enums\UserRole;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class IsAdmin
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (auth()->user()->role !== UserRole::ADMIN) {
abort(403, 'You are not authorized to access this page.');
}
return $next($request);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Middleware;
use App\Facades\Toast;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class MustHaveCurrentProject
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
/** @var User $user */
$user = $request->user();
if (! $user->currentProject) {
Toast::warning('Please select a project to continue');
return redirect()->route('profile');
}
return $next($request);
}
}

View File

@ -22,7 +22,7 @@ public function handle(Request $request, Closure $next): Response
/** @var User $user */
$user = $request->user();
if ($server->project_id != $user->current_project_id) {
if ($server->project_id != $user->current_project_id && $user->can('view', $server)) {
$user->current_project_id = $server->project_id;
$user->save();
}

View File

@ -2,6 +2,7 @@
namespace App\Models;
use App\Enums\CronjobStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -41,7 +42,14 @@ public function server(): BelongsTo
public static function crontab(Server $server, string $user): string
{
$data = '';
$cronJobs = $server->cronJobs()->where('user', $user)->get();
$cronJobs = $server->cronJobs()
->where('user', $user)
->whereIn('status', [
CronjobStatus::READY,
CronjobStatus::CREATING,
CronjobStatus::ENABLING,
])
->get();
foreach ($cronJobs as $key => $cronJob) {
$data .= $cronJob->frequency.' '.$cronJob->command;
if ($key != count($cronJobs) - 1) {

View File

@ -7,6 +7,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
@ -18,6 +19,7 @@
* @property User $user
* @property Collection<Server> $servers
* @property Collection<NotificationChannel> $notificationChannels
* @property Collection<SourceControl> $sourceControls
*/
class Project extends Model
{
@ -53,4 +55,14 @@ public function notificationChannels(): HasMany
{
return $this->hasMany(NotificationChannel::class);
}
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'user_project')->withTimestamps();
}
public function sourceControls(): HasMany
{
return $this->hasMany(SourceControl::class);
}
}

View File

@ -9,6 +9,7 @@
use App\SSH\Cron\Cron;
use App\SSH\OS\OS;
use App\SSH\Systemd\Systemd;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@ -55,6 +56,8 @@
* @property Queue[] $daemons
* @property SshKey[] $sshKeys
* @property string $hostname
* @property int $updates
* @property Carbon $last_update_check
*/
class Server extends AbstractModel
{
@ -82,6 +85,8 @@ class Server extends AbstractModel
'security_updates',
'progress',
'progress_step',
'updates',
'last_update_check',
];
protected $casts = [
@ -95,6 +100,8 @@ class Server extends AbstractModel
'available_updates' => 'integer',
'security_updates' => 'integer',
'progress' => 'integer',
'updates' => 'integer',
'last_update_check' => 'datetime',
];
protected $hidden = [
@ -384,4 +391,11 @@ public function cron(): Cron
{
return new Cron($this);
}
public function checkForUpdates(): void
{
$this->updates = $this->os()->availableUpdates();
$this->last_update_check = now();
$this->save();
}
}

View File

@ -115,4 +115,27 @@ public static function log(Server $server, string $type, string $content, ?Site
$log->save();
$log->write($content);
}
public static function make(Server $server, string $type): ServerLog
{
return new static([
'server_id' => $server->id,
'name' => $server->id.'-'.strtotime('now').'-'.$type.'.log',
'type' => $type,
'disk' => config('core.logs_disk'),
]);
}
public function forSite(Site|int $site): ServerLog
{
if ($site instanceof Site) {
$site = $site->id;
}
if (is_int($site)) {
$this->site_id = $site;
}
return $this;
}
}

View File

@ -3,6 +3,7 @@
namespace App\Models;
use App\Exceptions\SourceControlIsNotConnected;
use App\Exceptions\SSHError;
use App\SiteTypes\SiteType;
use App\SSH\Services\Webserver\Webserver;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -271,6 +272,15 @@ public function hasFeature(string $feature): bool
public function getEnv(): string
{
return $this->server->os()->readFile($this->path.'/.env');
try {
return $this->server->os()->readFile($this->path.'/.env');
} catch (SSHError) {
return '';
}
}
public function hasSSL(): bool
{
return $this->ssls->isNotEmpty();
}
}

View File

@ -3,7 +3,9 @@
namespace App\Models;
use App\SourceControlProviders\SourceControlProvider;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
@ -12,6 +14,7 @@
* @property ?string $profile
* @property ?string $url
* @property string $access_token
* @property ?int $project_id
*/
class SourceControl extends AbstractModel
{
@ -23,11 +26,13 @@ class SourceControl extends AbstractModel
'profile',
'url',
'access_token',
'project_id',
];
protected $casts = [
'access_token' => 'encrypted',
'provider_data' => 'encrypted:array',
'project_id' => 'integer',
];
public function provider(): SourceControlProvider
@ -46,4 +51,16 @@ public function sites(): HasMany
{
return $this->hasMany(Site::class);
}
public function project(): BelongsTo
{
return $this->belongsTo(Project::class);
}
public static function getByCurrentProject(): Collection
{
return self::query()
->where('project_id', auth()->user()->current_project_id)
->orWhereNull('project_id')->get();
}
}

View File

@ -17,6 +17,7 @@
* @property string $status
* @property Site $site
* @property string $ca_path
* @property ?array $domains
*/
class Ssl extends AbstractModel
{
@ -30,6 +31,7 @@ class Ssl extends AbstractModel
'ca',
'expires_at',
'status',
'domains',
];
protected $casts = [
@ -38,6 +40,7 @@ class Ssl extends AbstractModel
'pk' => 'encrypted',
'ca' => 'encrypted',
'expires_at' => 'datetime',
'domains' => 'array',
];
public function site(): BelongsTo
@ -111,4 +114,16 @@ public function validateSetup(string $result): bool
return true;
}
public function getDomains(): array
{
if (! empty($this->domains) && is_array($this->domains)) {
return $this->domains;
}
$this->domains = [$this->site->domain];
$this->save();
return $this->domains;
}
}

View File

@ -2,7 +2,9 @@
namespace App\Models;
use App\Enums\UserRole;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
@ -30,6 +32,7 @@
* @property int $current_project_id
* @property Project $currentProject
* @property Collection<Project> $projects
* @property string $role
*/
class User extends Authenticatable
{
@ -43,6 +46,7 @@ class User extends Authenticatable
'password',
'timezone',
'current_project_id',
'role',
];
protected $hidden = [
@ -60,7 +64,9 @@ public static function boot(): void
parent::boot();
static::created(function (User $user) {
$user->createDefaultProject();
if (Project::count() === 0) {
$user->createDefaultProject();
}
});
}
@ -117,9 +123,9 @@ public function connectedSourceControls(): array
return $connectedSourceControls;
}
public function projects(): HasMany
public function projects(): BelongsToMany
{
return $this->hasMany(Project::class);
return $this->belongsToMany(Project::class, 'user_project')->withTimestamps();
}
public function currentProject(): HasOne
@ -138,9 +144,10 @@ public function createDefaultProject(): Project
if (! $project) {
$project = new Project();
$project->user_id = $this->id;
$project->name = 'Default';
$project->name = 'default';
$project->save();
$project->users()->attach($this->id);
}
$this->current_project_id = $project->id;
@ -148,4 +155,9 @@ public function createDefaultProject(): Project
return $project;
}
public function isAdmin(): bool
{
return $this->role === UserRole::ADMIN;
}
}

View File

@ -35,7 +35,7 @@ public function connect(): bool
__('Congratulations! 🎉'),
__("You've connected your Discord to :app", ['app' => config('app.name')])."\n".
__('Manage your notification channels')."\n".
route('notification-channels')
route('settings.notification-channels')
);
if (! $connect) {

View File

@ -35,7 +35,7 @@ public function connect(): bool
__('Congratulations! 🎉'),
__("You've connected your Slack to :app", ['app' => config('app.name')])."\n".
__('Manage your notification channels')."\n".
route('notification-channels')
route('settings.notification-channels')
);
if (! $connect) {

View File

@ -0,0 +1,33 @@
<?php
namespace App\Notifications;
use App\Models\Server;
use Illuminate\Notifications\Messages\MailMessage;
class ServerUpdateFailed extends AbstractNotification
{
protected Server $server;
public function __construct(Server $server)
{
$this->server = $server;
}
public function rawText(): string
{
return __("Update failed for server [:server] \nCheck your server's logs \n:logs", [
'server' => $this->server->name,
'logs' => url('/servers/'.$this->server->id.'/logs'),
]);
}
public function toEmail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject(__('Server update failed!'))
->line('Your server ['.$this->server->name.'] update has been failed.')
->line('Check your server logs')
->action('View Logs', url('/servers/'.$this->server->id.'/logs'));
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Policies;
use App\Enums\UserRole;
use App\Models\Project;
use App\Models\User;
class ProjectPolicy
{
public function viewAny(User $user): bool
{
return $user->role === UserRole::ADMIN;
}
public function view(User $user, Project $project): bool
{
return $user->role === UserRole::ADMIN || $project->users->contains($user);
}
public function create(User $user): bool
{
return $user->role === UserRole::ADMIN;
}
public function update(User $user, Project $project): bool
{
return $user->role === UserRole::ADMIN;
}
public function delete(User $user, Project $project): bool
{
return $user->role === UserRole::ADMIN;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Policies;
use App\Enums\UserRole;
use App\Models\Project;
use App\Models\Server;
use App\Models\User;
class ServerPolicy
{
public function viewAny(User $user, Project $project): bool
{
return $user->role === UserRole::ADMIN || $project->users->contains($user);
}
public function view(User $user, Server $server): bool
{
return $user->role === UserRole::ADMIN || $server->project->users->contains($user);
}
public function create(User $user, Project $project): bool
{
return $user->role === UserRole::ADMIN || $project->users->contains($user);
}
public function update(User $user, Server $server): bool
{
return $user->role === UserRole::ADMIN || $server->project->users->contains($user);
}
public function delete(User $user, Server $server): bool
{
return $user->role === UserRole::ADMIN || $server->project->users->contains($user);
}
public function manage(User $user, Server $server): bool
{
return $user->role === UserRole::ADMIN || $server->project->users->contains($user);
}
}

View File

@ -30,6 +30,19 @@ public function upgrade(): void
);
}
public function availableUpdates(): int
{
$result = $this->server->ssh()->exec(
$this->getScript('available-updates.sh'),
'check-available-updates'
);
// -1 because the first line is not a package
$availableUpdates = str($result)->after('Available updates:')->trim()->toInteger() - 1;
return max($availableUpdates, 0);
}
public function createUser(string $user, string $password, string $key): void
{
$this->server->ssh()->exec(
@ -127,16 +140,18 @@ public function tail(string $path, int $lines): string
);
}
public function runScript(string $path, string $script, ?int $siteId = null): ServerLog
public function runScript(string $path, string $script, ?ServerLog $serverLog): ServerLog
{
$ssh = $this->server->ssh();
if ($serverLog) {
$ssh->setLog($serverLog);
}
$ssh->exec(
$this->getScript('run-script.sh', [
'path' => $path,
'script' => $script,
]),
'run-script',
$siteId
'run-script'
);
return $ssh->log;

View File

@ -0,0 +1,5 @@
sudo DEBIAN_FRONTEND=noninteractive apt-get update
AVAILABLE_UPDATES=$(sudo DEBIAN_FRONTEND=noninteractive apt list --upgradable | wc -l)
echo "Available updates:$AVAILABLE_UPDATES"

View File

@ -1,3 +1,8 @@
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common curl zip unzip git gcc openssl
git config --global user.email "__email__"
git config --global user.name "__name__"
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -;
sudo DEBIAN_FRONTEND=noninteractive apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install nodejs -y

View File

@ -2,8 +2,3 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get clean
sudo DEBIAN_FRONTEND=noninteractive apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove -y
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -;
sudo DEBIAN_FRONTEND=noninteractive apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install nodejs -y

View File

@ -159,7 +159,7 @@ public function runBackup(BackupFile $backupFile): void
);
// cleanup
$this->service->server->ssh()->exec('rm '.$backupFile->name.'.zip');
$this->service->server->ssh()->exec('rm '.$backupFile->path());
$backupFile->size = $upload['size'];
$backupFile->save();

View File

@ -5,6 +5,7 @@
use App\Models\Metric;
use App\SSH\HasScripts;
use App\SSH\Services\AbstractService;
use Closure;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\Rule;
use Ramsey\Uuid\Uuid;
@ -21,7 +22,12 @@ public function creationRules(array $input): array
{
return [
'type' => [
Rule::unique('services', 'type')->where('server_id', $this->service->server_id),
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',

View File

@ -117,9 +117,14 @@ public function changePHPVersion(Site $site, $version): void
*/
public function setupSSL(Ssl $ssl): void
{
$domains = '';
foreach ($ssl->getDomains() as $domain) {
$domains .= ' -d '.$domain;
}
$command = $this->getScript('nginx/create-letsencrypt-ssl.sh', [
'email' => $ssl->site->server->creator->email,
'domain' => $ssl->site->domain,
'domains' => $domains,
'web_directory' => $ssl->site->getWebDirectoryPath(),
]);
if ($ssl->type == 'custom') {

View File

@ -1,3 +1,3 @@
if ! sudo certbot certonly --force-renewal --nginx --noninteractive --agree-tos --cert-name __domain__ -m __email__ -d __domain__ --verbose; then
if ! sudo certbot certonly --force-renewal --nginx --noninteractive --agree-tos --cert-name __domain__ -m __email__ __domains__ --verbose; then
echo 'VITO_SSH_ERROR' && exit 1
fi

View File

@ -1,10 +1,9 @@
server {
listen 80;
listen 443 ssl;
server_name __domain__ www.__domain__;
server_name __domain__ __aliases__;
root __path__/__web_directory__;
ssl on;
ssl_certificate __certificate__;
ssl_certificate_key __private_key__;

View File

@ -1,6 +1,6 @@
server {
listen 80;
server_name __domain__ www.__domain__;
server_name __domain__ __aliases__;
root __path__/__web_directory__;
add_header X-Frame-Options "SAMEORIGIN";

View File

@ -1,31 +0,0 @@
server {
listen __port__;
server_name _;
root /home/vito/phpmyadmin;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php__php_version__-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}

View File

@ -4,7 +4,6 @@ server {
server_name __domain__ __aliases__;
root __path__;
ssl on;
ssl_certificate __certificate__;
ssl_certificate_key __private_key__;

View File

@ -4,7 +4,6 @@ server {
server_name __domain__ __aliases__;
root __path__/__web_directory__;
ssl on;
ssl_certificate __certificate__;
ssl_certificate_key __private_key__;

View File

@ -44,4 +44,12 @@ public function download(string $src, string $dest): void
'download-from-dropbox'
);
}
/**
* @TODO Implement delete method
*/
public function delete(string $path): void
{
//
}
}

View File

@ -45,4 +45,12 @@ public function download(string $src, string $dest): void
'download-from-ftp'
);
}
/**
* @TODO Implement delete method
*/
public function delete(string $path): void
{
//
}
}

49
app/SSH/Storage/Local.php Normal file
View File

@ -0,0 +1,49 @@
<?php
namespace App\SSH\Storage;
use App\SSH\HasScripts;
class Local extends AbstractStorage
{
use HasScripts;
public function upload(string $src, string $dest): array
{
$destDir = dirname($this->storageProvider->credentials['path'].$dest);
$destFile = basename($this->storageProvider->credentials['path'].$dest);
$this->server->ssh()->exec(
$this->getScript('local/upload.sh', [
'src' => $src,
'dest_dir' => $destDir,
'dest_file' => $destFile,
]),
'upload-to-local'
);
return [
'size' => null,
];
}
public function download(string $src, string $dest): void
{
$this->server->ssh()->exec(
$this->getScript('local/download.sh', [
'src' => $this->storageProvider->credentials['path'].$src,
'dest' => $dest,
]),
'download-from-local'
);
}
public function delete(string $path): void
{
$this->server->ssh()->exec(
$this->getScript('local/delete.sh', [
'path' => $this->storageProvider->credentials['path'].$path,
]),
'delete-from-local'
);
}
}

View File

@ -7,4 +7,6 @@ interface Storage
public function upload(string $src, string $dest): array;
public function download(string $src, string $dest): void;
public function delete(string $path): void;
}

View File

@ -0,0 +1 @@
rm __path__

View File

@ -0,0 +1 @@
cp __src__ __dest__

View File

@ -0,0 +1,2 @@
mkdir -p __dest_dir__
cp __src__ __dest_dir__/__dest_file__

View File

@ -3,6 +3,7 @@
namespace App\SiteTypes;
use App\Enums\SiteFeature;
use App\SSH\Services\Webserver\Webserver;
use Illuminate\Validation\Rule;
class PHPBlank extends PHPSite
@ -42,7 +43,9 @@ public function data(array $input): array
public function install(): void
{
$this->site->server->webserver()->handler()->createVHost($this->site);
/** @var Webserver $webserver */
$webserver = $this->site->server->webserver()->handler();
$webserver->createVHost($this->site);
$this->progress(65);
$this->site->php()?->restart();
}

View File

@ -41,9 +41,12 @@ public function data(array $input): array
public function install(): void
{
$this->site->server->webserver()->handler()->createVHost($this->site);
/** @var Webserver $webserver */
$webserver = $this->site->server->webserver()->handler();
$webserver->createVHost($this->site);
$this->progress(30);
app(\App\SSH\PHPMyAdmin\PHPMyAdmin::class)->install($this->site);
$this->progress(65);
$this->site->php()?->restart();
}
}

View File

@ -68,7 +68,9 @@ public function data(array $input): array
*/
public function install(): void
{
$this->site->server->webserver()->handler()->createVHost($this->site);
/** @var Webserver $webserver */
$webserver = $this->site->server->webserver()->handler();
$webserver->createVHost($this->site);
$this->progress(15);
$this->deployKey();
$this->progress(30);

View File

@ -81,7 +81,9 @@ public function data(array $input): array
public function install(): void
{
$this->site->server->webserver()->handler()->createVHost($this->site);
/** @var Webserver $webserver */
$webserver = $this->site->server->webserver()->handler();
$webserver->createVHost($this->site);
$this->progress(30);
/** @var Database $database */
$database = app(CreateDatabase::class)->create($this->site->server, [

View File

@ -0,0 +1,38 @@
<?php
namespace App\StorageProviders;
use App\Models\Server;
use App\SSH\Storage\Storage;
class Local extends AbstractStorageProvider
{
public function validationRules(): array
{
return [
'path' => 'required',
];
}
public function credentialData(array $input): array
{
return [
'path' => $input['path'],
];
}
public function connect(): bool
{
return true;
}
public function ssh(Server $server): Storage
{
return new \App\SSH\Storage\Local($server, $this->storageProvider);
}
public function delete(array $paths): void
{
//
}
}

View File

@ -36,10 +36,13 @@ public function connect(bool $sftp = false): void
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
{
if ($log) {
$this->setLog($log, $siteId);
} else {
$this->log = null;
if (! $this->log && $log) {
$this->log = $this->server->logs()->create([
'site_id' => $siteId,
'name' => $this->server->id.'-'.strtotime('now').'-'.$log.'.log',
'type' => $log,
'disk' => config('core.logs_disk'),
]);
}
$this->commands[] = $command;

View File

@ -5,10 +5,10 @@
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class ProfileLayout extends Component
class SettingsLayout extends Component
{
public function render(): View
{
return view('layouts.profile');
return view('layouts.settings');
}
}

View File

@ -16,6 +16,7 @@
'operating_systems' => [
\App\Enums\OperatingSystem::UBUNTU20,
\App\Enums\OperatingSystem::UBUNTU22,
\App\Enums\OperatingSystem::UBUNTU24,
],
'webservers' => ['none', 'nginx'],
'php_versions' => [
@ -106,26 +107,32 @@
'custom' => [
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
\App\Enums\OperatingSystem::UBUNTU24 => 'root',
],
'aws' => [
\App\Enums\OperatingSystem::UBUNTU20 => 'ubuntu',
\App\Enums\OperatingSystem::UBUNTU22 => 'ubuntu',
\App\Enums\OperatingSystem::UBUNTU24 => 'ubuntu',
],
'linode' => [
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
\App\Enums\OperatingSystem::UBUNTU24 => 'root',
],
'digitalocean' => [
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
\App\Enums\OperatingSystem::UBUNTU24 => 'root',
],
'vultr' => [
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
\App\Enums\OperatingSystem::UBUNTU24 => 'root',
],
'hetzner' => [
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
\App\Enums\OperatingSystem::UBUNTU24 => 'root',
],
],
@ -164,6 +171,9 @@
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'nginx',
],
\App\Enums\OperatingSystem::UBUNTU24 => [
'latest' => 'nginx',
],
],
'mysql' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
@ -174,6 +184,10 @@
'5.7' => 'mysql',
'8.0' => 'mysql',
],
\App\Enums\OperatingSystem::UBUNTU24 => [
'5.7' => 'mysql',
'8.0' => 'mysql',
],
],
'mariadb' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
@ -184,6 +198,10 @@
'10.3' => 'mariadb',
'10.4' => 'mariadb',
],
\App\Enums\OperatingSystem::UBUNTU24 => [
'10.3' => 'mariadb',
'10.4' => 'mariadb',
],
],
'postgresql' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
@ -200,6 +218,13 @@
'15' => 'postgresql',
'16' => 'postgresql',
],
\App\Enums\OperatingSystem::UBUNTU24 => [
'12' => 'postgresql',
'13' => 'postgresql',
'14' => 'postgresql',
'15' => 'postgresql',
'16' => 'postgresql',
],
],
'php' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
@ -225,6 +250,18 @@
'8.2' => 'php8.2-fpm',
'8.3' => 'php8.3-fpm',
],
\App\Enums\OperatingSystem::UBUNTU24 => [
'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',
],
],
'redis' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
@ -233,6 +270,9 @@
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'redis',
],
\App\Enums\OperatingSystem::UBUNTU24 => [
'latest' => 'redis',
],
],
'supervisor' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
@ -241,6 +281,9 @@
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'supervisor',
],
\App\Enums\OperatingSystem::UBUNTU24 => [
'latest' => 'supervisor',
],
],
'ufw' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
@ -249,6 +292,9 @@
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'ufw',
],
\App\Enums\OperatingSystem::UBUNTU24 => [
'latest' => 'ufw',
],
],
'vito-agent' => [
\App\Enums\OperatingSystem::UBUNTU20 => [
@ -257,6 +303,9 @@
\App\Enums\OperatingSystem::UBUNTU22 => [
'latest' => 'vito-agent',
],
\App\Enums\OperatingSystem::UBUNTU24 => [
'latest' => 'vito-agent',
],
],
],
@ -359,10 +408,12 @@
'storage_providers' => [
\App\Enums\StorageProvider::DROPBOX,
\App\Enums\StorageProvider::FTP,
\App\Enums\StorageProvider::LOCAL,
],
'storage_providers_class' => [
'dropbox' => \App\StorageProviders\Dropbox::class,
'ftp' => \App\StorageProviders\Ftp::class,
\App\Enums\StorageProvider::DROPBOX => \App\StorageProviders\Dropbox::class,
\App\Enums\StorageProvider::FTP => \App\StorageProviders\Ftp::class,
\App\Enums\StorageProvider::LOCAL => \App\StorageProviders\Local::class,
],
'ssl_types' => [

Some files were not shown because too many files have changed in this diff Show More