diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/01_BUG_REPORT.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/BUG_REPORT.yml rename to .github/ISSUE_TEMPLATE/01_BUG_REPORT.yml diff --git a/.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml b/.github/ISSUE_TEMPLATE/02_ENHANCEMENT_BOUNTY.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml rename to .github/ISSUE_TEMPLATE/02_ENHANCEMENT_BOUNTY.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index 0edaa4f1cc..0000000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Docker Image CI - -on: - # push: - # branches: [ "main" ] - # pull_request: - # branches: [ "*" ] - push: - branches: ["this-does-not-exist"] - pull_request: - branches: ["this-does-not-exist"] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Cache Docker layers - uses: actions/cache@v2 - with: - path: | - /usr/local/share/ca-certificates - /var/cache/apt/archives - /var/lib/apt/lists - ~/.cache - key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }} - restore-keys: | - ${{ runner.os }}-docker- - - name: Build the Docker image - run: | - cp .env.example .env - docker run --rm -u "$(id -u):$(id -g)" \ - -v "$(pwd):/app" \ - -w /app composer:2 \ - composer install --ignore-platform-reqs - ./vendor/bin/spin build - - name: Start the stack - run: | - ./vendor/bin/spin up -d - ./vendor/bin/spin exec coolify php artisan key:generate - ./vendor/bin/spin exec coolify php artisan migrate:fresh --seed - - name: Test (missing E2E tests) - run: | - ./vendor/bin/spin exec coolify php artisan test diff --git a/.github/workflows/fix-php-code-style-issues b/.github/workflows/fix-php-code-style-issues deleted file mode 100644 index aebce91bcc..0000000000 --- a/.github/workflows/fix-php-code-style-issues +++ /dev/null @@ -1,25 +0,0 @@ -name: Fix PHP code style issues - -on: [push] - -permissions: - contents: write - -jobs: - php-code-styling: - runs-on: ubuntu-latest - timeout-minutes: 5 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - - - name: Fix PHP code style issues - uses: aglipanci/laravel-pint-action@2.4 - - - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: Fix styling diff --git a/.github/workflows/lock-closed-issues-discussions-and-prs.yml b/.github/workflows/lock-closed-issues-discussions-and-prs.yml new file mode 100644 index 0000000000..d008539647 --- /dev/null +++ b/.github/workflows/lock-closed-issues-discussions-and-prs.yml @@ -0,0 +1,17 @@ +name: Lock closed Issues, Discussions, and PRs + +on: + schedule: + - cron: '0 1 * * *' + +jobs: + lock-threads: + runs-on: ubuntu-latest + steps: + - name: Lock threads after 30 days of inactivity + uses: dessant/lock-threads@v5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + issue-inactive-days: '30' + pr-inactive-days: '30' + discussion-inactive-days: '30' diff --git a/.github/workflows/manage-stale-issues-and-prs.yml b/.github/workflows/manage-stale-issues-and-prs.yml new file mode 100644 index 0000000000..2afc996cb5 --- /dev/null +++ b/.github/workflows/manage-stale-issues-and-prs.yml @@ -0,0 +1,28 @@ +name: Manage Stale Issues and PRs + +on: + schedule: + - cron: '0 2 * * *' + +jobs: + manage-stale: + runs-on: ubuntu-latest + steps: + - name: Manage stale issues and PRs + uses: actions/stale@v9 + id: stale + with: + stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.' + stale-pr-message: 'This pull request will be automatically closed in a few days if no response is received. Please update your PR or comment if you would like to continue working on it.' + close-issue-message: 'This issue has been automatically closed due to inactivity.' + close-pr-message: 'This pull request has been automatically closed due to inactivity.' + days-before-stale: 14 + days-before-close: 7 + stale-issue-label: '⏱︎ Stale' + stale-pr-label: '⏱︎ Stale' + only-labels: '💤 Waiting for feedback' + remove-stale-when-updated: true + operations-per-run: 100 + labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback' + close-issue-reason: 'not_planned' + exempt-all-milestones: false diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml deleted file mode 100644 index d7a680170c..0000000000 --- a/.github/workflows/pr-build.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: PR Build (v4) - -on: - pull_request: - types: - - opened - branches-ignore: ["main", "v3"] - paths-ignore: - - .github/workflows/coolify-helper.yml - - docker/coolify-helper/Dockerfile - -env: - REGISTRY: ghcr.io - IMAGE_NAME: "coollabsio/coolify" - -jobs: - amd64: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - attestations: write - id-token: write - actions: write - steps: - - uses: actions/checkout@v4 - - name: Login to ghcr.io - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build image and push to registry - uses: docker/build-push-action@v5 - with: - context: . - file: docker/prod/Dockerfile - platforms: linux/amd64 - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }} - aarch64: - runs-on: [self-hosted, arm64] - permissions: - contents: read - packages: write - attestations: write - id-token: write - actions: write - steps: - - uses: actions/checkout@v4 - - name: Login to ghcr.io - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build image and push to registry - uses: docker/build-push-action@v5 - with: - context: . - file: docker/prod/Dockerfile - platforms: linux/aarch64 - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64 - merge-manifest: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - attestations: write - id-token: write - actions: write - needs: [amd64, aarch64] - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ghcr.io - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Create & publish manifest - run: | - docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }} - - uses: sarisia/actions-status-discord@v1 - if: always() - with: - webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a3e0e538c..80ec0614e9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,19 +6,16 @@ You can ask for guidance anytime on our [Discord server](https://coollabs.io/dis ## Table of Contents -- [Contributing to Coolify](#contributing-to-coolify) - - [Table of Contents](#table-of-contents) - - [1. Setup Development Environment](#1-setup-development-environment) - - [2. Verify Installation (Optional)](#2-verify-installation-optional) - - [3. Fork and Setup Local Repository](#3-fork-and-setup-local-repository) - - [4. Set up Environment Variables](#4-set-up-environment-variables) - - [5. Start Coolify](#5-start-coolify) - - [6. Start Development](#6-start-development) - - [7. Development Notes](#7-development-notes) - - [8. Create a Pull Request](#8-create-a-pull-request) - - [Additional Contribution Guidelines](#additional-contribution-guidelines) - - [Contributing a New Service](#contributing-a-new-service) - - [Contributing to Documentation](#contributing-to-documentation) +1. [Setup Development Environment](#1-setup-development-environment) +2. [Verify Installation](#2-verify-installation-optional) +3. [Fork and Setup Local Repository](#3-fork-and-setup-local-repository) +4. [Set up Environment Variables](#4-set-up-environment-variables) +5. [Start Coolify](#5-start-coolify) +6. [Start Development](#6-start-development) +7. [Create a Pull Request](#7-create-a-pull-request) +8. [Development Notes](#development-notes) +9. [Resetting Development Environment](#resetting-development-environment) +10. [Additional Contribution Guidelines](#additional-contribution-guidelines) ## 1. Setup Development Environment @@ -29,15 +26,15 @@ Follow the steps below for your operating system: 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/) + - Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install?ref=coolify) + - After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/?ref=coolify) - 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/) + - Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/?ref=coolify) - 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) + - 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?ref=coolify) @@ -46,12 +43,12 @@ Follow the steps below for your operating system: 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) + - Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation?ref=coolify) - Docker Desktop: - - Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/) + - Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/?ref=coolify) 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) + - 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?ref=coolify) @@ -60,12 +57,12 @@ Follow the steps below for your operating system: 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 + - Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/?ref=coolify) 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/) + - If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/?ref=coolify) 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) + - 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?ref=coolify) @@ -89,14 +86,14 @@ After installing Docker (or Orbstack) and Spin, verify the installation: | Editor | Platform | Download Link | |--------|----------|---------------| - | Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download) | - | Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/) | - | Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download) | + | Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download?ref=coolify) | + | Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/?ref=coolify) | + | Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download?ref=coolify) | 3. Clone the Coolify Repository from your fork to your local machine - Use `git clone` in the command line, or - Use GitHub Desktop (recommended): - - Download and install from [https://desktop.github.com/](https://desktop.github.com/) + - Download and install from [https://desktop.github.com/](https://desktop.github.com/?ref=coolify) - 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` @@ -149,26 +146,7 @@ After installing Docker (or Orbstack) and Spin, verify the installation: > 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 environment-specific issues. - -> [!IMPORTANT] -> Forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches. - -## 8. Create a Pull Request +## 7. Create a Pull Request 1. After making changes or adding a new service: - Commit your changes to your forked repository. @@ -183,8 +161,7 @@ When working on Coolify, keep the following in mind: 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". + - Use the Pull Request Template provided and fill in the details. > [!IMPORTANT] > Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch. @@ -198,6 +175,61 @@ When working on Coolify, keep the following in mind: After submission, maintainers will review your PR and may request changes or provide feedback. +## 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 environment-specific issues. + +> [!IMPORTANT] +> Forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches. + +## Resetting Development Environment + +If you encounter issues or break your database or something else, follow these steps to start from a clean slate (works since `v4.0.0-beta.342`): + +1. Stop all running containers `ctrl + c`. + +2. Remove all Coolify containers: + ```bash + docker rm coolify coolify-db coolify-redis coolify-realtime coolify-testing-host coolify-minio coolify-vite-1 coolify-mail + ``` + +3. Remove Coolify volumes (it is possible that the volumes have no `coolify` prefix on your machine, in that case remove the prefix from the command): + ```bash + docker volume rm coolify_dev_backups_data coolify_dev_postgres_data coolify_dev_redis_data coolify_dev_coolify_data coolify_dev_minio_data + ``` + +4. Remove unused images: + ```bash + docker image prune -a + ``` + +5. Start Coolify again: + ```bash + spin up + ``` + +6. Run database migrations and seeders: + ```bash + docker exec -it coolify php artisan migrate:fresh --seed + ``` + +After completing these steps, you'll have a fresh development setup. + +> [!IMPORTANT] +> Always run database migrations and seeders after switching branches or pulling updates to ensure your local database structure matches the current codebase and includes necessary seed data. + ## Additional Contribution Guidelines ### Contributing a New Service diff --git a/RELEASE.md b/RELEASE.md index 2cb96b72bb..d9f05f17d7 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,35 +2,120 @@ This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed. +## Table of Contents +- [Release Process](#release-process) +- [Version Types](#version-types) + - [Stable](#stable) + - [Nightly](#nightly) + - [Beta](#beta) +- [Version Availability](#version-availability) + - [Self-Hosted](#self-hosted) + - [Cloud](#cloud) +- [Manually Update to Specific Versions](#manually-update-to-specific-versions) + ## Release Process -1. **Development on `next` or separate branches** - - Changes, fixes and new features are developed on the `next` or even separate branches. +1. **Development on `next` or Feature Branches** + - Improvements, fixes, and new features are developed on the `next` branch or separate feature branches. 2. **Merging to `main`** - - Once changes are ready, they are merged from `next` into the `main` branch. + - Once ready, changes are merged from the `next` branch into the `main` branch. -3. **Building the release** - - After merging to `main`, a new release is built. - - Note: A push to `main` does not automatically mean a new version is released. +3. **Building the Release** + - After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry with the version tag and the `latest` tag. -4. **Creating a GitHub release** - - A new release is created on GitHub with the new version details. +4. **Creating a GitHub Release** + - A new GitHub release is manually created with details of the changes made in the version. 5. **Updating the CDN** - - The final step is updating the version information on the CDN: - [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json) + - To make a new version publicly available, the version information on the CDN needs to be updated: [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json) > [!NOTE] -> The CDN update may not occur immediately after the GitHub release. It can happen hours or even days later due to additional testing, stability checks, or potential hotfixes. - +> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated.** + +## Version Types + +
+ Stable (coming soon) + +- **Stable** + - The production version suitable for stable, production environments (generally recommended). + - **Update Frequency:** Every 2 to 4 weeks, with more frequent possible hotfixes. + - **Release Size:** Larger but less frequent releases. Multiple nightly versions are consolidated into a single stable release. + - **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`). + - **Installation Command:** + ```bash + curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash + ``` + +
+ +
+ Nightly + +- **Nightly** + - The latest development version, suitable for testing the latest changes and experimenting with new features. + - **Update Frequency:** Daily or bi-weekly updates. + - **Release Size:** Smaller, more frequent releases. + - **Versioning Scheme:** TO BE DETERMINED + - **Installation Command:** + ```bash + curl -fsSL https://cdn.coollabs.io/coolify-nightly/install.sh | bash -s next + ``` + +
+ +
+ Beta + +- **Beta** + - Test releases for the upcoming stable version. + - **Purpose:** Allows users to test and provide feedback on new features and changes before they become stable. + - **Update Frequency:** Available if we think beta testing is necessary. + - **Release Size:** Same size as stable release as it will become the next stabe release after some time. + - **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`). + - **Installation Command:** + ```bash + curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash + ``` + +
+ +> [!WARNING] +> Do not use nightly/beta builds in production as there is no guarantee of stability. ## Version Availability -It's important to understand that a new version released on GitHub may not immediately become available for users to update (through manual or auto-update). +When a new version is released and a new GitHub release is created, it doesn't immediately become available for your instance. Here's how version availability works for different instance types. + +### Self-Hosted + +- **Update Frequency:** More frequent updates, especially on the nightly release channel. +- **Update Availability:** New versions are available once the CDN has been updated. +- **Update Methods:** + 1. **Manual Update in Instance Settings:** + - Go to `Settings > Update Check Frequency` and click the `Check Manually` button. + - If an update is available, an upgrade button will appear on the sidebar. + 2. **Automatic Update:** + - If enabled, the instance will update automatically at the time set in the settings. + 3. **Re-run Installation Script:** + - Run the installation script again to upgrade to the latest version available on the CDN: + ```bash + curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash + ``` + +> [!IMPORTANT] +> If a new release is available on GitHub but your instance hasn't updated yet or no upgrade button is shown in the UI, the CDN might not have been updated yet. This intentional delay ensures stability and allows for hotfixes before official release. + +### Cloud + +- **Update Frequency:** Less frequent as it's a managed service. +- **Update Availability:** New versions are available once Andras has updated the cloud version manually. +- **Update Method:** + - Updates are managed by Andras, who ensures each cloud version is thoroughly tested and stable before releasing it. > [!IMPORTANT] -> If you see a new release on GitHub but haven't received the update, it's likely because the CDN hasn't been updated yet. This is intentional and ensures stability and allows for hotfixes before the new version is officially released. +> The cloud version of Coolify may be several versions behind the latest GitHub releases even if the CDN is updated. This is intentional to ensure stability and reliability for cloud users and Andras will manully update the cloud version when the update is ready. ## Manually Update to Specific Versions @@ -42,4 +127,4 @@ To update your Coolify instance to a specific (unreleased) version, use the foll ```bash curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s ``` --> Replace `` with the version you want to update to (for example `4.0.0-beta.332`). +Replace `` with the version you want to update to (for example `4.0.0-beta.332`). diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index 0779da31de..ed563eaae9 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -651,8 +651,9 @@ private function old_way() // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); } - // Check if proxy is running - $this->server->proxyType(); + if (! $this->server->proxySet() || $this->server->proxy->force_stop) { + return; + } $foundProxyContainer = $this->containers->filter(function ($value, $key) { if ($this->server->isSwarm()) { return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik'; diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index f025e56619..4ef9618d0c 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -35,7 +35,7 @@ public function handle(Server $server, bool $async = true, bool $force = false): "echo 'Creating required Docker Compose file.'", "echo 'Starting coolify-proxy.'", 'docker stack deploy -c docker-compose.yml coolify-proxy', - "echo 'Proxy started successfully.'", + "echo 'Successfully started coolify-proxy.'", ]); } else { $caddfile = 'import /dynamic/*.caddy'; @@ -46,12 +46,14 @@ public function handle(Server $server, bool $async = true, bool $force = false): "echo 'Creating required Docker Compose file.'", "echo 'Pulling docker image.'", 'docker compose pull', - "echo 'Stopping existing coolify-proxy.'", - 'docker stop -t 10 coolify-proxy || true', - 'docker rm coolify-proxy || true', + 'if docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then', + " echo 'Stopping and removing existing coolify-proxy.'", + ' docker rm -f coolify-proxy || true', + " echo 'Successfully stopped and removed existing coolify-proxy.'", + 'fi', "echo 'Starting coolify-proxy.'", 'docker compose up -d --remove-orphans', - "echo 'Proxy started successfully.'", + "echo 'Successfully started coolify-proxy.'", ]); $commands = $commands->merge(connectProxyToNetworks($server)); } diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php index 1034c13d68..19bb033835 100644 --- a/app/Actions/Server/CleanupDocker.php +++ b/app/Actions/Server/CleanupDocker.php @@ -11,29 +11,30 @@ class CleanupDocker use AsAction; public function handle(Server $server) - { - - $commands = $this->getCommands(); - - foreach ($commands as $command) { - instant_remote_process([$command], $server, false); - } - } - - private function getCommands(): array { $settings = InstanceSettings::get(); $helperImageVersion = data_get($settings, 'helper_version'); $helperImage = config('coolify.helper_image'); - $helperImageWithVersion = config('coolify.helper_image').':'.$helperImageVersion; + $helperImageWithVersion = "$helperImage:$helperImageVersion"; - $commonCommands = [ - 'docker container prune -f --filter "label=coolify.managed=true"', + $commands = [ + 'docker container prune -f --filter "label=coolify.managed=true" --filter "label!=coolify.proxy=true"', 'docker image prune -af --filter "label!=coolify.managed=true"', 'docker builder prune -af', - "docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi", + "docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f", ]; - return $commonCommands; + $serverSettings = $server->settings; + if ($serverSettings->delete_unused_volumes) { + $commands[] = 'docker volume prune -af'; + } + + if ($serverSettings->delete_unused_networks) { + $commands[] = 'docker network prune -f'; + } + + foreach ($commands as $command) { + instant_remote_process([$command], $server, false); + } } } diff --git a/app/Enums/ContainerStatusTypes.php b/app/Enums/ContainerStatusTypes.php new file mode 100644 index 0000000000..ffcb6d5b59 --- /dev/null +++ b/app/Enums/ContainerStatusTypes.php @@ -0,0 +1,14 @@ +server->isFunctional()) { return; } - if ($this->server->settings->force_docker_cleanup) { - Log::info('DockerCleanupJob force cleanup on '.$this->server->name); + + if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) { + Log::info('DockerCleanupJob '.($this->manualCleanup ? 'manual' : 'force').' cleanup on '.$this->server->name); CleanupDocker::run(server: $this->server); return; diff --git a/app/Jobs/ServerCheckJob.php b/app/Jobs/ServerCheckJob.php index 7fde44f499..39d4aa0c02 100644 --- a/app/Jobs/ServerCheckJob.php +++ b/app/Jobs/ServerCheckJob.php @@ -69,7 +69,9 @@ public function handle() return 'No containers found.'; } GetContainersStatus::run($this->server, $this->containers, $containerReplicates); - $this->checkLogDrainContainer(); + if ($this->server->isLogDrainEnabled()) { + $this->checkLogDrainContainer(); + } } } catch (\Throwable $e) { @@ -115,9 +117,6 @@ private function serverStatus() private function checkLogDrainContainer() { - if (! $this->server->isLogDrainEnabled()) { - return; - } $foundLogDrainContainer = $this->containers->filter(function ($value, $key) { return data_get($value, 'Name') === '/coolify-log-drain'; })->first(); diff --git a/app/Jobs/ServerLimitCheckJob.php b/app/Jobs/ServerLimitCheckJob.php index b2c816f5d0..1f09d5a3b5 100644 --- a/app/Jobs/ServerLimitCheckJob.php +++ b/app/Jobs/ServerLimitCheckJob.php @@ -10,7 +10,6 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\Middleware\; use Illuminate\Queue\SerializesModels; class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php index ec87beead4..98b2b42632 100644 --- a/app/Livewire/Project/Database/BackupEdit.php +++ b/app/Livewire/Project/Database/BackupEdit.php @@ -182,7 +182,7 @@ public function render() { return view('livewire.project.database.backup-edit', [ 'checkboxes' => [ - ['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from local storage.'], + ['id' => 'delete_associated_backups_locally', 'label' => __('database.delete_backups_locally')], // ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected S3 Storage.'] // ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.'] ], diff --git a/app/Livewire/Project/Database/Heading.php b/app/Livewire/Project/Database/Heading.php index 765213f609..49884ff9af 100644 --- a/app/Livewire/Project/Database/Heading.php +++ b/app/Livewire/Project/Database/Heading.php @@ -78,7 +78,7 @@ public function render() { return view('livewire.project.database.heading', [ 'checkboxes' => [ - ['id' => 'docker_cleanup', 'label' => 'Cleanup docker build cache and unused images (next deployment could take longer).'], + ['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')], ], ]); } diff --git a/app/Livewire/Security/ApiTokens.php b/app/Livewire/Security/ApiTokens.php index ff8679d21e..40752630e0 100644 --- a/app/Livewire/Security/ApiTokens.php +++ b/app/Livewire/Security/ApiTokens.php @@ -2,6 +2,7 @@ namespace App\Livewire\Security; +use App\Models\InstanceSettings; use Livewire\Component; class ApiTokens extends Component @@ -16,6 +17,8 @@ class ApiTokens extends Component public array $permissions = ['read-only']; + public $isApiEnabled; + public function render() { return view('livewire.security.api-tokens'); @@ -23,6 +26,7 @@ public function render() public function mount() { + $this->isApiEnabled = InstanceSettings::get()->is_api_enabled; $this->tokens = auth()->user()->tokens->sortByDesc('created_at'); } diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index 3cb3305b52..c4f25c79d5 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -4,6 +4,7 @@ use App\Actions\Server\StartSentinel; use App\Actions\Server\StopSentinel; +use App\Jobs\DockerCleanupJob; use App\Jobs\PullSentinelImageJob; use App\Models\Server; use Livewire\Component; @@ -24,6 +25,10 @@ class Form extends Component public $timezones; + public $delete_unused_volumes = false; + + public $delete_unused_networks = false; + public function getListeners() { $teamId = auth()->user()->currentTeam()->id; @@ -58,6 +63,8 @@ public function getListeners() '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', + 'server.settings.delete_unused_volumes' => 'boolean', + 'server.settings.delete_unused_networks' => 'boolean', ]; protected $validationAttributes = [ @@ -79,6 +86,8 @@ public function getListeners() 'server.settings.metrics_history_days' => 'Metrics History', 'server.settings.is_server_api_enabled' => 'Server API', 'server.settings.server_timezone' => 'Server Timezone', + 'server.settings.delete_unused_volumes' => 'Delete Unused Volumes', + 'server.settings.delete_unused_networks' => 'Delete Unused Networks', ]; public function mount(Server $server) @@ -88,6 +97,8 @@ public function mount(Server $server) $this->wildcard_domain = $this->server->settings->wildcard_domain; $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold; $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency; + $this->server->settings->delete_unused_volumes = $server->settings->delete_unused_volumes; + $this->server->settings->delete_unused_networks = $server->settings->delete_unused_networks; } public function updated($field) @@ -137,6 +148,7 @@ public function instantSave() try { refresh_server_connection($this->server->privateKey); $this->validateServer(false); + $this->server->settings->save(); $this->server->save(); $this->dispatch('success', 'Server updated.'); @@ -154,6 +166,7 @@ public function instantSave() ray('Sentinel is not enabled'); StopSentinel::dispatch($this->server); } + $this->server->settings->save(); // $this->checkPortForServerApi(); } catch (\Throwable $e) { @@ -234,9 +247,9 @@ public function submit() $this->server->settings->server_timezone = $newTimezone; $this->server->settings->save(); } - $this->server->settings->save(); $this->server->save(); + $this->dispatch('success', 'Server updated.'); } catch (\Throwable $e) { return handleError($e, $this); @@ -250,6 +263,16 @@ public function updatedServerSettingsServerTimezone($value) $this->dispatch('success', 'Server timezone updated.'); } + public function manualCleanup() + { + try { + DockerCleanupJob::dispatch($this->server, true); + $this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function manualCloudflareConfig() { $this->server->settings->is_cloudflare_tunnel = true; diff --git a/app/Livewire/Server/Proxy/Status.php b/app/Livewire/Server/Proxy/Status.php index d23d7fc20e..20db4dad4f 100644 --- a/app/Livewire/Server/Proxy/Status.php +++ b/app/Livewire/Server/Proxy/Status.php @@ -49,6 +49,10 @@ public function checkProxy(bool $notification = false) if ($this->server->proxy->status === 'running') { $this->polling = false; $notification && $this->dispatch('success', 'Proxy is running.'); + } elseif ($this->server->proxy->status === 'exited' and ! $this->server->proxy->force_stop) { + $notification && $this->dispatch('error', 'Proxy has exited.'); + } elseif ($this->server->proxy->force_stop) { + $notification && $this->dispatch('error', 'Proxy is stopped manually.'); } else { $notification && $this->dispatch('error', 'Proxy is not running.'); } diff --git a/app/Models/Server.php b/app/Models/Server.php index adc8aa7e44..a1b4c08289 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -36,6 +36,8 @@ 'validation_logs' => ['type' => 'string'], 'log_drain_notification_sent' => ['type' => 'boolean'], 'swarm_cluster' => ['type' => 'string'], + 'delete_unused_volumes' => ['type' => 'boolean'], + 'delete_unused_networks' => ['type' => 'boolean'], ] )] @@ -105,6 +107,8 @@ protected static function booted() 'proxy' => SchemalessAttributes::class, 'logdrain_axiom_api_key' => 'encrypted', 'logdrain_newrelic_license_key' => 'encrypted', + 'delete_unused_volumes' => 'boolean', + 'delete_unused_networks' => 'boolean', ]; protected $schemalessAttributes = [ @@ -1048,6 +1052,10 @@ public function validateConnection($isManualCheck = true) return ['uptime' => false, 'error' => 'Server skipped.']; } try { + // Make sure the private key is stored + if ($server->privateKey) { + $server->privateKey->storeInFileSystem(); + } instant_remote_process(['ls /'], $server); $server->settings()->update([ 'is_reachable' => true, diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php index 6c9378cacd..fbd7b0b158 100644 --- a/app/View/Components/Forms/Input.php +++ b/app/View/Components/Forms/Input.php @@ -22,6 +22,7 @@ public function __construct( public bool $allowToPeak = true, public bool $isMultiline = false, public string $defaultClass = 'input', + public string $autocomplete = 'off', ) {} public function render(): View|Closure|string diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index c4c15b8fe4..5d1ad5390d 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -96,6 +96,8 @@ function connectProxyToNetworks(Server $server) "echo 'Connecting coolify-proxy to $network network...'", "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --driver overlay --attachable $network >/dev/null", "docker network connect $network coolify-proxy >/dev/null 2>&1 || true", + "echo 'Successfully connected coolify-proxy to $network network.'", + "echo 'Proxy started and configured successfully!'", ]; }); } else { @@ -104,6 +106,8 @@ function connectProxyToNetworks(Server $server) "echo 'Connecting coolify-proxy to $network network...'", "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null", "docker network connect $network coolify-proxy >/dev/null 2>&1 || true", + "echo 'Successfully connected coolify-proxy to $network network.'", + "echo 'Proxy started and configured successfully!'", ]; }); } @@ -144,6 +148,7 @@ function generate_default_proxy_configuration(Server $server) 'traefik.http.routers.traefik.service=api@internal', 'traefik.http.services.traefik.loadbalancer.server.port=8080', 'coolify.managed=true', + 'coolify.proxy=true', ]; $config = [ 'networks' => $array_of_networks->toArray(), @@ -217,7 +222,6 @@ function generate_default_proxy_configuration(Server $server) } } elseif ($proxy_type === 'CADDY') { $config = [ - 'version' => '3.8', 'networks' => $array_of_networks->toArray(), 'services' => [ 'caddy' => [ @@ -236,12 +240,9 @@ function generate_default_proxy_configuration(Server $server) '80:80', '443:443', ], - // "healthcheck" => [ - // "test" => "wget -qO- http://localhost:80|| exit 1", - // "interval" => "4s", - // "timeout" => "2s", - // "retries" => 5, - // ], + 'labels' => [ + 'coolify.managed=true', + ], 'volumes' => [ '/var/run/docker.sock:/var/run/docker.sock:ro', "{$proxy_path}/dynamic:/dynamic", diff --git a/config/sentry.php b/config/sentry.php index 60f866b7b1..1e83eae9e5 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.346', + 'release' => '4.0.0-beta.347', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index cbee66d3e8..6e7f911aef 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ boolean('delete_unused_volumes')->default(false); + $table->boolean('delete_unused_networks')->default(false); + }); + } + + public function down() + { + Schema::table('server_settings', function (Blueprint $table) { + $table->dropColumn('delete_unused_volumes'); + $table->dropColumn('delete_unused_networks'); + }); + } +}; diff --git a/database/migrations/2024_09_26_083441_disable_api_by_default.php b/database/migrations/2024_09_26_083441_disable_api_by_default.php new file mode 100644 index 0000000000..d0803f751d --- /dev/null +++ b/database/migrations/2024_09_26_083441_disable_api_by_default.php @@ -0,0 +1,18 @@ +boolean('is_api_enabled')->default(false)->change(); + }); + } +}; diff --git a/database/seeders/ApplicationPreviewSeeder.php b/database/seeders/ApplicationPreviewSeeder.php deleted file mode 100644 index 7649390736..0000000000 --- a/database/seeders/ApplicationPreviewSeeder.php +++ /dev/null @@ -1,20 +0,0 @@ - $application_1->id, - // 'pull_request_id' => 1 - // ]); - } -} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 874762aef3..be50831087 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -17,22 +17,14 @@ public function run(): void ServerSeeder::class, ServerSettingSeeder::class, ProjectSeeder::class, - ProjectSettingSeeder::class, - EnvironmentSeeder::class, StandaloneDockerSeeder::class, - SwarmDockerSeeder::class, - KubernetesSeeder::class, GithubAppSeeder::class, GitlabAppSeeder::class, ApplicationSeeder::class, ApplicationSettingsSeeder::class, - ApplicationPreviewSeeder::class, - EnvironmentVariableSeeder::class, LocalPersistentVolumeSeeder::class, S3StorageSeeder::class, StandalonePostgresqlSeeder::class, - ScheduledDatabaseBackupSeeder::class, - ScheduledDatabaseBackupExecutionSeeder::class, OauthSettingSeeder::class, ]); } diff --git a/database/seeders/EnvironmentSeeder.php b/database/seeders/EnvironmentSeeder.php deleted file mode 100644 index 1c6d562a9f..0000000000 --- a/database/seeders/EnvironmentSeeder.php +++ /dev/null @@ -1,13 +0,0 @@ - 'NODE_ENV', - // 'value' => 'production', - // 'is_build_time' => true, - // 'application_id' => 1, - // ]); - } -} diff --git a/database/seeders/GitSeeder.php b/database/seeders/GitSeeder.php deleted file mode 100644 index c8dc3ab6d3..0000000000 --- a/database/seeders/GitSeeder.php +++ /dev/null @@ -1,26 +0,0 @@ - 'https://api.github.com', - // 'html_url' => 'https://github.com', - // 'is_public' => false, - // 'private_key_id' => $private_key_1->id, - // 'project_id' => $project->id, - // ]); - } -} diff --git a/database/seeders/KubernetesSeeder.php b/database/seeders/KubernetesSeeder.php deleted file mode 100644 index f6a852e050..0000000000 --- a/database/seeders/KubernetesSeeder.php +++ /dev/null @@ -1,16 +0,0 @@ -settings->wildcard_domain = 'wildcard.example.com'; - // $first_project->settings->save(); - } -} diff --git a/database/seeders/ScheduledDatabaseBackupExecutionSeeder.php b/database/seeders/ScheduledDatabaseBackupExecutionSeeder.php deleted file mode 100644 index 7e4c337647..0000000000 --- a/database/seeders/ScheduledDatabaseBackupExecutionSeeder.php +++ /dev/null @@ -1,28 +0,0 @@ - 'success', - // 'message' => 'Backup created successfully.', - // 'size' => '10243467789556', - // 'scheduled_database_backup_id' => 1, - // ]); - // ScheduledDatabaseBackupExecution::create([ - // 'status' => 'failed', - // 'message' => 'Backup failed.', - // 'size' => '10243456', - // 'scheduled_database_backup_id' => 1, - // ]); - } -} diff --git a/database/seeders/ScheduledDatabaseBackupSeeder.php b/database/seeders/ScheduledDatabaseBackupSeeder.php deleted file mode 100644 index fefbada0dd..0000000000 --- a/database/seeders/ScheduledDatabaseBackupSeeder.php +++ /dev/null @@ -1,33 +0,0 @@ - true, - // 'frequency' => '* * * * *', - // 'number_of_backups_locally' => 2, - // 'database_id' => 1, - // 'database_type' => 'App\Models\StandalonePostgresql', - // 's3_storage_id' => 1, - // 'team_id' => 0, - // ]); - // ScheduledDatabaseBackup::create([ - // 'enabled' => true, - // 'frequency' => '* * * * *', - // 'number_of_backups_locally' => 3, - // 'database_id' => 1, - // 'database_type' => 'App\Models\StandalonePostgresql', - // 'team_id' => 0, - // ]); - } -} diff --git a/database/seeders/ServiceApplicationSeeder.php b/database/seeders/ServiceApplicationSeeder.php deleted file mode 100644 index 04648f83c9..0000000000 --- a/database/seeders/ServiceApplicationSeeder.php +++ /dev/null @@ -1,16 +0,0 @@ - 'Swarm Docker 1', - // 'server_id' => 1, - // ]); - } -} diff --git a/database/seeders/WaitlistSeeder.php b/database/seeders/WaitlistSeeder.php deleted file mode 100644 index de6837c60f..0000000000 --- a/database/seeders/WaitlistSeeder.php +++ /dev/null @@ -1,16 +0,0 @@ - @endif - merge(['class' => $defaultClass]) }} @required($required) + merge(['class' => $defaultClass]) }} @required($required) @if ($id !== 'null') wire:model={{ $id }} @endif wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300' wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled" @@ -35,7 +36,7 @@ class="flex absolute inset-y-0 right-0 items-center pr-2 cursor-pointer hover:da @else - merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly) @if ($id !== 'null') wire:model={{ $id }} @endif wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300' diff --git a/resources/views/livewire/project/database/heading.blade.php b/resources/views/livewire/project/database/heading.blade.php index 8265ca7725..e334b2ce99 100644 --- a/resources/views/livewire/project/database/heading.blade.php +++ b/resources/views/livewire/project/database/heading.blade.php @@ -1,7 +1,7 @@