mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 14:06:15 +00:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
7cda14cb76 | |||
3bf3f7eebc | |||
e17fdbb1a0 | |||
e99146209e | |||
1223ea1499 | |||
2356e44f5b | |||
8c7c3d2192 | |||
e2b9d18a71 | |||
75e554ad74 | |||
4d59529767 | |||
ea31d0978f | |||
ee4e9e5452 | |||
48c12e26b2 | |||
72f68b5917 | |||
a889cf11a2 | |||
75a4fde8de | |||
fd67097884 |
7
.github/workflows/docker-latest.yml
vendored
7
.github/workflows/docker-latest.yml
vendored
@ -2,9 +2,9 @@ name: Docker Latest
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 2.x
|
||||
# push:
|
||||
# branches:
|
||||
# - 2.x
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
@ -31,5 +31,4 @@ jobs:
|
||||
-f docker/Dockerfile \
|
||||
-t vitodeploy/vito:latest \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--no-cache \
|
||||
--push
|
||||
|
4
.github/workflows/docker-release.yml
vendored
4
.github/workflows/docker-release.yml
vendored
@ -2,8 +2,8 @@ name: Docker Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ created ]
|
||||
# release:
|
||||
# types: [ created ]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
|
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
@ -40,5 +40,8 @@ jobs:
|
||||
- name: Create sqlite database
|
||||
run: touch storage/database-test.sqlite
|
||||
|
||||
- name: Set up the .env file
|
||||
run: touch .env
|
||||
|
||||
- name: Run test suite
|
||||
run: php artisan test
|
||||
|
@ -15,16 +15,12 @@ class ManageBackupFile
|
||||
*/
|
||||
public function download(BackupFile $file): StreamedResponse
|
||||
{
|
||||
$localFilename = "backup_{$file->id}_{$file->name}.zip";
|
||||
$file->backup->server->ssh()->download(
|
||||
Storage::disk('tmp')->path(basename($file->path())),
|
||||
$file->path()
|
||||
);
|
||||
|
||||
if (! Storage::disk('backups')->exists($localFilename)) {
|
||||
$file->backup->server->ssh()->download(
|
||||
Storage::disk('backups')->path($localFilename),
|
||||
$file->path()
|
||||
);
|
||||
}
|
||||
|
||||
return Storage::disk('backups')->download($localFilename, $file->name.'.zip');
|
||||
return Storage::disk('tmp')->download(basename($file->path()));
|
||||
}
|
||||
|
||||
public function delete(BackupFile $file): void
|
||||
|
39
app/Actions/FileManager/FetchFiles.php
Normal file
39
app/Actions/FileManager/FetchFiles.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\FileManager;
|
||||
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Models\File;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class FetchFiles
|
||||
{
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function fetch(User $user, Server $server, array $input): void
|
||||
{
|
||||
File::parse(
|
||||
$user,
|
||||
$server,
|
||||
$input['path'],
|
||||
$input['user'],
|
||||
$server->os()->ls($input['path'], $input['user'])
|
||||
);
|
||||
}
|
||||
|
||||
public static function rules(Server $server): array
|
||||
{
|
||||
return [
|
||||
'path' => [
|
||||
'required',
|
||||
],
|
||||
'user' => [
|
||||
'required',
|
||||
Rule::in($server->getSshUsers()),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\FirewallRule;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\Firewall\Firewall;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CreateRule
|
||||
{
|
||||
public function create(Server $server, array $input): FirewallRule
|
||||
{
|
||||
$rule = new FirewallRule([
|
||||
'server_id' => $server->id,
|
||||
'type' => $input['type'],
|
||||
'protocol' => $input['protocol'],
|
||||
'port' => $input['port'],
|
||||
'source' => $input['source'],
|
||||
'mask' => $input['mask'] ?? null,
|
||||
]);
|
||||
|
||||
/** @var Firewall $firewallHandler */
|
||||
$firewallHandler = $server->firewall()->handler();
|
||||
$firewallHandler->addRule(
|
||||
$rule->type,
|
||||
$rule->getRealProtocol(),
|
||||
$rule->port,
|
||||
$rule->source,
|
||||
$rule->mask
|
||||
);
|
||||
|
||||
$rule->status = FirewallRuleStatus::READY;
|
||||
$rule->save();
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
'type' => [
|
||||
'required',
|
||||
'in:allow,deny',
|
||||
],
|
||||
'protocol' => [
|
||||
'required',
|
||||
Rule::in(array_keys(config('core.firewall_protocols_port'))),
|
||||
],
|
||||
'port' => [
|
||||
'required',
|
||||
'numeric',
|
||||
'min:1',
|
||||
'max:65535',
|
||||
],
|
||||
'source' => [
|
||||
'required',
|
||||
'ip',
|
||||
],
|
||||
'mask' => [
|
||||
'nullable',
|
||||
'numeric',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\FirewallRule;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
|
||||
class DeleteRule
|
||||
{
|
||||
public function delete(Server $server, FirewallRule $rule): void
|
||||
{
|
||||
$rule->status = FirewallRuleStatus::DELETING;
|
||||
$rule->save();
|
||||
|
||||
$server->firewall()
|
||||
->handler()
|
||||
->removeRule(
|
||||
$rule->type,
|
||||
$rule->getRealProtocol(),
|
||||
$rule->port,
|
||||
$rule->source,
|
||||
$rule->mask
|
||||
);
|
||||
|
||||
$rule->delete();
|
||||
}
|
||||
}
|
117
app/Actions/FirewallRule/ManageRule.php
Executable file
117
app/Actions/FirewallRule/ManageRule.php
Executable file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\FirewallRule;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\Firewall\Firewall;
|
||||
|
||||
class ManageRule
|
||||
{
|
||||
public function create(Server $server, array $input): FirewallRule
|
||||
{
|
||||
$sourceAny = $input['source_any'] ?? empty($input['source'] ?? null);
|
||||
$rule = new FirewallRule([
|
||||
'name' => $input['name'],
|
||||
'server_id' => $server->id,
|
||||
'type' => $input['type'],
|
||||
'protocol' => $input['protocol'],
|
||||
'port' => $input['port'],
|
||||
'source' => $sourceAny ? null : $input['source'],
|
||||
'mask' => $sourceAny ? null : ($input['mask'] ?? null),
|
||||
'status' => FirewallRuleStatus::CREATING,
|
||||
]);
|
||||
|
||||
$rule->save();
|
||||
|
||||
dispatch(fn () => $this->applyRule($rule));
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
public function update(FirewallRule $rule, array $input): FirewallRule
|
||||
{
|
||||
$sourceAny = $input['source_any'] ?? empty($input['source'] ?? null);
|
||||
$rule->update([
|
||||
'name' => $input['name'],
|
||||
'type' => $input['type'],
|
||||
'protocol' => $input['protocol'],
|
||||
'port' => $input['port'],
|
||||
'source' => $sourceAny ? null : $input['source'],
|
||||
'mask' => $sourceAny ? null : ($input['mask'] ?? null),
|
||||
'status' => FirewallRuleStatus::UPDATING,
|
||||
]);
|
||||
|
||||
dispatch(fn () => $this->applyRule($rule));
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
public function delete(FirewallRule $rule): void
|
||||
{
|
||||
$rule->status = FirewallRuleStatus::DELETING;
|
||||
$rule->save();
|
||||
|
||||
dispatch(fn () => $this->applyRule($rule));
|
||||
}
|
||||
|
||||
protected function applyRule($rule): void
|
||||
{
|
||||
try {
|
||||
/** @var Firewall $handler */
|
||||
$handler = $rule->server->firewall()->handler();
|
||||
$handler->applyRules();
|
||||
} catch (\Exception $e) {
|
||||
$rule->server->firewallRules()
|
||||
->where('status', '!=', FirewallRuleStatus::READY)
|
||||
->update(['status' => FirewallRuleStatus::FAILED]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if ($rule->status === FirewallRuleStatus::DELETING) {
|
||||
$rule->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$rule->status = FirewallRuleStatus::READY;
|
||||
$rule->save();
|
||||
}
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:18',
|
||||
],
|
||||
'type' => [
|
||||
'required',
|
||||
'in:allow,deny',
|
||||
],
|
||||
'protocol' => [
|
||||
'required',
|
||||
'in:tcp,udp',
|
||||
],
|
||||
'port' => [
|
||||
'required',
|
||||
'numeric',
|
||||
'min:1',
|
||||
'max:65535',
|
||||
],
|
||||
'source' => [
|
||||
'nullable',
|
||||
'ip',
|
||||
],
|
||||
'mask' => [
|
||||
'nullable',
|
||||
'numeric',
|
||||
'min:1',
|
||||
'max:32',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -197,26 +197,29 @@ public function createFirewallRules(Server $server): void
|
||||
$server->firewallRules()->createMany([
|
||||
[
|
||||
'type' => 'allow',
|
||||
'protocol' => 'ssh',
|
||||
'name' => 'SSH',
|
||||
'protocol' => 'tcp',
|
||||
'port' => 22,
|
||||
'source' => '0.0.0.0',
|
||||
'mask' => 0,
|
||||
'source' => null,
|
||||
'mask' => null,
|
||||
'status' => FirewallRuleStatus::READY,
|
||||
],
|
||||
[
|
||||
'type' => 'allow',
|
||||
'protocol' => 'http',
|
||||
'name' => 'HTTP',
|
||||
'protocol' => 'tcp',
|
||||
'port' => 80,
|
||||
'source' => '0.0.0.0',
|
||||
'mask' => 0,
|
||||
'source' => null,
|
||||
'mask' => null,
|
||||
'status' => FirewallRuleStatus::READY,
|
||||
],
|
||||
[
|
||||
'type' => 'allow',
|
||||
'protocol' => 'https',
|
||||
'name' => 'HTTPS',
|
||||
'protocol' => 'tcp',
|
||||
'port' => 443,
|
||||
'source' => '0.0.0.0',
|
||||
'mask' => 0,
|
||||
'source' => null,
|
||||
'mask' => null,
|
||||
'status' => FirewallRuleStatus::READY,
|
||||
],
|
||||
]);
|
||||
|
@ -2,19 +2,20 @@
|
||||
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Exceptions\SSHUploadFailed;
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Models\Site;
|
||||
|
||||
class UpdateEnv
|
||||
{
|
||||
/**
|
||||
* @throws SSHUploadFailed
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function update(Site $site, array $input): void
|
||||
{
|
||||
$site->server->os()->editFile(
|
||||
$site->server->os()->editFileAs(
|
||||
$site->path.'/.env',
|
||||
$input['env']
|
||||
$site->user,
|
||||
trim($input['env']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,11 @@ final class FirewallRuleStatus
|
||||
{
|
||||
const CREATING = 'creating';
|
||||
|
||||
const UPDATING = 'updating';
|
||||
|
||||
const READY = 'ready';
|
||||
|
||||
const DELETING = 'deleting';
|
||||
|
||||
const FAILED = 'failed';
|
||||
}
|
||||
|
@ -14,6 +14,9 @@
|
||||
* @method static setLog(?ServerLog $log)
|
||||
* @method static connect()
|
||||
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false, callable $streamCallback = null)
|
||||
* @method static string upload(string $local, string $remote, ?string $owner = null)
|
||||
* @method static string download(string $local, string $remote)
|
||||
* @method static string write(string $path, string $content, string $owner = null)
|
||||
* @method static string assertExecuted(array|string $commands)
|
||||
* @method static string assertExecutedContains(string $command)
|
||||
* @method static string assertFileUploaded(string $toPath, ?string $content = null)
|
||||
|
@ -94,11 +94,7 @@ public function connect(bool $sftp = false): void
|
||||
*/
|
||||
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false, ?callable $streamCallback = null): string
|
||||
{
|
||||
if (! $log) {
|
||||
$log = 'run-command';
|
||||
}
|
||||
|
||||
if (! $this->log) {
|
||||
if (! $this->log && $log) {
|
||||
$this->log = ServerLog::make($this->server, $log);
|
||||
if ($siteId) {
|
||||
$this->log->forSite($siteId);
|
||||
@ -116,13 +112,15 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
||||
|
||||
try {
|
||||
if ($this->asUser) {
|
||||
$command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"';
|
||||
$command = addslashes($command);
|
||||
$command = str_replace('\\\'', '\'', $command);
|
||||
$command = 'sudo su - '.$this->asUser.' -c '.'"'.trim($command).'"';
|
||||
}
|
||||
|
||||
$this->connection->setTimeout(0);
|
||||
if ($stream) {
|
||||
$this->connection->exec($command, function ($output) use ($streamCallback) {
|
||||
$this->log->write($output);
|
||||
$this->log?->write($output);
|
||||
|
||||
return $streamCallback($output);
|
||||
});
|
||||
@ -131,7 +129,7 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
||||
} else {
|
||||
$output = '';
|
||||
$this->connection->exec($command, function ($out) use (&$output) {
|
||||
$this->log->write($out);
|
||||
$this->log?->write($out);
|
||||
$output .= $out;
|
||||
});
|
||||
|
||||
@ -159,7 +157,7 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function upload(string $local, string $remote): void
|
||||
public function upload(string $local, string $remote, ?string $owner = null): void
|
||||
{
|
||||
$this->log = null;
|
||||
|
||||
@ -167,7 +165,17 @@ public function upload(string $local, string $remote): void
|
||||
$this->connect(true);
|
||||
}
|
||||
|
||||
$this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
|
||||
$tmpName = Str::random(10).strtotime('now');
|
||||
$tempPath = home_path($this->user).'/'.$tmpName;
|
||||
|
||||
$this->connection->put($tempPath, $local, SFTP::SOURCE_LOCAL_FILE);
|
||||
|
||||
$this->exec(sprintf('sudo mv %s %s', $tempPath, $remote));
|
||||
if (! $owner) {
|
||||
$owner = $this->user;
|
||||
}
|
||||
$this->exec(sprintf('sudo chown %s:%s %s', $owner, $owner, $remote));
|
||||
$this->exec(sprintf('sudo chmod 644 %s', $remote));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,22 +195,15 @@ public function download(string $local, string $remote): void
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function write(string $remotePath, string $content, bool $sudo = false): void
|
||||
public function write(string $remotePath, string $content, ?string $owner = null): void
|
||||
{
|
||||
$tmpName = Str::random(10).strtotime('now');
|
||||
|
||||
try {
|
||||
/** @var FilesystemAdapter $storageDisk */
|
||||
$storageDisk = Storage::disk('local');
|
||||
|
||||
$storageDisk->put($tmpName, $content);
|
||||
|
||||
if ($sudo) {
|
||||
$this->upload($storageDisk->path($tmpName), sprintf('/home/%s/%s', $this->server->ssh_user, $tmpName));
|
||||
$this->exec(sprintf('sudo mv /home/%s/%s %s', $this->server->ssh_user, $tmpName, $remotePath));
|
||||
} else {
|
||||
$this->upload($storageDisk->path($tmpName), $remotePath);
|
||||
}
|
||||
$this->upload($storageDisk->path($tmpName), $remotePath, $owner);
|
||||
} catch (Throwable $e) {
|
||||
throw new SSHCommandError(
|
||||
message: $e->getMessage()
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Actions\FirewallRule\CreateRule;
|
||||
use App\Actions\FirewallRule\DeleteRule;
|
||||
use App\Actions\FirewallRule\ManageRule;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\FirewallRuleResource;
|
||||
use App\Models\FirewallRule;
|
||||
@ -21,6 +20,7 @@
|
||||
use Spatie\RouteAttributes\Attributes\Middleware;
|
||||
use Spatie\RouteAttributes\Attributes\Post;
|
||||
use Spatie\RouteAttributes\Attributes\Prefix;
|
||||
use Spatie\RouteAttributes\Attributes\Put;
|
||||
|
||||
#[Prefix('api/projects/{project}/servers/{server}/firewall-rules')]
|
||||
#[Middleware(['auth:sanctum', 'can-see-project'])]
|
||||
@ -41,10 +41,11 @@ public function index(Project $project, Server $server): ResourceCollection
|
||||
|
||||
#[Post('/', name: 'api.projects.servers.firewall-rules.create', middleware: 'ability:write')]
|
||||
#[Endpoint(title: 'create', description: 'Create a new firewall rule.')]
|
||||
#[BodyParam(name: 'name', required: true)]
|
||||
#[BodyParam(name: 'type', required: true, enum: ['allow', 'deny'])]
|
||||
#[BodyParam(name: 'protocol', required: true, enum: ['tcp', 'udp'])]
|
||||
#[BodyParam(name: 'port', required: true)]
|
||||
#[BodyParam(name: 'source', required: true)]
|
||||
#[BodyParam(name: 'source', required: false)]
|
||||
#[BodyParam(name: 'mask', description: 'Mask for source IP.', example: '0')]
|
||||
#[ResponseFromApiResource(FirewallRuleResource::class, FirewallRule::class)]
|
||||
public function create(Request $request, Project $project, Server $server): FirewallRuleResource
|
||||
@ -53,9 +54,31 @@ public function create(Request $request, Project $project, Server $server): Fire
|
||||
|
||||
$this->validateRoute($project, $server);
|
||||
|
||||
$this->validate($request, CreateRule::rules());
|
||||
$this->validate($request, ManageRule::rules());
|
||||
|
||||
$firewallRule = app(CreateRule::class)->create($server, $request->all());
|
||||
$firewallRule = app(ManageRule::class)->create($server, $request->all());
|
||||
|
||||
return new FirewallRuleResource($firewallRule);
|
||||
}
|
||||
|
||||
#[Put('{firewallRule}', name: 'api.projects.servers.firewall-rules.edit', middleware: 'ability:write')]
|
||||
#[Endpoint(title: 'edit', description: 'Update an existing firewall rule.')]
|
||||
#[BodyParam(name: 'name', required: true)]
|
||||
#[BodyParam(name: 'type', required: true, enum: ['allow', 'deny'])]
|
||||
#[BodyParam(name: 'protocol', required: true, enum: ['tcp', 'udp'])]
|
||||
#[BodyParam(name: 'port', required: true)]
|
||||
#[BodyParam(name: 'source', required: false)]
|
||||
#[BodyParam(name: 'mask', description: 'Mask for source IP.', example: '0')]
|
||||
#[ResponseFromApiResource(FirewallRuleResource::class, FirewallRule::class)]
|
||||
public function edit(Request $request, Project $project, Server $server, FirewallRule $firewallRule): FirewallRuleResource
|
||||
{
|
||||
$this->authorize('update', [FirewallRule::class, $firewallRule]);
|
||||
|
||||
$this->validateRoute($project, $server);
|
||||
|
||||
$this->validate($request, ManageRule::rules());
|
||||
|
||||
$firewallRule = app(ManageRule::class)->update($firewallRule, $request->all());
|
||||
|
||||
return new FirewallRuleResource($firewallRule);
|
||||
}
|
||||
@ -81,7 +104,7 @@ public function delete(Project $project, Server $server, FirewallRule $firewallR
|
||||
|
||||
$this->validateRoute($project, $server, $firewallRule);
|
||||
|
||||
app(DeleteRule::class)->delete($server, $firewallRule);
|
||||
app(ManageRule::class)->delete($firewallRule);
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
|
@ -29,7 +29,8 @@ public function __invoke(Request $request)
|
||||
->firstOrFail();
|
||||
|
||||
foreach ($gitHook->actions as $action) {
|
||||
if ($action == 'deploy') {
|
||||
$webhookBranch = $gitHook->site->sourceControl->provider()->getWebhookBranch($request->array());
|
||||
if ($action == 'deploy' && $gitHook->site->branch === $webhookBranch) {
|
||||
try {
|
||||
app(Deploy::class)->run($gitHook->site);
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
|
@ -13,6 +13,7 @@ public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'server_id' => $this->server_id,
|
||||
'type' => $this->type,
|
||||
'protocol' => $this->protocol,
|
||||
|
147
app/Models/File.php
Normal file
147
app/Models/File.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $user_id
|
||||
* @property int $server_id
|
||||
* @property string $path
|
||||
* @property string $type
|
||||
* @property string $server_user
|
||||
* @property string $name
|
||||
* @property int $size
|
||||
* @property int $links
|
||||
* @property string $owner
|
||||
* @property string $group
|
||||
* @property string $date
|
||||
* @property string $permissions
|
||||
* @property User $user
|
||||
* @property Server $server
|
||||
*/
|
||||
class File extends AbstractModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'server_id',
|
||||
'server_user',
|
||||
'path',
|
||||
'type',
|
||||
'name',
|
||||
'size',
|
||||
'links',
|
||||
'owner',
|
||||
'group',
|
||||
'date',
|
||||
'permissions',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'user_id' => 'integer',
|
||||
'server_id' => 'integer',
|
||||
'size' => 'integer',
|
||||
'links' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected static function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::deleting(function (File $file) {
|
||||
if ($file->name === '.' || $file->name === '..') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file->server->os()->deleteFile($file->getFilePath(), $file->server_user);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public static function path(User $user, Server $server, string $serverUser): string
|
||||
{
|
||||
$file = self::query()
|
||||
->where('user_id', $user->id)
|
||||
->where('server_id', $server->id)
|
||||
->where('server_user', $serverUser)
|
||||
->first();
|
||||
|
||||
if ($file) {
|
||||
return $file->path;
|
||||
}
|
||||
|
||||
return home_path($serverUser);
|
||||
}
|
||||
|
||||
public static function parse(User $user, Server $server, string $path, string $serverUser, string $listOutput): void
|
||||
{
|
||||
self::query()
|
||||
->where('user_id', $user->id)
|
||||
->where('server_id', $server->id)
|
||||
->delete();
|
||||
|
||||
// Split output by line
|
||||
$lines = explode("\n", trim($listOutput));
|
||||
|
||||
// Skip the first two lines (total count and . & .. directories)
|
||||
array_shift($lines);
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/^([drwx\-]+)\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+([\w\s:\-]+)\s+(.+)$/', $line, $matches)) {
|
||||
$type = match ($matches[1][0]) {
|
||||
'-' => 'file',
|
||||
'd' => 'directory',
|
||||
default => 'unknown',
|
||||
};
|
||||
if ($type === 'unknown') {
|
||||
continue;
|
||||
}
|
||||
if ($matches[7] === '.') {
|
||||
continue;
|
||||
}
|
||||
self::create([
|
||||
'user_id' => $user->id,
|
||||
'server_id' => $server->id,
|
||||
'server_user' => $serverUser,
|
||||
'path' => $path,
|
||||
'type' => $type,
|
||||
'name' => $matches[7],
|
||||
'size' => (int) $matches[5],
|
||||
'links' => (int) $matches[2],
|
||||
'owner' => $matches[3],
|
||||
'group' => $matches[4],
|
||||
'date' => $matches[6],
|
||||
'permissions' => $matches[1],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getFilePath(): string
|
||||
{
|
||||
return $this->path.'/'.$this->name;
|
||||
}
|
||||
|
||||
public function isExtractable(): bool
|
||||
{
|
||||
$extension = pathinfo($this->name, PATHINFO_EXTENSION);
|
||||
|
||||
return in_array($extension, ['zip', 'tar', 'tar.gz', 'bz2', 'tar.bz2']);
|
||||
}
|
||||
}
|
@ -2,11 +2,13 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $server_id
|
||||
* @property string $name
|
||||
* @property string $type
|
||||
* @property string $protocol
|
||||
* @property int $port
|
||||
@ -21,6 +23,7 @@ class FirewallRule extends AbstractModel
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'server_id',
|
||||
'type',
|
||||
'protocol',
|
||||
@ -36,13 +39,19 @@ class FirewallRule extends AbstractModel
|
||||
'port' => 'integer',
|
||||
];
|
||||
|
||||
public function getStatusColor(): string
|
||||
{
|
||||
return match ($this->status) {
|
||||
FirewallRuleStatus::CREATING,
|
||||
FirewallRuleStatus::UPDATING,
|
||||
FirewallRuleStatus::DELETING => 'warning',
|
||||
FirewallRuleStatus::READY => 'success',
|
||||
FirewallRuleStatus::FAILED => 'danger',
|
||||
};
|
||||
}
|
||||
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
public function getRealProtocol(): string
|
||||
{
|
||||
return $this->protocol === 'udp' ? 'udp' : 'tcp';
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
use App\Actions\Server\CheckConnection;
|
||||
use App\Enums\ServerStatus;
|
||||
use App\Enums\ServiceStatus;
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Facades\SSH;
|
||||
use App\ServerTypes\ServerType;
|
||||
use App\SSH\Cron\Cron;
|
||||
@ -22,6 +23,7 @@
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @property int $project_id
|
||||
@ -146,7 +148,7 @@ public static function boot(): void
|
||||
}
|
||||
$server->provider()->delete();
|
||||
DB::commit();
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
@ -465,6 +467,9 @@ public function cron(): Cron
|
||||
return new Cron($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function checkForUpdates(): void
|
||||
{
|
||||
$this->updates = $this->os()->availableUpdates();
|
||||
@ -480,4 +485,15 @@ public function getAvailableUpdatesAttribute(?int $value): int
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function download(string $path, string $disk = 'tmp'): void
|
||||
{
|
||||
$this->ssh()->download(
|
||||
Storage::disk($disk)->path(basename($path)),
|
||||
$path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
use Filament\Panel;
|
||||
use Filament\Support\Assets\Js;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Support\Facades\FilamentAsset;
|
||||
use Filament\Support\Facades\FilamentColor;
|
||||
use Filament\Support\Facades\FilamentView;
|
||||
@ -89,6 +90,7 @@ public function panel(Panel $panel): Panel
|
||||
->colors([
|
||||
'primary' => Color::Indigo,
|
||||
])
|
||||
->maxContentWidth(MaxWidth::ScreenTwoExtraLarge)
|
||||
->viteTheme('resources/css/filament/app/theme.css')
|
||||
->brandLogo(fn () => view('components.brand'))
|
||||
->brandLogoHeight('30px')
|
||||
|
@ -3,14 +3,9 @@
|
||||
namespace App\SSH\OS;
|
||||
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Exceptions\SSHUploadFailed;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerLog;
|
||||
use App\Models\Site;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
class OS
|
||||
{
|
||||
@ -178,24 +173,23 @@ public function reboot(): void
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHUploadFailed
|
||||
* @deprecated use write() instead
|
||||
*
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function editFile(string $path, ?string $content = null): void
|
||||
public function editFileAs(string $path, string $user, ?string $content = null): void
|
||||
{
|
||||
$tmpName = Str::random(10).strtotime('now');
|
||||
try {
|
||||
/** @var FilesystemAdapter $storageDisk */
|
||||
$storageDisk = Storage::disk('local');
|
||||
$storageDisk->put($tmpName, $content);
|
||||
$this->server->ssh()->upload(
|
||||
$storageDisk->path($tmpName),
|
||||
$path
|
||||
);
|
||||
} catch (Throwable) {
|
||||
throw new SSHUploadFailed;
|
||||
} finally {
|
||||
$this->deleteTempFile($tmpName);
|
||||
}
|
||||
$sudo = $user === 'root';
|
||||
$actualUser = $sudo ? $this->server->getSshUser() : $user;
|
||||
|
||||
$this->server->ssh($actualUser)->exec(
|
||||
view('ssh.os.edit-file', [
|
||||
'path' => $path,
|
||||
'content' => $content,
|
||||
'sudo' => $sudo,
|
||||
]),
|
||||
'edit-file'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -203,11 +197,11 @@ public function editFile(string $path, ?string $content = null): void
|
||||
*/
|
||||
public function readFile(string $path): string
|
||||
{
|
||||
return $this->server->ssh()->exec(
|
||||
return trim($this->server->ssh()->exec(
|
||||
view('ssh.os.read-file', [
|
||||
'path' => $path,
|
||||
])
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -263,10 +257,14 @@ public function download(string $url, string $path): string
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function unzip(string $path): string
|
||||
public function extract(string $path, ?string $destination = null, ?string $user = null): void
|
||||
{
|
||||
return $this->server->ssh()->exec(
|
||||
'unzip '.$path
|
||||
$this->server->ssh($user)->exec(
|
||||
view('ssh.os.extract', [
|
||||
'path' => $path,
|
||||
'destination' => $destination,
|
||||
]),
|
||||
'extract'
|
||||
);
|
||||
}
|
||||
|
||||
@ -304,9 +302,9 @@ public function resourceInfo(): array
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function deleteFile(string $path): void
|
||||
public function deleteFile(string $path, ?string $user = null): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->server->ssh($user)->exec(
|
||||
view('ssh.os.delete-file', [
|
||||
'path' => $path,
|
||||
]),
|
||||
@ -314,10 +312,31 @@ public function deleteFile(string $path): void
|
||||
);
|
||||
}
|
||||
|
||||
private function deleteTempFile(string $name): void
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function ls(string $path, ?string $user = null): string
|
||||
{
|
||||
if (Storage::disk('local')->exists($name)) {
|
||||
Storage::disk('local')->delete($name);
|
||||
}
|
||||
return $this->server->ssh($user)->exec('ls -la '.$path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function write(string $path, string $content, ?string $user = null): void
|
||||
{
|
||||
$this->server->ssh()->write(
|
||||
$path,
|
||||
$content,
|
||||
$user
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function mkdir(string $path, ?string $user = null): string
|
||||
{
|
||||
return $this->server->ssh($user)->exec('mkdir -p '.$path);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,5 @@
|
||||
|
||||
interface Firewall
|
||||
{
|
||||
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void;
|
||||
|
||||
public function removeRule(string $type, string $protocol, int $port, string $source, ?string $mask): void;
|
||||
public function applyRules(): void;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\SSH\Services\Firewall;
|
||||
|
||||
use App\Enums\FirewallRuleStatus;
|
||||
use App\Exceptions\SSHError;
|
||||
|
||||
class Ufw extends AbstractFirewall
|
||||
@ -26,34 +27,16 @@ public function uninstall(): void
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void
|
||||
public function applyRules(): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.firewall.ufw.add-rule', [
|
||||
'type' => $type,
|
||||
'protocol' => $protocol,
|
||||
'port' => $port,
|
||||
'source' => $source,
|
||||
'mask' => $mask || $mask === 0 ? '/'.$mask : '',
|
||||
]),
|
||||
'add-firewall-rule'
|
||||
);
|
||||
}
|
||||
$rules = $this->service->server
|
||||
->firewallRules()
|
||||
->where('status', '!=', FirewallRuleStatus::DELETING)
|
||||
->get();
|
||||
|
||||
/**
|
||||
* @throws SSHError
|
||||
*/
|
||||
public function removeRule(string $type, string $protocol, int $port, string $source, ?string $mask): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.firewall.ufw.remove-rule', [
|
||||
'type' => $type,
|
||||
'protocol' => $protocol,
|
||||
'port' => $port,
|
||||
'source' => $source,
|
||||
'mask' => $mask || $mask === 0 ? '/'.$mask : '',
|
||||
]),
|
||||
'remove-firewall-rule'
|
||||
view('ssh.services.firewall.ufw.apply-rules', compact('rules')),
|
||||
'apply-rules'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ public function createFpmPool(string $user, string $version, $site_id): void
|
||||
'user' => $user,
|
||||
'version' => $version,
|
||||
]),
|
||||
true
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->systemd()->restart($this->service->unit);
|
||||
|
@ -55,7 +55,7 @@ public function create(
|
||||
'numprocs' => (string) $numprocs,
|
||||
'logFile' => $logFile,
|
||||
]),
|
||||
true
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
|
@ -26,7 +26,7 @@ public function install(): void
|
||||
view('ssh.services.webserver.nginx.nginx', [
|
||||
'user' => $this->service->server->getSshUser(),
|
||||
]),
|
||||
true
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->systemd()->restart('nginx');
|
||||
@ -83,7 +83,7 @@ public function createVHost(Site $site): void
|
||||
view('ssh.services.webserver.nginx.vhost', [
|
||||
'site' => $site,
|
||||
]),
|
||||
true
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
@ -108,7 +108,7 @@ public function updateVHost(Site $site, ?string $vhost = null): void
|
||||
$vhost ?? view('ssh.services.webserver.nginx.vhost', [
|
||||
'site' => $site,
|
||||
]),
|
||||
true
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->systemd()->restart('nginx');
|
||||
@ -199,11 +199,13 @@ public function setupSSL(Ssl $ssl): void
|
||||
*/
|
||||
public function removeSSL(Ssl $ssl): void
|
||||
{
|
||||
$this->service->server->ssh()->exec(
|
||||
'sudo rm -rf '.dirname($ssl->certificate_path).'*',
|
||||
'remove-ssl',
|
||||
$ssl->site_id
|
||||
);
|
||||
if ($ssl->certificate_path) {
|
||||
$this->service->server->ssh()->exec(
|
||||
'sudo rm -rf '.dirname($ssl->certificate_path),
|
||||
'remove-ssl',
|
||||
$ssl->site_id
|
||||
);
|
||||
}
|
||||
|
||||
$this->updateVHost($ssl->site);
|
||||
}
|
||||
|
@ -70,4 +70,9 @@ protected function handleResponseErrors(Response $res, string $repo): void
|
||||
throw new RepositoryPermissionDenied($repo);
|
||||
}
|
||||
}
|
||||
|
||||
public function getWebhookBranch(array $payload): string
|
||||
{
|
||||
return str($payload['ref'] ?? '')->after('refs/heads/')->toString();
|
||||
}
|
||||
}
|
||||
|
@ -183,4 +183,9 @@ private function getAuthenticationHeaders(): array
|
||||
'Authorization' => 'Basic '.$basicAuth,
|
||||
];
|
||||
}
|
||||
|
||||
public function getWebhookBranch(array $payload): string
|
||||
{
|
||||
return data_get($payload, 'push.changes.0.new.name', 'default-branch');
|
||||
}
|
||||
}
|
||||
|
@ -176,4 +176,9 @@ public function getApiUrl(): string
|
||||
|
||||
return $host.$this->apiVersion;
|
||||
}
|
||||
|
||||
public function getWebhookBranch(array $payload): string
|
||||
{
|
||||
return $payload['ref'] ?? '';
|
||||
}
|
||||
}
|
||||
|
@ -36,4 +36,6 @@ public function getLastCommit(string $repo, string $branch): ?array;
|
||||
* @throws FailedToDeployGitKey
|
||||
*/
|
||||
public function deployKey(string $title, string $repo, string $key): void;
|
||||
|
||||
public function getWebhookBranch(array $payload): string;
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function upload(string $local, string $remote): void
|
||||
public function upload(string $local, string $remote, ?string $owner = null): void
|
||||
{
|
||||
$this->uploadedLocalPath = $local;
|
||||
$this->uploadedRemotePath = $remote;
|
||||
|
@ -178,3 +178,31 @@ function get_from_route(string $modelName, string $routeKey): mixed
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function absolute_path(string $path): string
|
||||
{
|
||||
$parts = explode('/', $path);
|
||||
$absoluteParts = [];
|
||||
|
||||
foreach ($parts as $part) {
|
||||
if ($part === '' || $part === '.') {
|
||||
continue; // Skip empty and current directory parts
|
||||
}
|
||||
if ($part === '..') {
|
||||
array_pop($absoluteParts); // Move up one directory
|
||||
} else {
|
||||
$absoluteParts[] = $part; // Add valid directory parts
|
||||
}
|
||||
}
|
||||
|
||||
return '/'.implode('/', $absoluteParts);
|
||||
}
|
||||
|
||||
function home_path(string $user): string
|
||||
{
|
||||
if ($user === 'root') {
|
||||
return '/root';
|
||||
}
|
||||
|
||||
return '/home/'.$user;
|
||||
}
|
||||
|
26
app/Web/Pages/Servers/FileManager/Index.php
Normal file
26
app/Web/Pages/Servers/FileManager/Index.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\FileManager;
|
||||
|
||||
use App\Web\Pages\Servers\Page;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
protected static ?string $slug = 'servers/{server}/file-manager';
|
||||
|
||||
protected static ?string $title = 'File Manager';
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->authorize('manage', $this->server);
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\FilesList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
}
|
372
app/Web/Pages/Servers/FileManager/Widgets/FilesList.php
Normal file
372
app/Web/Pages/Servers/FileManager/Widgets/FilesList.php
Normal file
@ -0,0 +1,372 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\FileManager\Widgets;
|
||||
|
||||
use App\Actions\FileManager\FetchFiles;
|
||||
use App\Exceptions\SSHError;
|
||||
use App\Models\File;
|
||||
use App\Models\Server;
|
||||
use App\Web\Fields\CodeEditorField;
|
||||
use App\Web\Pages\Servers\FileManager\Index;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Support\Enums\ActionSize;
|
||||
use Filament\Support\Enums\IconPosition;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\ActionGroup;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class FilesList extends Widget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public string $serverUser;
|
||||
|
||||
public string $path;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->serverUser = $this->server->ssh_user;
|
||||
$this->path = home_path($this->serverUser);
|
||||
if (request()->has('path') && request()->has('user')) {
|
||||
$this->path = request('path');
|
||||
$this->serverUser = request('user');
|
||||
}
|
||||
$this->refresh();
|
||||
}
|
||||
|
||||
protected function getTableHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->homeAction(),
|
||||
$this->userAction(),
|
||||
ActionGroup::make([
|
||||
$this->refreshAction(),
|
||||
$this->newFileAction(),
|
||||
$this->newDirectoryAction(),
|
||||
$this->uploadAction(),
|
||||
])
|
||||
->tooltip('Toolbar')
|
||||
->icon('heroicon-o-ellipsis-vertical')
|
||||
->color('gray')
|
||||
->size(ActionSize::Large)
|
||||
->iconPosition(IconPosition::After)
|
||||
->dropdownPlacement('bottom-end'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return File::query()
|
||||
->where('user_id', auth()->id())
|
||||
->where('server_id', $this->server->id);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query($this->getTableQuery())
|
||||
->headerActions($this->getTableHeaderActions())
|
||||
->heading(str($this->path)->substr(-50)->start(str($this->path)->length() > 50 ? '...' : ''))
|
||||
->columns([
|
||||
IconColumn::make('type')
|
||||
->sortable()
|
||||
->icon(fn (File $file) => $this->getIcon($file)),
|
||||
TextColumn::make('name')
|
||||
->sortable(),
|
||||
TextColumn::make('size')
|
||||
->sortable(),
|
||||
TextColumn::make('owner')
|
||||
->sortable(),
|
||||
TextColumn::make('group')
|
||||
->sortable(),
|
||||
TextColumn::make('date')
|
||||
->sortable(),
|
||||
TextColumn::make('permissions')
|
||||
->sortable(),
|
||||
])
|
||||
->recordUrl(function (File $file) {
|
||||
if ($file->type === 'directory') {
|
||||
return Index::getUrl([
|
||||
'server' => $this->server->id,
|
||||
'user' => $file->server_user,
|
||||
'path' => absolute_path($file->path.'/'.$file->name),
|
||||
]);
|
||||
}
|
||||
|
||||
return '';
|
||||
})
|
||||
->defaultSort('type')
|
||||
->actions([
|
||||
$this->extractAction(),
|
||||
$this->downloadAction(),
|
||||
$this->editAction(),
|
||||
$this->deleteAction(),
|
||||
])
|
||||
->checkIfRecordIsSelectableUsing(
|
||||
fn (File $file): bool => $file->name !== '..',
|
||||
)
|
||||
->bulkActions([
|
||||
DeleteBulkAction::make()
|
||||
->requiresConfirmation(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function changeUser(string $user): void
|
||||
{
|
||||
$this->redirect(
|
||||
Index::getUrl([
|
||||
'server' => $this->server->id,
|
||||
'user' => $user,
|
||||
'path' => home_path($user),
|
||||
]),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
try {
|
||||
app(FetchFiles::class)->fetch(
|
||||
auth()->user(),
|
||||
$this->server,
|
||||
[
|
||||
'user' => $this->serverUser,
|
||||
'path' => $this->path,
|
||||
]
|
||||
);
|
||||
} catch (SSHError) {
|
||||
abort(404);
|
||||
}
|
||||
$this->dispatch('$refresh');
|
||||
}
|
||||
|
||||
protected function getIcon(File $file): string
|
||||
{
|
||||
if ($file->type === 'directory') {
|
||||
return 'heroicon-o-folder';
|
||||
}
|
||||
|
||||
if (str($file->name)->endsWith('.blade.php')) {
|
||||
return 'laravel';
|
||||
}
|
||||
|
||||
if (str($file->name)->endsWith('.php')) {
|
||||
return 'php';
|
||||
}
|
||||
|
||||
return 'heroicon-o-document-text';
|
||||
}
|
||||
|
||||
protected function homeAction(): Action
|
||||
{
|
||||
return Action::make('home')
|
||||
->label('Home')
|
||||
->size(ActionSize::Small)
|
||||
->icon('heroicon-o-home')
|
||||
->action(function () {
|
||||
$this->path = home_path($this->serverUser);
|
||||
$this->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
protected function userAction(): ActionGroup
|
||||
{
|
||||
$users = [];
|
||||
foreach ($this->server->getSshUsers() as $user) {
|
||||
$users[] = Action::make('user-'.$user)
|
||||
->action(fn () => $this->changeUser($user))
|
||||
->label($user);
|
||||
}
|
||||
|
||||
return ActionGroup::make($users)
|
||||
->tooltip('Change user')
|
||||
->label($this->serverUser)
|
||||
->button()
|
||||
->size(ActionSize::Small)
|
||||
->color('gray')
|
||||
->icon('heroicon-o-chevron-up-down')
|
||||
->iconPosition(IconPosition::After)
|
||||
->dropdownPlacement('bottom-end');
|
||||
}
|
||||
|
||||
protected function refreshAction(): Action
|
||||
{
|
||||
return Action::make('refresh')
|
||||
->label('Refresh')
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->action(fn () => $this->refresh());
|
||||
}
|
||||
|
||||
protected function newFileAction(): Action
|
||||
{
|
||||
return Action::make('new-file')
|
||||
->label('New File')
|
||||
->icon('heroicon-o-document-text')
|
||||
->action(function (array $data) {
|
||||
run_action($this, function () use ($data) {
|
||||
$this->server->os()->write(
|
||||
$this->path.'/'.$data['name'],
|
||||
str_replace("\r\n", "\n", $data['content']),
|
||||
$this->serverUser
|
||||
);
|
||||
$this->refresh();
|
||||
});
|
||||
})
|
||||
->form(function () {
|
||||
return [
|
||||
TextInput::make('name')
|
||||
->placeholder('file-name.txt'),
|
||||
CodeEditorField::make('content'),
|
||||
];
|
||||
})
|
||||
->modalSubmitActionLabel('Create')
|
||||
->modalHeading('New File')
|
||||
->modalWidth('4xl');
|
||||
}
|
||||
|
||||
protected function newDirectoryAction(): Action
|
||||
{
|
||||
return Action::make('new-directory')
|
||||
->label('New Directory')
|
||||
->icon('heroicon-o-folder')
|
||||
->action(function (array $data) {
|
||||
run_action($this, function () use ($data) {
|
||||
$this->server->os()->mkdir(
|
||||
$this->path.'/'.$data['name'],
|
||||
$this->serverUser
|
||||
);
|
||||
$this->refresh();
|
||||
});
|
||||
})
|
||||
->form(function () {
|
||||
return [
|
||||
TextInput::make('name')
|
||||
->placeholder('directory name'),
|
||||
];
|
||||
})
|
||||
->modalSubmitActionLabel('Create')
|
||||
->modalHeading('New Directory')
|
||||
->modalWidth('lg');
|
||||
}
|
||||
|
||||
protected function uploadAction(): Action
|
||||
{
|
||||
return Action::make('upload')
|
||||
->label('Upload File')
|
||||
->icon('heroicon-o-arrow-up-on-square')
|
||||
->action(function (array $data) {
|
||||
//
|
||||
})
|
||||
->after(function (array $data) {
|
||||
run_action($this, function () use ($data) {
|
||||
foreach ($data['file'] as $file) {
|
||||
$this->server->ssh()->upload(
|
||||
Storage::disk('tmp')->path($file),
|
||||
$this->path.'/'.$file,
|
||||
$this->serverUser
|
||||
);
|
||||
}
|
||||
$this->refresh();
|
||||
});
|
||||
})
|
||||
->form(function () {
|
||||
return [
|
||||
FileUpload::make('file')
|
||||
->disk('tmp')
|
||||
->multiple()
|
||||
->preserveFilenames(),
|
||||
];
|
||||
})
|
||||
->modalSubmitActionLabel('Upload to Server')
|
||||
->modalHeading('Upload File')
|
||||
->modalWidth('xl');
|
||||
}
|
||||
|
||||
protected function extractAction(): Action
|
||||
{
|
||||
return Action::make('extract')
|
||||
->tooltip('Extract')
|
||||
->icon('heroicon-o-archive-box')
|
||||
->hiddenLabel()
|
||||
->visible(fn (File $file) => $file->isExtractable())
|
||||
->action(function (File $file) {
|
||||
$file->server->os()->extract($file->getFilePath(), $file->path, $file->server_user);
|
||||
$this->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
protected function downloadAction(): Action
|
||||
{
|
||||
return Action::make('download')
|
||||
->tooltip('Download')
|
||||
->icon('heroicon-o-arrow-down-tray')
|
||||
->hiddenLabel()
|
||||
->visible(fn (File $file) => $file->type === 'file')
|
||||
->action(function (File $file) {
|
||||
$file->server->ssh($file->server_user)->download(
|
||||
Storage::disk('tmp')->path($file->name),
|
||||
$file->getFilePath()
|
||||
);
|
||||
|
||||
return Storage::disk('tmp')->download($file->name);
|
||||
});
|
||||
}
|
||||
|
||||
protected function editAction(): Action
|
||||
{
|
||||
return Action::make('edit')
|
||||
->tooltip('Edit')
|
||||
->icon('heroicon-o-pencil')
|
||||
->hiddenLabel()
|
||||
->visible(fn (File $file) => $file->type === 'file')
|
||||
->action(function (File $file, array $data) {
|
||||
$file->server->os()->write(
|
||||
$file->getFilePath(),
|
||||
str_replace("\r\n", "\n", $data['content']),
|
||||
$file->server_user
|
||||
);
|
||||
$this->refresh();
|
||||
})
|
||||
->form(function (File $file) {
|
||||
return [
|
||||
CodeEditorField::make('content')
|
||||
->formatStateUsing(function () use ($file) {
|
||||
$file->server->ssh($file->server_user)->download(
|
||||
Storage::disk('tmp')->path($file->name),
|
||||
$file->getFilePath()
|
||||
);
|
||||
|
||||
return Storage::disk('tmp')->get(basename($file->getFilePath()));
|
||||
}),
|
||||
];
|
||||
})
|
||||
->modalSubmitActionLabel('Save')
|
||||
->modalHeading('Edit')
|
||||
->modalWidth('4xl');
|
||||
}
|
||||
|
||||
protected function deleteAction(): Action
|
||||
{
|
||||
return Action::make('delete')
|
||||
->tooltip('Delete')
|
||||
->icon('heroicon-o-trash')
|
||||
->color('danger')
|
||||
->hiddenLabel()
|
||||
->requiresConfirmation()
|
||||
->visible(fn (File $file) => $file->name !== '..')
|
||||
->action(function (File $file) {
|
||||
run_action($this, function () use ($file) {
|
||||
$file->delete();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -2,14 +2,17 @@
|
||||
|
||||
namespace App\Web\Pages\Servers\Firewall;
|
||||
|
||||
use App\Actions\FirewallRule\CreateRule;
|
||||
use App\Actions\FirewallRule\ManageRule;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Web\Pages\Servers\Page;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
@ -31,6 +34,64 @@ public function getWidgets(): array
|
||||
];
|
||||
}
|
||||
|
||||
public static function getFirewallForm(?FirewallRule $record = null): array
|
||||
{
|
||||
return [
|
||||
TextInput::make('name')
|
||||
->label('Purpose')
|
||||
->default($record->name ?? null)
|
||||
->rules(ManageRule::rules()['name']),
|
||||
Select::make('type')
|
||||
->label('Type')
|
||||
->default($record->type ?? 'allow')
|
||||
->options([
|
||||
'allow' => 'Allow',
|
||||
'deny' => 'Deny',
|
||||
])
|
||||
->rules(ManageRule::rules()['type']),
|
||||
Select::make('protocol')
|
||||
->label('Protocol')
|
||||
->default($record->protocol ?? 'tcp')
|
||||
->options([
|
||||
'tcp' => 'TCP',
|
||||
'udp' => 'UDP',
|
||||
])
|
||||
->rules(ManageRule::rules()['protocol']),
|
||||
TextInput::make('port')
|
||||
->label('Port')
|
||||
->default($record->port ?? null)
|
||||
->rules(['required', 'integer']),
|
||||
Checkbox::make('source_any')
|
||||
->label('Any Source')
|
||||
->default(($record->source ?? null) == null)
|
||||
->rules(['boolean'])
|
||||
->helperText('Allow connections from any source, regardless of their IP address or subnet mask.')
|
||||
->live(),
|
||||
TextInput::make('source')
|
||||
->hidden(fn (Get $get) => $get('source_any') == true)
|
||||
->label('Source')
|
||||
->helperText('The IP address of the source of the connection.')
|
||||
->rules(ManageRule::rules()['source'])
|
||||
->default($record->source ?? null)
|
||||
->suffixAction(
|
||||
\Filament\Forms\Components\Actions\Action::make('get_ip')
|
||||
->icon('heroicon-o-globe-alt')
|
||||
->color('primary')
|
||||
->tooltip('Use My IP')
|
||||
->action(function ($set) {
|
||||
$ip = Request::ip();
|
||||
$set('source', $ip);
|
||||
})
|
||||
),
|
||||
TextInput::make('mask')
|
||||
->hidden(fn (Get $get) => $get('source_any') == true)
|
||||
->label('Mask')
|
||||
->default($record->mask ?? null)
|
||||
->helperText('The subnet mask of the source of the connection. Leave blank for a single IP address.')
|
||||
->rules(ManageRule::rules()['mask']),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
@ -45,37 +106,19 @@ protected function getHeaderActions(): array
|
||||
->label('Create a Rule')
|
||||
->icon('heroicon-o-plus')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->form([
|
||||
Select::make('type')
|
||||
->native(false)
|
||||
->options([
|
||||
'allow' => 'Allow',
|
||||
'deny' => 'Deny',
|
||||
])
|
||||
->rules(CreateRule::rules()['type']),
|
||||
Select::make('protocol')
|
||||
->native(false)
|
||||
->options([
|
||||
'tcp' => 'TCP',
|
||||
'udp' => 'UDP',
|
||||
])
|
||||
->rules(CreateRule::rules()['protocol']),
|
||||
TextInput::make('port')
|
||||
->rules(CreateRule::rules()['port']),
|
||||
TextInput::make('source')
|
||||
->rules(CreateRule::rules()['source']),
|
||||
TextInput::make('mask')
|
||||
->rules(CreateRule::rules()['mask']),
|
||||
])
|
||||
->modalHeading('Create Firewall Rule')
|
||||
->modalDescription('Add a new rule to the firewall')
|
||||
->modalSubmitActionLabel('Create')
|
||||
->form(self::getFirewallForm())
|
||||
->action(function (array $data) {
|
||||
run_action($this, function () use ($data) {
|
||||
app(CreateRule::class)->create($this->server, $data);
|
||||
app(ManageRule::class)->create($this->server, $data);
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Firewall rule created!')
|
||||
->title('Applying Firewall Rule')
|
||||
->send();
|
||||
});
|
||||
}),
|
||||
|
@ -2,15 +2,18 @@
|
||||
|
||||
namespace App\Web\Pages\Servers\Firewall\Widgets;
|
||||
|
||||
use App\Actions\FirewallRule\DeleteRule;
|
||||
use App\Actions\FirewallRule\ManageRule;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use App\Web\Pages\Servers\Firewall\Index;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RulesList extends Widget
|
||||
{
|
||||
@ -26,19 +29,40 @@ protected function getTableQuery(): Builder
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable()
|
||||
->label('Purpose'),
|
||||
TextColumn::make('type')
|
||||
->sortable()
|
||||
->extraAttributes(['class' => 'uppercase'])
|
||||
->color(fn (FirewallRule $record) => $record->type === 'allow' ? 'green' : 'red'),
|
||||
->badge()
|
||||
->color(fn ($state) => $state === 'allow' ? 'success' : 'warning')
|
||||
->label('Type')
|
||||
->formatStateUsing(fn ($state) => Str::upper($state)),
|
||||
TextColumn::make('id')
|
||||
->sortable()
|
||||
->label('Source')
|
||||
->formatStateUsing(function (FirewallRule $record) {
|
||||
$source = $record->source == null ? 'any' : $record->source;
|
||||
if ($source !== 'any' && $record->mask !== null) {
|
||||
$source .= '/'.$record->mask;
|
||||
}
|
||||
|
||||
return $source;
|
||||
}),
|
||||
TextColumn::make('protocol')
|
||||
->sortable()
|
||||
->extraAttributes(['class' => 'uppercase']),
|
||||
->badge()
|
||||
->color('primary')
|
||||
->label('Protocol')
|
||||
->formatStateUsing(fn ($state) => Str::upper($state)),
|
||||
TextColumn::make('port')
|
||||
->sortable(),
|
||||
TextColumn::make('source')
|
||||
->sortable(),
|
||||
TextColumn::make('mask')
|
||||
->sortable(),
|
||||
->sortable()
|
||||
->label('Port'),
|
||||
TextColumn::make('status')
|
||||
->label('Status')
|
||||
->badge()
|
||||
->color(fn (FirewallRule $record) => $record->getStatusColor()),
|
||||
];
|
||||
}
|
||||
|
||||
@ -49,6 +73,28 @@ public function table(Table $table): Table
|
||||
->query($this->getTableQuery())
|
||||
->columns($this->getTableColumns())
|
||||
->actions([
|
||||
Action::make('edit')
|
||||
->icon('heroicon-o-pencil')
|
||||
->tooltip('Edit')
|
||||
->hiddenLabel()
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->modalHeading('Edit Firewall Rule')
|
||||
->modalDescription('Edit the associated servers firewall rule.')
|
||||
->modalSubmitActionLabel('Update')
|
||||
->authorize(fn (FirewallRule $record) => auth()->user()->can('update', $record))
|
||||
->form(fn ($record) => Index::getFirewallForm($record))
|
||||
->action(function (FirewallRule $record, array $data) {
|
||||
run_action($this, function () use ($record, $data) {
|
||||
app(ManageRule::class)->update($record, $data);
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Applying Firewall Rule')
|
||||
->send();
|
||||
});
|
||||
}),
|
||||
Action::make('delete')
|
||||
->icon('heroicon-o-trash')
|
||||
->tooltip('Delete')
|
||||
@ -58,7 +104,7 @@ public function table(Table $table): Table
|
||||
->authorize(fn (FirewallRule $record) => auth()->user()->can('delete', $record))
|
||||
->action(function (FirewallRule $record) {
|
||||
try {
|
||||
app(DeleteRule::class)->delete($this->server, $record);
|
||||
app(ManageRule::class)->delete($record);
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
|
@ -15,6 +15,7 @@
|
||||
use App\Web\Pages\Servers\Console\Index as ConsoleIndex;
|
||||
use App\Web\Pages\Servers\CronJobs\Index as CronJobsIndex;
|
||||
use App\Web\Pages\Servers\Databases\Index as DatabasesIndex;
|
||||
use App\Web\Pages\Servers\FileManager\Index as FileManagerIndex;
|
||||
use App\Web\Pages\Servers\Firewall\Index as FirewallIndex;
|
||||
use App\Web\Pages\Servers\Logs\Index as LogsIndex;
|
||||
use App\Web\Pages\Servers\Metrics\Index as MetricsIndex;
|
||||
@ -59,6 +60,13 @@ public function getSubNavigation(): array
|
||||
->url(DatabasesIndex::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
|
||||
if (auth()->user()->can('manage', $this->server)) {
|
||||
$items[] = NavigationItem::make(FileManagerIndex::getNavigationLabel())
|
||||
->icon('heroicon-o-folder')
|
||||
->isActiveWhen(fn () => request()->routeIs(FileManagerIndex::getRouteName().'*'))
|
||||
->url(FileManagerIndex::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
|
||||
if (auth()->user()->can('viewAny', [Service::class, $this->server])) {
|
||||
$items[] = NavigationItem::make(PHPIndex::getNavigationLabel())
|
||||
->icon('icon-php-alt')
|
||||
|
@ -211,7 +211,7 @@
|
||||
// 'ExampleClass' => App\Example\ExampleClass::class,
|
||||
])->toArray(),
|
||||
|
||||
'version' => '2.2.1',
|
||||
'version' => '2.3.0',
|
||||
|
||||
'demo' => env('APP_DEMO', false),
|
||||
];
|
||||
|
@ -485,14 +485,6 @@
|
||||
'post_max_size' => 'M',
|
||||
],
|
||||
|
||||
/*
|
||||
* firewall
|
||||
*/
|
||||
'firewall_protocols_port' => [
|
||||
'tcp' => '',
|
||||
'udp' => '',
|
||||
],
|
||||
|
||||
/*
|
||||
* Disable these IPs for servers
|
||||
*/
|
||||
|
@ -62,7 +62,7 @@
|
||||
'root' => storage_path('app/key-pairs'),
|
||||
],
|
||||
|
||||
'backups' => [
|
||||
'tmp' => [
|
||||
'driver' => 'local',
|
||||
'root' => sys_get_temp_dir(),
|
||||
],
|
||||
|
32
database/factories/FileFactory.php
Normal file
32
database/factories/FileFactory.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\File;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class FileFactory extends Factory
|
||||
{
|
||||
protected $model = File::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => $this->faker->randomNumber(), //
|
||||
'server_id' => $this->faker->randomNumber(),
|
||||
'server_user' => $this->faker->word(),
|
||||
'path' => $this->faker->word(),
|
||||
'type' => 'file',
|
||||
'name' => $this->faker->name(),
|
||||
'size' => $this->faker->randomNumber(),
|
||||
'links' => $this->faker->randomNumber(),
|
||||
'owner' => $this->faker->word(),
|
||||
'group' => $this->faker->word(),
|
||||
'date' => $this->faker->word(),
|
||||
'permissions' => $this->faker->word(),
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
];
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ class FirewallRuleFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->word,
|
||||
'type' => 'allow',
|
||||
'protocol' => 'tcp',
|
||||
'port' => $this->faker->numberBetween(1, 65535),
|
||||
|
33
database/migrations/2025_02_02_124012_create_files_table.php
Normal file
33
database/migrations/2025_02_02_124012_create_files_table.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('files', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->unsignedBigInteger('server_id');
|
||||
$table->string('server_user');
|
||||
$table->string('path');
|
||||
$table->string('type');
|
||||
$table->string('name');
|
||||
$table->unsignedBigInteger('size');
|
||||
$table->unsignedBigInteger('links');
|
||||
$table->string('owner');
|
||||
$table->string('group');
|
||||
$table->string('date');
|
||||
$table->string('permissions');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('files');
|
||||
}
|
||||
};
|
@ -0,0 +1,39 @@
|
||||
<?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('firewall_rules', function (Blueprint $table) {
|
||||
$table->string('name')->default('Undefined')->after('id');
|
||||
$table->ipAddress('source')->default(null)->nullable()->change();
|
||||
});
|
||||
|
||||
DB::statement("UPDATE firewall_rules SET name = UPPER(protocol) WHERE protocol IN ('ssh', 'http', 'https')");
|
||||
DB::statement("UPDATE firewall_rules SET protocol = 'tcp' WHERE protocol IN ('ssh', 'http', 'https')");
|
||||
DB::statement("UPDATE firewall_rules SET source = null WHERE source = '0.0.0.0'");
|
||||
DB::statement("UPDATE firewall_rules SET mask = null WHERE mask = '0'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement("UPDATE firewall_rules SET protocol = LOWER(name) WHERE protocol = 'tcp' AND LOWER(name) IN ('ssh', 'http', 'https')");
|
||||
DB::statement("UPDATE firewall_rules SET source = '0.0.0.0' WHERE source is null");
|
||||
DB::statement("UPDATE firewall_rules SET mask = '0' WHERE mask is null");
|
||||
|
||||
Schema::table('firewall_rules', function (Blueprint $table) {
|
||||
$table->dropColumn('name');
|
||||
$table->ipAddress('source')->default('0.0.0.0')->change();
|
||||
});
|
||||
}
|
||||
};
|
@ -1,26 +1,22 @@
|
||||
FROM ubuntu:22.04
|
||||
FROM ubuntu:24.04
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
# upgrade
|
||||
RUN apt-get clean && apt-get update && apt-get update && apt-get upgrade -y && apt-get autoremove -y
|
||||
RUN apt-get update && apt-get upgrade -y && apt-get autoremove -y
|
||||
|
||||
# requirements
|
||||
RUN apt-get install -y software-properties-common curl zip unzip gcc
|
||||
|
||||
# nginx
|
||||
RUN apt-get install -y nginx
|
||||
|
||||
# php
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y cron gnupg gosu curl ca-certificates zip unzip supervisor libcap2-bin libpng-dev \
|
||||
python2 dnsutils librsvg2-bin fswatch wget openssh-client \
|
||||
RUN apt-get install -y software-properties-common curl zip unzip gcc nginx \
|
||||
cron gnupg gosu curl ca-certificates zip unzip supervisor libcap2-bin libpng-dev \
|
||||
dnsutils librsvg2-bin fswatch wget openssh-client \
|
||||
&& add-apt-repository ppa:ondrej/php -y \
|
||||
&& apt-get update \
|
||||
&& 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-intl
|
||||
|
||||
# php
|
||||
COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
|
||||
|
||||
# composer
|
||||
|
21
docker/publish.sh
Normal file
21
docker/publish.sh
Normal file
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
TAG=$1
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "No tag provided"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf /tmp/vito
|
||||
|
||||
git clone git@github.com:vitodeploy/vito.git /tmp/vito
|
||||
|
||||
cd /tmp/vito || exit
|
||||
|
||||
docker buildx build . \
|
||||
-f docker/Dockerfile \
|
||||
-t vitodeploy/vito:"$TAG" \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--no-cache \
|
||||
--push
|
646
package-lock.json
generated
646
package-lock.json
generated
@ -11,7 +11,7 @@
|
||||
"apexcharts": "^3.44.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"brace": "^0.11.1",
|
||||
"laravel-vite-plugin": "^0.7.2",
|
||||
"laravel-vite-plugin": "^1.2.0",
|
||||
"postcss": "^8.4.45",
|
||||
"postcss-nesting": "^13.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
@ -19,7 +19,7 @@
|
||||
"prettier-plugin-sh": "^0.14.0",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"vite": "^4.5.5"
|
||||
"vite": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
@ -35,10 +35,27 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
|
||||
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
||||
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
|
||||
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -49,13 +66,13 @@
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -66,13 +83,13 @@
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -83,13 +100,13 @@
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -100,13 +117,13 @@
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -117,13 +134,13 @@
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -134,13 +151,13 @@
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -151,13 +168,13 @@
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
|
||||
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
|
||||
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -168,13 +185,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -185,13 +202,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
|
||||
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
|
||||
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -202,13 +219,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
|
||||
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
|
||||
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@ -219,13 +236,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
|
||||
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
|
||||
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@ -236,13 +253,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
|
||||
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
|
||||
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@ -253,13 +270,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
|
||||
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
|
||||
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@ -270,13 +287,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
|
||||
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
|
||||
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@ -287,13 +304,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -304,13 +321,30 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -321,13 +355,30 @@
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -338,13 +389,13 @@
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -355,13 +406,13 @@
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
|
||||
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -372,13 +423,13 @@
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
|
||||
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
|
||||
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -389,13 +440,13 @@
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
|
||||
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -406,7 +457,7 @@
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
@ -529,6 +580,272 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz",
|
||||
"integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz",
|
||||
"integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz",
|
||||
"integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz",
|
||||
"integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz",
|
||||
"integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz",
|
||||
"integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz",
|
||||
"integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz",
|
||||
"integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz",
|
||||
"integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz",
|
||||
"integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz",
|
||||
"integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz",
|
||||
"integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz",
|
||||
"integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@tailwindcss/forms": {
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
|
||||
@ -558,6 +875,13 @@
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
|
||||
@ -946,9 +1270,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
||||
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
|
||||
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@ -956,31 +1280,34 @@
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/android-arm": "0.18.20",
|
||||
"@esbuild/android-arm64": "0.18.20",
|
||||
"@esbuild/android-x64": "0.18.20",
|
||||
"@esbuild/darwin-arm64": "0.18.20",
|
||||
"@esbuild/darwin-x64": "0.18.20",
|
||||
"@esbuild/freebsd-arm64": "0.18.20",
|
||||
"@esbuild/freebsd-x64": "0.18.20",
|
||||
"@esbuild/linux-arm": "0.18.20",
|
||||
"@esbuild/linux-arm64": "0.18.20",
|
||||
"@esbuild/linux-ia32": "0.18.20",
|
||||
"@esbuild/linux-loong64": "0.18.20",
|
||||
"@esbuild/linux-mips64el": "0.18.20",
|
||||
"@esbuild/linux-ppc64": "0.18.20",
|
||||
"@esbuild/linux-riscv64": "0.18.20",
|
||||
"@esbuild/linux-s390x": "0.18.20",
|
||||
"@esbuild/linux-x64": "0.18.20",
|
||||
"@esbuild/netbsd-x64": "0.18.20",
|
||||
"@esbuild/openbsd-x64": "0.18.20",
|
||||
"@esbuild/sunos-x64": "0.18.20",
|
||||
"@esbuild/win32-arm64": "0.18.20",
|
||||
"@esbuild/win32-ia32": "0.18.20",
|
||||
"@esbuild/win32-x64": "0.18.20"
|
||||
"@esbuild/aix-ppc64": "0.25.0",
|
||||
"@esbuild/android-arm": "0.25.0",
|
||||
"@esbuild/android-arm64": "0.25.0",
|
||||
"@esbuild/android-x64": "0.25.0",
|
||||
"@esbuild/darwin-arm64": "0.25.0",
|
||||
"@esbuild/darwin-x64": "0.25.0",
|
||||
"@esbuild/freebsd-arm64": "0.25.0",
|
||||
"@esbuild/freebsd-x64": "0.25.0",
|
||||
"@esbuild/linux-arm": "0.25.0",
|
||||
"@esbuild/linux-arm64": "0.25.0",
|
||||
"@esbuild/linux-ia32": "0.25.0",
|
||||
"@esbuild/linux-loong64": "0.25.0",
|
||||
"@esbuild/linux-mips64el": "0.25.0",
|
||||
"@esbuild/linux-ppc64": "0.25.0",
|
||||
"@esbuild/linux-riscv64": "0.25.0",
|
||||
"@esbuild/linux-s390x": "0.25.0",
|
||||
"@esbuild/linux-x64": "0.25.0",
|
||||
"@esbuild/netbsd-arm64": "0.25.0",
|
||||
"@esbuild/netbsd-x64": "0.25.0",
|
||||
"@esbuild/openbsd-arm64": "0.25.0",
|
||||
"@esbuild/openbsd-x64": "0.25.0",
|
||||
"@esbuild/sunos-x64": "0.25.0",
|
||||
"@esbuild/win32-arm64": "0.25.0",
|
||||
"@esbuild/win32-ia32": "0.25.0",
|
||||
"@esbuild/win32-x64": "0.25.0"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
@ -1255,20 +1582,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-vite-plugin": {
|
||||
"version": "0.7.8",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
|
||||
"integrity": "sha512-HWYqpQYHR3kEQ1LsHX7gHJoNNf0bz5z5mDaHBLzS+PGLCTmYqlU5/SZyeEgObV7z7bC/cnStYcY9H1DI1D5Udg==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.2.0.tgz",
|
||||
"integrity": "sha512-R0pJ+IcTVeqEMoKz/B2Ij57QVq3sFTABiFmb06gAwFdivbOgsUtuhX6N2MGLEArajrS3U5JbberzwOe7uXHMHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picocolors": "^1.0.0",
|
||||
"vite-plugin-full-reload": "^1.0.5"
|
||||
"vite-plugin-full-reload": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"clean-orphaned-assets": "bin/clean.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^3.0.0 || ^4.0.0"
|
||||
"vite": "^5.0.0 || ^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
@ -1546,9 +1876,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
|
||||
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1998,19 +2328,41 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.29.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
|
||||
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz",
|
||||
"integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.6"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0",
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.34.8",
|
||||
"@rollup/rollup-android-arm64": "4.34.8",
|
||||
"@rollup/rollup-darwin-arm64": "4.34.8",
|
||||
"@rollup/rollup-darwin-x64": "4.34.8",
|
||||
"@rollup/rollup-freebsd-arm64": "4.34.8",
|
||||
"@rollup/rollup-freebsd-x64": "4.34.8",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.34.8",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.34.8",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.8",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.8",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.8",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.34.8",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.8",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@ -2480,41 +2832,48 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz",
|
||||
"integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==",
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz",
|
||||
"integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
"postcss": "^8.4.27",
|
||||
"rollup": "^3.27.1"
|
||||
"esbuild": "^0.25.0",
|
||||
"postcss": "^8.5.3",
|
||||
"rollup": "^4.30.1"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">= 14",
|
||||
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"sass-embedded": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
"terser": "^5.16.0",
|
||||
"tsx": "^4.8.1",
|
||||
"yaml": "^2.4.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"jiti": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
@ -2524,6 +2883,9 @@
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
@ -2532,6 +2894,12 @@
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
},
|
||||
"tsx": {
|
||||
"optional": true
|
||||
},
|
||||
"yaml": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -13,7 +13,7 @@
|
||||
"apexcharts": "^3.44.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"brace": "^0.11.1",
|
||||
"laravel-vite-plugin": "^0.7.2",
|
||||
"laravel-vite-plugin": "^1.2.0",
|
||||
"postcss": "^8.4.45",
|
||||
"postcss-nesting": "^13.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
@ -21,6 +21,6 @@
|
||||
"prettier-plugin-sh": "^0.14.0",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"vite": "^4.5.5"
|
||||
"vite": "^6.2.0"
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
rm -f {{ $path }}
|
||||
rm -rf {{ $path }}
|
||||
|
9
resources/views/ssh/os/edit-file.blade.php
Normal file
9
resources/views/ssh/os/edit-file.blade.php
Normal file
@ -0,0 +1,9 @@
|
||||
@if($sudo) sudo @endif tee {!! $path !!} << 'VITO_SSH_EOF' > /dev/null
|
||||
{!! $content !!}
|
||||
VITO_SSH_EOF
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully wrote to {{ $path }}"
|
||||
else
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
15
resources/views/ssh/os/extract.blade.php
Normal file
15
resources/views/ssh/os/extract.blade.php
Normal file
@ -0,0 +1,15 @@
|
||||
@php
|
||||
$extension = pathinfo($path, PATHINFO_EXTENSION);
|
||||
@endphp
|
||||
|
||||
@if($extension === 'zip')
|
||||
unzip -o {{ $path }} -d {{ $destination }}
|
||||
@elseif($extension === 'tar'))
|
||||
tar -xf {{ $path }} -C {{ $destination }}
|
||||
@elseif(in_array($extension, ['gz', 'tar.gz']))
|
||||
tar -xzf {{ $path }} -C {{ $destination }}
|
||||
@elseif(in_array($extension, ['bz2', 'tar.bz2']))
|
||||
tar -xjf {{ $path }} -C {{ $destination }}
|
||||
@else
|
||||
echo "Unsupported archive format: {{ $extension }}"
|
||||
@endif
|
@ -2,4 +2,4 @@
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
{{ $script }}
|
||||
{!! $script !!}
|
||||
|
@ -0,0 +1,11 @@
|
||||
if ! sudo DEBIAN_FRONTEND=noninteractive mysqldump -u root {{ $database }} > {{ $file }}.sql; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! DEBIAN_FRONTEND=noninteractive zip {{ $file }}.zip {{ $file }}.sql; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! rm {{ $file }}.sql; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
@ -0,0 +1,9 @@
|
||||
if ! sudo mysql -e "CREATE USER IF NOT EXISTS '{{ $username }}'@'{{ $host }}' IDENTIFIED BY '{{ $password }}'"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo mysql -e "FLUSH PRIVILEGES"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
echo "Command executed"
|
@ -0,0 +1,5 @@
|
||||
if ! sudo mysql -e "CREATE DATABASE IF NOT EXISTS {{ $name }} CHARACTER SET utf8 COLLATE utf8_general_ci"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
echo "Command executed"
|
@ -0,0 +1,9 @@
|
||||
if ! sudo mysql -e "DROP USER IF EXISTS '{{ $username }}'@'{{ $host }}'"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo mysql -e "FLUSH PRIVILEGES"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
echo "Command executed"
|
@ -0,0 +1,5 @@
|
||||
if ! sudo mysql -e "DROP DATABASE IF EXISTS {{ $name }}"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
echo "Command executed"
|
@ -0,0 +1,9 @@
|
||||
if ! sudo mysql -e "GRANT ALL PRIVILEGES ON {{ $database }}.* TO '{{ $username }}'@'{{ $host }}'"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo mysql -e "FLUSH PRIVILEGES"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
echo "Linking to {{ $database }} finished"
|
@ -0,0 +1,11 @@
|
||||
if ! DEBIAN_FRONTEND=noninteractive unzip {{ $file }}.zip; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo DEBIAN_FRONTEND=noninteractive mysql -u root {{ $database }} < {{ $file }}.sql; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! rm {{ $file }}.sql {{ $file }}.zip; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
@ -0,0 +1,5 @@
|
||||
if ! sudo mysql -e "REVOKE ALL PRIVILEGES, GRANT OPTION FROM '{{ $username }}'@'{{ $host }}'"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
echo "Command executed"
|
@ -1,11 +0,0 @@
|
||||
if ! sudo ufw {{ $type }} from {{ $source }}{{ $mask }} to any proto {{ $protocol }} port {{ $port }}; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo ufw reload; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo service ufw restart; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
@ -0,0 +1,37 @@
|
||||
@include('ssh.services.firewall.ufw.backup-rules')
|
||||
|
||||
if ! sudo ufw --force reset; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
||||
if ! sudo ufw default deny incoming; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo ufw default allow outgoing; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
@foreach($rules as $rule)
|
||||
@php
|
||||
$source = isset($rule->source) && $rule->source !== null
|
||||
? $rule->source . (isset($rule->mask) && $rule->mask !== null ? '/' . $rule->mask : '')
|
||||
: 'any';
|
||||
@endphp
|
||||
|
||||
if ! sudo ufw {{ $rule->type }} from {{ $source }} to any proto {{ $rule->protocol }} port {{ $rule->port }}; then
|
||||
@include('ssh.services.firewall.ufw.restore-rules')
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
@endforeach
|
||||
|
||||
if ! sudo ufw --force enable; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo ufw reload; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
@include('ssh.services.firewall.ufw.clear-backups')
|
@ -0,0 +1,6 @@
|
||||
sudo cp /etc/ufw/before.rules /tmp/ufw.before.backup
|
||||
sudo cp /etc/ufw/after.rules /tmp/ufw.after.backup
|
||||
sudo cp /etc/ufw/user.rules /tmp/ufw.user.backup
|
||||
sudo cp /etc/ufw/before6.rules /tmp/ufw.before6.backup
|
||||
sudo cp /etc/ufw/after6.rules /tmp/ufw.after6.backup
|
||||
sudo cp /etc/ufw/user6.rules /tmp/ufw.user6.backup
|
@ -0,0 +1,6 @@
|
||||
sudo rm -f /tmp/ufw.before.backup
|
||||
sudo rm -f /tmp/ufw.after.backup
|
||||
sudo rm -f /tmp/ufw.user.backup
|
||||
sudo rm -f /tmp/ufw.before6.backup
|
||||
sudo rm -f /tmp/ufw.after6.backup
|
||||
sudo rm -f /tmp/ufw.user6.backup
|
@ -1,11 +0,0 @@
|
||||
if ! sudo ufw delete {{ $type }} from {{ $source }}{{ $mask }} to any proto {{ $protocol }} port {{ $port }}; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo ufw reload; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo service ufw restart; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
@ -0,0 +1,10 @@
|
||||
sudo ufw --force disable
|
||||
|
||||
sudo cp /tmp/ufw.before.backup /etc/ufw/before.rules
|
||||
sudo cp /tmp/ufw.after.backup /etc/ufw/after.rules
|
||||
sudo cp /tmp/ufw.user.backup /etc/ufw/user.rules
|
||||
sudo cp /tmp/ufw.before6.backup /etc/ufw/before6.rules
|
||||
sudo cp /tmp/ufw.after6.backup /etc/ufw/after6.rules
|
||||
sudo cp /tmp/ufw.user6.backup /etc/ufw/user6.rules
|
||||
|
||||
sudo ufw --force enable
|
@ -6,8 +6,12 @@
|
||||
}
|
||||
@endif
|
||||
|
||||
@php
|
||||
$backendName = preg_replace("/[^A-Za-z0-9 ]/", '', $site->domain).'_backend';
|
||||
@endphp
|
||||
|
||||
@if ($site->type === \App\Enums\SiteType::LOAD_BALANCER)
|
||||
upstream backend {
|
||||
upstream {{ $backendName }} {
|
||||
@switch($site->type_data['method'] ?? \App\Enums\LoadBalancerMethod::ROUND_ROBIN)
|
||||
@case(\App\Enums\LoadBalancerMethod::LEAST_CONNECTIONS)
|
||||
least_conn;
|
||||
@ -49,7 +53,7 @@
|
||||
|
||||
@if ($site->type()->language() === 'php')
|
||||
@php
|
||||
$phpSocket = 'unix:/var/run/php/php-fpm.sock';
|
||||
$phpSocket = "unix:/var/run/php/php{$site->php_version}-fpm.sock";
|
||||
if ($site->isIsolated()) {
|
||||
$phpSocket = "unix:/run/php/php{$site->php_version}-fpm-{$site->user}.sock";
|
||||
}
|
||||
@ -67,7 +71,7 @@
|
||||
|
||||
@if ($site->type === \App\Enums\SiteType::LOAD_BALANCER)
|
||||
location / {
|
||||
proxy_pass http://backend$request_uri;
|
||||
proxy_pass http://{{ $backendName }}$request_uri;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
@ -23,16 +23,47 @@ public function test_create_firewall_rule(): void
|
||||
'project' => $this->server->project,
|
||||
'server' => $this->server,
|
||||
]), [
|
||||
'name' => 'Test',
|
||||
'type' => 'allow',
|
||||
'protocol' => 'tcp',
|
||||
'port' => '1234',
|
||||
'source' => '0.0.0.0',
|
||||
'mask' => '0',
|
||||
'mask' => '1',
|
||||
])
|
||||
->assertSuccessful()
|
||||
->assertJsonFragment([
|
||||
'port' => 1234,
|
||||
'status' => FirewallRuleStatus::READY,
|
||||
'status' => FirewallRuleStatus::CREATING,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_edit_firewall_rule(): void
|
||||
{
|
||||
SSH::fake();
|
||||
|
||||
Sanctum::actingAs($this->user, ['read', 'write']);
|
||||
|
||||
$rule = FirewallRule::factory()->create([
|
||||
'server_id' => $this->server->id,
|
||||
'port' => 1234,
|
||||
]);
|
||||
|
||||
$this->json('PUT', route('api.projects.servers.firewall-rules.edit', [
|
||||
'project' => $this->server->project,
|
||||
'server' => $this->server,
|
||||
'firewallRule' => $rule,
|
||||
]), [
|
||||
'name' => 'Test',
|
||||
'type' => 'allow',
|
||||
'protocol' => 'tcp',
|
||||
'port' => '55',
|
||||
'source' => null,
|
||||
'mask' => null,
|
||||
])
|
||||
->assertSuccessful()
|
||||
->assertJsonFragment([
|
||||
'port' => 55,
|
||||
'status' => FirewallRuleStatus::UPDATING,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -202,22 +202,25 @@ public function test_update_env_file(): void
|
||||
->assertSuccessful()
|
||||
->assertNotified('.env updated!');
|
||||
|
||||
SSH::assertFileUploaded('/home/vito/'.$this->site->domain.'/.env', 'APP_ENV="production"');
|
||||
SSH::assertExecutedContains('tee /home/vito/vito.test/.env << \'VITO_SSH_EOF\'');
|
||||
SSH::assertExecutedContains('APP_ENV="production"');
|
||||
}
|
||||
|
||||
public function test_git_hook_deployment(): void
|
||||
/**
|
||||
* @dataProvider hookData
|
||||
*/
|
||||
public function test_git_hook_deployment(string $provider, array $webhook, string $url, array $payload, bool $skip): void
|
||||
{
|
||||
SSH::fake();
|
||||
Http::fake([
|
||||
'github.com/*' => Http::response([
|
||||
'sha' => '123',
|
||||
'commit' => [
|
||||
'message' => 'test commit message',
|
||||
'name' => 'test commit name',
|
||||
'email' => 'user@example.com',
|
||||
'url' => 'https://github.com',
|
||||
],
|
||||
], 200),
|
||||
$url => Http::response($payload),
|
||||
]);
|
||||
|
||||
$this->site->update([
|
||||
'branch' => 'main',
|
||||
]);
|
||||
$this->site->sourceControl->update([
|
||||
'provider' => $provider,
|
||||
]);
|
||||
|
||||
GitHook::factory()->create([
|
||||
@ -232,15 +235,29 @@ public function test_git_hook_deployment(): void
|
||||
'content' => 'git pull',
|
||||
]);
|
||||
|
||||
$this->post(route('api.git-hooks'), [
|
||||
$this->post(route('api.git-hooks', [
|
||||
'secret' => 'secret',
|
||||
])->assertSessionDoesntHaveErrors();
|
||||
]), $webhook)->assertSessionDoesntHaveErrors();
|
||||
|
||||
if ($skip) {
|
||||
$this->assertDatabaseMissing('deployments', [
|
||||
'site_id' => $this->site->id,
|
||||
'deployment_script_id' => $this->site->deploymentScript->id,
|
||||
'status' => DeploymentStatus::FINISHED,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->assertDatabaseHas('deployments', [
|
||||
'site_id' => $this->site->id,
|
||||
'deployment_script_id' => $this->site->deploymentScript->id,
|
||||
'status' => DeploymentStatus::FINISHED,
|
||||
]);
|
||||
|
||||
$deployment = $this->site->deployments()->first();
|
||||
$this->assertEquals('saeed', $deployment->commit_data['name']);
|
||||
$this->assertEquals('saeed@vitodeploy.com', $deployment->commit_data['email']);
|
||||
}
|
||||
|
||||
public function test_git_hook_deployment_invalid_secret(): void
|
||||
@ -270,4 +287,146 @@ public function test_git_hook_deployment_invalid_secret(): void
|
||||
'status' => DeploymentStatus::FINISHED,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function hookData(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'github',
|
||||
[
|
||||
'ref' => 'refs/heads/main',
|
||||
],
|
||||
'github.com/*',
|
||||
[
|
||||
'sha' => '123',
|
||||
'commit' => [
|
||||
'committer' => [
|
||||
'name' => 'saeed',
|
||||
'email' => 'saeed@vitodeploy.com',
|
||||
],
|
||||
'message' => 'test commit message',
|
||||
'url' => 'https://github.com',
|
||||
],
|
||||
],
|
||||
false,
|
||||
],
|
||||
[
|
||||
'github',
|
||||
[
|
||||
'ref' => 'refs/heads/other-branch',
|
||||
],
|
||||
'github.com/*',
|
||||
[
|
||||
'sha' => '123',
|
||||
'commit' => [
|
||||
'committer' => [
|
||||
'name' => 'saeed',
|
||||
'email' => 'saeed@vitodeploy.com',
|
||||
],
|
||||
'message' => 'test commit message',
|
||||
'url' => 'https://github.com',
|
||||
],
|
||||
],
|
||||
true,
|
||||
],
|
||||
[
|
||||
'gitlab',
|
||||
[
|
||||
'ref' => 'main',
|
||||
],
|
||||
'gitlab.com/*',
|
||||
[
|
||||
[
|
||||
'id' => '123',
|
||||
'committer_name' => 'saeed',
|
||||
'committer_email' => 'saeed@vitodeploy.com',
|
||||
'title' => 'test',
|
||||
'web_url' => 'https://gitlab.com',
|
||||
],
|
||||
],
|
||||
false,
|
||||
],
|
||||
[
|
||||
'gitlab',
|
||||
[
|
||||
'ref' => 'other-branch',
|
||||
],
|
||||
'gitlab.com/*',
|
||||
[
|
||||
[
|
||||
'id' => '123',
|
||||
'committer_name' => 'saeed',
|
||||
'committer_email' => 'saeed@vitodeploy.com',
|
||||
'title' => 'test',
|
||||
'web_url' => 'https://gitlab.com',
|
||||
],
|
||||
],
|
||||
true,
|
||||
],
|
||||
[
|
||||
'bitbucket',
|
||||
[
|
||||
'push' => [
|
||||
'changes' => [
|
||||
[
|
||||
'new' => [
|
||||
'name' => 'main',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'bitbucket.org/*',
|
||||
[
|
||||
'values' => [
|
||||
[
|
||||
'hash' => '123',
|
||||
'author' => [
|
||||
'raw' => 'saeed <saeed@vitodeploy.com>',
|
||||
],
|
||||
'message' => 'test',
|
||||
'links' => [
|
||||
'html' => [
|
||||
'href' => 'https://bitbucket.org',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
false,
|
||||
],
|
||||
[
|
||||
'bitbucket',
|
||||
[
|
||||
'push' => [
|
||||
'changes' => [
|
||||
[
|
||||
'new' => [
|
||||
'name' => 'other-branch',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'bitbucket.org/*',
|
||||
[
|
||||
'values' => [
|
||||
[
|
||||
'hash' => '123',
|
||||
'author' => [
|
||||
'raw' => 'saeed <saeed@vitodeploy.com>',
|
||||
],
|
||||
'message' => 'test',
|
||||
'links' => [
|
||||
'html' => [
|
||||
'href' => 'https://bitbucket.org',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
true,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -22,11 +22,16 @@ class DatabaseBackupTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_create_backup(): void
|
||||
/**
|
||||
* @dataProvider data
|
||||
*/
|
||||
public function test_create_backup(string $db): void
|
||||
{
|
||||
SSH::fake();
|
||||
Http::fake();
|
||||
|
||||
$this->setupDatabase($db);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
$database = Database::factory()->create([
|
||||
@ -152,8 +157,13 @@ public function test_update_backup(): void
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_delete_backup(): void
|
||||
/**
|
||||
* @dataProvider data
|
||||
*/
|
||||
public function test_delete_backup(string $db): void
|
||||
{
|
||||
$this->setupDatabase($db);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
$database = Database::factory()->create([
|
||||
@ -182,11 +192,16 @@ public function test_delete_backup(): void
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_restore_backup(): void
|
||||
/**
|
||||
* @dataProvider data
|
||||
*/
|
||||
public function test_restore_backup(string $db): void
|
||||
{
|
||||
Http::fake();
|
||||
SSH::fake();
|
||||
|
||||
$this->setupDatabase($db);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
$database = Database::factory()->create([
|
||||
@ -220,4 +235,24 @@ public function test_restore_backup(): void
|
||||
'status' => BackupFileStatus::RESTORED,
|
||||
]);
|
||||
}
|
||||
|
||||
private function setupDatabase(string $database): void
|
||||
{
|
||||
$this->server->services()->where('type', 'database')->delete();
|
||||
|
||||
$this->server->services()->create([
|
||||
'type' => 'database',
|
||||
'name' => config('core.databases_name.'.$database),
|
||||
'version' => config('core.databases_version.'.$database),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function data(): array
|
||||
{
|
||||
return [
|
||||
[\App\Enums\Database::MYSQL80],
|
||||
[\App\Enums\Database::MARIADB104],
|
||||
[\App\Enums\Database::POSTGRESQL16],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
138
tests/Feature/FileManagerTest.php
Normal file
138
tests/Feature/FileManagerTest.php
Normal file
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Facades\SSH;
|
||||
use App\Models\File;
|
||||
use App\Web\Pages\Servers\FileManager\Index;
|
||||
use App\Web\Pages\Servers\FileManager\Widgets\FilesList;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class FileManagerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_see_files(): void
|
||||
{
|
||||
SSH::fake(<<<'EOF'
|
||||
total 32
|
||||
drwxr-xr-x 7 vito vito 4096 Feb 2 19:42 .
|
||||
drwxr-xr-x 3 root root 4096 Feb 1 18:44 ..
|
||||
drwx------ 3 vito vito 4096 Feb 1 18:45 .cache
|
||||
drwxrwxr-x 3 vito vito 4096 Feb 1 18:45 .config
|
||||
-rw-rw-r-- 1 vito vito 82 Feb 2 14:13 .gitconfig
|
||||
drwxrwxr-x 3 vito vito 4096 Feb 1 18:45 .local
|
||||
drwxr-xr-x 2 vito vito 4096 Feb 2 14:13 .ssh
|
||||
drwxrwxr-x 3 vito vito 4096 Feb 2 21:25 test.vitodeploy.com
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
$this->get(
|
||||
Index::getUrl([
|
||||
'server' => $this->server,
|
||||
])
|
||||
)
|
||||
->assertSuccessful()
|
||||
->assertSee('.cache')
|
||||
->assertSee('.config');
|
||||
}
|
||||
|
||||
public function test_upload_file(): void
|
||||
{
|
||||
SSH::fake();
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
Livewire::test(FilesList::class, [
|
||||
'server' => $this->server,
|
||||
])
|
||||
->callTableAction('upload', null, [
|
||||
'file' => UploadedFile::fake()->create('test.txt'),
|
||||
])
|
||||
->assertSuccessful();
|
||||
}
|
||||
|
||||
public function test_create_file(): void
|
||||
{
|
||||
SSH::fake(<<<'EOF'
|
||||
total 3
|
||||
drwxr-xr-x 7 vito vito 4096 Feb 2 19:42 .
|
||||
drwxr-xr-x 3 root root 4096 Feb 1 18:44 ..
|
||||
-rw-rw-r-- 1 vito vito 82 Feb 2 14:13 test.txt
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
Livewire::test(FilesList::class, [
|
||||
'server' => $this->server,
|
||||
])
|
||||
->callTableAction('new-file', null, [
|
||||
'name' => 'test.txt',
|
||||
'content' => 'Hello, world!',
|
||||
])
|
||||
->assertSuccessful();
|
||||
|
||||
$this->assertDatabaseHas('files', [
|
||||
'name' => 'test.txt',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_create_directory(): void
|
||||
{
|
||||
SSH::fake(<<<'EOF'
|
||||
total 3
|
||||
drwxr-xr-x 7 vito vito 4096 Feb 2 19:42 .
|
||||
drwxr-xr-x 3 root root 4096 Feb 1 18:44 ..
|
||||
drwxr-xr-x 2 vito vito 4096 Feb 2 14:13 test
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
Livewire::test(FilesList::class, [
|
||||
'server' => $this->server,
|
||||
])
|
||||
->callTableAction('new-directory', null, [
|
||||
'name' => 'test',
|
||||
])
|
||||
->assertSuccessful();
|
||||
|
||||
$this->assertDatabaseHas('files', [
|
||||
'name' => 'test',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_download_file(): void
|
||||
{
|
||||
SSH::fake(<<<'EOF'
|
||||
total 3
|
||||
drwxr-xr-x 7 vito vito 4096 Feb 2 19:42 .
|
||||
drwxr-xr-x 3 root root 4096 Feb 1 18:44 ..
|
||||
-rw-rw-r-- 1 vito vito 82 Feb 2 14:13 test.txt
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
$this->get(
|
||||
Index::getUrl([
|
||||
'server' => $this->server,
|
||||
])
|
||||
)->assertSuccessful();
|
||||
|
||||
$file = File::query()->where('name', 'test.txt')->firstOrFail();
|
||||
|
||||
Livewire::test(FilesList::class, [
|
||||
'server' => $this->server,
|
||||
])
|
||||
->assertTableActionVisible('download', $file)
|
||||
->callTableAction('download', $file)
|
||||
->assertSuccessful();
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ public function test_create_firewall_rule(): void
|
||||
'server' => $this->server,
|
||||
])
|
||||
->callAction('create', [
|
||||
'name' => 'Test',
|
||||
'type' => 'allow',
|
||||
'protocol' => 'tcp',
|
||||
'port' => '1234',
|
||||
|
Reference in New Issue
Block a user