diff --git a/app/Console/Commands/CleanupDatabase.php b/app/Console/Commands/CleanupDatabase.php new file mode 100644 index 000000000..60e6ea901 --- /dev/null +++ b/app/Console/Commands/CleanupDatabase.php @@ -0,0 +1,59 @@ +where('failed_at', '<', now()->subDays(7)); + $count = $failed_jobs->count(); + echo "Delete $count entries from failed_jobs.\n"; + if ($this->option('yes')) { + $failed_jobs->delete(); + } + + // Cleanup sessions table + $sessions = DB::table('sessions')->where('last_activity', '<', now()->subDays($keep_days)->timestamp); + $count = $sessions->count(); + echo "Delete $count entries from sessions.\n"; + if ($this->option('yes')) { + $sessions->delete(); + } + + // Cleanup activity_log table + $activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days)); + $count = $activity_log->count(); + echo "Delete $count entries from activity_log.\n"; + if ($this->option('yes')) { + $activity_log->delete(); + } + + // Cleanup application_deployment_queues table + $application_deployment_queues = DB::table('application_deployment_queues')->where('created_at', '<', now()->subDays($keep_days)); + $count = $application_deployment_queues->count(); + echo "Delete $count entries from application_deployment_queues.\n"; + if ($this->option('yes')) { + $application_deployment_queues->delete(); + } + + // Cleanup webhooks table + $webhooks = DB::table('webhooks')->where('created_at', '<', now()->subDays($keep_days)); + $count = $webhooks->count(); + echo "Delete $count entries from webhooks.\n"; + if ($this->option('yes')) { + $webhooks->delete(); + } + + } +} diff --git a/app/Console/Commands/CleanupUnreachableServers.php b/app/Console/Commands/CleanupUnreachableServers.php index 8bbd27d64..b63dc1d36 100644 --- a/app/Console/Commands/CleanupUnreachableServers.php +++ b/app/Console/Commands/CleanupUnreachableServers.php @@ -8,15 +8,16 @@ use Illuminate\Console\Command; class CleanupUnreachableServers extends Command { protected $signature = 'cleanup:unreachable-servers'; - protected $description = 'Cleanup Unreachable Servers (3 days)'; + protected $description = 'Cleanup Unreachable Servers (7 days)'; public function handle() { echo "Running unreachable server cleanup...\n"; - $servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(3))->get(); + $servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(7))->get(); if ($servers->count() > 0) { foreach ($servers as $server) { echo "Cleanup unreachable server ($server->id) with name $server->name"; + send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up..."); $server->update([ 'ip' => '1.2.3.4' ]); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 76fcbfc1f..e92c602cf 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -69,12 +69,34 @@ class Kernel extends ConsoleKernel } foreach ($containerServers as $server) { $schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer(); + // $schedule + // ->call(function () use ($server) { + // $randomSeconds = rand(1, 40); + // $job = new ContainerStatusJob($server); + // $job->delay($randomSeconds); + // ray('dispatching container status job in ' . $randomSeconds . ' seconds'); + // dispatch($job); + // })->name('container-status-' . $server->id)->everyMinute()->onOneServer(); if ($server->isLogDrainEnabled()) { $schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer(); + // $schedule + // ->call(function () use ($server) { + // $randomSeconds = rand(1, 40); + // $job = new CheckLogDrainContainerJob($server); + // $job->delay($randomSeconds); + // dispatch($job); + // })->name('log-drain-container-check-' . $server->id)->everyMinute()->onOneServer(); } } foreach ($servers as $server) { $schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer(); + // $schedule + // ->call(function () use ($server) { + // $randomSeconds = rand(1, 40); + // $job = new ServerStatusJob($server); + // $job->delay($randomSeconds); + // dispatch($job); + // })->name('server-status-job-' . $server->id)->everyMinute()->onOneServer(); } } private function instance_auto_update($schedule) diff --git a/app/Http/Controllers/Api/Deploy.php b/app/Http/Controllers/Api/Deploy.php index 21da51d66..27d4b1ea0 100644 --- a/app/Http/Controllers/Api/Deploy.php +++ b/app/Http/Controllers/Api/Deploy.php @@ -14,7 +14,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Collection; use Visus\Cuid2\Cuid2; -class Deploy extends Controller +class APIDeploy extends Controller { public function deploy(Request $request) { diff --git a/app/Http/Controllers/Api/Project.php b/app/Http/Controllers/Api/Project.php index fa2ba34bb..110e51803 100644 --- a/app/Http/Controllers/Api/Project.php +++ b/app/Http/Controllers/Api/Project.php @@ -6,7 +6,7 @@ use App\Http\Controllers\Controller; use App\Models\Project as ModelsProject; use Illuminate\Http\Request; -class Project extends Controller +class APIProject extends Controller { public function projects(Request $request) { diff --git a/app/Http/Controllers/Api/Server.php b/app/Http/Controllers/Api/Server.php index e7b071a43..bab37928f 100644 --- a/app/Http/Controllers/Api/Server.php +++ b/app/Http/Controllers/Api/Server.php @@ -6,7 +6,7 @@ use App\Http\Controllers\Controller; use App\Models\Server as ModelsServer; use Illuminate\Http\Request; -class Server extends Controller +class APIServer extends Controller { public function servers(Request $request) { diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index f3e8c4129..0949ef5e6 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1223,6 +1223,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if ((bool)$this->application->settings->is_consistent_container_name_enabled) { $custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options); if (count($custom_compose) > 0) { + $ipv4 = data_get($custom_compose, 'ip.0'); + $ipv6 = data_get($custom_compose, 'ip6.0'); + data_forget($custom_compose, 'ip'); + data_forget($custom_compose, 'ip6'); + if ($ipv4 || $ipv6) { + data_forget($docker_compose['services'][$this->application->uuid], 'networks'); + } + if ($ipv4) { + $docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4; + } + if ($ipv6) { + $docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6; + } $docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose); } } else { @@ -1230,6 +1243,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted data_forget($docker_compose, 'services.' . $this->container_name); $custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options); if (count($custom_compose) > 0) { + $ipv4 = data_get($custom_compose, 'ip.0'); + $ipv6 = data_get($custom_compose, 'ip6.0'); + data_forget($custom_compose, 'ip'); + data_forget($custom_compose, 'ip6'); + if ($ipv4 || $ipv6) { + data_forget($docker_compose['services'][$this->application->uuid], 'networks'); + } + if ($ipv4) { + $docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4; + } + if ($ipv6) { + $docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6; + } $docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose); } } @@ -1649,7 +1675,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); ); } } - $this->next(ApplicationDeploymentStatus::FAILED->value); } } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index fbdd555a1..68035a258 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -43,6 +43,10 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted public function handle() { + if (!$this->server->isFunctional()) { + return 'Server is not ready.'; + }; + $applications = $this->server->applications(); $skip_these_applications = collect([]); foreach ($applications as $application) { @@ -57,10 +61,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted $applications = $applications->filter(function ($value, $key) use ($skip_these_applications) { return !$skip_these_applications->pluck('id')->contains($value->id); }); - - if (!$this->server->isFunctional()) { - return 'Server is not ready.'; - }; try { if ($this->server->isSwarm()) { $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false); diff --git a/app/Jobs/ServerLimitCheckJob.php b/app/Jobs/ServerLimitCheckJob.php new file mode 100644 index 000000000..052260895 --- /dev/null +++ b/app/Jobs/ServerLimitCheckJob.php @@ -0,0 +1,68 @@ +team->uuid))]; + } + + public function uniqueId(): int + { + return $this->team->uuid; + } + + public function handle() + { + try { + $servers = $this->team->servers; + $servers_count = $servers->count(); + $limit = $this->team->limits['serverLimit']; + $number_of_servers_to_disable = $servers_count - $limit; + ray('ServerLimitCheckJob', $this->team->uuid, $servers_count, $limit, $number_of_servers_to_disable); + if ($number_of_servers_to_disable > 0) { + ray('Disabling servers'); + $servers = $servers->sortbyDesc('created_at'); + $servers_to_disable = $servers->take($number_of_servers_to_disable); + $servers_to_disable->each(function ($server) { + $server->forceDisableServer(); + $this->team->notify(new ForceDisabled($server)); + }); + } else if ($number_of_servers_to_disable === 0) { + $servers->each(function ($server) { + if ($server->isForceDisabled()) { + $server->forceEnableServer(); + $this->team->notify(new ForceEnabled($server)); + } + }); + } + } catch (\Throwable $e) { + send_internal_notification('ServerLimitCheckJob failed with: ' . $e->getMessage()); + ray($e->getMessage()); + return handleError($e); + } + } +} diff --git a/app/Jobs/ServerStatusJob.php b/app/Jobs/ServerStatusJob.php index d0243fa9a..31683d097 100644 --- a/app/Jobs/ServerStatusJob.php +++ b/app/Jobs/ServerStatusJob.php @@ -41,15 +41,6 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted throw new \RuntimeException('Server is not ready.'); }; try { - // $this->server->validateConnection(); - // $this->server->validateOS(); - // $docker_installed = $this->server->validateDockerEngine(); - // if (!$docker_installed) { - // $this->server->installDocker(); - // $this->server->validateDockerEngine(); - // } - - // $this->server->validateDockerEngineVersion(); if ($this->server->isFunctional()) { $this->cleanup(notify: false); } diff --git a/app/Livewire/Admin/Index.php b/app/Livewire/Admin/Index.php index 4ff4ff21b..27e912eed 100644 --- a/app/Livewire/Admin/Index.php +++ b/app/Livewire/Admin/Index.php @@ -3,6 +3,7 @@ namespace App\Livewire\Admin; use App\Models\User; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Crypt; use Livewire\Component; @@ -27,6 +28,7 @@ class Index extends Component auth()->login($user); if ($user_id === 0) { + Cache::forget('team:0'); session()->forget('adminToken'); } else { $token_payload = [ @@ -35,6 +37,7 @@ class Index extends Component $token = Crypt::encrypt($token_payload); session(['adminToken' => $token]); } + session()->regenerate(); return refreshSession(); } public function render() diff --git a/app/Livewire/Sponsorship.php b/app/Livewire/LayoutPopups.php similarity index 86% rename from app/Livewire/Sponsorship.php rename to app/Livewire/LayoutPopups.php index c4dedffc0..dd7f14678 100644 --- a/app/Livewire/Sponsorship.php +++ b/app/Livewire/LayoutPopups.php @@ -4,7 +4,7 @@ namespace App\Livewire; use Livewire\Component; -class Sponsorship extends Component +class LayoutPopups extends Component { public function getListeners() { @@ -23,6 +23,6 @@ class Sponsorship extends Component } public function render() { - return view('livewire.sponsorship'); + return view('livewire.layout-popups'); } } diff --git a/app/Livewire/Server/Create.php b/app/Livewire/Server/Create.php index 147128457..2f30caf0e 100644 --- a/app/Livewire/Server/Create.php +++ b/app/Livewire/Server/Create.php @@ -3,6 +3,7 @@ namespace App\Livewire\Server; use App\Models\PrivateKey; +use App\Models\Team; use Livewire\Component; class Create extends Component @@ -16,11 +17,7 @@ class Create extends Component $this->limit_reached = false; return; } - $team = currentTeam(); - $servers = $team->servers->count(); - ['serverLimit' => $serverLimit] = $team->limits; - - $this->limit_reached = $servers >= $serverLimit; + $this->limit_reached = Team::serverLimitReached(); } public function render() { diff --git a/app/Livewire/Server/New/ByIp.php b/app/Livewire/Server/New/ByIp.php index cd0166c54..9799443c7 100644 --- a/app/Livewire/Server/New/ByIp.php +++ b/app/Livewire/Server/New/ByIp.php @@ -5,6 +5,7 @@ namespace App\Livewire\Server\New; use App\Enums\ProxyStatus; use App\Enums\ProxyTypes; use App\Models\Server; +use App\Models\Team; use Livewire\Component; class ByIp extends Component @@ -76,6 +77,9 @@ class ByIp extends Component if (is_null($this->private_key_id)) { return $this->dispatch('error', 'You must select a private key'); } + if (Team::serverLimitReached()) { + return $this->dispatch('error', 'You have reached the server limit for your subscription.'); + } $payload = [ 'name' => $this->name, 'description' => $this->description, diff --git a/app/Livewire/Subscription/Actions.php b/app/Livewire/Subscription/Actions.php index 3050e38ab..a6a201f3b 100644 --- a/app/Livewire/Subscription/Actions.php +++ b/app/Livewire/Subscription/Actions.php @@ -2,17 +2,17 @@ namespace App\Livewire\Subscription; +use App\Models\Team; use Illuminate\Support\Facades\Http; use Livewire\Component; class Actions extends Component { public $server_limits = 0; + public function mount() { - $limits = currentTeam()->limits; - $this->server_limits = data_get($limits, 'serverLimit', 0); - + $this->server_limits = Team::serverLimit(); } public function cancel() { diff --git a/app/Livewire/Subscription/PricingPlans.php b/app/Livewire/Subscription/PricingPlans.php index a3afcaabc..3996d70d5 100644 --- a/app/Livewire/Subscription/PricingPlans.php +++ b/app/Livewire/Subscription/PricingPlans.php @@ -9,8 +9,9 @@ use Stripe\Checkout\Session; class PricingPlans extends Component { public bool $isTrial = false; - public function mount() { - $this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended'); + public function mount() + { + $this->isTrial = !data_get(currentTeam(), 'subscription.stripe_trial_already_ended'); if (config('constants.limits.trial_period') == 0) { $this->isTrial = false; } @@ -26,15 +27,15 @@ class PricingPlans extends Component case 'basic-yearly': $priceId = config('subscription.stripe_price_id_basic_yearly'); break; - case 'ultimate-monthly': - $priceId = config('subscription.stripe_price_id_ultimate_monthly'); - break; case 'pro-monthly': $priceId = config('subscription.stripe_price_id_pro_monthly'); break; case 'pro-yearly': $priceId = config('subscription.stripe_price_id_pro_yearly'); break; + case 'ultimate-monthly': + $priceId = config('subscription.stripe_price_id_ultimate_monthly'); + break; case 'ultimate-yearly': $priceId = config('subscription.stripe_price_id_ultimate_yearly'); break; @@ -64,18 +65,25 @@ class PricingPlans extends Component 'success_url' => route('dashboard', ['success' => true]), 'cancel_url' => route('subscription.index', ['cancelled' => true]), ]; - - if (!data_get($team,'subscription.stripe_trial_already_ended')) { - if (config('constants.limits.trial_period') > 0) { - $payload['subscription_data'] = [ - 'trial_period_days' => config('constants.limits.trial_period'), - 'trial_settings' => [ - 'end_behavior' => [ - 'missing_payment_method' => 'cancel', - ] - ], + if (str($type)->contains('ultimate')) { + $payload['line_items'][0]['adjustable_quantity'] = [ + 'enabled' => true, + 'minimum' => 10, ]; + $payload['line_items'][0]['quantity'] = 10; } + + if (!data_get($team, 'subscription.stripe_trial_already_ended')) { + if (config('constants.limits.trial_period') > 0) { + $payload['subscription_data'] = [ + 'trial_period_days' => config('constants.limits.trial_period'), + 'trial_settings' => [ + 'end_behavior' => [ + 'missing_payment_method' => 'cancel', + ] + ], + ]; + } $payload['payment_method_collection'] = 'if_required'; } $customer = currentTeam()->subscription?->stripe_customer_id ?? null; diff --git a/app/Livewire/Tags/Show.php b/app/Livewire/Tags/Show.php index 05b25955a..c0c975f6d 100644 --- a/app/Livewire/Tags/Show.php +++ b/app/Livewire/Tags/Show.php @@ -2,7 +2,7 @@ namespace App\Livewire\Tags; -use App\Http\Controllers\Api\Deploy; +use App\Http\Controllers\Api\APIDeploy as Deploy; use App\Models\ApplicationDeploymentQueue; use App\Models\Tag; use Livewire\Component; diff --git a/app/Models/Server.php b/app/Models/Server.php index 016efd5f7..fc400935a 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -10,6 +10,7 @@ use App\Notifications\Server\Unreachable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; use Spatie\SchemalessAttributes\SchemalessAttributesTrait; use Illuminate\Support\Str; @@ -69,7 +70,7 @@ class Server extends BaseModel static public function isUsable() { - return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false)->whereRelation('settings', 'is_build_server', false); + return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false)->whereRelation('settings', 'is_build_server', false)->whereRelation('settings', 'force_disabled', false); } static public function destinationsByServer(string $server_id) @@ -149,8 +150,31 @@ class Server extends BaseModel ray('skipping 1.2.3.4'); return true; } + if ($this->settings->force_disabled === true) { + ray('force_disabled'); + return true; + } return false; } + public function isForceDisabled() + { + return $this->settings->force_disabled; + } + public function forceEnableServer() + { + $this->settings->update([ + 'force_disabled' => false, + ]); + } + public function forceDisableServer() + { + $this->settings->update([ + 'force_disabled' => true, + ]); + $sshKeyFileLocation = "id.root@{$this->uuid}"; + Storage::disk('ssh-keys')->delete($sshKeyFileLocation); + Storage::disk('ssh-mux')->delete($this->muxFilename()); + } public function isServerReady(int $tries = 3) { if ($this->skipServer()) { @@ -374,7 +398,7 @@ class Server extends BaseModel } public function isFunctional() { - return $this->settings->is_reachable && $this->settings->is_usable; + return $this->settings->is_reachable && $this->settings->is_usable && !$this->settings->force_disabled; } public function isLogDrainEnabled() { diff --git a/app/Models/Team.php b/app/Models/Team.php index 042f74789..7858dde49 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -48,7 +48,22 @@ class Team extends Model implements SendsDiscord, SendsEmail } return explode(',', $recipients); } - + static public function serverLimitReached() { + $serverLimit = Team::serverLimit(); + $team = currentTeam(); + $servers = $team->servers->count(); + return $servers >= $serverLimit; + } + public function serverOverflow() { + if ($this->serverLimit() < $this->servers->count()) { + return true; + } + return false; + } + static public function serverLimit() + { + return Team::find(currentTeam()->id)->limits['serverLimit']; + } public function limits(): Attribute { return Attribute::make( @@ -63,14 +78,19 @@ class Team extends Model implements SendsDiscord, SendsEmail $subscription = $subscription->type(); } } - $serverLimit = config('constants.limits.server')[strtolower($subscription)]; + if ($this->custom_server_limit) { + $serverLimit = $this->custom_server_limit; + } else { + $serverLimit = config('constants.limits.server')[strtolower($subscription)]; + } $sharedEmailEnabled = config('constants.limits.email')[strtolower($subscription)]; return ['serverLimit' => $serverLimit, 'sharedEmailEnabled' => $sharedEmailEnabled]; } ); } - public function environment_variables() { + public function environment_variables() + { return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id'); } public function members() @@ -130,7 +150,8 @@ class Team extends Model implements SendsDiscord, SendsEmail { return $this->hasMany(S3Storage::class)->where('is_usable', true); } - public function trialEnded() { + public function trialEnded() + { foreach ($this->servers as $server) { $server->settings()->update([ 'is_usable' => false, @@ -138,7 +159,8 @@ class Team extends Model implements SendsDiscord, SendsEmail ]); } } - public function trialEndedButSubscribed() { + public function trialEndedButSubscribed() + { foreach ($this->servers as $server) { $server->settings()->update([ 'is_usable' => true, diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php new file mode 100644 index 000000000..4bce44e46 --- /dev/null +++ b/app/Notifications/Server/ForceDisabled.php @@ -0,0 +1,63 @@ +subject("Coolify: Server ({$this->server->name}) disabled because it is not paid!"); + $mail->view('emails.server-force-disabled', [ + 'name' => $this->server->name, + ]); + return $mail; + } + + public function toDiscord(): string + { + $message = "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions)."; + return $message; + } + public function toTelegram(): array + { + return [ + "message" => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions)." + ]; + } +} diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php new file mode 100644 index 000000000..c29a08644 --- /dev/null +++ b/app/Notifications/Server/ForceEnabled.php @@ -0,0 +1,63 @@ +subject("Coolify: Server ({$this->server->name}) enabled again!"); + $mail->view('emails.server-force-enabled', [ + 'name' => $this->server->name, + ]); + return $mail; + } + + public function toDiscord(): string + { + $message = "Coolify: Server ({$this->server->name}) enabled again!"; + return $message; + } + public function toTelegram(): array + { + return [ + "message" => "Coolify: Server ({$this->server->name}) enabled again!" + ]; + } +} diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php index 529dacd7a..9b34fabae 100644 --- a/app/Traits/ExecuteRemoteCommand.php +++ b/app/Traits/ExecuteRemoteCommand.php @@ -24,6 +24,12 @@ trait ExecuteRemoteCommand if ($this->server instanceof Server === false) { throw new \RuntimeException('Server is not set or is not an instance of Server model'); } + if ($this->server->settings->force_disabled) { + $this->application_deployment_queue->update([ + 'status' => ApplicationDeploymentStatus::FAILED->value, + ]); + throw new \RuntimeException('Server is disabled'); + } $commandsText->each(function ($single_command) { $command = data_get($single_command, 'command') ?? $single_command[0] ?? null; if ($command === null) { diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 370c05930..6a3e3f839 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -423,7 +423,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null '--security-opt', '--sysctl', '--ulimit', - '--device' + '--device', ]); $mapping = collect([ '--cap-add' => 'cap_add', @@ -435,6 +435,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null '--init' => 'init', '--ulimit' => 'ulimits', '--privileged' => 'privileged', + '--ip' => 'ip', ]); foreach ($matches as $match) { $option = $match[1]; diff --git a/config/sentry.php b/config/sentry.php index 00c5f77de..40a4c9906 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.224', + 'release' => '4.0.0-beta.225', // 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 c4e13f9de..c3f92910d 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ integer('custom_server_limit')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn('custom_server_limit'); + }); + } +}; diff --git a/database/migrations/2024_02_25_222150_add_server_force_disabled_field.php b/database/migrations/2024_02_25_222150_add_server_force_disabled_field.php new file mode 100644 index 000000000..8509a9909 --- /dev/null +++ b/database/migrations/2024_02_25_222150_add_server_force_disabled_field.php @@ -0,0 +1,28 @@ +boolean('force_disabled')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('server_settings', function (Blueprint $table) { + $table->dropColumn('force_disabled'); + }); + } +}; diff --git a/resources/views/components/banner.blade.php b/resources/views/components/banner.blade.php new file mode 100644 index 000000000..d75a6d75e --- /dev/null +++ b/resources/views/components/banner.blade.php @@ -0,0 +1,22 @@ +@props(['closable' => true]) +
+
- $? - /month + VAT + Custom + {{-- pay-as-you-go --}} - $? - /month + VAT + Custom + {{-- /month + VAT --}}
- - billed monthly + + pay-as-you-go - billed annually + pay-as-you-go @if ($showSubscribeButtons) @isset($ultimate) @@ -219,7 +219,7 @@ d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" /> - Connect unlimited servers + Connect 10+ servers