diff --git a/app/Console/Commands/CleanupApplicationDeploymentQueue.php b/app/Console/Commands/CleanupApplicationDeploymentQueue.php new file mode 100644 index 000000000..7c871d10b --- /dev/null +++ b/app/Console/Commands/CleanupApplicationDeploymentQueue.php @@ -0,0 +1,25 @@ +option('team-id'); + $servers = \App\Models\Server::where('team_id', $team_id)->get(); + foreach ($servers as $server) { + $deployments = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->where("server_id", $server->id)->get(); + foreach ($deployments as $deployment) { + $deployment->update(['status' => 'failed']); + instant_remote_process(['docker rm -f ' . $deployment->deployment_uuid], $server, false); + } + } + } +} diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 0949ef5e6..bc4b655b3 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -167,65 +167,71 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if ($this->application->is_github_based()) { ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS); } + if ($this->application->build_pack === 'dockerfile') { + if (data_get($this->application, 'dockerfile_location')) { + $this->dockerfile_location = $this->application->dockerfile_location; + } + } } } public function handle(): void { - // Generate custom host<->ip mapping - $allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server); - if (!is_null($allContainers)) { - $allContainers = format_docker_command_output_to_json($allContainers); - $ips = collect([]); - if (count($allContainers) > 0) { - $allContainers = $allContainers[0]; - $allContainers = collect($allContainers)->sort()->values(); - foreach ($allContainers as $container) { - $containerName = data_get($container, 'Name'); - if ($containerName === 'coolify-proxy') { - continue; - } - if (preg_match('/-(\d{12})/', $containerName)) { - continue; - } - $containerIp = data_get($container, 'IPv4Address'); - if ($containerName && $containerIp) { - $containerIp = str($containerIp)->before('/'); - $ips->put($containerName, $containerIp->value()); + try { + // Generate custom host<->ip mapping + $allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server); + + if (!is_null($allContainers)) { + $allContainers = format_docker_command_output_to_json($allContainers); + $ips = collect([]); + if (count($allContainers) > 0) { + $allContainers = $allContainers[0]; + $allContainers = collect($allContainers)->sort()->values(); + foreach ($allContainers as $container) { + $containerName = data_get($container, 'Name'); + if ($containerName === 'coolify-proxy') { + continue; + } + if (preg_match('/-(\d{12})/', $containerName)) { + continue; + } + $containerIp = data_get($container, 'IPv4Address'); + if ($containerName && $containerIp) { + $containerIp = str($containerIp)->before('/'); + $ips->put($containerName, $containerIp->value()); + } } } + $this->addHosts = $ips->map(function ($ip, $name) { + return "--add-host $name:$ip"; + })->implode(' '); } - $this->addHosts = $ips->map(function ($ip, $name) { - return "--add-host $name:$ip"; - })->implode(' '); - } - if ($this->application->dockerfile_target_build) { - $this->buildTarget = " --target {$this->application->dockerfile_target_build} "; - } + if ($this->application->dockerfile_target_build) { + $this->buildTarget = " --target {$this->application->dockerfile_target_build} "; + } - // Check custom port - ['repository' => $this->customRepository, 'port' => $this->customPort] = $this->application->customRepository(); + // Check custom port + ['repository' => $this->customRepository, 'port' => $this->customPort] = $this->application->customRepository(); - if (data_get($this->application, 'settings.is_build_server_enabled')) { - $teamId = data_get($this->application, 'environment.project.team.id'); - $buildServers = Server::buildServers($teamId)->get(); - if ($buildServers->count() === 0) { - $this->application_deployment_queue->addLogEntry("Build server feature activated, but no suitable build server found. Using the deployment server."); + if (data_get($this->application, 'settings.is_build_server_enabled')) { + $teamId = data_get($this->application, 'environment.project.team.id'); + $buildServers = Server::buildServers($teamId)->get(); + if ($buildServers->count() === 0) { + $this->application_deployment_queue->addLogEntry("Build server feature activated, but no suitable build server found. Using the deployment server."); + $this->build_server = $this->server; + $this->original_server = $this->server; + } else { + $this->application_deployment_queue->addLogEntry("Build server feature activated and found a suitable build server. Using it to build your application - if needed."); + $this->build_server = $buildServers->random(); + $this->original_server = $this->server; + $this->use_build_server = true; + } + } else { + // Set build server & original_server to the same as deployment server $this->build_server = $this->server; $this->original_server = $this->server; - } else { - $this->application_deployment_queue->addLogEntry("Build server feature activated and found a suitable build server. Using it to build your application - if needed."); - $this->build_server = $buildServers->random(); - $this->original_server = $this->server; - $this->use_build_server = true; } - } else { - // Set build server & original_server to the same as deployment server - $this->build_server = $this->server; - $this->original_server = $this->server; - } - try { if ($this->restart_only && $this->application->build_pack !== 'dockerimage') { $this->just_restart(); if ($this->server->isProxyShouldRun()) { @@ -1660,6 +1666,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); public function failed(Throwable $exception): void { + + $this->next(ApplicationDeploymentStatus::FAILED->value); $this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr'); if (str($exception->getMessage())->isNotEmpty()) { $this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr'); @@ -1667,6 +1675,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if ($this->application->build_pack !== 'dockercompose') { $code = $exception->getCode(); + ray($code); if ($code !== 69420) { // 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one $this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr'); @@ -1675,6 +1684,5 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); ); } } - $this->next(ApplicationDeploymentStatus::FAILED->value); } } diff --git a/app/Livewire/Admin/Index.php b/app/Livewire/Admin/Index.php index 27e912eed..c6b1d0a34 100644 --- a/app/Livewire/Admin/Index.php +++ b/app/Livewire/Admin/Index.php @@ -15,30 +15,26 @@ class Index extends Component if (!isCloud()) { return redirect()->route('dashboard'); } - if (auth()->user()->id !== 0 && session('adminToken') === null) { + if (auth()->user()->id !== 0) { return redirect()->route('dashboard'); } $this->users = User::whereHas('teams', function ($query) { $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); - })->get(); + })->get()->filter(function ($user) { + return $user->id !== 0; + }); } public function switchUser(int $user_id) { - $user = User::find($user_id); - auth()->login($user); - - if ($user_id === 0) { - Cache::forget('team:0'); - session()->forget('adminToken'); - } else { - $token_payload = [ - 'valid' => true, - ]; - $token = Crypt::encrypt($token_payload); - session(['adminToken' => $token]); + if (auth()->user()->id !== 0) { + return redirect()->route('dashboard'); } - session()->regenerate(); - return refreshSession(); + $user = User::find($user_id); + $team_to_switch_to = $user->teams->first(); + Cache::forget("team:{$user->id}"); + auth()->login($user); + refreshSession($team_to_switch_to); + return redirect(request()->header('Referer')); } public function render() { diff --git a/app/Livewire/Dashboard.php b/app/Livewire/Dashboard.php index a44cd18af..632f14d65 100644 --- a/app/Livewire/Dashboard.php +++ b/app/Livewire/Dashboard.php @@ -23,8 +23,8 @@ class Dashboard extends Component public function cleanup_queue() { $this->dispatch('success', 'Cleanup started.'); - Artisan::queue('app:init', [ - '--cleanup-deployments' => 'true' + Artisan::queue('cleanup:application-deployment-queue', [ + '--team-id' => currentTeam()->id ]); } public function get_deployments() diff --git a/app/Livewire/Project/Database/Import.php b/app/Livewire/Project/Database/Import.php index c111753d5..42683c161 100644 --- a/app/Livewire/Project/Database/Import.php +++ b/app/Livewire/Project/Database/Import.php @@ -29,8 +29,8 @@ class Import extends Component public string $container; public array $importCommands = []; public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB'; - public string $mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p $MYSQL_PASSWORD $MYSQL_DATABASE'; - public string $mariadbRestoreCommand = 'mariadb -u $MARIADB_USER -p $MARIADB_PASSWORD $MARIADB_DATABASE'; + public string $mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE'; + public string $mariadbRestoreCommand = 'mariadb -u $MARIADB_USER -p$MARIADB_PASSWORD $MARIADB_DATABASE'; public function getListeners() { diff --git a/app/Livewire/Project/Service/Navbar.php b/app/Livewire/Project/Service/Navbar.php index fda86321b..5640084b9 100644 --- a/app/Livewire/Project/Service/Navbar.php +++ b/app/Livewire/Project/Service/Navbar.php @@ -64,7 +64,7 @@ class Navbar extends Component StopService::run($this->service); $this->service->refresh(); if ($forceCleanup) { - $this->dispatch('success', 'Force cleanup service.'); + $this->dispatch('success', 'Containers cleaned up.'); } else { $this->dispatch('success', 'Service stopped.'); } diff --git a/app/Livewire/Project/Shared/ExecuteContainerCommand.php b/app/Livewire/Project/Shared/ExecuteContainerCommand.php index 3687669a9..a33716e72 100644 --- a/app/Livewire/Project/Shared/ExecuteContainerCommand.php +++ b/app/Livewire/Project/Shared/ExecuteContainerCommand.php @@ -107,6 +107,9 @@ class ExecuteContainerCommand extends Component { $this->validate(); try { + if ($this->server->isForceDisabled()) { + throw new \RuntimeException('Server is disabled.'); + } // Wrap command to prevent escaped execution in the host. $cmd = 'sh -c "if [ -f ~/.profile ]; then . ~/.profile; fi; ' . str_replace('"', '\"', $this->command) . '"'; if (!empty($this->workDir)) { diff --git a/app/Models/Server.php b/app/Models/Server.php index fc400935a..73e10a587 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -147,11 +147,11 @@ class Server extends BaseModel public function skipServer() { if ($this->ip === '1.2.3.4') { - ray('skipping 1.2.3.4'); + // ray('skipping 1.2.3.4'); return true; } if ($this->settings->force_disabled === true) { - ray('force_disabled'); + // ray('force_disabled'); return true; } return false; diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php index 9b34fabae..529dacd7a 100644 --- a/app/Traits/ExecuteRemoteCommand.php +++ b/app/Traits/ExecuteRemoteCommand.php @@ -24,12 +24,6 @@ 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/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index cec7e82aa..13905391e 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -110,6 +110,9 @@ function instant_scp(string $source, string $dest, Server $server, $throwError = } function generateSshCommand(Server $server, string $command) { + if ($server->settings->force_disabled) { + throw new \RuntimeException('Server is disabled.'); + } $user = $server->user; $port = $server->port; $privateKeyLocation = savePrivateKeyToFs($server); diff --git a/config/sentry.php b/config/sentry.php index 40a4c9906..96c7207e0 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.225', + 'release' => '4.0.0-beta.226', // 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 c3f92910d..b0f6d7453 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ user()->name }}

Users

-
+
Root
@foreach ($users as $user) diff --git a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php index 5bcfb9cbc..cb788dda4 100644 --- a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php +++ b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php @@ -1,7 +1,7 @@
-
+
@if ($server->isFunctional())
diff --git a/templates/compose/invoice-ninja.yaml b/templates/compose/invoice-ninja.yaml new file mode 100644 index 000000000..92f6d82fa --- /dev/null +++ b/templates/compose/invoice-ninja.yaml @@ -0,0 +1,100 @@ +# ignore: true +# documentation: https://invoiceninja.github.io/selfhost.html +# slogan: The leading open-source invoicing platform +# tags: invoicing, billing, accounting, finance, self-hosted + +services: + invoice-ninja: + image: invoiceninja/invoiceninja:5 + environment: + - SERVICE_FQDN_INVOICENINJA + - APP_ENV=production + - APP_URL=${SERVICE_FQDN_INVOICENINJA} + - APP_KEY=${SERVICE_BASE64_INVOICENINJA} + - APP_DEBUG=false + - REQUIRE_HTTPS=false + - PHANTOMJS_PDF_GENERATION=false + - PDF_GENERATOR=snappdf + - TRUSTED_PROXIES=* + - QUEUE_CONNECTION=database + - DB_HOST=mysql + - DB_PORT=3306 + - DB_DATABASE=${MYSQL_DATABASE:-invoice_ninja} + - DB_USERNAME=${SERVICE_USER_MYSQL} + - DB_PASSWORD=${SERVICE_PASSWORD_MYSQL} + volumes: + - invoice-ninja-public:/var/www/app/public + - invoice-ninja-storage:/var/www/app/storage + - type: bind + source: ./php.ini + target: /usr/local/etc/php/php.ini + content: | + session.auto_start = Off + short_open_tag = Off + + error_reporting = E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT & ~E_DEPRECATED + + ; opcache.enable=1 + ; opcache.preload=/srv/www/invoiceninja/current/preload.php + ; opcache.preload_user=www-data + + ; ; The OPcache shared memory storage size. + ; opcache.max_accelerated_files=300000 + ; opcache.validate_timestamps=1 + ; opcache.revalidate_freq=30 + ; opcache.jit_buffer_size=256M + ; opcache.jit=1205 + ; opcache.memory_consumption=1024M + + + post_max_size = 60M + upload_max_filesize = 50M + memory_limit=512M + - type: bind + source: ./php-cli.ini + target: /usr/local/etc/php/php-cli.ini + content: | + session.auto_start = Off + short_open_tag = Off + + error_reporting = E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT & ~E_DEPRECATED + + ; opcache.enable_cli=1 + ; opcache.fast_shutdown=1 + ; opcache.memory_consumption=256 + ; opcache.interned_strings_buffer=8 + ; opcache.max_accelerated_files=4000 + ; opcache.revalidate_freq=60 + ; # http://symfony.com/doc/current/performance.html + ; realpath_cache_size = 4096K + ; realpath_cache_ttl = 600 + + memory_limit = 2G + post_max_size = 60M + upload_max_filesize = 50M + depends_on: + mysql: + condition: service_healthy + mysql: + image: mariadb:lts + environment: + - MYSQL_USER=${SERVICE_USER_MYSQL} + - MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL} + - MYSQL_DATABASE=${MYSQL_DATABASE:-invoice_ninja} + - MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT} + healthcheck: + test: + [ + "CMD", + "mysqladmin", + "ping", + "-h", + "localhost", + "-uroot", + "-p${SERVICE_PASSWORD_MYSQLROOT}", + ] + interval: 5s + timeout: 20s + retries: 10 + volumes: + - invoice-ninja-mysql-data:/var/lib/mysql diff --git a/versions.json b/versions.json index 2dab168ea..f26a88b43 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "3.12.36" }, "v4": { - "version": "4.0.0-beta.225" + "version": "4.0.0-beta.226" } } }