diff --git a/.github/workflows/development-build.yml b/.github/workflows/development-build.yml index 51415444f..1fa47a930 100644 --- a/.github/workflows/development-build.yml +++ b/.github/workflows/development-build.yml @@ -52,7 +52,7 @@ jobs: push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 merge-manifest: - runs-on: [self-hosted, x64] + runs-on: ubuntu-latest permissions: contents: read packages: write diff --git a/.github/workflows/production-build.yml b/.github/workflows/production-build.yml index 559145269..f6219767b 100644 --- a/.github/workflows/production-build.yml +++ b/.github/workflows/production-build.yml @@ -10,7 +10,7 @@ env: jobs: amd64: - runs-on: ubuntu-latest + runs-on: [self-hosted, x64] steps: - uses: actions/checkout@v3 - name: Login to ghcr.io diff --git a/app/Actions/CoolifyTask/RunRemoteProcess.php b/app/Actions/CoolifyTask/RunRemoteProcess.php index e213f57f1..842bdd2ac 100644 --- a/app/Actions/CoolifyTask/RunRemoteProcess.php +++ b/app/Actions/CoolifyTask/RunRemoteProcess.php @@ -73,7 +73,7 @@ class RunRemoteProcess $this->time_start = hrtime(true); $status = ProcessStatus::IN_PROGRESS; - $processResult = processWithEnv()->forever()->run($this->getCommand(), $this->handleOutput(...)); + $processResult = Process::forever()->run($this->getCommand(), $this->handleOutput(...)); if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) { $status = ProcessStatus::ERROR; diff --git a/app/Actions/Proxy/CheckConfigurationSync.php b/app/Actions/Proxy/CheckConfigurationSync.php index b6dcb1069..c1a261796 100644 --- a/app/Actions/Proxy/CheckConfigurationSync.php +++ b/app/Actions/Proxy/CheckConfigurationSync.php @@ -16,10 +16,7 @@ class CheckConfigurationSync if ($reset || is_null($proxy_configuration)) { $proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value; - resolve(SaveConfigurationSync::class)($server, $proxy_configuration); - return $proxy_configuration; } - return $proxy_configuration; } } diff --git a/app/Actions/Proxy/SaveConfigurationSync.php b/app/Actions/Proxy/SaveConfigurationSync.php index 8be3ca9db..d58dad0c7 100644 --- a/app/Actions/Proxy/SaveConfigurationSync.php +++ b/app/Actions/Proxy/SaveConfigurationSync.php @@ -7,11 +7,12 @@ use Illuminate\Support\Str; class SaveConfigurationSync { - public function __invoke(Server $server, string $configuration) + public function __invoke(Server $server) { try { + $proxy_settings = resolve(CheckConfigurationSync::class)($server, true); $proxy_path = get_proxy_path(); - $docker_compose_yml_base64 = base64_encode($configuration); + $docker_compose_yml_base64 = base64_encode($proxy_settings); $server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; $server->save(); diff --git a/app/Console/Commands/Emails.php b/app/Console/Commands/Emails.php index 67c50675f..7324ad694 100644 --- a/app/Console/Commands/Emails.php +++ b/app/Console/Commands/Emails.php @@ -24,7 +24,7 @@ use Illuminate\Console\Command; use Illuminate\Mail\Message; use Illuminate\Notifications\Messages\MailMessage; use Mail; -use Str; +use Illuminate\Support\Str; use function Laravel\Prompts\confirm; use function Laravel\Prompts\select; @@ -62,7 +62,7 @@ class Emails extends Command 'application-status-changed' => 'Application - Status Changed', 'backup-success' => 'Database - Backup Success', 'backup-failed' => 'Database - Backup Failed', - 'invitation-link' => 'Invitation Link', + // 'invitation-link' => 'Invitation Link', 'waitlist-invitation-link' => 'Waitlist Invitation Link', 'waitlist-confirmation' => 'Waitlist Confirmation', 'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription', @@ -141,20 +141,20 @@ class Emails extends Command $this->mail = (new BackupSuccess($backup, $db))->toMail(); $this->sendEmail(); break; - case 'invitation-link': - $user = User::all()->first(); - $invitation = TeamInvitation::whereEmail($user->email)->first(); - if (!$invitation) { - $invitation = TeamInvitation::create([ - 'uuid' => Str::uuid(), - 'email' => $user->email, - 'team_id' => 1, - 'link' => 'http://example.com', - ]); - } - $this->mail = (new InvitationLink($user))->toMail(); - $this->sendEmail(); - break; + // case 'invitation-link': + // $user = User::all()->first(); + // $invitation = TeamInvitation::whereEmail($user->email)->first(); + // if (!$invitation) { + // $invitation = TeamInvitation::create([ + // 'uuid' => Str::uuid(), + // 'email' => $user->email, + // 'team_id' => 1, + // 'link' => 'http://example.com', + // ]); + // } + // $this->mail = (new InvitationLink($user))->toMail(); + // $this->sendEmail(); + // break; case 'waitlist-invitation-link': $this->mail = new MailMessage(); $this->mail->view('emails.waitlist-invitation', [ diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index d15107c19..368f76f8f 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -21,7 +21,7 @@ class Kernel extends ConsoleKernel if (isDev()) { // $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer(); // $schedule->command('horizon:snapshot')->everyMinute(); - // $schedule->job(new CleanupInstanceStuffsJob)->everyMinute(); + $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); // $schedule->job(new CheckResaleLicenseJob)->hourly(); // $schedule->job(new DockerCleanupJob)->everyOddHour(); // $this->instance_auto_update($schedule); @@ -29,7 +29,7 @@ class Kernel extends ConsoleKernel $this->check_resources($schedule); } else { $schedule->command('horizon:snapshot')->everyFiveMinutes(); - $schedule->job(new CleanupInstanceStuffsJob)->everyTenMinutes()->onOneServer(); + $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer(); $schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer(); $this->instance_auto_update($schedule); diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 913879c6b..0794d2507 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -3,21 +3,18 @@ namespace App\Http\Controllers; use App\Models\InstanceSettings; -use App\Models\Project; use App\Models\S3Storage; use App\Models\StandalonePostgresql; use App\Models\TeamInvitation; use App\Models\User; -use Auth; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Routing\Controller as BaseController; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Http; +use Illuminate\Support\Str; use Throwable; -use Str; - class Controller extends BaseController { @@ -35,8 +32,15 @@ class Controller extends BaseController return redirect()->route('login'); } if (Hash::check($password, $user->password)) { + $invitation = TeamInvitation::whereEmail($email); + if ($invitation->exists()) { + $team = $invitation->first()->team; + $user->teams()->attach($team->id, ['role' => $invitation->first()->role]); + $invitation->delete(); + } else { + $team = $user->teams()->first(); + } Auth::login($user); - $team = $user->teams()->first(); session(['currentTeam' => $team]); return redirect()->route('dashboard'); } @@ -137,24 +141,20 @@ class Controller extends BaseController try { $invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail(); $user = User::whereEmail($invitation->email)->firstOrFail(); - if (is_null(auth()->user())) { - return redirect()->route('login'); - } if (auth()->user()->id !== $user->id) { abort(401); } - - $createdAt = $invitation->created_at; - $diff = $createdAt->diffInMinutes(now()); - if ($diff <= config('constants.invitation.link.expiration')) { + $invitationValid = $invitation->isValid(); + if ($invitationValid) { $user->teams()->attach($invitation->team->id, ['role' => $invitation->role]); + refreshSession($invitation->team); $invitation->delete(); return redirect()->route('team.index'); } else { - $invitation->delete(); abort(401); } } catch (Throwable $e) { + ray($e->getMessage()); throw $e; } } diff --git a/app/Http/Livewire/Help.php b/app/Http/Livewire/Help.php index d467a11f9..79d7db1ee 100644 --- a/app/Http/Livewire/Help.php +++ b/app/Http/Livewire/Help.php @@ -19,7 +19,7 @@ class Help extends Component ]; public function mount() { - $this->path = Route::current()->uri(); + $this->path = Route::current()?->uri() ?? null; if (isDev()) { $this->description = "I'm having trouble with {$this->path}"; $this->subject = "Help with {$this->path}"; @@ -41,7 +41,7 @@ class Help extends Component ] ); $mail->subject("[HELP - {$subscriptionType}]: {$this->subject}"); - send_user_an_email($mail, 'hi@coollabs.io', auth()->user()?->email); + send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io'); $this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.'); } catch (\Throwable $e) { return general_error_handler($e, $this); diff --git a/app/Http/Livewire/Notifications/DiscordSettings.php b/app/Http/Livewire/Notifications/DiscordSettings.php index 97a8878f1..800addc24 100644 --- a/app/Http/Livewire/Notifications/DiscordSettings.php +++ b/app/Http/Livewire/Notifications/DiscordSettings.php @@ -46,9 +46,6 @@ class DiscordSettings extends Component public function saveModel() { $this->team->save(); - if (is_a($this->team, Team::class)) { - refreshSession(); - } $this->emit('success', 'Settings saved.'); } diff --git a/app/Http/Livewire/Notifications/EmailSettings.php b/app/Http/Livewire/Notifications/EmailSettings.php index 939b8bce0..5bbf846f4 100644 --- a/app/Http/Livewire/Notifications/EmailSettings.php +++ b/app/Http/Livewire/Notifications/EmailSettings.php @@ -110,9 +110,6 @@ class EmailSettings extends Component public function saveModel() { $this->team->save(); - if (is_a($this->team, Team::class)) { - refreshSession(); - } $this->emit('success', 'Settings saved.'); } public function submit() @@ -141,10 +138,11 @@ class EmailSettings extends Component try { $this->resetErrorBag(); $this->validate([ + 'team.smtp_from_address' => 'required|email', + 'team.smtp_from_name' => 'required', 'team.resend_api_key' => 'required' ]); $this->team->save(); - refreshSession(); $this->emit('success', 'Settings saved successfully.'); } catch (\Throwable $e) { $this->team->resend_enabled = false; diff --git a/app/Http/Livewire/Notifications/TelegramSettings.php b/app/Http/Livewire/Notifications/TelegramSettings.php index 15dca1490..b0313e1fb 100644 --- a/app/Http/Livewire/Notifications/TelegramSettings.php +++ b/app/Http/Livewire/Notifications/TelegramSettings.php @@ -52,9 +52,6 @@ class TelegramSettings extends Component public function saveModel() { $this->team->save(); - if (is_a($this->team, Team::class)) { - refreshSession(); - } $this->emit('success', 'Settings saved.'); } diff --git a/app/Http/Livewire/PrivateKey/Create.php b/app/Http/Livewire/PrivateKey/Create.php index 37363195d..c48b4490e 100644 --- a/app/Http/Livewire/PrivateKey/Create.php +++ b/app/Http/Livewire/PrivateKey/Create.php @@ -4,13 +4,15 @@ namespace App\Http\Livewire\PrivateKey; use App\Models\PrivateKey; use Livewire\Component; +use phpseclib3\Crypt\PublicKeyLoader; class Create extends Component { - public string|null $from = null; + public ?string $from = null; public string $name; - public string|null $description = null; + public ?string $description = null; public string $value; + public ?string $publicKey = null; protected $rules = [ 'name' => 'required|string', 'value' => 'required|string', @@ -20,6 +22,23 @@ class Create extends Component 'value' => 'private Key', ]; + public function generateNewKey() + { + $this->name = generate_random_name(); + $this->description = 'Created by Coolify'; + ['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey(); + } + public function updated($updateProperty) + { + if ($updateProperty === 'value') { + try { + $this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH',['comment' => '']); + } catch (\Throwable $e) { + $this->publicKey = "Invalid private key"; + } + } + $this->validateOnly($updateProperty); + } public function createPrivateKey() { $this->validate(); diff --git a/app/Http/Livewire/Project/Application/Previews.php b/app/Http/Livewire/Project/Application/Previews.php index 9633d03a6..afe8a16f9 100644 --- a/app/Http/Livewire/Project/Application/Previews.php +++ b/app/Http/Livewire/Project/Application/Previews.php @@ -2,8 +2,6 @@ namespace App\Http\Livewire\Project\Application; -use App\Jobs\ApplicationContainerStatusJob; -use App\Jobs\ContainerStatusJob; use App\Models\Application; use App\Models\ApplicationPreview; use Illuminate\Support\Collection; diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php index ef84dca08..d73c644ef 100644 --- a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -5,7 +5,7 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable; use App\Models\EnvironmentVariable; use Livewire\Component; use Visus\Cuid2\Cuid2; -use Str; +use Illuminate\Support\Str; class All extends Component { diff --git a/app/Http/Livewire/Server/Form.php b/app/Http/Livewire/Server/Form.php index b288d3bfa..184c59704 100644 --- a/app/Http/Livewire/Server/Form.php +++ b/app/Http/Livewire/Server/Form.php @@ -88,7 +88,9 @@ class Form extends Component public function submit() { $this->validate(); - $uniqueIPs = Server::all()->pluck('ip')->toArray(); + $uniqueIPs = Server::all()->reject(function (Server $server) { + return $server->id === $this->server->id; + })->pluck('ip')->toArray(); if (in_array($this->server->ip, $uniqueIPs)) { $this->emit('error', 'IP address is already in use by another team.'); return; diff --git a/app/Http/Livewire/Server/Proxy.php b/app/Http/Livewire/Server/Proxy.php index 7f0a11f98..174d426b5 100644 --- a/app/Http/Livewire/Server/Proxy.php +++ b/app/Http/Livewire/Server/Proxy.php @@ -48,7 +48,7 @@ class Proxy extends Component public function submit() { try { - resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings); + resolve(SaveConfigurationSync::class)($this->server); $this->server->proxy->redirect_url = $this->redirect_url; $this->server->save(); diff --git a/app/Http/Livewire/Server/Proxy/Deploy.php b/app/Http/Livewire/Server/Proxy/Deploy.php index 19e421d1f..df369c49a 100644 --- a/app/Http/Livewire/Server/Proxy/Deploy.php +++ b/app/Http/Livewire/Server/Proxy/Deploy.php @@ -2,6 +2,7 @@ namespace App\Http\Livewire\Server\Proxy; +use App\Actions\Proxy\SaveConfigurationSync; use App\Actions\Proxy\StartProxy; use App\Models\Server; use Livewire\Component; @@ -21,7 +22,7 @@ class Deploy extends Component $this->server->proxy->last_applied_settings && $this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings ) { - resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings); + resolve(SaveConfigurationSync::class)($this->server); } $activity = resolve(StartProxy::class)($this->server); diff --git a/app/Http/Livewire/Server/Proxy/Modal.php b/app/Http/Livewire/Server/Proxy/Modal.php new file mode 100644 index 000000000..2674abe3d --- /dev/null +++ b/app/Http/Livewire/Server/Proxy/Modal.php @@ -0,0 +1,16 @@ +emit('proxyStatusUpdated'); + } +} diff --git a/app/Http/Livewire/Server/Show.php b/app/Http/Livewire/Server/Show.php index 75053cc1a..025fc82ff 100644 --- a/app/Http/Livewire/Server/Show.php +++ b/app/Http/Livewire/Server/Show.php @@ -13,7 +13,10 @@ class Show extends Component public function mount() { try { - $this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail(); + $this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first(); + if (is_null($this->server)) { + return redirect()->route('server.all'); + } } catch (\Throwable $e) { return general_error_handler(err: $e, that: $this); } diff --git a/app/Http/Livewire/Subscription/PricingPlans.php b/app/Http/Livewire/Subscription/PricingPlans.php index 9e26e3b9d..77c550d1e 100644 --- a/app/Http/Livewire/Subscription/PricingPlans.php +++ b/app/Http/Livewire/Subscription/PricingPlans.php @@ -44,6 +44,7 @@ class PricingPlans extends Component return; } $payload = [ + 'billing_address_collection' => 'required', 'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id, 'line_items' => [[ 'price' => $priceId, diff --git a/app/Http/Livewire/Team/Delete.php b/app/Http/Livewire/Team/Delete.php index 32e386ebc..b09395df4 100644 --- a/app/Http/Livewire/Team/Delete.php +++ b/app/Http/Livewire/Team/Delete.php @@ -12,7 +12,6 @@ class Delete extends Component $currentTeam = currentTeam(); $currentTeam->delete(); - $team = auth()->user()->teams()->first(); $currentTeam->members->each(function ($user) use ($currentTeam) { if ($user->id === auth()->user()->id) { return; diff --git a/app/Http/Livewire/Team/Form.php b/app/Http/Livewire/Team/Form.php index 212957e44..febbf33a3 100644 --- a/app/Http/Livewire/Team/Form.php +++ b/app/Http/Livewire/Team/Form.php @@ -27,7 +27,6 @@ class Form extends Component $this->validate(); try { $this->team->save(); - refreshSession(); } catch (\Throwable $e) { return general_error_handler($e, $this); } diff --git a/app/Http/Livewire/Team/InviteLink.php b/app/Http/Livewire/Team/InviteLink.php index b554e6575..c22c54a61 100644 --- a/app/Http/Livewire/Team/InviteLink.php +++ b/app/Http/Livewire/Team/InviteLink.php @@ -4,9 +4,13 @@ namespace App\Http\Livewire\Team; use App\Models\TeamInvitation; use App\Models\User; -use App\Notifications\TransactionalEmails\InvitationLink; +use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Support\Facades\Artisan; use Livewire\Component; use Visus\Cuid2\Cuid2; +use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; class InviteLink extends Component { @@ -20,53 +24,68 @@ class InviteLink extends Component public function viaEmail() { - $this->generate_invite_link(isEmail: true); + $this->generate_invite_link(sendEmail: true); } - private function generate_invite_link(bool $isEmail = false) + public function viaLink() + { + $this->generate_invite_link(sendEmail: false); + } + private function generate_invite_link(bool $sendEmail = false) { try { - $uuid = new Cuid2(32); - $link = url('/') . config('constants.invitation.link.base_url') . $uuid; - - $user = User::whereEmail($this->email); - - if (!$user->exists()) { - return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email)."); - } - $member_emails = currentTeam()->members()->get()->pluck('email'); if ($member_emails->contains($this->email)) { return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . "."); } + $uuid = new Cuid2(32); + $link = url('/') . config('constants.invitation.link.base_url') . $uuid; + $user = User::whereEmail($this->email)->first(); - $invitation = TeamInvitation::whereEmail($this->email); - - if ($invitation->exists()) { - $created_at = $invitation->first()->created_at; - $diff = $created_at->diffInMinutes(now()); - if ($diff <= config('constants.invitation.link.expiration')) { - return general_error_handler(that: $this, customErrorMessage: "Invitation already sent to $this->email and waiting for action."); + if (is_null($user)) { + $password = Str::password(); + $user = User::create([ + 'name' => Str::of($this->email)->before('@'), + 'email' => $this->email, + 'password' => Hash::make($password), + 'force_password_reset' => true, + ]); + $token = Crypt::encryptString("{$user->email}@@@$password"); + $link = route('auth.link', ['token' => $token]); + } + $invitation = TeamInvitation::whereEmail($this->email)->first(); + if (!is_null($invitation)) { + $invitationValid = $invitation->isValid(); + if ($invitationValid) { + return general_error_handler(that: $this, customErrorMessage: "Pending invitation already exists for $this->email."); } else { $invitation->delete(); } } - TeamInvitation::firstOrCreate([ + $invitation = TeamInvitation::firstOrCreate([ 'team_id' => currentTeam()->id, 'uuid' => $uuid, 'email' => $this->email, 'role' => $this->role, 'link' => $link, - 'via' => $isEmail ? 'email' : 'link', + 'via' => $sendEmail ? 'email' : 'link', ]); - if ($isEmail) { - $user->first()->notify(new InvitationLink); + if ($sendEmail) { + $mail = new MailMessage(); + $mail->view('emails.invitation-link', [ + 'team' => currentTeam()->name, + 'invitation_link' => $link, + ]); + $mail->subject('You have been invited to ' . currentTeam()->name . ' on ' . config('app.name') . '.'); + send_user_an_email($mail, $this->email); $this->emit('success', 'Invitation sent via email successfully.'); + $this->emit('refreshInvitations'); + return; } else { $this->emit('success', 'Invitation link generated.'); + $this->emit('refreshInvitations'); } - $this->emit('refreshInvitations'); } catch (\Throwable $e) { $error_message = $e->getMessage(); if ($e->getCode() === '23505') { @@ -75,9 +94,4 @@ class InviteLink extends Component return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message); } } - - public function viaLink() - { - $this->generate_invite_link(); - } } diff --git a/app/Http/Livewire/Team/Member.php b/app/Http/Livewire/Team/Member.php index df8fcd7af..34b8e6bde 100644 --- a/app/Http/Livewire/Team/Member.php +++ b/app/Http/Livewire/Team/Member.php @@ -3,6 +3,7 @@ namespace App\Http\Livewire\Team; use App\Models\User; +use Illuminate\Support\Facades\Cache; use Livewire\Component; class Member extends Component @@ -24,6 +25,10 @@ class Member extends Component public function remove() { $this->member->teams()->detach(currentTeam()); + Cache::forget("team:{$this->member->id}"); + Cache::remember('team:' . $this->member->id, 3600, function() { + return $this->member->teams()->first(); + }); $this->emit('reloadWindow'); } } diff --git a/app/Http/Livewire/Waitlist/Index.php b/app/Http/Livewire/Waitlist/Index.php index d2ce6fe19..86f8d8929 100644 --- a/app/Http/Livewire/Waitlist/Index.php +++ b/app/Http/Livewire/Waitlist/Index.php @@ -6,7 +6,7 @@ use App\Jobs\SendConfirmationForWaitlistJob; use App\Models\User; use App\Models\Waitlist; use Livewire\Component; -use Str; +use Illuminate\Support\Str; class Index extends Component { diff --git a/app/Http/Middleware/IsBoardingFlow.php b/app/Http/Middleware/IsBoardingFlow.php index 5858fe191..6e820f483 100644 --- a/app/Http/Middleware/IsBoardingFlow.php +++ b/app/Http/Middleware/IsBoardingFlow.php @@ -5,6 +5,7 @@ namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; +use Illuminate\Support\Str; class IsBoardingFlow { @@ -17,6 +18,9 @@ class IsBoardingFlow { // ray()->showQueries()->color('orange'); if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) { + if (Str::startsWith($request->path(), 'invitations')) { + return $next($request); + } return redirect('boarding'); } return $next($request); diff --git a/app/Http/Middleware/IsSubscriptionValid.php b/app/Http/Middleware/IsSubscriptionValid.php index a9354e982..5774e5190 100644 --- a/app/Http/Middleware/IsSubscriptionValid.php +++ b/app/Http/Middleware/IsSubscriptionValid.php @@ -5,6 +5,7 @@ namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; +use Illuminate\Support\Str; class IsSubscriptionValid { @@ -31,6 +32,9 @@ class IsSubscriptionValid if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) { // ray('SubscriptionValid Middleware'); if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) { + if (Str::startsWith($request->path(), 'invitations')) { + return $next($request); + } return redirect('subscription'); } else { return $next($request); diff --git a/app/Jobs/ApplicationContainerStatusJob.php b/app/Jobs/ApplicationContainerStatusJob.php deleted file mode 100644 index ae41a5c41..000000000 --- a/app/Jobs/ApplicationContainerStatusJob.php +++ /dev/null @@ -1,54 +0,0 @@ -containerName = generateApplicationContainerName($application->uuid, $pullRequestId); - } - - public function uniqueId(): string - { - return $this->containerName; - } - - public function handle(): void - { - try { - $status = getApplicationContainerStatus(application: $this->application); - if ($this->application->status === 'running' && $status !== 'running') { - // $this->application->environment->project->team->notify(new StatusChanged($this->application)); - } - - if ($this->pullRequestId !== 0) { - $preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pullRequestId); - $preview->status = $status; - $preview->save(); - } else { - $this->application->status = $status; - $this->application->save(); - } - } catch (\Throwable $e) { - ray($e->getMessage()); - throw $e; - } - } -} diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index ea682b03a..dd6648603 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -652,7 +652,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private function generate_healthcheck_commands() { - if ($this->application->dockerfile) { + if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') { // TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl. return 'exit 0'; } diff --git a/app/Jobs/CleanupInstanceStuffsJob.php b/app/Jobs/CleanupInstanceStuffsJob.php index f9d88cd4d..81a6963ea 100644 --- a/app/Jobs/CleanupInstanceStuffsJob.php +++ b/app/Jobs/CleanupInstanceStuffsJob.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Models\TeamInvitation; use App\Models\Waitlist; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; @@ -32,7 +33,12 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeE } catch (\Throwable $e) { send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage()); ray($e->getMessage()); - throw $e; + } + try { + $this->cleanup_invitation_link(); + } catch (\Throwable $e) { + send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage()); + ray($e->getMessage()); } } @@ -43,4 +49,11 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeE $item->delete(); } } + private function cleanup_invitation_link() + { + $invitation = TeamInvitation::all(); + foreach ($invitation as $item) { + $item->isValid(); + } + } } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 8f59c7a2c..100c33e51 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -17,9 +17,9 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; -use Str; +use Illuminate\Support\Str; -class ContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted +class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; @@ -89,7 +89,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypt $labels = data_get($container, 'Config.Labels'); $labels = Arr::undot(format_docker_labels_to_json($labels)); $labelId = data_get($labels, 'coolify.applicationId'); - ray($labelId); if ($labelId) { if (str_contains($labelId,'-pr-')) { $previewId = (int) Str::after($labelId, '-pr-'); diff --git a/app/Jobs/DatabaseContainerStatusJob.php b/app/Jobs/DatabaseContainerStatusJob.php deleted file mode 100644 index f1d0de301..000000000 --- a/app/Jobs/DatabaseContainerStatusJob.php +++ /dev/null @@ -1,57 +0,0 @@ -containerName = $database->uuid; - } - - public function uniqueId(): string - { - return $this->containerName; - } - - public function handle(): void - { - try { - $status = getContainerStatus( - server: $this->database->destination->server, - container_id: $this->containerName, - throwError: false - ); - - if ($this->database->status === 'running' && $status !== 'running') { - if (data_get($this->database, 'environment.project.team')) { - // $this->database->environment->project->team->notify(new StatusChanged($this->database)); - } - } - if ($this->database->status !== $status) { - $this->database->status = $status; - $this->database->save(); - } - } catch (\Throwable $e) { - send_internal_notification('DatabaseContainerStatusJob failed with: ' . $e->getMessage()); - ray($e->getMessage()); - throw $e; - } - } -} diff --git a/app/Jobs/ProxyContainerStatusJob.php b/app/Jobs/ProxyContainerStatusJob.php deleted file mode 100644 index 6cfd2692f..000000000 --- a/app/Jobs/ProxyContainerStatusJob.php +++ /dev/null @@ -1,87 +0,0 @@ -server = $server; - } - - public function middleware(): array - { - return [new WithoutOverlapping($this->server->uuid)]; - } - - public function uniqueId(): string - { - ray($this->server->uuid); - return $this->server->uuid; - } - - public function handle(): void - { - try { - $proxyType = data_get($this->server, 'proxy.type'); - if ($proxyType === ProxyTypes::NONE->value) { - return; - } - if (is_null($proxyType)) { - if ($this->server->isProxyShouldRun()) { - $this->server->proxy->type = ProxyTypes::TRAEFIK_V2->value; - $this->server->proxy->status = ProxyStatus::EXITED->value; - $this->server->save(); - resolve(StartProxy::class)($this->server); - return; - } - } - - $container = getContainerStatus(server: $this->server, all_data: true, container_id: 'coolify-proxy', throwError: false); - $containerStatus = data_get($container, 'State.Status'); - $databaseContainerStatus = data_get($this->server, 'proxy.status', 'exited'); - - - if ($proxyType !== ProxyTypes::NONE->value) { - if ($containerStatus === 'running') { - $this->server->proxy->status = $containerStatus; - $this->server->save(); - return; - } - if ((is_null($containerStatus) ||$containerStatus !== 'running' || $databaseContainerStatus !== 'running' || ($containerStatus && $databaseContainerStatus !== $containerStatus)) && $this->server->isProxyShouldRun()) { - $this->server->proxy->status = $containerStatus; - $this->server->save(); - resolve(StartProxy::class)($this->server); - return; - } - } - } catch (\Throwable $e) { - if ($e->getCode() === 1) { - $this->server->proxy->status = 'exited'; - $this->server->save(); - } - send_internal_notification('ProxyContainerStatusJob failed with: ' . $e->getMessage()); - ray($e->getMessage()); - throw $e; - } - } -} diff --git a/app/Jobs/SendMessageToTelegramJob.php b/app/Jobs/SendMessageToTelegramJob.php index 37f1cf381..23d49c40e 100644 --- a/app/Jobs/SendMessageToTelegramJob.php +++ b/app/Jobs/SendMessageToTelegramJob.php @@ -9,7 +9,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Http; -use Str; +use Illuminate\Support\Str; class SendMessageToTelegramJob implements ShouldQueue, ShouldBeEncrypted { diff --git a/app/Models/Team.php b/app/Models/Team.php index 558c8dc3b..e090796d5 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -19,6 +19,13 @@ class Team extends Model implements SendsDiscord, SendsEmail 'resend_api_key' => 'encrypted', ]; + protected static function booted() + { + static::saved(function () { + refreshSession(); + }); + } + public function routeNotificationForDiscord() { return data_get($this, 'discord_webhook_url', null); diff --git a/app/Models/TeamInvitation.php b/app/Models/TeamInvitation.php index a5c382b3b..75a726f9f 100644 --- a/app/Models/TeamInvitation.php +++ b/app/Models/TeamInvitation.php @@ -19,4 +19,13 @@ class TeamInvitation extends Model { return $this->belongsTo(Team::class); } + public function isValid() { + $createdAt = $this->created_at; + $diff = $createdAt->diffInMinutes(now()); + if ($diff <= config('constants.invitation.link.expiration')) { + return true; + } else { + $this->delete(); + } + } } diff --git a/app/Models/User.php b/app/Models/User.php index 1671db496..aba05acf3 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,10 +4,10 @@ namespace App\Models; use App\Notifications\Channels\SendsEmail; use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword; -use Cache; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Facades\Cache; use Laravel\Fortify\TwoFactorAuthenticatable; use Laravel\Sanctum\HasApiTokens; @@ -61,7 +61,7 @@ class User extends Authenticatable implements SendsEmail public function isAdmin() { - return $this->pivot->role === 'admin' || $this->pivot->role === 'owner'; + return data_get($this->pivot,'role') === 'admin' || data_get($this->pivot,'role') === 'owner'; } public function isAdminFromSession() @@ -78,7 +78,8 @@ class User extends Authenticatable implements SendsEmail if ($is_part_of_root_team && $is_admin_of_root_team) { return true; } - $role = $teams->where('id', auth()->user()->id)->first()->pivot->role; + $team = $teams->where('id', session('currentTeam')->id)->first(); + $role = data_get($team,'pivot.role'); return $role === 'admin' || $role === 'owner'; } diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php index e03c414b8..7416e1ce3 100644 --- a/app/Traits/ExecuteRemoteCommand.php +++ b/app/Traits/ExecuteRemoteCommand.php @@ -39,7 +39,7 @@ trait ExecuteRemoteCommand $this->save = data_get($single_command, 'save'); $remote_command = generateSshCommand( $ip, $user, $port, $command); - $process = processWithEnv()->timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) { + $process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) { $output = Str::of($output)->trim(); $new_log_entry = [ 'command' => $command, diff --git a/app/View/Components/Forms/Textarea.php b/app/View/Components/Forms/Textarea.php index c85c8b6f7..a34a08339 100644 --- a/app/View/Components/Forms/Textarea.php +++ b/app/View/Components/Forms/Textarea.php @@ -24,6 +24,7 @@ class Textarea extends Component public bool $disabled = false, public bool $readonly = false, public string|null $helper = null, + public bool $realtimeValidation = false, public string $defaultClass = "textarea bg-coolgray-200 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50" ) { // diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 03483afdc..0d99af516 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -75,7 +75,6 @@ function getApplicationContainerStatus(Application $application) { } function getContainerStatus(Server $server, string $container_id, bool $all_data = false, bool $throwError = false) { - // check_server_connection($server); $container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError); if (!$container) { return 'exited'; diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index 84f2dc9d3..a124efae8 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -15,6 +15,7 @@ use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Sleep; use Spatie\Activitylog\Models\Activity; +use Illuminate\Support\Str; function remote_process( array $command, @@ -49,20 +50,23 @@ function remote_process( ])(); } -function removePrivateKeyFromSshAgent(Server $server) -{ - if (data_get($server, 'privateKey.private_key') === null) { - throw new \Exception("Server {$server->name} does not have a private key"); - } - processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -d -"); -} +// function removePrivateKeyFromSshAgent(Server $server) +// { +// if (data_get($server, 'privateKey.private_key') === null) { +// throw new \Exception("Server {$server->name} does not have a private key"); +// } +// // processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -d -"); +// } function addPrivateKeyToSshAgent(Server $server) { if (data_get($server, 'privateKey.private_key') === null) { throw new \Exception("Server {$server->name} does not have a private key"); } - // ray('adding key', $server->privateKey->private_key); - processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -q -"); + $sshKeyFileLocation = "id.root@{$server->uuid}"; + Storage::disk('ssh-keys')->makeDirectory('.'); + Storage::disk('ssh-mux')->makeDirectory('.'); + Storage::disk('ssh-keys')->put($sshKeyFileLocation, $server->privateKey->private_key); + return '/var/www/html/storage/app/ssh/keys/' . $sshKeyFileLocation; } function generateSshCommand(string $server_ip, string $user, string $port, string $command, bool $isMux = true) @@ -71,7 +75,7 @@ function generateSshCommand(string $server_ip, string $user, string $port, strin if (!$server) { throw new \Exception("Server with ip {$server_ip} not found"); } - addPrivateKeyToSshAgent($server); + $privateKeyLocation = addPrivateKeyToSshAgent($server); $timeout = config('constants.ssh.command_timeout'); $connectionTimeout = config('constants.ssh.connection_timeout'); $serverInterval = config('constants.ssh.server_interval'); @@ -83,7 +87,8 @@ function generateSshCommand(string $server_ip, string $user, string $port, strin $ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r '; } $command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command"; - $ssh_command .= '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' + $ssh_command .= "-i {$privateKeyLocation} " + . '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' . '-o PasswordAuthentication=no ' . "-o ConnectTimeout=$connectionTimeout " . "-o ServerAliveInterval=$serverInterval " @@ -97,28 +102,11 @@ function generateSshCommand(string $server_ip, string $user, string $port, strin // ray($ssh_command); return $ssh_command; } -function processWithEnv() -{ - return Process::env(['SSH_AUTH_SOCK' => config('coolify.ssh_auth_sock')]); -} -function instantCommand(string $command, $throwError = true) -{ - $process = processWithEnv()->run($command); - $output = trim($process->output()); - $exitCode = $process->exitCode(); - if ($exitCode !== 0) { - if (!$throwError) { - return null; - } - throw new \RuntimeException($process->errorOutput(), $exitCode); - } - return $output; -} function instant_remote_process(array $command, Server $server, $throwError = true, $repeat = 1) { $command_string = implode("\n", $command); $ssh_command = generateSshCommand($server->ip, $server->user, $server->port, $command_string); - $process = processWithEnv()->run($ssh_command); + $process = Process::run($ssh_command); $output = trim($process->output()); $exitCode = $process->exitCode(); if ($exitCode !== 0) { @@ -169,14 +157,8 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d function refresh_server_connection(PrivateKey $private_key) { foreach ($private_key->servers as $server) { - // Delete the old ssh mux file to force a new one to be created Storage::disk('ssh-mux')->delete($server->muxFilename()); - // check if user is authenticated - // if (currentTeam()->id) { - // currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get(); - // } } - removePrivateKeyFromSshAgent($server); } function validateServer(Server $server) @@ -221,29 +203,6 @@ function validateServer(Server $server) } } -function check_server_connection(Server $server) -{ - try { - refresh_server_connection($server->privateKey); - instant_remote_process(['uptime'], $server); - $server->unreachable_count = 0; - $server->settings->is_reachable = true; - } catch (\Throwable $e) { - if ($server->unreachable_count == 2) { - $server->team->notify(new NotReachable($server)); - $server->settings->is_reachable = false; - $server->settings->save(); - } else { - $server->unreachable_count += 1; - } - - throw $e; - } finally { - $server->settings->save(); - $server->save(); - } -} - function checkRequiredCommands(Server $server) { $commands = collect(["jq", "jc"]); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index f42ac83d7..39066e1b6 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -2,6 +2,7 @@ use App\Models\InstanceSettings; use App\Models\Team; +use App\Models\User; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; @@ -10,6 +11,7 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; use Illuminate\Database\QueryException; use Illuminate\Mail\Message; use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Route; @@ -60,7 +62,11 @@ function showBoarding(): bool function refreshSession(?Team $team = null): void { if (!$team) { - $team = Team::find(currentTeam()->id); + if (auth()->user()->currentTeam()) { + $team = Team::find(auth()->user()->currentTeam()->id); + } else { + $team = User::find(auth()->user()->id)->teams->first(); + } } Cache::forget('team:' . auth()->user()->id); Cache::remember('team:' . auth()->user()->id, 3600, function() use ($team) { @@ -275,6 +281,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null [], fn (Message $message) => $message ->to($email) + ->replyTo($email) ->cc($cc) ->subject($mail->subject) ->html((string) $mail->render()) diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php index ae3506782..ac41b1a1d 100644 --- a/bootstrap/helpers/subscriptions.php +++ b/bootstrap/helpers/subscriptions.php @@ -56,7 +56,7 @@ function isSubscriptionActive() } $subscription = $team?->subscription; - if (!$subscription) { + if (is_null($subscription)) { return false; } if (isLemon()) { diff --git a/config/coolify.php b/config/coolify.php index 68960015b..cd16d6b6f 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -8,5 +8,4 @@ return [ 'dev_webhook' => env('SERVEO_URL'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'), - 'ssh_auth_sock' => env('SSH_AUTH_SOCK', '/tmp/coolify-ssh-agent.sock'), ]; diff --git a/config/sentry.php b/config/sentry.php index b0db797f1..279b05b3d 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.36', + 'release' => '4.0.0-beta.37', 'server_name' => env('APP_ID', 'coolify'), // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index c3cb2484c..bf9d03915 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ text('link')->change(); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('team_invitations', function (Blueprint $table) { + $table->string('link')->change(); + }); + } +}; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 9af78058f..cefdec07f 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -21,7 +21,6 @@ services: SSL_MODE: "off" AUTORUN_LARAVEL_STORAGE_LINK: "false" AUTORUN_LARAVEL_MIGRATION: "false" - SSH_AUTH_SOCK: "/tmp/coolify-ssh-agent.sock" volumes: - .:/var/www/html/:cached postgres: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 473513115..6268f9963 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -64,7 +64,6 @@ services: - LEMON_SQUEEZY_BASIC_PLAN_IDS - LEMON_SQUEEZY_PRO_PLAN_IDS - LEMON_SQUEEZY_ULTIMATE_PLAN_IDS - - SSH_AUTH_SOCK="/tmp/coolify-ssh-agent.sock" ports: - "${APP_PORT:-8000}:80" expose: diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/type b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/type deleted file mode 100644 index bdd22a185..000000000 --- a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/up b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/up deleted file mode 100644 index 0490fb31f..000000000 --- a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/up +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/execlineb -P -foreground { - s6-sleep 5 - su - webuser -c "ssh-agent -a /tmp/coolify-ssh-agent.sock" -} diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/ssh-agent b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/ssh-agent deleted file mode 100644 index e69de29bb..000000000 diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/type b/docker/prod-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/type deleted file mode 100644 index bdd22a185..000000000 --- a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/up b/docker/prod-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/up deleted file mode 100644 index 0490fb31f..000000000 --- a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/ssh-agent/up +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/execlineb -P -foreground { - s6-sleep 5 - su - webuser -c "ssh-agent -a /tmp/coolify-ssh-agent.sock" -} diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/ssh-agent b/docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/ssh-agent deleted file mode 100644 index e69de29bb..000000000 diff --git a/resources/views/components/forms/textarea.blade.php b/resources/views/components/forms/textarea.blade.php index f00a0e1d7..be45629ab 100644 --- a/resources/views/components/forms/textarea.blade.php +++ b/resources/views/components/forms/textarea.blade.php @@ -30,9 +30,12 @@ @endif + @if ($realtimeValidation) wire:model.debounce.500ms="{{ $id }}" + @else + wire:model.defer={{ $value ?? $id }} + wire:dirty.class="input-warning"@endif + @disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}" name="{{ $name }}" + name={{ $id }} > @error($id) -
{{config('constants.limits.trial_period')}} days trial included on all plans, without credit card details.
+
{{ config('constants.limits.trial_period') }} + days trial included on all plans, without credit card details.
Save 1 month annually with the yearly plans.
@@ -289,6 +290,170 @@
+
Included in all plans
+
+
+
+
+ + + +
+
Bring Your Own Servers
+
+
+ Bring your own server from any cloud providers, or even your own server at home! All you need is SSH + access. You will have full control over your server, and you can even use it for other purposes. +
+
+
+
+
+ + + + + + + + +
+
Server Automations
+
+
+ Once you connected your server, Coolify will start managing it and do a + lot of adminstrative tasks for you. You can also write your own scripts to + automate your server*. +
+
+
+
+
+ + + + + + +
+
No Vendor Lock-in
+
+
+ You own your own data. All configurations saved on your own servers, so if + you decide to stop using Coolify, you can still continue to manage your + deployed resources. +
+
+ +
+
+
+ + + + + + + + +
+
Monitoring
+
+
+ Coolify will automatically monitor your configured servers and deployed + resources. Notifies you if something goes wrong on your favourite + channels, like Discord, Telegram, via Email and more... +
+
+
+
+
+ + + + + + +
+
Automatic Backups
+
+
+ We automatically backup your databases to any S3 compatible solution. If + something goes wrong, you can easily restore your data with a few clicks. +
+
+
+
+
+ + + + + +
+
Powerful API
+
+
+ Programatically deploy, query, and manage your servers & resources. + Integrate to your CI/CD pipelines, or build your own custom integrations. * +
+
+
+
+
+ + + + + + + +
+
Push to Deploy
+
+
+ Git integration is default today. We support hosted (github.com, + gitlab.com*) or self-hosted* + (Github Enterprise, Gitlab) Git repositories. +
+
+
+
+
+ + + +
+
Pull Request Deployments
+
+
+ Automagically deploy new commits and pull requests separately to quickly + review contributions and speed up your teamwork! +
+
+
+
+ * Some features are work in progress and will be available soon. +
@isset($other) {{ $other }} diff --git a/resources/views/components/server/navbar.blade.php b/resources/views/components/server/navbar.blade.php index 382e968c3..5aaafead1 100644 --- a/resources/views/components/server/navbar.blade.php +++ b/resources/views/components/server/navbar.blade.php @@ -1,4 +1,5 @@
+

Server

@if ($server->settings->is_reachable) diff --git a/resources/views/emails/invitation-link.blade.php b/resources/views/emails/invitation-link.blade.php index 05c36bb8a..ae72745ba 100644 --- a/resources/views/emails/invitation-link.blade.php +++ b/resources/views/emails/invitation-link.blade.php @@ -6,6 +6,5 @@ Please [click here]({{ $invitation_link }}) to accept the invitation. If you have any questions, please contact the team owner.

-If it was not you who requested this invitation, please ignore this email, or instantly revoke the invitation by clicking [here]({{ $invitation_link }}/revoke). - +If it was not you who requested this invitation, please ignore this email. diff --git a/resources/views/livewire/notifications/email-settings.blade.php b/resources/views/livewire/notifications/email-settings.blade.php index d7acaac1a..d96eb4875 100644 --- a/resources/views/livewire/notifications/email-settings.blade.php +++ b/resources/views/livewire/notifications/email-settings.blade.php @@ -21,7 +21,8 @@ Copy from Instance Settings @endif - @if (isEmailEnabled($team) && auth()->user()->isAdminFromSession()) + @if (isEmailEnabled($team) && + auth()->user()->isAdminFromSession()) Send Test Email @@ -51,61 +52,52 @@
-
- -
SMTP Server
-
- -
-
-
-
-
-
- - - -
-
- - - -
-
-
- - Save - -
-
+
+

SMTP Server

+
+
-
-
- -
Resend
-
- +
+
+
+ + + +
+
+ + + +
-
-
- -
-
- -
-
-
- - Save - -
- +
+ + Save + +
+ +
+
+

Resend

+
+
-
+
+
+
+ +
+
+
+ + Save + +
+
+
@endif @if (isEmailEnabled($team) || data_get($team, 'use_instance_email_settings')) diff --git a/resources/views/livewire/private-key/create.blade.php b/resources/views/livewire/private-key/create.blade.php index 2c58b47c5..3d9ade364 100644 --- a/resources/views/livewire/private-key/create.blade.php +++ b/resources/views/livewire/private-key/create.blade.php @@ -4,8 +4,13 @@
- + + Generate new SSH key for me + + ACTION REQUIRED: Copy the 'Public Key' to your server's + ~/.ssh/authorized_keys + file. Save Private Key diff --git a/resources/views/livewire/server/proxy.blade.php b/resources/views/livewire/server/proxy.blade.php index 0b38ad940..59ce5bf4c 100644 --- a/resources/views/livewire/server/proxy.blade.php +++ b/resources/views/livewire/server/proxy.blade.php @@ -1,16 +1,6 @@
@if ($server->isFunctional()) @if (data_get($server,'proxy.type')) - - - - - - - Close - - -
@if ($selectedProxy === 'TRAEFIK_V2')
diff --git a/resources/views/livewire/server/proxy/modal.blade.php b/resources/views/livewire/server/proxy/modal.blade.php new file mode 100644 index 000000000..3dfb2d31c --- /dev/null +++ b/resources/views/livewire/server/proxy/modal.blade.php @@ -0,0 +1,12 @@ +
+ + + + + + + Close + + + +
diff --git a/resources/views/livewire/server/proxy/status.blade.php b/resources/views/livewire/server/proxy/status.blade.php index 3aec00746..db496f561 100644 --- a/resources/views/livewire/server/proxy/status.blade.php +++ b/resources/views/livewire/server/proxy/status.blade.php @@ -1,3 +1,4 @@ +
@if ($server->proxy->status === 'running') diff --git a/resources/views/subscription/index.blade.php b/resources/views/subscription/index.blade.php index e13d856de..d36f26c81 100644 --- a/resources/views/subscription/index.blade.php +++ b/resources/views/subscription/index.blade.php @@ -1,7 +1,34 @@ @if ($settings->is_resale_license_active) -
-
+ @if (auth()->user()->isAdminFromSession()) +
+
+
+

Subscription

+ +
+
+ Currently active team: {{ session('currentTeam.name') }} +
+ @if (request()->query->get('cancelled')) +
+ + + + Something went wrong with your subscription. Please try again or contact + support. +
+ @endif + @if (config('subscription.provider') !== null) + + @endif +
+
+ @else +

Subscription

@@ -10,22 +37,10 @@ Currently active team: {{ session('currentTeam.name') }}
- @if (request()->query->get('cancelled')) -
- - - - Something went wrong with your subscription. Please try again or contact support. -
- @endif - @if (config('subscription.provider') !== null) - - @endif +
You are not an admin or have been removed from this team. If this does not make sense, please contact us.
-
+ @endif @else -
Resale license is not active. Please contact your instance admin.
+
Resale license is not active. Please contact your instance admin.
@endif diff --git a/versions.json b/versions.json index 6da75e544..16f7fa73d 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "3.12.36" }, "v4": { - "version": "4.0.0-beta.36" + "version": "4.0.0-beta.37" } } }