Compare commits

..

11 Commits

Author SHA1 Message Date
b62c40c97d Bump micromatch from 4.0.5 to 4.0.8 (#287)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-07 18:58:45 +02:00
e39e8c17a2 Add S3 and Wasabi as storage providers (#281) 2024-09-06 23:29:43 +02:00
1391eb32d8 Fix Discord connection check (#285) 2024-09-03 21:33:17 +02:00
7f5e68e131 Tags (#277) 2024-08-20 21:26:27 +02:00
431da1b728 Fix FTP and add more tests (#274) 2024-08-09 19:45:00 +02:00
8c487a64fa Add ace editor (#269) 2024-08-07 21:12:31 +02:00
a67e586a5d fix: image ids updated for DigitalOcean (#271) 2024-08-07 19:58:33 +02:00
960db714b7 Fix update issue (#268) 2024-08-03 12:40:44 +02:00
7da0221ccb Revert "fix: Avoid echoing when asking for the password (#255)" (#266)
This reverts commit 55269dbcde.
2024-08-01 18:28:07 +02:00
9ac5f9ebb3 revert migration squash (#261) 2024-07-30 20:39:05 +02:00
ed8965b92b Fix .env updates for double quotations (#259) 2024-07-27 17:43:46 +02:00
156 changed files with 5379 additions and 1151 deletions

View File

@ -2,10 +2,14 @@
namespace App\Actions\Site;
use App\Exceptions\SSHUploadFailed;
use App\Models\Site;
class UpdateEnv
{
/**
* @throws SSHUploadFailed
*/
public function update(Site $site, array $input): void
{
$site->server->os()->editFile(

View File

@ -0,0 +1,58 @@
<?php
namespace App\Actions\Tag;
use App\Models\Server;
use App\Models\Site;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class AttachTag
{
public function attach(User $user, array $input): Tag
{
$this->validate($input);
/** @var Server|Site $taggable */
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);
$tag = Tag::query()->where('name', $input['name'])->first();
if ($tag) {
if (! $taggable->tags->contains($tag->id)) {
$taggable->tags()->attach($tag->id);
}
return $tag;
}
$tag = new Tag([
'project_id' => $user->currentProject->id,
'name' => $input['name'],
'color' => config('core.tag_colors')[array_rand(config('core.tag_colors'))],
]);
$tag->save();
$taggable->tags()->attach($tag->id);
return $tag;
}
private function validate(array $input): void
{
Validator::make($input, [
'name' => [
'required',
],
'taggable_id' => [
'required',
'integer',
],
'taggable_type' => [
'required',
Rule::in(config('core.taggable_types')),
],
])->validate();
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Actions\Tag;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class CreateTag
{
public function create(User $user, array $input): Tag
{
$this->validate($input);
$tag = Tag::query()
->where('project_id', $user->current_project_id)
->where('name', $input['name'])
->first();
if ($tag) {
throw ValidationException::withMessages([
'name' => ['Tag with this name already exists.'],
]);
}
$tag = new Tag([
'project_id' => $user->currentProject->id,
'name' => $input['name'],
'color' => $input['color'],
]);
$tag->save();
return $tag;
}
private function validate(array $input): void
{
Validator::make($input, [
'name' => [
'required',
],
'color' => [
'required',
Rule::in(config('core.tag_colors')),
],
])->validate();
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Actions\Tag;
use App\Models\Tag;
use Illuminate\Support\Facades\DB;
class DeleteTag
{
public function delete(Tag $tag): void
{
DB::table('taggables')->where('tag_id', $tag->id)->delete();
$tag->delete();
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Actions\Tag;
use App\Models\Server;
use App\Models\Site;
use App\Models\Tag;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class DetachTag
{
public function detach(Tag $tag, array $input): void
{
$this->validate($input);
/** @var Server|Site $taggable */
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);
$taggable->tags()->detach($tag->id);
}
private function validate(array $input): void
{
Validator::make($input, [
'taggable_id' => [
'required',
'integer',
],
'taggable_type' => [
'required',
Rule::in(config('core.taggable_types')),
],
])->validate();
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Actions\Tag;
use App\Models\Tag;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class EditTag
{
public function edit(Tag $tag, array $input): void
{
$this->validate($input);
$tag->name = $input['name'];
$tag->color = $input['color'];
$tag->save();
}
/**
* @throws ValidationException
*/
private function validate(array $input): void
{
$rules = [
'name' => [
'required',
],
'color' => [
'required',
Rule::in(config('core.tag_colors')),
],
];
Validator::make($input, $rules)->validate();
}
}

View File

@ -9,4 +9,8 @@ final class StorageProvider
const FTP = 'ftp';
const LOCAL = 'local';
const S3 = 's3';
const WASABI = 'wasabi';
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Exceptions;
class SSHUploadFailed extends SSHError
{
//
}

30
app/Facades/FTP.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace App\Facades;
use App\Support\Testing\FTPFake;
use FTP\Connection;
use Illuminate\Support\Facades\Facade;
/**
* @method static bool|Connection connect(string $host, string $port, bool $ssl = false)
* @method static bool login(string $username, string $password, bool|Connection $connection)
* @method static void close(bool|Connection $connection)
* @method static bool passive(bool|Connection $connection, bool $passive)
* @method static bool delete(bool|Connection $connection, string $path)
* @method static void assertConnected(string $host)
*/
class FTP extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'ftp';
}
public static function fake(): FTPFake
{
static::swap($fake = new FTPFake());
return $fake;
}
}

View File

@ -16,6 +16,8 @@
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false)
* @method static string assertExecuted(array|string $commands)
* @method static string assertExecutedContains(string $command)
* @method static string assertFileUploaded(string $toPath, ?string $content = null)
* @method static string getUploadedLocalPath()
* @method static disconnect()
*/
class SSH extends FacadeAlias

37
app/Helpers/FTP.php Normal file
View File

@ -0,0 +1,37 @@
<?php
namespace App\Helpers;
use FTP\Connection;
class FTP
{
public function connect(string $host, string $port, bool $ssl = false): bool|Connection
{
if ($ssl) {
return ftp_ssl_connect($host, $port, 5);
}
return ftp_connect($host, $port, 5);
}
public function login(string $username, string $password, bool|Connection $connection): bool
{
return ftp_login($connection, $username, $password);
}
public function close(bool|Connection $connection): void
{
ftp_close($connection);
}
public function passive(bool|Connection $connection, bool $passive): bool
{
return ftp_pasv($connection, $passive);
}
public function delete(bool|Connection $connection, string $path): bool
{
return ftp_delete($connection, $path);
}
}

View File

@ -10,6 +10,7 @@
use App\Exceptions\RepositoryNotFound;
use App\Exceptions\RepositoryPermissionDenied;
use App\Exceptions\SourceControlIsNotConnected;
use App\Exceptions\SSHUploadFailed;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Models\Deployment;
@ -81,9 +82,12 @@ public function updateEnv(Server $server, Site $site, Request $request): Redirec
{
$this->authorize('manage', $server);
app(UpdateEnv::class)->update($site, $request->input());
Toast::success('Env updated!');
try {
app(UpdateEnv::class)->update($site, $request->input());
Toast::success('Env updated!');
} catch (SSHUploadFailed) {
Toast::error('Failed to update .env file!');
}
return back();
}

View File

@ -0,0 +1,90 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Actions\Tag\AttachTag;
use App\Actions\Tag\CreateTag;
use App\Actions\Tag\DeleteTag;
use App\Actions\Tag\DetachTag;
use App\Actions\Tag\EditTag;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Http\Controllers\Controller;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class TagController extends Controller
{
public function index(Request $request): View
{
$data = [
'tags' => Tag::getByProjectId(auth()->user()->current_project_id)->get(),
];
if ($request->has('edit')) {
$data['editTag'] = Tag::find($request->input('edit'));
}
return view('settings.tags.index', $data);
}
public function create(Request $request): HtmxResponse
{
/** @var User $user */
$user = $request->user();
app(CreateTag::class)->create(
$user,
$request->input(),
);
Toast::success('Tag created.');
return htmx()->redirect(route('settings.tags'));
}
public function update(Tag $tag, Request $request): HtmxResponse
{
app(EditTag::class)->edit(
$tag,
$request->input(),
);
Toast::success('Tag updated.');
return htmx()->redirect(route('settings.tags'));
}
public function attach(Request $request): RedirectResponse
{
/** @var User $user */
$user = $request->user();
app(AttachTag::class)->attach($user, $request->input());
return back()->with([
'status' => 'tag-created',
]);
}
public function detach(Request $request, Tag $tag): RedirectResponse
{
app(DetachTag::class)->detach($tag, $request->input());
return back()->with([
'status' => 'tag-detached',
]);
}
public function delete(Tag $tag): RedirectResponse
{
app(DeleteTag::class)->delete($tag);
Toast::success('Tag deleted.');
return back();
}
}

View File

@ -65,4 +65,9 @@ public function sourceControls(): HasMany
{
return $this->hasMany(SourceControl::class);
}
public function tags(): HasMany
{
return $this->hasMany(Tag::class);
}
}

View File

@ -14,6 +14,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
@ -214,6 +215,11 @@ public function sshKeys(): BelongsToMany
->withTimestamps();
}
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
public function getSshUser(): string
{
if ($this->ssh_user) {

View File

@ -11,6 +11,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Str;
/**
@ -126,6 +127,11 @@ public function ssls(): HasMany
return $this->hasMany(Ssl::class);
}
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
/**
* @throws SourceControlIsNotConnected
*/

55
app/Models/Tag.php Normal file
View File

@ -0,0 +1,55 @@
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
/**
* @property int $id
* @property int $project_id
* @property string $name
* @property string $color
* @property Carbon $created_at
* @property Carbon $updated_at
*/
class Tag extends Model
{
use HasFactory;
protected $fillable = [
'project_id',
'name',
'color',
];
protected $casts = [
'project_id' => 'int',
];
public function project(): BelongsTo
{
return $this->belongsTo(Project::class);
}
public function servers(): MorphToMany
{
return $this->morphedByMany(Server::class, 'taggable');
}
public function sites(): MorphToMany
{
return $this->morphedByMany(Site::class, 'taggable');
}
public static function getByProjectId(int $projectId): Builder
{
return self::query()
->where('project_id', $projectId)
->orWhereNull('project_id');
}
}

View File

@ -56,7 +56,7 @@ private function checkConnection(string $subject, string $text): bool
'content' => '*'.$subject.'*'."\n".$text,
]);
return $connect->ok();
return $connect->successful();
}
public function send(object $notifiable, NotificationInterface $notification): void

View File

@ -2,6 +2,7 @@
namespace App\Providers;
use App\Helpers\FTP;
use App\Helpers\Notifier;
use App\Helpers\SSH;
use App\Helpers\Toast;
@ -36,5 +37,8 @@ public function boot(): void
$this->app->bind('toast', function () {
return new Toast;
});
$this->app->bind('ftp', function () {
return new FTP;
});
}
}

20
app/SSH/HasS3Storage.php Normal file
View File

@ -0,0 +1,20 @@
<?php
namespace App\SSH;
trait HasS3Storage
{
private function prepareS3Path(string $path, string $prefix = ''): string
{
$path = trim($path);
$path = ltrim($path, '/');
$path = preg_replace('/[^a-zA-Z0-9\-_\.\/]/', '_', $path);
$path = preg_replace('/\/+/', '/', $path);
if ($prefix) {
$path = trim($prefix, '/').'/'.$path;
}
return $path;
}
}

View File

@ -2,9 +2,14 @@
namespace App\SSH\OS;
use App\Exceptions\SSHUploadFailed;
use App\Models\Server;
use App\Models\ServerLog;
use App\SSH\HasScripts;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Throwable;
class OS
{
@ -109,14 +114,25 @@ public function reboot(): void
);
}
/**
* @throws SSHUploadFailed
*/
public function editFile(string $path, ?string $content = null): void
{
$this->server->ssh()->exec(
$this->getScript('edit-file.sh', [
'path' => $path,
'content' => $content ?? '',
]),
);
$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);
}
}
public function readFile(string $path): string
@ -200,4 +216,11 @@ public function resourceInfo(): array
'disk_free' => str($info)->after('disk_free:')->before(PHP_EOL)->toString(),
];
}
private function deleteTempFile(string $name): void
{
if (Storage::disk('local')->exists($name)) {
Storage::disk('local')->delete($name);
}
}
}

View File

@ -1,3 +0,0 @@
if ! echo "__content__" | tee __path__; then
echo 'VITO_SSH_ERROR' && exit 1
fi

78
app/SSH/Storage/S3.php Normal file
View File

@ -0,0 +1,78 @@
<?php
namespace App\SSH\Storage;
use App\Exceptions\SSHCommandError;
use App\Models\Server;
use App\Models\StorageProvider;
use App\SSH\HasS3Storage;
use App\SSH\HasScripts;
use Illuminate\Support\Facades\Log;
class S3 extends S3AbstractStorage
{
use HasS3Storage, HasScripts;
public function __construct(Server $server, StorageProvider $storageProvider)
{
parent::__construct($server, $storageProvider);
$this->setBucketRegion($this->storageProvider->credentials['region']);
$this->setApiUrl();
}
/**
* @throws SSHCommandError
*/
public function upload(string $src, string $dest): array
{
$uploadCommand = $this->getScript('s3/upload.sh', [
'src' => $src,
'bucket' => $this->storageProvider->credentials['bucket'],
'dest' => $this->prepareS3Path($this->storageProvider->credentials['path'].'/'.$dest),
'key' => $this->storageProvider->credentials['key'],
'secret' => $this->storageProvider->credentials['secret'],
'region' => $this->getBucketRegion(),
'endpoint' => $this->getApiUrl(),
]);
$upload = $this->server->ssh()->exec($uploadCommand, 'upload-to-s3');
if (str_contains($upload, 'Error') || ! str_contains($upload, 'upload:')) {
Log::error('Failed to upload to S3', ['output' => $upload]);
throw new SSHCommandError('Failed to upload to S3: '.$upload);
}
return [
'size' => null, // You can parse the size from the output if needed
];
}
/**
* @throws SSHCommandError
*/
public function download(string $src, string $dest): void
{
$downloadCommand = $this->getScript('s3/download.sh', [
'src' => $this->prepareS3Path($this->storageProvider->credentials['path'].'/'.$src),
'dest' => $dest,
'bucket' => $this->storageProvider->credentials['bucket'],
'key' => $this->storageProvider->credentials['key'],
'secret' => $this->storageProvider->credentials['secret'],
'region' => $this->getBucketRegion(),
'endpoint' => $this->getApiUrl(),
]);
$download = $this->server->ssh()->exec($downloadCommand, 'download-from-s3');
if (! str_contains($download, 'Download successful')) {
Log::error('Failed to download from S3', ['output' => $download]);
throw new SSHCommandError('Failed to download from S3: '.$download);
}
}
/**
* @TODO Implement delete method
*/
public function delete(string $path): void {}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\SSH\Storage;
abstract class S3AbstractStorage extends AbstractStorage
{
protected ?string $apiUrl = null;
protected ?string $bucketRegion = null;
public function getApiUrl(): string
{
return $this->apiUrl;
}
public function setApiUrl(?string $region = null): void
{
$this->bucketRegion = $region ?? $this->bucketRegion;
$this->apiUrl = "https://s3.{$this->bucketRegion}.amazonaws.com";
}
// Getter and Setter for $bucketRegion
public function getBucketRegion(): string
{
return $this->bucketRegion;
}
public function setBucketRegion(string $region): void
{
$this->bucketRegion = $region;
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace App\SSH\Storage;
use App\Exceptions\SSHCommandError;
use App\Models\Server;
use App\Models\StorageProvider;
use App\SSH\HasS3Storage;
use App\SSH\HasScripts;
use Illuminate\Support\Facades\Log;
class Wasabi extends S3AbstractStorage
{
use HasS3Storage, HasScripts;
public function __construct(Server $server, StorageProvider $storageProvider)
{
parent::__construct($server, $storageProvider);
$this->setBucketRegion($this->storageProvider->credentials['region']);
$this->setApiUrl();
}
/**
* @throws SSHCommandError
*/
public function upload(string $src, string $dest): array
{
$uploadCommand = $this->getScript('wasabi/upload.sh', [
'src' => $src,
'bucket' => $this->storageProvider->credentials['bucket'],
'dest' => $this->prepareS3Path($this->storageProvider->credentials['path'].'/'.$dest),
'key' => $this->storageProvider->credentials['key'],
'secret' => $this->storageProvider->credentials['secret'],
'region' => $this->storageProvider->credentials['region'],
'endpoint' => $this->getApiUrl(),
]);
$upload = $this->server->ssh()->exec($uploadCommand, 'upload-to-wasabi');
if (str_contains($upload, 'Error') || ! str_contains($upload, 'upload:')) {
Log::error('Failed to upload to wasabi', ['output' => $upload]);
throw new SSHCommandError('Failed to upload to wasabi: '.$upload);
}
return [
'size' => null, // You can parse the size from the output if needed
];
}
/**
* @throws SSHCommandError
*/
public function download(string $src, string $dest): void
{
$downloadCommand = $this->getScript('wasabi/download.sh', [
'src' => $this->prepareS3Path($this->storageProvider->credentials['path'].'/'.$src),
'dest' => $dest,
'bucket' => $this->storageProvider->credentials['bucket'],
'key' => $this->storageProvider->credentials['key'],
'secret' => $this->storageProvider->credentials['secret'],
'region' => $this->storageProvider->credentials['region'],
'endpoint' => $this->getApiUrl(),
]);
$download = $this->server->ssh()->exec($downloadCommand, 'download-from-wasabi');
if (! str_contains($download, 'Download successful')) {
Log::error('Failed to download from wasabi', ['output' => $download]);
throw new SSHCommandError('Failed to download from wasabi: '.$download);
}
}
/**
* @TODO Implement delete method
*/
public function delete(string $path): void {}
public function setApiUrl(?string $region = null): void
{
$this->bucketRegion = $region ?? $this->bucketRegion;
$this->apiUrl = "https://{$this->storageProvider->credentials['bucket']}.s3.{$this->getBucketRegion()}.wasabisys.com";
}
}

View File

@ -0,0 +1,32 @@
#!/bin/bash
# Configure AWS CLI with provided credentials
/usr/local/bin/aws configure set aws_access_key_id "__key__"
/usr/local/bin/aws configure set aws_secret_access_key "__secret__"
/usr/local/bin/aws configure set default.region "__region__"
# Use the provided endpoint in the correct format
ENDPOINT="__endpoint__"
BUCKET="__bucket__"
REGION="__region__"
# Ensure that DEST does not have a trailing slash
SRC="__src__"
DEST="__dest__"
# Download the file from S3
echo "Downloading s3://__bucket__/__src__ to __dest__"
download_output=$(/usr/local/bin/aws s3 cp "s3://$BUCKET/$SRC" "$DEST" --endpoint-url="$ENDPOINT" --region "$REGION" 2>&1)
download_exit_code=$?
# Log output and exit code
echo "Download command output: $download_output"
echo "Download command exit code: $download_exit_code"
# Check if the download was successful
if [ $download_exit_code -eq 0 ]; then
echo "Download successful"
else
echo "Download failed"
exit 1
fi

View File

@ -0,0 +1,59 @@
#!/bin/bash
# Check if AWS CLI is installed
if ! command -v aws &> /dev/null
then
echo "AWS CLI is not installed. Installing..."
# Detect system architecture
ARCH=$(uname -m)
if [ "$ARCH" == "x86_64" ]; then
CLI_URL="https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"
elif [ "$ARCH" == "aarch64" ]; then
CLI_URL="https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"
else
echo "Unsupported architecture: $ARCH"
exit 1
fi
# Download and install AWS CLI
sudo curl "$CLI_URL" -o "awscliv2.zip"
sudo unzip awscliv2.zip
sudo ./aws/install --update
sudo rm -rf awscliv2.zip aws
echo "AWS CLI installation completed."
else
echo "AWS CLI is already installed."
/usr/local/bin/aws --version
fi
# Configure AWS CLI with provided credentials
/usr/local/bin/aws configure set aws_access_key_id "__key__"
/usr/local/bin/aws configure set aws_secret_access_key "__secret__"
# Use the provided endpoint in the correct format
ENDPOINT="__endpoint__"
BUCKET="__bucket__"
REGION="__region__"
# Ensure that DEST does not have a trailing slash
SRC="__src__"
DEST="__dest__"
# Upload the file
echo "Uploading __src__ to s3://$BUCKET/$DEST"
upload_output=$(/usr/local/bin/aws s3 cp "$SRC" "s3://$BUCKET/$DEST" --endpoint-url="$ENDPOINT" --region "$REGION" 2>&1)
upload_exit_code=$?
# Log output and exit code
echo "Upload command output: $upload_output"
echo "Upload command exit code: $upload_exit_code"
# Check if the upload was successful
if [ $upload_exit_code -eq 0 ]; then
echo "Upload successful"
else
echo "Upload failed"
exit 1
fi

View File

@ -0,0 +1,31 @@
#!/bin/bash
# Configure AWS CLI with provided credentials
/usr/local/bin/aws configure set aws_access_key_id "__key__"
/usr/local/bin/aws configure set aws_secret_access_key "__secret__"
# Use the provided endpoint in the correct format
ENDPOINT="__endpoint__"
BUCKET="__bucket__"
REGION="__region__"
# Ensure that DEST does not have a trailing slash
SRC="__src__"
DEST="__dest__"
# Download the file from S3
echo "Downloading s3://__bucket____src__ to __dest__"
download_output=$(/usr/local/bin/aws s3 cp "s3://$BUCKET/$SRC" "$DEST" --endpoint-url="$ENDPOINT" --region "$REGION" 2>&1)
download_exit_code=$?
# Log output and exit code
echo "Download command output: $download_output"
echo "Download command exit code: $download_exit_code"
# Check if the download was successful
if [ $download_exit_code -eq 0 ]; then
echo "Download successful"
else
echo "Download failed"
exit 1
fi

View File

@ -0,0 +1,59 @@
#!/bin/bash
# Check if AWS CLI is installed
if ! command -v aws &> /dev/null
then
echo "AWS CLI is not installed. Installing..."
# Detect system architecture
ARCH=$(uname -m)
if [ "$ARCH" == "x86_64" ]; then
CLI_URL="https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"
elif [ "$ARCH" == "aarch64" ]; then
CLI_URL="https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"
else
echo "Unsupported architecture: $ARCH"
exit 1
fi
# Download and install AWS CLI
sudo curl "$CLI_URL" -o "awscliv2.zip"
sudo unzip awscliv2.zip
sudo ./aws/install --update
sudo rm -rf awscliv2.zip aws
echo "AWS CLI installation completed."
else
echo "AWS CLI is already installed."
aws --version
fi
# Configure AWS CLI with provided credentials
/usr/local/bin/aws configure set aws_access_key_id "__key__"
/usr/local/bin/aws configure set aws_secret_access_key "__secret__"
# Use the provided endpoint in the correct format
ENDPOINT="__endpoint__"
BUCKET="__bucket__"
REGION="__region__"
# Ensure that DEST does not have a trailing slash
SRC="__src__"
DEST="__dest__"
# Upload the file
echo "Uploading __src__ to s3://$BUCKET/$DEST"
upload_output=$(/usr/local/bin/aws s3 cp "$SRC" "s3://$BUCKET/$DEST" --endpoint-url="$ENDPOINT" --region "$REGION" 2>&1)
upload_exit_code=$?
# Log output and exit code
echo "Upload command output: $upload_output"
echo "Upload command exit code: $upload_exit_code"
# Check if the upload was successful
if [ $upload_exit_code -eq 0 ]; then
echo "Upload successful"
else
echo "Upload failed"
exit 1
fi

View File

@ -41,7 +41,7 @@ public function connect(): bool
$isConnected = $connection && $this->login($connection);
if ($isConnected) {
ftp_close($connection);
\App\Facades\FTP::close($connection);
}
return $isConnected;
@ -58,31 +58,36 @@ public function delete(array $paths): void
if ($connection && $this->login($connection)) {
if ($this->storageProvider->credentials['passive']) {
ftp_pasv($connection, true);
\App\Facades\FTP::passive($connection, true);
}
foreach ($paths as $path) {
ftp_delete($connection, $this->storageProvider->credentials['path'].'/'.$path);
\App\Facades\FTP::delete($connection, $this->storageProvider->credentials['path'].'/'.$path);
}
}
ftp_close($connection);
\App\Facades\FTP::close($connection);
}
private function connection(): bool|Connection
{
$credentials = $this->storageProvider->credentials;
if ($credentials['ssl']) {
return ftp_ssl_connect($credentials['host'], $credentials['port'], 5);
}
return ftp_connect($credentials['host'], $credentials['port'], 5);
return \App\Facades\FTP::connect(
$credentials['host'],
$credentials['port'],
$credentials['ssl']
);
}
private function login(Connection $connection): bool
private function login(bool|Connection $connection): bool
{
$credentials = $this->storageProvider->credentials;
return ftp_login($connection, $credentials['username'], $credentials['password']);
return \App\Facades\FTP::login(
$credentials['username'],
$credentials['password'],
$connection
);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\StorageProviders;
use App\Models\Server;
use App\SSH\Storage\S3 as S3Storage;
use App\SSH\Storage\Storage;
use Aws\S3\Exception\S3Exception;
use Illuminate\Support\Facades\Log;
class S3 extends S3AbstractStorageProvider
{
public function validationRules(): array
{
return [
'key' => 'required|string',
'secret' => 'required|string',
'region' => 'required|string',
'bucket' => 'required|string',
'path' => 'required|string',
];
}
public function credentialData(array $input): array
{
return [
'key' => $input['key'],
'secret' => $input['secret'],
'region' => $input['region'],
'bucket' => $input['bucket'],
'path' => $input['path'],
];
}
public function connect(): bool
{
try {
$this->setBucketRegion($this->storageProvider->credentials['region']);
$this->setApiUrl();
$this->buildClientConfig();
$this->getClient()->listBuckets();
return true;
} catch (S3Exception $e) {
Log::error('Failed to connect to S3', ['exception' => $e]);
return false;
}
}
public function ssh(Server $server): Storage
{
return new S3Storage($server, $this->storageProvider);
}
public function delete(array $paths): void {}
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\StorageProviders;
use App\Models\StorageProvider;
use Aws\S3\S3Client;
abstract class S3AbstractStorageProvider extends AbstractStorageProvider implements S3ClientInterface, S3StorageInterface
{
protected ?string $apiUrl = null;
protected ?string $bucketRegion = null;
protected ?S3Client $client = null;
protected StorageProvider $storageProvider;
protected array $clientConfig = [];
public function getApiUrl(): string
{
return $this->apiUrl;
}
public function setApiUrl(?string $region = null): void
{
$this->bucketRegion = $region ?? $this->bucketRegion;
$this->apiUrl = "https://s3.{$this->bucketRegion}.amazonaws.com";
}
public function getBucketRegion(): string
{
return $this->bucketRegion;
}
public function setBucketRegion(string $region): void
{
$this->bucketRegion = $region;
}
public function getClient(): S3Client
{
return new S3Client($this->clientConfig);
}
/**
* Build the configuration array for the S3 client.
* This method can be overridden by child classes to modify the configuration.
*/
public function buildClientConfig(): array
{
$this->clientConfig = [
'credentials' => [
'key' => $this->storageProvider->credentials['key'],
'secret' => $this->storageProvider->credentials['secret'],
],
'region' => $this->getBucketRegion(),
'version' => 'latest',
'endpoint' => $this->getApiUrl(),
];
return $this->clientConfig;
}
/**
* Set or update a configuration parameter for the S3 client.
*/
public function setConfigParam(array $param): void
{
foreach ($param as $key => $value) {
$this->clientConfig[$key] = $value;
}
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\StorageProviders;
use Aws\S3\S3Client;
interface S3ClientInterface
{
public function getClient(): S3Client;
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\StorageProviders;
interface S3StorageInterface
{
public function getApiUrl(): string;
public function setApiUrl(?string $region = null): void;
public function getBucketRegion(): string;
public function setBucketRegion(string $region): void;
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\StorageProviders;
use App\Models\Server;
use App\SSH\Storage\Storage;
use App\SSH\Storage\Wasabi as WasabiStorage;
use Aws\S3\Exception\S3Exception;
use Illuminate\Support\Facades\Log;
class Wasabi extends S3AbstractStorageProvider
{
private const DEFAULT_REGION = 'us-east-1';
public function validationRules(): array
{
return [
'key' => 'required|string',
'secret' => 'required|string',
'region' => 'required|string',
'bucket' => 'required|string',
'path' => 'required|string',
];
}
public function credentialData(array $input): array
{
return [
'key' => $input['key'],
'secret' => $input['secret'],
'region' => $input['region'],
'bucket' => $input['bucket'],
'path' => $input['path'],
];
}
public function connect(): bool
{
try {
$this->setBucketRegion(self::DEFAULT_REGION);
$this->setApiUrl();
$this->buildClientConfig();
$this->getClient()->listBuckets();
return true;
} catch (S3Exception $e) {
Log::error('Failed to connect to S3', ['exception' => $e]);
return false;
}
}
/**
* Build the configuration array for the S3 client.
* This method can be overridden by child classes to modify the configuration.
*/
public function buildClientConfig(): array
{
$this->clientConfig = [
'credentials' => [
'key' => $this->storageProvider->credentials['key'],
'secret' => $this->storageProvider->credentials['secret'],
],
'region' => $this->getBucketRegion(),
'version' => 'latest',
'endpoint' => $this->getApiUrl(),
'use_path_style_endpoint' => true,
];
return $this->clientConfig;
}
public function ssh(Server $server): Storage
{
return new WasabiStorage($server, $this->storageProvider);
}
public function setApiUrl(?string $region = null): void
{
$this->bucketRegion = $region ?? $this->bucketRegion;
$this->apiUrl = "https://s3.{$this->bucketRegion}.wasabisys.com";
}
public function delete(array $paths): void {}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Support\Testing;
use FTP\Connection;
use PHPUnit\Framework\Assert;
class FTPFake
{
protected array $connections = [];
protected array $logins = [];
public function connect(string $host, string $port, bool $ssl = false): bool|Connection
{
$this->connections[] = compact('host', 'port', 'ssl');
return true;
}
public function login(string $username, string $password, bool|Connection $connection): bool
{
$this->logins[] = compact('username', 'password');
return true;
}
public function close(bool|Connection $connection): void
{
//
}
public function passive(bool|Connection $connection, bool $passive): void
{
//
}
public function delete(bool|Connection $connection, string $path): void
{
//
}
public function assertConnected(string $host): void
{
if (! $this->connections) {
Assert::fail('No connections are made');
}
$connected = false;
foreach ($this->connections as $connection) {
if ($connection['host'] === $host) {
$connected = true;
break;
}
}
if (! $connected) {
Assert::fail('The expected host is not connected');
}
Assert::assertTrue(true, $connected);
}
}

View File

@ -17,6 +17,12 @@ class SSHFake extends SSH
protected bool $connectionWillFail = false;
protected string $uploadedLocalPath;
protected string $uploadedRemotePath;
protected string $uploadedContent;
public function __construct(?string $output = null)
{
$this->output = $output;
@ -63,6 +69,9 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
public function upload(string $local, string $remote): void
{
$this->uploadedLocalPath = $local;
$this->uploadedRemotePath = $remote;
$this->uploadedContent = file_get_contents($local);
$this->log = null;
}
@ -105,4 +114,22 @@ public function assertExecutedContains(string $command): void
}
Assert::assertTrue(true, $executed);
}
public function assertFileUploaded(string $toPath, ?string $content = null): void
{
if (! $this->uploadedLocalPath || ! $this->uploadedRemotePath) {
Assert::fail('File is not uploaded');
}
Assert::assertEquals($toPath, $this->uploadedRemotePath);
if ($content) {
Assert::assertEquals($content, $this->uploadedContent);
}
}
public function getUploadedLocalPath(): string
{
return $this->uploadedLocalPath;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Str;
use Illuminate\View\Component;
class Editor extends Component
{
public string $id;
public string $name;
public ?string $value;
public array $options;
public function __construct(
string $name,
?string $value,
public string $lang,
public bool $readonly = false,
public bool $lineNumbers = true,
) {
$this->id = $name.'-'.Str::random(8);
$this->name = $name;
$this->value = json_encode($value ?? '');
$this->options = $this->getOptions();
}
private function getOptions(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'lang' => $this->lang,
'value' => $this->value,
];
}
public function render(): View|Closure|string
{
return view('components.editor');
}
}

View File

@ -427,11 +427,16 @@
\App\Enums\StorageProvider::DROPBOX,
\App\Enums\StorageProvider::FTP,
\App\Enums\StorageProvider::LOCAL,
\App\Enums\StorageProvider::S3,
\App\Enums\StorageProvider::WASABI,
],
'storage_providers_class' => [
\App\Enums\StorageProvider::DROPBOX => \App\StorageProviders\Dropbox::class,
\App\Enums\StorageProvider::FTP => \App\StorageProviders\Ftp::class,
\App\Enums\StorageProvider::FTP => \App\StorageProviders\FTP::class,
\App\Enums\StorageProvider::LOCAL => \App\StorageProviders\Local::class,
\App\Enums\StorageProvider::S3 => \App\StorageProviders\S3::class,
\App\Enums\StorageProvider::WASABI => \App\StorageProviders\Wasabi::class,
],
'ssl_types' => [
@ -445,4 +450,30 @@
30,
90,
],
'tag_colors' => [
'slate',
'gray',
'red',
'orange',
'amber',
'yellow',
'lime',
'green',
'emerald',
'teal',
'cyan',
'sky',
'blue',
'indigo',
'violet',
'purple',
'fuchsia',
'pink',
'rose',
],
'taggable_types' => [
\App\Models\Server::class,
\App\Models\Site::class,
],
];

View File

@ -618,8 +618,8 @@
'images' => [
'ubuntu_18' => '112929540',
'ubuntu_20' => '112929454',
'ubuntu_22' => '129211873',
'ubuntu_24' => '155133621',
'ubuntu_22' => '159651797',
'ubuntu_24' => '160232537',
],
],
'vultr' => [

View File

@ -0,0 +1,23 @@
<?php
namespace Database\Factories;
use App\Models\Tag;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
class TagFactory extends Factory
{
protected $model = Tag::class;
public function definition(): array
{
return [
'project_id' => 1,
'created_at' => Carbon::now(), //
'updated_at' => Carbon::now(),
'name' => $this->faker->randomElement(['production', 'staging', 'development']),
'color' => $this->faker->randomElement(config('core.tag_colors')),
];
}
}

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->string('profile_photo_path', 2048)->nullable();
$table->text('two_factor_secret')->nullable();
$table->text('two_factor_recovery_codes')->nullable();
$table->string('timezone')->default('UTC')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
}

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePasswordResetsTable extends Migration
{
public function up(): void
{
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
public function down(): void
{
Schema::dropIfExists('password_reset_tokens');
}
}

View File

@ -0,0 +1,70 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Get the migration connection name.
*/
public function getConnection(): ?string
{
return config('telescope.storage.database.connection');
}
/**
* Run the migrations.
*/
public function up(): void
{
$schema = Schema::connection($this->getConnection());
$schema->create('telescope_entries', function (Blueprint $table) {
$table->bigIncrements('sequence');
$table->uuid('uuid');
$table->uuid('batch_id');
$table->string('family_hash')->nullable();
$table->boolean('should_display_on_index')->default(true);
$table->string('type', 20);
$table->longText('content');
$table->dateTime('created_at')->nullable();
$table->unique('uuid');
$table->index('batch_id');
$table->index('family_hash');
$table->index('created_at');
$table->index(['type', 'should_display_on_index']);
});
$schema->create('telescope_entries_tags', function (Blueprint $table) {
$table->uuid('entry_uuid');
$table->string('tag');
$table->primary(['entry_uuid', 'tag']);
$table->index('tag');
$table->foreign('entry_uuid')
->references('uuid')
->on('telescope_entries')
->onDelete('cascade');
});
$schema->create('telescope_monitoring', function (Blueprint $table) {
$table->string('tag')->primary();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$schema = Schema::connection($this->getConnection());
$schema->dropIfExists('telescope_entries_tags');
$schema->dropIfExists('telescope_entries');
$schema->dropIfExists('telescope_monitoring');
}
};

View File

@ -0,0 +1,26 @@
<?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('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
public function down(): void
{
Schema::dropIfExists('failed_jobs');
}
};

View File

@ -0,0 +1,27 @@
<?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('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@ -0,0 +1,25 @@
<?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('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity')->index();
});
}
public function down(): void
{
Schema::dropIfExists('sessions');
}
};

View File

@ -0,0 +1,38 @@
<?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('servers', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('name')->index();
$table->string('ssh_user')->nullable();
$table->ipAddress('ip')->index()->nullable();
$table->ipAddress('local_ip')->nullable();
$table->unsignedInteger('provider_id')->nullable();
$table->integer('port')->default(22);
$table->string('os');
$table->string('type');
$table->json('type_data')->nullable();
$table->string('provider');
$table->json('provider_data')->nullable();
$table->longText('authentication')->nullable();
$table->longText('public_key')->nullable();
$table->string('status')->default('installing');
$table->integer('progress')->default(0);
$table->string('progress_step')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('servers');
}
};

View File

@ -0,0 +1,30 @@
<?php
use App\Enums\ServiceStatus;
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('services', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->string('type');
$table->json('type_data')->nullable();
$table->string('name');
$table->string('version');
$table->string('status')->default(ServiceStatus::INSTALLING);
$table->boolean('is_default')->default(1);
$table->string('unit')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('services');
}
};

View File

@ -0,0 +1,26 @@
<?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('jobs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('queue')->index();
$table->longText('payload');
$table->integer('attempts');
$table->integer('reserved_at')->nullable();
$table->integer('available_at');
$table->integer('created_at');
});
}
public function down(): void
{
Schema::dropIfExists('jobs');
}
};

View File

@ -0,0 +1,26 @@
<?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('server_logs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->unsignedBigInteger('site_id')->nullable();
$table->string('type');
$table->string('name');
$table->string('disk');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('server_logs');
}
};

View File

@ -0,0 +1,35 @@
<?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('sites', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id')->index();
$table->string('type');
$table->json('type_data')->nullable();
$table->string('domain')->index();
$table->json('aliases')->nullable();
$table->string('web_directory')->nullable();
$table->string('path');
$table->string('php_version')->nullable();
$table->string('source_control')->nullable();
$table->string('repository')->nullable();
$table->string('branch')->nullable();
$table->integer('port')->nullable();
$table->string('status')->default('installing');
$table->integer('progress')->default(0)->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('sites');
}
};

View File

@ -0,0 +1,24 @@
<?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('source_controls', function (Blueprint $table) {
$table->id();
$table->string('provider');
$table->json('provider_data')->nullable();
$table->longText('access_token')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('source_controls');
}
};

View File

@ -0,0 +1,27 @@
<?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('deployments', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('site_id');
$table->unsignedBigInteger('deployment_script_id');
$table->unsignedInteger('log_id')->nullable();
$table->json('commit_data')->nullable();
$table->string('commit_id')->nullable();
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('deployments');
}
};

View File

@ -0,0 +1,25 @@
<?php
use App\Enums\DatabaseStatus;
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('databases', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->string('name');
$table->string('status')->default(DatabaseStatus::CREATING);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('databases');
}
};

View File

@ -0,0 +1,28 @@
<?php
use App\Enums\DatabaseUserStatus;
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('database_users', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->string('username');
$table->longText('password')->nullable();
$table->json('databases')->nullable();
$table->string('host')->default('localhost');
$table->string('status')->default(DatabaseUserStatus::CREATING);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('database_users');
}
};

View File

@ -0,0 +1,29 @@
<?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('firewall_rules', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->string('type');
$table->string('protocol');
$table->integer('port');
$table->ipAddress('source')->default('0.0.0.0');
$table->string('mask')->nullable();
$table->text('note')->nullable();
$table->string('status')->default('creating');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('firewall_rules');
}
};

View File

@ -0,0 +1,27 @@
<?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('cron_jobs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->text('command');
$table->string('user');
$table->string('frequency');
$table->boolean('hidden')->default(0);
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('cron_jobs');
}
};

View File

@ -0,0 +1,24 @@
<?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('deployment_scripts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('site_id');
$table->string('name')->nullable();
$table->longText('content')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('deployment_scripts');
}
};

View File

@ -0,0 +1,29 @@
<?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('ssls', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('site_id');
$table->string('type')->default('letsencrypt');
$table->string('domains')->nullable();
$table->longText('certificate')->nullable();
$table->longText('pk')->nullable();
$table->longText('ca')->nullable();
$table->timestamp('expires_at');
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('ssls');
}
};

View File

@ -0,0 +1,26 @@
<?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('redirects', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('site_id');
$table->integer('mode');
$table->text('from');
$table->text('to');
$table->string('status')->default('creating');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('redirects');
}
};

View File

@ -0,0 +1,31 @@
<?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('queues', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('server_id')->nullable();
$table->unsignedBigInteger('site_id');
$table->text('command');
$table->string('user');
$table->boolean('auto_start')->default(1);
$table->boolean('auto_restart')->default(1);
$table->integer('numprocs')->default(8);
$table->boolean('redirect_stderr')->default(1);
$table->string('stdout_logfile')->nullable();
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('queues');
}
};

View File

@ -0,0 +1,24 @@
<?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('ssh_keys', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('name');
$table->longText('public_key');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('ssh_keys');
}
};

View File

@ -0,0 +1,24 @@
<?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('server_ssh_keys', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->unsignedBigInteger('ssh_key_id');
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('server_ssh_keys');
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('git_hooks', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('site_id');
$table->unsignedBigInteger('source_control_id');
$table->string('secret')->unique()->index();
$table->json('events');
$table->json('actions');
$table->string('hook_id')->nullable();
$table->json('hook_response')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('git_hooks');
}
};

View File

@ -0,0 +1,26 @@
<?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('server_providers', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('profile')->nullable();
$table->string('provider');
$table->longText('credentials');
$table->boolean('connected')->default(1);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('server_providers');
}
};

View File

@ -0,0 +1,24 @@
<?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('scripts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('name');
$table->longText('content');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('scripts');
}
};

View File

@ -0,0 +1,25 @@
<?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('script_executions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('script_id');
$table->unsignedBigInteger('server_id');
$table->string('user')->nullable();
$table->timestamp('finished_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('script_executions');
}
};

View File

@ -0,0 +1,26 @@
<?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('notification_channels', function (Blueprint $table) {
$table->id();
$table->string('provider');
$table->string('label');
$table->json('data')->nullable();
$table->boolean('connected')->default(false);
$table->boolean('is_default')->default(false);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('notification_channels');
}
};

View File

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStorageProvidersTable extends Migration
{
public function up(): void
{
Schema::create('storage_providers', function (Blueprint $table) {
$table->id();
$table->string('provider');
$table->string('label')->nullable();
$table->string('token', 1000)->nullable();
$table->string('refresh_token', 1000)->nullable();
$table->boolean('connected')->default(1);
$table->timestamp('token_expires_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('storage_providers');
}
}

View File

@ -0,0 +1,29 @@
<?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('backups', function (Blueprint $table) {
$table->id();
$table->string('type');
$table->string('name');
$table->unsignedBigInteger('server_id');
$table->unsignedBigInteger('storage_id');
$table->unsignedBigInteger('database_id')->nullable();
$table->string('interval');
$table->bigInteger('keep_backups');
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('backups');
}
};

View File

@ -0,0 +1,26 @@
<?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('backup_files', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('backup_id');
$table->string('name');
$table->bigInteger('size')->nullable();
$table->string('status');
$table->timestamp('restored_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('backup_files');
}
};

View File

@ -0,0 +1,19 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
if (DB::getDriverName() === 'mysql') {
DB::statement('ALTER TABLE firewall_rules MODIFY mask varchar(10) null');
}
}
public function down(): void
{
//
}
};

View File

@ -0,0 +1,22 @@
<?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::table('sites', function (Blueprint $table) {
$table->longText('ssh_key')->nullable()->after('repository');
});
}
public function down(): void
{
Schema::table('sites', function (Blueprint $table) {
$table->dropColumn('ssh_key');
});
}
};

View File

@ -0,0 +1,22 @@
<?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::table('source_controls', function (Blueprint $table) {
$table->string('url')->nullable()->after('provider');
});
}
public function down(): void
{
Schema::table('source_controls', function (Blueprint $table) {
$table->dropColumn('url');
});
}
};

View File

@ -0,0 +1,22 @@
<?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::table('source_controls', function (Blueprint $table) {
$table->string('profile')->after('provider')->nullable();
});
}
public function down(): void
{
Schema::table('source_controls', function (Blueprint $table) {
$table->dropColumn('profile');
});
}
};

View File

@ -0,0 +1,22 @@
<?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::table('sites', function (Blueprint $table) {
$table->unsignedBigInteger('source_control_id')->nullable()->after('source_control');
});
}
public function down(): void
{
Schema::table('sites', function (Blueprint $table) {
$table->dropColumn('source_control_id');
});
}
};

View 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::table('storage_providers', function (Blueprint $table) {
$table->unsignedBigInteger('user_id')->after('id');
$table->string('profile')->after('user_id');
$table->longText('credentials')->nullable()->after('provider');
});
Schema::table('storage_providers', function (Blueprint $table) {
$table->dropColumn(['token', 'refresh_token', 'token_expires_at', 'label', 'connected']);
});
}
public function down(): void
{
Schema::table('storage_providers', function (Blueprint $table) {
$table->string('token')->nullable();
$table->string('refresh_token')->nullable();
$table->string('token_expires_at')->nullable();
$table->string('label')->nullable();
});
Schema::table('storage_providers', function (Blueprint $table) {
$table->dropColumn(['user_id', 'profile', 'credentials']);
});
}
};

View File

@ -0,0 +1,22 @@
<?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::table('backups', function (Blueprint $table) {
$table->dropColumn('name');
});
}
public function down(): void
{
Schema::table('backups', function (Blueprint $table) {
$table->string('name')->nullable();
});
}
};

View File

@ -0,0 +1,22 @@
<?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::table('backup_files', function (Blueprint $table) {
$table->string('restored_to')->after('status')->nullable();
});
}
public function down(): void
{
Schema::table('backup_files', function (Blueprint $table) {
$table->dropColumn('restored_to');
});
}
};

View File

@ -0,0 +1,32 @@
<?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('users', function (Blueprint $table) {
$table->timestamp('two_factor_confirmed_at')
->after('two_factor_recovery_codes')
->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn([
'two_factor_confirmed_at',
]);
});
}
};

View File

@ -0,0 +1,23 @@
<?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('projects', function (Blueprint $table) {
$table->id();
$table->bigInteger('user_id');
$table->string('name');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('projects');
}
};

View File

@ -0,0 +1,22 @@
<?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::table('servers', function (Blueprint $table) {
$table->unsignedBigInteger('project_id')->nullable()->after('id');
});
}
public function down(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->dropColumn('project_id');
});
}
};

View File

@ -0,0 +1,27 @@
<?php
use App\Models\User;
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::table('users', function (Blueprint $table) {
$table->unsignedBigInteger('current_project_id')->nullable()->after('timezone');
});
User::query()->each(function (User $user) {
$project = $user->createDefaultProject();
$user->servers()->update(['project_id' => $project->id]);
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('current_project_id');
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('server_logs', function (Blueprint $table) {
$table->boolean('is_remote')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('server_logs', function (Blueprint $table) {
$table->dropColumn('is_remote');
});
}
};

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('metrics', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->decimal('load', 5, 2);
$table->decimal('memory_total', 15, 0);
$table->decimal('memory_used', 15, 0);
$table->decimal('memory_free', 15, 0);
$table->decimal('disk_total', 15, 0);
$table->decimal('disk_used', 15, 0);
$table->decimal('disk_free', 15, 0);
$table->timestamps();
$table->index(['server_id', 'created_at']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('metrics');
}
};

View File

@ -0,0 +1,31 @@
<?php
use App\Enums\UserRole;
use App\Models\User;
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('users', function (Blueprint $table) {
$table->string('role')->default(UserRole::USER);
});
User::query()->update(['role' => UserRole::ADMIN]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
};

View File

@ -0,0 +1,38 @@
<?php
use App\Models\Project;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('user_project', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('project_id');
$table->timestamps();
});
Project::all()->each(function (Project $project) {
$project->users()->attach($project->user_id);
});
User::all()->each(function (User $user) {
$user->current_project_id = $user->projects()->first()?->id;
$user->save();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('user_project');
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('projects', function (Blueprint $table) {
$table->dropColumn('user_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('projects', function (Blueprint $table) {
$table->bigInteger('user_id')->nullable();
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('source_controls', function (Blueprint $table) {
$table->unsignedBigInteger('project_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('source_controls', function (Blueprint $table) {
$table->dropColumn('project_id');
});
}
};

View File

@ -0,0 +1,24 @@
<?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::table('servers', function (Blueprint $table) {
$table->integer('updates')->default(0);
$table->timestamp('last_update_check')->nullable();
});
}
public function down(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->dropColumn('updates');
$table->dropColumn('last_update_check');
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::dropIfExists('script_executions');
Schema::create('script_executions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('script_id');
$table->unsignedBigInteger('server_log_id')->nullable();
$table->string('user');
$table->json('variables')->nullable();
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('script_executions');
}
};

View File

@ -0,0 +1,24 @@
<?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('tags', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('project_id');
$table->string('name');
$table->string('color');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('tags');
}
};

View File

@ -0,0 +1,24 @@
<?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('taggables', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tag_id');
$table->unsignedBigInteger('taggable_id');
$table->string('taggable_type');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('taggables');
}
};

View File

@ -1,108 +0,0 @@
CREATE TABLE IF NOT EXISTS "migrations" ("id" integer primary key autoincrement not null, "migration" varchar not null, "batch" integer not null);
CREATE TABLE IF NOT EXISTS "users" ("id" integer primary key autoincrement not null, "name" varchar not null, "email" varchar not null, "password" varchar not null, "remember_token" varchar, "profile_photo_path" varchar, "two_factor_secret" text, "two_factor_recovery_codes" text, "timezone" varchar default 'UTC', "created_at" datetime, "updated_at" datetime, "two_factor_confirmed_at" datetime, "current_project_id" integer, "role" varchar not null default 'user');
CREATE UNIQUE INDEX "users_email_unique" on "users" ("email");
CREATE TABLE IF NOT EXISTS "password_reset_tokens" ("email" varchar not null, "token" varchar not null, "created_at" datetime);
CREATE INDEX "password_reset_tokens_email_index" on "password_reset_tokens" ("email");
CREATE TABLE IF NOT EXISTS "telescope_entries" ("sequence" integer primary key autoincrement not null, "uuid" varchar not null, "batch_id" varchar not null, "family_hash" varchar, "should_display_on_index" tinyint(1) not null default '1', "type" varchar not null, "content" text not null, "created_at" datetime);
CREATE UNIQUE INDEX "telescope_entries_uuid_unique" on "telescope_entries" ("uuid");
CREATE INDEX "telescope_entries_batch_id_index" on "telescope_entries" ("batch_id");
CREATE INDEX "telescope_entries_family_hash_index" on "telescope_entries" ("family_hash");
CREATE INDEX "telescope_entries_created_at_index" on "telescope_entries" ("created_at");
CREATE INDEX "telescope_entries_type_should_display_on_index_index" on "telescope_entries" ("type", "should_display_on_index");
CREATE TABLE IF NOT EXISTS "telescope_entries_tags" ("entry_uuid" varchar not null, "tag" varchar not null, foreign key("entry_uuid") references "telescope_entries"("uuid") on delete cascade, primary key ("entry_uuid", "tag"));
CREATE INDEX "telescope_entries_tags_tag_index" on "telescope_entries_tags" ("tag");
CREATE TABLE IF NOT EXISTS "telescope_monitoring" ("tag" varchar not null, primary key ("tag"));
CREATE TABLE IF NOT EXISTS "failed_jobs" ("id" integer primary key autoincrement not null, "uuid" varchar not null, "connection" text not null, "queue" text not null, "payload" text not null, "exception" text not null, "failed_at" datetime not null default CURRENT_TIMESTAMP);
CREATE UNIQUE INDEX "failed_jobs_uuid_unique" on "failed_jobs" ("uuid");
CREATE TABLE IF NOT EXISTS "personal_access_tokens" ("id" integer primary key autoincrement not null, "tokenable_type" varchar not null, "tokenable_id" integer not null, "name" varchar not null, "token" varchar not null, "abilities" text, "last_used_at" datetime, "expires_at" datetime, "created_at" datetime, "updated_at" datetime);
CREATE INDEX "personal_access_tokens_tokenable_type_tokenable_id_index" on "personal_access_tokens" ("tokenable_type", "tokenable_id");
CREATE UNIQUE INDEX "personal_access_tokens_token_unique" on "personal_access_tokens" ("token");
CREATE TABLE IF NOT EXISTS "sessions" ("id" varchar not null, "user_id" integer, "ip_address" varchar, "user_agent" text, "payload" text not null, "last_activity" integer not null, primary key ("id"));
CREATE INDEX "sessions_user_id_index" on "sessions" ("user_id");
CREATE INDEX "sessions_last_activity_index" on "sessions" ("last_activity");
CREATE TABLE IF NOT EXISTS "servers" ("id" integer primary key autoincrement not null, "user_id" integer not null, "name" varchar not null, "ssh_user" varchar, "ip" varchar, "local_ip" varchar, "provider_id" integer, "port" integer not null default '22', "os" varchar not null, "type" varchar not null, "type_data" text, "provider" varchar not null, "provider_data" text, "authentication" text, "public_key" text, "status" varchar not null default 'installing', "progress" integer not null default '0', "progress_step" varchar, "created_at" datetime, "updated_at" datetime, "project_id" integer, "updates" integer not null default '0', "last_update_check" datetime);
CREATE INDEX "servers_name_index" on "servers" ("name");
CREATE INDEX "servers_ip_index" on "servers" ("ip");
CREATE TABLE IF NOT EXISTS "services" ("id" integer primary key autoincrement not null, "server_id" integer not null, "type" varchar not null, "type_data" text, "name" varchar not null, "version" varchar not null, "status" varchar not null default 'installing', "is_default" tinyint(1) not null default '1', "unit" varchar, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "jobs" ("id" integer primary key autoincrement not null, "queue" varchar not null, "payload" text not null, "attempts" integer not null, "reserved_at" integer, "available_at" integer not null, "created_at" integer not null);
CREATE INDEX "jobs_queue_index" on "jobs" ("queue");
CREATE TABLE IF NOT EXISTS "server_logs" ("id" integer primary key autoincrement not null, "server_id" integer not null, "site_id" integer, "type" varchar not null, "name" varchar not null, "disk" varchar not null, "created_at" datetime, "updated_at" datetime, "is_remote" tinyint(1) not null default '0');
CREATE TABLE IF NOT EXISTS "sites" ("id" integer primary key autoincrement not null, "server_id" integer not null, "type" varchar not null, "type_data" text, "domain" varchar not null, "aliases" text, "web_directory" varchar, "path" varchar not null, "php_version" varchar, "source_control" varchar, "repository" varchar, "branch" varchar, "port" integer, "status" varchar not null default 'installing', "progress" integer default '0', "created_at" datetime, "updated_at" datetime, "ssh_key" text, "source_control_id" integer);
CREATE INDEX "sites_server_id_index" on "sites" ("server_id");
CREATE INDEX "sites_domain_index" on "sites" ("domain");
CREATE TABLE IF NOT EXISTS "source_controls" ("id" integer primary key autoincrement not null, "provider" varchar not null, "provider_data" text, "access_token" text, "created_at" datetime, "updated_at" datetime, "url" varchar, "profile" varchar, "project_id" integer);
CREATE TABLE IF NOT EXISTS "deployments" ("id" integer primary key autoincrement not null, "site_id" integer not null, "deployment_script_id" integer not null, "log_id" integer, "commit_data" text, "commit_id" varchar, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "databases" ("id" integer primary key autoincrement not null, "server_id" integer not null, "name" varchar not null, "status" varchar not null default 'creating', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "database_users" ("id" integer primary key autoincrement not null, "server_id" integer not null, "username" varchar not null, "password" text, "databases" text, "host" varchar not null default 'localhost', "status" varchar not null default 'creating', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "firewall_rules" ("id" integer primary key autoincrement not null, "server_id" integer not null, "type" varchar not null, "protocol" varchar not null, "port" integer not null, "source" varchar not null default '0.0.0.0', "mask" varchar, "note" text, "status" varchar not null default 'creating', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "cron_jobs" ("id" integer primary key autoincrement not null, "server_id" integer not null, "command" text not null, "user" varchar not null, "frequency" varchar not null, "hidden" tinyint(1) not null default '0', "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "deployment_scripts" ("id" integer primary key autoincrement not null, "site_id" integer not null, "name" varchar, "content" text, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "ssls" ("id" integer primary key autoincrement not null, "site_id" integer not null, "type" varchar not null default 'letsencrypt', "domains" varchar, "certificate" text, "pk" text, "ca" text, "expires_at" datetime not null, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "redirects" ("id" integer primary key autoincrement not null, "site_id" integer not null, "mode" integer not null, "from" text not null, "to" text not null, "status" varchar not null default 'creating', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "queues" ("id" integer primary key autoincrement not null, "server_id" integer, "site_id" integer not null, "command" text not null, "user" varchar not null, "auto_start" tinyint(1) not null default '1', "auto_restart" tinyint(1) not null default '1', "numprocs" integer not null default '8', "redirect_stderr" tinyint(1) not null default '1', "stdout_logfile" varchar, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "ssh_keys" ("id" integer primary key autoincrement not null, "user_id" integer not null, "name" varchar not null, "public_key" text not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "server_ssh_keys" ("id" integer primary key autoincrement not null, "server_id" integer not null, "ssh_key_id" integer not null, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "git_hooks" ("id" integer primary key autoincrement not null, "site_id" integer not null, "source_control_id" integer not null, "secret" varchar not null, "events" text not null, "actions" text not null, "hook_id" varchar, "hook_response" text, "created_at" datetime, "updated_at" datetime);
CREATE UNIQUE INDEX "git_hooks_secret_unique" on "git_hooks" ("secret");
CREATE TABLE IF NOT EXISTS "server_providers" ("id" integer primary key autoincrement not null, "user_id" integer not null, "profile" varchar, "provider" varchar not null, "credentials" text not null, "connected" tinyint(1) not null default '1', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "scripts" ("id" integer primary key autoincrement not null, "user_id" integer not null, "name" varchar not null, "content" text not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "notification_channels" ("id" integer primary key autoincrement not null, "provider" varchar not null, "label" varchar not null, "data" text, "connected" tinyint(1) not null default '0', "is_default" tinyint(1) not null default '0', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "storage_providers" ("id" integer primary key autoincrement not null, "provider" varchar not null, "created_at" datetime, "updated_at" datetime, "user_id" integer not null, "profile" varchar not null, "credentials" text);
CREATE TABLE IF NOT EXISTS "backups" ("id" integer primary key autoincrement not null, "type" varchar not null, "server_id" integer not null, "storage_id" integer not null, "database_id" integer, "interval" varchar not null, "keep_backups" integer not null, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "backup_files" ("id" integer primary key autoincrement not null, "backup_id" integer not null, "name" varchar not null, "size" integer, "status" varchar not null, "restored_at" datetime, "created_at" datetime, "updated_at" datetime, "restored_to" varchar);
CREATE TABLE IF NOT EXISTS "projects" ("id" integer primary key autoincrement not null, "name" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "metrics" ("id" integer primary key autoincrement not null, "server_id" integer not null, "load" numeric not null, "memory_total" numeric not null, "memory_used" numeric not null, "memory_free" numeric not null, "disk_total" numeric not null, "disk_used" numeric not null, "disk_free" numeric not null, "created_at" datetime, "updated_at" datetime);
CREATE INDEX "metrics_server_id_created_at_index" on "metrics" ("server_id", "created_at");
CREATE TABLE IF NOT EXISTS "user_project" ("id" integer primary key autoincrement not null, "user_id" integer not null, "project_id" integer not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "script_executions" ("id" integer primary key autoincrement not null, "script_id" integer not null, "server_log_id" integer, "user" varchar not null, "variables" text, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
INSERT INTO migrations VALUES(147,'2014_10_12_000000_create_users_table',1);
INSERT INTO migrations VALUES(148,'2014_10_12_100000_create_password_resets_table',1);
INSERT INTO migrations VALUES(149,'2018_08_08_100000_create_telescope_entries_table',1);
INSERT INTO migrations VALUES(150,'2019_08_19_000000_create_failed_jobs_table',1);
INSERT INTO migrations VALUES(151,'2019_12_14_000001_create_personal_access_tokens_table',1);
INSERT INTO migrations VALUES(152,'2021_06_23_192743_create_sessions_table',1);
INSERT INTO migrations VALUES(153,'2021_06_23_211827_create_servers_table',1);
INSERT INTO migrations VALUES(154,'2021_06_23_214143_create_services_table',1);
INSERT INTO migrations VALUES(155,'2021_06_25_102220_create_jobs_table',1);
INSERT INTO migrations VALUES(156,'2021_06_25_124831_create_server_logs_table',1);
INSERT INTO migrations VALUES(157,'2021_06_26_211903_create_sites_table',1);
INSERT INTO migrations VALUES(158,'2021_06_28_085814_create_source_controls_table',1);
INSERT INTO migrations VALUES(159,'2021_07_02_065815_create_deployments_table',1);
INSERT INTO migrations VALUES(160,'2021_07_03_133319_create_databases_table',1);
INSERT INTO migrations VALUES(161,'2021_07_03_133327_create_database_users_table',1);
INSERT INTO migrations VALUES(162,'2021_07_15_090830_create_firewall_rules_table',1);
INSERT INTO migrations VALUES(163,'2021_07_30_204454_create_cron_jobs_table',1);
INSERT INTO migrations VALUES(164,'2021_08_13_213657_create_deployment_scripts_table',1);
INSERT INTO migrations VALUES(165,'2021_08_14_165326_create_ssls_table',1);
INSERT INTO migrations VALUES(166,'2021_08_26_055643_create_redirects_table',1);
INSERT INTO migrations VALUES(167,'2021_08_27_064512_create_queues_table',1);
INSERT INTO migrations VALUES(168,'2021_08_29_210204_create_ssh_keys_table',1);
INSERT INTO migrations VALUES(169,'2021_08_30_174511_create_server_ssh_keys_table',1);
INSERT INTO migrations VALUES(170,'2021_11_12_093030_create_git_hooks_table',1);
INSERT INTO migrations VALUES(171,'2021_11_14_190808_create_server_providers_table',1);
INSERT INTO migrations VALUES(172,'2021_12_09_062430_create_scripts_table',1);
INSERT INTO migrations VALUES(173,'2021_12_10_204458_create_script_executions_table',1);
INSERT INTO migrations VALUES(174,'2021_12_24_151835_create_notification_channels_table',1);
INSERT INTO migrations VALUES(175,'2022_01_29_183900_create_storage_providers_table',1);
INSERT INTO migrations VALUES(176,'2022_02_11_085718_create_backups_table',1);
INSERT INTO migrations VALUES(177,'2022_02_11_085815_create_backup_files_table',1);
INSERT INTO migrations VALUES(178,'2023_07_21_210213_update_firewall_rules_table',1);
INSERT INTO migrations VALUES(179,'2023_07_23_143530_add_ssh_key_field_to_sites_table',1);
INSERT INTO migrations VALUES(180,'2023_07_30_163805_add_url_to_source_controls_table',1);
INSERT INTO migrations VALUES(181,'2023_07_30_200348_add_profile_to_source_controls_table',1);
INSERT INTO migrations VALUES(182,'2023_07_30_205328_add_source_control_id_to_sites_table',1);
INSERT INTO migrations VALUES(183,'2023_08_13_095440_update_storage_providers_table',1);
INSERT INTO migrations VALUES(184,'2023_08_17_231824_update_backups_table',1);
INSERT INTO migrations VALUES(185,'2023_08_25_183201_update_backup_files_table',1);
INSERT INTO migrations VALUES(186,'2023_09_10_185414_add_two_factor_fields_to_users_table',1);
INSERT INTO migrations VALUES(187,'2023_10_01_120250_create_projects_table',1);
INSERT INTO migrations VALUES(188,'2024_01_01_232932_update_servers_table',1);
INSERT INTO migrations VALUES(189,'2024_01_01_235900_update_users_table',1);
INSERT INTO migrations VALUES(190,'2024_04_07_204001_add_is_remote_to_server_logs_table',1);
INSERT INTO migrations VALUES(191,'2024_04_08_212940_create_metrics_table',1);
INSERT INTO migrations VALUES(192,'2024_04_24_213204_add_role_to_users_table',1);
INSERT INTO migrations VALUES(193,'2024_04_26_122230_create_user_project_table',1);
INSERT INTO migrations VALUES(194,'2024_04_26_123326_drop_user_id_from_projects_table',1);
INSERT INTO migrations VALUES(195,'2024_05_07_184201_add_project_id_to_source_controls_table',2);
INSERT INTO migrations VALUES(197,'2024_05_10_212155_add_updates_field_to_servers_table',3);
INSERT INTO migrations VALUES(198,'2024_06_06_093350_create_script_executions_table',4);

View File

@ -8,9 +8,12 @@
use App\Models\Site;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Foundation\Testing\WithFaker;
class DatabaseSeeder extends Seeder
{
use WithFaker;
/**
* Seed the application's database.
*/
@ -20,6 +23,12 @@ public function run(): void
'name' => 'Test User',
'email' => 'user@example.com',
]);
$this->createResources($user);
}
private function createResources(User $user): void
{
$server = Server::factory()->create([
'user_id' => $user->id,
'project_id' => $user->currentProject->id,

16
package-lock.json generated
View File

@ -4,13 +4,13 @@
"requires": true,
"packages": {
"": {
"name": "vito",
"devDependencies": {
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.9",
"alpinejs": "^3.4.2",
"apexcharts": "^3.44.2",
"autoprefixer": "^10.4.2",
"brace": "^0.11.1",
"flowbite": "^2.3.0",
"flowbite-datepicker": "^1.2.6",
"htmx.org": "^1.9.10",
@ -685,6 +685,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/brace": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz",
"integrity": "sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q==",
"dev": true
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@ -1277,12 +1283,12 @@
}
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": {
"braces": "^3.0.2",
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {

View File

@ -10,8 +10,11 @@
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.9",
"alpinejs": "^3.4.2",
"apexcharts": "^3.44.2",
"autoprefixer": "^10.4.2",
"brace": "^0.11.1",
"flowbite": "^2.3.0",
"flowbite-datepicker": "^1.2.6",
"htmx.org": "^1.9.10",
"laravel-echo": "^1.15.0",
"laravel-vite-plugin": "^0.7.2",
@ -21,8 +24,6 @@
"prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "^3.1.0",
"tippy.js": "^6.3.7",
"vite": "^4.5.3",
"apexcharts": "^3.44.2",
"flowbite-datepicker": "^1.2.6"
"vite": "^4.5.3"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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