Merge branch 'services' into services
This commit is contained in:
commit
9b040422d7
204 changed files with 11648 additions and 2791 deletions
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -8,4 +8,4 @@
|
|||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
||||
.styleci.yml export-ignore
|
||||
|
|
@ -11,7 +11,7 @@ on:
|
|||
- docker/coolify-helper/Dockerfile
|
||||
- docker/coolify-realtime/Dockerfile
|
||||
- docker/testing-host/Dockerfile
|
||||
- templates/*
|
||||
- templates/**
|
||||
|
||||
env:
|
||||
GITHUB_REGISTRY: ghcr.io
|
||||
|
|
|
|||
2
.github/workflows/coolify-staging-build.yml
vendored
2
.github/workflows/coolify-staging-build.yml
vendored
|
|
@ -11,7 +11,7 @@ on:
|
|||
- docker/coolify-helper/Dockerfile
|
||||
- docker/coolify-realtime/Dockerfile
|
||||
- docker/testing-host/Dockerfile
|
||||
- templates/*
|
||||
- templates/**
|
||||
|
||||
env:
|
||||
GITHUB_REGISTRY: ghcr.io
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -35,3 +35,4 @@ scripts/load-test/*
|
|||
.ignition.json
|
||||
.env.dusk.local
|
||||
docker/coolify-realtime/node_modules
|
||||
.DS_Store
|
||||
|
|
|
|||
65
.gitpod.yml
65
.gitpod.yml
|
|
@ -1,65 +0,0 @@
|
|||
tasks:
|
||||
- name: Setup Spin environment and Composer dependencies
|
||||
# Fix because of https://github.com/gitpod-io/gitpod/issues/16614
|
||||
before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m)
|
||||
init: |
|
||||
cp .env.development.example .env &&
|
||||
sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env
|
||||
sed -i "s#USERID=#USERID=33333#g" .env
|
||||
sed -i "s#GROUPID=#GROUPID=33333#g" .env
|
||||
composer install --ignore-platform-reqs
|
||||
./vendor/bin/spin up -d
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan key:generate
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan storage:link
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed
|
||||
cat .coolify-logo
|
||||
gp sync-done spin-is-ready
|
||||
|
||||
- name: Install Node dependencies and run Vite
|
||||
command: |
|
||||
echo "Waiting for Sail environment to boot up."
|
||||
gp sync-await spin-is-ready
|
||||
./vendor/bin/spin exec vite npm install
|
||||
./vendor/bin/spin exec vite npm run dev -- --host
|
||||
|
||||
- name: Laravel Queue Worker, listening to code changes
|
||||
command: |
|
||||
echo "Waiting for Sail environment to boot up."
|
||||
gp sync-await spin-is-ready
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan queue:listen
|
||||
|
||||
ports:
|
||||
- port: 5432
|
||||
onOpen: ignore
|
||||
name: PostgreSQL
|
||||
visibility: public
|
||||
- port: 5173
|
||||
onOpen: ignore
|
||||
visibility: public
|
||||
name: Node Server for Vite
|
||||
- port: 8000
|
||||
onOpen: ignore
|
||||
visibility: public
|
||||
name: Coolify
|
||||
|
||||
# Configure vscode
|
||||
vscode:
|
||||
extensions:
|
||||
- bmewburn.vscode-intelephense-client
|
||||
- ikappas.composer
|
||||
- ms-azuretools.vscode-docker
|
||||
- ecmel.vscode-html-css
|
||||
- MehediDracula.php-namespace-resolver
|
||||
- wmaurer.change-case
|
||||
- Equinusocio.vsc-community-material-theme
|
||||
- EditorConfig.EditorConfig
|
||||
- streetsidesoftware.code-spell-checker
|
||||
- rangav.vscode-thunder-client
|
||||
- PKief.material-icon-theme
|
||||
- cierra.livewire-vscode
|
||||
- lennardv.livewire-goto-updated
|
||||
- bradlc.vscode-tailwindcss
|
||||
- heybourn.headwind
|
||||
- adrianwilczynski.alpine-js-intellisense
|
||||
- amiralizadeh9480.laravel-extra-intellisense
|
||||
- shufo.vscode-blade-formatter
|
||||
24
README.md
24
README.md
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
# About the Project
|
||||
|
||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||
|
||||
It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else.
|
||||
|
||||
|
|
@ -22,6 +22,9 @@ # Installation
|
|||
```
|
||||
You can find the installation script source [here](./scripts/install.sh).
|
||||
|
||||
> [!NOTE]
|
||||
> Please refer to the [docs](https://coolify.io/docs/installation) for more information about the installation.
|
||||
|
||||
# Support
|
||||
|
||||
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
|
||||
|
|
@ -37,21 +40,20 @@ # Donations
|
|||
|
||||
### Special Sponsors
|
||||
|
||||

|
||||

|
||||
|
||||
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
|
||||
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
|
||||
* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities.
|
||||
* [Tolgee](https://tolgee.io/?ref=coolify) - Developer & translator friendly web-based localization platform.
|
||||
* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies.
|
||||
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
|
||||
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
|
||||
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
||||
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management.
|
||||
* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions.
|
||||
* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies.
|
||||
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
|
||||
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers.
|
||||
* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses.
|
||||
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
|
||||
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries.
|
||||
* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools.
|
||||
|
|
@ -60,6 +62,7 @@ ### Special Sponsors
|
|||
* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses.
|
||||
* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly.
|
||||
* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
||||
* [LiquidWeb](https://liquidweb.com/?utm_source=coolify.io) - Fast web hosting provider.
|
||||
|
||||
|
||||
## Github Sponsors ($40+)
|
||||
|
|
@ -88,6 +91,11 @@ ## Github Sponsors ($40+)
|
|||
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
||||
<a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
|
||||
<a href="https://x.com/adithsuhas17?utm_source=coolify.io"><img src="https://github.com/adith-suhas-sv.png" width="60px" alt="Adith Suhas" /></a>
|
||||
<a href="https://startupfa.me?utm_source=coolify.io"><img src="https://github.com/startupfame.png" width="60px" alt="StartupFame" /></a>
|
||||
<a href="https://jonasjaeger.com?utm_source=coolify.io"><img src="https://github.com/toxin20.png" width="60px" alt="Jonas Jaeger" /></a>
|
||||
<a href="https://github.com/therealjp?utm_source=coolify.io"><img src="https://github.com/therealjp.png" width="60px" alt="JP" /></a>
|
||||
<a href="https://evercam.io/?utm_source=coolify.io"><img src="https://github.com/evercam.png" width="60px" alt="Evercam" /></a>
|
||||
<a href="https://web3.career/?utm_source=coolify.io"><img src="https://web3.career/favicon1.png" width="60px" alt="Web3 Career" /></a>
|
||||
|
||||
## Organizations
|
||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||
|
|
@ -121,7 +129,6 @@ ## Why should I use the Cloud version?
|
|||
- Better support
|
||||
- Less maintenance for you
|
||||
|
||||
|
||||
# Recognitions
|
||||
|
||||
<p>
|
||||
|
|
@ -138,6 +145,13 @@ # Recognitions
|
|||
|
||||
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
# Core Maintainers
|
||||
|
||||
| Andras Bacsai | Peak |
|
||||
|------------|------------|
|
||||
| <img src="https://github.com/andrasbacsai.png" width="200px" alt="Andras Bacsai" /> | <img src="https://github.com/peaklabs-dev.png" width="200px" alt="Peak Labs" /> |
|
||||
| <a href="https://x.com/heyandras"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Twitter.svg" width="25px"></a> <a href="https://github.com/andrasbacsai"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Github.svg" width="25px"></a> | <a href="https://x.com/peaklabs_dev"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Twitter.svg" width="25px"></a> <a href="https://github.com/peaklabs-dev"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Github.svg" width="25px"></a> |
|
||||
|
||||
# Repo Activity
|
||||
|
||||

|
||||
|
|
|
|||
33
RELEASE.md
33
RELEASE.md
|
|
@ -1,6 +1,6 @@
|
|||
# Coolify Release Guide
|
||||
|
||||
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
|
||||
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how Coolify releases are managed and deployed.
|
||||
|
||||
## Table of Contents
|
||||
- [Release Process](#release-process)
|
||||
|
|
@ -19,19 +19,19 @@ ## Release Process
|
|||
- Improvements, fixes, and new features are developed on the `next` branch or separate feature branches.
|
||||
|
||||
2. **Merging to `main`**
|
||||
- Once ready, changes are merged from the `next` branch into the `main` branch.
|
||||
- Once ready, changes are merged from the `next` branch into the `main` branch (via a pull request).
|
||||
|
||||
3. **Building the Release**
|
||||
- After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry with the version tag and the `latest` tag.
|
||||
- After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry and Docker Hub with the specific version tag and the `latest` tag.
|
||||
|
||||
4. **Creating a GitHub Release**
|
||||
- A new GitHub release is manually created with details of the changes made in the version.
|
||||
|
||||
5. **Updating the CDN**
|
||||
- To make a new version publicly available, the version information on the CDN needs to be updated: [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
|
||||
- To make a new version publicly available, the version information on the CDN needs to be updated manually. After that the new version number will be available at [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json).
|
||||
|
||||
> [!NOTE]
|
||||
> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated.**
|
||||
> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated. After the CDN is updated, a discord announcement will be made in the Production Release channel.**
|
||||
|
||||
## Version Types
|
||||
|
||||
|
|
@ -39,10 +39,10 @@ ## Version Types
|
|||
<summary><strong>Stable (coming soon)</strong></summary>
|
||||
|
||||
- **Stable**
|
||||
- The production version suitable for stable, production environments (generally recommended).
|
||||
- **Update Frequency:** Every 2 to 4 weeks, with more frequent possible hotfixes.
|
||||
- The production version suitable for stable, production environments (recommended).
|
||||
- **Update Frequency:** Every 2 to 4 weeks, with more frequent possible fixes.
|
||||
- **Release Size:** Larger but less frequent releases. Multiple nightly versions are consolidated into a single stable release.
|
||||
- **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`).
|
||||
- **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`, `4.1.0`, etc.).
|
||||
- **Installation Command:**
|
||||
```bash
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
|
|
@ -57,7 +57,7 @@ ## Version Types
|
|||
- The latest development version, suitable for testing the latest changes and experimenting with new features.
|
||||
- **Update Frequency:** Daily or bi-weekly updates.
|
||||
- **Release Size:** Smaller, more frequent releases.
|
||||
- **Versioning Scheme:** TO BE DETERMINED
|
||||
- **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-nightly.1`, `4.1.0-nightly.2`, etc.).
|
||||
- **Installation Command:**
|
||||
```bash
|
||||
curl -fsSL https://cdn.coollabs.io/coolify-nightly/install.sh | bash -s next
|
||||
|
|
@ -73,11 +73,11 @@ ## Version Types
|
|||
- **Purpose:** Allows users to test and provide feedback on new features and changes before they become stable.
|
||||
- **Update Frequency:** Available if we think beta testing is necessary.
|
||||
- **Release Size:** Same size as stable release as it will become the next stabe release after some time.
|
||||
- **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`).
|
||||
- **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`, `4.1.0-beta.2`, etc.).
|
||||
- **Installation Command:**
|
||||
```bash
|
||||
```bash
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
```
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
|
@ -117,12 +117,15 @@ ### Cloud
|
|||
> [!IMPORTANT]
|
||||
> The cloud version of Coolify may be several versions behind the latest GitHub releases even if the CDN is updated. This is intentional to ensure stability and reliability for cloud users and Andras will manully update the cloud version when the update is ready.
|
||||
|
||||
## Manually Update to Specific Versions
|
||||
## Manually Update/ Downgrade to Specific Versions
|
||||
|
||||
> [!CAUTION]
|
||||
> Updating to unreleased versions is not recommended and may cause issues. Use at your own risk!
|
||||
> Updating to unreleased versions is not recommended and can cause issues.
|
||||
|
||||
To update your Coolify instance to a specific (unreleased) version, use the following command:
|
||||
> [!IMPORTANT]
|
||||
> Downgrading is supported but not recommended and can cause issues because of database migrations and other changes.
|
||||
|
||||
To update your Coolify instance to a specific version, use the following command:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
|
||||
|
|
|
|||
23
SECURITY.md
23
SECURITY.md
|
|
@ -2,15 +2,24 @@ # Security Policy
|
|||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
Currently supported, maintained and updated versions:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| > 4 | :white_check_mark: |
|
||||
| 3 | :x: |
|
||||
| Version | Supported | Support Status |
|
||||
| ------- | ------------------ | -------------- |
|
||||
| 4.x | :white_check_mark: | Active Development & Security Updates |
|
||||
| < 4.0 | :x: | End of Life (no security updates) |
|
||||
|
||||
## Security Updates
|
||||
|
||||
We take security seriously. Security updates are released as soon as possible after a vulnerability is discovered and verified.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you have any vulnerability please report at hi@coollabs.io
|
||||
If you discover a security vulnerability, please follow these steps:
|
||||
|
||||
1. **DO NOT** disclose the vulnerability publicly.
|
||||
2. Send a detailed report to: `hi@coollabs.io`.
|
||||
3. Include in your report:
|
||||
- A description of the vulnerability
|
||||
- Steps to reproduce the issue
|
||||
- Potential impact
|
||||
|
|
|
|||
37
app/Actions/Application/IsHorizonQueueEmpty.php
Normal file
37
app/Actions/Application/IsHorizonQueueEmpty.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Actions\Application;
|
||||
|
||||
use Laravel\Horizon\Contracts\JobRepository;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class IsHorizonQueueEmpty
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$hostname = gethostname();
|
||||
$recent = app(JobRepository::class)->getRecent();
|
||||
if ($recent) {
|
||||
$running = $recent->filter(function ($job) use ($hostname) {
|
||||
$payload = json_decode($job->payload);
|
||||
$tags = data_get($payload, 'tags');
|
||||
|
||||
return $job->status != 'completed' &&
|
||||
$job->status != 'failed' &&
|
||||
isset($tags) &&
|
||||
is_array($tags) &&
|
||||
in_array('server:'.$hostname, $tags);
|
||||
});
|
||||
if ($running->count() > 0) {
|
||||
echo 'false';
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
echo 'true';
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@ class StopApplication
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true)
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
namespace App\Actions\CoolifyTask;
|
||||
|
||||
use App\Data\CoolifyTaskArgs;
|
||||
use App\Enums\ActivityTypes;
|
||||
use App\Jobs\CoolifyTask;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
|
|
@ -47,11 +46,7 @@ public function __invoke(): Activity
|
|||
call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish,
|
||||
call_event_data: $this->remoteProcessArgs->call_event_data,
|
||||
);
|
||||
if ($this->remoteProcessArgs->type === ActivityTypes::COMMAND->value) {
|
||||
dispatch($job)->onQueue('high');
|
||||
} else {
|
||||
dispatch($job);
|
||||
}
|
||||
dispatch($job);
|
||||
$this->activity->refresh();
|
||||
|
||||
return $this->activity;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ class StartDatabase
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
|
|
@ -49,7 +51,7 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
|||
break;
|
||||
}
|
||||
if ($database->is_public && $database->public_port) {
|
||||
StartDatabaseProxy::dispatch($database)->onQueue('high');
|
||||
StartDatabaseProxy::dispatch($database);
|
||||
}
|
||||
|
||||
return $activity;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ class StartDatabaseProxy
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase $database)
|
||||
{
|
||||
$internalPort = null;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ class StopDatabaseProxy
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|ServiceDatabase|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||
{
|
||||
$server = data_get($database, 'destination.server');
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Notifications\Container\ContainerRestarted;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
|
@ -16,6 +15,8 @@ class GetContainersStatus
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public $applications;
|
||||
|
||||
public ?Collection $containers;
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Actions\License;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class CheckResaleLicense
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$settings = instanceSettings();
|
||||
if (isDev()) {
|
||||
$settings->update([
|
||||
'is_resale_license_active' => true,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
// if (!$settings->resale_license) {
|
||||
// return;
|
||||
// }
|
||||
$base_url = config('coolify.license_url');
|
||||
$instance_id = config('app.id');
|
||||
$data = Http::withHeaders([
|
||||
'Accept' => 'application/json',
|
||||
])->get("$base_url/lemon/validate", [
|
||||
'license_key' => $settings->resale_license,
|
||||
'instance_id' => $instance_id,
|
||||
])->json();
|
||||
if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') {
|
||||
$settings->update([
|
||||
'is_resale_license_active' => true,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
$data = Http::withHeaders([
|
||||
'Accept' => 'application/json',
|
||||
])->get("$base_url/lemon/activate", [
|
||||
'license_key' => $settings->resale_license,
|
||||
'instance_id' => $instance_id,
|
||||
])->json();
|
||||
if (data_get($data, 'activated') === true) {
|
||||
$settings->update([
|
||||
'is_resale_license_active' => true,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
if (data_get($data, 'license_key.status') === 'active') {
|
||||
throw new \Exception('Invalid license key.');
|
||||
}
|
||||
throw new \Exception('Cannot activate license key.');
|
||||
} catch (\Throwable $e) {
|
||||
$settings->update([
|
||||
'resale_license' => null,
|
||||
'is_resale_license_active' => false,
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,11 +9,13 @@ class CleanupDocker
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Server $server)
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
$helperImageVersion = data_get($settings, 'helper_version');
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$helperImage = config('constants.coolify.helper_image');
|
||||
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
||||
|
||||
$commands = [
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class InstallDocker
|
|||
|
||||
public function handle(Server $server)
|
||||
{
|
||||
$dockerVersion = config('constants.docker_install_version');
|
||||
$dockerVersion = config('constants.docker.minimum_required_version');
|
||||
$supported_os_type = $server->validateOS();
|
||||
if (! $supported_os_type) {
|
||||
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
|
||||
|
|
|
|||
|
|
@ -130,10 +130,10 @@ private function checkLogDrainContainer()
|
|||
if ($foundLogDrainContainer) {
|
||||
$status = data_get($foundLogDrainContainer, 'State.Status');
|
||||
if ($status !== 'running') {
|
||||
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||
StartLogDrain::dispatch($this->server);
|
||||
}
|
||||
} else {
|
||||
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||
StartLogDrain::dispatch($this->server);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ class StartLogDrain
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Server $server)
|
||||
{
|
||||
if ($server->settings->is_logdrain_newrelic_enabled) {
|
||||
|
|
@ -169,7 +171,7 @@ public function handle(Server $server)
|
|||
');
|
||||
$license_key = $server->settings->logdrain_newrelic_license_key;
|
||||
$base_uri = $server->settings->logdrain_newrelic_base_uri;
|
||||
$base_path = config('coolify.base_config_path');
|
||||
$base_path = config('constants.coolify.base_config_path');
|
||||
|
||||
$config_path = $base_path.'/log-drains';
|
||||
$fluent_bit_config = $config_path.'/fluent-bit.conf';
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public function handle($manual_update = false)
|
|||
if (! $this->server) {
|
||||
return;
|
||||
}
|
||||
CleanupDocker::dispatch($this->server)->onQueue('high');
|
||||
CleanupDocker::dispatch($this->server);
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('version');
|
||||
if (! $manual_update) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ class ValidateServer
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public ?string $uptime = null;
|
||||
|
||||
public ?string $error = null;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ class RestartService
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Service $service)
|
||||
{
|
||||
StopService::run($service);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ class StartService
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$service->saveComposeConfigs();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ class StopService
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public string $jobQueue = 'high';
|
||||
|
||||
public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true)
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ class CleanupRedis extends Command
|
|||
|
||||
public function handle()
|
||||
{
|
||||
echo "Cleanup Redis keys.\n";
|
||||
$prefix = config('database.redis.options.prefix');
|
||||
|
||||
$keys = Redis::connection()->keys('*:laravel*');
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ class CleanupStuckedResources extends Command
|
|||
|
||||
public function handle()
|
||||
{
|
||||
echo "Running cleanup stucked resources.\n";
|
||||
$this->cleanup_stucked_resources();
|
||||
}
|
||||
|
||||
|
|
|
|||
49
app/Console/Commands/CloudCheckSubscription.php
Normal file
49
app/Console/Commands/CloudCheckSubscription.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Team;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CloudCheckSubscription extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'cloud:check-subscription';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check Cloud subscriptions';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||
$activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->get();
|
||||
foreach ($activeSubscribers as $team) {
|
||||
$stripeSubscriptionId = $team->subscription->stripe_subscription_id;
|
||||
$stripeInvoicePaid = $team->subscription->stripe_invoice_paid;
|
||||
$stripeCustomerId = $team->subscription->stripe_customer_id;
|
||||
if (! $stripeSubscriptionId) {
|
||||
echo "Team {$team->id} has no subscription, but invoice status is: {$stripeInvoicePaid}\n";
|
||||
echo "Link on Stripe: https://dashboard.stripe.com/customers/{$stripeCustomerId}\n";
|
||||
|
||||
continue;
|
||||
}
|
||||
$subscription = $stripe->subscriptions->retrieve($stripeSubscriptionId);
|
||||
if ($subscription->status === 'active') {
|
||||
continue;
|
||||
}
|
||||
echo "Subscription {$stripeSubscriptionId} is not active ({$subscription->status})\n";
|
||||
echo "Link on Stripe: https://dashboard.stripe.com/subscriptions/{$stripeSubscriptionId}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Dev extends Command
|
||||
{
|
||||
|
|
@ -31,19 +32,32 @@ public function generateOpenApi()
|
|||
{
|
||||
// Generate OpenAPI documentation
|
||||
echo "Generating OpenAPI documentation.\n";
|
||||
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
||||
// https://github.com/OAI/OpenAPI-Specification/releases
|
||||
$process = Process::run([
|
||||
'/var/www/html/vendor/bin/openapi',
|
||||
'app',
|
||||
'-o',
|
||||
'openapi.yaml',
|
||||
'--version',
|
||||
'3.1.0',
|
||||
]);
|
||||
$error = $process->errorOutput();
|
||||
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||
echo $error;
|
||||
echo $process->output();
|
||||
// Convert YAML to JSON
|
||||
$yaml = file_get_contents('openapi.yaml');
|
||||
$json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT);
|
||||
file_put_contents('openapi.json', $json);
|
||||
echo "Converted OpenAPI YAML to JSON.\n";
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
// Generate APP_KEY if not exists
|
||||
|
||||
if (empty(env('APP_KEY'))) {
|
||||
if (empty(config('app.key'))) {
|
||||
echo "Generating APP_KEY.\n";
|
||||
Artisan::call('key:generate');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ class Horizon extends Command
|
|||
|
||||
public function handle()
|
||||
{
|
||||
if (config('coolify.is_horizon_enabled')) {
|
||||
$this->info('Horizon is enabled. Starting.');
|
||||
if (config('constants.horizon.is_horizon_enabled')) {
|
||||
$this->info('[x]: Horizon is enabled. Starting.');
|
||||
$this->call('horizon');
|
||||
exit(0);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Actions\Server\StopSentinel;
|
||||
use App\Enums\ActivityTypes;
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Jobs\CheckHelperImageJob;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Environment;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
|
|
@ -25,6 +26,8 @@ class Init extends Command
|
|||
|
||||
public function handle()
|
||||
{
|
||||
$this->optimize();
|
||||
|
||||
if (isCloud() && ! $this->option('force-cloud')) {
|
||||
echo "Skipping init as we are on cloud and --force-cloud option is not set\n";
|
||||
|
||||
|
|
@ -39,7 +42,6 @@ public function handle()
|
|||
}
|
||||
|
||||
// Backward compatibility
|
||||
// $this->disable_metrics();
|
||||
$this->replace_slash_in_environment_name();
|
||||
$this->restore_coolify_db_backup();
|
||||
$this->update_user_emails();
|
||||
|
|
@ -53,16 +55,32 @@ public function handle()
|
|||
} else {
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
}
|
||||
echo "[3]: Cleanup Redis keys.\n";
|
||||
$this->call('cleanup:redis');
|
||||
|
||||
echo "[4]: Cleanup stucked resources.\n";
|
||||
$this->call('cleanup:stucked-resources');
|
||||
|
||||
try {
|
||||
$this->pullHelperImage();
|
||||
} catch (\Throwable $e) {
|
||||
//
|
||||
}
|
||||
|
||||
if (isCloud()) {
|
||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||
if ($response->successful()) {
|
||||
$services = $response->json();
|
||||
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||
try {
|
||||
$this->pullTemplatesFromCDN();
|
||||
} catch (\Throwable $e) {
|
||||
echo "Could not pull templates from CDN: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! isCloud()) {
|
||||
try {
|
||||
$this->pullTemplatesFromCDN();
|
||||
} catch (\Throwable $e) {
|
||||
echo "Could not pull templates from CDN: {$e->getMessage()}\n";
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$localhost = $this->servers->where('id', 0)->first();
|
||||
$localhost->setupDynamicProxyConfiguration();
|
||||
|
|
@ -70,8 +88,8 @@ public function handle()
|
|||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||
}
|
||||
$settings = instanceSettings();
|
||||
if (! is_null(env('AUTOUPDATE', null))) {
|
||||
if (env('AUTOUPDATE') == true) {
|
||||
if (! is_null(config('constants.coolify.autoupdate', null))) {
|
||||
if (config('constants.coolify.autoupdate') == true) {
|
||||
$settings->update(['is_auto_update_enabled' => true]);
|
||||
} else {
|
||||
$settings->update(['is_auto_update_enabled' => false]);
|
||||
|
|
@ -80,19 +98,26 @@ public function handle()
|
|||
}
|
||||
}
|
||||
|
||||
// private function disable_metrics()
|
||||
// {
|
||||
// if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||
// foreach ($this->servers as $server) {
|
||||
// if ($server->settings->is_metrics_enabled === true) {
|
||||
// $server->settings->update(['is_metrics_enabled' => false]);
|
||||
// }
|
||||
// if ($server->isFunctional()) {
|
||||
// StopSentinel::dispatch($server)->onQueue('high');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
private function pullHelperImage()
|
||||
{
|
||||
CheckHelperImageJob::dispatch();
|
||||
}
|
||||
|
||||
private function pullTemplatesFromCDN()
|
||||
{
|
||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||
if ($response->successful()) {
|
||||
$services = $response->json();
|
||||
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||
}
|
||||
}
|
||||
|
||||
private function optimize()
|
||||
{
|
||||
echo "[1]: Optimizing Laravel (caching config, routes, views).\n";
|
||||
Artisan::call('optimize:clear');
|
||||
Artisan::call('optimize');
|
||||
}
|
||||
|
||||
private function update_user_emails()
|
||||
{
|
||||
|
|
@ -207,15 +232,15 @@ private function send_alive_signal()
|
|||
$settings = instanceSettings();
|
||||
$do_not_track = data_get($settings, 'do_not_track');
|
||||
if ($do_not_track == true) {
|
||||
echo "Skipping alive as do_not_track is enabled\n";
|
||||
echo "[2]: Skipping sending live signal as do_not_track is enabled\n";
|
||||
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
|
||||
echo "I am alive!\n";
|
||||
echo "[2]: Sending live signal!\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in alive: {$e->getMessage()}\n";
|
||||
echo "[2]: Error in sending live signal: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,15 @@ public function handle()
|
|||
{
|
||||
// Generate OpenAPI documentation
|
||||
echo "Generating OpenAPI documentation.\n";
|
||||
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
||||
// https://github.com/OAI/OpenAPI-Specification/releases
|
||||
$process = Process::run([
|
||||
'/var/www/html/vendor/bin/openapi',
|
||||
'app',
|
||||
'-o',
|
||||
'openapi.yaml',
|
||||
'--version',
|
||||
'3.1.0',
|
||||
]);
|
||||
$error = $process->errorOutput();
|
||||
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ class Scheduler extends Command
|
|||
|
||||
public function handle()
|
||||
{
|
||||
if (config('coolify.is_scheduler_enabled')) {
|
||||
$this->info('Scheduler is enabled. Starting.');
|
||||
if (config('constants.horizon.is_scheduler_enabled')) {
|
||||
$this->info('[x]: Scheduler is enabled. Starting.');
|
||||
$this->call('schedule:work');
|
||||
exit(0);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ private function deleteApplication()
|
|||
if (! $confirmed) {
|
||||
break;
|
||||
}
|
||||
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||
DeleteResourceJob::dispatch($toDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ private function deleteDatabase()
|
|||
if (! $confirmed) {
|
||||
return;
|
||||
}
|
||||
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||
DeleteResourceJob::dispatch($toDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ private function deleteService()
|
|||
if (! $confirmed) {
|
||||
return;
|
||||
}
|
||||
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||
DeleteResourceJob::dispatch($toDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public function handle()
|
|||
|
||||
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
||||
$headers = [
|
||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
||||
'AccessKey' => config('constants.bunny.storage_api_key'),
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
];
|
||||
|
|
@ -69,7 +69,7 @@ public function handle()
|
|||
});
|
||||
PendingRequest::macro('purge', function ($url) use ($that) {
|
||||
$headers = [
|
||||
'AccessKey' => env('BUNNY_API_KEY'),
|
||||
'AccessKey' => config('constants.bunny.api_key'),
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
$that->info('Purging: '.$url);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ class Kernel extends ConsoleKernel
|
|||
{
|
||||
private $allServers;
|
||||
|
||||
private Schedule $scheduleInstance;
|
||||
|
||||
private InstanceSettings $settings;
|
||||
|
||||
private string $updateCheckFrequency;
|
||||
|
|
@ -36,82 +38,90 @@ class Kernel extends ConsoleKernel
|
|||
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
$this->scheduleInstance = $schedule;
|
||||
$this->allServers = Server::where('ip', '!=', '1.2.3.4');
|
||||
|
||||
$this->settings = instanceSettings();
|
||||
$this->updateCheckFrequency = $this->settings->update_check_frequency ?: '0 * * * *';
|
||||
|
||||
$this->instanceTimezone = $this->settings->instance_timezone ?: config('app.timezone');
|
||||
|
||||
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||
if (validate_timezone($this->instanceTimezone) === false) {
|
||||
$this->instanceTimezone = config('app.timezone');
|
||||
}
|
||||
|
||||
$this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||
|
||||
if (isDev()) {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
$schedule->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer();
|
||||
$this->scheduleInstance->command('horizon:snapshot')->everyMinute();
|
||||
$this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
$this->scheduleInstance->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer();
|
||||
|
||||
// Server Jobs
|
||||
$this->checkResources($schedule);
|
||||
$this->checkResources();
|
||||
|
||||
$this->checkScheduledBackups($schedule);
|
||||
$this->checkScheduledTasks($schedule);
|
||||
$this->checkScheduledBackups();
|
||||
$this->checkScheduledTasks();
|
||||
|
||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||
$this->scheduleInstance->command('uploads:clear')->everyTwoMinutes();
|
||||
|
||||
} else {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
||||
$schedule->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
$this->scheduleUpdates($schedule);
|
||||
$this->scheduleInstance->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$this->scheduleInstance->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
||||
|
||||
$this->scheduleInstance->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||
|
||||
$this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
$this->scheduleUpdates();
|
||||
|
||||
// Server Jobs
|
||||
$this->checkResources($schedule);
|
||||
$this->checkResources();
|
||||
|
||||
$this->pullImages($schedule);
|
||||
$this->pullImages();
|
||||
|
||||
$this->checkScheduledBackups($schedule);
|
||||
$this->checkScheduledTasks($schedule);
|
||||
$this->checkScheduledBackups();
|
||||
$this->checkScheduledTasks();
|
||||
|
||||
$schedule->command('cleanup:database --yes')->daily();
|
||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||
$this->scheduleInstance->command('cleanup:database --yes')->daily();
|
||||
$this->scheduleInstance->command('uploads:clear')->everyTwoMinutes();
|
||||
}
|
||||
}
|
||||
|
||||
private function pullImages($schedule): void
|
||||
private function pullImages(): void
|
||||
{
|
||||
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
||||
foreach ($servers as $server) {
|
||||
if ($server->isSentinelEnabled()) {
|
||||
$schedule->job(function () use ($server) {
|
||||
$this->scheduleInstance->job(function () use ($server) {
|
||||
CheckAndStartSentinelJob::dispatch($server);
|
||||
})->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||
}
|
||||
}
|
||||
$schedule->job(new CheckHelperImageJob)
|
||||
$this->scheduleInstance->job(new CheckHelperImageJob)
|
||||
->cron($this->updateCheckFrequency)
|
||||
->timezone($this->instanceTimezone)
|
||||
->onOneServer();
|
||||
}
|
||||
|
||||
private function scheduleUpdates($schedule): void
|
||||
private function scheduleUpdates(): void
|
||||
{
|
||||
$schedule->job(new CheckForUpdatesJob)
|
||||
$this->scheduleInstance->job(new CheckForUpdatesJob)
|
||||
->cron($this->updateCheckFrequency)
|
||||
->timezone($this->instanceTimezone)
|
||||
->onOneServer();
|
||||
|
||||
if ($this->settings->is_auto_update_enabled) {
|
||||
$autoUpdateFrequency = $this->settings->auto_update_frequency;
|
||||
$schedule->job(new UpdateCoolifyJob)
|
||||
$this->scheduleInstance->job(new UpdateCoolifyJob)
|
||||
->cron($autoUpdateFrequency)
|
||||
->timezone($this->instanceTimezone)
|
||||
->onOneServer();
|
||||
}
|
||||
}
|
||||
|
||||
private function checkResources($schedule): void
|
||||
private function checkResources(): void
|
||||
{
|
||||
if (isCloud()) {
|
||||
$servers = $this->allServers->whereHas('team.subscription')->get();
|
||||
|
|
@ -128,31 +138,34 @@ private function checkResources($schedule): void
|
|||
$lastSentinelUpdate = $server->sentinel_updated_at;
|
||||
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
||||
// Check container status every minute if Sentinel does not activated
|
||||
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||
// $schedule->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
|
||||
if (validate_timezone($serverTimezone) === false) {
|
||||
$serverTimezone = config('app.timezone');
|
||||
}
|
||||
$this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer();
|
||||
// $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
|
||||
|
||||
// Check storage usage every 10 minutes if Sentinel does not activated
|
||||
$schedule->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
||||
$this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
||||
}
|
||||
if ($server->settings->force_docker_cleanup) {
|
||||
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||
$this->scheduleInstance->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||
} else {
|
||||
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
||||
$this->scheduleInstance->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
||||
}
|
||||
|
||||
// Cleanup multiplexed connections every hour
|
||||
$schedule->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
||||
$this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
||||
|
||||
// Temporary solution until we have better memory management for Sentinel
|
||||
if ($server->isSentinelEnabled()) {
|
||||
$schedule->job(function () use ($server) {
|
||||
$this->scheduleInstance->job(function () use ($server) {
|
||||
$server->restartContainer('coolify-sentinel');
|
||||
})->daily()->onOneServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkScheduledBackups($schedule): void
|
||||
private function checkScheduledBackups(): void
|
||||
{
|
||||
$scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get();
|
||||
if ($scheduled_backups->isEmpty()) {
|
||||
|
|
@ -174,13 +187,13 @@ private function checkScheduledBackups($schedule): void
|
|||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||
}
|
||||
$schedule->job(new DatabaseBackupJob(
|
||||
$this->scheduleInstance->job(new DatabaseBackupJob(
|
||||
backup: $scheduled_backup
|
||||
))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||
}
|
||||
}
|
||||
|
||||
private function checkScheduledTasks($schedule): void
|
||||
private function checkScheduledTasks(): void
|
||||
{
|
||||
$scheduled_tasks = ScheduledTask::where('enabled', true)->get();
|
||||
if ($scheduled_tasks->isEmpty()) {
|
||||
|
|
@ -214,7 +227,7 @@ private function checkScheduledTasks($schedule): void
|
|||
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
||||
}
|
||||
$schedule->job(new ScheduledTaskJob(
|
||||
$this->scheduleInstance->job(new ScheduledTaskJob(
|
||||
task: $scheduled_task
|
||||
))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,17 +21,14 @@ public static function serverSshConfiguration(Server $server)
|
|||
];
|
||||
}
|
||||
|
||||
public static function ensureMultiplexedConnection(Server $server)
|
||||
public static function ensureMultiplexedConnection(Server $server): bool
|
||||
{
|
||||
if (! self::isMultiplexingEnabled()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$sshConfig = self::serverSshConfiguration($server);
|
||||
$muxSocket = $sshConfig['muxFilename'];
|
||||
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||
|
||||
self::validateSshKey($sshKeyLocation);
|
||||
|
||||
$checkCommand = "ssh -O check -o ControlPath=$muxSocket ";
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
|
|
@ -41,16 +38,17 @@ public static function ensureMultiplexedConnection(Server $server)
|
|||
$process = Process::run($checkCommand);
|
||||
|
||||
if ($process->exitCode() !== 0) {
|
||||
self::establishNewMultiplexedConnection($server);
|
||||
return self::establishNewMultiplexedConnection($server);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function establishNewMultiplexedConnection(Server $server)
|
||||
public static function establishNewMultiplexedConnection(Server $server): bool
|
||||
{
|
||||
$sshConfig = self::serverSshConfiguration($server);
|
||||
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||
$muxSocket = $sshConfig['muxFilename'];
|
||||
|
||||
$connectionTimeout = config('constants.ssh.connection_timeout');
|
||||
$serverInterval = config('constants.ssh.server_interval');
|
||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||
|
|
@ -60,15 +58,14 @@ public static function establishNewMultiplexedConnection(Server $server)
|
|||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
$establishCommand .= ' -o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||
}
|
||||
|
||||
$establishCommand .= self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval);
|
||||
$establishCommand .= "{$server->user}@{$server->ip}";
|
||||
|
||||
$establishProcess = Process::run($establishCommand);
|
||||
|
||||
if ($establishProcess->exitCode() !== 0) {
|
||||
throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function removeMuxFile(Server $server)
|
||||
|
|
@ -97,9 +94,8 @@ public static function generateScpCommand(Server $server, string $source, string
|
|||
if ($server->isIpv6()) {
|
||||
$scp_command .= '-6 ';
|
||||
}
|
||||
if (self::isMultiplexingEnabled()) {
|
||||
if (self::isMultiplexingEnabled() && self::ensureMultiplexedConnection($server)) {
|
||||
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||
self::ensureMultiplexedConnection($server);
|
||||
}
|
||||
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
|
|
@ -120,6 +116,9 @@ public static function generateSshCommand(Server $server, string $command)
|
|||
|
||||
$sshConfig = self::serverSshConfiguration($server);
|
||||
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||
|
||||
self::validateSshKey($server->privateKey);
|
||||
|
||||
$muxSocket = $sshConfig['muxFilename'];
|
||||
|
||||
$timeout = config('constants.ssh.command_timeout');
|
||||
|
|
@ -127,9 +126,8 @@ public static function generateSshCommand(Server $server, string $command)
|
|||
|
||||
$ssh_command = "timeout $timeout ssh ";
|
||||
|
||||
if (self::isMultiplexingEnabled()) {
|
||||
if (self::isMultiplexingEnabled() && self::ensureMultiplexedConnection($server)) {
|
||||
$ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||
self::ensureMultiplexedConnection($server);
|
||||
}
|
||||
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
|
|
@ -151,16 +149,17 @@ public static function generateSshCommand(Server $server, string $command)
|
|||
|
||||
private static function isMultiplexingEnabled(): bool
|
||||
{
|
||||
return config('constants.ssh.mux_enabled') && ! config('coolify.is_windows_docker_desktop');
|
||||
return config('constants.ssh.mux_enabled') && ! config('constants.coolify.is_windows_docker_desktop');
|
||||
}
|
||||
|
||||
private static function validateSshKey(string $sshKeyLocation): void
|
||||
private static function validateSshKey(PrivateKey $privateKey): void
|
||||
{
|
||||
$checkKeyCommand = "ls $sshKeyLocation 2>/dev/null";
|
||||
$keyLocation = $privateKey->getKeyLocation();
|
||||
$checkKeyCommand = "ls $keyLocation 2>/dev/null";
|
||||
$keyCheckProcess = Process::run($checkKeyCommand);
|
||||
|
||||
if ($keyCheckProcess->exitCode() !== 0) {
|
||||
throw new \RuntimeException("SSH key file not accessible: $sshKeyLocation");
|
||||
$privateKey->storeInFileSystem();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1224,7 +1224,7 @@ private function create_application(Request $request, $type)
|
|||
$service->name = "service-$service->uuid";
|
||||
$service->parse(isNew: true);
|
||||
if ($instantDeploy) {
|
||||
StartService::dispatch($service)->onQueue('high');
|
||||
StartService::dispatch($service);
|
||||
}
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
|
|
@ -1379,7 +1379,7 @@ public function delete_by_uuid(Request $request)
|
|||
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||
)->onQueue('high');
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Application deletion request queued.',
|
||||
|
|
@ -2523,7 +2523,7 @@ public function action_stop(Request $request)
|
|||
if (! $application) {
|
||||
return response()->json(['message' => 'Application not found.'], 404);
|
||||
}
|
||||
StopApplication::dispatch($application)->onQueue('high');
|
||||
StopApplication::dispatch($application);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -497,9 +497,9 @@ public function update_by_uuid(Request $request)
|
|||
$database->update($request->all());
|
||||
|
||||
if ($whatToDoWithDatabaseProxy === 'start') {
|
||||
StartDatabaseProxy::dispatch($database)->onQueue('high');
|
||||
StartDatabaseProxy::dispatch($database);
|
||||
} elseif ($whatToDoWithDatabaseProxy === 'stop') {
|
||||
StopDatabaseProxy::dispatch($database)->onQueue('high');
|
||||
StopDatabaseProxy::dispatch($database);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
|
|
@ -1151,7 +1151,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
|
|||
}
|
||||
$database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
$database->refresh();
|
||||
$payload = [
|
||||
|
|
@ -1206,7 +1206,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
|
|||
}
|
||||
$database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
|
|
@ -1264,7 +1264,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
|
|||
}
|
||||
$database = create_standalone_mysql($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
|
|
@ -1320,7 +1320,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
|
|||
}
|
||||
$database = create_standalone_redis($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
|
|
@ -1357,7 +1357,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
|
|||
removeUnnecessaryFieldsFromRequest($request);
|
||||
$database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
|
|
@ -1406,7 +1406,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
|
|||
}
|
||||
$database = create_standalone_keydb($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
|
|
@ -1442,7 +1442,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
|
|||
removeUnnecessaryFieldsFromRequest($request);
|
||||
$database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
|
|
@ -1500,7 +1500,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
|
|||
}
|
||||
$database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all());
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
$database->refresh();
|
||||
|
|
@ -1593,7 +1593,7 @@ public function delete_by_uuid(Request $request)
|
|||
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||
)->onQueue('high');
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Database deletion request queued.',
|
||||
|
|
@ -1666,7 +1666,7 @@ public function action_deploy(Request $request)
|
|||
if (str($database->status)->contains('running')) {
|
||||
return response()->json(['message' => 'Database is already running.'], 400);
|
||||
}
|
||||
StartDatabase::dispatch($database)->onQueue('high');
|
||||
StartDatabase::dispatch($database);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
|
|
@ -1742,7 +1742,7 @@ public function action_stop(Request $request)
|
|||
if (str($database->status)->contains('stopped') || str($database->status)->contains('exited')) {
|
||||
return response()->json(['message' => 'Database is already stopped.'], 400);
|
||||
}
|
||||
StopDatabase::dispatch($database)->onQueue('high');
|
||||
StopDatabase::dispatch($database);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
|
|
@ -1815,7 +1815,7 @@ public function action_restart(Request $request)
|
|||
if (! $database) {
|
||||
return response()->json(['message' => 'Database not found.'], 404);
|
||||
}
|
||||
RestartDatabase::dispatch($database)->onQueue('high');
|
||||
RestartDatabase::dispatch($database);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ public function deploy_resource($resource, bool $force = false): array
|
|||
break;
|
||||
default:
|
||||
// Database resource
|
||||
StartDatabase::dispatch($resource)->onQueue('high');
|
||||
StartDatabase::dispatch($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ public function disable_api(Request $request)
|
|||
public function feedback(Request $request)
|
||||
{
|
||||
$content = $request->input('content');
|
||||
$webhook_url = config('coolify.feedback_discord_webhook');
|
||||
$webhook_url = config('constants.webhooks.feedback_discord_webhook');
|
||||
if ($webhook_url) {
|
||||
Http::post($webhook_url, [
|
||||
'content' => $content,
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ public function project_by_uuid(Request $request)
|
|||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Project details',
|
||||
description: 'Environment details',
|
||||
content: new OA\JsonContent(ref: '#/components/schemas/Environment')),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
|
|
|
|||
|
|
@ -81,15 +81,8 @@ public function keys(Request $request)
|
|||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get all private keys.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
||||
)
|
||||
),
|
||||
]),
|
||||
content: new OA\JsonContent(ref: '#/components/schemas/PrivateKey')
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
|
|
|
|||
|
|
@ -426,6 +426,7 @@ public function domains_by_server(Request $request)
|
|||
'private_key_uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the private key.'],
|
||||
'is_build_server' => ['type' => 'boolean', 'example' => false, 'description' => 'Is build server.'],
|
||||
'instant_validate' => ['type' => 'boolean', 'example' => false, 'description' => 'Instant validate.'],
|
||||
'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'example' => 'traefik', 'description' => 'The proxy type.'],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -461,7 +462,7 @@ public function domains_by_server(Request $request)
|
|||
)]
|
||||
public function create_server(Request $request)
|
||||
{
|
||||
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
||||
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type'];
|
||||
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
|
|
@ -481,6 +482,7 @@ public function create_server(Request $request)
|
|||
'user' => 'string|nullable',
|
||||
'is_build_server' => 'boolean|nullable',
|
||||
'instant_validate' => 'boolean|nullable',
|
||||
'proxy_type' => 'string|nullable',
|
||||
]);
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
|
|
@ -512,6 +514,14 @@ public function create_server(Request $request)
|
|||
if (is_null($request->instant_validate)) {
|
||||
$request->offsetSet('instant_validate', false);
|
||||
}
|
||||
if ($request->proxy_type) {
|
||||
$validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
|
||||
return str($proxyType->value)->lower();
|
||||
});
|
||||
if (! $validProxyTypes->contains(str($request->proxy_type)->lower())) {
|
||||
return response()->json(['message' => 'Invalid proxy type.'], 422);
|
||||
}
|
||||
}
|
||||
$privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first();
|
||||
if (! $privateKey) {
|
||||
return response()->json(['message' => 'Private key not found.'], 404);
|
||||
|
|
@ -521,6 +531,8 @@ public function create_server(Request $request)
|
|||
return response()->json(['message' => 'Server with this IP already exists.'], 400);
|
||||
}
|
||||
|
||||
$proxyType = $request->proxy_type ? str($request->proxy_type)->upper() : ProxyTypes::TRAEFIK->value;
|
||||
|
||||
$server = ModelsServer::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
|
|
@ -530,7 +542,7 @@ public function create_server(Request $request)
|
|||
'private_key_id' => $privateKey->id,
|
||||
'team_id' => $teamId,
|
||||
'proxy' => [
|
||||
'type' => ProxyTypes::TRAEFIK->value,
|
||||
'type' => $proxyType,
|
||||
'status' => ProxyStatus::EXITED->value,
|
||||
],
|
||||
]);
|
||||
|
|
@ -538,7 +550,7 @@ public function create_server(Request $request)
|
|||
'is_build_server' => $request->is_build_server,
|
||||
]);
|
||||
if ($request->instant_validate) {
|
||||
ValidateServer::dispatch($server)->onQueue('high');
|
||||
ValidateServer::dispatch($server);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
|
|
@ -571,6 +583,7 @@ public function create_server(Request $request)
|
|||
'private_key_uuid' => ['type' => 'string', 'description' => 'The UUID of the private key.'],
|
||||
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
|
||||
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
|
||||
'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'description' => 'The proxy type.'],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -604,7 +617,7 @@ public function create_server(Request $request)
|
|||
)]
|
||||
public function update_server(Request $request)
|
||||
{
|
||||
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
||||
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type'];
|
||||
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
|
|
@ -624,6 +637,7 @@ public function update_server(Request $request)
|
|||
'user' => 'string|nullable',
|
||||
'is_build_server' => 'boolean|nullable',
|
||||
'instant_validate' => 'boolean|nullable',
|
||||
'proxy_type' => 'string|nullable',
|
||||
]);
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
|
|
@ -644,6 +658,16 @@ public function update_server(Request $request)
|
|||
if (! $server) {
|
||||
return response()->json(['message' => 'Server not found.'], 404);
|
||||
}
|
||||
if ($request->proxy_type) {
|
||||
$validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
|
||||
return str($proxyType->value)->lower();
|
||||
});
|
||||
if ($validProxyTypes->contains(str($request->proxy_type)->lower())) {
|
||||
$server->changeProxy($request->proxy_type, async: true);
|
||||
} else {
|
||||
return response()->json(['message' => 'Invalid proxy type.'], 422);
|
||||
}
|
||||
}
|
||||
$server->update($request->only(['name', 'description', 'ip', 'port', 'user']));
|
||||
if ($request->is_build_server) {
|
||||
$server->settings()->update([
|
||||
|
|
@ -651,10 +675,12 @@ public function update_server(Request $request)
|
|||
]);
|
||||
}
|
||||
if ($request->instant_validate) {
|
||||
ValidateServer::dispatch($server)->onQueue('high');
|
||||
ValidateServer::dispatch($server);
|
||||
}
|
||||
|
||||
return response()->json(serializeApiResponse($server))->setStatusCode(201);
|
||||
return response()->json([
|
||||
|
||||
])->setStatusCode(201);
|
||||
}
|
||||
|
||||
#[OA\Delete(
|
||||
|
|
@ -787,7 +813,7 @@ public function validate_server(Request $request)
|
|||
if (! $server) {
|
||||
return response()->json(['message' => 'Server not found.'], 404);
|
||||
}
|
||||
ValidateServer::dispatch($server)->onQueue('high');
|
||||
ValidateServer::dispatch($server);
|
||||
|
||||
return response()->json(['message' => 'Validation started.']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ public function create_service(Request $request)
|
|||
}
|
||||
$service->parse(isNew: true);
|
||||
if ($instantDeploy) {
|
||||
StartService::dispatch($service)->onQueue('high');
|
||||
StartService::dispatch($service);
|
||||
}
|
||||
$domains = $service->applications()->get()->pluck('fqdn')->sort();
|
||||
$domains = $domains->map(function ($domain) {
|
||||
|
|
@ -487,7 +487,7 @@ public function delete_by_uuid(Request $request)
|
|||
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||
)->onQueue('high');
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Service deletion request queued.',
|
||||
|
|
@ -1076,7 +1076,7 @@ public function action_deploy(Request $request)
|
|||
if (str($service->status())->contains('running')) {
|
||||
return response()->json(['message' => 'Service is already running.'], 400);
|
||||
}
|
||||
StartService::dispatch($service)->onQueue('high');
|
||||
StartService::dispatch($service);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
|
|
@ -1154,7 +1154,7 @@ public function action_stop(Request $request)
|
|||
if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
|
||||
return response()->json(['message' => 'Service is already stopped.'], 400);
|
||||
}
|
||||
StopService::dispatch($service)->onQueue('high');
|
||||
StopService::dispatch($service);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
|
|
@ -1229,7 +1229,7 @@ public function action_restart(Request $request)
|
|||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
RestartService::dispatch($service)->onQueue('high');
|
||||
RestartService::dispatch($service);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ public function manual(Request $request)
|
|||
|
||||
return;
|
||||
}
|
||||
|
||||
$return_payloads = collect([]);
|
||||
$payload = $request->collect();
|
||||
$headers = $request->headers->all();
|
||||
|
|
@ -48,6 +49,15 @@ public function manual(Request $request)
|
|||
return response($return_payloads);
|
||||
}
|
||||
|
||||
if (empty($x_gitlab_token)) {
|
||||
$return_payloads->push([
|
||||
'status' => 'failed',
|
||||
'message' => 'Invalid signature.',
|
||||
]);
|
||||
|
||||
return response($return_payloads);
|
||||
}
|
||||
|
||||
if ($x_gitlab_event === 'push') {
|
||||
$branch = data_get($payload, 'ref');
|
||||
$full_name = data_get($payload, 'project.path_with_namespace');
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\ServerLimitCheckJob;
|
||||
use App\Jobs\SubscriptionInvoiceFailedJob;
|
||||
use App\Jobs\SubscriptionTrialEndedJob;
|
||||
use App\Jobs\SubscriptionTrialEndsSoonJob;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\Team;
|
||||
use App\Models\Webhook;
|
||||
|
|
@ -260,42 +258,7 @@ public function events(Request $request)
|
|||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$team = data_get($subscription, 'team');
|
||||
if ($team) {
|
||||
$team->trialEnded();
|
||||
}
|
||||
$subscription->update([
|
||||
'stripe_subscription_id' => null,
|
||||
'stripe_plan_id' => null,
|
||||
'stripe_cancel_at_period_end' => false,
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => false,
|
||||
]);
|
||||
// send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
|
||||
break;
|
||||
case 'customer.subscription.trial_will_end':
|
||||
// Not used for now
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$team = data_get($subscription, 'team');
|
||||
if (! $team) {
|
||||
return response('No team found for subscription: '.$subscription->id, 400);
|
||||
}
|
||||
SubscriptionTrialEndsSoonJob::dispatch($team);
|
||||
break;
|
||||
case 'customer.subscription.paused':
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$team = data_get($subscription, 'team');
|
||||
if (! $team) {
|
||||
return response('No team found for subscription: '.$subscription->id, 400);
|
||||
}
|
||||
$team->trialEnded();
|
||||
$subscription->update([
|
||||
'stripe_trial_already_ended' => true,
|
||||
'stripe_invoice_paid' => false,
|
||||
]);
|
||||
SubscriptionTrialEndedJob::dispatch($team);
|
||||
// send_internal_notification('Subscription paused for customer: '.$customerId);
|
||||
$team?->subscriptionEnded();
|
||||
break;
|
||||
default:
|
||||
// Unhandled event type
|
||||
|
|
|
|||
|
|
@ -166,6 +166,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
public function __construct(int $application_deployment_queue_id)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
|
||||
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
|
||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
||||
$this->build_pack = data_get($this->application, 'build_pack');
|
||||
|
|
@ -225,6 +227,11 @@ public function __construct(int $application_deployment_queue_id)
|
|||
}
|
||||
}
|
||||
|
||||
public function tags(): array
|
||||
{
|
||||
return ['server:'.gethostname()];
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$this->application_deployment_queue->update([
|
||||
|
|
@ -344,8 +351,7 @@ private function decide_what_to_do()
|
|||
private function post_deployment()
|
||||
{
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
GetContainersStatus::dispatch($this->server)->onQueue('high');
|
||||
// dispatch(new ContainerStatusJob($this->server));
|
||||
GetContainersStatus::dispatch($this->server);
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
|
|
@ -1318,7 +1324,7 @@ private function create_workdir()
|
|||
private function prepare_builder_image()
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$helperImage = config('constants.coolify.helper_image');
|
||||
$helperImage = "{$helperImage}:{$settings->helper_version}";
|
||||
// Get user home directory
|
||||
$this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ public function __construct(
|
|||
public ApplicationPreview $preview,
|
||||
public ProcessStatus $status,
|
||||
public ?string $deployment_uuid = null
|
||||
) {}
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ public function __construct(
|
|||
public bool $ignore_errors,
|
||||
public $call_event_on_finish,
|
||||
public $call_event_data,
|
||||
) {}
|
||||
) {
|
||||
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
public function __construct($backup)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->backup = $backup;
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +199,7 @@ public function handle(): void
|
|||
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
||||
}
|
||||
|
||||
if (is_null($databasesToBackup)) {
|
||||
if (filled($databasesToBackup)) {
|
||||
if (str($databaseType)->contains('postgres')) {
|
||||
$databasesToBackup = [$this->database->postgres_db];
|
||||
} elseif (str($databaseType)->contains('mongodb')) {
|
||||
|
|
@ -319,12 +320,10 @@ public function handle(): void
|
|||
'filename' => null,
|
||||
]);
|
||||
}
|
||||
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
|
||||
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database));
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
if ($this->team) {
|
||||
|
|
@ -524,7 +523,7 @@ private function upload_to_s3(): void
|
|||
private function getFullImageName(): string
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$helperImage = config('constants.coolify.helper_image');
|
||||
$latestVersion = $settings->helper_version;
|
||||
|
||||
return "{$helperImage}:{$latestVersion}";
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ public function __construct(
|
|||
public bool $deleteVolumes = true,
|
||||
public bool $dockerCleanup = true,
|
||||
public bool $deleteConnectedNetworks = true
|
||||
) {}
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
|
@ -87,7 +89,6 @@ public function handle()
|
|||
$this->resource?->delete_connected_networks($this->resource->uuid);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->resource->forceDelete();
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||
}
|
||||
|
||||
public function __construct(public Server $server, public bool $manualCleanup = false) {}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,14 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
public $timeout = 1000;
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$helperImage = config('constants.coolify.helper_image');
|
||||
$latest_version = instanceSettings()->helper_version;
|
||||
instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server, false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
public $timeout = 10;
|
||||
|
||||
public function __construct() {}
|
||||
public function __construct()
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -360,7 +360,7 @@ private function isRunning(string $containerStatus)
|
|||
private function checkLogDrainContainer()
|
||||
{
|
||||
if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) {
|
||||
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||
StartLogDrain::dispatch($this->server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ class ScheduledTaskJob implements ShouldQueue
|
|||
|
||||
public function __construct($task)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
|
||||
$this->task = $task;
|
||||
if ($service = $task->service()->first()) {
|
||||
$this->resource = $service;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ class SendMessageToDiscordJob implements ShouldBeEncrypted, ShouldQueue
|
|||
public function __construct(
|
||||
public DiscordMessage $message,
|
||||
public string $webhookUrl
|
||||
) {}
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@ public function __construct(
|
|||
public string $token,
|
||||
public string $chatId,
|
||||
public ?string $topicId = null,
|
||||
) {}
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||
}
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
|
|
@ -94,10 +94,10 @@ private function checkLogDrainContainer()
|
|||
if ($foundLogDrainContainer) {
|
||||
$status = data_get($foundLogDrainContainer, 'State.Status');
|
||||
if ($status !== 'running') {
|
||||
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||
StartLogDrain::dispatch($this->server);
|
||||
}
|
||||
} else {
|
||||
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||
StartLogDrain::dispatch($this->server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldQueue
|
|||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) {}
|
||||
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public function backoff(): int
|
|||
return isDev() ? 1 : 3;
|
||||
}
|
||||
|
||||
public function __construct(public Server $server, public ?int $percentage = null) {}
|
||||
public function __construct(public Server $server, public int|string|null $percentage = null) {}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ class ServerStorageSaveJob implements ShouldBeEncrypted, ShouldQueue
|
|||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public LocalFileVolume $localFileVolume) {}
|
||||
public function __construct(public LocalFileVolume $localFileVolume)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue
|
|||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(protected Team $team) {}
|
||||
public function __construct(protected Team $team)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Team;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public Team $team
|
||||
) {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$session = getStripeCustomerPortalSession($this->team);
|
||||
$mail = new MailMessage;
|
||||
$mail->subject('Action required: You trial in Coolify Cloud ended.');
|
||||
$mail->view('emails.trial-ended', [
|
||||
'stripeCustomerPortal' => $session->url,
|
||||
]);
|
||||
$this->team->members()->each(function ($member) use ($mail) {
|
||||
if ($member->isAdmin()) {
|
||||
send_user_an_email($mail, $member->email);
|
||||
send_internal_notification('Trial reminder email sent to '.$member->email);
|
||||
}
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('SubscriptionTrialEndsSoonJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Team;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public Team $team
|
||||
) {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$session = getStripeCustomerPortalSession($this->team);
|
||||
$mail = new MailMessage;
|
||||
$mail->subject('You trial in Coolify Cloud ends soon.');
|
||||
$mail->view('emails.trial-ends-soon', [
|
||||
'stripeCustomerPortal' => $session->url,
|
||||
]);
|
||||
$this->team->members()->each(function ($member) use ($mail) {
|
||||
if ($member->isAdmin()) {
|
||||
send_user_an_email($mail, $member->email);
|
||||
send_internal_notification('Trial reminder email sent to '.$member->email);
|
||||
}
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('SubscriptionTrialEndsSoonJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,11 @@ class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
public $timeout = 600;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace App\Livewire\Admin;
|
||||
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Container\Attributes\Auth as AttributesAuth;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
|
@ -43,17 +43,13 @@ public function submitSearch()
|
|||
|
||||
public function getSubscribers()
|
||||
{
|
||||
$this->inactiveSubscribers = User::whereDoesntHave('teams', function ($query) {
|
||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
||||
})->count();
|
||||
$this->activeSubscribers = User::whereHas('teams', function ($query) {
|
||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
||||
})->count();
|
||||
$this->inactiveSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', false)->count();
|
||||
$this->activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->count();
|
||||
}
|
||||
|
||||
public function switchUser(int $user_id)
|
||||
{
|
||||
if (AttributesAuth::id() !== 0) {
|
||||
if (Auth::id() !== 0) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$user = User::find($user_id);
|
||||
|
|
|
|||
|
|
@ -66,11 +66,15 @@ class Index extends Component
|
|||
|
||||
public bool $serverReachable = true;
|
||||
|
||||
public ?string $minDockerVersion = null;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (auth()->user()?->isMember() && auth()->user()->currentTeam()->show_boarding === true) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
$this->minDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
|
||||
$this->privateKeyName = generate_random_name();
|
||||
$this->remoteServerName = generate_random_name();
|
||||
if (isDev()) {
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Dev;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class Compose extends Component
|
||||
{
|
||||
public string $compose = '';
|
||||
|
||||
public string $base64 = '';
|
||||
|
||||
public $services;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->services = get_service_templates();
|
||||
}
|
||||
|
||||
public function setService(string $selected)
|
||||
{
|
||||
$this->base64 = data_get($this->services, $selected.'.compose');
|
||||
if ($this->base64) {
|
||||
$this->compose = base64_decode($this->base64);
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedCompose($value)
|
||||
{
|
||||
$this->base64 = base64_encode($value);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.dev.compose');
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +73,9 @@ class Email extends Component
|
|||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $resendApiKey = null;
|
||||
|
||||
#[Validate(['required', 'email'])]
|
||||
public string $testEmailAddress = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
|
|
@ -132,14 +135,21 @@ public function syncData(bool $toModel = false)
|
|||
}
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
public function sendTestEmail()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'testEmailAddress' => 'required|email',
|
||||
], [
|
||||
'testEmailAddress.required' => 'Test email address is required.',
|
||||
'testEmailAddress.email' => 'Please enter a valid email address.',
|
||||
]);
|
||||
|
||||
$executed = RateLimiter::attempt(
|
||||
'test-email:'.$this->team->id,
|
||||
$perMinute = 0,
|
||||
function () {
|
||||
$this->team?->notify(new Test($this->emails));
|
||||
$this->team?->notify(new Test($this->testEmailAddress));
|
||||
$this->dispatch('success', 'Test Email sent.');
|
||||
},
|
||||
$decaySeconds = 10,
|
||||
|
|
|
|||
|
|
@ -45,13 +45,11 @@ public function mount()
|
|||
public function check_status($showNotification = false)
|
||||
{
|
||||
if ($this->application->destination->server->isFunctional()) {
|
||||
GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high');
|
||||
GetContainersStatus::dispatch($this->application->destination->server);
|
||||
}
|
||||
if ($showNotification) {
|
||||
$this->dispatch('success', 'Success', 'Application status updated.');
|
||||
}
|
||||
// Removed because it caused flickering
|
||||
// $this->dispatch('configurationChanged');
|
||||
}
|
||||
|
||||
public function force_deploy_without_cache()
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ class CreateScheduledBackup extends Component
|
|||
|
||||
public bool $enabled = true;
|
||||
|
||||
#[Validate(['required', 'integer'])]
|
||||
public int $s3StorageId;
|
||||
#[Validate(['nullable', 'integer'])]
|
||||
public ?int $s3StorageId = null;
|
||||
|
||||
public Collection $definedS3s;
|
||||
|
||||
|
|
@ -49,6 +49,7 @@ public function submit()
|
|||
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'enabled' => true,
|
||||
'frequency' => $this->frequency,
|
||||
|
|
@ -58,6 +59,7 @@ public function submit()
|
|||
'database_type' => $this->database->getMorphClass(),
|
||||
'team_id' => currentTeam()->id,
|
||||
];
|
||||
|
||||
if ($this->database->type() === 'standalone-postgresql') {
|
||||
$payload['databases_to_backup'] = $this->database->postgres_db;
|
||||
} elseif ($this->database->type() === 'standalone-mysql') {
|
||||
|
|
@ -72,11 +74,11 @@ public function submit()
|
|||
} else {
|
||||
$this->dispatch('refreshScheduledBackups');
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->frequency = '';
|
||||
$this->saveToS3 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,9 +91,12 @@ public function loadServices()
|
|||
{
|
||||
$services = get_service_templates(true);
|
||||
$services = collect($services)->map(function ($service, $key) {
|
||||
$logo = data_get($service, 'logo', 'svgs/coolify.png');
|
||||
|
||||
return [
|
||||
'name' => str($key)->headline(),
|
||||
'logo' => asset(data_get($service, 'logo', 'svgs/coolify.png')),
|
||||
'logo' => asset($logo),
|
||||
'logo_github_url' => 'https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/main/public/a'.$logo,
|
||||
] + (array) $service;
|
||||
})->all();
|
||||
$gitBasedApplications = [
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public function submit()
|
|||
$this->validate();
|
||||
$tags = str($this->newTags)->trim()->explode(' ');
|
||||
foreach ($tags as $tag) {
|
||||
$tag = strip_tags($tag);
|
||||
if (strlen($tag) < 2) {
|
||||
$this->dispatch('error', 'Invalid tag.', "Tag <span class='dark:text-warning'>$tag</span> is invalid. Min length is 2.");
|
||||
|
||||
|
|
@ -65,6 +66,7 @@ public function submit()
|
|||
public function addTag(string $id, string $name)
|
||||
{
|
||||
try {
|
||||
$name = strip_tags($name);
|
||||
if ($this->resource->tags()->where('id', $id)->exists()) {
|
||||
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$name</span> already added.");
|
||||
|
||||
|
|
|
|||
|
|
@ -6,64 +6,60 @@
|
|||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
class ByIp extends Component
|
||||
{
|
||||
#[Locked]
|
||||
public $private_keys;
|
||||
|
||||
#[Locked]
|
||||
public $limit_reached;
|
||||
|
||||
#[Validate('nullable|integer', as: 'Private Key')]
|
||||
public ?int $private_key_id = null;
|
||||
|
||||
#[Validate('nullable|string', as: 'Private Key Name')]
|
||||
public $new_private_key_name;
|
||||
|
||||
#[Validate('nullable|string', as: 'Private Key Description')]
|
||||
public $new_private_key_description;
|
||||
|
||||
#[Validate('nullable|string', as: 'Private Key Value')]
|
||||
public $new_private_key_value;
|
||||
|
||||
#[Validate('required|string', as: 'Name')]
|
||||
public string $name;
|
||||
|
||||
#[Validate('nullable|string', as: 'Description')]
|
||||
public ?string $description = null;
|
||||
|
||||
#[Validate('required|string', as: 'IP Address/Domain')]
|
||||
public string $ip;
|
||||
|
||||
#[Validate('required|string', as: 'User')]
|
||||
public string $user = 'root';
|
||||
|
||||
#[Validate('required|integer|between:1,65535', as: 'Port')]
|
||||
public int $port = 22;
|
||||
|
||||
#[Validate('required|boolean', as: 'Swarm Manager')]
|
||||
public bool $is_swarm_manager = false;
|
||||
|
||||
#[Validate('required|boolean', as: 'Swarm Worker')]
|
||||
public bool $is_swarm_worker = false;
|
||||
|
||||
#[Validate('nullable|integer', as: 'Swarm Cluster')]
|
||||
public $selected_swarm_cluster = null;
|
||||
|
||||
#[Validate('required|boolean', as: 'Build Server')]
|
||||
public bool $is_build_server = false;
|
||||
|
||||
#[Locked]
|
||||
public Collection $swarm_managers;
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'description' => 'nullable|string',
|
||||
'ip' => 'required',
|
||||
'user' => 'required|string',
|
||||
'port' => 'required|integer',
|
||||
'is_swarm_manager' => 'required|boolean',
|
||||
'is_swarm_worker' => 'required|boolean',
|
||||
'is_build_server' => 'required|boolean',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
'name' => 'Name',
|
||||
'description' => 'Description',
|
||||
'ip' => 'IP Address/Domain',
|
||||
'user' => 'User',
|
||||
'port' => 'Port',
|
||||
'is_swarm_manager' => 'Swarm Manager',
|
||||
'is_swarm_worker' => 'Swarm Worker',
|
||||
'is_build_server' => 'Build Server',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->name = generate_random_name();
|
||||
|
|
@ -88,6 +84,12 @@ public function submit()
|
|||
{
|
||||
$this->validate();
|
||||
try {
|
||||
if (Server::where('team_id', currentTeam()->id)
|
||||
->where('ip', $this->ip)
|
||||
->exists()) {
|
||||
return $this->dispatch('error', 'This IP/Domain is already in use by another server in your team.');
|
||||
}
|
||||
|
||||
if (is_null($this->private_key_id)) {
|
||||
return $this->dispatch('error', 'You must select a private key');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
use App\Actions\Proxy\CheckConfiguration;
|
||||
use App\Actions\Proxy\SaveConfiguration;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
|
|
@ -44,14 +43,13 @@ public function changeProxy()
|
|||
|
||||
public function selectProxy($proxy_type)
|
||||
{
|
||||
$this->server->proxy->set('status', 'exited');
|
||||
$this->server->proxy->set('type', $proxy_type);
|
||||
$this->server->save();
|
||||
$this->selectedProxy = $this->server->proxy->type;
|
||||
if ($this->server->proxySet()) {
|
||||
StartProxy::run($this->server, false);
|
||||
try {
|
||||
$this->server->changeProxy($proxy_type, async: false);
|
||||
$this->selectedProxy = $this->server->proxy->type;
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
|
|
|
|||
|
|
@ -107,6 +107,15 @@ public function syncData(bool $toModel = false)
|
|||
{
|
||||
if ($toModel) {
|
||||
$this->validate();
|
||||
|
||||
if (Server::where('team_id', currentTeam()->id)
|
||||
->where('ip', $this->ip)
|
||||
->where('id', '!=', $this->server->id)
|
||||
->exists()) {
|
||||
$this->ip = $this->server->ip;
|
||||
throw new \Exception('This IP/Domain is already in use by another server in your team.');
|
||||
}
|
||||
|
||||
$this->server->name = $this->name;
|
||||
$this->server->description = $this->description;
|
||||
$this->server->ip = $this->ip;
|
||||
|
|
@ -127,7 +136,14 @@ public function syncData(bool $toModel = false)
|
|||
$this->server->settings->sentinel_custom_url = $this->sentinelCustomUrl;
|
||||
$this->server->settings->is_sentinel_enabled = $this->isSentinelEnabled;
|
||||
$this->server->settings->is_sentinel_debug_enabled = $this->isSentinelDebugEnabled;
|
||||
$this->server->settings->server_timezone = $this->serverTimezone;
|
||||
|
||||
if (! validate_timezone($this->serverTimezone)) {
|
||||
$this->serverTimezone = config('app.timezone');
|
||||
throw new \Exception('Invalid timezone.');
|
||||
} else {
|
||||
$this->server->settings->server_timezone = $this->serverTimezone;
|
||||
}
|
||||
|
||||
$this->server->settings->save();
|
||||
} else {
|
||||
$this->name = $this->server->name;
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ public function validateDockerVersion()
|
|||
$this->dispatch('refreshBoardingIndex');
|
||||
$this->dispatch('success', 'Server validated.');
|
||||
} else {
|
||||
$this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||
$requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
|
||||
$this->error = 'Minimum Docker Engine version '.$requiredDockerVersion.' is not instaled. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||
$this->server->update([
|
||||
'validation_logs' => $this->error,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -139,6 +139,14 @@ public function submit()
|
|||
$error_show = false;
|
||||
$this->server = Server::findOrFail(0);
|
||||
$this->resetErrorBag();
|
||||
|
||||
if (! validate_timezone($this->instance_timezone)) {
|
||||
$this->instance_timezone = config('app.timezone');
|
||||
throw new \Exception('Invalid timezone.');
|
||||
} else {
|
||||
$this->settings->instance_timezone = $this->instance_timezone;
|
||||
}
|
||||
|
||||
if ($this->settings->public_port_min > $this->settings->public_port_max) {
|
||||
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Actions\License\CheckResaleLicense;
|
||||
use App\Models\InstanceSettings;
|
||||
use Livewire\Component;
|
||||
|
||||
class License extends Component
|
||||
{
|
||||
public InstanceSettings $settings;
|
||||
|
||||
public ?string $instance_id = null;
|
||||
|
||||
protected $rules = [
|
||||
'settings.resale_license' => 'nullable',
|
||||
'settings.is_resale_license_active' => 'nullable',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
'settings.resale_license' => 'License',
|
||||
'instance_id' => 'Instance Id (Do not change this)',
|
||||
'settings.is_resale_license_active' => 'Is License Active',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (! isCloud()) {
|
||||
abort(404);
|
||||
}
|
||||
if (! isInstanceAdmin()) {
|
||||
return redirect()->route('home');
|
||||
}
|
||||
$this->instance_id = config('app.id');
|
||||
$this->settings = instanceSettings();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.settings.license');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$this->settings->save();
|
||||
if ($this->settings->resale_license) {
|
||||
try {
|
||||
CheckResaleLicense::run();
|
||||
$this->dispatch('reloadWindow');
|
||||
} catch (\Throwable $e) {
|
||||
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: '.$e->getMessage());
|
||||
|
||||
return redirect()->route('settings.license');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,13 +14,25 @@ class Index extends Component
|
|||
|
||||
public $containers = [];
|
||||
|
||||
public bool $isLoadingContainers = true;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (! auth()->user()->isAdmin()) {
|
||||
abort(403);
|
||||
}
|
||||
$this->servers = Server::isReachable()->get();
|
||||
$this->containers = $this->getAllActiveContainers();
|
||||
}
|
||||
|
||||
public function loadContainers()
|
||||
{
|
||||
try {
|
||||
$this->containers = $this->getAllActiveContainers();
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->isLoadingContainers = false;
|
||||
}
|
||||
}
|
||||
|
||||
private function getAllActiveContainers()
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public function render()
|
|||
|
||||
public function mount()
|
||||
{
|
||||
if (config('coolify.waitlist') == false) {
|
||||
if (config('constants.waitlist.enabled') == false) {
|
||||
return redirect()->route('register');
|
||||
}
|
||||
$this->waitingInLine = Waitlist::whereVerified(true)->count();
|
||||
|
|
|
|||
|
|
@ -906,21 +906,7 @@ public function isConfigurationChanged(bool $save = false)
|
|||
|
||||
public function customRepository()
|
||||
{
|
||||
preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches);
|
||||
$port = 22;
|
||||
if (count($matches) === 1) {
|
||||
$port = $matches[0];
|
||||
$gitHost = str($this->git_repository)->before(':');
|
||||
$gitRepo = str($this->git_repository)->after('/');
|
||||
$repository = "$gitHost:$gitRepo";
|
||||
} else {
|
||||
$repository = $this->git_repository;
|
||||
}
|
||||
|
||||
return [
|
||||
'repository' => $repository,
|
||||
'port' => $port,
|
||||
];
|
||||
return convertGitUrl($this->git_repository, $this->deploymentType(), $this->source);
|
||||
}
|
||||
|
||||
public function generateBaseDir(string $uuid)
|
||||
|
|
@ -953,6 +939,122 @@ public function setGitImportSettings(string $deployment_uuid, string $git_clone_
|
|||
return $git_clone_command;
|
||||
}
|
||||
|
||||
public function getGitRemoteStatus(string $deployment_uuid)
|
||||
{
|
||||
try {
|
||||
['commands' => $lsRemoteCommand] = $this->generateGitLsRemoteCommands(deployment_uuid: $deployment_uuid, exec_in_docker: false);
|
||||
instant_remote_process([$lsRemoteCommand], $this->destination->server, true);
|
||||
|
||||
return [
|
||||
'is_accessible' => true,
|
||||
'error' => null,
|
||||
];
|
||||
} catch (\RuntimeException $ex) {
|
||||
return [
|
||||
'is_accessible' => false,
|
||||
'error' => $ex->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function generateGitLsRemoteCommands(string $deployment_uuid, bool $exec_in_docker = true)
|
||||
{
|
||||
$branch = $this->git_branch;
|
||||
['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
|
||||
$commands = collect([]);
|
||||
$base_command = 'git ls-remote';
|
||||
|
||||
if ($this->deploymentType() === 'source') {
|
||||
$source_html_url = data_get($this, 'source.html_url');
|
||||
$url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL));
|
||||
$source_html_url_host = $url['host'];
|
||||
$source_html_url_scheme = $url['scheme'];
|
||||
|
||||
if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
|
||||
if ($this->source->is_public) {
|
||||
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
|
||||
$base_command = "{$base_command} {$this->source->html_url}/{$customRepository}";
|
||||
} else {
|
||||
$github_access_token = generate_github_installation_token($this->source);
|
||||
|
||||
if ($exec_in_docker) {
|
||||
$base_command = "{$base_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git";
|
||||
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git";
|
||||
} else {
|
||||
$base_command = "{$base_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
|
||||
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
|
||||
}
|
||||
}
|
||||
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $base_command));
|
||||
} else {
|
||||
$commands->push($base_command);
|
||||
}
|
||||
|
||||
return [
|
||||
'commands' => $commands->implode(' && '),
|
||||
'branch' => $branch,
|
||||
'fullRepoUrl' => $fullRepoUrl,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->deploymentType() === 'deploy_key') {
|
||||
$fullRepoUrl = $customRepository;
|
||||
$private_key = data_get($this, 'private_key.private_key');
|
||||
if (is_null($private_key)) {
|
||||
throw new RuntimeException('Private key not found. Please add a private key to the application and try again.');
|
||||
}
|
||||
$private_key = base64_encode($private_key);
|
||||
$base_comamnd = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$base_command} {$customRepository}";
|
||||
|
||||
if ($exec_in_docker) {
|
||||
$commands = collect([
|
||||
executeInDocker($deployment_uuid, 'mkdir -p /root/.ssh'),
|
||||
executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"),
|
||||
executeInDocker($deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'),
|
||||
]);
|
||||
} else {
|
||||
$commands = collect([
|
||||
'mkdir -p /root/.ssh',
|
||||
"echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null",
|
||||
'chmod 600 /root/.ssh/id_rsa',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $base_comamnd));
|
||||
} else {
|
||||
$commands->push($base_comamnd);
|
||||
}
|
||||
|
||||
return [
|
||||
'commands' => $commands->implode(' && '),
|
||||
'branch' => $branch,
|
||||
'fullRepoUrl' => $fullRepoUrl,
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->deploymentType() === 'other') {
|
||||
$fullRepoUrl = $customRepository;
|
||||
$base_command = "{$base_command} {$customRepository}";
|
||||
$base_command = $this->setGitImportSettings($deployment_uuid, $base_command, public: true);
|
||||
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $base_command));
|
||||
} else {
|
||||
$commands->push($base_command);
|
||||
}
|
||||
|
||||
return [
|
||||
'commands' => $commands->implode(' && '),
|
||||
'branch' => $branch,
|
||||
'fullRepoUrl' => $fullRepoUrl,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null)
|
||||
{
|
||||
$branch = $this->git_branch;
|
||||
|
|
@ -1214,6 +1316,11 @@ public function loadComposeFile($isInit = false)
|
|||
$workdir = rtrim($this->base_directory, '/');
|
||||
$composeFile = $this->docker_compose_location;
|
||||
$fileList = collect([".$workdir$composeFile"]);
|
||||
$gitRemoteStatus = $this->getGitRemoteStatus(deployment_uuid: $uuid);
|
||||
if (! $gitRemoteStatus['is_accessible']) {
|
||||
throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}");
|
||||
}
|
||||
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
"mkdir -p /tmp/{$uuid}",
|
||||
|
|
|
|||
|
|
@ -218,10 +218,12 @@ public static function generateFingerprint($privateKey)
|
|||
|
||||
private static function fingerprintExists($fingerprint, $excludeId = null)
|
||||
{
|
||||
$query = self::where('fingerprint', $fingerprint);
|
||||
$query = self::query()
|
||||
->where('fingerprint', $fingerprint)
|
||||
->where('id', '!=', $excludeId);
|
||||
|
||||
if (! is_null($excludeId)) {
|
||||
$query->where('id', '!=', $excludeId);
|
||||
if (currentTeam()) {
|
||||
$query->where('team_id', currentTeam()->id);
|
||||
}
|
||||
|
||||
return $query->exists();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Enums\ProxyTypes;
|
||||
|
|
@ -26,22 +27,23 @@
|
|||
description: 'Server model',
|
||||
type: 'object',
|
||||
properties: [
|
||||
'id' => ['type' => 'integer'],
|
||||
'uuid' => ['type' => 'string'],
|
||||
'name' => ['type' => 'string'],
|
||||
'description' => ['type' => 'string'],
|
||||
'ip' => ['type' => 'string'],
|
||||
'user' => ['type' => 'string'],
|
||||
'port' => ['type' => 'integer'],
|
||||
'proxy' => ['type' => 'object'],
|
||||
'high_disk_usage_notification_sent' => ['type' => 'boolean'],
|
||||
'unreachable_notification_sent' => ['type' => 'boolean'],
|
||||
'unreachable_count' => ['type' => 'integer'],
|
||||
'validation_logs' => ['type' => 'string'],
|
||||
'log_drain_notification_sent' => ['type' => 'boolean'],
|
||||
'swarm_cluster' => ['type' => 'string'],
|
||||
'delete_unused_volumes' => ['type' => 'boolean'],
|
||||
'delete_unused_networks' => ['type' => 'boolean'],
|
||||
'id' => ['type' => 'integer', 'description' => 'The server ID.'],
|
||||
'uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||
'name' => ['type' => 'string', 'description' => 'The server name.'],
|
||||
'description' => ['type' => 'string', 'description' => 'The server description.'],
|
||||
'ip' => ['type' => 'string', 'description' => 'The IP address.'],
|
||||
'user' => ['type' => 'string', 'description' => 'The user.'],
|
||||
'port' => ['type' => 'integer', 'description' => 'The port number.'],
|
||||
'proxy' => ['type' => 'object', 'description' => 'The proxy configuration.'],
|
||||
'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'description' => 'The proxy type.'],
|
||||
'high_disk_usage_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the high disk usage notification has been sent.'],
|
||||
'unreachable_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unreachable notification has been sent.'],
|
||||
'unreachable_count' => ['type' => 'integer', 'description' => 'The unreachable count for your server.'],
|
||||
'validation_logs' => ['type' => 'string', 'description' => 'The validation logs.'],
|
||||
'log_drain_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the log drain notification has been sent.'],
|
||||
'swarm_cluster' => ['type' => 'string', 'description' => 'The swarm cluster configuration.'],
|
||||
'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'],
|
||||
'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'],
|
||||
]
|
||||
)]
|
||||
|
||||
|
|
@ -462,7 +464,7 @@ public function reloadCaddy()
|
|||
|
||||
public function proxyPath()
|
||||
{
|
||||
$base_path = config('coolify.base_config_path');
|
||||
$base_path = config('constants.coolify.base_config_path');
|
||||
$proxyType = $this->proxyType();
|
||||
$proxy_path = "$base_path/proxy";
|
||||
// TODO: should use /traefik for already exisiting configurations?
|
||||
|
|
@ -1057,10 +1059,6 @@ public function validateConnection(bool $isManualCheck = true, bool $justCheckin
|
|||
return ['uptime' => false, 'error' => 'Server skipped.'];
|
||||
}
|
||||
try {
|
||||
// Make sure the private key is stored
|
||||
if ($this->privateKey) {
|
||||
$this->privateKey->storeInFileSystem();
|
||||
}
|
||||
instant_remote_process(['ls /'], $this);
|
||||
if ($this->settings->is_reachable === false) {
|
||||
$this->settings->is_reachable = true;
|
||||
|
|
@ -1251,4 +1249,25 @@ public function restartContainer(string $containerName)
|
|||
{
|
||||
return instant_remote_process(['docker restart '.$containerName], $this, false);
|
||||
}
|
||||
|
||||
public function changeProxy(string $proxyType, bool $async = true)
|
||||
{
|
||||
$validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
|
||||
return str($proxyType->value)->lower();
|
||||
});
|
||||
if ($validProxyTypes->contains(str($proxyType)->lower())) {
|
||||
$this->proxy->set('type', str($proxyType)->upper());
|
||||
$this->proxy->set('status', 'exited');
|
||||
$this->save();
|
||||
if ($this->proxySet()) {
|
||||
if ($async) {
|
||||
StartProxy::dispatch($this);
|
||||
} else {
|
||||
StartProxy::run($this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new \Exception('Invalid proxy type.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1171,7 +1171,7 @@ public function documentation()
|
|||
$services = get_service_templates();
|
||||
$service = data_get($services, str($this->name)->beforeLast('-')->value, []);
|
||||
|
||||
return data_get($service, 'documentation', config('constants.docs.base_url'));
|
||||
return data_get($service, 'documentation', config('constants.urls.docs'));
|
||||
}
|
||||
|
||||
public function applications()
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ public function limits(): Attribute
|
|||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (config('coolify.self_hosted') || $this->id === 0) {
|
||||
if (config('constants.coolify.self_hosted') || $this->id === 0) {
|
||||
$subscription = 'self-hosted';
|
||||
} else {
|
||||
$subscription = data_get($this, 'subscription');
|
||||
|
|
@ -257,8 +257,15 @@ public function s3s()
|
|||
return $this->hasMany(S3Storage::class)->where('is_usable', true);
|
||||
}
|
||||
|
||||
public function trialEnded()
|
||||
public function subscriptionEnded()
|
||||
{
|
||||
$this->subscription->update([
|
||||
'stripe_subscription_id' => null,
|
||||
'stripe_plan_id' => null,
|
||||
'stripe_cancel_at_period_end' => false,
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => false,
|
||||
]);
|
||||
foreach ($this->servers as $server) {
|
||||
$server->settings()->update([
|
||||
'is_usable' => false,
|
||||
|
|
@ -267,16 +274,6 @@ public function trialEnded()
|
|||
}
|
||||
}
|
||||
|
||||
public function trialEndedButSubscribed()
|
||||
{
|
||||
foreach ($this->servers as $server) {
|
||||
$server->settings()->update([
|
||||
'is_usable' => true,
|
||||
'is_reachable' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function isAnyNotificationEnabled()
|
||||
{
|
||||
if (isCloud()) {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||
|
||||
public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->application = $application;
|
||||
$this->deployment_uuid = $deployment_uuid;
|
||||
$this->preview = $preview;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
|||
|
||||
public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->application = $application;
|
||||
$this->deployment_uuid = $deployment_uuid;
|
||||
$this->preview = $preview;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class StatusChanged extends Notification implements ShouldQueue
|
|||
|
||||
public function __construct(public Application $resource)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->resource_name = data_get($resource, 'name');
|
||||
$this->project_uuid = data_get($resource, 'environment.project.uuid');
|
||||
$this->environment_name = data_get($resource, 'environment.name');
|
||||
|
|
|
|||
|
|
@ -17,6 +17,6 @@ public function send(SendsDiscord $notifiable, Notification $notification): void
|
|||
if (! $webhookUrl) {
|
||||
return;
|
||||
}
|
||||
dispatch(new SendMessageToDiscordJob($message, $webhookUrl))->onQueue('high');
|
||||
SendMessageToDiscordJob::dispatch($message, $webhookUrl);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,6 @@ public function send($notifiable, $notification): void
|
|||
if (! $telegramToken || ! $chatId || ! $message) {
|
||||
return;
|
||||
}
|
||||
dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId, $topicId))->onQueue('high');
|
||||
SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ class ContainerRestarted extends Notification implements ShouldQueue
|
|||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public string $name, public Server $server, public ?string $url = null) {}
|
||||
public function __construct(public string $name, public Server $server, public ?string $url = null)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ class ContainerStopped extends Notification implements ShouldQueue
|
|||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public string $name, public Server $server, public ?string $url = null) {}
|
||||
public function __construct(public string $name, public Server $server, public ?string $url = null)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class BackupFailed extends Notification implements ShouldQueue
|
|||
|
||||
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output, public $database_name)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->name = $database->name;
|
||||
$this->frequency = $backup->frequency;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class BackupSuccess extends Notification implements ShouldQueue
|
|||
|
||||
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $database_name)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
$this->name = $database->name;
|
||||
$this->frequency = $backup->frequency;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ class GeneralNotification extends Notification implements ShouldQueue
|
|||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public string $message) {}
|
||||
public function __construct(public string $message)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class TaskFailed extends Notification implements ShouldQueue
|
|||
|
||||
public function __construct(public ScheduledTask $task, public string $output)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
if ($task->application) {
|
||||
$this->url = $task->application->failedTaskLink($task->uuid);
|
||||
} elseif ($task->service) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ class DockerCleanup extends Notification implements ShouldQueue
|
|||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public Server $server, public string $message) {}
|
||||
public function __construct(public Server $server, public string $message)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue