Merge branch 'coollabsio:main' into service-getoutline

This commit is contained in:
peaklabs-dev
2024-09-05 18:05:58 +02:00
committed by GitHub
184 changed files with 6710 additions and 2175 deletions

View File

@@ -1,16 +1,31 @@
APP_NAME=Coolify-localhost # Coolify Configuration
APP_ID=development
APP_ENV=local APP_ENV=local
APP_NAME="Coolify Development"
APP_ID=development
APP_KEY= APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
APP_PORT=8000 APP_PORT=8000
APP_DEBUG=true
MUX_ENABLED=false MUX_ENABLED=false
# Enable Laravel Telescope for debugging
TELESCOPE_ENABLED=false
# Selenium Driver URL for Dusk
DUSK_DRIVER_URL=http://selenium:4444 DUSK_DRIVER_URL=http://selenium:4444
## For Andras only # PostgreSQL Database Configuration
# To purge cache DB_DATABASE=coolify
DB_USERNAME=coolify
DB_PASSWORD=password
DB_HOST=host.docker.internal
DB_PORT=5432
#Set custom ray port
RAY_PORT=
# Special Keys for Andras
# For cache purging
BUNNY_API_KEY= BUNNY_API_KEY=
# To upload assets # For asset uploads
BUNNY_STORAGE_API_KEY= BUNNY_STORAGE_API_KEY=

View File

@@ -1,10 +1,16 @@
# Coolify Configuration
APP_ID= APP_ID=
APP_NAME=Coolify APP_NAME=Coolify
APP_KEY= APP_KEY=
# PostgreSQL Database Configuration
DB_USERNAME=coolify
DB_PASSWORD= DB_PASSWORD=
# Redis Configuration
REDIS_PASSWORD= REDIS_PASSWORD=
# Pusher Configuration
PUSHER_APP_ID= PUSHER_APP_ID=
PUSHER_APP_KEY= PUSHER_APP_KEY=
PUSHER_APP_SECRET= PUSHER_APP_SECRET=

View File

@@ -25,6 +25,10 @@ jobs:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
@@ -33,7 +37,7 @@ jobs:
file: docker/coolify-helper/Dockerfile file: docker/coolify-helper/Dockerfile
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
aarch64: aarch64:
runs-on: [ self-hosted, arm64 ] runs-on: [ self-hosted, arm64 ]
permissions: permissions:
@@ -47,6 +51,10 @@ jobs:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
@@ -55,7 +63,7 @@ jobs:
file: docker/coolify-helper/Dockerfile file: docker/coolify-helper/Dockerfile
platforms: linux/aarch64 platforms: linux/aarch64
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
merge-manifest: merge-manifest:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
@@ -75,9 +83,13 @@ jobs:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest - name: Create & publish manifest
run: | run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1 - uses: sarisia/actions-status-discord@v1
if: always() if: always()
with: with:

189
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,189 @@
# Contributing
> "First, thanks for considering contributing to my project. It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
You can ask for guidance anytime on our [Discord server](https://coollabs.io/discord) in the `#contribute` channel.
## Code Contribution
## 1. Setup your development environment
Follow the steps below for your operating system:
### Windows
1. Install `docker-ce`, Docker Desktop (or similar):
- Docker CE (recommended):
- Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install)
- After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/)
- Make sure to choose the appropriate Linux distribution (e.g., Ubuntu) when following the Docker installation guide
- Install Docker Desktop (easier):
- Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)
- Ensure WSL2 backend is enabled in Docker Desktop settings
2. Install Spin:
- Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2)
### MacOS
1. Install Orbstack, Docker Desktop (or similar):
- Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop):
- Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation)
- Docker Desktop:
- Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/)
2. Install Spin:
- Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin)
### Linux
1. Install Docker Engine, Docker Desktop (or similar):
- Docker Engine (recommended, as there is no VM overhead):
- Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/) for your Linux distribution
- Docker Desktop:
- If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/)
2. Install Spin:
- Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions)
## 2. Verify installation (optional)
After installing Docker (or Orbstack) and Spin, verify the installation:
1. Open a terminal or command prompt
2. Run the following commands:
```bash
docker --version
spin --version
```
You should see version information for both Docker and Spin.
## 3. Fork the Coolify repository and setup your local repository
1. Fork the [Coolify](https://github.com/coollabsio/coolify) repository to your GitHub account.
2. Install a code editor on your machine (below are some popular choices, choose one):
- Visual Studio Code (recommended free):
- Windows/macOS/Linux: Download and install from [https://code.visualstudio.com/download](https://code.visualstudio.com/download)
- Cursor (recommended but paid for getting the full benefits):
- Windows/macOS/Linux: Download and install from [https://www.cursor.com/](https://www.cursor.com/)
- Zed (very fast code editor):
- macOS/Linux: Download and install from [https://zed.dev/download](https://zed.dev/download)
- Windows: Not available yet
3. Clone the Coolify Repository from your fork to your local machine
- Use `git clone` in the command line
- Use GitHub Desktop (recommended):
- Download and install from [https://desktop.github.com/](https://desktop.github.com/)
- Open GitHub Desktop and login with your GitHub account
- Click on `File` -> `Clone Repository` select `github.com` as the repository location, then select your forked Coolify repository, choose the local path and then click `Clone`
4. Open the cloned Coolify Repository in your chosen code editor.
## 4. Set up Environment Variables
1. In the Code Editor, locate the `.env.development.example` file in the root directory of your local Coolify repository.
2. Duplicate the `.env.development.example` file and rename the copy to `.env`.
3. Open the new `.env` file and review its contents. Adjust any environment variables as needed for your development setup.
4. If you encounter errors during database migrations, update the database connection settings in your `.env` file. Use the IP address or hostname of your PostgreSQL database container. You can find this information by running `docker ps` after executing `spin up`.
5. Save the changes to your `.env` file.
## 5. Start Coolify
1. Open a terminal in the local Coolify directory.
2. Run the following command in the terminal (leave that terminal open):
```
spin up
```
Note: You may see some errors, but don't worry; this is expected.
3. If you encounter permission errors, especially on macOS, use:
```
sudo spin up
```
Note: If you change environment variables afterwards or anything seems broken, press Ctrl + C to stop the process and run `spin up` again.
## 6. Start Development
1. Access your Coolify instance:
- URL: `http://localhost:8000`
- Login: `test@example.com`
- Password: `password`
2. Additional development tools:
- Laravel Horizon (scheduler): `http://localhost:8000/horizon`
Note: Only accessible when logged in as root user
- Mailpit (email catcher): `http://localhost:8025`
- Telescope (debugging tool): `http://localhost:8000/telescope`
Note: Disabled by default (so the database is not overloaded), enable by adding the following environment variable to your `.env` file:
```env
TELESCOPE_ENABLED=true
```
## 7. Development Notes
When working on Coolify, keep the following in mind:
1. **Database Migrations**: After switching branches or making changes to the database structure, always run migrations:
```bash
docker exec -it coolify php artisan migrate
```
2. **Resetting Development Setup**: To reset your development setup to a clean database with default values:
```bash
docker exec -it coolify php artisan migrate:fresh --seed
```
3. **Troubleshooting**: If you encounter unexpected behavior, ensure your database is up-to-date with the latest migrations and if possible reset the development setup to eliminate any envrionement specific issues.
Remember, forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches.
## 8. Contributing a New Service
To add a new service to Coolify, please refer to our documentation:
[Adding a New Service](https://coolify.io/docs/knowledge-base/add-a-service)
## 9. Create a Pull Request
1. After making changes or adding a new service:
- Commit your changes to your forked repository.
- Push the changes to your GitHub account.
2. Creating the Pull Request (PR):
- Navigate to the main Coolify repository on GitHub.
- Click the "Pull requests" tab.
- Click the green "New pull request" button.
- Choose your fork and branch as the compare branch.
- Click "Create pull request".
3. Filling out the PR details:
- Give your PR a descriptive title.
- In the description, explain the changes you've made.
- Reference any related issues by using keywords like "Fixes #123" or "Closes #456".
4. Important note:
Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
5. Submit your PR:
- Review your changes one last time.
- Click "Create pull request" to submit.
After submission, maintainers will review your PR and may request changes or provide feedback.

View File

@@ -1,34 +0,0 @@
# Contributing
> "First, thanks for considering to contribute to my project.
It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
You can ask for guidance anytime on our
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
## Code Contribution
### 1) Setup your development environment
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
### 2) Set your environment variables
- Copy [.env.development.example](./.env.development.example) to .env.
## 3) Start & setup Coolify
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
### 4) Start development
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
Mails are caught by Mailpit: `localhost:8025`
## New Service Contribution
Check out the docs [here](https://coolify.io/docs/knowledge-base/add-a-service).

View File

@@ -35,20 +35,32 @@ Thank you so much!
Special thanks to our biggest sponsors! Special thanks to our biggest sponsors!
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a> ### Special Sponsors
<a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="150"/></a>
<a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="150"/></a> ![image](https://github.com/user-attachments/assets/c95a07df-7c5a-4e77-a35a-81f25fcbece1)
<a href="https://bc.direct/?ref=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a>
<a href="https://www.quantcdn.io/?ref=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="150"/></a> * [CCCareers](https://cccareers.org/) - A career development platform for coding bootcamp graduates.
<a href="https://arcjet.com/?ref=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a> * [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering dedicated servers and cloud services.
<a href="https://supa.guide/?ref=coolify.io" target="_blank"><img src="./other/logos/supaguide.png" alt="supaguide logo" width="200"/></a> * [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization platform.
<a href="https://tigrisdata.com/?ref=coolify.io" target="_blank"><img src="./other/logos/tigris.svg" alt="tigris logo" width="140"/></a> * [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions.
<a href="https://fractalnetworks.co/?ref=coolify.io" target="_blank"><img src="./other/logos/fractal.svg" alt="fractal logo" width="180"/></a> * [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) for fast content delivery.
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a> * [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform for data analytics and visualization.
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a> * [SupaGuide](https://supa.guide/?ref=coolify.io) - A platform offering guides and resources for web development and design.
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a> * [Tigris](https://tigrisdata.com/?ref=coolify.io) - A data integration platform for connecting and managing data sources.
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a> * [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure for secure data exchange.
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a> * [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising.
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered data analytics platform for business insights.
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-based platform for automating DevOps and infrastructure management.
* [Latitude](https://latitude.sh/?ref=coolify.io) - A platform offering location-based services and geospatial data.
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in brand identity and digital presence.
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform specializing in remote and flexible work opportunities.
* [Hostinger](https://hostinger.com?ref=coolify.io) - A web hosting company offering shared, VPS, and cloud hosting services.
* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps and cloud consulting company offering infrastructure automation services.
* [Ubicloud](https://ubicloud.com/?ref=coolify.io) - A cloud-based platform for IoT device management and data analytics.
* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and marketing services.
* [Saasykit](https://saasykit.com/?ref=coolify.io) - SaaSykit is a Laravel-based boilerplate with everything you need to build an awesome SaaS.
* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud-based platform for data storage and processing.
## Github Sponsors ($40+) ## Github Sponsors ($40+)
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a> <a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
@@ -71,8 +83,11 @@ Special thanks to our biggest sponsors!
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a> <a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a> <a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a> <a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
<a href="https://www.breakcold.com/?utm_source=coolify.io"><img src="https://github.com/breakcold.png" width="60px" alt="Breakcold" /></a>
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a> <a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a> <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>
## Organizations ## Organizations
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a> <a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>

View File

@@ -79,14 +79,7 @@ class StartClickhouse
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
@@ -102,6 +95,11 @@ class StartClickhouse
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -162,6 +160,8 @@ class StartClickhouse
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}"); $environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
} }
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
return $environment_variables->all(); return $environment_variables->all();
} }
} }

View File

@@ -79,14 +79,7 @@ class StartDragonfly
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
@@ -102,6 +95,11 @@ class StartDragonfly
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";

View File

@@ -78,14 +78,7 @@ class StartKeydb
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
@@ -110,6 +103,10 @@ class StartKeydb
]; ];
$docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes"; $docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -166,6 +163,8 @@ class StartKeydb
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}"); $environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
} }
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
return $environment_variables->all(); return $environment_variables->all();
} }

View File

@@ -73,14 +73,7 @@ class StartMariadb
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
@@ -104,6 +97,11 @@ class StartMariadb
'read_only' => true, 'read_only' => true,
]; ];
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -171,6 +169,8 @@ class StartMariadb
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}"); $environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
} }
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
return $environment_variables->all(); return $environment_variables->all();
} }

View File

@@ -81,14 +81,7 @@ class StartMongodb
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
@@ -121,6 +114,10 @@ class StartMongodb
'read_only' => true, 'read_only' => true,
]; ];
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -185,6 +182,8 @@ class StartMongodb
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}"); $environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
} }
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
return $environment_variables->all(); return $environment_variables->all();
} }

View File

@@ -73,14 +73,7 @@ class StartMysql
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
@@ -104,6 +97,11 @@ class StartMysql
'read_only' => true, 'read_only' => true,
]; ];
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -171,6 +169,8 @@ class StartMysql
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}"); $environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
} }
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
return $environment_variables->all(); return $environment_variables->all();
} }

View File

@@ -37,6 +37,7 @@ class StartPostgresql
$this->generate_init_scripts(); $this->generate_init_scripts();
$this->add_custom_conf(); $this->add_custom_conf();
$docker_compose = [ $docker_compose = [
'services' => [ 'services' => [
$container_name => [ $container_name => [
@@ -80,14 +81,7 @@ class StartPostgresql
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
@@ -126,6 +120,10 @@ class StartPostgresql
'config_file=/etc/postgresql/postgresql.conf', 'config_file=/etc/postgresql/postgresql.conf',
]; ];
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -193,6 +191,8 @@ class StartPostgresql
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}"); $environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
} }
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
return $environment_variables->all(); return $environment_variables->all();
} }

View File

@@ -82,14 +82,7 @@ class StartRedis
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
@@ -114,6 +107,11 @@ class StartRedis
]; ];
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes"; $docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -170,6 +168,8 @@ class StartRedis
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}"); $environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
} }
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
return $environment_variables->all(); return $environment_variables->all();
} }

View File

@@ -9,17 +9,24 @@ class CleanupDocker
{ {
use AsAction; use AsAction;
public function handle(Server $server, bool $force = true) public function handle(Server $server)
{ {
// cleanup docker images, containers, and builder caches
if ($force) { $commands = $this->getCommands();
instant_remote_process(['docker image prune -af'], $server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false); foreach ($commands as $command) {
instant_remote_process(['docker builder prune -af'], $server, false); instant_remote_process([$command], $server, false);
} else {
instant_remote_process(['docker image prune -f'], $server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
instant_remote_process(['docker builder prune -f'], $server, false);
} }
} }
private function getCommands(): array
{
$commonCommands = [
'docker container prune -f --filter "label=coolify.managed=true"',
'docker image prune -af',
'docker builder prune -af',
];
return $commonCommands;
}
} }

View File

@@ -47,7 +47,11 @@ class InstallLogDrain
[FILTER] [FILTER]
Name modify Name modify
Match * Match *
Set server_name {$server->name} Set coolify.server_name {$server->name}
Rename COOLIFY_APP_NAME coolify.app_name
Rename COOLIFY_PROJECT_NAME coolify.project_name
Rename COOLIFY_SERVER_IP coolify.server_ip
Rename COOLIFY_ENVIRONMENT_NAME coolify.environment_name
[OUTPUT] [OUTPUT]
Name nrlogs Name nrlogs
Match * Match *
@@ -98,7 +102,11 @@ class InstallLogDrain
[FILTER] [FILTER]
Name modify Name modify
Match * Match *
Set server_name {$server->name} Set coolify.server_name {$server->name}
Rename COOLIFY_APP_NAME coolify.app_name
Rename COOLIFY_PROJECT_NAME coolify.project_name
Rename COOLIFY_SERVER_IP coolify.server_ip
Rename COOLIFY_ENVIRONMENT_NAME coolify.environment_name
[OUTPUT] [OUTPUT]
Name http Name http
Match * Match *

View File

@@ -26,7 +26,7 @@ class UpdateCoolify
if (! $this->server) { if (! $this->server) {
return; return;
} }
CleanupDocker::dispatch($this->server, false)->onQueue('high'); CleanupDocker::dispatch($this->server)->onQueue('high');
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) { if ($response->successful()) {
$versions = $response->json(); $versions = $response->json();

View File

@@ -19,14 +19,19 @@ class StopService
ray('Stopping service: '.$service->name); ray('Stopping service: '.$service->name);
$applications = $service->applications()->get(); $applications = $service->applications()->get();
foreach ($applications as $application) { foreach ($applications as $application) {
instant_remote_process(command: ["docker stop --time=30 {$application->name}-{$service->uuid}"], server: $server, throwError: false); if ($applications->count() < 6) {
instant_remote_process(command: ["docker stop --time=10 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
}
instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false); instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false); instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
$application->update(['status' => 'exited']); $application->update(['status' => 'exited']);
} }
$dbs = $service->databases()->get(); $dbs = $service->databases()->get();
foreach ($dbs as $db) { foreach ($dbs as $db) {
instant_remote_process(command: ["docker stop --time=30 {$db->name}-{$service->uuid}"], server: $server, throwError: false); if ($dbs->count() < 6) {
instant_remote_process(command: ["docker stop --time=10 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
}
instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false); instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false); instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
$db->update(['status' => 'exited']); $db->update(['status' => 'exited']);

View File

@@ -3,6 +3,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\ScheduledTask; use App\Models\ScheduledTask;
use App\Models\Service; use App\Models\Service;
use App\Models\ServiceApplication; use App\Models\ServiceApplication;
@@ -42,6 +43,17 @@ class CleanupStuckedResources extends Command
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo "Error in cleaning stuck application: {$e->getMessage()}\n"; echo "Error in cleaning stuck application: {$e->getMessage()}\n";
} }
try {
$applicationsPreviews = ApplicationPreview::get();
foreach ($applicationsPreviews as $applicationPreview) {
if (! data_get($applicationPreview, 'application')) {
echo "Deleting stuck application preview: {$applicationPreview->uuid}\n";
$applicationPreview->delete();
}
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
}
try { try {
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get(); $postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($postgresqls as $postgresql) { foreach ($postgresqls as $postgresql) {

View File

@@ -132,6 +132,9 @@ class Init extends Command
private function cleanup_unused_network_from_coolify_proxy() private function cleanup_unused_network_from_coolify_proxy()
{ {
if (isCloud()) {
return;
}
foreach ($this->servers as $server) { foreach ($this->servers as $server) {
if (! $server->isFunctional()) { if (! $server->isFunctional()) {
continue; continue;

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;
class OpenApi extends Command
{
protected $signature = 'openapi';
protected $description = 'Generate OpenApi file.';
public function handle()
{
// Generate OpenAPI documentation
echo "Generating OpenAPI documentation.\n";
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
$error = $process->errorOutput();
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
$error = preg_replace('/^\h*\v+/m', '', $error);
echo $error;
echo $process->output();
}
}

View File

@@ -16,7 +16,7 @@ class SyncBunny extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'sync:bunny {--templates} {--release}'; protected $signature = 'sync:bunny {--templates} {--release} {--nightly}';
/** /**
* The console command description. * The console command description.
@@ -33,6 +33,7 @@ class SyncBunny extends Command
$that = $this; $that = $this;
$only_template = $this->option('templates'); $only_template = $this->option('templates');
$only_version = $this->option('release'); $only_version = $this->option('release');
$nightly = $this->option('nightly');
$bunny_cdn = 'https://cdn.coollabs.io'; $bunny_cdn = 'https://cdn.coollabs.io';
$bunny_cdn_path = 'coolify'; $bunny_cdn_path = 'coolify';
$bunny_cdn_storage_name = 'coolcdn'; $bunny_cdn_storage_name = 'coolcdn';
@@ -45,9 +46,15 @@ class SyncBunny extends Command
$upgrade_script = 'upgrade.sh'; $upgrade_script = 'upgrade.sh';
$production_env = '.env.production'; $production_env = '.env.production';
$service_template = 'service-templates.json'; $service_template = 'service-templates.json';
$versions = 'versions.json'; $versions = 'versions.json';
$compose_file_location = "$parent_dir/$compose_file";
$compose_file_prod_location = "$parent_dir/$compose_file_prod";
$install_script_location = "$parent_dir/scripts/install.sh";
$upgrade_script_location = "$parent_dir/scripts/upgrade.sh";
$production_env_location = "$parent_dir/.env.production";
$versions_location = "$parent_dir/$versions";
PendingRequest::macro('storage', function ($fileName) use ($that) { PendingRequest::macro('storage', function ($fileName) use ($that) {
$headers = [ $headers = [
'AccessKey' => env('BUNNY_STORAGE_API_KEY'), 'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
@@ -73,8 +80,26 @@ class SyncBunny extends Command
]); ]);
}); });
try { try {
if ($nightly) {
$bunny_cdn_path = 'coolify-nightly';
$compose_file_location = "$parent_dir/other/nightly/$compose_file";
$compose_file_prod_location = "$parent_dir/other/nightly/$compose_file_prod";
$production_env_location = "$parent_dir/other/nightly/$production_env";
$upgrade_script_location = "$parent_dir/other/nightly/$upgrade_script";
$install_script_location = "$parent_dir/other/nightly/$install_script";
$versions_location = "$parent_dir/other/nightly/$versions";
}
if (! $only_template && ! $only_version) { if (! $only_template && ! $only_version) {
$this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.'); if ($nightly) {
$this->info('About to sync files NIGHTLY (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
} else {
$this->info('About to sync files PRODUCTION (docker-compose.yml, docker-compose.prod.yml, upgrade.sh, install.sh, etc) to BunnyCDN.');
}
$confirmed = confirm('Are you sure you want to sync?');
if (! $confirmed) {
return;
}
} }
if ($only_template) { if ($only_template) {
$this->info('About to sync service-templates.json to BunnyCDN.'); $this->info('About to sync service-templates.json to BunnyCDN.');
@@ -90,8 +115,12 @@ class SyncBunny extends Command
return; return;
} elseif ($only_version) { } elseif ($only_version) {
$this->info('About to sync versions.json to BunnyCDN.'); if ($nightly) {
$file = file_get_contents("$parent_dir/$versions"); $this->info('About to sync NIGHLTY versions.json to BunnyCDN.');
} else {
$this->info('About to sync PRODUCTION versions.json to BunnyCDN.');
}
$file = file_get_contents($versions_location);
$json = json_decode($file, true); $json = json_decode($file, true);
$actual_version = data_get($json, 'coolify.v4.version'); $actual_version = data_get($json, 'coolify.v4.version');
@@ -100,7 +129,7 @@ class SyncBunny extends Command
return; return;
} }
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"), $pool->storage(fileName: $versions_location)->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]); ]);
$this->info('versions.json uploaded & purged...'); $this->info('versions.json uploaded & purged...');
@@ -109,11 +138,11 @@ class SyncBunny extends Command
} }
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"), $pool->storage(fileName: "$compose_file_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"), $pool->storage(fileName: "$compose_file_prod_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
$pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"), $pool->storage(fileName: "$production_env_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
$pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"), $pool->storage(fileName: "$upgrade_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
$pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"), $pool->storage(fileName: "$install_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
]); ]);
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),

View File

@@ -30,11 +30,11 @@ class Kernel extends ConsoleKernel
$this->all_servers = Server::all(); $this->all_servers = Server::all();
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
$schedule->command('telescope:prune')->daily();
if (isDev()) { if (isDev()) {
// Instance Jobs // Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute(); $schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
// Server Jobs // Server Jobs
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
@@ -43,9 +43,9 @@ class Kernel extends ConsoleKernel
} else { } else {
// Instance Jobs // Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes(); $schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily(); $schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
$schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->onOneServer(); $schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->onOneServer(); $schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
$this->schedule_updates($schedule); $this->schedule_updates($schedule);
@@ -66,9 +66,19 @@ class Kernel extends ConsoleKernel
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); $servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) { foreach ($servers as $server) {
if ($server->isSentinelEnabled()) { if ($server->isSentinelEnabled()) {
$schedule->job(new PullSentinelImageJob($server))->cron($settings->update_check_frequency)->onOneServer(); $schedule->job(function () use ($server) {
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $server, false);
$sentinel_found = json_decode($sentinel_found, true);
$status = data_get($sentinel_found, '0.State.Status', 'exited');
if ($status !== 'running') {
PullSentinelImageJob::dispatch($server);
} }
$schedule->job(new PullHelperImageJob($server))->cron($settings->update_check_frequency)->onOneServer(); })->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
}
$schedule->job(new PullHelperImageJob($server))
->cron($settings->update_check_frequency)
->timezone($settings->instance_timezone)
->onOneServer();
} }
} }
@@ -77,11 +87,17 @@ class Kernel extends ConsoleKernel
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
$updateCheckFrequency = $settings->update_check_frequency; $updateCheckFrequency = $settings->update_check_frequency;
$schedule->job(new CheckForUpdatesJob)->cron($updateCheckFrequency)->onOneServer(); $schedule->job(new CheckForUpdatesJob)
->cron($updateCheckFrequency)
->timezone($settings->instance_timezone)
->onOneServer();
if ($settings->is_auto_update_enabled) { if ($settings->is_auto_update_enabled) {
$autoUpdateFrequency = $settings->auto_update_frequency; $autoUpdateFrequency = $settings->auto_update_frequency;
$schedule->job(new UpdateCoolifyJob)->cron($autoUpdateFrequency)->onOneServer(); $schedule->job(new UpdateCoolifyJob)
->cron($autoUpdateFrequency)
->timezone($settings->instance_timezone)
->onOneServer();
} }
} }
@@ -96,7 +112,12 @@ class Kernel extends ConsoleKernel
} }
foreach ($servers as $server) { foreach ($servers as $server) {
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer(); $schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer(); $serverTimezone = $server->settings->server_timezone;
if ($server->settings->force_docker_cleanup) {
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
} else {
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
}
} }
} }
@@ -117,12 +138,15 @@ class Kernel extends ConsoleKernel
continue; continue;
} }
$server = $scheduled_backup->server();
$serverTimezone = $server->settings->server_timezone;
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
} }
$schedule->job(new DatabaseBackupJob( $schedule->job(new DatabaseBackupJob(
backup: $scheduled_backup backup: $scheduled_backup
))->cron($scheduled_backup->frequency)->onOneServer(); ))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer();
} }
} }
@@ -155,12 +179,16 @@ class Kernel extends ConsoleKernel
continue; continue;
} }
} }
$server = $scheduled_task->server();
$serverTimezone = $server->settings->server_timezone ?: config('app.timezone');
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
} }
$schedule->job(new ScheduledTaskJob( $schedule->job(new ScheduledTaskJob(
task: $scheduled_task task: $scheduled_task
))->cron($scheduled_task->frequency)->onOneServer(); ))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer();
} }
} }

View File

@@ -53,6 +53,7 @@ class ApplicationsController extends Controller
summary: 'List', summary: 'List',
description: 'List all applications.', description: 'List all applications.',
path: '/applications', path: '/applications',
operationId: 'list-applications',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -101,6 +102,7 @@ class ApplicationsController extends Controller
summary: 'Create (Public)', summary: 'Create (Public)',
description: 'Create new application based on a public git repository.', description: 'Create new application based on a public git repository.',
path: '/applications/public', path: '/applications/public',
operationId: 'create-public-application',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -201,7 +203,8 @@ class ApplicationsController extends Controller
#[OA\Post( #[OA\Post(
summary: 'Create (Private - GH App)', summary: 'Create (Private - GH App)',
description: 'Create new application based on a private repository through a Github App.', description: 'Create new application based on a private repository through a Github App.',
path: '/applications/private-gh-app', path: '/applications/private-github-app',
operationId: 'create-private-github-app-application',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -303,6 +306,7 @@ class ApplicationsController extends Controller
summary: 'Create (Private - Deploy Key)', summary: 'Create (Private - Deploy Key)',
description: 'Create new application based on a private repository through a Deploy Key.', description: 'Create new application based on a private repository through a Deploy Key.',
path: '/applications/private-deploy-key', path: '/applications/private-deploy-key',
operationId: 'create-private-deploy-key-application',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -404,6 +408,7 @@ class ApplicationsController extends Controller
summary: 'Create (Dockerfile)', summary: 'Create (Dockerfile)',
description: 'Create new application based on a simple Dockerfile.', description: 'Create new application based on a simple Dockerfile.',
path: '/applications/dockerfile', path: '/applications/dockerfile',
operationId: 'create-dockerfile-application',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -490,6 +495,7 @@ class ApplicationsController extends Controller
summary: 'Create (Docker Image)', summary: 'Create (Docker Image)',
description: 'Create new application based on a prebuilt docker image', description: 'Create new application based on a prebuilt docker image',
path: '/applications/dockerimage', path: '/applications/dockerimage',
operationId: 'create-dockerimage-application',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -573,6 +579,7 @@ class ApplicationsController extends Controller
summary: 'Create (Docker Compose)', summary: 'Create (Docker Compose)',
description: 'Create new application based on a docker-compose file.', description: 'Create new application based on a docker-compose file.',
path: '/applications/dockercompose', path: '/applications/dockercompose',
operationId: 'create-dockercompose-application',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1171,6 +1178,7 @@ class ApplicationsController extends Controller
summary: 'Get', summary: 'Get',
description: 'Get application by UUID.', description: 'Get application by UUID.',
path: '/applications/{uuid}', path: '/applications/{uuid}',
operationId: 'get-application-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1235,6 +1243,7 @@ class ApplicationsController extends Controller
summary: 'Delete', summary: 'Delete',
description: 'Delete application by UUID.', description: 'Delete application by UUID.',
path: '/applications/{uuid}', path: '/applications/{uuid}',
operationId: 'delete-application-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1321,6 +1330,7 @@ class ApplicationsController extends Controller
summary: 'Update', summary: 'Update',
description: 'Update application by UUID.', description: 'Update application by UUID.',
path: '/applications/{uuid}', path: '/applications/{uuid}',
operationId: 'update-application-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1450,7 +1460,7 @@ class ApplicationsController extends Controller
], 404); ], 404);
} }
$server = $application->destination->server; $server = $application->destination->server;
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect']; $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy'];
$validator = customApiValidator($request->all(), [ $validator = customApiValidator($request->all(), [
sharedDataApplications(), sharedDataApplications(),
@@ -1526,6 +1536,10 @@ class ApplicationsController extends Controller
} }
$request->offsetUnset('docker_compose_domains'); $request->offsetUnset('docker_compose_domains');
} }
$instantDeploy = $request->instant_deploy;
removeUnnecessaryFieldsFromRequest($request);
$data = $request->all(); $data = $request->all();
data_set($data, 'fqdn', $domains); data_set($data, 'fqdn', $domains);
if ($dockerComposeDomainsJson->count() > 0) { if ($dockerComposeDomainsJson->count() > 0) {
@@ -1534,6 +1548,16 @@ class ApplicationsController extends Controller
$application->fill($data); $application->fill($data);
$application->save(); $application->save();
if ($instantDeploy) {
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
is_api: true,
);
}
return response()->json([ return response()->json([
'uuid' => $application->uuid, 'uuid' => $application->uuid,
]); ]);
@@ -1543,6 +1567,7 @@ class ApplicationsController extends Controller
summary: 'List Envs', summary: 'List Envs',
description: 'List all envs by application UUID.', description: 'List all envs by application UUID.',
path: '/applications/{uuid}/envs', path: '/applications/{uuid}/envs',
operationId: 'list-envs-by-application-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1625,6 +1650,7 @@ class ApplicationsController extends Controller
summary: 'Update Env', summary: 'Update Env',
description: 'Update env by application UUID.', description: 'Update env by application UUID.',
path: '/applications/{uuid}/envs', path: '/applications/{uuid}/envs',
operationId: 'update-env-by-application-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1807,6 +1833,7 @@ class ApplicationsController extends Controller
summary: 'Update Envs (Bulk)', summary: 'Update Envs (Bulk)',
description: 'Update multiple envs by application UUID.', description: 'Update multiple envs by application UUID.',
path: '/applications/{uuid}/envs/bulk', path: '/applications/{uuid}/envs/bulk',
operationId: 'update-envs-by-application-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1998,6 +2025,7 @@ class ApplicationsController extends Controller
summary: 'Create Env', summary: 'Create Env',
description: 'Create env by application UUID.', description: 'Create env by application UUID.',
path: '/applications/{uuid}/envs', path: '/applications/{uuid}/envs',
operationId: 'create-env-by-application-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -2157,6 +2185,7 @@ class ApplicationsController extends Controller
summary: 'Delete Env', summary: 'Delete Env',
description: 'Delete env by UUID.', description: 'Delete env by UUID.',
path: '/applications/{uuid}/envs/{env_uuid}', path: '/applications/{uuid}/envs/{env_uuid}',
operationId: 'delete-env-by-application-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -2242,6 +2271,7 @@ class ApplicationsController extends Controller
summary: 'Start', summary: 'Start',
description: 'Start application. `Post` request is also accepted.', description: 'Start application. `Post` request is also accepted.',
path: '/applications/{uuid}/start', path: '/applications/{uuid}/start',
operationId: 'start-application-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -2345,6 +2375,7 @@ class ApplicationsController extends Controller
summary: 'Stop', summary: 'Stop',
description: 'Stop application. `Post` request is also accepted.', description: 'Stop application. `Post` request is also accepted.',
path: '/applications/{uuid}/stop', path: '/applications/{uuid}/stop',
operationId: 'stop-application-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -2417,6 +2448,7 @@ class ApplicationsController extends Controller
summary: 'Restart', summary: 'Restart',
description: 'Restart application. `Post` request is also accepted.', description: 'Restart application. `Post` request is also accepted.',
path: '/applications/{uuid}/restart', path: '/applications/{uuid}/restart',
operationId: 'restart-application-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],

View File

@@ -46,6 +46,7 @@ class DatabasesController extends Controller
summary: 'List', summary: 'List',
description: 'List all databases.', description: 'List all databases.',
path: '/databases', path: '/databases',
operationId: 'list-databases',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -91,6 +92,7 @@ class DatabasesController extends Controller
summary: 'Get', summary: 'Get',
description: 'Get database by UUID.', description: 'Get database by UUID.',
path: '/databases/{uuid}', path: '/databases/{uuid}',
operationId: 'get-database-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -151,6 +153,7 @@ class DatabasesController extends Controller
summary: 'Update', summary: 'Update',
description: 'Update database by UUID.', description: 'Update database by UUID.',
path: '/databases/{uuid}', path: '/databases/{uuid}',
operationId: 'update-database-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -510,6 +513,7 @@ class DatabasesController extends Controller
summary: 'Create (PostgreSQL)', summary: 'Create (PostgreSQL)',
description: 'Create a new PostgreSQL database.', description: 'Create a new PostgreSQL database.',
path: '/databases/postgresql', path: '/databases/postgresql',
operationId: 'create-database-postgresql',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -575,6 +579,7 @@ class DatabasesController extends Controller
summary: 'Create (Clickhouse)', summary: 'Create (Clickhouse)',
description: 'Create a new Clickhouse database.', description: 'Create a new Clickhouse database.',
path: '/databases/clickhouse', path: '/databases/clickhouse',
operationId: 'create-database-clickhouse',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -636,6 +641,7 @@ class DatabasesController extends Controller
summary: 'Create (DragonFly)', summary: 'Create (DragonFly)',
description: 'Create a new DragonFly database.', description: 'Create a new DragonFly database.',
path: '/databases/dragonfly', path: '/databases/dragonfly',
operationId: 'create-database-dragonfly',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -696,6 +702,7 @@ class DatabasesController extends Controller
summary: 'Create (Redis)', summary: 'Create (Redis)',
description: 'Create a new Redis database.', description: 'Create a new Redis database.',
path: '/databases/redis', path: '/databases/redis',
operationId: 'create-database-redis',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -757,6 +764,7 @@ class DatabasesController extends Controller
summary: 'Create (KeyDB)', summary: 'Create (KeyDB)',
description: 'Create a new KeyDB database.', description: 'Create a new KeyDB database.',
path: '/databases/keydb', path: '/databases/keydb',
operationId: 'create-database-keydb',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -818,6 +826,7 @@ class DatabasesController extends Controller
summary: 'Create (MariaDB)', summary: 'Create (MariaDB)',
description: 'Create a new MariaDB database.', description: 'Create a new MariaDB database.',
path: '/databases/mariadb', path: '/databases/mariadb',
operationId: 'create-database-mariadb',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -882,6 +891,7 @@ class DatabasesController extends Controller
summary: 'Create (MySQL)', summary: 'Create (MySQL)',
description: 'Create a new MySQL database.', description: 'Create a new MySQL database.',
path: '/databases/mysql', path: '/databases/mysql',
operationId: 'create-database-mysql',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -945,6 +955,7 @@ class DatabasesController extends Controller
summary: 'Create (MongoDB)', summary: 'Create (MongoDB)',
description: 'Create a new MongoDB database.', description: 'Create a new MongoDB database.',
path: '/databases/mongodb', path: '/databases/mongodb',
operationId: 'create-database-mongodb',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1514,6 +1525,7 @@ class DatabasesController extends Controller
summary: 'Delete', summary: 'Delete',
description: 'Delete database by UUID.', description: 'Delete database by UUID.',
path: '/databases/{uuid}', path: '/databases/{uuid}',
operationId: 'delete-database-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1597,6 +1609,7 @@ class DatabasesController extends Controller
summary: 'Start', summary: 'Start',
description: 'Start database. `Post` request is also accepted.', description: 'Start database. `Post` request is also accepted.',
path: '/databases/{uuid}/start', path: '/databases/{uuid}/start',
operationId: 'start-database-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1672,6 +1685,7 @@ class DatabasesController extends Controller
summary: 'Stop', summary: 'Stop',
description: 'Stop database. `Post` request is also accepted.', description: 'Stop database. `Post` request is also accepted.',
path: '/databases/{uuid}/stop', path: '/databases/{uuid}/stop',
operationId: 'stop-database-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1747,6 +1761,7 @@ class DatabasesController extends Controller
summary: 'Restart', summary: 'Restart',
description: 'Restart database. `Post` request is also accepted.', description: 'Restart database. `Post` request is also accepted.',
path: '/databases/{uuid}/restart', path: '/databases/{uuid}/restart',
operationId: 'restart-database-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],

View File

@@ -32,6 +32,7 @@ class DeployController extends Controller
summary: 'List', summary: 'List',
description: 'List currently running deployments', description: 'List currently running deployments',
path: '/deployments', path: '/deployments',
operationId: 'list-deployments',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -79,6 +80,7 @@ class DeployController extends Controller
summary: 'Get', summary: 'Get',
description: 'Get deployment by UUID.', description: 'Get deployment by UUID.',
path: '/deployments/{uuid}', path: '/deployments/{uuid}',
operationId: 'get-deployment-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -134,6 +136,7 @@ class DeployController extends Controller
summary: 'Deploy', summary: 'Deploy',
description: 'Deploy by tag or uuid. `Post` request also accepted.', description: 'Deploy by tag or uuid. `Post` request also accepted.',
path: '/deploy', path: '/deploy',
operationId: 'deploy-by-tag-or-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],

View File

@@ -1,35 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\EnvironmentVariable;
use Illuminate\Http\Request;
class EnvironmentVariablesController extends Controller
{
public function delete_env_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
if (! $env) {
return response()->json([
'message' => 'Environment variable not found.',
], 404);
}
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
if (! $found_app) {
return response()->json([
'message' => 'Environment variable not found.',
], 404);
}
$env->delete();
return response()->json([
'message' => 'Environment variable deleted.',
]);
}
}

View File

@@ -13,6 +13,7 @@ class OtherController extends Controller
summary: 'Version', summary: 'Version',
description: 'Get Coolify version.', description: 'Get Coolify version.',
path: '/version', path: '/version',
operationId: 'version',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -43,6 +44,7 @@ class OtherController extends Controller
summary: 'Enable API', summary: 'Enable API',
description: 'Enable API (only with root permissions).', description: 'Enable API (only with root permissions).',
path: '/enable', path: '/enable',
operationId: 'enable-api',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -94,6 +96,7 @@ class OtherController extends Controller
summary: 'Disable API', summary: 'Disable API',
description: 'Disable API (only with root permissions).', description: 'Disable API (only with root permissions).',
path: '/disable', path: '/disable',
operationId: 'disable-api',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -158,6 +161,7 @@ class OtherController extends Controller
summary: 'Healthcheck', summary: 'Healthcheck',
description: 'Healthcheck endpoint.', description: 'Healthcheck endpoint.',
path: '/healthcheck', path: '/healthcheck',
operationId: 'healthcheck',
responses: [ responses: [
new OA\Response( new OA\Response(
response: 200, response: 200,

View File

@@ -13,6 +13,7 @@ class ProjectController extends Controller
summary: 'List', summary: 'List',
description: 'list projects.', description: 'list projects.',
path: '/projects', path: '/projects',
operationId: 'list-projects',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -56,6 +57,7 @@ class ProjectController extends Controller
summary: 'Get', summary: 'Get',
description: 'Get project by Uuid.', description: 'Get project by Uuid.',
path: '/projects/{uuid}', path: '/projects/{uuid}',
operationId: 'get-project-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -102,6 +104,7 @@ class ProjectController extends Controller
summary: 'Environment', summary: 'Environment',
description: 'Get environment by name.', description: 'Get environment by name.',
path: '/projects/{uuid}/{environment_name}', path: '/projects/{uuid}/{environment_name}',
operationId: 'get-environment-by-name',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -155,6 +158,7 @@ class ProjectController extends Controller
summary: 'Create', summary: 'Create',
description: 'Create Project.', description: 'Create Project.',
path: '/projects', path: '/projects',
operationId: 'create-project',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -250,6 +254,7 @@ class ProjectController extends Controller
summary: 'Update', summary: 'Update',
description: 'Update Project.', description: 'Update Project.',
path: '/projects/{uuid}', path: '/projects/{uuid}',
operationId: 'update-project-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -355,6 +360,7 @@ class ProjectController extends Controller
summary: 'Delete', summary: 'Delete',
description: 'Delete project by UUID.', description: 'Delete project by UUID.',
path: '/projects/{uuid}', path: '/projects/{uuid}',
operationId: 'delete-project-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],

View File

@@ -13,6 +13,7 @@ class ResourcesController extends Controller
summary: 'List', summary: 'List',
description: 'Get all resources.', description: 'Get all resources.',
path: '/resources', path: '/resources',
operationId: 'list-resources',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],

View File

@@ -26,6 +26,7 @@ class SecurityController extends Controller
summary: 'List', summary: 'List',
description: 'List all private keys.', description: 'List all private keys.',
path: '/security/keys', path: '/security/keys',
operationId: 'list-private-keys',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -68,6 +69,7 @@ class SecurityController extends Controller
summary: 'Get', summary: 'Get',
description: 'Get key by UUID.', description: 'Get key by UUID.',
path: '/security/keys/{uuid}', path: '/security/keys/{uuid}',
operationId: 'get-private-key-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -124,6 +126,7 @@ class SecurityController extends Controller
summary: 'Create', summary: 'Create',
description: 'Create a new private key.', description: 'Create a new private key.',
path: '/security/keys', path: '/security/keys',
operationId: 'create-private-key',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -217,6 +220,7 @@ class SecurityController extends Controller
summary: 'Update', summary: 'Update',
description: 'Update a private key.', description: 'Update a private key.',
path: '/security/keys', path: '/security/keys',
operationId: 'update-private-key',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -313,6 +317,7 @@ class SecurityController extends Controller
summary: 'Delete', summary: 'Delete',
description: 'Delete a private key.', description: 'Delete a private key.',
path: '/security/keys/{uuid}', path: '/security/keys/{uuid}',
operationId: 'delete-private-key-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],

View File

@@ -46,6 +46,7 @@ class ServersController extends Controller
summary: 'List', summary: 'List',
description: 'List all servers.', description: 'List all servers.',
path: '/servers', path: '/servers',
operationId: 'list-servers',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -100,6 +101,7 @@ class ServersController extends Controller
summary: 'Get', summary: 'Get',
description: 'Get server by UUID.', description: 'Get server by UUID.',
path: '/servers/{uuid}', path: '/servers/{uuid}',
operationId: 'get-server-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -177,6 +179,7 @@ class ServersController extends Controller
summary: 'Resources', summary: 'Resources',
description: 'Get resources by server.', description: 'Get resources by server.',
path: '/servers/{uuid}/resources', path: '/servers/{uuid}/resources',
operationId: 'get-resources-by-server-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -254,6 +257,7 @@ class ServersController extends Controller
summary: 'Domains', summary: 'Domains',
description: 'Get domains by server.', description: 'Get domains by server.',
path: '/servers/{uuid}/domains', path: '/servers/{uuid}/domains',
operationId: 'get-domains-by-server-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -401,6 +405,7 @@ class ServersController extends Controller
summary: 'Create', summary: 'Create',
description: 'Create Server.', description: 'Create Server.',
path: '/servers', path: '/servers',
operationId: 'create-server',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -545,6 +550,7 @@ class ServersController extends Controller
summary: 'Update', summary: 'Update',
description: 'Update Server.', description: 'Update Server.',
path: '/servers/{uuid}', path: '/servers/{uuid}',
operationId: 'update-server-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -655,6 +661,7 @@ class ServersController extends Controller
summary: 'Delete', summary: 'Delete',
description: 'Delete server by UUID.', description: 'Delete server by UUID.',
path: '/servers/{uuid}', path: '/servers/{uuid}',
operationId: 'delete-server-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -727,6 +734,7 @@ class ServersController extends Controller
summary: 'Validate', summary: 'Validate',
description: 'Validate server by UUID.', description: 'Validate server by UUID.',
path: '/servers/{uuid}/validate', path: '/servers/{uuid}/validate',
operationId: 'validate-server-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],

View File

@@ -38,6 +38,7 @@ class ServicesController extends Controller
summary: 'List', summary: 'List',
description: 'List all services.', description: 'List all services.',
path: '/services', path: '/services',
operationId: 'list-services',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -88,6 +89,7 @@ class ServicesController extends Controller
summary: 'Create', summary: 'Create',
description: 'Create a one-click service', description: 'Create a one-click service',
path: '/services', path: '/services',
operationId: 'create-service',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -365,6 +367,7 @@ class ServicesController extends Controller
summary: 'Get', summary: 'Get',
description: 'Get service by UUID.', description: 'Get service by UUID.',
path: '/services/{uuid}', path: '/services/{uuid}',
operationId: 'get-service-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -422,6 +425,7 @@ class ServicesController extends Controller
summary: 'Delete', summary: 'Delete',
description: 'Delete service by UUID.', description: 'Delete service by UUID.',
path: '/services/{uuid}', path: '/services/{uuid}',
operationId: 'delete-service-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -483,6 +487,7 @@ class ServicesController extends Controller
summary: 'Start', summary: 'Start',
description: 'Start service. `Post` request is also accepted.', description: 'Start service. `Post` request is also accepted.',
path: '/services/{uuid}/start', path: '/services/{uuid}/start',
operationId: 'start-service-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -558,6 +563,7 @@ class ServicesController extends Controller
summary: 'Stop', summary: 'Stop',
description: 'Stop service. `Post` request is also accepted.', description: 'Stop service. `Post` request is also accepted.',
path: '/services/{uuid}/stop', path: '/services/{uuid}/stop',
operationId: 'stop-service-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -633,6 +639,7 @@ class ServicesController extends Controller
summary: 'Restart', summary: 'Restart',
description: 'Restart service. `Post` request is also accepted.', description: 'Restart service. `Post` request is also accepted.',
path: '/services/{uuid}/restart', path: '/services/{uuid}/restart',
operationId: 'restart-service-by-uuid',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],

View File

@@ -32,6 +32,7 @@ class TeamController extends Controller
summary: 'List', summary: 'List',
description: 'Get all teams.', description: 'Get all teams.',
path: '/teams', path: '/teams',
operationId: 'list-teams',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -79,6 +80,7 @@ class TeamController extends Controller
summary: 'Get', summary: 'Get',
description: 'Get team by TeamId.', description: 'Get team by TeamId.',
path: '/teams/{id}', path: '/teams/{id}',
operationId: 'get-team-by-id',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -129,6 +131,7 @@ class TeamController extends Controller
summary: 'Members', summary: 'Members',
description: 'Get members by TeamId.', description: 'Get members by TeamId.',
path: '/teams/{id}/members', path: '/teams/{id}/members',
operationId: 'get-members-by-team-id',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -189,6 +192,7 @@ class TeamController extends Controller
summary: 'Authenticated Team', summary: 'Authenticated Team',
description: 'Get currently authenticated team.', description: 'Get currently authenticated team.',
path: '/teams/current', path: '/teams/current',
operationId: 'get-current-team',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -225,6 +229,7 @@ class TeamController extends Controller
summary: 'Authenticated Team Members', summary: 'Authenticated Team Members',
description: 'Get currently authenticated team members.', description: 'Get currently authenticated team members.',
path: '/teams/current/members', path: '/teams/current/members',
operationId: 'get-current-team-members',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],

View File

@@ -109,10 +109,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private bool $is_debug_enabled; private bool $is_debug_enabled;
private $build_args; private Collection|string $build_args;
private $env_args; private $env_args;
private $environment_variables;
private $env_nixpacks_args; private $env_nixpacks_args;
private $docker_compose; private $docker_compose;
@@ -157,7 +159,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private ?string $coolify_variables = null; private ?string $coolify_variables = null;
private bool $preserveRepository = true; private bool $preserveRepository = false;
public $tries = 1; public $tries = 1;
@@ -166,6 +168,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
$this->application = Application::find($this->application_deployment_queue->application_id); $this->application = Application::find($this->application_deployment_queue->application_id);
$this->build_pack = data_get($this->application, 'build_pack'); $this->build_pack = data_get($this->application, 'build_pack');
$this->build_args = collect([]);
$this->application_deployment_queue_id = $application_deployment_queue_id; $this->application_deployment_queue_id = $application_deployment_queue_id;
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
@@ -198,9 +201,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id); $this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) { if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
if ($this->pull_request_id === 0) {
$this->container_name = $this->application->settings->custom_internal_name; $this->container_name = $this->application->settings->custom_internal_name;
} else {
$this->container_name = "{$this->application->settings->custom_internal_name}-pr-{$this->pull_request_id}";
} }
ray('New container name: ', $this->container_name); }
ray('New container name: ', $this->container_name)->green();
savePrivateKeyToFs($this->server); savePrivateKeyToFs($this->server);
$this->saved_outputs = collect(); $this->saved_outputs = collect();
@@ -276,6 +283,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->original_server = $this->server; $this->original_server = $this->server;
} else { } else {
$this->build_server = $buildServers->random(); $this->build_server = $buildServers->random();
$this->application_deployment_queue->build_server_id = $this->build_server->id;
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name})."); $this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
$this->original_server = $this->server; $this->original_server = $this->server;
$this->use_build_server = true; $this->use_build_server = true;
@@ -414,15 +422,42 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->check_git_if_build_needed(); $this->check_git_if_build_needed();
$this->clone_repository(); $this->clone_repository();
if ($this->preserveRepository) {
foreach ($this->application->fileStorages as $fileStorage) {
$path = $fileStorage->fs_path;
$saveName = 'file_stat_'.$fileStorage->id;
$realPathInGit = str($path)->replace($this->application->workdir(), $this->workdir)->value();
// check if the file is a directory or a file inside the repository
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "stat -c '%F' {$realPathInGit}"), 'hidden' => true, 'ignore_errors' => true, 'save' => $saveName]
);
if ($this->saved_outputs->has($saveName)) {
$fileStat = $this->saved_outputs->get($saveName);
if ($fileStat->value() === 'directory' && ! $fileStorage->is_directory) {
$fileStorage->is_directory = true;
$fileStorage->content = null;
$fileStorage->save();
$fileStorage->deleteStorageOnServer();
$fileStorage->saveStorageOnServer();
} elseif ($fileStat->value() === 'regular file' && $fileStorage->is_directory) {
$fileStorage->is_directory = false;
$fileStorage->is_based_on_git = true;
$fileStorage->save();
$fileStorage->deleteStorageOnServer();
$fileStorage->saveStorageOnServer();
}
}
}
}
$this->generate_image_names(); $this->generate_image_names();
$this->cleanup_git(); $this->cleanup_git();
$this->application->loadComposeFile(isInit: false); $this->application->loadComposeFile(isInit: false);
if ($this->application->settings->is_raw_compose_deployment_enabled) { if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose(); $this->application->oldRawParser();
$yaml = $composeFile = $this->application->docker_compose_raw; $yaml = $composeFile = $this->application->docker_compose_raw;
$this->save_environment_variables(); $this->save_environment_variables();
} else { } else {
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id')); $composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
$this->save_environment_variables(); $this->save_environment_variables();
if (! is_null($this->env_filename)) { if (! is_null($this->env_filename)) {
$services = collect($composeFile['services']); $services = collect($composeFile['services']);
@@ -439,11 +474,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
return; return;
} }
$yaml = Yaml::dump($composeFile->toArray(), 10); $yaml = Yaml::dump(convertToArray($composeFile), 10);
} }
$this->docker_compose_base64 = base64_encode($yaml); $this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), 'hidden' => true, executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"),
'hidden' => true,
]); ]);
// Build new container to limit downtime. // Build new container to limit downtime.
$this->application_deployment_queue->addLogEntry('Pulling & building required images.'); $this->application_deployment_queue->addLogEntry('Pulling & building required images.');
@@ -473,13 +509,18 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
// TODO // TODO
} else { } else {
$this->execute_remote_command([ $this->execute_remote_command([
"docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true", 'hidden' => true, 'ignore_errors' => true, "docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true",
'hidden' => true,
'ignore_errors' => true,
], [ ], [
"docker network connect {$networkId} coolify-proxy || true", 'hidden' => true, 'ignore_errors' => true, "docker network connect {$networkId} coolify-proxy || true",
'hidden' => true,
'ignore_errors' => true,
]); ]);
} }
// Start compose file // Start compose file
$server_workdir = $this->application->workdir();
if ($this->application->settings->is_raw_compose_deployment_enabled) { if ($this->application->settings->is_raw_compose_deployment_enabled) {
if ($this->docker_compose_custom_start_command) { if ($this->docker_compose_custom_start_command) {
$this->write_deployment_configurations(); $this->write_deployment_configurations();
@@ -488,7 +529,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
); );
} else { } else {
$this->write_deployment_configurations(); $this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
$this->docker_compose_location = '/docker-compose.yaml'; $this->docker_compose_location = '/docker-compose.yaml';
$command = "{$this->coolify_variables} docker compose"; $command = "{$this->coolify_variables} docker compose";
@@ -508,15 +548,26 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
); );
} else { } else {
$command = "{$this->coolify_variables} docker compose"; $command = "{$this->coolify_variables} docker compose";
if ($this->preserveRepository) {
if ($this->env_filename) {
$command .= " --env-file {$server_workdir}/{$this->env_filename}";
}
$command .= " --project-name {$this->application->uuid} --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->write_deployment_configurations();
$this->execute_remote_command(
['command' => $command, 'hidden' => true],
);
} else {
if ($this->env_filename) { if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}"; $command .= " --env-file {$this->workdir}/{$this->env_filename}";
} }
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"; $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->write_deployment_configurations();
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true], [executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
); );
$this->write_deployment_configurations();
}
} }
} }
@@ -610,15 +661,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
[ [
"mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir",
], ],
// removing this now as we are using docker cp
// [
// "rm -rf $this->configuration_dir/{*,.*}",
// ],
[ [
"docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}", "docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}",
], ],
); );
} }
foreach ($this->application->fileStorages as $fileStorage) {
if (! $fileStorage->is_based_on_git && ! $fileStorage->is_directory) {
$fileStorage->saveStorageOnServer();
}
}
if ($this->use_build_server) { if ($this->use_build_server) {
$this->server = $this->build_server; $this->server = $this->build_server;
} }
@@ -698,7 +750,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name})."); $this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true, executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"),
'hidden' => true,
], ],
); );
if ($this->application->docker_registry_image_tag) { if ($this->application->docker_registry_image_tag) {
@@ -706,10 +759,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag."); $this->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag.");
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true, executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"),
'ignore_errors' => true,
'hidden' => true,
], ],
[ [
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true, executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"),
'ignore_errors' => true,
'hidden' => true,
], ],
); );
} }
@@ -806,14 +863,20 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function check_image_locally_or_remotely() private function check_image_locally_or_remotely()
{ {
$this->execute_remote_command([ $this->execute_remote_command([
"docker images -q {$this->production_image_name} 2>/dev/null", 'hidden' => true, 'save' => 'local_image_found', "docker images -q {$this->production_image_name} 2>/dev/null",
'hidden' => true,
'save' => 'local_image_found',
]); ]);
if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) { if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) {
$this->execute_remote_command([ $this->execute_remote_command([
"docker pull {$this->production_image_name} 2>/dev/null", 'ignore_errors' => true, 'hidden' => true, "docker pull {$this->production_image_name} 2>/dev/null",
'ignore_errors' => true,
'hidden' => true,
]); ]);
$this->execute_remote_command([ $this->execute_remote_command([
"docker images -q {$this->production_image_name} 2>/dev/null", 'hidden' => true, 'save' => 'local_image_found', "docker images -q {$this->production_image_name} 2>/dev/null",
'hidden' => true,
'save' => 'local_image_found',
]); ]);
} }
} }
@@ -846,17 +909,24 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} }
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) { if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) {
$envs->push("COOLIFY_FQDN={$this->preview->fqdn}"); $envs->push("COOLIFY_FQDN={$this->preview->fqdn}");
$envs->push("COOLIFY_DOMAIN_URL={$this->preview->fqdn}");
} }
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) { if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) {
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', ''); $url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
$envs->push("COOLIFY_URL={$url}"); $envs->push("COOLIFY_URL={$url}");
$envs->push("COOLIFY_DOMAIN_FQDN={$url}");
} }
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}"); $envs->push("COOLIFY_BRANCH={$local_branch}");
} }
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
} }
}
add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables_preview);
foreach ($sorted_environment_variables_preview as $env) { foreach ($sorted_environment_variables_preview as $env) {
$real_value = $env->real_value; $real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') { if ($env->version === '4.0.0-beta.239') {
@@ -891,18 +961,31 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} }
} }
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) { if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
if ($this->application->compose_parsing_version === '3') {
$envs->push("COOLIFY_URL={$this->application->fqdn}");
} else {
$envs->push("COOLIFY_FQDN={$this->application->fqdn}"); $envs->push("COOLIFY_FQDN={$this->application->fqdn}");
} }
}
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) { if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', ''); $url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
if ($this->application->compose_parsing_version === '3') {
$envs->push("COOLIFY_FQDN={$url}");
} else {
$envs->push("COOLIFY_URL={$url}"); $envs->push("COOLIFY_URL={$url}");
} }
}
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}"); $envs->push("COOLIFY_BRANCH={$local_branch}");
} }
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
} }
}
add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables);
foreach ($sorted_environment_variables as $env) { foreach ($sorted_environment_variables as $env) {
$real_value = $env->real_value; $real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') { if ($env->version === '4.0.0-beta.239') {
@@ -979,6 +1062,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
); );
} }
} }
$this->environment_variables = $envs;
} }
private function laravel_finetunes() private function laravel_finetunes()
@@ -1361,7 +1445,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} }
$this->execute_remote_command( $this->execute_remote_command(
[ [
$importCommands, 'hidden' => true, $importCommands,
'hidden' => true,
] ]
); );
$this->create_workdir(); $this->create_workdir();
@@ -1571,7 +1656,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
// Check for custom HEALTHCHECK // Check for custom HEALTHCHECK
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) { if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile_from_repo', 'ignore_errors' => true, executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"),
'hidden' => true,
'save' => 'dockerfile_from_repo',
'ignore_errors' => true,
]); ]);
$dockerfile = collect(str($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n")); $dockerfile = collect(str($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
$this->application->parseHealthcheckFromDockerfile($dockerfile); $this->application->parseHealthcheckFromDockerfile($dockerfile);
@@ -1674,14 +1762,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$docker_compose['services'][$this->container_name]['labels'] = $labels; $docker_compose['services'][$this->container_name]['labels'] = $labels;
} }
if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) { if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) {
$docker_compose['services'][$this->container_name]['logging'] = [ $docker_compose['services'][$this->container_name]['logging'] = generate_fluentd_configuration();
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
} }
if ($this->application->settings->is_gpu_enabled) { if ($this->application->settings->is_gpu_enabled) {
$docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'] = [ $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'] = [
@@ -1708,13 +1789,20 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) { if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array; $docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
} }
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages; if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) {
$docker_compose['services'][$this->container_name]['volumes'] = [];
}
$docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_storages);
} }
if (count($persistent_file_volumes) > 0) { if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) {
$docker_compose['services'][$this->container_name]['volumes'] = [];
}
$docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path"; return "$item->fs_path:$item->mount_path";
})->toArray(); })->toArray());
} }
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
@@ -1837,13 +1925,23 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application_deployment_queue->addLogEntry("Pulling latest image ($image) from the registry."); $this->application_deployment_queue->addLogEntry("Pulling latest image ($image) from the registry.");
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "docker pull {$image}"), 'hidden' => true, executeInDocker($this->deployment_uuid, "docker pull {$image}"),
'hidden' => true,
] ]
); );
} }
private function build_image() private function build_image()
{ {
// Add Coolify related variables to the build args
$this->environment_variables->filter(function ($key, $value) {
return str($key)->startsWith('COOLIFY_');
})->each(function ($key, $value) {
$this->build_args->push("--build-arg '{$key}'");
});
$this->build_args = $this->build_args->implode(' ');
$this->application_deployment_queue->addLogEntry('----------------------------------------'); $this->application_deployment_queue->addLogEntry('----------------------------------------');
if ($this->application->build_pack === 'static') { if ($this->application->build_pack === 'static') {
$this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.'); $this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.');
@@ -1887,12 +1985,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
if ($this->force_rebuild) { if ($this->force_rebuild) {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"),
'hidden' => true,
]); ]);
$build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}"; $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
} else { } else {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"),
'hidden' => true,
]); ]);
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}"; $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
} }
@@ -1900,10 +2000,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command); $base64_build_command = base64_encode($build_command);
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
], ],
[ [
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
] ]
); );
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]); $this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
@@ -1917,10 +2019,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
], ],
[ [
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
] ]
); );
} }
@@ -1957,10 +2061,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null"), executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null"),
], ],
[ [
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
], ],
[ [
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
] ]
); );
} else { } else {
@@ -1974,10 +2080,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command); $base64_build_command = base64_encode($build_command);
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
], ],
[ [
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
] ]
); );
} else { } else {
@@ -1986,22 +2094,26 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
if ($this->force_rebuild) { if ($this->force_rebuild) {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"),
'hidden' => true,
]); ]);
$build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
} else { } else {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"),
'hidden' => true,
]); ]);
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
} }
$base64_build_command = base64_encode($build_command); $base64_build_command = base64_encode($build_command);
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
], ],
[ [
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
] ]
); );
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]); $this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
@@ -2015,10 +2127,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
], ],
[ [
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
] ]
); );
} }
@@ -2044,7 +2158,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->execute_remote_command( $this->execute_remote_command(
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true] ["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
); );
} }
private function stop_running_container(bool $force = false) private function stop_running_container(bool $force = false)
@@ -2114,15 +2227,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->build_args->push("--build-arg {$env->key}={$value}"); $this->build_args->push("--build-arg {$env->key}={$value}");
} }
} }
$this->build_args = $this->build_args->implode(' ');
ray($this->build_args);
} }
private function add_build_env_variables_to_dockerfile() private function add_build_env_variables_to_dockerfile()
{ {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile', executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"),
'hidden' => true,
'save' => 'dockerfile',
]); ]);
$dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); $dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
if ($this->pull_request_id === 0) { if ($this->pull_request_id === 0) {
@@ -2140,7 +2252,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else { } else {
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
} }
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
} }
} }
$dockerfile_base64 = base64_encode($dockerfile->implode("\n")); $dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
@@ -2168,7 +2279,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$exec = "docker exec {$containerName} {$cmd}"; $exec = "docker exec {$containerName} {$cmd}";
$this->execute_remote_command( $this->execute_remote_command(
[ [
'command' => $exec, 'hidden' => true, 'command' => $exec,
'hidden' => true,
], ],
); );
@@ -2195,7 +2307,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
try { try {
$this->execute_remote_command( $this->execute_remote_command(
[ [
'command' => $exec, 'hidden' => true, 'save' => 'post-deployment-command-output', 'command' => $exec,
'hidden' => true,
'save' => 'post-deployment-command-output',
], ],
); );
} catch (Exception $e) { } catch (Exception $e) {

View File

@@ -56,6 +56,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public ?string $backup_output = null; public ?string $backup_output = null;
public ?string $postgres_password = null;
public ?S3Storage $s3 = null; public ?S3Storage $s3 = null;
public function __construct($backup) public function __construct($backup)
@@ -89,8 +91,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public function handle(): void public function handle(): void
{ {
try { try {
BackupCreated::dispatch($this->team->id);
// Check if team is exists // Check if team is exists
if (is_null($this->team)) { if (is_null($this->team)) {
$this->backup->update(['status' => 'failed']); $this->backup->update(['status' => 'failed']);
@@ -99,6 +99,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
return; return;
} }
BackupCreated::dispatch($this->team->id);
$status = str(data_get($this->database, 'status')); $status = str(data_get($this->database, 'status'));
if (! $status->startsWith('running') && $this->database->id !== 0) { if (! $status->startsWith('running') && $this->database->id !== 0) {
ray('database not running'); ray('database not running');
@@ -134,6 +137,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
} else { } else {
$databasesToBackup = $this->database->postgres_user; $databasesToBackup = $this->database->postgres_user;
} }
$this->postgres_password = $envs->filter(function ($env) {
return str($env)->startsWith('POSTGRES_PASSWORD=');
})->first();
if ($this->postgres_password) {
$this->postgres_password = str($this->postgres_password)->after('POSTGRES_PASSWORD=')->value();
}
} elseif (str($databaseType)->contains('mysql')) { } elseif (str($databaseType)->contains('mysql')) {
$this->container_name = "{$this->database->name}-$serviceUuid"; $this->container_name = "{$this->database->name}-$serviceUuid";
$this->directory_name = $serviceName.'-'.$this->container_name; $this->directory_name = $serviceName.'-'.$this->container_name;
@@ -336,7 +346,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$url = $this->database->internal_db_url; $url = $this->database->internal_db_url;
if ($databaseWithCollections === 'all') { if ($databaseWithCollections === 'all') {
$commands[] = 'mkdir -p '.$this->backup_dir; $commands[] = 'mkdir -p '.$this->backup_dir;
if (str($this->database->image)->startsWith('mongo:4.0')) { if (str($this->database->image)->startsWith('mongo:4')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
} else { } else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
@@ -351,13 +361,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
} }
$commands[] = 'mkdir -p '.$this->backup_dir; $commands[] = 'mkdir -p '.$this->backup_dir;
if ($collectionsToExclude->count() === 0) { if ($collectionsToExclude->count() === 0) {
if (str($this->database->image)->startsWith('mongo:4.0')) { if (str($this->database->image)->startsWith('mongo:4')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
} else { } else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
} }
} else { } else {
if (str($this->database->image)->startsWith('mongo:4.0')) { if (str($this->database->image)->startsWith('mongo:4')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
} else { } else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
@@ -381,7 +391,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{ {
try { try {
$commands[] = 'mkdir -p '.$this->backup_dir; $commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location"; $backupCommand = 'docker exec';
if ($this->postgres_password) {
$backupCommand .= " -e PGPASSWORD=$this->postgres_password";
}
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
$commands[] = $backupCommand;
$this->backup_output = instant_remote_process($commands, $this->server); $this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output); $this->backup_output = trim($this->backup_output);
if ($this->backup_output === '') { if ($this->backup_output === '') {
@@ -452,7 +468,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
if ($this->backup->number_of_backups_locally === 0) { if ($this->backup->number_of_backups_locally === 0) {
$deletable = $this->backup->executions()->where('status', 'success'); $deletable = $this->backup->executions()->where('status', 'success');
} else { } else {
$deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally - 1); $deletable = $this->backup->executions()->where('status', 'success')->skip($this->backup->number_of_backups_locally - 1);
} }
foreach ($deletable->get() as $execution) { foreach ($deletable->get() as $execution) {
delete_backup_locally($execution->filename, $this->server); delete_backup_locally($execution->filename, $this->server);

View File

@@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@@ -17,21 +18,33 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 300; public $timeout = 600;
public int|string|null $usageBefore = null; public $tries = 1;
public ?string $usageBefore = null;
public function __construct(public Server $server) {} public function __construct(public Server $server) {}
public function middleware(): array
{
return [new WithoutOverlapping($this->server->id)];
}
public function uniqueId(): int
{
return $this->server->id;
}
public function handle(): void public function handle(): void
{ {
try { try {
if (! $this->server->isFunctional()) { if (! $this->server->isFunctional()) {
return; return;
} }
if ($this->server->settings->is_force_cleanup_enabled) { if ($this->server->settings->force_docker_cleanup) {
Log::info('DockerCleanupJob force cleanup on '.$this->server->name); Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
CleanupDocker::run(server: $this->server, force: true); CleanupDocker::run(server: $this->server);
return; return;
} }
@@ -39,12 +52,12 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
$this->usageBefore = $this->server->getDiskUsage(); $this->usageBefore = $this->server->getDiskUsage();
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) { if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
Log::info('DockerCleanupJob force cleanup on '.$this->server->name); Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
CleanupDocker::run(server: $this->server, force: true); CleanupDocker::run(server: $this->server);
return; return;
} }
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) { if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
CleanupDocker::run(server: $this->server, force: false); CleanupDocker::run(server: $this->server);
$usageAfter = $this->server->getDiskUsage(); $usageAfter = $this->server->getDiskUsage();
if ($usageAfter < $this->usageBefore) { if ($usageAfter < $this->usageBefore) {
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.')); $this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
@@ -56,7 +69,8 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
Log::info('No need to clean up '.$this->server->name); Log::info('No need to clean up '.$this->server->name);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e->getMessage()); CleanupDocker::run(server: $this->server);
Log::error('DockerCleanupJob failed: '.$e->getMessage());
throw $e; throw $e;
} }
} }

View File

@@ -36,6 +36,8 @@ class ScheduledTaskJob implements ShouldQueue
public array $containers = []; public array $containers = [];
public string $server_timezone;
public function __construct($task) public function __construct($task)
{ {
$this->task = $task; $this->task = $task;
@@ -47,6 +49,19 @@ class ScheduledTaskJob implements ShouldQueue
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.'); throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
} }
$this->team = Team::find($task->team_id); $this->team = Team::find($task->team_id);
$this->server_timezone = $this->getServerTimezone();
}
private function getServerTimezone(): string
{
if ($this->resource instanceof Application) {
$timezone = $this->resource->destination->server->settings->server_timezone;
return $timezone;
} elseif ($this->resource instanceof Service) {
$timezone = $this->resource->server->settings->server_timezone;
return $timezone;
}
return 'UTC';
} }
public function middleware(): array public function middleware(): array
@@ -61,6 +76,7 @@ class ScheduledTaskJob implements ShouldQueue
public function handle(): void public function handle(): void
{ {
try { try {
$this->task_log = ScheduledTaskExecution::create([ $this->task_log = ScheduledTaskExecution::create([
'scheduled_task_id' => $this->task->id, 'scheduled_task_id' => $this->task->id,
@@ -121,6 +137,7 @@ class ScheduledTaskJob implements ShouldQueue
$this->team?->notify(new TaskFailed($this->task, $e->getMessage())); $this->team?->notify(new TaskFailed($this->task, $e->getMessage()));
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage()); // send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
throw $e; throw $e;
} finally {
} }
} }
} }

View File

@@ -79,7 +79,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
} }
GetContainersStatus::run($this->server, $this->containers, $containerReplicates); GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
$this->checkLogDrainContainer(); $this->checkLogDrainContainer();
$this->checkSentinel();
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -90,21 +89,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
} }
private function checkSentinel()
{
if ($this->server->isSentinelEnabled()) {
$sentinelContainerFound = $this->containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-sentinel';
})->first();
if ($sentinelContainerFound) {
$status = data_get($sentinelContainerFound, 'State.Status');
if ($status !== 'running') {
PullSentinelImageJob::dispatch($this);
}
}
}
}
private function serverStatus() private function serverStatus()
{ {
['uptime' => $uptime] = $this->server->validateConnection(); ['uptime' => $uptime] = $this->server->validateConnection();

View File

@@ -50,15 +50,6 @@ class Dashboard extends Component
])->sortBy('id')->groupBy('server_name')->toArray(); ])->sortBy('id')->groupBy('server_name')->toArray();
} }
// public function getIptables()
// {
// $servers = Server::ownedByCurrentTeam()->get();
// foreach ($servers as $server) {
// checkRequiredCommands($server);
// $iptables = instant_remote_process(['docker run --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c "iptables -L -n | jc --iptables"'], $server);
// ray($iptables);
// }
// }
public function render() public function render()
{ {
return view('livewire.dashboard'); return view('livewire.dashboard');

View File

@@ -66,9 +66,9 @@ class Advanced extends Component
$this->dispatch('resetDefaultLabels', false); $this->dispatch('resetDefaultLabels', false);
} }
if ($this->application->settings->is_raw_compose_deployment_enabled) { if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose(); $this->application->oldRawParser();
} else { } else {
$this->application->parseCompose(); $this->application->parse();
} }
$this->application->settings->save(); $this->application->settings->save();
$this->dispatch('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
@@ -96,6 +96,12 @@ class Advanced extends Component
} else { } else {
$this->application->settings->custom_internal_name = null; $this->application->settings->custom_internal_name = null;
} }
if (is_null($this->application->settings->custom_internal_name)) {
$this->application->settings->save();
$this->dispatch('success', 'Custom name saved.');
return;
}
$customInternalName = $this->application->settings->custom_internal_name; $customInternalName = $this->application->settings->custom_internal_name;
$server = $this->application->destination->server; $server = $this->application->destination->server;
$allApplications = $server->applications(); $allApplications = $server->applications();

View File

@@ -55,9 +55,14 @@ class DeploymentNavbar extends Component
public function cancel() public function cancel()
{ {
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}"; $kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
$build_server_id = $this->application_deployment_queue->build_server_id;
$server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id; $server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
try { try {
if ($this->application->settings->is_build_server_enabled) {
$server = Server::find($build_server_id);
} else {
$server = Server::find($server_id); $server = Server::find($server_id);
}
if ($this->application_deployment_queue->logs) { if ($this->application_deployment_queue->logs) {
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR); $previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);

View File

@@ -3,7 +3,6 @@
namespace App\Livewire\Project\Application; namespace App\Livewire\Project\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\LocalFileVolume;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
@@ -30,6 +29,8 @@ class General extends Component
public ?string $ports_exposes = null; public ?string $ports_exposes = null;
public bool $is_preserve_repository_enabled = false;
public bool $is_container_label_escape_enabled = true; public bool $is_container_label_escape_enabled = true;
public $customLabels; public $customLabels;
@@ -130,7 +131,7 @@ class General extends Component
public function mount() public function mount()
{ {
try { try {
$this->parsedServices = $this->application->parseCompose(); $this->parsedServices = $this->application->parse();
if (is_null($this->parsedServices) || empty($this->parsedServices)) { if (is_null($this->parsedServices) || empty($this->parsedServices)) {
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.'); $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
@@ -145,6 +146,7 @@ class General extends Component
} }
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes; $this->ports_exposes = $this->application->ports_exposes;
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled;
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
$this->customLabels = $this->application->parseContainerLabels(); $this->customLabels = $this->application->parseContainerLabels();
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) { if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
@@ -168,9 +170,21 @@ class General extends Component
$this->application->settings->save(); $this->application->settings->save();
$this->dispatch('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
$this->application->refresh(); $this->application->refresh();
// If port_exposes changed, reset default labels
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) { if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
$this->resetDefaultLabels(false); $this->resetDefaultLabels(false);
} }
if ($this->is_preserve_repository_enabled !== $this->application->settings->is_preserve_repository_enabled) {
if ($this->application->settings->is_preserve_repository_enabled === false) {
$this->application->fileStorages->each(function ($storage) {
$storage->is_based_on_git = $this->application->settings->is_preserve_repository_enabled;
$storage->save();
});
}
}
} }
public function loadComposeFile($isInit = false) public function loadComposeFile($isInit = false)
@@ -179,39 +193,18 @@ class General extends Component
if ($isInit && $this->application->docker_compose_raw) { if ($isInit && $this->application->docker_compose_raw) {
return; return;
} }
// Must reload the application to get the latest database changes
// Why? Not sure, but it works.
// $this->application->refresh();
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit); ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
if (is_null($this->parsedServices)) { if (is_null($this->parsedServices)) {
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.'); $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
return; return;
} }
$compose = $this->application->parseCompose(); $this->application->parse();
$services = data_get($compose, 'services');
if ($services) {
$volumes = collect($services)->map(function ($service) {
return data_get($service, 'volumes');
})->flatten()->filter(function ($volume) {
return str($volume)->startsWith('/data/coolify');
})->unique()->values();
foreach ($volumes as $volume) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application),
],
[
'fs_path' => $source,
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application),
]
);
}
}
$this->dispatch('success', 'Docker compose file loaded.'); $this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded'); $this->dispatch('compose_loaded');
$this->dispatch('refreshStorages'); $this->dispatch('refreshStorages');

View File

@@ -79,8 +79,15 @@ class Previews extends Component
return; return;
} }
$fqdn = generateFqdn($this->application->destination->server, $this->application->uuid); if ($this->application->build_pack === 'dockercompose') {
$preview->generate_preview_fqdn_compose();
$this->application->refresh();
$this->dispatch('success', 'Domain generated.');
return;
}
$fqdn = generateFqdn($this->application->destination->server, $this->application->uuid);
$url = Url::fromString($fqdn); $url = Url::fromString($fqdn);
$template = $this->application->preview_url_template; $template = $this->application->preview_url_template;
$host = $url->getHost(); $host = $url->getHost();

View File

@@ -8,9 +8,8 @@ use Livewire\Component;
class BackupExecutions extends Component class BackupExecutions extends Component
{ {
public ?ScheduledDatabaseBackup $backup = null; public ?ScheduledDatabaseBackup $backup = null;
public $database;
public $executions = []; public $executions = [];
public $setDeletableBackup; public $setDeletableBackup;
public function getListeners() public function getListeners()
@@ -58,7 +57,53 @@ class BackupExecutions extends Component
public function refreshBackupExecutions(): void public function refreshBackupExecutions(): void
{ {
if ($this->backup) { if ($this->backup) {
$this->executions = $this->backup->executions()->get()->sortBy('created_at'); $this->executions = $this->backup->executions()->get();
} }
} }
public function mount(ScheduledDatabaseBackup $backup)
{
$this->backup = $backup;
$this->database = $backup->database;
$this->refreshBackupExecutions();
}
public function server()
{
if ($this->database) {
$server = null;
if ($this->database instanceof \App\Models\ServiceDatabase) {
$server = $this->database->service->destination->server;
} elseif ($this->database->destination && $this->database->destination->server) {
$server = $this->database->destination->server;
}
if ($server) {
return $server;
}
}
return null;
}
public function getServerTimezone()
{
$server = $this->server();
if (!$server) {
return 'UTC';
}
$serverTimezone = $server->settings->server_timezone;
return $serverTimezone;
}
public function formatDateInServerTimezone($date)
{
$serverTimezone = $this->getServerTimezone();
$dateObj = new \DateTime($date);
try {
$dateObj->setTimezone(new \DateTimeZone($serverTimezone));
} catch (\Exception $e) {
$dateObj->setTimezone(new \DateTimeZone('UTC'));
}
return $dateObj->format('Y-m-d H:i:s T');
}
} }

View File

@@ -31,6 +31,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -42,6 +43,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()

View File

@@ -30,6 +30,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -40,6 +41,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()

View File

@@ -31,6 +31,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -42,6 +43,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()

View File

@@ -34,6 +34,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -48,6 +49,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Options',
]; ];
public function mount() public function mount()

View File

@@ -33,6 +33,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -46,6 +47,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()

View File

@@ -34,6 +34,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -48,6 +49,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()

View File

@@ -49,6 +49,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -65,6 +66,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()

View File

@@ -31,6 +31,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -42,6 +43,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Options',
]; ];
public function mount() public function mount()

View File

@@ -5,6 +5,8 @@ namespace App\Livewire\Project\New;
use App\Models\EnvironmentVariable; use App\Models\EnvironmentVariable;
use App\Models\Project; use App\Models\Project;
use App\Models\Service; use App\Models\Service;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Livewire\Component; use Livewire\Component;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
@@ -58,12 +60,26 @@ class DockerCompose extends Component
$project = Project::where('uuid', $this->parameters['project_uuid'])->first(); $project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$destination_uuid = $this->query['destination'];
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
}
if (! $destination) {
throw new \Exception('Destination not found. What?!');
}
$destination_class = $destination->getMorphClass();
$service = Service::create([ $service = Service::create([
'name' => 'service'.Str::random(10), 'name' => 'service'.Str::random(10),
'docker_compose_raw' => $this->dockerComposeRaw, 'docker_compose_raw' => $this->dockerComposeRaw,
'environment_id' => $environment->id, 'environment_id' => $environment->id,
'server_id' => (int) $server_id, 'server_id' => (int) $server_id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
]); ]);
$variables = parseEnvFormatToArray($this->envFile); $variables = parseEnvFormatToArray($this->envFile);
foreach ($variables as $key => $variable) { foreach ($variables as $key => $variable) {
EnvironmentVariable::create([ EnvironmentVariable::create([

View File

@@ -99,6 +99,16 @@ class PublicGitRepository extends Component
} }
} }
public function updatedDockerComposeLocation()
{
if ($this->docker_compose_location) {
$this->docker_compose_location = rtrim($this->docker_compose_location, '/');
if (! str($this->docker_compose_location)->startsWith('/')) {
$this->docker_compose_location = '/'.$this->docker_compose_location;
}
}
}
public function updatedBuildPack() public function updatedBuildPack()
{ {
if ($this->build_pack === 'nixpacks') { if ($this->build_pack === 'nixpacks') {

View File

@@ -45,6 +45,8 @@ class Select extends Component
public ?string $selectedEnvironment = null; public ?string $selectedEnvironment = null;
public string $postgresql_type = 'postgres:16-alpine';
public ?string $existingPostgresqlUrl = null; public ?string $existingPostgresqlUrl = null;
public ?string $search = null; public ?string $search = null;
@@ -202,6 +204,8 @@ class Select extends Component
$docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first(); $docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first();
if ($docker) { if ($docker) {
$this->setDestination($docker->uuid); $this->setDestination($docker->uuid);
return $this->whatToDoNext();
} }
} }
$this->current_step = 'destinations'; $this->current_step = 'destinations';
@@ -211,6 +215,28 @@ class Select extends Component
{ {
$this->destination_uuid = $destination_uuid; $this->destination_uuid = $destination_uuid;
return $this->whatToDoNext();
}
public function setPostgresqlType(string $type)
{
$this->postgresql_type = $type;
return redirect()->route('project.resource.create', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name'],
'type' => $this->type,
'destination' => $this->destination_uuid,
'server_id' => $this->server_id,
'database_image' => $this->postgresql_type,
]);
}
public function whatToDoNext()
{
if ($this->type === 'postgresql') {
$this->current_step = 'select-postgresql-type';
} else {
return redirect()->route('project.resource.create', [ return redirect()->route('project.resource.create', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
@@ -219,6 +245,7 @@ class Select extends Component
'server_id' => $this->server_id, 'server_id' => $this->server_id,
]); ]);
} }
}
public function loadServers() public function loadServers()
{ {

View File

@@ -18,6 +18,7 @@ class Create extends Component
$type = str(request()->query('type')); $type = str(request()->query('type'));
$destination_uuid = request()->query('destination'); $destination_uuid = request()->query('destination');
$server_id = request()->query('server_id'); $server_id = request()->query('server_id');
$database_image = request()->query('database_image');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (! $project) { if (! $project) {
@@ -33,7 +34,11 @@ class Create extends Component
if (in_array($type, DATABASE_TYPES)) { if (in_array($type, DATABASE_TYPES)) {
if ($type->value() === 'postgresql') { if ($type->value() === 'postgresql') {
$database = create_standalone_postgresql($environment->id, $destination_uuid); $database = create_standalone_postgresql(
environmentId: $environment->id,
destinationUuid: $destination_uuid,
databaseImage: $database_image
);
} elseif ($type->value() === 'redis') { } elseif ($type->value() === 'redis') {
$database = create_standalone_redis($environment->id, $destination_uuid); $database = create_standalone_redis($environment->id, $destination_uuid);
} elseif ($type->value() === 'mongodb') { } elseif ($type->value() === 'mongodb') {
@@ -86,18 +91,16 @@ class Create extends Component
$oneClickDotEnvs->each(function ($value) use ($service) { $oneClickDotEnvs->each(function ($value) use ($service) {
$key = str()->before($value, '='); $key = str()->before($value, '=');
$value = str(str()->after($value, '=')); $value = str(str()->after($value, '='));
$generatedValue = $value; if ($value) {
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
$generatedValue = generateEnvValue($command->value(), $service);
}
EnvironmentVariable::create([ EnvironmentVariable::create([
'key' => $key, 'key' => $key,
'value' => $generatedValue, 'value' => $value,
'service_id' => $service->id, 'service_id' => $service->id,
'is_build_time' => false, 'is_build_time' => false,
'is_preview' => false, 'is_preview' => false,
]); ]);
}
}); });
} }
$service->parse(isNew: true); $service->parse(isNew: true);

View File

@@ -25,6 +25,7 @@ class Configuration extends Component
return [ return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status', "echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
'check_status', 'check_status',
'refresh' => '$refresh',
]; ];
} }
@@ -75,6 +76,12 @@ class Configuration extends Component
{ {
try { try {
GetContainersStatus::run($this->service->server); GetContainersStatus::run($this->service->server);
$this->service->applications->each(function ($application) {
$application->refresh();
});
$this->service->databases->each(function ($database) {
$database->refresh();
});
$this->dispatch('$refresh'); $this->dispatch('$refresh');
} catch (\Exception $e) { } catch (\Exception $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@@ -11,7 +11,11 @@ class EditCompose extends Component
public $serviceId; public $serviceId;
protected $listeners = ['refreshEnvs', 'envsUpdated']; protected $listeners = [
'refreshEnvs',
'envsUpdated',
'refresh' => 'envsUpdated',
];
protected $rules = [ protected $rules = [
'service.docker_compose_raw' => 'required', 'service.docker_compose_raw' => 'required',
@@ -39,6 +43,7 @@ class EditCompose extends Component
{ {
$this->dispatch('info', 'Saving new docker compose...'); $this->dispatch('info', 'Saving new docker compose...');
$this->dispatch('saveCompose', $this->service->docker_compose_raw); $this->dispatch('saveCompose', $this->service->docker_compose_raw);
$this->dispatch('refreshStorages');
} }
public function instantSave() public function instantSave()

View File

@@ -33,6 +33,7 @@ class FileStorage extends Component
'fileStorage.fs_path' => 'required', 'fileStorage.fs_path' => 'required',
'fileStorage.mount_path' => 'required', 'fileStorage.mount_path' => 'required',
'fileStorage.content' => 'nullable', 'fileStorage.content' => 'nullable',
'fileStorage.is_based_on_git' => 'required|boolean',
]; ];
public function mount() public function mount()
@@ -45,6 +46,7 @@ class FileStorage extends Component
$this->workdir = null; $this->workdir = null;
$this->fs_path = $this->fileStorage->fs_path; $this->fs_path = $this->fileStorage->fs_path;
} }
$this->fileStorage->loadStorageOnServer();
} }
public function convertToDirectory() public function convertToDirectory()
@@ -53,6 +55,7 @@ class FileStorage extends Component
$this->fileStorage->deleteStorageOnServer(); $this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = true; $this->fileStorage->is_directory = true;
$this->fileStorage->content = null; $this->fileStorage->content = null;
$this->fileStorage->is_based_on_git = false;
$this->fileStorage->save(); $this->fileStorage->save();
$this->fileStorage->saveStorageOnServer(); $this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -68,6 +71,9 @@ class FileStorage extends Component
$this->fileStorage->deleteStorageOnServer(); $this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = false; $this->fileStorage->is_directory = false;
$this->fileStorage->content = null; $this->fileStorage->content = null;
if (data_get($this->resource, 'settings.is_preserve_repository_enabled')) {
$this->fileStorage->is_based_on_git = true;
}
$this->fileStorage->save(); $this->fileStorage->save();
$this->fileStorage->saveStorageOnServer(); $this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -53,7 +53,7 @@ class StackForm extends Component
public function saveCompose($raw) public function saveCompose($raw)
{ {
$this->service->docker_compose_raw = $raw; $this->service->docker_compose_raw = $raw;
$this->submit(); $this->submit(notify: false);
} }
public function instantSave() public function instantSave()
@@ -62,7 +62,7 @@ class StackForm extends Component
$this->dispatch('success', 'Service settings saved.'); $this->dispatch('success', 'Service settings saved.');
} }
public function submit() public function submit($notify = true)
{ {
try { try {
$this->validate(); $this->validate();
@@ -76,7 +76,7 @@ class StackForm extends Component
$this->service->refresh(); $this->service->refresh();
$this->service->saveComposeConfigs(); $this->service->saveComposeConfigs();
$this->dispatch('refreshEnvs'); $this->dispatch('refreshEnvs');
$this->dispatch('success', 'Service saved.'); $notify && $this->dispatch('success', 'Service saved.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally { } finally {

View File

@@ -23,8 +23,9 @@ class All extends Component
public string $view = 'normal'; public string $view = 'normal';
protected $listeners = [ protected $listeners = [
'refreshEnvs',
'saveKey' => 'submit', 'saveKey' => 'submit',
'refreshEnvs',
'environmentVariableDeleted' => 'refreshEnvs',
]; ];
protected $rules = [ protected $rules = [
@@ -40,220 +41,240 @@ class All extends Component
$this->showPreview = true; $this->showPreview = true;
} }
$this->modalId = new Cuid2; $this->modalId = new Cuid2;
$this->sortMe(); $this->sortEnvironmentVariables();
$this->getDevView();
}
public function sortMe()
{
if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') {
if ($this->resource->settings->is_env_sorting_enabled) {
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('key');
$this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('key');
} else {
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('id');
$this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('id');
}
}
$this->getDevView();
} }
public function instantSave() public function instantSave()
{ {
if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') {
$this->resource->settings->save(); $this->resource->settings->save();
$this->sortEnvironmentVariables();
$this->dispatch('success', 'Environment variable settings updated.'); $this->dispatch('success', 'Environment variable settings updated.');
$this->sortMe();
} }
public function sortEnvironmentVariables()
{
if ($this->resource->type() === 'application') {
$this->resource->load(['environment_variables', 'environment_variables_preview']);
} else {
$this->resource->load(['environment_variables']);
}
$sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order';
$sortFunction = function ($variables) use ($sortBy) {
if (! $variables) {
return $variables;
}
if ($sortBy === 'key') {
return $variables->sortBy(function ($item) {
return strtolower($item->key);
}, SORT_NATURAL | SORT_FLAG_CASE)->values();
} else {
return $variables->sortBy('order')->values();
}
};
$this->resource->environment_variables = $sortFunction($this->resource->environment_variables);
$this->resource->environment_variables_preview = $sortFunction($this->resource->environment_variables_preview);
$this->getDevView();
} }
public function getDevView() public function getDevView()
{ {
$this->variables = $this->resource->environment_variables->map(function ($item) { $this->variables = $this->formatEnvironmentVariables($this->resource->environment_variables);
if ($item->is_shown_once) {
return "$item->key=(locked secret)";
}
if ($item->is_multiline) {
return "$item->key=(multiline, edit in normal view)";
}
return "$item->key=$item->value";
})->join('
');
if ($this->showPreview) { if ($this->showPreview) {
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) { $this->variablesPreview = $this->formatEnvironmentVariables($this->resource->environment_variables_preview);
}
}
private function formatEnvironmentVariables($variables)
{
return $variables->map(function ($item) {
if ($item->is_shown_once) { if ($item->is_shown_once) {
return "$item->key=(locked secret)"; return "$item->key=(Locked Secret, delete and add again to change)";
} }
if ($item->is_multiline) { if ($item->is_multiline) {
return "$item->key=(multiline, edit in normal view)"; return "$item->key=(Multiline environment variable, edit in normal view)";
} }
return "$item->key=$item->value"; return "$item->key=$item->value";
})->join(' })->join("\n");
');
}
} }
public function switch() public function switch()
{ {
if ($this->view === 'normal') { $this->view = $this->view === 'normal' ? 'dev' : 'normal';
$this->view = 'dev'; $this->sortEnvironmentVariables();
} else {
$this->view = 'normal';
}
$this->sortMe();
} }
public function saveVariables($isPreview) public function submit($data = null)
{
if ($isPreview) {
$variables = parseEnvFormatToArray($this->variablesPreview);
$this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete();
} else {
$variables = parseEnvFormatToArray($this->variables);
$this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete();
}
foreach ($variables as $key => $variable) {
if ($isPreview) {
$found = $this->resource->environment_variables_preview()->where('key', $key)->first();
} else {
$found = $this->resource->environment_variables()->where('key', $key)->first();
}
if ($found) {
if ($found->is_shown_once || $found->is_multiline) {
continue;
}
$found->value = $variable;
// if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) {
// $type = str($found->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// return;
// }
// }
$found->save();
continue;
} else {
$environment = new EnvironmentVariable;
$environment->key = $key;
$environment->value = $variable;
// if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) {
// $type = str($environment->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// return;
// }
// }
$environment->is_build_time = false;
$environment->is_multiline = false;
$environment->is_preview = $isPreview ? true : false;
switch ($this->resource->type()) {
case 'application':
$environment->application_id = $this->resource->id;
break;
case 'standalone-postgresql':
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'standalone-redis':
$environment->standalone_redis_id = $this->resource->id;
break;
case 'standalone-mongodb':
$environment->standalone_mongodb_id = $this->resource->id;
break;
case 'standalone-mysql':
$environment->standalone_mysql_id = $this->resource->id;
break;
case 'standalone-mariadb':
$environment->standalone_mariadb_id = $this->resource->id;
break;
case 'standalone-keydb':
$environment->standalone_keydb_id = $this->resource->id;
break;
case 'standalone-dragonfly':
$environment->standalone_dragonfly_id = $this->resource->id;
break;
case 'standalone-clickhouse':
$environment->standalone_clickhouse_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;
}
$environment->save();
}
}
if ($isPreview) {
$this->dispatch('success', 'Preview environment variables updated.');
} else {
$this->dispatch('success', 'Environment variables updated.');
}
$this->refreshEnvs();
}
public function refreshEnvs()
{
$this->resource->refresh();
$this->getDevView();
}
public function submit($data)
{ {
try { try {
if ($data === null) {
$this->handleBulkSubmit();
} else {
$this->handleSingleSubmit($data);
}
$this->updateOrder();
$this->sortEnvironmentVariables();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
private function updateOrder()
{
$variables = parseEnvFormatToArray($this->variables);
$order = 1;
foreach ($variables as $key => $value) {
$env = $this->resource->environment_variables()->where('key', $key)->first();
if ($env) {
$env->order = $order;
$env->save();
}
$order++;
}
if ($this->showPreview) {
$previewVariables = parseEnvFormatToArray($this->variablesPreview);
$order = 1;
foreach ($previewVariables as $key => $value) {
$env = $this->resource->environment_variables_preview()->where('key', $key)->first();
if ($env) {
$env->order = $order;
$env->save();
}
$order++;
}
}
}
private function handleBulkSubmit()
{
$variables = parseEnvFormatToArray($this->variables);
$this->deleteRemovedVariables(false, $variables);
$this->updateOrCreateVariables(false, $variables);
if ($this->showPreview) {
$previewVariables = parseEnvFormatToArray($this->variablesPreview);
$this->deleteRemovedVariables(true, $previewVariables);
$this->updateOrCreateVariables(true, $previewVariables);
}
$this->dispatch('success', 'Environment variables updated.');
}
private function handleSingleSubmit($data)
{
$found = $this->resource->environment_variables()->where('key', $data['key'])->first(); $found = $this->resource->environment_variables()->where('key', $data['key'])->first();
if ($found) { if ($found) {
$this->dispatch('error', 'Environment variable already exists.'); $this->dispatch('error', 'Environment variable already exists.');
return; return;
} }
$maxOrder = $this->resource->environment_variables()->max('order') ?? 0;
$environment = $this->createEnvironmentVariable($data);
$environment->order = $maxOrder + 1;
$environment->save();
}
private function createEnvironmentVariable($data)
{
$environment = new EnvironmentVariable; $environment = new EnvironmentVariable;
$environment->key = $data['key']; $environment->key = $data['key'];
$environment->value = $data['value']; $environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time']; $environment->is_build_time = $data['is_build_time'] ?? false;
$environment->is_multiline = $data['is_multiline']; $environment->is_multiline = $data['is_multiline'] ?? false;
$environment->is_literal = $data['is_literal']; $environment->is_literal = $data['is_literal'] ?? false;
$environment->is_preview = $data['is_preview']; $environment->is_preview = $data['is_preview'] ?? false;
switch ($this->resource->type()) { $resourceType = $this->resource->type();
case 'application': $resourceIdField = $this->getResourceIdField($resourceType);
$environment->application_id = $this->resource->id;
break; if ($resourceIdField) {
case 'standalone-postgresql': $environment->$resourceIdField = $this->resource->id;
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'standalone-redis':
$environment->standalone_redis_id = $this->resource->id;
break;
case 'standalone-mongodb':
$environment->standalone_mongodb_id = $this->resource->id;
break;
case 'standalone-mysql':
$environment->standalone_mysql_id = $this->resource->id;
break;
case 'standalone-mariadb':
$environment->standalone_mariadb_id = $this->resource->id;
break;
case 'standalone-keydb':
$environment->standalone_keydb_id = $this->resource->id;
break;
case 'standalone-dragonfly':
$environment->standalone_dragonfly_id = $this->resource->id;
break;
case 'standalone-clickhouse':
$environment->standalone_clickhouse_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;
} }
return $environment;
}
private function getResourceIdField($resourceType)
{
$resourceTypes = [
'application' => 'application_id',
'standalone-postgresql' => 'standalone_postgresql_id',
'standalone-redis' => 'standalone_redis_id',
'standalone-mongodb' => 'standalone_mongodb_id',
'standalone-mysql' => 'standalone_mysql_id',
'standalone-mariadb' => 'standalone_mariadb_id',
'standalone-keydb' => 'standalone_keydb_id',
'standalone-dragonfly' => 'standalone_dragonfly_id',
'standalone-clickhouse' => 'standalone_clickhouse_id',
'service' => 'service_id',
];
return $resourceTypes[$resourceType] ?? null;
}
private function deleteRemovedVariables($isPreview, $variables)
{
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
$this->resource->$method()->whereNotIn('key', array_keys($variables))->delete();
}
private function updateOrCreateVariables($isPreview, $variables)
{
foreach ($variables as $key => $value) {
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
$found = $this->resource->$method()->where('key', $key)->first();
if ($found) {
if (! $found->is_shown_once && ! $found->is_multiline) {
$found->value = $value;
$found->save();
}
} else {
$environment = new EnvironmentVariable;
$environment->key = $key;
$environment->value = $value;
$environment->is_build_time = false;
$environment->is_multiline = false;
$environment->is_preview = $isPreview;
$this->setEnvironmentResourceId($environment);
$environment->save(); $environment->save();
$this->refreshEnvs();
$this->dispatch('success', 'Environment variable added.');
} catch (\Throwable $e) {
return handleError($e, $this);
} }
} }
} }
private function setEnvironmentResourceId($environment)
{
$resourceTypes = [
'application' => 'application_id',
'standalone-postgresql' => 'standalone_postgresql_id',
'standalone-redis' => 'standalone_redis_id',
'standalone-mongodb' => 'standalone_mongodb_id',
'standalone-mysql' => 'standalone_mysql_id',
'standalone-mariadb' => 'standalone_mariadb_id',
'standalone-keydb' => 'standalone_keydb_id',
'standalone-dragonfly' => 'standalone_dragonfly_id',
'standalone-clickhouse' => 'standalone_clickhouse_id',
'service' => 'service_id',
];
$resourceType = $this->resource->type();
if (isset($resourceTypes[$resourceType])) {
$environment->{$resourceTypes[$resourceType]} = $this->resource->id;
}
}
public function refreshEnvs()
{
$this->resource->refresh();
$this->sortEnvironmentVariables();
$this->getDevView();
}
}

View File

@@ -24,7 +24,8 @@ class Show extends Component
public string $type; public string $type;
protected $listeners = [ protected $listeners = [
'refresh' => 'refresh', 'refreshEnvs' => 'refresh',
'refresh',
'compose_loaded' => '$refresh', 'compose_loaded' => '$refresh',
]; ];
@@ -129,7 +130,8 @@ class Show extends Component
{ {
try { try {
$this->env->delete(); $this->env->delete();
$this->dispatch('refreshEnvs'); $this->dispatch('environmentVariableDeleted');
$this->dispatch('success', 'Environment variable deleted successfully.');
} catch (\Exception $e) { } catch (\Exception $e) {
return handleError($e); return handleError($e);
} }

View File

@@ -97,7 +97,7 @@ class GetLogs extends Component
if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) { if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) {
return; return;
} }
if (! $this->numberOfLines) { if ($this->numberOfLines <= 0) {
$this->numberOfLines = 1000; $this->numberOfLines = 1000;
} }
if ($this->container) { if ($this->container) {

View File

@@ -26,7 +26,7 @@ class All extends Component
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name')); $this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
} elseif ($this->resource->type() == 'application') { } elseif ($this->resource->type() == 'application') {
if ($this->resource->build_pack === 'dockercompose') { if ($this->resource->build_pack === 'dockercompose') {
$parsed = $this->resource->parseCompose(); $parsed = $this->resource->parse();
$containers = collect(data_get($parsed, 'services'))->keys(); $containers = collect(data_get($parsed, 'services'))->keys();
$this->containerNames = $containers; $this->containerNames = $containers;
} else { } else {

View File

@@ -7,8 +7,8 @@ use Livewire\Component;
class Executions extends Component class Executions extends Component
{ {
public $executions = []; public $executions = [];
public $selectedKey; public $selectedKey;
public $task;
public function getListeners() public function getListeners()
{ {
@@ -26,4 +26,44 @@ class Executions extends Component
} }
$this->selectedKey = $key; $this->selectedKey = $key;
} }
public function server()
{
if (!$this->task) {
return null;
}
if ($this->task->application) {
if ($this->task->application->destination && $this->task->application->destination->server) {
return $this->task->application->destination->server;
}
} elseif ($this->task->service) {
if ($this->task->service->destination && $this->task->service->destination->server) {
return $this->task->service->destination->server;
}
}
return null;
}
public function getServerTimezone()
{
$server = $this->server();
if (!$server) {
return 'UTC';
}
$serverTimezone = $server->settings->server_timezone;
return $serverTimezone;
}
public function formatDateInServerTimezone($date)
{
$serverTimezone = $this->getServerTimezone();
$dateObj = new \DateTime($date);
try {
$dateObj->setTimezone(new \DateTimeZone($serverTimezone));
} catch (\Exception $e) {
$dateObj->setTimezone(new \DateTimeZone('UTC'));
}
return $dateObj->format('Y-m-d H:i:s T');
}
} }

View File

@@ -18,12 +18,12 @@ class Form extends Component
public ?string $wildcard_domain = null; public ?string $wildcard_domain = null;
public int $cleanup_after_percentage;
public bool $dockerInstallationStarted = false; public bool $dockerInstallationStarted = false;
public bool $revalidate = false; public bool $revalidate = false;
public $timezones;
protected $listeners = ['serverInstalled', 'revalidate' => '$refresh']; protected $listeners = ['serverInstalled', 'revalidate' => '$refresh'];
protected $rules = [ protected $rules = [
@@ -37,7 +37,6 @@ class Form extends Component
'server.settings.is_swarm_manager' => 'required|boolean', 'server.settings.is_swarm_manager' => 'required|boolean',
'server.settings.is_swarm_worker' => 'required|boolean', 'server.settings.is_swarm_worker' => 'required|boolean',
'server.settings.is_build_server' => 'required|boolean', 'server.settings.is_build_server' => 'required|boolean',
'server.settings.is_force_cleanup_enabled' => 'required|boolean',
'server.settings.concurrent_builds' => 'required|integer|min:1', 'server.settings.concurrent_builds' => 'required|integer|min:1',
'server.settings.dynamic_timeout' => 'required|integer|min:1', 'server.settings.dynamic_timeout' => 'required|integer|min:1',
'server.settings.is_metrics_enabled' => 'required|boolean', 'server.settings.is_metrics_enabled' => 'required|boolean',
@@ -46,6 +45,10 @@ class Form extends Component
'server.settings.metrics_history_days' => 'required|integer|min:1', 'server.settings.metrics_history_days' => 'required|integer|min:1',
'wildcard_domain' => 'nullable|url', 'wildcard_domain' => 'nullable|url',
'server.settings.is_server_api_enabled' => 'required|boolean', 'server.settings.is_server_api_enabled' => 'required|boolean',
'server.settings.server_timezone' => 'required|string|timezone',
'server.settings.force_docker_cleanup' => 'required|boolean',
'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -66,12 +69,27 @@ class Form extends Component
'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval', 'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval',
'server.settings.metrics_history_days' => 'Metrics History', 'server.settings.metrics_history_days' => 'Metrics History',
'server.settings.is_server_api_enabled' => 'Server API', 'server.settings.is_server_api_enabled' => 'Server API',
'server.settings.server_timezone' => 'Server Timezone',
]; ];
public function mount() public function mount(Server $server)
{ {
$this->server = $server;
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
$this->wildcard_domain = $this->server->settings->wildcard_domain; $this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage; $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
}
public function updated($field)
{
if ($field === 'server.settings.docker_cleanup_frequency') {
$frequency = $this->server->settings->docker_cleanup_frequency;
if (empty($frequency) || ! validate_cron_expression($frequency)) {
$this->dispatch('error', 'Invalid Cron / Human expression for Docker Cleanup Frequency. Resetting to default 10 minutes.');
$this->server->settings->docker_cleanup_frequency = '*/10 * * * *';
}
}
} }
public function serverInstalled() public function serverInstalled()
@@ -116,7 +134,6 @@ class Form extends Component
} }
if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) { if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) {
ray('Starting sentinel'); ray('Starting sentinel');
} }
} else { } else {
ray('Sentinel is not enabled'); ray('Sentinel is not enabled');
@@ -172,6 +189,7 @@ class Form extends Component
public function submit() public function submit()
{ {
try {
if (isCloud() && ! isDev()) { if (isCloud() && ! isDev()) {
$this->validate(); $this->validate();
$this->validate([ $this->validate([
@@ -190,9 +208,30 @@ class Form extends Component
} }
refresh_server_connection($this->server->privateKey); refresh_server_connection($this->server->privateKey);
$this->server->settings->wildcard_domain = $this->wildcard_domain; $this->server->settings->wildcard_domain = $this->wildcard_domain;
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage; if ($this->server->settings->force_docker_cleanup) {
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
} else {
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
}
$currentTimezone = $this->server->settings->getOriginal('server_timezone');
$newTimezone = $this->server->settings->server_timezone;
if ($currentTimezone !== $newTimezone || $currentTimezone === '') {
$this->server->settings->server_timezone = $newTimezone;
$this->server->settings->save();
}
$this->server->settings->save(); $this->server->settings->save();
$this->server->save(); $this->server->save();
$this->dispatch('success', 'Server updated.'); $this->dispatch('success', 'Server updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function updatedServerSettingsServerTimezone($value)
{
$this->server->settings->server_timezone = $value;
$this->server->settings->save();
$this->dispatch('success', 'Server timezone updated.');
} }
} }

View File

@@ -40,6 +40,7 @@ class Index extends Component
'settings.is_auto_update_enabled' => 'boolean', 'settings.is_auto_update_enabled' => 'boolean',
'auto_update_frequency' => 'string', 'auto_update_frequency' => 'string',
'update_check_frequency' => 'string', 'update_check_frequency' => 'string',
'settings.instance_timezone' => 'required|string|timezone',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -54,6 +55,8 @@ class Index extends Component
'update_check_frequency' => 'Update Check Frequency', 'update_check_frequency' => 'Update Check Frequency',
]; ];
public $timezones;
public function mount() public function mount()
{ {
if (isInstanceAdmin()) { if (isInstanceAdmin()) {
@@ -65,6 +68,7 @@ class Index extends Component
$this->is_api_enabled = $this->settings->is_api_enabled; $this->is_api_enabled = $this->settings->is_api_enabled;
$this->auto_update_frequency = $this->settings->auto_update_frequency; $this->auto_update_frequency = $this->settings->auto_update_frequency;
$this->update_check_frequency = $this->settings->update_check_frequency; $this->update_check_frequency = $this->settings->update_check_frequency;
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
} else { } else {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
@@ -166,6 +170,13 @@ class Index extends Component
} }
} }
public function updatedSettingsInstanceTimezone($value)
{
$this->settings->instance_timezone = $value;
$this->settings->save();
$this->dispatch('success', 'Instance timezone updated.');
}
public function render() public function render()
{ {
return view('livewire.settings.index'); return view('livewire.settings.index');

View File

@@ -16,7 +16,7 @@ class Show extends Component
public array $parameters; public array $parameters;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey']; protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey'];
public function saveKey($data) public function saveKey($data)
{ {

View File

@@ -102,6 +102,8 @@ class Application extends BaseModel
{ {
use SoftDeletes; use SoftDeletes;
private static $parserVersion = '3';
protected $guarded = []; protected $guarded = [];
protected $appends = ['server_status']; protected $appends = ['server_status'];
@@ -125,7 +127,7 @@ class Application extends BaseModel
ApplicationSetting::create([ ApplicationSetting::create([
'application_id' => $application->id, 'application_id' => $application->id,
]); ]);
$application->compose_parsing_version = '2'; $application->compose_parsing_version = self::$parserVersion;
$application->save(); $application->save();
}); });
static::forceDeleting(function ($application) { static::forceDeleting(function ($application) {
@@ -138,6 +140,7 @@ class Application extends BaseModel
$task->delete(); $task->delete();
} }
$application->tags()->detach(); $application->tags()->detach();
$application->previews()->delete();
}); });
} }
@@ -412,23 +415,6 @@ class Application extends BaseModel
); );
} }
public function dockerComposePrLocation(): Attribute
{
return Attribute::make(
set: function ($value) {
if (is_null($value) || $value === '') {
return '/docker-compose.yaml';
} else {
if ($value !== '/') {
return Str::start(Str::replaceEnd('/', '', $value), '/');
}
return Str::start($value, '/');
}
}
);
}
public function baseDirectory(): Attribute public function baseDirectory(): Attribute
{ {
return Attribute::make( return Attribute::make(
@@ -479,12 +465,12 @@ class Application extends BaseModel
$main_server_status = $this->destination->server->isFunctional(); $main_server_status = $this->destination->server->isFunctional();
foreach ($additional_servers_status as $status) { foreach ($additional_servers_status as $status) {
$server_status = str($status)->before(':')->value(); $server_status = str($status)->before(':')->value();
if ($main_server_status !== $server_status) { if ($server_status !== 'running') {
return false; return false;
} }
} }
return true; return $main_server_status;
} }
} }
); );
@@ -1040,7 +1026,7 @@ class Application extends BaseModel
} }
} }
public function parseRawCompose() public function oldRawParser()
{ {
try { try {
$yaml = Yaml::parse($this->docker_compose_raw); $yaml = Yaml::parse($this->docker_compose_raw);
@@ -1100,9 +1086,11 @@ class Application extends BaseModel
instant_remote_process($commands, $this->destination->server, false); instant_remote_process($commands, $this->destination->server, false);
} }
public function parseCompose(int $pull_request_id = 0, ?int $preview_id = null) public function parse(int $pull_request_id = 0, ?int $preview_id = null)
{ {
if ($this->docker_compose_raw) { if ($this->compose_parsing_version === '3') {
return newParser($this, $pull_request_id, $preview_id);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id); return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
} else { } else {
return collect([]); return collect([]);
@@ -1154,7 +1142,7 @@ class Application extends BaseModel
if ($composeFileContent) { if ($composeFileContent) {
$this->docker_compose_raw = $composeFileContent; $this->docker_compose_raw = $composeFileContent;
$this->save(); $this->save();
$parsedServices = $this->parseCompose(); $parsedServices = $this->parse();
if ($this->docker_compose_domains) { if ($this->docker_compose_domains) {
$json = collect(json_decode($this->docker_compose_domains)); $json = collect(json_decode($this->docker_compose_domains));
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray(); $names = collect(data_get($parsedServices, 'services'))->keys()->toArray();

View File

@@ -12,9 +12,9 @@ class ApplicationPreview extends BaseModel
protected static function booted() protected static function booted()
{ {
static::deleting(function ($preview) { static::deleting(function ($preview) {
if ($preview->application->build_pack === 'dockercompose') { if (data_get($preview, 'application.build_pack') === 'dockercompose') {
$server = $preview->application->destination->server; $server = $preview->application->destination->server;
$composeFile = $preview->application->parseCompose(pull_request_id: $preview->pull_request_id); $composeFile = $preview->application->parse(pull_request_id: $preview->pull_request_id);
$volumes = data_get($composeFile, 'volumes'); $volumes = data_get($composeFile, 'volumes');
$networks = data_get($composeFile, 'networks'); $networks = data_get($composeFile, 'networks');
$networkKeys = collect($networks)->keys(); $networkKeys = collect($networks)->keys();

View File

@@ -6,7 +6,6 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
#[OA\Schema( #[OA\Schema(
@@ -97,8 +96,22 @@ class EnvironmentVariable extends Model
$resource = Application::find($this->application_id); $resource = Application::find($this->application_id);
} elseif ($this->service_id) { } elseif ($this->service_id) {
$resource = Service::find($this->service_id); $resource = Service::find($this->service_id);
} elseif ($this->database_id) { } elseif ($this->standalone_postgresql_id) {
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id')); $resource = StandalonePostgresql::find($this->standalone_postgresql_id);
} elseif ($this->standalone_redis_id) {
$resource = StandaloneRedis::find($this->standalone_redis_id);
} elseif ($this->standalone_mongodb_id) {
$resource = StandaloneMongodb::find($this->standalone_mongodb_id);
} elseif ($this->standalone_mysql_id) {
$resource = StandaloneMysql::find($this->standalone_mysql_id);
} elseif ($this->standalone_mariadb_id) {
$resource = StandaloneMariadb::find($this->standalone_mariadb_id);
} elseif ($this->standalone_keydb_id) {
$resource = StandaloneKeydb::find($this->standalone_keydb_id);
} elseif ($this->standalone_dragonfly_id) {
$resource = StandaloneDragonfly::find($this->standalone_dragonfly_id);
} elseif ($this->standalone_clickhouse_id) {
$resource = StandaloneClickhouse::find($this->standalone_clickhouse_id);
} }
return $resource; return $resource;
@@ -122,63 +135,6 @@ class EnvironmentVariable extends Model
); );
} }
protected function isFoundInCompose(): Attribute
{
return Attribute::make(
get: function () {
if (! $this->application_id) {
return true;
}
$found_in_compose = false;
$found_in_args = false;
$resource = $this->resource();
$compose = data_get($resource, 'docker_compose_raw');
if (! $compose) {
return true;
}
$yaml = Yaml::parse($compose);
$services = collect(data_get($yaml, 'services'));
if ($services->isEmpty()) {
return false;
}
foreach ($services as $service) {
$environments = collect(data_get($service, 'environment'));
$args = collect(data_get($service, 'build.args'));
if ($environments->isEmpty() && $args->isEmpty()) {
$found_in_compose = false;
break;
}
$found_in_compose = $environments->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_compose) {
break;
}
$found_in_args = $args->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_args) {
break;
}
}
return $found_in_compose || $found_in_args;
}
);
}
protected function isShared(): Attribute protected function isShared(): Attribute
{ {
return Attribute::make( return Attribute::make(
@@ -201,8 +157,10 @@ class EnvironmentVariable extends Model
$environment_variable = trim($environment_variable); $environment_variable = trim($environment_variable);
$sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/'); $sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/');
if ($sharedEnvsFound->isEmpty()) { if ($sharedEnvsFound->isEmpty()) {
return $environment_variable; return $environment_variable;
} }
foreach ($sharedEnvsFound as $sharedEnv) { foreach ($sharedEnvsFound as $sharedEnv) {
$type = str($sharedEnv)->match('/(.*?)\./'); $type = str($sharedEnv)->match('/(.*?)\./');
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) { if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {

View File

@@ -37,6 +37,30 @@ class InstanceSettings extends Model implements SendsEmail
); );
} }
public function updateCheckFrequency(): Attribute
{
return Attribute::make(
set: function ($value) {
return translate_cron_expression($value);
},
get: function ($value) {
return translate_cron_expression($value);
}
);
}
public function autoUpdateFrequency(): Attribute
{
return Attribute::make(
set: function ($value) {
return translate_cron_expression($value);
},
get: function ($value) {
return translate_cron_expression($value);
}
);
}
public static function get() public static function get()
{ {
return InstanceSettings::findOrFail(0); return InstanceSettings::findOrFail(0);

View File

@@ -24,8 +24,9 @@ class LocalFileVolume extends BaseModel
return $this->morphTo('resource'); return $this->morphTo('resource');
} }
public function deleteStorageOnServer() public function loadStorageOnServer()
{ {
$this->load(['service']);
$isService = data_get($this->resource, 'service'); $isService = data_get($this->resource, 'service');
if ($isService) { if ($isService) {
$workdir = $this->resource->service->workdir(); $workdir = $this->resource->service->workdir();
@@ -35,17 +36,46 @@ class LocalFileVolume extends BaseModel
$server = $this->resource->destination->server; $server = $this->resource->destination->server;
} }
$commands = collect([]); $commands = collect([]);
$fs_path = data_get($this, 'fs_path'); $path = data_get_str($this, 'fs_path');
$isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server); if ($path->startsWith('.')) {
$isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server); $path = $path->after('.');
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') { $path = $workdir.$path;
ray($isFile, $isDir); }
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
if ($isFile === 'OK') { if ($isFile === 'OK') {
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true"); $content = instant_remote_process(["cat $path"], $server, false);
$this->content = $content;
$this->is_directory = false;
$this->save();
}
}
public function deleteStorageOnServer()
{
$this->load(['service']);
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
$server = $this->resource->service->server;
} else {
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
$commands = collect([]);
$path = data_get_str($this, 'fs_path');
if ($path->startsWith('.')) {
$path = $path->after('.');
$path = $workdir.$path;
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($path && $path != '/' && $path != '.' && $path != '..') {
if ($isFile === 'OK') {
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
} elseif ($isDir === 'OK') { } elseif ($isDir === 'OK') {
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true"); $commands->push("rm -rf $path > /dev/null 2>&1 || true");
$commands->push("rmdir $fs_path > /dev/null 2>&1 || true"); $commands->push("rmdir $path > /dev/null 2>&1 || true");
} }
} }
if ($commands->count() > 0) { if ($commands->count() > 0) {
@@ -55,6 +85,7 @@ class LocalFileVolume extends BaseModel
public function saveStorageOnServer() public function saveStorageOnServer()
{ {
$this->load(['service']);
$isService = data_get($this->resource, 'service'); $isService = data_get($this->resource, 'service');
if ($isService) { if ($isService) {
$workdir = $this->resource->service->workdir(); $workdir = $this->resource->service->workdir();
@@ -74,30 +105,36 @@ class LocalFileVolume extends BaseModel
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true"); $commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
} }
} }
$fileVolume = $this; $path = data_get_str($this, 'fs_path');
$path = str(data_get($fileVolume, 'fs_path')); $content = data_get($this, 'content');
$content = data_get($fileVolume, 'content');
if ($path->startsWith('.')) { if ($path->startsWith('.')) {
$path = $path->after('.'); $path = $path->after('.');
$path = $workdir.$path; $path = $workdir.$path;
} }
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server); $isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server); $isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($isFile == 'OK' && $fileVolume->is_directory) { if ($isFile == 'OK' && $this->is_directory) {
$content = instant_remote_process(["cat $path"], $server, false); $content = instant_remote_process(["cat $path"], $server, false);
$fileVolume->is_directory = false; $this->is_directory = false;
$fileVolume->content = $content; $this->content = $content;
$fileVolume->save(); $this->save();
FileStorageChanged::dispatch(data_get($server, 'team_id')); FileStorageChanged::dispatch(data_get($server, 'team_id'));
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.'); throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) { } elseif ($isDir == 'OK' && ! $this->is_directory) {
$fileVolume->is_directory = true; if ($path == '/' || $path == '.' || $path == '..' || $path == '' || str($path)->isEmpty() || is_null($path)) {
$fileVolume->save(); $this->is_directory = true;
$this->save();
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.'); throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.');
} }
if ($isDir == 'NOK' && ! $fileVolume->is_directory) { instant_remote_process([
$chmod = data_get($fileVolume, 'chmod'); "rm -fr $path",
$chown = data_get($fileVolume, 'chown'); "touch $path",
], $server, false);
FileStorageChanged::dispatch(data_get($server, 'team_id'));
}
if ($isDir == 'NOK' && ! $this->is_directory) {
$chmod = data_get($this, 'chmod');
$chown = data_get($this, 'chown');
if ($content) { if ($content) {
$content = base64_encode($content); $content = base64_encode($content);
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null"); $commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
@@ -111,7 +148,7 @@ class LocalFileVolume extends BaseModel
if ($chmod) { if ($chmod) {
$commands->push("chmod $chmod $path"); $commands->push("chmod $chmod $path");
} }
} elseif ($isDir == 'NOK' && $fileVolume->is_directory) { } elseif ($isDir == 'NOK' && $this->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true"); $commands->push("mkdir -p $path > /dev/null 2>&1 || true");
} }

View File

@@ -34,4 +34,14 @@ class ScheduledDatabaseBackup extends BaseModel
{ {
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get(); return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get();
} }
public function server()
{
if ($this->database) {
if ($this->database->destination && $this->database->destination->server) {
$server = $this->database->destination->server;
return $server;
}
}
return null;
}
} }

View File

@@ -4,6 +4,8 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
use App\Models\Service;
use App\Models\Application;
class ScheduledTask extends BaseModel class ScheduledTask extends BaseModel
{ {
@@ -26,6 +28,27 @@ class ScheduledTask extends BaseModel
public function executions(): HasMany public function executions(): HasMany
{ {
return $this->hasMany(ScheduledTaskExecution::class); return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc');
}
public function server()
{
if ($this->application) {
if ($this->application->destination && $this->application->destination->server) {
$server = $this->application->destination->server;
return $server;
}
} elseif ($this->service) {
if ($this->service->destination && $this->service->destination->server) {
$server = $this->service->destination->server;
return $server;
}
} elseif ($this->database) {
if ($this->database->destination && $this->database->destination->server) {
$server = $this->database->destination->server;
return $server;
}
}
return null;
} }
} }

View File

@@ -649,7 +649,7 @@ $schema://$host {
} }
} }
public function getDiskUsage() public function getDiskUsage(): ?string
{ {
return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false); return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
} }

View File

@@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
@@ -10,10 +11,10 @@ use OpenApi\Attributes as OA;
type: 'object', type: 'object',
properties: [ properties: [
'id' => ['type' => 'integer'], 'id' => ['type' => 'integer'],
'cleanup_after_percentage' => ['type' => 'integer'],
'concurrent_builds' => ['type' => 'integer'], 'concurrent_builds' => ['type' => 'integer'],
'dynamic_timeout' => ['type' => 'integer'], 'dynamic_timeout' => ['type' => 'integer'],
'force_disabled' => ['type' => 'boolean'], 'force_disabled' => ['type' => 'boolean'],
'force_server_cleanup' => ['type' => 'boolean'],
'is_build_server' => ['type' => 'boolean'], 'is_build_server' => ['type' => 'boolean'],
'is_cloudflare_tunnel' => ['type' => 'boolean'], 'is_cloudflare_tunnel' => ['type' => 'boolean'],
'is_jump_server' => ['type' => 'boolean'], 'is_jump_server' => ['type' => 'boolean'],
@@ -37,6 +38,8 @@ use OpenApi\Attributes as OA;
'metrics_history_days' => ['type' => 'integer'], 'metrics_history_days' => ['type' => 'integer'],
'metrics_refresh_rate_seconds' => ['type' => 'integer'], 'metrics_refresh_rate_seconds' => ['type' => 'integer'],
'metrics_token' => ['type' => 'string'], 'metrics_token' => ['type' => 'string'],
'docker_cleanup_frequency' => ['type' => 'string'],
'docker_cleanup_threshold' => ['type' => 'integer'],
'server_id' => ['type' => 'integer'], 'server_id' => ['type' => 'integer'],
'wildcard_domain' => ['type' => 'string'], 'wildcard_domain' => ['type' => 'string'],
'created_at' => ['type' => 'string'], 'created_at' => ['type' => 'string'],
@@ -47,8 +50,25 @@ class ServerSetting extends Model
{ {
protected $guarded = []; protected $guarded = [];
protected $casts = [
'force_docker_cleanup' => 'boolean',
'docker_cleanup_threshold' => 'integer',
];
public function server() public function server()
{ {
return $this->belongsTo(Server::class); return $this->belongsTo(Server::class);
} }
public function dockerCleanupFrequency(): Attribute
{
return Attribute::make(
set: function ($value) {
return translate_cron_expression($value);
},
get: function ($value) {
return translate_cron_expression($value);
}
);
}
} }

View File

@@ -7,9 +7,10 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Spatie\Url\Url; use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml; use Visus\Cuid2\Cuid2;
#[OA\Schema( #[OA\Schema(
description: 'Service model', description: 'Service model',
@@ -23,6 +24,7 @@ use Symfony\Component\Yaml\Yaml;
'description' => ['type' => 'string', 'description' => 'The description of the service.'], 'description' => ['type' => 'string', 'description' => 'The description of the service.'],
'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'], 'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'],
'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'], 'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'],
'destination_type' => ['type' => 'string', 'description' => 'Destination type.'],
'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'], 'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'], 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'], 'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
@@ -38,10 +40,20 @@ class Service extends BaseModel
{ {
use HasFactory, SoftDeletes; use HasFactory, SoftDeletes;
private static $parserVersion = '3';
protected $guarded = []; protected $guarded = [];
protected $appends = ['server_status']; protected $appends = ['server_status'];
protected static function booted()
{
static::created(function ($service) {
$service->compose_parsing_version = self::$parserVersion;
$service->save();
});
}
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray(); $domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray();
@@ -205,6 +217,41 @@ class Service extends BaseModel
foreach ($applications as $application) { foreach ($applications as $application) {
$image = str($application->image)->before(':')->value(); $image = str($application->image)->before(':')->value();
switch ($image) { switch ($image) {
case str($image)?->contains('rabbitmq'):
$data = collect([]);
$host_port = $this->environment_variables()->where('key', 'PORT')->first();
$username = $this->environment_variables()->where('key', 'SERVICE_USER_RABBITMQ')->first();
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_RABBITMQ')->first();
if ($host_port) {
$data = $data->merge([
'Host Port Binding' => [
'key' => data_get($host_port, 'key'),
'value' => data_get($host_port, 'value'),
'rules' => 'required',
],
]);
}
if ($username) {
$data = $data->merge([
'Username' => [
'key' => data_get($username, 'key'),
'value' => data_get($username, 'value'),
'rules' => 'required',
],
]);
}
if ($password) {
$data = $data->merge([
'Password' => [
'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('RabbitMQ', $data->toArray());
break;
case str($image)?->contains('tolgee'): case str($image)?->contains('tolgee'):
$data = collect([]); $data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first(); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
@@ -504,6 +551,9 @@ class Service extends BaseModel
default: default:
$data = collect([]); $data = collect([]);
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first(); $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
// Chaskiq
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first(); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
if ($admin_user) { if ($admin_user) {
$data = $data->merge([ $data = $data->merge([
@@ -525,6 +575,15 @@ class Service extends BaseModel
], ],
]); ]);
} }
if ($admin_email) {
$data = $data->merge([
'Email' => [
'key' => 'ADMIN_EMAIL',
'value' => data_get($admin_email, 'value'),
'rules' => 'required|email',
],
]);
}
$fields->put('Admin', $data->toArray()); $fields->put('Admin', $data->toArray());
break; break;
case str($image)?->contains('vaultwarden'): case str($image)?->contains('vaultwarden'):
@@ -617,6 +676,32 @@ class Service extends BaseModel
$fields->put('GitLab', $data->toArray()); $fields->put('GitLab', $data->toArray());
break; break;
case str($image)->contains('code-server'):
$data = collect([]);
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_PASSWORDCODESERVER')->first();
if ($password) {
$data = $data->merge([
'Password' => [
'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$sudoPassword = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_SUDOCODESERVER')->first();
if ($sudoPassword) {
$data = $data->merge([
'Sudo Password' => [
'key' => data_get($sudoPassword, 'key'),
'value' => data_get($sudoPassword, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Code Server', $data->toArray());
break;
} }
} }
$databases = $this->databases()->get(); $databases = $this->databases()->get();
@@ -663,8 +748,8 @@ class Service extends BaseModel
$fields->put('PostgreSQL', $data->toArray()); $fields->put('PostgreSQL', $data->toArray());
break; break;
case str($image)->contains('mysql'): case str($image)->contains('mysql'):
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS']; $userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER'];
$passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS']; $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD'];
$rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT']; $rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT'];
$dbNameVariables = ['MYSQL_DATABASE']; $dbNameVariables = ['MYSQL_DATABASE'];
$mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first(); $mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
@@ -713,10 +798,10 @@ class Service extends BaseModel
$fields->put('MySQL', $data->toArray()); $fields->put('MySQL', $data->toArray());
break; break;
case str($image)->contains('mariadb'): case str($image)->contains('mariadb'):
$userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER']; $userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER', 'SERVICE_USER_MYSQL', 'MYSQL_USER'];
$passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS']; $passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS', 'MYSQL_PASSWORD'];
$rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS']; $rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS', 'MYSQL_ROOT_PASSWORD'];
$dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA']; $dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA', 'MYSQL_DATABASE'];
$mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first(); $mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
$mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first(); $mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
$mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first(); $mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first();
@@ -763,6 +848,7 @@ class Service extends BaseModel
} }
$fields->put('MariaDB', $data->toArray()); $fields->put('MariaDB', $data->toArray());
break; break;
} }
} }
@@ -897,7 +983,8 @@ class Service extends BaseModel
public function environment_variables(): HasMany public function environment_variables(): HasMany
{ {
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
return $this->hasMany(EnvironmentVariable::class)->orderByRaw("key LIKE 'SERVICE%' DESC, value ASC");
} }
public function environment_variables_preview(): HasMany public function environment_variables_preview(): HasMany
@@ -913,14 +1000,19 @@ class Service extends BaseModel
public function saveComposeConfigs() public function saveComposeConfigs()
{ {
$workdir = $this->workdir(); $workdir = $this->workdir();
$commands[] = "mkdir -p $workdir";
instant_remote_process([
"mkdir -p $workdir",
"cd $workdir",
], $this->server);
$filename = new Cuid2.'-docker-compose.yml';
Storage::disk('local')->put("tmp/{$filename}", $this->docker_compose);
$path = Storage::path("tmp/{$filename}");
instant_scp($path, "{$workdir}/docker-compose.yml", $this->server);
Storage::disk('local')->delete("tmp/{$filename}");
$commands[] = "cd $workdir"; $commands[] = "cd $workdir";
$json = Yaml::parse($this->docker_compose);
$this->docker_compose = Yaml::dump($json, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
$docker_compose_base64 = base64_encode($this->docker_compose);
$commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null";
$commands[] = 'rm -f .env || true'; $commands[] = 'rm -f .env || true';
$envs_from_coolify = $this->environment_variables()->get(); $envs_from_coolify = $this->environment_variables()->get();
@@ -935,7 +1027,14 @@ class Service extends BaseModel
public function parse(bool $isNew = false): Collection public function parse(bool $isNew = false): Collection
{ {
if ($this->compose_parsing_version === '3') {
return newParser($this);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile($this, $isNew); return parseDockerComposeFile($this, $isNew);
} else {
return collect([]);
}
} }
public function networks() public function networks()

View File

@@ -44,7 +44,7 @@ class DockerCleanup extends Notification implements ShouldQueue
// $mail->view('emails.high-disk-usage', [ // $mail->view('emails.high-disk-usage', [
// 'name' => $this->server->name, // 'name' => $this->server->name,
// 'disk_usage' => $this->disk_usage, // 'disk_usage' => $this->disk_usage,
// 'threshold' => $this->cleanup_after_percentage, // 'threshold' => $this->docker_cleanup_threshold,
// ]); // ]);
// return $mail; // return $mail;
// } // }

View File

@@ -17,7 +17,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) {} public function __construct(public Server $server, public int $disk_usage, public int $docker_cleanup_threshold) {}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {
@@ -46,7 +46,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
$mail->view('emails.high-disk-usage', [ $mail->view('emails.high-disk-usage', [
'name' => $this->server->name, 'name' => $this->server->name,
'disk_usage' => $this->disk_usage, 'disk_usage' => $this->disk_usage,
'threshold' => $this->cleanup_after_percentage, 'threshold' => $this->docker_cleanup_threshold,
]); ]);
return $mail; return $mail;
@@ -54,7 +54,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup."; $message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->docker_cleanup_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.";
return $message; return $message;
} }
@@ -62,7 +62,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
public function toTelegram(): array public function toTelegram(): array
{ {
return [ return [
'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.", 'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->docker_cleanup_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
]; ];
} }
} }

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\TelescopeApplicationServiceProvider;
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// Telescope::night();
$this->hideSensitiveRequestDetails();
$isLocal = $this->app->environment('local');
Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
return $isLocal ||
$entry->isReportableException() ||
$entry->isFailedRequest() ||
$entry->isFailedJob() ||
$entry->isScheduledTask() ||
$entry->hasMonitoredTag();
});
}
/**
* Prevent sensitive request details from being logged by Telescope.
*/
protected function hideSensitiveRequestDetails(): void
{
if ($this->app->environment('local')) {
return;
}
Telescope::hideRequestParameters(['_token']);
Telescope::hideRequestHeaders([
'cookie',
'x-csrf-token',
'x-xsrf-token',
]);
}
/**
* Register the Telescope gate.
*
* This gate determines who can access Telescope in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewTelescope', function ($user) {
$root_user = User::find(0);
return in_array($user->email, [
$root_user->email,
]);
});
}
}

View File

@@ -91,7 +91,7 @@ function next_queuable(string $server_id, string $application_id): bool
$server = Server::find($server_id); $server = Server::find($server_id);
$concurrent_builds = $server->settings->concurrent_builds; $concurrent_builds = $server->settings->concurrent_builds;
ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}"); ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}")->green();
if ($deployments->count() > $concurrent_builds) { if ($deployments->count() > $concurrent_builds) {
return false; return false;

View File

@@ -19,7 +19,7 @@ function generate_database_name(string $type): string
return $type.'-database-'.$cuid; return $type.'-database-'.$cuid;
} }
function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null): StandalonePostgresql function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null, string $databaseImage = 'postgres:16-alpine'): StandalonePostgresql
{ {
$destination = StandaloneDocker::where('uuid', $destinationUuid)->first(); $destination = StandaloneDocker::where('uuid', $destinationUuid)->first();
if (! $destination) { if (! $destination) {
@@ -27,6 +27,7 @@ function create_standalone_postgresql($environmentId, $destinationUuid, ?array $
} }
$database = new StandalonePostgresql; $database = new StandalonePostgresql;
$database->name = generate_database_name('postgresql'); $database->name = generate_database_name('postgresql');
$database->image = $databaseImage;
$database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environmentId; $database->environment_id = $environmentId;
$database->destination_id = $destination->id; $database->destination_id = $destination->id;

View File

@@ -140,6 +140,8 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
function generateApplicationContainerName(Application $application, $pull_request_id = 0) function generateApplicationContainerName(Application $application, $pull_request_id = 0)
{ {
// TODO: refactor generateApplicationContainerName, we do not need $application and $pull_request_id
$consistent_container_name = $application->settings->is_consistent_container_name_enabled; $consistent_container_name = $application->settings->is_consistent_container_name_enabled;
$now = now()->format('Hisu'); $now = now()->format('Hisu');
if ($pull_request_id !== 0 && $pull_request_id !== null) { if ($pull_request_id !== 0 && $pull_request_id !== null) {
@@ -251,7 +253,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
return $payload; return $payload;
} }
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both') function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null)
{ {
$labels = collect([]); $labels = collect([]);
if ($serviceLabels) { if ($serviceLabels) {
@@ -270,6 +272,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
if (is_null($port) && ! is_null($onlyPort)) { if (is_null($port) && ! is_null($onlyPort)) {
$port = $onlyPort; $port = $onlyPort;
} }
if (is_null($port) && $predefinedPort) {
$port = $predefinedPort;
}
$labels->push("caddy_{$loop}={$schema}://{$host}"); $labels->push("caddy_{$loop}={$schema}://{$host}");
$labels->push("caddy_{$loop}.header=-Server"); $labels->push("caddy_{$loop}.header=-Server");
$labels->push("caddy_{$loop}.try_files={path} /index.html /index.php"); $labels->push("caddy_{$loop}.try_files={path} /index.html /index.php");
@@ -677,18 +682,19 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
'--sysctl', '--sysctl',
'--ulimit', '--ulimit',
'--device', '--device',
'--shm-size',
]); ]);
$mapping = collect([ $mapping = collect([
'--cap-add' => 'cap_add', '--cap-add' => 'cap_add',
'--cap-drop' => 'cap_drop', '--cap-drop' => 'cap_drop',
'--security-opt' => 'security_opt', '--security-opt' => 'security_opt',
'--sysctl' => 'sysctls', '--sysctl' => 'sysctls',
'--ulimit' => 'ulimits',
'--device' => 'devices', '--device' => 'devices',
'--init' => 'init', '--init' => 'init',
'--ulimit' => 'ulimits', '--ulimit' => 'ulimits',
'--privileged' => 'privileged', '--privileged' => 'privileged',
'--ip' => 'ip', '--ip' => 'ip',
'--shm-size' => 'shm_size',
]); ]);
foreach ($matches as $match) { foreach ($matches as $match) {
$option = $match[1]; $option = $match[1];
@@ -704,6 +710,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
$options = collect($options); $options = collect($options);
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js // Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
foreach ($options as $option => $value) { foreach ($options as $option => $value) {
// ray($option,$value);
if (! data_get($mapping, $option)) { if (! data_get($mapping, $option)) {
continue; continue;
} }
@@ -728,6 +735,10 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
} }
}); });
$compose_options->put($mapping[$option], $ulimits); $compose_options->put($mapping[$option], $ulimits);
} elseif ($option === '--shm-size') {
if (! is_null($value) && is_array($value) && count($value) > 0) {
$compose_options->put($mapping[$option], $value[0]);
}
} else { } else {
if ($list_options->contains($option)) { if ($list_options->contains($option)) {
if ($compose_options->has($mapping[$option])) { if ($compose_options->has($mapping[$option])) {
@@ -749,6 +760,26 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
return $compose_options->toArray(); return $compose_options->toArray();
} }
function generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $network)
{
$ipv4 = data_get($docker_run_options, 'ip.0');
$ipv6 = data_get($docker_run_options, 'ip6.0');
data_forget($docker_run_options, 'ip');
data_forget($docker_run_options, 'ip6');
if ($ipv4 || $ipv6) {
data_forget($docker_compose['services'][$container_name], 'networks');
}
if ($ipv4) {
$docker_compose['services'][$container_name]['networks'][$network]['ipv4_address'] = $ipv4;
}
if ($ipv6) {
$docker_compose['services'][$container_name]['networks'][$network]['ipv6_address'] = $ipv6;
}
$docker_compose['services'][$container_name] = array_merge_recursive($docker_compose['services'][$container_name], $docker_run_options);
return $docker_compose;
}
function validateComposeFile(string $compose, int $server_id): string|Throwable function validateComposeFile(string $compose, int $server_id): string|Throwable
{ {
return 'OK'; return 'OK';

View File

@@ -146,12 +146,11 @@ function generate_default_proxy_configuration(Server $server)
'coolify.managed=true', 'coolify.managed=true',
]; ];
$config = [ $config = [
'version' => '3.8',
'networks' => $array_of_networks->toArray(), 'networks' => $array_of_networks->toArray(),
'services' => [ 'services' => [
'traefik' => [ 'traefik' => [
'container_name' => 'coolify-proxy', 'container_name' => 'coolify-proxy',
'image' => 'traefik:v2.11', 'image' => 'traefik:v3.1',
'restart' => RESTART_MODE, 'restart' => RESTART_MODE,
'extra_hosts' => [ 'extra_hosts' => [
'host.docker.internal:host-gateway', 'host.docker.internal:host-gateway',

View File

@@ -170,7 +170,7 @@ function generateSshCommand(Server $server, string $command)
// ray($ssh_command); // ray($ssh_command);
return $ssh_command; return $ssh_command;
} }
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false) function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string
{ {
$timeout = config('constants.ssh.command_timeout'); $timeout = config('constants.ssh.command_timeout');
if ($command instanceof Collection) { if ($command instanceof Collection) {

View File

@@ -53,7 +53,9 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
if ($isFile == 'OK') { if ($isFile == 'OK') {
// If its a file & exists // If its a file & exists
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server); $filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
if ($fileVolume->is_based_on_git) {
$fileVolume->content = $filesystemContent; $fileVolume->content = $filesystemContent;
}
$fileVolume->is_directory = false; $fileVolume->is_directory = false;
$fileVolume->save(); $fileVolume->save();
} elseif ($isDir == 'OK') { } elseif ($isDir == 'OK') {

File diff suppressed because it is too large Load Diff

View File

@@ -14,10 +14,11 @@
"guzzlehttp/guzzle": "^7.5.0", "guzzlehttp/guzzle": "^7.5.0",
"laravel/fortify": "^v1.16.0", "laravel/fortify": "^v1.16.0",
"laravel/framework": "^v11", "laravel/framework": "^v11",
"laravel/horizon": "^5.23.1", "laravel/horizon": "^5.27.1",
"laravel/prompts": "^0.1.6", "laravel/prompts": "^0.1.6",
"laravel/sanctum": "^v4.0", "laravel/sanctum": "^v4.0",
"laravel/socialite": "^v5.14.0", "laravel/socialite": "^v5.14.0",
"laravel/telescope": "^5.2",
"laravel/tinker": "^v2.8.1", "laravel/tinker": "^v2.8.1",
"laravel/ui": "^4.2", "laravel/ui": "^4.2",
"lcobucci/jwt": "^5.0.0", "lcobucci/jwt": "^5.0.0",
@@ -93,7 +94,9 @@
}, },
"extra": { "extra": {
"laravel": { "laravel": {
"dont-discover": [] "dont-discover": [
"laravel/telescope"
]
} }
}, },
"config": { "config": {

854
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -199,6 +199,7 @@ return [
App\Providers\EventServiceProvider::class, App\Providers\EventServiceProvider::class,
App\Providers\HorizonServiceProvider::class, App\Providers\HorizonServiceProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class,
App\Providers\TelescopeServiceProvider::class,
], ],

View File

@@ -35,34 +35,6 @@ return [
'connections' => [ 'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [ 'pgsql' => [
'driver' => 'pgsql', 'driver' => 'pgsql',
'url' => env('DATABASE_URL'), 'url' => env('DATABASE_URL'),
@@ -77,22 +49,6 @@ return [
'search_path' => 'public', 'search_path' => 'public',
'sslmode' => 'prefer', 'sslmode' => 'prefer',
], ],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
], ],
/* /*

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.323', 'release' => '4.0.0-beta.329',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

206
config/telescope.php Normal file
View File

@@ -0,0 +1,206 @@
<?php
use Laravel\Telescope\Http\Middleware\Authorize;
use Laravel\Telescope\Watchers;
return [
/*
|--------------------------------------------------------------------------
| Telescope Master Switch
|--------------------------------------------------------------------------
|
| This option may be used to disable all Telescope watchers regardless
| of their individual configuration, which simply provides a single
| and convenient way to enable or disable Telescope data storage.
|
*/
'enabled' => env('TELESCOPE_ENABLED', false),
/*
|--------------------------------------------------------------------------
| Telescope Domain
|--------------------------------------------------------------------------
|
| This is the subdomain where Telescope will be accessible from. If the
| setting is null, Telescope will reside under the same domain as the
| application. Otherwise, this value will be used as the subdomain.
|
*/
'domain' => env('TELESCOPE_DOMAIN'),
/*
|--------------------------------------------------------------------------
| Telescope Path
|--------------------------------------------------------------------------
|
| This is the URI path where Telescope will be accessible from. Feel free
| to change this path to anything you like. Note that the URI will not
| affect the paths of its internal API that aren't exposed to users.
|
*/
'path' => env('TELESCOPE_PATH', 'telescope'),
/*
|--------------------------------------------------------------------------
| Telescope Storage Driver
|--------------------------------------------------------------------------
|
| This configuration options determines the storage driver that will
| be used to store Telescope's data. In addition, you may set any
| custom options as needed by the particular driver you choose.
|
*/
'driver' => env('TELESCOPE_DRIVER', 'database'),
'storage' => [
'database' => [
'connection' => env('DB_CONNECTION', 'pgsql'),
'chunk' => 1000,
],
],
/*
|--------------------------------------------------------------------------
| Telescope Queue
|--------------------------------------------------------------------------
|
| This configuration options determines the queue connection and queue
| which will be used to process ProcessPendingUpdate jobs. This can
| be changed if you would prefer to use a non-default connection.
|
*/
'queue' => [
'connection' => env('TELESCOPE_QUEUE_CONNECTION', null),
'queue' => env('TELESCOPE_QUEUE', null),
],
/*
|--------------------------------------------------------------------------
| Telescope Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will be assigned to every Telescope route, giving you
| the chance to add your own middleware to this list or change any of
| the existing middleware. Or, you can simply stick with this list.
|
*/
'middleware' => [
'web',
Authorize::class,
],
/*
|--------------------------------------------------------------------------
| Allowed / Ignored Paths & Commands
|--------------------------------------------------------------------------
|
| The following array lists the URI paths and Artisan commands that will
| not be watched by Telescope. In addition to this list, some Laravel
| commands, like migrations and queue commands, are always ignored.
|
*/
'only_paths' => [
// 'api/*'
],
'ignore_paths' => [
'livewire*',
'nova-api*',
'pulse*',
'broadcasting/auth',
],
'ignore_commands' => [
//
],
/*
|--------------------------------------------------------------------------
| Telescope Watchers
|--------------------------------------------------------------------------
|
| The following array lists the "watchers" that will be registered with
| Telescope. The watchers gather the application's profile data when
| a request or task is executed. Feel free to customize this list.
|
*/
'watchers' => [
Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true),
Watchers\CacheWatcher::class => [
'enabled' => env('TELESCOPE_CACHE_WATCHER', true),
'hidden' => [],
],
Watchers\ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true),
Watchers\CommandWatcher::class => [
'enabled' => env('TELESCOPE_COMMAND_WATCHER', true),
'ignore' => [],
],
Watchers\DumpWatcher::class => [
'enabled' => env('TELESCOPE_DUMP_WATCHER', true),
'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false),
],
Watchers\EventWatcher::class => [
'enabled' => env('TELESCOPE_EVENT_WATCHER', true),
'ignore' => [],
],
Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true),
Watchers\GateWatcher::class => [
'enabled' => env('TELESCOPE_GATE_WATCHER', false),
'ignore_abilities' => [],
'ignore_packages' => true,
'ignore_paths' => [],
],
Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', false),
Watchers\LogWatcher::class => [
'enabled' => env('TELESCOPE_LOG_WATCHER', true),
'level' => 'debug',
],
Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', false),
Watchers\ModelWatcher::class => [
'enabled' => env('TELESCOPE_MODEL_WATCHER', true),
'events' => ['eloquent.*'],
'hydrations' => true,
],
Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', false),
Watchers\QueryWatcher::class => [
'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
'ignore_packages' => true,
'ignore_paths' => [],
'slow' => 100,
],
Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true),
Watchers\RequestWatcher::class => [
'enabled' => env('TELESCOPE_REQUEST_WATCHER', true),
'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64),
'ignore_http_methods' => [],
'ignore_status_codes' => [],
],
Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', false),
Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true),
],
];

View File

@@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.323'; return '4.0.0-beta.329';

View File

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

View File

@@ -0,0 +1,60 @@
<?php
use App\Models\ServerSetting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddServerCleanupFieldsToServerSettingsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$serverSettings = ServerSetting::all();
Schema::table('server_settings', function (Blueprint $table) {
$table->boolean('force_docker_cleanup')->default(false);
$table->string('docker_cleanup_frequency')->default('*/10 * * * *');
$table->integer('docker_cleanup_threshold')->default(80);
// Remove old columns
$table->dropColumn('cleanup_after_percentage');
$table->dropColumn('is_force_cleanup_enabled');
});
foreach ($serverSettings as $serverSetting) {
$serverSetting->force_docker_cleanup = $serverSetting->is_force_cleanup_enabled;
$serverSetting->docker_cleanup_threshold = $serverSetting->cleanup_after_percentage;
$serverSetting->save();
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$serverSettings = ServerSetting::all();
Schema::table('server_settings', function (Blueprint $table) {
$table->dropColumn('force_docker_cleanup');
$table->dropColumn('docker_cleanup_frequency');
$table->dropColumn('docker_cleanup_threshold');
// Add back old columns
$table->integer('cleanup_after_percentage')->default(80);
$table->boolean('force_server_cleanup')->default(false);
$table->boolean('is_force_cleanup_enabled')->default(false);
});
foreach ($serverSettings as $serverSetting) {
$serverSetting->is_force_cleanup_enabled = $serverSetting->force_docker_cleanup;
$serverSetting->cleanup_after_percentage = $serverSetting->docker_cleanup_threshold;
$serverSetting->save();
}
}
}

View File

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

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddTimezoneToServerAndInstanceSettings extends Migration
{
public function up()
{
Schema::table('server_settings', function (Blueprint $table) {
$table->string('server_timezone')->default('');
});
Schema::table('instance_settings', function (Blueprint $table) {
$table->string('instance_timezone')->default('UTC');
});
}
public function down()
{
Schema::table('server_settings', function (Blueprint $table) {
$table->dropColumn('server_timezone');
});
Schema::table('instance_settings', function (Blueprint $table) {
$table->dropColumn('instance_timezone');
});
}
}

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