diff --git a/.AI_INSTRUCTIONS_SYNC.md b/.AI_INSTRUCTIONS_SYNC.md new file mode 100644 index 0000000000..bbe0a90e1c --- /dev/null +++ b/.AI_INSTRUCTIONS_SYNC.md @@ -0,0 +1,156 @@ +# AI Instructions Synchronization Guide + +This document explains how AI instructions are organized and synchronized across different AI tools used with Coolify. + +## Overview + +Coolify maintains AI instructions in two parallel systems: + +1. **CLAUDE.md** - For Claude Code (claude.ai/code) +2. **.cursor/rules/** - For Cursor IDE and other AI assistants + +Both systems share core principles but are optimized for their respective workflows. + +## Structure + +### CLAUDE.md +- **Purpose**: Condensed, workflow-focused guide for Claude Code +- **Format**: Single markdown file +- **Includes**: + - Quick-reference development commands + - High-level architecture overview + - Core patterns and guidelines + - Embedded Laravel Boost guidelines + - References to detailed .cursor/rules/ documentation + +### .cursor/rules/ +- **Purpose**: Detailed, topic-specific documentation +- **Format**: Multiple .mdc files organized by topic +- **Structure**: + - `README.mdc` - Main index and overview + - `cursor_rules.mdc` - Maintenance guidelines + - Topic-specific files (testing-patterns.mdc, security-patterns.mdc, etc.) +- **Used by**: Cursor IDE, Claude Code (for detailed reference), other AI assistants + +## Cross-References + +Both systems reference each other: + +- **CLAUDE.md** → references `.cursor/rules/` for detailed documentation +- **.cursor/rules/README.mdc** → references `CLAUDE.md` for Claude Code workflow +- **.cursor/rules/cursor_rules.mdc** → notes that changes should sync with CLAUDE.md + +## Maintaining Consistency + +When updating AI instructions, follow these guidelines: + +### 1. Core Principles (MUST be consistent) +- Laravel version (currently Laravel 12) +- PHP version (8.4) +- Testing execution rules (Docker for Feature tests, mocking for Unit tests) +- Security patterns and authorization requirements +- Code style requirements (Pint, PSR-12) + +### 2. Where to Make Changes + +**For workflow changes** (how to run commands, development setup): +- Primary: `CLAUDE.md` +- Secondary: `.cursor/rules/development-workflow.mdc` + +**For architectural patterns** (how code should be structured): +- Primary: `.cursor/rules/` topic files +- Secondary: Reference in `CLAUDE.md` "Additional Documentation" section + +**For testing patterns**: +- Both: Must be synchronized +- `CLAUDE.md` - Contains condensed testing execution rules +- `.cursor/rules/testing-patterns.mdc` - Contains detailed examples and patterns + +### 3. Update Checklist + +When making significant changes: + +- [ ] Identify if change affects core principles (version numbers, critical patterns) +- [ ] Update primary location (CLAUDE.md or .cursor/rules/) +- [ ] Check if update affects cross-referenced content +- [ ] Update secondary location if needed +- [ ] Verify cross-references are still accurate +- [ ] Run: `./vendor/bin/pint CLAUDE.md .cursor/rules/*.mdc` (if applicable) + +### 4. Common Inconsistencies to Watch + +- **Version numbers**: Laravel, PHP, package versions +- **Testing instructions**: Docker execution requirements +- **File paths**: Ensure relative paths work from root +- **Command syntax**: Docker commands, artisan commands +- **Architecture decisions**: Laravel 10 structure vs Laravel 12+ structure + +## File Organization + +``` +/ +├── CLAUDE.md # Claude Code instructions (condensed) +├── .AI_INSTRUCTIONS_SYNC.md # This file +└── .cursor/ + └── rules/ + ├── README.mdc # Index and overview + ├── cursor_rules.mdc # Maintenance guide + ├── testing-patterns.mdc # Testing details + ├── development-workflow.mdc # Dev setup details + ├── security-patterns.mdc # Security details + ├── application-architecture.mdc + ├── deployment-architecture.mdc + ├── database-patterns.mdc + ├── frontend-patterns.mdc + ├── api-and-routing.mdc + ├── form-components.mdc + ├── technology-stack.mdc + ├── project-overview.mdc + └── laravel-boost.mdc # Laravel-specific patterns +``` + +## Recent Updates + +### 2025-10-07 +- ✅ Added cross-references between CLAUDE.md and .cursor/rules/ +- ✅ Synchronized Laravel version (12) across all files +- ✅ Added comprehensive testing execution rules (Docker for Feature tests) +- ✅ Added test design philosophy (prefer mocking over database) +- ✅ Fixed inconsistencies in testing documentation +- ✅ Created this synchronization guide + +## Maintenance Commands + +```bash +# Check for version inconsistencies +grep -r "Laravel [0-9]" CLAUDE.md .cursor/rules/*.mdc + +# Check for PHP version consistency +grep -r "PHP [0-9]" CLAUDE.md .cursor/rules/*.mdc + +# Format all documentation +./vendor/bin/pint CLAUDE.md .cursor/rules/*.mdc + +# Search for specific patterns across all docs +grep -r "pattern_to_check" CLAUDE.md .cursor/rules/ +``` + +## Contributing + +When contributing documentation: + +1. Check both CLAUDE.md and .cursor/rules/ for existing documentation +2. Add to appropriate location(s) based on guidelines above +3. Add cross-references if creating new patterns +4. Update this file if changing organizational structure +5. Verify consistency before submitting PR + +## Questions? + +If unsure about where to document something: + +- **Quick reference / workflow** → CLAUDE.md +- **Detailed patterns / examples** → .cursor/rules/[topic].mdc +- **Both?** → Start with .cursor/rules/, then reference in CLAUDE.md + +When in doubt, prefer detailed documentation in .cursor/rules/ and concise references in CLAUDE.md. diff --git a/.cursor/rules/README.mdc b/.cursor/rules/README.mdc index 07f19a8166..d0597bb72d 100644 --- a/.cursor/rules/README.mdc +++ b/.cursor/rules/README.mdc @@ -9,6 +9,10 @@ alwaysApply: false This comprehensive set of Cursor Rules provides deep insights into **Coolify**, an open-source self-hostable alternative to Heroku/Netlify/Vercel. These rules will help you understand, navigate, and contribute to this complex Laravel-based deployment platform. +> **Cross-Reference**: This directory is for **detailed, topic-specific rules** used by Cursor IDE and other AI assistants. For Claude Code specifically, also see **[CLAUDE.md](mdc:CLAUDE.md)** which provides a condensed, workflow-focused guide. Both systems share core principles but are optimized for their respective tools. +> +> **Maintaining Rules**: When updating these rules, see **[.AI_INSTRUCTIONS_SYNC.md](mdc:.AI_INSTRUCTIONS_SYNC.md)** for synchronization guidelines to keep CLAUDE.md and .cursor/rules/ consistent. + ## Rule Categories ### 🏗️ Architecture & Foundation @@ -71,7 +75,7 @@ Coolify uses a **team-based multi-tenancy** model where: - **Multi-server** support with SSH connections ### 3. Technology Stack -- **Backend**: Laravel 11 + PHP 8.4 +- **Backend**: Laravel 12 + PHP 8.4 - **Frontend**: Livewire 3.5 + Alpine.js + Tailwind CSS 4.1 - **Database**: PostgreSQL 15 + Redis 7 - **Containerization**: Docker + Docker Compose diff --git a/.cursor/rules/cursor_rules.mdc b/.cursor/rules/cursor_rules.mdc index 7dfae3de0f..9edccd496f 100644 --- a/.cursor/rules/cursor_rules.mdc +++ b/.cursor/rules/cursor_rules.mdc @@ -4,6 +4,12 @@ globs: .cursor/rules/*.mdc alwaysApply: true --- +# Cursor Rules Maintenance Guide + +> **Important**: These rules in `.cursor/rules/` are shared between Cursor IDE and other AI assistants. Changes here should be reflected in **[CLAUDE.md](mdc:CLAUDE.md)** when they affect core workflows or patterns. +> +> **Synchronization Guide**: See **[.AI_INSTRUCTIONS_SYNC.md](mdc:.AI_INSTRUCTIONS_SYNC.md)** for detailed guidelines on maintaining consistency between CLAUDE.md and .cursor/rules/. + - **Required Rule Structure:** ```markdown --- diff --git a/.cursor/rules/database-patterns.mdc b/.cursor/rules/database-patterns.mdc index a4f65f5fbd..ec60a43b39 100644 --- a/.cursor/rules/database-patterns.mdc +++ b/.cursor/rules/database-patterns.mdc @@ -142,6 +142,29 @@ Schema::create('applications', function (Blueprint $table) { - **Soft deletes** for audit trails - **Activity logging** with Spatie package +### **CRITICAL: Mass Assignment Protection** +**When adding new database columns, you MUST update the model's `$fillable` array.** Without this, Laravel will silently ignore mass assignment operations like `Model::create()` or `$model->update()`. + +**Checklist for new columns:** +1. ✅ Create migration file +2. ✅ Run migration +3. ✅ **Add column to model's `$fillable` array** +4. ✅ Update any Livewire components that sync this property +5. ✅ Test that the column can be read and written + +**Example:** +```php +class Server extends BaseModel +{ + protected $fillable = [ + 'name', + 'ip', + 'port', + 'is_validating', // ← MUST add new columns here + ]; +} +``` + ### Relationship Patterns ```php // Typical relationship structure in Application model diff --git a/.cursor/rules/laravel-boost.mdc b/.cursor/rules/laravel-boost.mdc index 005ede8492..c409a46472 100644 --- a/.cursor/rules/laravel-boost.mdc +++ b/.cursor/rules/laravel-boost.mdc @@ -185,7 +185,7 @@ protected function isAccessible(User $user, ?string $path = null): bool ### Database - When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost. -- Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. +- Laravel 12 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. ### Models - Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. diff --git a/.cursor/rules/testing-patterns.mdc b/.cursor/rules/testing-patterns.mdc index a0e64dbae2..8d250b56a1 100644 --- a/.cursor/rules/testing-patterns.mdc +++ b/.cursor/rules/testing-patterns.mdc @@ -5,11 +5,56 @@ alwaysApply: false --- # Coolify Testing Architecture & Patterns +> **Cross-Reference**: These detailed testing patterns align with the testing guidelines in **[CLAUDE.md](mdc:CLAUDE.md)**. Both documents share the same core principles about Docker execution and mocking preferences. + ## Testing Philosophy Coolify employs **comprehensive testing strategies** using modern PHP testing frameworks to ensure reliability of deployment operations, infrastructure management, and user interactions. -!Important: Always run tests inside `coolify` container. +### Test Execution Rules + +**CRITICAL**: Tests are categorized by database dependency: + +#### Unit Tests (`tests/Unit/`) +- **MUST NOT** use database connections +- **MUST** use mocking for models and external dependencies +- **CAN** run outside Docker: `./vendor/bin/pest tests/Unit` +- Purpose: Test isolated logic, helper functions, and business rules + +#### Feature Tests (`tests/Feature/`) +- **MAY** use database connections (factories, migrations, models) +- **MUST** run inside Docker container: `docker exec coolify php artisan test` +- **MUST** use `RefreshDatabase` trait if touching database +- Purpose: Test API endpoints, workflows, and integration scenarios + +**Rule of thumb**: If your test needs `Server::factory()->create()` or any database operation, it's a Feature test and MUST run in Docker. + +### Prefer Mocking Over Database + +When writing tests, always prefer mocking over real database operations: + +```php +// ❌ BAD: Unit test using database +it('extracts custom commands', function () { + $server = Server::factory()->create(['ip' => '1.2.3.4']); + $commands = extract_custom_proxy_commands($server, $yaml); + expect($commands)->toBeArray(); +}); + +// ✅ GOOD: Unit test using mocking +it('extracts custom commands', function () { + $server = Mockery::mock('App\Models\Server'); + $server->shouldReceive('proxyType')->andReturn('traefik'); + $commands = extract_custom_proxy_commands($server, $yaml); + expect($commands)->toBeArray(); +}); +``` + +**Design principles for testable code:** +- Use dependency injection instead of global state +- Create interfaces for external dependencies (SSH, Docker, etc.) +- Separate business logic from data persistence +- Make functions accept interfaces instead of concrete models when possible ## Testing Framework Stack diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index d73398046a..a2c92df597 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -34,13 +34,15 @@ jobs: - name: Run Claude Code Review id: claude-review - uses: anthropics/claude-code-action@v1 + uses: anthropics/claude-code-action@beta with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - prompt: | - REPO: ${{ github.repository }} - PR NUMBER: ${{ github.event.pull_request.number }} + # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1) + # model: "claude-opus-4-1-20250805" + + # Direct prompt for automated review (no @claude mention needed) + direct_prompt: | Please review this pull request and provide feedback on: - Code quality and best practices - Potential bugs or issues @@ -48,11 +50,30 @@ jobs: - Security concerns - Test coverage - Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. + Be constructive and helpful in your feedback. - Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. + # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR + # use_sticky_comment: true + + # Optional: Customize review based on file types + # direct_prompt: | + # Review this PR focusing on: + # - For TypeScript files: Type safety and proper interface usage + # - For API endpoints: Security, input validation, and error handling + # - For React components: Performance, accessibility, and best practices + # - For tests: Coverage, edge cases, and test quality + + # Optional: Different prompts for different authors + # direct_prompt: | + # ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' && + # 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' || + # 'Please provide a thorough code review focusing on our coding standards and best practices.' }} + + # Optional: Add specific tools for running tests or linting + # allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)" - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://docs.claude.com/en/docs/claude-code/sdk#command-line for available options - claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' + # Optional: Skip review for certain conditions + # if: | + # !contains(github.event.pull_request.title, '[skip-review]') && + # !contains(github.event.pull_request.title, '[WIP]') diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index e93c95bffd..9daf0e90e7 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -16,6 +16,8 @@ jobs: (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && github.event.action == 'labeled' && github.event.label.name == 'Claude') || + (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'Claude') || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest permissions: @@ -34,22 +36,30 @@ jobs: id: claude uses: anthropics/claude-code-action@v1 with: - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - # This is an optional setting that allows Claude to read CI results on PRs - additional_permissions: | - actions: read + anthropic_api_key: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + # This is an optional setting that allows Claude to read CI results on PRs additional_permissions: | actions: read - - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' - - # Optional: Add claude_args to customize behavior and configuration - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://docs.claude.com/en/docs/claude-code/sdk#command-line for available options - # claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)' - + + # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1) + # model: "claude-opus-4-1-20250805" + + # Optional: Customize the trigger phrase (default: @claude) + # trigger_phrase: "/claude" + + # Optional: Trigger when specific user is assigned to an issue + # assignee_trigger: "claude-bot" + + # Optional: Allow Claude to run specific commands + # allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)" + + # Optional: Add custom instructions for Claude to customize its behavior for your project + # custom_instructions: | + # Follow our coding standards + # Ensure all new code has tests + # Use TypeScript for new files + + # Optional: Custom environment variables for Claude + # claude_env: | + # NODE_ENV: test diff --git a/.github/workflows/coolify-staging-build.yml b/.github/workflows/coolify-staging-build.yml index 09b1e94214..c6aa2dd908 100644 --- a/.github/workflows/coolify-staging-build.yml +++ b/.github/workflows/coolify-staging-build.yml @@ -28,6 +28,13 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Sanitize branch name for Docker tag + id: sanitize + run: | + # Replace slashes and other invalid characters with dashes + SANITIZED_NAME=$(echo "${{ github.ref_name }}" | sed 's/[\/]/-/g') + echo "tag=${SANITIZED_NAME}" >> $GITHUB_OUTPUT + - name: Login to ${{ env.GITHUB_REGISTRY }} uses: docker/login-action@v3 with: @@ -50,8 +57,8 @@ jobs: platforms: linux/amd64 push: true tags: | - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} + ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }} + ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }} aarch64: runs-on: [self-hosted, arm64] @@ -61,6 +68,13 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Sanitize branch name for Docker tag + id: sanitize + run: | + # Replace slashes and other invalid characters with dashes + SANITIZED_NAME=$(echo "${{ github.ref_name }}" | sed 's/[\/]/-/g') + echo "tag=${SANITIZED_NAME}" >> $GITHUB_OUTPUT + - name: Login to ${{ env.GITHUB_REGISTRY }} uses: docker/login-action@v3 with: @@ -83,8 +97,8 @@ jobs: platforms: linux/aarch64 push: true tags: | - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 + ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }}-aarch64 + ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }}-aarch64 merge-manifest: runs-on: ubuntu-latest @@ -95,6 +109,13 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Sanitize branch name for Docker tag + id: sanitize + run: | + # Replace slashes and other invalid characters with dashes + SANITIZED_NAME=$(echo "${{ github.ref_name }}" | sed 's/[\/]/-/g') + echo "tag=${SANITIZED_NAME}" >> $GITHUB_OUTPUT + - uses: docker/setup-buildx-action@v3 - name: Login to ${{ env.GITHUB_REGISTRY }} @@ -114,14 +135,14 @@ jobs: - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }} run: | docker buildx imagetools create \ - --append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 \ - --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} + --append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }}-aarch64 \ + --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }} - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }} run: | docker buildx imagetools create \ - --append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 \ - --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} + --append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }}-aarch64 \ + --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }} - uses: sarisia/actions-status-discord@v1 if: always() diff --git a/CHANGELOG.md b/CHANGELOG.md index aefabfd29f..ea4510776b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -390,6896 +390,1288 @@ All notable changes to this project will be documented in this file. ## [4.0.0-beta.426] - 2025-08-28 -### 🚜 Refactor - -- *(policy)* Simplify ServiceDatabasePolicy methods to always return true and add manageBackups method - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- Update coolify version to 4.0.0-beta.426 and nightly version to 4.0.0-beta.427 - -## [4.0.0-beta.425] - 2025-08-28 - -### 🚀 Features - -- *(domains)* Implement domain conflict detection and user confirmation modal across application components -- *(domains)* Add force_domain_override option and enhance domain conflict detection responses - -### 🐛 Bug Fixes - -- *(previews)* Simplify FQDN generation logic by removing unnecessary empty check -- *(templates)* Update Matrix service compose configuration for improved compatibility and clarity - -### 🚜 Refactor - -- *(urls)* Replace generateFqdn with generateUrl for consistent URL generation across applications -- *(domains)* Rename check_domain_usage to checkDomainUsage and update references across the application -- *(auth)* Simplify access control logic in CanAccessTerminal and ServerPolicy by allowing all users to perform actions - -### 📚 Documentation - -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- Update coolify version to 4.0.0-beta.425 and nightly version to 4.0.0-beta.426 - -## [4.0.0-beta.424] - 2025-08-27 - -### 🐛 Bug Fixes - -- *(parsers)* Do not modify service names, only for getting fqdns and related envs -- *(compose)* Temporary allow to edit volumes in apps (compose based) and services - -### 📚 Documentation - -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- Update coolify version to 4.0.0-beta.424 and nightly version to 4.0.0-beta.425 - -## [4.0.0-beta.423] - 2025-08-27 - -### 🚜 Refactor - -- *(parsers)* Remove unnecessary hyphen-to-underscore replacement for service names in serviceParser function - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- Update coolify version to 4.0.0-beta.423 and nightly version to 4.0.0-beta.424 - -## [4.0.0-beta.422] - 2025-08-27 - -### 🐛 Bug Fixes - -- *(parsers)* Replace hyphens with underscores in service names for consistency. this allows to properly parse custom domains in docker compose based applications -- *(parsers)* Implement parseDockerVolumeString function to handle various Docker volume formats and modes, including environment variables and Windows paths. Add unit tests for comprehensive coverage. -- *(git)* Submodule update command uses an unsupported option (#6454) -- *(service)* Swap URL for FQDN on matrix template (#6466) -- *(parsers)* Enhance volume string handling by preserving mode in application and service parsers. Update related unit tests for validation. -- *(docker)* Update parser version in FQDN generation for service-specific URLs - -### 🚜 Refactor - -- *(git)* Improve submodule cloning - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- Update version -- Update development node version - -## [4.0.0-beta.421] - 2025-08-26 - -### 🚀 Features - -- *(policies)* Add EnvironmentVariablePolicy for managing environment variables ( it was missing ) - -### 🐛 Bug Fixes - -- *(backups)* Rollback helper update for now - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(core)* Update version -- *(versions)* Update coolify version to 4.0.0-beta.421 and nightly version to 4.0.0-beta.422 - -## [4.0.0-beta.420.9] - 2025-08-26 - -### 🐛 Bug Fixes - -- *(backups)* S3 backup upload is failing - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(core)* Update version - -## [4.0.0-beta.420.8] - 2025-08-26 - -### 🚜 Refactor - -- *(policies)* Remove Response type hint from update methods in ApplicationPreviewPolicy and DatabasePolicy for improved flexibility - -### 📚 Documentation - -- Update changelog - -## [4.0.0-beta.420.7] - 2025-08-26 - ### 🚀 Features -- *(service)* Add TriliumNext service (#5970) -- *(service)* Add Matrix service (#6029) -- *(service)* Add GitHub Action runner service (#6209) -- *(terminal)* Dispatch focus event for terminal after connection and enhance focus handling in JavaScript -- *(lang)* Add Polish language & improve forgot_password translation (#6306) -- *(service)* Update Authentik template (#6264) -- *(service)* Add sequin template (#6105) -- *(service)* Add pi-hole template (#6020) -- *(services)* Add Chroma service (#6201) -- *(service)* Add OpenPanel template (#5310) -- *(service)* Add librechat template (#5654) -- *(service)* Add Homebox service (#6116) -- *(service)* Add pterodactyl & wings services (#5537) -- *(service)* Add Bluesky PDS template (#6302) -- *(input)* Add autofocus attribute to input component for improved accessibility -- *(core)* Finally fqdn is fqdn and url is url. haha -- *(user)* Add changelog read tracking and unread count method -- *(templates)* Add new service templates and update existing compose files for various applications -- *(changelog)* Implement automated changelog fetching from GitHub and enhance changelog read tracking -- *(drizzle-gateway)* Add new drizzle-gateway service with configuration and logo -- *(drizzle-gateway)* Enhance service configuration by adding Master Password field and updating compose file path -- *(templates)* Add new service templates for Homebox, LibreChat, Pterodactyl, and Wings with corresponding configurations and logos -- *(templates)* Add Bluesky PDS service template and update compose file with new environment variable -- *(readme)* Add CubePath as a big sponsor and include new small sponsors with logos -- *(api)* Add create_environment endpoint to ProjectController for environment creation in projects -- *(api)* Add endpoints for managing environments in projects, including listing, creating, and deleting environments -- *(backup)* Add disable local backup option and related logic for S3 uploads -- *(dev patches)* Add functionality to send test email with patch data in development mode -- *(templates)* Added category per service -- *(email)* Implement email change request and verification process -- Generate category for services -- *(service)* Add elasticsearch template (#6300) -- *(sanitization)* Integrate DOMPurify for HTML sanitization across components -- *(cleanup)* Add command for sanitizing name fields across models -- *(sanitization)* Enhance HTML sanitization with improved DOMPurify configuration -- *(validation)* Centralize validation patterns for names and descriptions -- *(git-settings)* Add support for shallow cloning in application settings -- *(auth)* Implement authorization checks for server updates across multiple components -- *(auth)* Implement authorization for PrivateKey management -- *(auth)* Implement authorization for Docker and server management -- *(validation)* Add custom validation rules for Git repository URLs and branches -- *(security)* Add authorization checks for package updates in Livewire components -- *(auth)* Implement authorization checks for application management -- *(auth)* Enhance API error handling for authorization exceptions -- *(auth)* Add comprehensive authorization checks for all kind of resource creations -- *(auth)* Implement authorization checks for database management -- *(auth)* Refine authorization checks for S3 storage and service management -- *(auth)* Implement comprehensive authorization checks across API controllers -- *(auth)* Introduce resource creation authorization middleware and policies for enhanced access control -- *(auth)* Add middleware for resource creation authorization -- *(auth)* Enhance authorization checks in Livewire components for resource management -- *(validation)* Add ValidIpOrCidr rule for validating IP addresses and CIDR notations; update API access settings UI and add comprehensive tests -- *(docs)* Update architecture and development guidelines; enhance form components with built-in authorization system and improve routing documentation -- *(docs)* Expand authorization documentation for custom Alpine.js components; include manual protection patterns and implementation guidelines -- *(sentinel)* Implement SentinelRestarted event and update Livewire components to handle server restart notifications -- *(api)* Enhance IP access control in middleware and settings; support CIDR notation and special case for 0.0.0.0 to allow all IPs -- *(acl)* Change views/backend code to able to use proper ACL's later on. Currently it is not enabled. -- *(docs)* Add Backlog.md guidelines and project manager backlog agent; enhance CLAUDE.md with new links for task management -- *(docs)* Add tasks for implementing Docker build caching and optimizing staging builds; include detailed acceptance criteria and implementation plans -- *(docker)* Implement Docker cleanup processing in ScheduledJobManager; refactor server task scheduling to streamline cleanup job dispatching -- *(docs)* Expand Backlog.md guidelines with comprehensive usage instructions, CLI commands, and best practices for task management to enhance project organization and collaboration - -### 🐛 Bug Fixes - -- *(service)* Triliumnext platform and link -- *(application)* Update service environment variables when generating domain for Docker Compose -- *(application)* Add option to suppress toast notifications when loading compose file -- *(git)* Tracking issue due to case sensitivity -- *(git)* Tracking issue due to case sensitivity -- *(git)* Tracking issue due to case sensitivity -- *(ui)* Delete button width on small screens (#6308) -- *(service)* Matrix entrypoint -- *(ui)* Add flex-wrap to prevent overflow on small screens (#6307) -- *(docker)* Volumes get delete when stopping a service if `Delete Unused Volumes` is activated (#6317) -- *(docker)* Cleanup always running on deletion -- *(proxy)* Remove hardcoded port 80/443 checks (#6275) -- *(service)* Update healthcheck of penpot backend container (#6272) -- *(api)* Duplicated logs in application endpoint (#6292) -- *(service)* Documenso signees always pending (#6334) -- *(api)* Update service upsert to retain name and description values if not set -- *(database)* Custom postgres configs with SSL (#6352) -- *(policy)* Update delete method to check for admin status in S3StoragePolicy -- *(container)* Sort containers alphabetically by name in ExecuteContainerCommand and update filtering in Terminal Index -- *(application)* Streamline environment variable updates for Docker Compose services and enhance FQDN generation logic -- *(constants)* Update 'Change Log' to 'Changelog' in settings dropdown -- *(constants)* Update coolify version to 4.0.0-beta.420.7 -- *(parsers)* Clarify comments and update variable checks for FQDN and URL handling -- *(terminal)* Update text color for terminal availability message and improve readability -- *(drizzle-gateway)* Remove healthcheck from drizzle-gateway compose file and update service template -- *(templates)* Should generate old SERVICE_FQDN service templates as well -- *(constants)* Update official service template URL to point to the v4.x branch for accuracy -- *(git)* Use exact refspec in ls-remote to avoid matching similarly named branches (e.g., changeset-release/main). Use refs/heads/ or provider-specific PR refs. -- *(ApplicationPreview)* Change null check to empty check for fqdn in generate_preview_fqdn method -- *(email notifications)* Enhance EmailChannel to validate team membership for recipients and handle errors gracefully -- *(service api)* Separate create and update service functionalities -- *(templates)* Added a category tag for the docs service filter -- *(application)* Clear Docker Compose specific data when switching away from dockercompose -- *(database)* Conditionally set started_at only if the database is running -- *(ui)* Handle null values in postgres metrics (#6388) -- Disable env sorting by default -- *(proxy)* Filter host network from default proxy (#6383) -- *(modal)* Enhance confirmation text handling -- *(notification)* Update unread count display and improve HTML rendering -- *(select)* Remove unnecessary sanitization for logo rendering -- *(tags)* Update tag display to limit name length and adjust styling -- *(init)* Improve error handling for deployment and template pulling processes -- *(settings-dropdown)* Adjust unread count badge size and display logic for better consistency -- *(sanitization)* Enhance DOMPurify hook to remove Alpine.js directives for improved XSS protection -- *(servercheck)* Properly check server statuses with and without Sentinel -- *(errors)* Update error pages to provide navigation options -- *(github-deploy-key)* Update background color for selected private keys in deployment key selection UI -- *(auth)* Enhance authorization checks in application management - -### 💼 Other - -- *(settings-dropdown)* Add icons to buttons for improved UI in settings dropdown -- *(ui)* Introduce task for simplifying resource operations UI by replacing boxes with dropdown selections to enhance user experience and streamline interactions - -### 🚜 Refactor - -- *(jobs)* Remove logging for ScheduledJobManager and ServerResourceManager start and completion -- *(services)* Update validation rules to be optional -- *(service)* Improve langfuse -- *(service)* Improve openpanel template -- *(service)* Improve librechat -- *(public-git-repository)* Enhance form structure and add autofocus to repository URL input -- *(public-git-repository)* Remove commented-out code for cleaner template -- *(templates)* Update service template file handling to use dynamic file name from constants -- *(parsers)* Streamline domain handling in applicationParser and improve DNS validation logic -- *(templates)* Replace SERVICE_FQDN variables with SERVICE_URL in compose files for consistency -- *(links)* Replace inline SVGs with reusable external link component for consistency and improved maintainability -- *(previews)* Improve layout and add deployment/application logs links for previews -- *(docker compose)* Remove deprecated newParser function and associated test file to streamline codebase -- *(shared helpers)* Remove unused parseServiceVolumes function to clean up codebase -- *(parsers)* Update volume parsing logic to use beforeLast and afterLast for improved accuracy -- *(validation)* Implement centralized validation patterns across components -- *(jobs)* Rename job classes to indicate deprecation status -- Update check frequency logic for cloud and self-hosted environments; streamline server task scheduling and timezone handling -- *(policies)* Remove Response type hint from update methods in ApplicationPreviewPolicy and DatabasePolicy for improved flexibility - -### 📚 Documentation - -- *(claude)* Clarify that artisan commands should only be run inside the "coolify" container during development -- Add AGENTS.md for project guidance and development instructions -- Update changelog -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(service)* Improve matrix service -- *(service)* Format runner service -- *(service)* Improve sequin -- *(service)* Add `NOT_SECURED` env to Postiz (#6243) -- *(service)* Improve evolution-api environment variables (#6283) -- *(service)* Update Langfuse template to v3 (#6301) -- *(core)* Remove unused argument -- *(deletion)* Rename isDeleteOperation to deleteConnectedNetworks -- *(docker)* Remove unused arguments on StopService -- *(service)* Homebox formatting -- Clarify usage of custom redis configuration (#6321) -- *(changelogs)* Add .gitignore for changelogs directory and remove outdated changelog files for May, June, and July 2025 -- *(service)* Change affine images (#6366) -- Elasticsearch URL, fromatting and add category -- Update service-templates json files -- *(docs)* Remove AGENTS.md file; enhance CLAUDE.md with detailed form authorization patterns and service configuration examples -- *(cleanup)* Remove unused GitLab view files for change, new, and show pages -- *(workflows)* Add backlog directory to build triggers for production and staging workflows -- *(config)* Disable auto_commit in backlog configuration to prevent automatic commits -- *(versions)* Update coolify version to 4.0.0-beta.420.8 and nightly version to 4.0.0-beta.420.9 in versions.json and constants.php -- *(docker)* Update soketi image version to 1.0.10 in production and Windows configurations - -### ◀️ Revert - -- *(parser)* Enhance FQDN generation logic for services and applications - -## [4.0.0-beta.420.6] - 2025-07-18 - -### 🚀 Features - -- *(service)* Enable password protection for the Wireguard Ul -- *(queues)* Improve Horizon config to reduce CPU and RAM usage (#6212) -- *(service)* Add Gowa service (#6164) -- *(container)* Add updatedSelectedContainer method to connect to non-default containers and update wire:model for improved reactivity -- *(application)* Implement environment variable updates for Docker Compose applications, including creation, updating, and deletion of SERVICE_FQDN and SERVICE_URL variables - -### 🐛 Bug Fixes - -- *(installer)* Public IPv4 link does not work -- *(composer)* Version constraint of prompts -- *(service)* Budibase secret keys (#6205) -- *(service)* Wg-easy host should be just the FQDN -- *(ui)* Search box overlaps the sidebar navigation (#6176) -- *(webhooks)* Exclude webhook routes from CSRF protection (#6200) -- *(services)* Update environment variable naming convention to use underscores instead of dashes for SERVICE_FQDN and SERVICE_URL - -### 🚜 Refactor - -- *(service)* Improve gowa -- *(previews)* Streamline preview domain generation logic in ApplicationDeploymentJob for improved clarity and maintainability -- *(services)* Simplify environment variable updates by using updateOrCreate and add cleanup for removed FQDNs - -### 📚 Documentation - -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(service)* Update Nitropage template (#6181) -- *(versions)* Update all version -- *(bump)* Update composer deps -- *(version)* Bump Coolify version to 4.0.0-beta.420.6 - -## [4.0.0-beta.420.4] - 2025-07-08 - -### 🚀 Features - -- *(scheduling)* Add command to manually run scheduled database backups and tasks with options for chunking, delays, and dry runs -- *(scheduling)* Add frequency filter option for manual execution of scheduled jobs -- *(logging)* Implement scheduled logs command and enhance backup/task scheduling with cron checks -- *(logging)* Add frequency filters for scheduled logs command to support hourly, daily, weekly, and monthly job views -- *(scheduling)* Introduce ScheduledJobManager and ServerResourceManager for enhanced job scheduling and resource management -- *(previews)* Implement soft delete and cleanup for ApplicationPreview, enhancing resource management in DeleteResourceJob - -### 🐛 Bug Fixes - -- *(service)* Update Postiz compose configuration for improved server availability -- *(install.sh)* Use IPV4_PUBLIC_IP variable in output instead of repeated curl -- *(env)* Generate literal env variables better -- *(deployment)* Update x-data initialization in deployment view for improved functionality -- *(deployment)* Enhance COOLIFY_URL and COOLIFY_FQDN variable generation for better compatibility -- *(deployment)* Improve docker-compose domain handling and environment variable generation -- *(deployment)* Refactor domain parsing and environment variable generation using Spatie URL library -- *(deployment)* Update COOLIFY_URL and COOLIFY_FQDN generation to use Spatie URL library for improved accuracy -- *(scheduling)* Change redis cleanup command frequency from hourly to weekly for better resource management -- *(versions)* Update coolify version numbers in versions.json and constants.php to 4.0.0-beta.420.5 and 4.0.0-beta.420.6 -- *(database)* Ensure internal port defaults correctly for unsupported database types in StartDatabaseProxy -- *(versions)* Update coolify version numbers in versions.json and constants.php to 4.0.0-beta.420.6 and 4.0.0-beta.420.7 -- *(scheduling)* Remove unnecessary padding from scheduled task form layout for improved UI consistency -- *(horizon)* Update queue configuration to use environment variable for dynamic queue management -- *(horizon)* Add silenced jobs -- *(application)* Sanitize service names for HTML form binding and ensure original names are stored in docker compose domains -- *(previews)* Adjust padding for rate limit message in application previews -- *(previews)* Order application previews by pull request ID in descending order -- *(previews)* Add unique wire keys for preview containers and services based on pull request ID -- *(previews)* Enhance domain generation logic for application previews, ensuring unique domains are created when none are set -- *(previews)* Refine preview domain generation for Docker Compose applications, ensuring correct method usage based on build pack type -- *(ui)* Typo on proxy request handler tooltip (#6192) -- *(backups)* Large database backups are not working (#6217) -- *(backups)* Error message if there is no exception - -### 🚜 Refactor - -- *(previews)* Streamline preview URL generation by utilizing application method -- *(application)* Adjust layout and spacing in general application view for improved UI -- *(postgresql)* Improve layout and spacing in SSL and Proxy configuration sections for better UI consistency -- *(scheduling)* Replace deprecated job checks with ScheduledJobManager and ServerResourceManager for improved scheduling efficiency -- *(previews)* Move preview domain generation logic to ApplicationPreview model for better encapsulation and consistency across webhook handlers - -### 📚 Documentation - -- Update changelog -- Update changelog - -## [4.0.0-beta.420.3] - 2025-07-03 - -### 📚 Documentation - -- Update changelog - -## [4.0.0-beta.420.2] - 2025-07-03 - -### 🚀 Features - -- *(template)* Added excalidraw (#6095) -- *(template)* Add excalidraw service configuration with documentation and tags - -### 🐛 Bug Fixes - -- *(terminal)* Ensure shell execution only uses valid shell if available in terminal command -- *(ui)* Improve destination selection description for clarity in resource segregation -- *(jobs)* Update middleware to use expireAfter for WithoutOverlapping in multiple job classes -- Removing eager loading (#6071) -- *(template)* Adjust health check interval and retries for excalidraw service -- *(ui)* Env variable settings wrong order -- *(service)* Ensure configuration changes are properly tracked and dispatched - -### 🚜 Refactor - -- *(ui)* Enhance project cloning interface with improved table layout for server and resource selection -- *(terminal)* Simplify command construction for SSH execution -- *(settings)* Streamline instance admin checks and initialization of settings in Livewire components -- *(policy)* Optimize team membership checks in S3StoragePolicy -- *(popup)* Improve styling and structure of the small popup component -- *(shared)* Enhance FQDN generation logic for services in newParser function -- *(redis)* Enhance CleanupRedis command with dry-run option and improved key deletion logic -- *(init)* Standardize method naming conventions and improve command structure in Init.php -- *(shared)* Improve error handling in getTopLevelNetworks function to return network name on invalid docker-compose.yml -- *(database)* Improve error handling for unsupported database types in StartDatabaseProxy - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Bump coolify and nightly versions to 4.0.0-beta.420.3 and 4.0.0-beta.420.4 respectively -- *(versions)* Update coolify and nightly versions to 4.0.0-beta.420.4 and 4.0.0-beta.420.5 respectively - -## [4.0.0-beta.420.1] - 2025-06-26 - -### 🐛 Bug Fixes - -- *(server)* Prepend 'mux_' to UUID in muxFilename method for consistent naming -- *(ui)* Enhance terminal access messaging to clarify server functionality and terminal status -- *(database)* Proxy ssl port if ssl is enabled - -### 🚜 Refactor - -- *(ui)* Separate views for instance settings to separate paths to make it cleaner -- *(ui)* Remove unnecessary step3ButtonText attributes from modal confirmation components for cleaner code - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Update Coolify versions to 4.0.0-beta.420.2 and 4.0.0-beta.420.3 in multiple files - -## [4.0.0-beta.420] - 2025-06-26 - -### 🚀 Features - -- *(service)* Add Miniflux service (#5843) -- *(service)* Add Pingvin Share service (#5969) -- *(auth)* Add Discord OAuth Provider (#5552) -- *(auth)* Add Clerk OAuth Provider (#5553) -- *(auth)* Add Zitadel OAuth Provider (#5490) -- *(core)* Set custom API rate limit (#5984) -- *(service)* Enhance service status handling and UI updates -- *(cleanup)* Add functionality to delete teams with no members or servers in CleanupStuckedResources command -- *(ui)* Add heart icon and enhance popup messaging for sponsorship support -- *(settings)* Add sponsorship popup toggle and corresponding database migration -- *(migrations)* Add optimized indexes to activity_log for improved query performance - -### 🐛 Bug Fixes - -- *(service)* Audiobookshelf healthcheck command (#5993) -- *(service)* Downgrade Evolution API phone version (#5977) -- *(service)* Pingvinshare-with-clamav -- *(ssh)* Scp requires square brackets for ipv6 (#6001) -- *(github)* Changing github app breaks the webhook. it does not anymore -- *(parser)* Improve FQDN generation and update environment variable handling -- *(ui)* Enhance status refresh buttons with loading indicators -- *(ui)* Update confirmation button text for stopping database and service -- *(routes)* Update middleware for deploy route to use 'api.ability:deploy' -- *(ui)* Refine API token creation form and update helper text for clarity -- *(ui)* Adjust layout of deployments section for improved alignment -- *(ui)* Adjust project grid layout and refine server border styling for better visibility -- *(ui)* Update border styling for consistency across components and enhance loading indicators -- *(ui)* Add padding to section headers in settings views for improved spacing -- *(ui)* Reduce gap between input fields in email settings for better alignment -- *(docker)* Conditionally enable gzip compression in Traefik labels based on configuration -- *(parser)* Enable gzip compression conditionally for Pocketbase images and streamline service creation logic -- *(ui)* Update padding for trademarks policy and enhance spacing in advanced settings section -- *(ui)* Correct closing tag for sponsorship link in layout popups -- *(ui)* Refine wording in sponsorship donation prompt in layout popups -- *(ui)* Update navbar icon color and enhance popup layout for sponsorship support -- *(ui)* Add target="_blank" to sponsorship links in layout popups for improved user experience -- *(models)* Refine comment wording in User model for clarity on user deletion criteria -- *(models)* Improve user deletion logic in User model to handle team member roles and prevent deletion if user is alone in root team -- *(ui)* Update wording in sponsorship prompt for clarity and engagement -- *(shared)* Refactor gzip handling for Pocketbase in newParser function for improved clarity - -### 🚜 Refactor - -- *(service)* Update Hoarder to their new name karakeep (#5964) -- *(service)* Karakeep naming and formatting -- *(service)* Improve miniflux -- *(core)* Rename API rate limit ENV -- *(ui)* Simplify container selection form in execute-container-command view -- *(email)* Streamline SMTP and resend settings logic for improved clarity -- *(invitation)* Rename methods for consistency and enhance invitation deletion logic -- *(user)* Streamline user deletion process and enhance team management logic - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(service)* Update Evolution API image to the official one (#6031) -- *(versions)* Bump coolify versions to v4.0.0-beta.420 and v4.0.0-beta.421 -- *(dependencies)* Update composer dependencies to latest versions including resend-laravel to ^0.19.0 and aws-sdk-php to 3.347.0 -- *(versions)* Update Coolify version to 4.0.0-beta.420.1 and add new services (karakeep, miniflux, pingvinshare) to service templates - -## [4.0.0-beta.419] - 2025-06-17 - -### 🚀 Features - -- *(core)* Add 'postmarketos' to supported OS list -- *(service)* Add memos service template (#5032) -- *(ui)* Upgrade to Tailwind v4 (#5710) -- *(service)* Add Navidrome service template (#5022) -- *(service)* Add Passbolt service (#5769) -- *(service)* Add Vert service (#5663) -- *(service)* Add Ryot service (#5232) -- *(service)* Add Marimo service (#5559) -- *(service)* Add Diun service (#5113) -- *(service)* Add Observium service (#5613) -- *(service)* Add Leantime service (#5792) -- *(service)* Add Limesurvey service (#5751) -- *(service)* Add Paymenter service (#5809) -- *(service)* Add CodiMD service (#4867) -- *(modal)* Add dispatchAction property to confirmation modal -- *(security)* Implement server patching functionality -- *(service)* Add Typesense service (#5643) -- *(service)* Add Yamtrack service (#5845) -- *(service)* Add PG Back Web service (#5079) -- *(service)* Update Maybe service and adjust it for the new release (#5795) -- *(oauth)* Set redirect uri as optional and add default value (#5760) -- *(service)* Add apache superset service (#4891) -- *(service)* Add One Time Secret service (#5650) -- *(service)* Add Seafile service (#5817) -- *(service)* Add Netbird-Client service (#5873) -- *(service)* Add OrangeHRM and Grist services (#5212) -- *(rules)* Add comprehensive documentation for Coolify architecture and development practices for AI tools, especially for cursor -- *(server)* Implement server patch check notifications -- *(api)* Add latest query param to Service restart API (#5881) -- *(api)* Add connect_to_docker_network setting to App creation API (#5691) -- *(routes)* Restrict backup download access to team admins and owners -- *(destination)* Update confirmation modal text and add persistent storage warning for server deployment -- *(terminal-access)* Implement terminal access control for servers and containers, including UI updates and backend logic -- *(ca-certificate)* Add CA certificate management functionality with UI integration and routing -- *(security-patches)* Add update check initialization and enhance notification messaging in UI -- *(previews)* Add force deploy without cache functionality and update deploy method to accept force rebuild parameter -- *(security-patterns)* Expand sensitive patterns list to include additional security-related variables -- *(database-backup)* Add MongoDB credential extraction and backup handling to DatabaseBackupJob -- *(activity-monitor)* Implement auto-scrolling functionality and dynamic content observation for improved user experience -- *(utf8-handling)* Implement UTF-8 sanitization for command outputs and enhance error handling in logs processing -- *(navbar)* Add Traefik dashboard availability check and server IP handling; refactor dynamic configurations loading -- *(proxy-dashboard)* Implement ProxyDashboardCacheService to manage Traefik dashboard cache; clear cache on configuration changes and proxy actions -- *(terminal-connection)* Enhance terminal connection handling with auto-connect feature and improved status messaging -- *(terminal)* Implement resize handling with ResizeObserver for improved terminal responsiveness -- *(migration)* Add is_sentinel_enabled column to server_settings with default true -- *(seeder)* Dispatch StartProxy action for each server in ProductionSeeder -- *(seeder)* Add CheckAndStartSentinelJob dispatch for each server in ProductionSeeder -- *(seeder)* Conditionally dispatch StartProxy action based on proxy check result -- *(service)* Update Changedetection template (#5937) - -### 🐛 Bug Fixes - -- *(constants)* Adding 'fedora-asahi-remix' as a supported OS (#5646) -- *(authentik)* Update docker-compose configuration for authentik service -- *(api)* Allow nullable destination_uuid (#5683) -- *(service)* Fix documenso startup and mail (#5737) -- *(docker)* Fix production dockerfile -- *(service)* Navidrome service -- *(service)* Passbolt -- *(service)* Add missing ENVs to NTFY service (#5629) -- *(service)* NTFY is behind a proxy -- *(service)* Vert logo and ENVs -- *(service)* Add platform to Observium service -- *(ActivityMonitor)* Prevent multiple event dispatches during polling -- *(service)* Convex ENVs and update image versions (#5827) -- *(service)* Paymenter -- *(ApplicationDeploymentJob)* Ensure correct COOLIFY_FQDN/COOLIFY_URL values (#4719) -- *(service)* Snapdrop no matching manifest error (#5849) -- *(service)* Use the same volume between chatwoot and sidekiq (#5851) -- *(api)* Validate docker_compose_raw input in ApplicationsController -- *(api)* Enhance validation for docker_compose_raw in ApplicationsController -- *(select)* Update PostgreSQL versions and titles in resource selection -- *(database)* Include DatabaseStatusChanged event in activityMonitor dispatch -- *(css)* Tailwind v5 things -- *(service)* Diun ENV for consistency -- *(service)* Memos service name -- *(css)* 8+ issue with new tailwind v4 -- *(css)* `bg-coollabs-gradient` not working anymore -- *(ui)* Add back missing service navbar components -- *(deploy)* Update resource timestamp handling in deploy_resource method -- *(patches)* DNF reboot logic is flipped -- *(deployment)* Correct syntax for else statement in docker compose build command -- *(shared)* Remove unused relation from queryDatabaseByUuidWithinTeam function -- *(deployment)* Correct COOLIFY_URL and COOLIFY_FQDN assignments based on parsing version in preview deployments -- *(docker)* Ensure correct parsing of environment variables by limiting explode to 2 parts -- *(project)* Update selected environment handling to use environment name instead of UUID -- *(ui)* Update server status display and improve server addition layout -- *(service)* Neon WS Proxy service not working on ARM64 (#5887) -- *(server)* Enhance error handling in server patch check notifications -- *(PushServerUpdateJob)* Add null checks before updating application and database statuses -- *(environment-variables)* Update label text for build variable checkboxes to improve clarity -- *(service-management)* Update service stop and restart messages for improved clarity and formatting -- *(preview-form)* Update helper text formatting in preview URL template input for better readability -- *(application-management)* Improve stop messages for application, database, and service to enhance clarity and formatting -- *(application-configuration)* Prevent access to preview deployments for deploy_key applications and update menu visibility accordingly -- *(select-component)* Handle exceptions during parameter retrieval and environment selection in the mount method -- *(previews)* Escape container names in stopContainers method to prevent shell injection vulnerabilities -- *(docker)* Add protection against empty container queries in GetContainersStatus to prevent unnecessary updates -- *(modal-confirmation)* Decode HTML entities in confirmation text to ensure proper display -- *(select-component)* Enhance user interaction by adding cursor styles and disabling selection during processing -- *(deployment-show)* Remove unnecessary fixed positioning for button container to improve layout responsiveness -- *(email-notifications)* Change notify method to notifyNow for immediate test email delivery -- *(service-templates)* Update Convex service configuration to use FQDN variables -- *(database-heading)* Simplify stop database message for clarity -- *(navbar)* Remove unnecessary x-init directive for loading proxy configuration -- *(patches)* Add padding to loading message for better visibility during update checks -- *(terminal-connection)* Improve error handling and stability for auto-connection; enhance component readiness checks and retry logic -- *(terminal)* Add unique wire:key to terminal component for improved reactivity and state management -- *(css)* Adjust utility classes in utilities.css for consistent application of Tailwind directives -- *(css)* Refine utility classes in utilities.css for proper Tailwind directive application -- *(install)* Update Docker installation script to use dynamic OS_TYPE and correct installation URL -- *(cloudflare)* Add error handling to automated Cloudflare configuration script -- *(navbar)* Add error handling for proxy status check to improve user feedback -- *(web)* Update user team retrieval method for consistent authentication handling -- *(cloudflare)* Update refresh method to correctly set Cloudflare tunnel status and improve user notification on IP address update -- *(service)* Update service template for affine and add migration service for improved deployment process -- *(supabase)* Update Supabase service images and healthcheck methods for improved reliability -- *(terminal)* Now it should work -- *(degraded-status)* Remove unnecessary whitespace in badge element for cleaner HTML -- *(routes)* Add name to security route for improved route management -- *(migration)* Update default value handling for is_sentinel_enabled column in server_settings -- *(seeder)* Conditionally dispatch CheckAndStartSentinelJob based on server's sentinel status -- *(service)* Disable healthcheck logging for Gotenberg (#6005) -- *(service)* Joplin volume name (#5930) -- *(server)* Update sentinelUpdatedAt assignment to use server's sentinel_updated_at property - -### 💼 Other - -- Add support for postmarketOS (#5608) -- *(core)* Simplify events for app/db/service status changes - -### 🚜 Refactor - -- *(service)* Observium -- *(service)* Improve leantime -- *(service)* Imporve limesurvey -- *(service)* Improve CodiMD -- *(service)* Typsense -- *(services)* Improve yamtrack -- *(service)* Improve paymenter -- *(service)* Consolidate configuration change dispatch logic and remove unused navbar component -- *(sidebar)* Simplify server patching link by removing button element -- *(slide-over)* Streamline button element and improve code readability -- *(service)* Enhance modal confirmation component with event dispatching for service stop actions -- *(slide-over)* Enhance class merging for improved component styling -- *(core)* Use property promotion -- *(service)* Improve maybe -- *(applications)* Remove unused docker compose raw decoding -- *(service)* Make TYPESENSE_API_KEY required -- *(ui)* Show toast when server does not work and on stop -- *(service)* Improve superset -- *(service)* Improve Onetimesecret -- *(service)* Improve Seafile -- *(service)* Improve orangehrm -- *(service)* Improve grist -- *(application)* Enhance application stopping logic to support multiple servers -- *(pricing-plans)* Improve label class binding for payment frequency selection -- *(error-handling)* Replace generic Exception with RuntimeException for improved error specificity -- *(error-handling)* Change Exception to RuntimeException for clearer error reporting -- *(service)* Remove informational dispatch during service stop for cleaner execution -- *(server-ui)* Improve layout and messaging in advanced settings and charts views -- *(terminal-access)* Streamline resource retrieval and enhance terminal access messaging in UI -- *(terminal)* Enhance terminal connection management and error handling, including improved reconnection logic and cleanup procedures -- *(application-deployment)* Separate handling of FAILED and CANCELLED_BY_USER statuses for clearer logic and notification -- *(jobs)* Update middleware to include job-specific identifiers for WithoutOverlapping -- *(jobs)* Modify middleware to use job-specific identifier for WithoutOverlapping -- *(environment-variables)* Remove debug logging from bulk submit handling for cleaner code -- *(environment-variables)* Simplify application build pack check in environment variable handling -- *(logs)* Adjust padding in logs view for improved layout consistency -- *(application-deployment)* Streamline post-deployment process by always dispatching container status check -- *(service-management)* Enhance container stopping logic by implementing parallel processing and removing deprecated methods -- *(activity-monitor)* Change activity property visibility and update view references for consistency -- *(activity-monitor)* Enhance layout responsiveness by adjusting class bindings and structure for better display -- *(service-management)* Update stopContainersInParallel method to enforce Server type hint for improved type safety -- *(service-management)* Rearrange docker cleanup logic in StopService to improve readability -- *(database-management)* Simplify docker cleanup logic in StopDatabase to enhance readability -- *(activity-monitor)* Consolidate activity monitoring logic and remove deprecated NewActivityMonitor component -- *(activity-monitor)* Update dispatch method to use activityMonitor instead of deprecated newActivityMonitor -- *(push-server-update)* Enhance application preview handling by incorporating pull request IDs and adding status update protections -- *(docker-compose)* Replace hardcoded Docker Compose configuration with external YAML template for improved database detection testing -- *(test-database-detection)* Rename services for clarity, add new database configurations, and update application service dependencies -- *(database-detection)* Enhance isDatabaseImage function to utilize service configuration for improved detection accuracy -- *(install-scripts)* Update Docker installation process to include manual installation fallback and improve error handling -- *(logs-view)* Update logs display for service containers with improved headings and dynamic key binding -- *(logs)* Enhance container loading logic and improve UI for logs display across various resource types -- *(cloudflare-tunnel)* Enhance layout and structure of Cloudflare Tunnel documentation and confirmation modal -- *(terminal-connection)* Streamline auto-connection logic and improve component readiness checks -- *(logs)* Remove unused methods and debug functionality from Logs.php for cleaner code -- *(remoteProcess)* Update sanitize_utf8_text function to accept nullable string parameter for improved type safety -- *(events)* Remove ProxyStarted event and associated ProxyStartedNotification listener for code cleanup -- *(navbar)* Remove unnecessary parameters from server navbar component for cleaner implementation -- *(proxy)* Remove commented-out listener and method for cleaner code structure -- *(events)* Update ProxyStatusChangedUI constructor to accept nullable teamId for improved flexibility -- *(cloudflare)* Update server retrieval method for improved query efficiency -- *(navbar)* Remove unused PHP use statement for cleaner code -- *(proxy)* Streamline proxy status handling and improve dashboard availability checks -- *(navbar)* Simplify proxy status handling and enhance loading indicators for better user experience -- *(resource-operations)* Filter out build servers from the server list and clean up commented-out code in the resource operations view -- *(execute-container-command)* Simplify connection logic and improve terminal availability checks -- *(navigation)* Remove wire:navigate directive from configuration links for cleaner HTML structure -- *(proxy)* Update StartProxy calls to use named parameter for async option -- *(clone-project)* Enhance server retrieval by including destinations and filtering out build servers -- *(ui)* Terminal -- *(ui)* Remove terminal header from execute-container-command view -- *(ui)* Remove unnecessary padding from deployment, backup, and logs sections - -### 📚 Documentation - -- Update changelog -- *(service)* Add new docs link for zipline (#5912) -- Update changelog -- Update changelog -- Update changelog - -### 🎨 Styling - -- *(css)* Update padding utility for password input and add newline in app.css -- *(css)* Refine badge utility styles in utilities.css -- *(css)* Enhance badge utility styles in utilities.css - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Update coolify version to 4.0.0-beta.419 and nightly version to 4.0.0-beta.420 in configuration files -- *(service)* Rename hoarder server to karakeep (#5607) -- *(service)* Update Supabase services (#5708) -- *(service)* Remove unused documenso env -- *(service)* Formatting and cleanup of ryot -- *(docs)* Remove changelog and add it to gitignore -- *(versions)* Update version to 4.0.0-beta.419 -- *(service)* Diun formatting -- *(docs)* Update CHANGELOG.md -- *(service)* Switch convex vars -- *(service)* Pgbackweb formatting and naming update -- *(service)* Remove typesense default API key -- *(service)* Format yamtrack healthcheck -- *(core)* Remove unused function -- *(ui)* Remove unused stopEvent code -- *(service)* Remove unused env -- *(tests)* Update test environment database name and add new feature test for converting container environment variables to array -- *(service)* Update Immich service (#5886) -- *(service)* Remove unused logo -- *(api)* Update API docs -- *(dependencies)* Update package versions in composer.json and composer.lock for improved compatibility and performance -- *(dependencies)* Update package versions in package.json and package-lock.json for improved stability and features -- *(version)* Update coolify-realtime to version 1.0.9 in docker-compose and versions files -- *(version)* Update coolify version to 4.0.0-beta.420 and nightly version to 4.0.0-beta.421 -- *(service)* Changedetection remove unused code - -## [4.0.0-beta.417] - 2025-05-07 - -### 🐛 Bug Fixes - -- *(select)* Update fallback logo path to use absolute URL for improved reliability - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Update coolify version to 4.0.0-beta.418 - -## [4.0.0-beta.416] - 2025-05-05 - -### 🚀 Features - -- *(migration)* Add 'is_migrated' and 'custom_type' columns to service_applications and service_databases tables -- *(backup)* Implement custom database type selection and enhance scheduled backups management -- *(README)* Add Gozunga and Macarne to sponsors list -- *(redis)* Add scheduled cleanup command for Redis keys and enhance cleanup logic - -### 🐛 Bug Fixes - -- *(service)* Graceful shutdown of old container (#5731) -- *(ServerCheck)* Enhance proxy container check to ensure it is running before proceeding -- *(applications)* Include pull_request_id in deployment queue check to prevent duplicate deployments -- *(database)* Update label for image input field to improve clarity -- *(ServerCheck)* Set default proxy status to 'exited' to handle missing container state -- *(database)* Reduce container stop timeout from 300 to 30 seconds for improved responsiveness -- *(ui)* System theming for charts (#5740) -- *(dev)* Mount points?! -- *(dev)* Proxy mount point -- *(ui)* Allow adding scheduled backups for non-migrated databases -- *(DatabaseBackupJob)* Escape PostgreSQL password in backup command (#5759) -- *(ui)* Correct closing div tag in service index view - -### 🚜 Refactor - -- *(Database)* Streamline container shutdown process and reduce timeout duration -- *(core)* Streamline container stopping process and reduce timeout duration; update related methods for consistency -- *(database)* Update DB facade usage for consistency across service files -- *(database)* Enhance application conversion logic and add existence checks for databases and applications -- *(actions)* Standardize method naming for network and configuration deletion across application and service classes -- *(logdrain)* Consolidate log drain stopping logic to reduce redundancy -- *(StandaloneMariadb)* Add type hint for destination method to improve code clarity -- *(DeleteResourceJob)* Streamline resource deletion logic and improve conditional checks for database types -- *(jobs)* Update middleware to prevent job release after expiration for CleanupInstanceStuffsJob, RestartProxyJob, and ServerCheckJob -- *(jobs)* Unify middleware configuration to prevent job release after expiration for DockerCleanupJob and PushServerUpdateJob - -### 📚 Documentation - -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(seeder)* Update git branch from 'main' to 'v4.x' for multiple examples in ApplicationSeeder -- *(versions)* Update coolify version to 4.0.0-beta.417 and nightly version to 4.0.0-beta.418 - -## [4.0.0-beta.415] - 2025-04-29 - -### 🐛 Bug Fixes - -- *(ui)* Remove required attribute from image input in service application view -- *(ui)* Change application image validation to be nullable in service application view -- *(Server)* Correct proxy path formatting for Traefik proxy type - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Update coolify version to 4.0.0-beta.416 and nightly version to 4.0.0-beta.417 in configuration files; fix links in deployment view - -## [4.0.0-beta.414] - 2025-04-28 - -### 🐛 Bug Fixes - -- *(ui)* Disable livewire navigate feature (causing spam of setInterval()) - -## [4.0.0-beta.413] - 2025-04-28 - -### 💼 Other - -- Adjust Workflows for v5 (#5689) - -### 📚 Documentation - -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(workflows)* Adjust workflow for announcement - -## [4.0.0-beta.411] - 2025-04-23 - -### 🚀 Features - -- *(deployment)* Add repository_project_id handling for private GitHub apps and clean up unused Caddy label logic -- *(api)* Enhance OpenAPI specifications with token variable and additional key attributes -- *(docker)* Add HTTP Basic Authentication support and enhance hostname parsing in Docker run conversion -- *(api)* Add HTTP Basic Authentication fields to OpenAPI specifications and enhance PrivateKey model descriptions -- *(README)* Add InterviewPal sponsorship link and corresponding SVG icon - -### 🐛 Bug Fixes - -- *(backup-edit)* Conditionally enable S3 checkbox based on available validated S3 storage -- *(source)* Update no sources found message for clarity -- *(api)* Correct middleware for service update route to ensure proper permissions -- *(api)* Handle JSON response in service creation and update methods for improved error handling -- Add 201 json code to servers validate api response -- *(docker)* Ensure password hashing only occurs when HTTP Basic Authentication is enabled -- *(docker)* Enhance hostname and GPU option validation in Docker run to compose conversion -- *(terminal)* Enhance WebSocket client verification with authorized IPs in terminal server -- *(ApplicationDeploymentJob)* Ensure source is an object before checking GitHub app properties - -### 🚜 Refactor - -- *(jobs)* Comment out unused Caddy label handling in ApplicationDeploymentJob and simplify proxy path logic in Server model -- *(database)* Simplify database type checks in ServiceDatabase and enhance image validation in Docker helper -- *(shared)* Remove unused ray debugging statement from newParser function -- *(applications)* Remove redundant error response in create_env method -- *(api)* Restructure routes to include versioning and maintain existing feedback endpoint -- *(api)* Remove token variable from OpenAPI specifications for clarity -- *(environment-variables)* Remove protected variable checks from delete methods for cleaner logic -- *(http-basic-auth)* Rename 'http_basic_auth_enable' to 'http_basic_auth_enabled' across application files for consistency -- *(docker)* Remove debug statement and enhance hostname handling in Docker run conversion -- *(server)* Simplify proxy path logic and remove unnecessary conditions - -### 📚 Documentation - -- Update changelog -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Update coolify version to 4.0.0-beta.411 and nightly version to 4.0.0-beta.412 in configuration files -- *(versions)* Update coolify version to 4.0.0-beta.412 and nightly version to 4.0.0-beta.413 in configuration files -- *(versions)* Update coolify version to 4.0.0-beta.413 and nightly version to 4.0.0-beta.414 in configuration files -- *(versions)* Update realtime version to 1.0.8 in versions.json -- *(versions)* Update realtime version to 1.0.8 in versions.json -- *(docker)* Update soketi image version to 1.0.8 in production configuration files -- *(versions)* Update coolify version to 4.0.0-beta.414 and nightly version to 4.0.0-beta.415 in configuration files - -## [4.0.0-beta.410] - 2025-04-18 - -### 🚀 Features - -- Add HTTP Basic Authentication -- *(readme)* Add new sponsors Supadata AI and WZ-IT to the README -- *(core)* Enable magic env variables for compose based applications - -### 🐛 Bug Fixes - -- *(application)* Append base directory to git branch URLs for improved path handling -- *(templates)* Correct casing of "denokv" to "denoKV" in service templates JSON -- *(navbar)* Update error message link to use route for environment variables navigation -- Unsend template -- Replace ports with expose -- *(templates)* Update Unsend compose configuration for improved service integration - -### 🚜 Refactor - -- *(jobs)* Update WithoutOverlapping middleware to use expireAfter for better queue management - -### 📚 Documentation - -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Bump coolify version to 4.0.0-beta.410 and update nightly version to 4.0.0-beta.411 in configuration files -- *(templates)* Update plausible and clickhouse images to latest versions and remove mail service - -## [4.0.0-beta.409] - 2025-04-16 - -### 🐛 Bug Fixes - -- *(parser)* Transform associative array labels into key=value format for better compatibility -- *(redis)* Update username and password input handling to clarify database sync requirements -- *(source)* Update connected source display to handle cases with no source connected - -### 🚜 Refactor - -- *(source)* Conditionally display connected source and change source options based on private key presence - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Bump coolify version to 4.0.0-beta.409 in configuration files - -## [4.0.0-beta.408] - 2025-04-14 - -### 🚀 Features - -- *(OpenApi)* Enhance OpenAPI specifications by adding UUID parameters for application, project, and service updates; improve deployment listing with pagination parameters; update command signature for OpenApi generation -- *(subscription)* Enhance subscription management with loading states and Stripe status checks - -### 🐛 Bug Fixes - -- *(pre-commit)* Correct input redirection for /dev/tty and add OpenAPI generation command -- *(pricing-plans)* Adjust grid class for improved layout consistency in subscription pricing plans -- *(migrations)* Make stripe_comment field nullable in subscriptions table -- *(mongodb)* Also apply custom config when SSL is enabled -- *(templates)* Correct casing of denoKV references in service templates and YAML files -- *(deployment)* Handle missing destination in deployment process to prevent errors - -### 💼 Other - -- Add missing openapi items to PrivateKey - -### 🚜 Refactor - -- *(commands)* Reorganize OpenAPI and Services generation commands into a new namespace for better structure; remove old command files -- *(Dockerfile)* Remove service generation command from the build process to streamline Dockerfile and improve build efficiency -- *(navbar-delete-team)* Simplify modal confirmation layout and enhance button styling for better user experience -- *(Server)* Remove debug logging from isReachableChanged method to clean up code and improve performance - -### 📚 Documentation - -- Update changelog -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Update nightly version to 4.0.0-beta.410 -- *(pre-commit)* Remove OpenAPI generation command from pre-commit hook -- *(versions)* Update realtime version to 1.0.7 and bump dependencies in package.json - -## [4.0.0-beta.407] - 2025-04-09 - -### 📚 Documentation - -- Update changelog - -## [4.0.0-beta.406] - 2025-04-05 - -### 🚀 Features - -- *(Deploy)* Add info dispatch for proxy check initiation -- *(EnvironmentVariable)* Add handling for Redis credentials in the environment variable component -- *(EnvironmentVariable)* Implement protection for critical environment variables and enhance deletion logic -- *(Application)* Add networkAliases attribute for handling network aliases as JSON or comma-separated values -- *(GithubApp)* Update default events to include 'pull_request' and streamline event handling -- *(CleanupDocker)* Add support for realtime image management in Docker cleanup process -- *(Deployment)* Enhance queue_application_deployment to handle existing deployments and return appropriate status messages -- *(SourceManagement)* Add functionality to change Git source and display current source in the application settings - -### 🐛 Bug Fixes - -- *(CheckProxy)* Update port conflict check to ensure accurate grep matching -- *(CheckProxy)* Refine port conflict detection with improved grep patterns -- *(CheckProxy)* Enhance port conflict detection by adjusting ss command for better output -- *(api)* Add back validateDataApplications (#5539) -- *(CheckProxy, Status)* Prevent proxy checks when force_stop is active; remove debug statement in General -- *(Status)* Conditionally check proxy status and refresh button based on force_stop state -- *(General)* Change redis_password property to nullable string -- *(DeployController)* Update request handling to use input method and enhance OpenAPI description for deployment endpoint - -### 💼 Other - -- Add missing UUID to openapi spec - -### 🚜 Refactor - -- *(Server)* Use data_get for safer access to settings properties in isFunctional method -- *(Application)* Rename network_aliases to custom_network_aliases across the application for clarity and consistency -- *(ApplicationDeploymentJob)* Streamline environment variable handling by introducing generate_coolify_env_variables method and consolidating logic for pull request and main branch scenarios -- *(ApplicationDeploymentJob, ApplicationDeploymentQueue)* Improve deployment status handling and log entry management with transaction support -- *(SourceManagement)* Sort sources by name and improve UI for changing Git source with better error handling -- *(Email)* Streamline SMTP and resend settings handling in copyFromInstanceSettings method -- *(Email)* Enhance error handling in SMTP and resend methods by passing context to handleError function -- *(DynamicConfigurations)* Improve handling of dynamic configuration content by ensuring fallback to empty string when content is null -- *(ServicesGenerate)* Update command signature from 'services:generate' to 'generate:services' for consistency; update Dockerfile to run service generation during build; update Odoo image version to 18 and add extra addons volume in compose configuration -- *(Dockerfile)* Streamline RUN commands for improved readability and maintainability by adding line continuations -- *(Dockerfile)* Reintroduce service generation command in the build process for consistency and ensure proper asset compilation - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Bump version to 406 -- *(versions)* Bump version to 407 and 408 for coolify and nightly -- *(versions)* Bump version to 408 for coolify and 409 for nightly - -## [4.0.0-beta.405] - 2025-04-04 - -### 🚀 Features - -- *(api)* Update OpenAPI spec for services (#5448) -- *(proxy)* Enhance proxy handling and port conflict detection - -### 🐛 Bug Fixes - -- *(api)* Used ssh keys can be deleted -- *(email)* Transactional emails not sending - -### 🚜 Refactor - -- *(CheckProxy)* Replace 'which' with 'command -v' for command availability checks - -### 📚 Documentation - -- Update changelog -- Update changelog -- Update changelog -- Update changelog -- Update changelog -- Update changelog -- Update changelog -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Bump version to 406 -- *(versions)* Bump version to 407 - -## [4.0.0-beta.404] - 2025-04-03 - -### 🚀 Features - -- *(lang)* Added Azerbaijani language updated turkish language. (#5497) -- *(lang)* Added Portuguese from Brazil language (#5500) -- *(lang)* Add Indonesian language translations (#5513) - -### 🐛 Bug Fixes - -- *(docs)* Comment out execute for now -- *(installation)* Mount the docker config -- *(installation)* Path to config file for docker login -- *(service)* Add health check to Bugsink service (#5512) -- *(email)* Emails are not sent in multiple cases -- *(deployments)* Use graceful shutdown instead of `rm` -- *(docs)* Contribute service url (#5517) -- *(proxy)* Proxy restart does not work on domain -- *(ui)* Only show copy button on https -- *(database)* Custom config for MongoDB (#5471) - -### 📚 Documentation - -- Update changelog -- Update changelog -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(service)* Remove unused code in Bugsink service -- *(versions)* Update version to 404 -- *(versions)* Bump version to 403 (#5520) -- *(versions)* Bump version to 404 - -## [4.0.0-beta.402] - 2025-04-01 - -### 🚀 Features - -- *(deployments)* Add list application deployments api route -- *(deploy)* Add pull request ID parameter to deploy endpoint -- *(api)* Add pull request ID parameter to applications endpoint -- *(api)* Add endpoints for retrieving application logs and deployments -- *(lang)* Added Norwegian language (#5280) -- *(dep)* Bump all dependencies - -### 🐛 Bug Fixes - -- Only get apps for the current team -- *(DeployController)* Cast 'pr' query parameter to integer -- *(deploy)* Validate team ID before deployment -- *(wakapi)* Typo in env variables and add some useful variables to wakapi.yaml (#5424) -- *(ui)* Instance Backup settings - -### 🚜 Refactor - -- *(dev)* Remove OpenAPI generation functionality -- *(migration)* Enhance local file volumes migration with logging - -### ⚙️ Miscellaneous Tasks - -- *(service)* Update minecraft service ENVs -- *(service)* Add more vars to infisical.yaml (#5418) -- *(service)* Add google variables to plausible.yaml (#5429) -- *(service)* Update authentik.yaml versions (#5373) -- *(core)* Remove redocs -- *(versions)* Update coolify version numbers to 4.0.0-beta.403 and 4.0.0-beta.404 - -## [4.0.0-beta.401] - 2025-03-28 - -### 📚 Documentation - -- Update changelog -- Update changelog - -## [4.0.0-beta.400] - 2025-03-27 - -### 🚀 Features - -- *(database)* Disable MongoDB SSL by default in migration -- *(database)* Add CA certificate generation for database servers -- *(application)* Add SPA configuration and update Nginx generation logic - -### 🐛 Bug Fixes - -- *(file-storage)* Double save on compose volumes -- *(parser)* Add logging support for applications in services - -### 🚜 Refactor - -- *(proxy)* Improve port availability checks with multiple methods -- *(database)* Update MongoDB SSL configuration for improved security -- *(database)* Enhance SSL configuration handling for various databases -- *(notifications)* Update Telegram button URL for staging environment -- *(models)* Remove unnecessary cloud check in isEnabled method -- *(database)* Streamline event listeners in Redis General component -- *(database)* Remove redundant database status display in MongoDB view -- *(database)* Update import statements for Auth in database components -- *(database)* Require PEM key file for SSL certificate regeneration -- *(database)* Change MySQL daemon command to MariaDB daemon -- *(nightly)* Update version numbers and enhance upgrade script -- *(versions)* Update version numbers for coolify and nightly -- *(email)* Validate team membership for email recipients -- *(shared)* Simplify deployment status check logic -- *(shared)* Add logging for running deployment jobs -- *(shared)* Enhance job status check to include 'reserved' -- *(email)* Improve error handling by passing context to handleError -- *(email)* Streamline email sending logic and improve configuration handling -- *(email)* Remove unnecessary whitespace in email sending logic -- *(email)* Allow custom email recipients in email sending logic -- *(email)* Enhance sender information formatting in email logic -- *(proxy)* Remove redundant stop call in restart method -- *(file-storage)* Add loadStorageOnServer method for improved error handling -- *(docker)* Parse and sanitize YAML compose file before encoding -- *(file-storage)* Improve layout and structure of input fields -- *(email)* Update label for test email recipient input -- *(database-backup)* Remove existing Docker container before backup upload -- *(database)* Improve decryption and deduplication of local file volumes -- *(database)* Remove debug output from volume update process - -### 📚 Documentation - -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Update version numbers for coolify and nightly - -### ◀️ Revert - -- Encrypting mount and fs_path - -## [4.0.0-beta.399] - 2025-03-25 - -### 🚀 Features - -- *(service)* Neon -- *(migration)* Add `ssl_certificates` table and model -- *(migration)* Add ssl setting to `standalone_postgresqls` table -- *(ui)* Add ssl settings to Postgres ui -- *(db)* Add ssl mode to Postgres URLs -- *(db)* Setup ssl during Postgres start -- *(migration)* Encrypt local file volumes content and paths -- *(ssl)* Ssl generation helper -- *(ssl)* Migrate to `ECC`certificates using `secp521r1` -- *(ssl)* Improve SSL helper -- *(ssl)* Add a Coolify CA Certificate to all servers -- *(seeder)* Call CA SSL seeder in prod and dev -- *(ssl)* Add Coolify CA Certificate when adding a new server -- *(installer)* Create CA folder during installation -- *(ssl)* Improve SSL helper -- *(ssl)* Use new improved helper for SSL generation -- *(ui)* Add CA cert UI -- *(ui)* New copy button component -- *(ui)* Use new copy button component everywhere -- *(ui)* Improve server advanced view -- *(migration)* Add CN and alternative names to DB -- *(databases)* Add CA SSL crt location to Postgres URLs -- *(ssl)* Improve ssl generation -- *(ssl)* Regenerate SSL certs job -- *(ssl)* Regenerate certificate and valid until UI -- *(ssl)* Regenerate CA cert and all other certs logic -- *(ssl)* Add full MySQL SSL Support -- *(ssl)* Add full MariaDB SSL support -- *(ssl)* Add `openssl.conf` to configure SSL extension properly -- *(ssl)* Improve SSL generation and security a lot -- *(ssl)* Check for SSL renewal twice daily -- *(ssl)* Add SSL relationships to all DBs -- Add full SSL support to MongoDB -- *(ssl)* Fix some issues and improve ssl generation helper -- *(ssl)* Ability to create `.pem` certs and add `clientAuth` to `extendedKeyUsage` -- *(ssl)* New modes for MongoDB and get `caCert` and `mountPath` correctly -- *(ssl)* Full SSL support for Redis -- New mode implementation for MongoDB -- *(ssl)* Improve Redis and remove modes -- Full SSL support for DrangonflyDB -- SSL notification -- *(github-source)* Enhance GitHub App configuration with manual and private key support -- *(ui)* Improve GitHub repository selection and styling -- *(database)* Implement two-step confirmation for database deletion -- *(assets)* Add new SVG logo for Coolify -- *(install)* Enhance Docker address pool configuration and validation -- *(install)* Improve Docker address pool management and service restart logic -- *(install)* Add missing env variable to install script -- *(LocalFileVolume)* Add binary file detection and update UI logic -- *(templates)* Change glance for v0.7 -- *(templates)* Add Freescout service template -- *(service)* Add Evolution API template -- *(service)* Add evolution-api and neon-ws-proxy templates -- *(svg)* Add coolify and evolution-api SVG logos -- *(api)* Add api to create custom services -- *(api)* Separate create and one-click routes -- *(api)* Update Services api routes and handlers -- *(api)* Unify service creation endpoint and enhance validation -- *(notifications)* Add discord ping functionality and settings -- *(user)* Implement session deletion on password reset -- *(github)* Enhance repository loading and validation in applications - -### 🐛 Bug Fixes - -- *(api)* Docker compose based apps creationg through api -- *(database)* Improve database type detection for Supabase Postgres images -- *(ssl)* Permission of ssl crt and key inside the container -- *(ui)* Make sure file mounts do not showing the encrypted values -- *(ssl)* Make default ssl mode require not verify-full as it does not need a ca cert -- *(ui)* Select component should not always uses title case -- *(db)* SSL certificates table and model -- *(migration)* Ssl certificates table -- *(databases)* Fix database name users new `uuid` instead of DB one -- *(database)* Fix volume and file mounts and naming -- *(migration)* Store subjectAlternativeNames as a json array in the db -- *(ssl)* Make sure the subjectAlternativeNames are unique and stored correctly -- *(ui)* Certificate expiration data is null before starting the DB -- *(deletion)* Fix DB deletion -- *(ssl)* Improve SSL cert file mounts -- *(ssl)* Always create ca crt on disk even if it is already there -- *(ssl)* Use mountPath parameter not a hardcoded path -- *(ssl)* Use 1 instead of on for mysql -- *(ssl)* Do not remove SSL directory -- *(ssl)* Wrong ssl cert is loaded to the server and UI error when regenerating SSL -- *(ssl)* Make sure when regenerating the CA cert it is not overwritten with a server cert -- *(ssl)* Regenerating certs for a specific DB -- *(ssl)* Fix MariaDB and MySQL need CA cert -- *(ssl)* Add mount path to DB to fix regeneration of certs -- *(ssl)* Fix SSL regeneration to sign with CA cert and use mount path -- *(ssl)* Get caCert correctly -- *(ssl)* Remove caCert even if it is a folder by accident -- *(ssl)* Ger caCert and `mountPath` correctly -- *(ui)* Only show Regenerate SSL Certificates button when there is a cert -- *(ssl)* Server id -- *(ssl)* When regenerating SSL certs the cert is not singed with the new CN -- *(ssl)* Adjust ca paths for MySQL -- *(ssl)* Remove mode selection for MariaDB as it is not supported -- *(ssl)* Permission issue with MariDB cert and key and paths -- *(ssl)* Rename Redis mode to verify-ca as it is not verify-full -- *(ui)* Remove unused mode for MongoDB -- *(ssl)* KeyDB port and caCert args are missing -- *(ui)* Enable SSL is not working correctly for KeyDB -- *(ssl)* Add `--tls` arg to DrangflyDB -- *(notification)* Always send SSL notifications -- *(database)* Change default value of enable_ssl to false for multiple tables -- *(ui)* Correct grammatical error in 404 page -- *(seeder)* Update GitHub app name in GithubAppSeeder -- *(plane)* Update APP_RELEASE to v0.25.2 in environment configuration -- *(domain)* Dispatch refreshStatus event after successful domain update -- *(database)* Correct container name generation for service databases -- *(database)* Limit container name length for database proxy -- *(database)* Handle unsupported database types in StartDatabaseProxy -- *(database)* Simplify container name generation in StartDatabaseProxy -- *(install)* Handle potential errors in Docker address pool configuration -- *(backups)* Retention settings -- *(redis)* Set default redis_username for new instances -- *(core)* Improve instantSave logic and error handling -- *(general)* Correct link to framework specific documentation -- *(core)* Redirect healthcheck route for dockercompose applications -- *(api)* Use name from request payload -- *(issue#4746)* Do not use setGitImportSettings inside of generateGitLsRemoteCommands -- Correct some spellings -- *(service)* Replace deprecated credentials env variables on keycloak service -- *(keycloak)* Update keycloak image version to 26.1 -- *(console)* Handle missing root user in password reset command -- *(ssl)* Handle missing CA certificate in SSL regeneration job -- *(copy-button)* Ensure text is safely passed to clipboard - -### 💼 Other - -- Bump Coolify to 4.0.0-beta.400 -- *(migration)* Add SSL fields to database tables -- SSL Support for KeyDB - -### 🚜 Refactor - -- *(ui)* Unhide log toggle in application settings -- *(nginx)* Streamline default Nginx configuration and improve error handling -- *(install)* Clean up install script and enhance Docker installation logic -- *(ScheduledTask)* Clean up code formatting and remove unused import -- *(app)* Remove unused MagicBar component and related code -- *(database)* Streamline SSL configuration handling across database types -- *(application)* Streamline healthcheck parsing from Dockerfile -- *(notifications)* Standardize getRecipients method signatures -- *(configuration)* Centralize configuration management in ConfigurationRepository -- *(docker)* Update image references to use centralized registry URL -- *(env)* Add centralized registry URL to environment configuration -- *(storage)* Simplify file storage iteration in Blade template -- *(models)* Add is_directory attribute to LocalFileVolume model -- *(modal)* Add ignoreWire attribute to modal-confirmation component -- *(invite-link)* Adjust layout for better responsiveness in form -- *(invite-link)* Enhance form layout for improved responsiveness -- *(network)* Enhance docker network creation with ipv6 fallback -- *(network)* Check for existing coolify network before creation -- *(database)* Enhance encryption process for local file volumes - -### 📚 Documentation - -- Update changelog -- Update changelog -- *(CONTRIBUTING)* Add note about Laravel Horizon accessibility -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(migration)* Remove unused columns -- *(ssl)* Improve code in ssl helper -- *(migration)* Ssl cert and key should not be nullable -- *(ssl)* Rename CA cert to `coolify-ca.crt` because of conflicts -- Rename ca crt folder to ssl -- *(ui)* Improve valid until handling -- Improve code quality suggested by code rabbit -- *(supabase)* Update Supabase service template and Postgres image version -- *(versions)* Update version numbers for coolify and nightly - -## [4.0.0-beta.398] - 2025-03-01 - -### 🚀 Features - -- *(billing)* Add Stripe past due subscription status tracking -- *(ui)* Add past due subscription warning banner - -### 🐛 Bug Fixes - -- *(billing)* Restrict Stripe subscription status update to 'active' only - -### 💼 Other - -- Bump Coolify to 4.0.0-beta.398 - -### 🚜 Refactor - -- *(billing)* Enhance Stripe subscription status handling and notifications - -## [4.0.0-beta.397] - 2025-02-28 - -### 🐛 Bug Fixes - -- *(billing)* Handle 'past_due' subscription status in Stripe processing -- *(revert)* Label parsing -- *(helpers)* Initialize command variable in parseCommandFromMagicEnvVariable - -### 📚 Documentation - -- Update changelog - -## [4.0.0-beta.396] - 2025-02-28 - -### 🚀 Features - -- *(ui)* Add wire:key to two-step confirmation settings -- *(database)* Add index to scheduled task executions for improved query performance -- *(database)* Add index to scheduled database backup executions - -### 🐛 Bug Fixes - -- *(core)* Production dockerfile -- *(ui)* Update storage configuration guidance link -- *(ui)* Set default SMTP encryption to starttls -- *(notifications)* Correct environment URL path in application notifications -- *(config)* Update default PostgreSQL host to coolify-db instead of postgres -- *(docker)* Improve Docker compose file validation process -- *(ui)* Restrict service retrieval to current team -- *(core)* Only validate custom compose files -- *(mail)* Set default mailer to array when not specified -- *(ui)* Correct redirect routes after task deletion -- *(core)* Adding a new server should not try to make the default docker network -- *(core)* Clean up unnecessary files during application image build -- *(core)* Improve label generation and merging for applications and services - -### 💼 Other - -- Bump all dependencies (#5216) - -### 🚜 Refactor - -- *(ui)* Simplify file storage modal confirmations -- *(notifications)* Improve transactional email settings handling -- *(scheduled-tasks)* Improve scheduled task creation and management - -### 📚 Documentation - -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- Bump helper and realtime version - -## [4.0.0-beta.395] - 2025-02-22 - -### 📚 Documentation - -- Update changelog - -## [4.0.0-beta.394] - 2025-02-17 - -### 📚 Documentation - -- Update changelog - -## [4.0.0-beta.393] - 2025-02-15 - -### 📚 Documentation - -- Update changelog - -## [4.0.0-beta.392] - 2025-02-13 - -### 🚀 Features - -- *(ui)* Add top padding to pricing plans view -- *(core)* Add error logging and cron parsing to docker/server schedules -- *(core)* Prevent using servers with existing resources as build servers -- *(ui)* Add textarea switching option in service compose editor - -### 🐛 Bug Fixes - -- Pull latest image from registry when using build server -- *(deployment)* Improve server selection for deployment cancellation -- *(deployment)* Improve log line rendering and formatting -- *(s3-storage)* Optimize team admin notification query -- *(core)* Improve connection testing with dynamic disk configuration for s3 backups -- *(core)* Update service status refresh event handling -- *(ui)* Adjust polling intervals for database and service status checks -- *(service)* Update Fider service template healthcheck command -- *(core)* Improve server selection error handling in Docker component -- *(core)* Add server functionality check before dispatching container status -- *(ui)* Disable sticky scroll in Monaco editor -- *(ui)* Add literal and multiline env support to services. -- *(services)* Owncloud docs link -- *(template)* Remove db-migration step from `infisical.yaml` (#5209) -- *(service)* Penpot (#5047) - -### 🚜 Refactor - -- Use pull flag on docker compose up - -### 📚 Documentation - -- Update changelog -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- Rollback Coolify version to 4.0.0-beta.392 -- Bump Coolify version to 4.0.0-beta.393 -- Bump Coolify version to 4.0.0-beta.394 -- Bump Coolify version to 4.0.0-beta.395 -- Bump Coolify version to 4.0.0-beta.396 -- *(services)* Update zipline to use new Database env var. (#5210) -- *(service)* Upgrade authentik service -- *(service)* Remove unused env from zipline - -## [4.0.0-beta.391] - 2025-02-04 - -### 🚀 Features - -- Add application api route -- Container logs -- Remove ansi color from log -- Add lines query parameter -- *(changelog)* Add git cliff for automatic changelog generation -- *(workflows)* Improve changelog generation and workflows -- *(ui)* Add periodic status checking for services -- *(deployment)* Ensure private key is stored in filesystem before deployment -- *(slack)* Show message title in notification previews (#5063) -- *(i18n)* Add Arabic translations (#4991) -- *(i18n)* Add French translations (#4992) -- *(services)* Update `service-templates.json` - -### 🐛 Bug Fixes - -- *(core)* Improve deployment failure Slack notification formatting -- *(core)* Update Slack notification formatting to use bold correctly -- *(core)* Enhance Slack deployment success notification formatting -- *(ui)* Simplify service templates loading logic -- *(ui)* Align title and add button vertically in various views -- Handle pullrequest:updated for reliable preview deployments -- *(ui)* Fix typo on team page (#5105) -- Cal.com documentation link give 404 (#5070) -- *(slack)* Notification settings URL in `HighDiskUsage` message (#5071) -- *(ui)* Correct typo in Storage delete dialog (#5061) -- *(lang)* Add missing italian translations (#5057) -- *(service)* Improve duplicati.yaml (#4971) -- *(service)* Links in homepage service (#5002) -- *(service)* Added SMTP credentials to getoutline yaml template file (#5011) -- *(service)* Added `KEY` Variable to Beszel Template (#5021) -- *(cloudflare-tunnels)* Dead links to docs (#5104) -- System-wide GitHub apps (#5114) - -### 🚜 Refactor - -- Simplify service start and restart workflows - -### 📚 Documentation - -- *(services)* Reword nitropage url and slogan -- *(readme)* Add Convex to special sponsors section -- Update changelog - -### ⚙️ Miscellaneous Tasks - -- *(config)* Increase default PHP memory limit to 256M -- Add openapi response -- *(workflows)* Make naming more clear and remove unused code -- Bump Coolify version to 4.0.0-beta.392/393 -- *(ci)* Update changelog generation workflow to target 'next' branch -- *(ci)* Update changelog generation workflow to target main branch - -## [4.0.0-beta.390] - 2025-01-28 - -### 🚀 Features - -- *(template)* Add Open Web UI -- *(templates)* Add Open Web UI service template -- *(ui)* Update GitHub source creation advanced section label -- *(core)* Add dynamic label reset for application settings -- *(ui)* Conditionally enable advanced application settings based on label readonly status -- *(env)* Added COOLIFY_RESOURCE_UUID environment variable -- *(vite)* Add Cloudflare async script and style tag attributes -- *(meta)* Add comprehensive SEO and social media meta tags -- *(core)* Add name to default proxy configuration - -### 🐛 Bug Fixes - -- *(ui)* Update database control UI to check server functionality before displaying actions -- *(ui)* Typo in upgrade message -- *(ui)* Cloudflare tunnel configuration should be an info, not a warning -- *(s3)* DigitalOcean storage buckets do not work -- *(ui)* Correct typo in container label helper text -- Disable certain parts if readonly label is turned off -- Cleanup old scheduled_task_executions -- Validate cron expression in Scheduled Task update -- *(core)* Check cron expression on save -- *(database)* Detect more postgres database image types -- *(templates)* Update service templates -- Remove quotes in COOLIFY_CONTAINER_NAME -- *(templates)* Update Trigger.dev service templates with v3 configuration -- *(database)* Adjust MongoDB restore command and import view styling -- *(core)* Improve public repository URL parsing for branch and base directory -- *(core)* Increase HTTP/2 max concurrent streams to 250 (default) -- *(ui)* Update docker compose file helper text to clarify repository modification -- *(ui)* Skip SERVICE_FQDN and SERVICE_URL variables during update -- *(core)* Stopping database is not disabling db proxy -- *(core)* Remove --remove-orphans flag from proxy startup command to prevent other proxy deletions (db) -- *(api)* Domain check when updating domain -- *(ui)* Always redirect to dashboard after team switch -- *(backup)* Escape special characters in database backup commands - -### 💼 Other - -- Trigger.dev templates - wrong key length issue -- Trigger.dev template - missing ports and wrong env usage -- Trigger.dev template - fixed otel config -- Trigger.dev template - fixed otel config -- Trigger.dev template - fixed port config - -### 🚜 Refactor - -- *(s3)* Improve S3 bucket endpoint formatting -- *(vite)* Improve environment variable handling in Vite configuration -- *(ui)* Simplify GitHub App registration UI and layout - -### ⚙️ Miscellaneous Tasks - -- *(version)* Bump Coolify version to 4.0.0-beta.391 - -### ◀️ Revert - -- Remove Cloudflare async tag attributes - -## [4.0.0-beta.389] - 2025-01-23 - -### 🚀 Features - -- *(docs)* Update tech stack -- *(terminal)* Show terminal unavailable if the container does not have a shell on the global terminal UI -- *(ui)* Improve deployment UI - -### 🐛 Bug Fixes - -- *(service)* Infinite loading and lag with invoiceninja service (#4876) -- *(service)* Invoiceninja service -- *(workflows)* `Waiting for changes` label should also be considered and improved messages -- *(workflows)* Remove tags only if the PR has been merged into the main branch -- *(terminal)* Terminal shows that it is not available, even though it is -- *(labels)* Docker labels do not generated correctly -- *(helper)* Downgrade Nixpacks to v1.29.0 -- *(labels)* Generate labels when they are empty not when they are already generated -- *(storage)* Hetzner storage buckets not working - -### 📚 Documentation - -- Add TECH_STACK.md (#4883) - -### ⚙️ Miscellaneous Tasks - -- *(versions)* Update coolify versions to v4.0.0-beta.389 -- *(core)* EnvironmentVariable Model now extends BaseModel to remove duplicated code -- *(versions)* Update coolify versions to v4.0.0-beta.3909 - -## [4.0.0-beta.388] - 2025-01-22 - -### 🚀 Features - -- *(core)* Add SOURCE_COMMIT variable to build environment in ApplicationDeploymentJob -- *(service)* Update affine.yaml with AI environment variables (#4918) -- *(service)* Add new service Flipt (#4875) - -### 🐛 Bug Fixes - -- *(core)* Update environment variable generation logic in ApplicationDeploymentJob to handle different build packs -- *(env)* Shared variables can not be updated -- *(ui)* Metrics stuck in loading state -- *(ui)* Use `wire:navigate` to navigate to the server settings page -- *(service)* Plunk API & health check endpoint (#4925) - -## [4.0.0-beta.386] - 2025-01-22 - -### 🐛 Bug Fixes - -- *(redis)* Update environment variable keys from standalone_redis_id to resourceable_id -- *(routes)* Local API docs not available on domain or IP -- *(routes)* Local API docs not available on domain or IP -- *(core)* Update application_id references to resourable_id and resourable_type for Nixpacks configuration -- *(core)* Correct spelling of 'resourable' to 'resourceable' in Nixpacks configuration for ApplicationDeploymentJob -- *(ui)* Traefik dashboard url not working -- *(ui)* Proxy status badge flashing during navigation - -### 🚜 Refactor - -- *(workflows)* Replace jq with PHP script for version retrieval in workflows - -### ⚙️ Miscellaneous Tasks - -- *(dep)* Bump helper version to 1.0.5 -- *(docker)* Add blank line for readability in Dockerfile -- *(versions)* Update coolify versions to v4.0.0-beta.388 -- *(versions)* Update coolify versions to v4.0.0-beta.389 and add helper version retrieval script - -## [4.0.0-beta.385] - 2025-01-21 - -### 🚀 Features - -- *(core)* Wip version of coolify.json - -### 🐛 Bug Fixes - -- *(email)* Transactional email sending -- *(ui)* Add missing save button for new Docker Cleanup page -- *(ui)* Show preview deployment environment variables -- *(ui)* Show error on terminal if container has no shell (bash/sh) -- *(parser)* Resource URL should only be parsed if there is one -- *(core)* Compose parsing for apps - -### ⚙️ Miscellaneous Tasks - -- *(dep)* Bump nixpacks version -- *(dep)* Version++ - -## [4.0.0-beta.384] - 2025-01-21 - -### 🐛 Bug Fixes - -- *(ui)* Backups link should not redirected to general -- Envs with special chars during build -- *(db)* `finished_at` timestamps are not set for existing deployments -- Load service templates on cloud - -## [4.0.0-beta.383] - 2025-01-20 - -### 🐛 Bug Fixes - -- *(service)* Add healthcheck to Cloudflared service (#4859) -- Remove wire:navigate from import backups - -## [4.0.0-beta.382] - 2025-01-17 - -### 🚀 Features - -- Add log file check message in upgrade script for better troubleshooting -- Add root user details to install script - -### 🐛 Bug Fixes - -- Create the private key before the server in the prod seeder -- Update ProductionSeeder to check for private key instead of server's private key -- *(ui)* Missing underline for docs link in the Swarm section (#4860) -- *(service)* Change chatwoot service postgres image from `postgres:12` to `pgvector/pgvector:pg12` -- Docker image parser -- Add public key attribute to privatekey model -- Correct service update logic in Docker Compose parser -- Update CDN URL in install script to point to nightly version - -### 🚜 Refactor - -- Comment out RootUserSeeder call in ProductionSeeder for clarity -- Streamline ProductionSeeder by removing debug logs and unnecessary checks, while ensuring essential seeding operations remain intact -- Remove debug echo statements from Init command to clean up output and improve readability - -## [4.0.0-beta.381] - 2025-01-17 - -### 🚀 Features - -- Able to import full db backups for pg/mysql/mariadb -- Restore backup from server file -- Docker volume data cloning -- Move volume data cloning to a Job -- Volume cloning for ResourceOperations -- Remote server volume cloning -- Add horizon server details to queue -- Enhance horizon:manage command with worker restart check -- Add is_coolify_host to the server api responses -- DB migration for Backup retention -- UI for backup retention settings -- New global s3 and local backup deletion function -- Use new backup deletion functions -- Add calibre-web service -- Add actual-budget service -- Add rallly service -- Template for Gotenberg, a Docker-powered stateless API for PDF files -- Enhance import command options with additional guidance and improved checkbox label -- Purify for better sanitization -- Move docker cleanup to its own tab -- DB and Model for docker cleanup executions -- DockerCleanupExecutions relationship -- DockerCleanupDone event -- Get command and output for logs from CleanupDocker -- New sidebar menu and order -- Docker cleanup executions UI -- Add execution log to dockerCleanupJob -- Improve deployment UI -- Root user envs and seeding -- Email, username and password validation when they are set via envs -- Improved error handling and log output -- Add root user configuration variables to production environment - -### 🐛 Bug Fixes - -- Compose envs -- Scheduled tasks and backups are executed by server timezone. -- Show backup timezone on the UI -- Disappearing UI after livewire event received -- Add default vector db for anythingllm -- We need XSRF-TOKEN for terminal -- Prevent default link behavior for resource and settings actions in dashboard -- Increase default php memory limit -- Show if only build servers are added to your team -- Update Livewire button click method to use camelCase -- Local dropzonejs -- Import backups due to js stuff should not be navigated -- Install inetutils on Arch Linux -- Use ip in place of hostname from inetutils in arch -- Update import command to append file redirection for database restoration -- Ui bug on pw confirmation -- Exclude system and computed fields from model replication -- Service cloning on a separate server -- Application cloning -- `Undefined variable $fs_path` for databases -- Service and database cloning and label generation -- Labels and URL generation when cloning -- Clone naming for different database data volumes -- Implement all the cloneMe changes for ResourceOperations as well -- Volume and fileStorages cloning -- View text and helpers -- Teable -- Trigger with external db -- Set `EXPERIMENTAL_FEATURES` to false for labelstudio -- Monaco editor disabled state -- Edge case where executions could be null -- Create destination properly -- Getcontainer status should timeout after 30s -- Enable response for temporary unavailability in sentinel push endpoint -- Use timeout in cleanup resources -- Add timeout to sentinel process checks for improved reliability -- Horizon job checker -- Update response message for sentinel push route -- Add own servers on cloud -- Application deployment -- Service update statsu -- If $SERVICE found in the service specific configuration, then search for it in the db -- Instance wide GitHub apps are not available on other teams then the source team -- Function calls -- UI -- Deletion of single backup -- Backup job deletion - delete all backups from s3 and local -- Use new removeOldBackups function -- Retention functions and folder deletion for local backups -- Storage retention setting -- Db without s3 should still backup -- Wording -- `Undefined variable $service` when creating a new service -- Nodebb service -- Calibre-web service -- Rallly and actualbudget service -- Removed container_name -- Added healthcheck for gotenberg template -- Gotenberg -- *(template)* Gotenberg healthcheck, use /health instead of /version -- Use wire:navigate on sidebar -- Use wire:navigate on dashboard -- Use wire:navigate on projects page -- More wire:navigate -- Even more wire:navigate -- Service navigation -- Logs icons everywhere + terminal -- Redis DB should use the new resourceable columns -- Joomla service -- Add back letters to prod password requirement -- Check System and GitHub time and throw and error if it is over 50s out of sync -- Error message and server time getting -- Error rendering -- Render html correctly now -- Indent -- Potential fix for permissions update -- Expiration time claim ('exp') must be a numeric value -- Sanitize html error messages -- Production password rule and cleanup code -- Use json as it is just better than string for huge amount of logs -- Use `wire:navigate` on server sidebar -- Use finished_at for the end time instead of created_at -- Cancelled deployments should not show end and duration time -- Redirect to server index instead of show on error in Advanced and DockerCleanup components -- Disable registration after creating the root user -- RootUserSeeder -- Regex username validation -- Add spacing around echo outputs -- Success message -- Silent return if envs are empty or not set. - -### 💼 Other - -- Arrrrr -- Dep -- Docker dep - -### 🚜 Refactor - -- Rename parameter in DatabaseBackupJob for clarity -- Improve checkbox component accessibility and styling -- Remove unused tags method from ApplicationDeploymentJob -- Improve deployment status check in isAnyDeploymentInprogress function -- Extend HorizonServiceProvider from HorizonApplicationServiceProvider -- Streamline job status retrieval and clean up repository interface -- Enhance ApplicationDeploymentJob and HorizonServiceProvider for improved job handling -- Remove commented-out unsubscribe route from API -- Update redirect calls to use a consistent navigation method in deployment functions -- AppServiceProvider -- Github.php -- Improve data formatting and UI - -### ⚙️ Miscellaneous Tasks - -- Improve Penpot healthchecks -- Switch up readonly lables to make more sense -- Remove unused computed fields -- Use the new job dispatch -- Disable volume data cloning for now -- Improve code -- Lowcoder service naming -- Use new functions -- Improve error styling -- Css -- More css as it still looks like shit -- Final css touches -- Ajust time to 50s (tests done) -- Remove debug log, finally found it -- Remove more logging -- Remove limit on commit message -- Remove dayjs -- Remove unused code and fix import - -## [4.0.0-beta.380] - 2024-12-27 - -### 🚀 Features - -- New ServerReachabilityChanged event -- Use new ServerReachabilityChanged event instead of isDirty -- Add infomaniak oauth -- Add server disk usage check frequency -- Add environment_uuid support and update API documentation -- Add service/resource/project labels -- Add coolify.environment label -- Add database subtype -- Migrate to new encryption options -- New encryption options - -### 🐛 Bug Fixes - -- Render html on error page correctly -- Invalid API response on missing project -- Applications API response code + schema -- Applications API writing to unavailable models -- If an init script is renamed the old version is still on the server -- Oauthseeder -- Compose loading seq -- Resource clone name + volume name generation -- Update Dockerfile entrypoint path to /etc/entrypoint.d -- Debug mode -- Unreachable notifications -- Remove duplicated ServerCheckJob call -- Few fixes and use new ServerReachabilityChanged event -- Use serverStatus not just status -- Oauth seeder -- Service ui structure -- Check port 8080 and fallback to 80 -- Refactor database view -- Always use docker cleanup frequency -- Advanced server UI -- Html css -- Fix domain being override when update application -- Use nixpacks predefined build variables, but still could update the default values from Coolify -- Use local monaco-editor instead of Cloudflare -- N8n timezone -- Smtp encryption -- Bind() to 0.0.0.0:80 failed -- Oauth seeder -- Unreachable notifications -- Instance settings migration -- Only encrypt instance email settings if there are any -- Error message -- Update healthcheck and port configurations to use port 8080 - -### 🚜 Refactor - -- Rename `coolify.environment` to `coolify.environmentName` - -### ⚙️ Miscellaneous Tasks - -- Regenerate API spec, removing notification fields -- Remove ray debugging -- Version ++ - -## [4.0.0-beta.378] - 2024-12-13 - -### 🐛 Bug Fixes - -- Monaco editor light and dark mode switching -- Service status indicator + oauth saving -- Socialite for azure and authentik -- Saving oauth -- Fallback for copy button -- Copy the right text -- Maybe fallback is now working -- Only show copy button on secure context - -## [4.0.0-beta.377] - 2024-12-13 - -### 🚀 Features - -- Add deploy-only token permission -- Able to deploy without cache on every commit -- Update private key nam with new slug as well -- Allow disabling default redirect, set status to 503 -- Add TLS configuration for default redirect in Server model -- Slack notifications -- Introduce root permission -- Able to download schedule task logs -- Migrate old email notification settings from the teams table -- Migrate old discord notification settings from the teams table -- Migrate old telegram notification settings from the teams table -- Add slack notifications to a new table -- Enable success messages again -- Use new notification stuff inside team model -- Some more notification settings and better defaults -- New email notification settings -- New shared function name `is_transactional_emails_enabled()` -- New shared notifications functions -- Email Notification Settings Model -- Telegram notification settings Model -- Discord notification settings Model -- Slack notification settings Model -- New Discord notification UI -- New Slack notification UI -- New telegram UI -- Use new notification event names -- Always sent notifications -- Scheduled task success notification -- Notification trait -- Get discord Webhook form new table -- Get Slack Webhook form new table -- Use new table or instance settings for email -- Use new place for settings and topic IDs for telegram -- Encrypt instance email settings -- Use encryption in instance settings model -- Scheduled task success and failure notifications -- Add docker cleanup success and failure notification settings columns -- UI for docker cleanup success and failure notification -- Docker cleanup email views -- Docker cleanup success and failure notification files -- Scheduled task success email -- Send new docker cleanup notifications -- :passport_control: integrate Authentik authentication with Coolify -- *(notification)* Add Pushover -- Add seeder command and configuration for database seeding -- Add new password magic env with symbols -- Add documenso service - -### 🐛 Bug Fixes - -- Resolve undefined searchInput reference in Alpine.js component -- URL and sync new app name -- Typos and naming -- Client and webhook secret disappear after sync -- Missing `mysql_password` API property -- Incorrect MongoDB init API property -- Old git versions does not have --cone implemented properly -- Don't allow editing traefik config -- Restart proxy -- Dev mode -- Ui -- Display actual values for disk space checks in installer script -- Proxy change behaviour -- Add warning color -- Import NotificationSlack correctly -- Add middleware to new abilities, better ux for selecting permissions, etc. -- Root + read:sensive could read senstive data with a middlewarew -- Always have download logs button on scheduled tasks -- Missing css -- Development image -- Dockerignore -- DB migration error -- Drop all unused smtp columns -- Backward compatibility -- Email notification channel enabled function -- Instance email settins -- Make sure resend is false if SMTP is true and vice versa -- Email Notification saving -- Slack and discord url now uses text filed because encryption makes the url very long -- Notification trait -- Encryption fixes -- Docker cleanup email template -- Add missing deployment notifications to telegram -- New docker cleanup settings are now saved to the DB correctly -- Ui + migrations -- Docker cleanup email notifications -- General notifications does not go through email channel -- Test notifications to only send it to the right channel -- Remove resale_license from db as well -- Nexus service -- Fileflows volume names -- --cone -- Provider error -- Database migration -- Seeder -- Migration call -- Slack helper -- Telegram helper -- Discord helper -- Telegram topic IDs -- Make pushover settings more clear -- Typo in pushover user key -- Use Livewire refresh method and lock properties -- Create pushover settings for existing teams -- Update token permission check from 'write' to 'root' -- Pushover -- Oauth seeder -- Correct heading display for OAuth settings in settings-oauth.blade.php -- Adjust spacing in login form for improved layout -- Services env values should be sensitive -- Documenso -- Dolibarr -- Typo -- Update OauthSettingSeeder to handle new provider definitions and ensure authentik is recreated if missing -- Improve OauthSettingSeeder to correctly delete non-existent providers and ensure proper handling of provider definitions -- Encrypt resend API key in instance settings -- Resend api key is already a text column - -### 💼 Other - -- Test rename GitHub app -- Checkmate service and fix prowlar slogan (too long) - -### 🚜 Refactor - -- Update Traefik configuration for improved security and logging -- Improve proxy configuration and code consistency in Server model -- Rename name method to sanitizedName in BaseModel for clarity -- Improve migration command and enhance application model with global scope and status checks -- Unify notification icon -- Remove unused Azure and Authentik service configurations from services.php -- Change email column types in instance_settings migration from string to text -- Change OauthSetting creation to updateOrCreate for better handling of existing records - -### ⚙️ Miscellaneous Tasks - -- Regenerate openapi spec -- Composer dep bump -- Dep bump -- Upgrade cloudflared and minio -- Remove comments and improve DB column naming -- Remove unused seeder -- Remove unused waitlist stuff -- Remove wired.php (not used anymore) -- Remove unused resale license job -- Remove commented out internal notification -- Remove more waitlist stuff -- Remove commented out notification -- Remove more waitlist stuff -- Remove unused code -- Fix typo -- Remove comment out code -- Some reordering -- Remove resale license reference -- Remove functions from shared.php -- Public settings for email notification -- Remove waitlist redirect -- Remove log -- Use new notification trait -- Remove unused route -- Remove unused email component -- Comment status changes as it is disabled for now -- Bump dep -- Reorder navbar -- Rename topicID to threadId like in the telegram API response -- Update PHP configuration to set memory limit using environment variable - -## [4.0.0-beta.376] - 2024-12-07 - -### 🐛 Bug Fixes - -- Api endpoint - -## [4.0.0-beta.374] - 2024-12-03 - -### 🐛 Bug Fixes - -- Application view loading -- Postiz service -- Only able to select the right keys -- Test email should not be required -- A few inputs - -### 🧪 Testing - -- Setup database for upcoming tests - -## [4.0.0-beta.372] - 2024-11-26 - -### 🚀 Features - -- Add MacOS template -- Add Windows template -- *(service)* :sparkles: add mealie -- Add hex magic env var - -### 🐛 Bug Fixes - -- Service generate includes yml files as well (haha) -- ServercheckJob should run every 5 minutes on cloud -- New resource icons -- Search should be more visible on scroll on new resource -- Logdrain settings -- Ui -- Email should be retried with backoff -- Alpine in body layout - -### 💼 Other - -- Caddy docker labels do not honor "strip prefix" option - -## [4.0.0-beta.371] - 2024-11-22 - -### 🐛 Bug Fixes - -- Improve helper text for metrics input fields -- Refine helper text for metrics input fields -- If mux conn fails, still use it without mux + save priv key with better logic -- Migration -- Always validate ssh key -- Make sure important jobs/actions are running on high prio queue -- Do not send internal notification for backups and status jobs -- Validateconnection -- View issue -- Heading -- Remove mux cleanup -- Db backup for services -- Version should come from constants + fix stripe webhook error reporting -- Undefined variable -- Remove version.php as everything is coming from constants.php -- Sentry error -- Websocket connections autoreconnect -- Sentry error -- Sentry -- Empty server API response -- Incorrect server API patch response -- Missing `uuid` parameter on server API patch -- Missing `settings` property on servers API -- Move servers API `delete_unused_*` properties -- Servers API returning `port` as a string -> integer -- Only return server uuid on server update - -## [4.0.0-beta.370] - 2024-11-15 - -### 🐛 Bug Fixes - -- Modal (+ add) on dynamic config was not opening, removed x-cloak -- AUTOUPDATE + checkbox opacity - -## [4.0.0-beta.369] - 2024-11-15 - -### 🐛 Bug Fixes - -- Modal-input - -## [4.0.0-beta.368] - 2024-11-15 - -### 🚀 Features - -- Check local horizon scheduler deployments -- Add internal api docs to /docs/api with auth -- Add proxy type change to create/update apis - -### 🐛 Bug Fixes - -- Show proper error message on invalid Git source -- Convert HTTP to SSH source when using deploy key on GitHub -- Cloud + stripe related -- Terminal view loading in async -- Cool 500 error (thanks hugodos) -- Update schema in code decorator -- Openapi docs -- Add tests for git url converts -- Minio / logto url generation -- Admin view -- Min docker version 26 -- Pull latest service-templates.json on init -- Workflow files for coolify build -- Autocompletes -- Timezone settings validation -- Invalid tz should not prevent other jobs to be executed -- Testing-host should be built locally -- Poll with modal issue -- Terminal opening issue -- If service img not found, use github as a source -- Fallback to local coolify.png -- Gather private ips -- Cf tunnel menu should be visible when server is not validated -- Deployment optimizations -- Init script + optimize laravel -- Default docker engine version + fix install script -- Pull helper image on init -- SPA static site default nginx conf - -### 💼 Other - -- Https://github.com/coollabsio/coolify/issues/4186 -- Separate resources by type in projects view -- Improve s3 add view - -### ⚙️ Miscellaneous Tasks - -- Update dep - -## [4.0.0-beta.365] - 2024-11-11 - -### 🚀 Features - -- Custom nginx configuration for static deployments + fix 404 redirects in nginx conf - -### 🐛 Bug Fixes - -- Trigger.dev db host & sslmode=disable -- Manual update should be executed only once + better UX -- Upgrade.sh -- Missing privateKey - -## [4.0.0-beta.364] - 2024-11-08 - -### 🐛 Bug Fixes - -- Define separate volumes for mattermost service template -- Github app name is too long -- ServerTimezone update - -### ⚙️ Miscellaneous Tasks - -- Edit www helper - -## [4.0.0-beta.363] - 2024-11-08 - -### 🚀 Features - -- Add Firefox template -- Add template for Wiki.js -- Add upgrade logs to /data/coolify/source - -### 🐛 Bug Fixes - -- Saving resend api key -- Wildcard domain save -- Disable cloudflare tunnel on "localhost" - -## [4.0.0-beta.362] - 2024-11-08 - -### 🐛 Bug Fixes - -- Notifications ui -- Disable wire:navigate -- Confirmation Settings css for light mode -- Server wildcard - -## [4.0.0-beta.361] - 2024-11-08 - -### 🚀 Features - -- Add Transmission template -- Add transmission healhcheck -- Add zipline template -- Dify template -- Required envs -- Add EdgeDB -- Show warning if people would like to use sslip with https -- Add is shared to env variables -- Variabel sync and support shared vars -- Add notification settings to server_disk_usage -- Add coder service tamplate and logo -- Debug mode for sentinel -- Add jitsi template -- Add --gpu support for custom docker command - -### 🐛 Bug Fixes - -- Make sure caddy is not removed by cleanup -- Libretranslate -- Do not allow to change number of lines when streaming logs -- Plunk -- No manual timezones -- Helper push -- Format -- Add port metadata and Coolify magic to generate the domain -- Sentinel -- Metrics -- Generate sentinel url -- Only enable Sentinel for new servers -- Is_static through API -- Allow setting standalone redis variables via ENVs (team variables...) -- Check for username separately form password -- Encrypt all existing redis passwords -- Pull helper image on helper_version change -- Redis database user and password -- Able to update ipv4 / ipv6 instance settings -- Metrics for dbs -- Sentinel start fixed -- Validate sentinel custom URL when enabling sentinel -- Should be able to reset labels in read-only mode with manual click -- No sentinel for swarm yet -- Charts ui -- Volume -- Sentinel config changes restarts sentinel -- Disable sentinel for now -- Disable Sentinel temporarily -- Disable Sentinel temporarily for non-dev environments -- Access team's github apps only -- Admins should now invite owner -- Add experimental flag -- GenerateSentinelUrl method -- NumberOfLines could be null -- Login / register view -- Restart sentinel once a day -- Changing private key manually won't trigger a notification -- Grammar for helper -- Fix my own grammar -- Add telescope only in dev mode -- New way to update container statuses -- Only run server storage every 10 mins if sentinel is not active -- Cloud admin view -- Queries in kernel.php -- Lower case emails only -- Change emails to lowercase on init -- Do not error on update email -- Always authenticate with lowercase emails -- Dashboard refactor -- Add min/max length to input/texarea -- Remove livewire legacy from help view -- Remove unnecessary endpoints (magic) -- Transactional email livewire -- Destinations livewire refactor -- Refactor destination/docker view -- Logdrains validation -- Reworded -- Use Auth(), add new db proxy stop event refactor clickhouse view -- Add user/pw to db view -- Sort servers by name -- Keydb view -- Refactor tags view / remove obsolete one -- Send discord/telegram notifications on high job queue -- Server view refresh on validation -- ShowBoarding -- Show docker installation logs & ubuntu 24.10 notification -- Do not overlap servercheckjob -- Server limit check -- Server validation -- Clear route / view -- Only skip docker installation on 24.10 if its not installed -- For --gpus device support -- Db/service start should be on high queue -- Do not stop sentinel on Coolify restart -- Run resourceCheck after new serviceCheckJob -- Mongodb in dev -- Better invitation errors -- Loading indicator for db proxies -- Do not execute gh workflow on template changes -- Only use sentry in cloud -- Update packagejson of coolify-realtime + add lock file -- Update last online with old function -- Seeder should not start sentinel -- Start sentinel on seeder - -### 💼 Other - -- Add peppermint -- Loggy -- Add UI for redis password and username -- Wireguard-easy template - -### 📚 Documentation - -- Update link to deploy api docs - -### ⚙️ Miscellaneous Tasks - -- Add transmission template desc -- Update transmission docs link -- Update version numbers to 4.0.0-beta.360 in configuration files -- Update AWS environment variable names in unsend.yaml -- Update AWS environment variable names in unsend.yaml -- Update livewire/livewire dependency to version 3.4.9 -- Update version to 4.0.0-beta.361 -- Update Docker build and push actions to v6 -- Update Docker build and push actions to v6 -- Update Docker build and push actions to v6 -- Sync coolify-helper to dockerhub as well -- Push realtime to dockerhub -- Sync coolify-realtime to dockerhub -- Rename workflows -- Rename development to staging build -- Sync coolify-testing-host to dockerhbu -- Sync coolify prod image to dockerhub as well -- Update Docker version to 26.0 -- Update project resource index page -- Update project service configuration view - -## [4.0.0-beta.360] - 2024-10-11 - -### ⚙️ Miscellaneous Tasks - -- Update livewire/livewire dependency to version 3.4.9 - -## [4.0.0-beta.359] - 2024-10-11 - -### 🐛 Bug Fixes - -- Use correct env variable for invoice ninja password - -### ⚙️ Miscellaneous Tasks - -- Update laravel/horizon dependency to version 5.29.1 -- Update service extra fields to use dynamic keys - -## [4.0.0-beta.358] - 2024-10-10 - -### 🚀 Features - -- Add customHelper to stack-form -- Add cloudbeaver template -- Add ntfy template -- Add qbittorrent template -- Add Homebox template -- Add owncloud service and logo -- Add immich service -- Auto generate url -- Refactored to work with coolify auto env vars -- Affine service template and logo -- Add LibreTranslate template -- Open version in a new tab - -### 🐛 Bug Fixes - -- Signup -- Application domains should be http and https only -- Validate and sanitize application domains -- Sanitize and validate application domains - -### 💼 Other - -- Other DB options for freshrss -- Nextcloud MariaDB and MySQL versions - -### ⚙️ Miscellaneous Tasks - -- Fix form submission and keydown event handling in modal-confirmation.blade.php -- Update version numbers to 4.0.0-beta.359 in configuration files -- Disable adding default environment variables in shared.php - -## [4.0.0-beta.357] - 2024-10-08 - -### 🚀 Features - -- Add Mautic 4 and 5 to service templates -- Add keycloak template -- Add onedev template -- Improve search functionality in project selection - -### 🐛 Bug Fixes - -- Update mattermost image tag and add default port -- Remove env, change timezone -- Postgres healthcheck -- Azimutt template - still not working haha -- New parser with SERVICE_URL_ envs -- Improve service template readability -- Update password variables in Service model -- Scheduled database server -- Select server view - -### 💼 Other - -- Keycloak - -### ⚙️ Miscellaneous Tasks - -- Add mattermost logo as svg -- Add mattermost svg to compose -- Update version to 4.0.0-beta.357 - -## [4.0.0-beta.356] - 2024-10-07 - -### 🚀 Features - -- Add Argilla service configuration to Service model -- Add Invoice Ninja service configuration to Service model -- Project search on frontend -- Add ollama service with open webui and logo -- Update setType method to use slug value for type -- Refactor setType method to use slug value for type -- Refactor setType method to use slug value for type -- Add Supertokens template -- Add easyappointments service template -- Add dozzle template -- Adds forgejo service with runners - -### 🐛 Bug Fixes - -- Reset description and subject fields after submitting feedback -- Tag mass redeployments -- Service env orders, application env orders -- Proxy conf in dev -- One-click services -- Use local service-templates in dev -- New services -- Remove not used extra host -- Chatwoot service -- Directus -- Database descriptions -- Update services -- Soketi -- Select server view - -### 💼 Other - -- Update helper version -- Outline -- Directus -- Supertokens -- Supertokens json -- Rabbitmq -- Easyappointments -- Soketi -- Dozzle -- Windmill -- Coolify.json - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.356 -- Remove commented code for shared variable type validation -- Update MariaDB image to version 11 and fix service environment variable orders -- Update anythingllm.yaml volumes configuration -- Update proxy configuration paths for Caddy and Nginx in dev -- Update password form submission in modal-confirmation component -- Update project query to order by name in uppercase -- Update project query to order by name in lowercase -- Update select.blade.php with improved search functionality -- Add Nitropage service template and logo -- Bump coolify-helper version to 1.0.2 -- Refactor loadServices2 method and remove unused code -- Update version to 4.0.0-beta.357 -- Update service names and volumes in windmill.yaml -- Update version to 4.0.0-beta.358 -- Ignore .ignition.json files in Docker and Git - -## [4.0.0-beta.355] - 2024-10-03 - -### 🐛 Bug Fixes - -- Scheduled backup for services view -- Parser, espacing container labels - -### ⚙️ Miscellaneous Tasks - -- Update homarr service template and remove unnecessary code -- Update version to 4.0.0-beta.355 - -## [4.0.0-beta.354] - 2024-10-03 - -### 🚀 Features - -- Add it-tools service template and logo -- Add homarr service tamplate and logo - -### 🐛 Bug Fixes - -- Parse proxy config and check the set ports usage -- Update FQDN - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.354 -- Remove debug statement in Service model -- Remove commented code in Server model -- Fix application deployment queue filter logic -- Refactor modal-confirmation component -- Update it-tools service template and port configuration -- Update homarr service template and remove unnecessary code - -## [4.0.0-beta.353] - 2024-10-03 - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.353 -- Update service application view - -## [4.0.0-beta.352] - 2024-10-03 - -### 🐛 Bug Fixes - -- Service application view -- Add new supported database images - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.352 -- Refactor DatabaseBackupJob to handle missing team - -## [4.0.0-beta.351] - 2024-10-03 - -### 🚀 Features - -- Add strapi template - -### 🐛 Bug Fixes - -- Able to support more database dynamically from Coolify's UI -- Strapi template -- Bitcoin core template -- Api useBuildServer - -## [4.0.0-beta.349] - 2024-10-01 - -### 🚀 Features - -- Add command to check application deployment queue -- Support Hetzner S3 -- Handle HTTPS domain in ConfigureCloudflareTunnels -- Backup all databases for mysql,mariadb,postgresql -- Restart service without pulling the latest image - -### 🐛 Bug Fixes - -- Remove autofocuses -- Ipv6 scp should use -6 flag -- Cleanup stucked applicationdeploymentqueue -- Realtime watch in development mode -- Able to select root permission easier - -### 💼 Other - -- Show backup button on supported db service stacks - -### 🚜 Refactor - -- Remove deployment queue when deleting an application -- Improve SSH command generation in Terminal.php and terminal-server.js -- Fix indentation in modal-confirmation.blade.php -- Improve parsing of commands for sudo in parseCommandsByLineForSudo -- Improve popup component styling and button behavior -- Encode delimiter in SshMultiplexingHelper -- Remove inactivity timer in terminal-server.js -- Improve socket reconnection interval in terminal.js -- Remove unnecessary watch command from soketi service entrypoint - -### ⚙️ Miscellaneous Tasks - -- Update version numbers to 4.0.0-beta.350 in configuration files -- Update command signature and description for cleanup application deployment queue -- Add missing import for Attribute class in ApplicationDeploymentQueue model -- Update modal input in server form to prevent closing on outside click -- Remove unnecessary command from SshMultiplexingHelper -- Remove commented out code for uploading to S3 in DatabaseBackupJob -- Update soketi service image to version 1.0.3 - -## [4.0.0-beta.348] - 2024-10-01 - -### 🚀 Features - -- Update resource deletion job to allow configurable options through API -- Add query parameters for deleting configurations, volumes, docker cleanup, and connected networks - -### 🐛 Bug Fixes - -- In dev mode do not ask confirmation on delete -- Mixpost -- Handle deletion of 'hello' in confirmation modal for dev environment - -### 💼 Other - -- Server storage check - -### 🚜 Refactor - -- Update search input placeholder in resource index view - -### ⚙️ Miscellaneous Tasks - -- Fix docs link in running state -- Update Coolify Realtime workflow to only trigger on the main branch -- Refactor instanceSettings() function to improve code readability -- Update Coolify Realtime image to version 1.0.2 -- Remove unnecessary code in DatabaseBackupJob.php -- Add "Not Usable" indicator for storage items -- Refactor instanceSettings() function and improve code readability -- Update version numbers to 4.0.0-beta.349 and 4.0.0-beta.350 - -## [4.0.0-beta.347] - 2024-09-28 - -### 🚀 Features - -- Allow specify use_build_server when creating/updating an application -- Add support for `use_build_server` in API endpoints for creating/updating applications -- Add Mixpost template - -### 🐛 Bug Fixes - -- Filebrowser template -- Edit is_build_server_enabled upon creating application on other application type -- Save settings after assigning value - -### 💼 Other - -- Remove memlock as it caused problems for some users - -### ⚙️ Miscellaneous Tasks - -- Update Mailpit logo to use SVG format - -## [4.0.0-beta.346] - 2024-09-27 - -### 🚀 Features - -- Add ContainerStatusTypes enum for managing container status - -### 🐛 Bug Fixes - -- Proxy fixes -- Proxy -- *(templates)* Filebrowser FQDN env variable -- Handle edge case when build variables and env variables are in different format -- Compose based terminal - -### 💼 Other - -- Manual cleanup button and unused volumes and network deletion -- Force helper image removal -- Use the new confirmation flow -- Typo -- Typo in install script -- If API is disabeled do not show API token creation stuff -- Disable API by default -- Add debug bar - -### 🚜 Refactor - -- Update environment variable name for uptime-kuma service -- Improve start proxy script to handle existing containers gracefully -- Update delete server confirmation modal buttons -- Remove unnecessary code - -### ⚙️ Miscellaneous Tasks - -- Add autocomplete attribute to input fields -- Refactor API Tokens component to use isApiEnabled flag -- Update versions.json file -- Remove unused .env.development.example file -- Update API Tokens view to include link to Settings menu -- Update web.php to cast server port as integer -- Update backup deletion labels to use language files -- Update database startup heading title -- Update database startup heading title -- Custom vite envs -- Update version numbers to 4.0.0-beta.348 -- Refactor code to improve SSH key handling and storage - -## [4.0.0-beta.343] - 2024-09-25 - -### 🐛 Bug Fixes - -- Parser -- Exited services statuses -- Make sure to reload window if app status changes -- Deploy key based deployments - -### 🚜 Refactor - -- Remove commented out code and improve environment variable handling in newParser function -- Improve label positioning in input and checkbox components -- Group and sort fields in StackForm by service name and password status -- Improve layout and add checkbox for task enablement in scheduled task form -- Update checkbox component to support full width option -- Update confirmation label in danger.blade.php template -- Fix typo in execute-container-command.blade.php -- Update OS_TYPE for Asahi Linux in install.sh script -- Add localhost as Server if it doesn't exist and not in cloud environment -- Add localhost as Server if it doesn't exist and not in cloud environment -- Update ProductionSeeder to fix issue with coolify_key assignment -- Improve modal confirmation titles and button labels -- Update install.sh script to remove redirection of upgrade output to /dev/null -- Fix modal input closeOutside prop in configuration.blade.php -- Add support for IPv6 addresses in sslip function - -### ⚙️ Miscellaneous Tasks - -- Update version numbers to 4.0.0-beta.343 -- Update version numbers to 4.0.0-beta.344 -- Update version numbers to 4.0.0-beta.345 -- Update version numbers to 4.0.0-beta.346 - -## [4.0.0-beta.342] - 2024-09-24 - -### 🚀 Features - -- Add nullable constraint to 'fingerprint' column in private_keys table -- *(api)* Add an endpoint to execute a command -- *(api)* Add endpoint to execute a command - -### 🐛 Bug Fixes - -- Proxy status -- Coolify-db should not be in the managed resources -- Store original root key in the original location -- Logto service -- Cloudflared service -- Migrations -- Cloudflare tunnel configuration, ui, etc - -### 💼 Other - -- Volumes on development environment -- Clean new volume name for dev volumes -- Persist DBs, services and so on stored in data/coolify -- Add SSH Key fingerprint to DB -- Add a fingerprint to every private key on save, create... -- Make sure invalid private keys can not be added -- Encrypt private SSH keys in the DB -- Add is_sftp and is_server_ssh_key coloums -- New ssh key file name on disk -- Store all keys on disk by default -- Populate SSH key folder -- Populate SSH keys in dev -- Use new function names and logic everywhere -- Create a Multiplexing Helper -- SSH multiplexing -- Remove unused code form multiplexing -- SSH Key cleanup job -- Private key with ID 2 on dev -- Move more functions to the PrivateKey Model -- Add ssh key fingerprint and generate one for existing keys -- ID issues on dev seeders -- Server ID 0 -- Make sure in use private keys are not deleted -- Do not delete SSH Key from disk during server validation error -- UI bug, do not write ssh key to disk in server dialog -- SSH Multiplexing for Jobs -- SSH algorhytm text -- Few multiplexing things -- Clear mux directory -- Multiplexing do not write file manually -- Integrate tow step process in the modal component WIP -- Ability to hide labels -- DB start, stop confirm -- Del init script -- General confirm -- Preview deployments and typos -- Service confirmation -- Confirm file storage -- Stop service confirm -- DB image cleanup -- Confirm ressource operation -- Environment variabel deletion -- Confirm scheduled tasks -- Confirm API token -- Confirm private key -- Confirm server deletion -- Confirm server settings -- Proxy stop and restart confirmation -- GH app deletion confirmation -- Redeploy all confirmation -- User deletion confirmation -- Team deletion confirmation -- Backup job confirmation -- Delete volume confirmation -- More conformations and fixes -- Delete unused private keys button -- Ray error because port is not uncommented -- #3322 deploy DB alterations before updating -- Css issue with advanced settings and remove cf tunnel in onboarding -- New cf tunnel install flow -- Made help text more clear -- Cloudflare tunnel -- Make helper text more clean to use a FQDN and not an URL - -### 🚜 Refactor - -- Update Docker cleanup label in Heading.php and Navbar.php -- Remove commented out code in Navbar.php -- Remove CleanupSshKeysJob from schedule in Kernel.php -- Update getAJoke function to exclude offensive jokes -- Update getAJoke function to use HTTPS for API request -- Update CleanupHelperContainersJob to use more efficient Docker command -- Update PrivateKey model to improve code readability and maintainability -- Remove unnecessary code in PrivateKey model -- Update PrivateKey model to use ownedByCurrentTeam() scope for cleanupUnusedKeys() -- Update install.sh script to check if coolify-db volume exists before generating SSH key -- Update ServerSeeder and PopulateSshKeysDirectorySeeder -- Improve attribute sanitization in Server model -- Update confirmation button text for deletion actions -- Remove unnecessary code in shared.php file -- Update environment variables for services in compose files -- Update select.blade.php to improve trademarks policy display -- Update select.blade.php to improve trademarks policy display -- Fix typo in subscription URLs -- Add Postiz service to compose file (disabled for now) -- Update shared.php to include predefined ports for services -- Simplify SSH key synchronization logic -- Remove unused code in DatabaseBackupStatusJob and PopulateSshKeysDirectorySeeder - -### ⚙️ Miscellaneous Tasks - -- Update version numbers to 4.0.0-beta.342 -- Update remove-labels-and-assignees-on-close.yml -- Add SSH key for localhost in ProductionSeeder -- Update SSH key generation in install.sh script -- Update ProductionSeeder to call OauthSettingSeeder and PopulateSshKeysDirectorySeeder -- Update install.sh to support Asahi Linux -- Update install.sh version to 1.6 -- Remove unused middleware and uniqueId method in DockerCleanupJob -- Refactor DockerCleanupJob to remove unused middleware and uniqueId method -- Remove unused migration file for populating SSH keys and clearing mux directory -- Add modified files to the commit -- Refactor pre-commit hook to improve performance and readability -- Update CONTRIBUTING.md with troubleshooting note about database migrations -- Refactor pre-commit hook to improve performance and readability -- Update cleanup command to use Redis instead of queue -- Update Docker commands to start proxy - -## [4.0.0-beta.341] - 2024-09-18 - -### 🚀 Features - -- Add buddy logo - -## [4.0.0-beta.336] - 2024-09-16 - -### 🚀 Features - -- Make coolify full width by default -- Fully functional terminal for command center -- Custom terminal host - -### 🐛 Bug Fixes - -- Keep-alive ws connections -- Add build.sh to debug logs -- Update Coolify installer -- Terminal -- Generate https for minio -- Install script -- Handle WebSocket connection close in terminal.blade.php -- Able to open terminal to any containers -- Refactor run-command -- If you exit a container manually, it should close the underlying tty as well -- Move terminal to separate view on services -- Only update helper image in DB -- Generated fqdn for SERVICE_FQDN_APP_3000 magic envs - -### 💼 Other - -- Remove labels and assignees on issue close -- Make sure this action is also triggered on PR issue close - -### 🚜 Refactor - -- Remove unnecessary code in ExecuteContainerCommand.php -- Improve Docker network connection command in StartService.php -- Terminal / run command -- Add authorization check in ExecuteContainerCommand mount method -- Remove unnecessary code in Terminal.php -- Remove unnecessary code in Terminal.blade.php -- Update WebSocket connection initialization in terminal.blade.php -- Remove unnecessary console.log statements in terminal.blade.php - -### ⚙️ Miscellaneous Tasks - -- Update release version to 4.0.0-beta.336 -- Update coolify environment variable assignment with double quotes -- Update shared.php to fix issues with source and network variables -- Update terminal styling for better readability -- Update button text for container connection form -- Update Dockerfile and workflow for Coolify Realtime (v4) -- Remove unused entrypoint script and update volume mapping -- Update .env file and docker-compose configuration -- Update APP_NAME environment variable in docker-compose.prod.yml -- Update WebSocket URL in terminal.blade.php -- Update Dockerfile and workflow for Coolify Realtime (v4) -- Update Dockerfile and workflow for Coolify Realtime (v4) -- Update Dockerfile and workflow for Coolify Realtime (v4) -- Rename Command Center to Terminal in code and views -- Update branch restriction for push event in coolify-helper.yml -- Update terminal button text and layout in application heading view -- Refactor terminal component and select form layout -- Update coolify nightly version to 4.0.0-beta.335 -- Update helper version to 1.0.1 -- Fix syntax error in versions.json -- Update version numbers to 4.0.0-beta.337 -- Update Coolify installer and scripts to include a function for fetching programming jokes -- Update docker network connection command in ApplicationDeploymentJob.php -- Add validation to prevent selecting 'default' server or container in RunCommand.php -- Update versions.json to reflect latest version of realtime container -- Update soketi image to version 1.0.1 -- Nightly - Update soketi image to version 1.0.1 and versions.json to reflect latest version of realtime container -- Update version numbers to 4.0.0-beta.339 -- Update version numbers to 4.0.0-beta.340 -- Update version numbers to 4.0.0-beta.341 - -### ◀️ Revert - -- Databasebackup - -## [4.0.0-beta.335] - 2024-09-12 - -### 🐛 Bug Fixes - -- Cloudflare tunnel with new multiplexing feature - -### 💼 Other - -- SSH Multiplexing on docker desktop on Windows - -### ⚙️ Miscellaneous Tasks - -- Update release version to 4.0.0-beta.335 -- Update constants.ssh.mux_enabled in remoteProcess.php -- Update listeners and proxy settings in server form and new server components -- Remove unnecessary null check for proxy_type in generate_default_proxy_configuration -- Remove unnecessary SSH command execution time logging - -## [4.0.0-beta.334] - 2024-09-12 - -### ⚙️ Miscellaneous Tasks - -- Remove itsgoingd/clockwork from require-dev in composer.json -- Update 'key' value of gitlab in Service.php to use environment variable - -## [4.0.0-beta.333] - 2024-09-11 - -### 🐛 Bug Fixes - -- Disable mux_enabled during server validation -- Move mc command to coolify image from helper -- Keydb. add `:` delimiter for connection string - -### 💼 Other - -- Remote servers with port and user -- Do not change localhost server name on revalidation -- Release.md file - -### 🚜 Refactor - -- Improve handling of environment variable merging in upgrade script - -### ⚙️ Miscellaneous Tasks - -- Update version numbers to 4.0.0-beta.333 -- Copy .env file to .env-{DATE} if it exists -- Update .env file with new values -- Update server check job middleware to use server ID instead of UUID -- Add reminder to backup .env file before running install script again -- Copy .env file to backup location during installation script -- Add reminder to backup .env file during installation script -- Update permissions in pr-build.yml and version numbers -- Add minio/mc command to Dockerfile - -## [4.0.0-beta.332] - 2024-09-10 - -### 🚀 Features - -- Expose project description in API response -- Add elixir finetunes to the deployment job - -### 🐛 Bug Fixes - -- Reenable overlapping servercheckjob -- Appwrite template + parser -- Don't add `networks` key if `network_mode` is used -- Remove debug statement in shared.php -- Scp through cloudflare -- Delete older versions of the helper image other than the latest one -- Update remoteProcess.php to handle null values in logItem properties - -### 💼 Other - -- Set a default server timezone -- Implement SSH Multiplexing -- Enabel mux -- Cleanup stale multiplexing connections - -### 🚜 Refactor - -- Improve environment variable handling in shared.php - -### ⚙️ Miscellaneous Tasks - -- Set timeout for ServerCheckJob to 60 seconds -- Update appwrite.yaml to include OpenSSL key variable assignment - -## [4.0.0-beta.330] - 2024-09-06 - -### 🐛 Bug Fixes - -- Parser -- Plunk NEXT_PUBLIC_API_URI - -### 💼 Other - -- Pull helper image if not available otherwise s3 backup upload fails - -### 🚜 Refactor - -- Improve handling of server timezones in scheduled backups and tasks -- Improve handling of server timezones in scheduled backups and tasks -- Improve handling of server timezones in scheduled backups and tasks -- Update cleanup schedule to run daily at midnight -- Skip returning volume if driver type is cifs or nfs - -### ⚙️ Miscellaneous Tasks - -- Update coolify-helper.yml to get version from versions.json -- Disable Ray by default -- Enable Ray by default and update Dockerfile with latest versions of PACK and NIXPACKS -- Update Ray configuration and Dockerfile -- Add middleware for updating environment variables by UUID in `api.php` routes -- Expose port 3000 in browserless.yaml template -- Update Ray configuration and Dockerfile -- Update coolify version to 4.0.0-beta.331 -- Update versions.json and sentry.php to 4.0.0-beta.332 -- Update version to 4.0.0-beta.332 -- Update DATABASE_URL in plunk.yaml to use plunk database -- Add coolify.managed=true label to Docker image builds -- Update docker image pruning command to exclude managed images -- Update docker cleanup schedule to run daily at midnight -- Update versions.json to version 1.0.1 -- Update coolify-helper.yml to include "next" branch in push trigger - -## [4.0.0-beta.326] - 2024-09-03 - -### 🚀 Features - -- Update server_settings table to force docker cleanup -- Update Docker Compose file with DB_URL environment variable -- Refactor shared.php to improve environment variable handling - -### 🐛 Bug Fixes - -- Wrong executions order -- Handle project not found error in environment_details API endpoint -- Deployment running for - without "ago" -- Update helper image pulling logic to only pull if the version is newer - -### 💼 Other - -- Plunk svg - -### 📚 Documentation - -- Update Plunk documentation link in compose/plunk.yaml - -### ⚙️ Miscellaneous Tasks - -- Update UI for displaying no executions found in scheduled task list -- Update UI for displaying deployment status in deployment list -- Update UI for displaying deployment status in deployment list -- Ignore unnecessary files in production build workflow -- Update server form layout and settings -- Update Dockerfile with latest versions of PACK and NIXPACKS - -## [4.0.0-beta.324] - 2024-09-02 - -### 🚀 Features - -- Preserve git repository with advanced file storages -- Added Windmill template -- Added Budibase template -- Add shm-size for custom docker commands -- Add custom docker container options to all databases -- Able to select different postgres database -- Add new logos for jobscollider and hostinger -- Order scheduled task executions -- Add Code Server environment variables to Service model -- Add coolify build env variables to building phase -- Add new logos for GlueOps, Ubicloud, Juxtdigital, Saasykit, and Massivegrid -- Add new logos for GlueOps, Ubicloud, Juxtdigital, Saasykit, and Massivegrid - -### 🐛 Bug Fixes - -- Timezone not updated when systemd is missing -- If volumes + file mounts are defined, should merge them together in the compose file -- All mongo v4 backups should use the different backup command -- Database custom environment variables -- Connect compose apps to the right predefined network -- Docker compose destination network -- Server status when there are multiple servers -- Sync fqdn change on the UI -- Pr build names in case custom name is used -- Application patch request instant_deploy -- Canceling deployment on build server -- Backup of password protected postgresql database -- Docker cleanup job -- Storages with preserved git repository -- Parser parser parser -- New parser only in dev -- Parser parser -- Numberoflines should be number -- Docker cleanup job -- Fix directory and file mount headings in file-storage.blade.php -- Preview fqdn generation -- Revert a few lines -- Service ui sync bug -- Setup script doesn't work on rhel based images with some curl variant already installed -- Let's wait for healthy container during installation and wait an extra 20 seconds (for migrations) -- Infra files -- Log drain only for Applications -- Copy large compose files through scp (not ssh) -- Check if array is associative or not -- Openapi endpoint urls -- Convert environment variables to one format in shared.php -- Logical volumes could be overwritten with new path -- Env variable in value parsed -- Pull coolify image only when the app needs to be updated - -### 💼 Other - -- Actually update timezone on the server -- Cron jobs are executed based on the server timezone -- Server timezone seeder -- Recent backups UI -- Use apt-get instead of apt -- Typo -- Only pull helper image if the version is newer than the one - -### 🚜 Refactor - -- Update event listeners in Show components -- Refresh application to get latest database changes -- Update RabbitMQ configuration to use environment variable for port -- Remove debug statement in parseDockerComposeFile function -- ParseServiceVolumes -- Update OpenApi command to generate documentation -- Remove unnecessary server status check in destination view -- Remove unnecessary admin user email and password in budibase.yaml -- Improve saving of custom internal name in Advanced.php -- Add conditional check for volumes in generate_compose_file() -- Improve storage mount forms in add.blade.php -- Load environment variables based on resource type in sortEnvironmentVariables() -- Remove unnecessary network cleanup in Init.php -- Remove unnecessary environment variable checks in parseDockerComposeFile() -- Add null check for docker_compose_raw in parseCompose() -- Update dockerComposeParser to use YAML data from $yaml instead of $compose -- Convert service variables to key-value pairs in parseDockerComposeFile function -- Update database service name from mariadb to mysql -- Remove unnecessary code in DatabaseBackupJob and BackupExecutions -- Update Docker Compose parsing function to convert service variables to key-value pairs -- Update Docker Compose parsing function to convert service variables to key-value pairs -- Remove unused server timezone seeder and related code -- Remove unused server timezone seeder and related code -- Remove unused PullCoolifyImageJob from schedule -- Update parse method in Advanced, All, ApplicationPreview, General, and ApplicationDeploymentJob classes -- Remove commented out code for getIptables() in Dashboard.php -- Update .env file path in install.sh script -- Update SELF_HOSTED environment variable in docker-compose.prod.yml -- Remove unnecessary code for creating coolify network in upgrade.sh -- Update environment variable handling in StartClickhouse.php and ApplicationDeploymentJob.php -- Improve handling of COOLIFY_URL in shared.php -- Update build_args property type in ApplicationDeploymentJob -- Update background color of sponsor section in README.md -- Update Docker Compose location handling in PublicGitRepository -- Upgrade process of Coolify - -### 🧪 Testing - -- More tests - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.324 -- New compose parser with tests -- Update version to 1.3.4 in install.sh and 1.0.6 in upgrade.sh -- Update memory limit to 64MB in horizon configuration -- Update php packages -- Update axios npm dependency to version 1.7.5 -- Update Coolify version to 4.0.0-beta.324 and fix file paths in upgrade script -- Update Coolify version to 4.0.0-beta.324 -- Update Coolify version to 4.0.0-beta.325 -- Update Coolify version to 4.0.0-beta.326 -- Add cd command to change directory before removing .env file -- Update Coolify version to 4.0.0-beta.327 -- Update Coolify version to 4.0.0-beta.328 -- Update sponsor links in README.md -- Update version.json to versions.json in GitHub workflow -- Cleanup stucked resources and scheduled backups -- Update GitHub workflow to use versions.json instead of version.json -- Update GitHub workflow to use versions.json instead of version.json -- Update GitHub workflow to use versions.json instead of version.json -- Update GitHub workflow to use jq container for version extraction -- Update GitHub workflow to use jq container for version extraction - -## [4.0.0-beta.323] - 2024-08-08 - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.323 - -## [4.0.0-beta.322] - 2024-08-08 - -### 🐛 Bug Fixes - -- Manual update process - -### 🚜 Refactor - -- Update Server model getContainers method to use collect() for containers and containerReplicates -- Import ProxyTypes enum and use TRAEFIK instead of TRAEFIK_V2 - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.322 - -## [4.0.0-beta.321] - 2024-08-08 - -### 🐛 Bug Fixes - -- Scheduledbackup not found - -### 🚜 Refactor - -- Update StandalonePostgresql database initialization and backup handling -- Update cron expressions and add helper text for scheduled tasks - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.321 - -## [4.0.0-beta.320] - 2024-08-08 - -### 🚀 Features - -- Delete team in cloud without subscription -- Coolify init should cleanup stuck networks in proxy -- Add manual update check functionality to settings page -- Update auto update and update check frequencies in settings -- Update Upgrade component to check for latest version of Coolify -- Improve homepage service template -- Support map fields in Directus -- Labels by proxy type -- Able to generate only the required labels for resources - -### 🐛 Bug Fixes - -- Only append docker network if service/app is running -- Remove lazy load from scheduled tasks -- Plausible template -- Service_url should not have a trailing slash -- If usagebefore cannot be determined, cleanup docker with force -- Async remote command -- Only run logdrain if necessary -- Remove network if it is only connected to coolify proxy itself -- Dir mounts should have proper dirs -- File storages (dir/file mount) handled properly -- Do not use port exposes on docker compose buildpacks -- Minecraft server template fixed -- Graceful shutdown -- Stop resources gracefully -- Handle null and empty disk usage in DockerCleanupJob -- Show latest version on manual update view -- Empty string content should be saved as a file -- Update Traefik labels on init -- Add missing middleware for server check job - -### 🚜 Refactor - -- Update CleanupDatabase.php to adjust keep_days based on environment -- Adjust keep_days in CleanupDatabase.php based on environment -- Remove commented out code for cleaning up networks in CleanupDocker.php -- Update livewire polling interval in heading.blade.php -- Remove unused code for checking server status in Heading.php -- Simplify log drain installation in ServerCheckJob -- Remove unnecessary debug statement in ServerCheckJob -- Simplify log drain installation and stop log drain if necessary -- Cleanup unnecessary dynamic proxy configuration in Init command -- Remove unnecessary debug statement in ApplicationDeploymentJob -- Update timeout for graceful_shutdown_container in ApplicationDeploymentJob -- Remove unused code and optimize CheckForUpdatesJob -- Update ProxyTypes enum values to use TRAEFIK instead of TRAEFIK_V2 -- Update Traefik labels on init and cleanup unnecessary dynamic proxy configuration - -### 🎨 Styling - -- Linting - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.320 -- Add pull_request image builds to GH actions -- Add comment explaining the purpose of disconnecting the network in cleanup_unused_network_from_coolify_proxy() -- Update formbricks template -- Update registration view to display a notice for first user that it will be an admin -- Update server form to use password input for IP Address/Domain field -- Update navbar to include service status check -- Update navbar and configuration to improve service status check functionality -- Update workflows to include PR build and merge manifest steps -- Update UpdateCoolifyJob timeout to 10 minutes -- Update UpdateCoolifyJob to dispatch CheckForUpdatesJob synchronously - -## [4.0.0-beta.319] - 2024-07-26 - -### 🐛 Bug Fixes - -- Parse docker composer -- Service env parsing -- Service env variables -- Activity type invalid -- Update env on ui - -### 💼 Other - -- Service env parsing - -### ⚙️ Miscellaneous Tasks - -- Collect/create/update volumes in parseDockerComposeFile function - -## [4.0.0-beta.318] - 2024-07-24 - -### 🚀 Features - -- Create/delete project endpoints -- Add patch request to projects -- Add server api endpoints -- Add branddev logo to README.md -- Update API endpoint summaries -- Update Caddy button label in proxy.blade.php -- Check custom internal name through server's applications. -- New server check job - -### 🐛 Bug Fixes - -- Preview deployments should be stopped properly via gh webhook -- Deleting application should delete preview deployments -- Plane service images -- Fix issue with deployment start command in ApplicationDeploymentJob -- Directory will be created by default for compose host mounts -- Restart proxy does not work + status indicator on the UI -- Uuid in api docs type -- Raw compose deployment .env not found -- Api -> application patch endpoint -- Remove pull always when uploading backup to s3 -- Handle array env vars -- Link in task failed job notifications -- Random generated uuid will be full length (not 7 characters) -- Gitlab service -- Gitlab logo -- Bitbucket repository url -- By default volumes that we cannot determine if they are directories or files are treated as directories -- Domain update on services on the UI -- Update SERVICE_FQDN/URL env variables when you change the domain -- Several shared environment variables in one value, parsed correctly -- Members of root team should not see instance admin stuff - -### 💼 Other - -- Formbricks template add required CRON_SECRET -- Add required CRON_SECRET to Formbricks template - -### ⚙️ Miscellaneous Tasks - -- Update APP_BASE_URL to use SERVICE_FQDN_PLANE -- Update resource-limits.blade.php with improved input field helpers -- Update version numbers to 4.0.0-beta.319 -- Remove commented out code for docker image pruning - -## [4.0.0-beta.314] - 2024-07-15 - -### 🚀 Features - -- Improve error handling in loadComposeFile method -- Add readonly labels -- Preserve git repository -- Force cleanup server - -### 🐛 Bug Fixes - -- Typo in is_literal helper -- Env is_literal helper text typo -- Update docker compose pull command with --policy always -- Plane service template -- Vikunja -- Docmost template -- Drupal -- Improve github source creation -- Tag deployments -- New docker compose parsing -- Handle / in preselecting branches -- Handle custom_internal_name check in ApplicationDeploymentJob.php -- If git limit reached, ignore it and continue with a default selection -- Backup downloads -- Missing input for api endpoint -- Volume detection (dir or file) is fixed -- Supabase -- Create file storage even if content is empty - -### 💼 Other - -- Add basedir + compose file in new compose based apps - -### 🚜 Refactor - -- Remove unused code and fix storage form layout -- Update Docker Compose build command to include --pull flag -- Update DockerCleanupJob to handle nullable usageBefore property -- Server status job and docker cleanup job -- Update DockerCleanupJob to use server settings for force cleanup -- Update DockerCleanupJob to use server settings for force cleanup -- Disable health check for Rust applications during deployment - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.315 -- Update version to 4.0.0-beta.316 -- Update bug report template -- Update repository form with simplified URL input field -- Update width of container in general.blade.php -- Update checkbox labels in general.blade.php -- Update general page of apps -- Handle JSON parsing errors in format_docker_command_output_to_json -- Update Traefik image version to v2.11 -- Update version to 4.0.0-beta.317 -- Update version to 4.0.0-beta.318 -- Update helper message with link to documentation -- Disable health check by default -- Remove commented out code for sending internal notification - -### ◀️ Revert - -- Pull policy -- Advanced dropdown - -## [4.0.0-beta.308] - 2024-07-11 - -### 🚀 Features - -- Cleanup unused docker networks from proxy -- Compose parser v2 -- Display time interval for rollback images -- Add security and storage access key env to twenty template -- Add new logo for Latitude -- Enable legacy model binding in Livewire configuration - -### 🐛 Bug Fixes - -- Do not overwrite hardcoded variables if they rely on another variable -- Remove networks when deleting a docker compose based app -- Api -- Always set project name during app deployments -- Remove volumes as well -- Gitea pr previews -- Prevent instance fqdn persisting to other servers dynamic proxy configs -- Better volume cleanups -- Cleanup parameter -- Update redirect URL in unauthenticated exception handler -- Respect top-level configs and secrets -- Service status changed event -- Disable sentinel until a few bugs are fixed -- Service domains and envs are properly updated -- *(reactive-resume)* New healthcheck command for MinIO -- *(MinIO)* New command healthcheck -- Update minio hc in services -- Add validation for missing docker compose file - -### 🚜 Refactor - -- Add force parameter to StartProxy handle method -- Comment out unused code for network cleanup -- Reset default labels when docker_compose_domains is modified -- Webhooks view -- Tags view -- Only get instanceSettings once from db -- Update Dockerfile to set CI environment variable to true -- Remove unnecessary code in AppServiceProvider.php -- Update Livewire configuration views -- Update Webhooks.php to use nullable type for webhook URLs -- Add lazy loading to tags in Livewire configuration view -- Update metrics.blade.php to improve alert message clarity -- Update version numbers to 4.0.0-beta.312 -- Update version numbers to 4.0.0-beta.314 - -### ⚙️ Miscellaneous Tasks - -- Update Plausible docker compose template to Plausible 2.1.0 -- Update Plausible docker compose template to Plausible 2.1.0 -- Update livewire/livewire dependency to version 3.4.9 -- Refactor checkIfDomainIsAlreadyUsed function -- Update storage.blade.php view for livewire project service -- Update version to 4.0.0-beta.310 -- Update composer dependencies -- Add new logo for Latitude -- Bump version to 4.0.0-beta.311 - -### ◀️ Revert - -- Instancesettings - -## [4.0.0-beta.301] - 2024-06-24 - -### 🚀 Features - -- Local fonts -- More API endpoints -- Bulk env update api endpoint -- Update server settings metrics history days to 7 -- New app API endpoint -- Private gh deployments through api -- Lots of api endpoints -- Api api api api api api -- Rename CloudCleanupSubs to CloudCleanupSubscriptions -- Early fraud warning webhook -- Improve internal notification message for early fraud warning webhook -- Add schema for uuid property in app update response - -### 🐛 Bug Fixes - -- Run user commands on high prio queue -- Load js locally -- Remove lemon + paddle things -- Run container commands on high priority -- Image logo -- Remove both option for api endpoints. it just makes things complicated -- Cleanup subs in cloud -- Show keydbs/dragonflies/clickhouses -- Only run cloud clean on cloud + remove root team -- Force cleanup on busy servers -- Check domain on new app via api -- Custom container name will be the container name, not just internal network name -- Api updates -- Yaml everywhere -- Add newline character to private key before saving -- Add validation for webhook endpoint selection -- Database input validators -- Remove own app from domain checks -- Return data of app update - -### 💼 Other - -- Update process -- Glances service -- Glances -- Able to update application - -### 🚜 Refactor - -- Update Service model's saveComposeConfigs method -- Add default environment to Service model's saveComposeConfigs method -- Improve handling of default environment in Service model's saveComposeConfigs method -- Remove commented out code in Service model's saveComposeConfigs method -- Update stack-form.blade.php to include wire:target attribute for submit button -- Update code to use str() instead of Str::of() for string manipulation -- Improve formatting and readability of source.blade.php -- Add is_build_time property to nixpacks_php_fallback_path and nixpacks_php_root_dir -- Simplify code for retrieving subscription in Stripe webhook - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.302 -- Update version to 4.0.0-beta.303 -- Update version to 4.0.0-beta.305 -- Update version to 4.0.0-beta.306 -- Add log1x/laravel-webfonts package -- Update version to 4.0.0-beta.307 -- Refactor ServerStatusJob constructor formatting -- Update Monaco Editor for Docker Compose and Proxy Configuration -- More details -- Refactor shared.php helper functions - -## [4.0.0-beta.298] - 2024-06-24 - -### 🚀 Features - -- Spanish translation -- Cancelling a deployment will check if new could be started. -- Add supaguide logo to donations section -- Nixpacks now could reach local dbs internally -- Add Tigris logo to other/logos directory -- COOLIFY_CONTAINER_NAME predefined variable -- Charts -- Sentinel + charts -- Container metrics -- Add high priority queue -- Add metrics warning for servers without Sentinel enabled -- Add blacksmith logo to donations section -- Preselect server and destination if only one found -- More api endpoints -- Add API endpoint to update application by UUID -- Update statusnook logo filename in compose template - -### 🐛 Bug Fixes - -- Stripprefix middleware correctly labeled to http -- Bitbucket link -- Compose generator -- Do no truncate repositories wtih domain (git) in it -- In services should edit compose file for volumes and envs -- Handle laravel deployment better -- Db proxy status shown better in the UI -- Show commit message on webhooks + prs -- Metrics parsing -- Charts -- Application custom labels reset after saving -- Static build with new nixpacks build process -- Make server charts one livewire component with one interval selector -- You can now add env variable from ui to services -- Update compose environment with UI defined variables -- Refresh deployable compose without reload -- Remove cloud stripe notifications -- App deployment should be in high queue -- Remove zoom from modals -- Get envs before sortby -- MB is % lol -- Projects with 0 envs - -### 💼 Other - -- Unnecessary notification - -### 🚜 Refactor - -- Update text color for stderr output in deployment show view -- Update text color for stderr output in deployment show view -- Remove debug code for saving environment variables -- Update Docker build commands for better performance and flexibility -- Update image sizes and add new logos to README.md -- Update README.md with new logos and fix styling -- Update shared.php to use correct key for retrieving sentinel version -- Update container name assignment in Application model -- Remove commented code for docker container removal -- Update Application model to include getDomainsByUuid method -- Update Project/Show component to sort environments by created_at -- Update profile index view to display 2FA QR code in a centered container -- Update dashboard.blade.php to use project's default environment for redirection -- Update gitCommitLink method to handle null values in source.html_url -- Update docker-compose generation to use multi-line literal block - -### ⚙️ Miscellaneous Tasks - -- Update version numbers to 4.0.0-beta.298 -- Switch to database sessions from redis -- Update dependencies and remove unused code -- Update tailwindcss and vue versions in package.json -- Update service template URL in constants.php -- Update sentinel version to 0.0.8 -- Update chart styling and loading text -- Update sentinel version to 0.0.9 -- Update Spanish translation for failed authentication messages -- Add portuguese traslation -- Add Turkish translations -- Add Vietnamese translate -- Add Treive logo to donations section -- Update README.md with latest release version badge -- Update latest release version badge in README.md -- Update version to 4.0.0-beta.299 -- Move server delete component to the bottom of the page -- Update version to 4.0.0-beta.301 - -## [4.0.0-beta.297] - 2024-06-11 - -### 🚀 Features - -- Easily redirect between www-and-non-www domains -- Add logos for new sponsors -- Add homepage template -- Update homepage.yaml with environment variables and volumes - -### 🐛 Bug Fixes - -- Multiline build args -- Setup script doesnt link to the correct source code file -- Install.sh do not reinstall packages on arch -- Just restart - -### 🚜 Refactor - -- Replaces duplications in code with a single function - -### ⚙️ Miscellaneous Tasks - -- Update page title in resource index view -- Update logo file path in logto.yaml -- Update logo file path in logto.yaml -- Remove commented out code for docker container removal -- Add isAnyDeploymentInprogress function to check if any deployments are in progress -- Add ApplicationDeploymentJob and pint.json - -## [4.0.0-beta.295] - 2024-06-10 - -### 🚀 Features - -- Able to change database passwords on the UI. It won't sync to the database. -- Able to add several domains to compose based previews -- Add bounty program link to bug report template -- Add titles -- Db proxy logs - -### 🐛 Bug Fixes - -- Custom docker compose commands, add project dir if needed -- Autoupdate process -- Backup executions view -- Handle previously defined compose previews -- Sort backup executions -- Supabase service, newest versions -- Set default name for Docker volumes if it is null -- Multiline variable should be literal + should be multiline in bash with \ -- Gitlab merge request should close PR - -### 💼 Other - -- Rocketchat -- New services based git apps - -### 🚜 Refactor - -- Append utm_source parameter to documentation URL -- Update save_environment_variables method to use application's environment_variables instead of environment_variables_preview -- Update deployment previews heading to "Deployments" -- Remove unused variables and improve code readability -- Initialize null properties in Github Change component -- Improve pre and post deployment command inputs -- Improve handling of Docker volumes in parseDockerComposeFile function - -### ⚙️ Miscellaneous Tasks - -- Update version numbers to 4.0.0-beta.295 -- Update supported OS list with almalinux -- Update install.sh to support PopOS -- Update install.sh script to version 1.3.2 and handle Linux Mint as Ubuntu - -## [4.0.0-beta.294] - 2024-06-04 - -### ⚙️ Miscellaneous Tasks - -- Update Dockerfile with latest versions of Docker, Docker Compose, Docker Buildx, Pack, and Nixpacks - -## [4.0.0-beta.289] - 2024-05-29 - -### 🚀 Features - -- Add PHP memory limit environment variable to docker-compose.prod.yml -- Add manual update option to UpdateCoolify handle method -- Add port configuration for Vaultwarden service - -### 🐛 Bug Fixes - -- Sync upgrade process -- Publish horizon -- Add missing team model -- Test new upgrade process? -- Throw exception -- Build server dirs not created on main server -- Compose load with non-root user -- Able to redeploy dockerfile based apps without cache -- Compose previews does have env variables -- Fine-tune cdn pulls -- Spamming :D -- Parse docker version better -- Compose issues -- SERVICE_FQDN has source port in it -- Logto service -- Allow invitations via email -- Sort by defined order + fixed typo -- Only ignore volumes with driver_opts -- Check env in args for compose based apps - -### 🚜 Refactor - -- Update destination.blade.php to add group class for better styling -- Applicationdeploymentjob -- Improve code structure in ApplicationDeploymentJob.php -- Remove unnecessary debug statement in ApplicationDeploymentJob.php -- Remove unnecessary debug statements and improve code structure in RunRemoteProcess.php and ApplicationDeploymentJob.php -- Remove unnecessary logging statements from UpdateCoolify -- Update storage form inputs in show.blade.php -- Improve Docker Compose parsing for services -- Remove unnecessary port appending in updateCompose function -- Remove unnecessary form class in profile index.blade.php -- Update form layout in invite-link.blade.php -- Add log entry when starting new application deployment -- Improve Docker Compose parsing for services -- Update Docker Compose parsing for services -- Update slogan in shlink.yaml -- Improve display of deployment time in index.blade.php -- Remove commented out code for clearing Ray logs -- Update save_environment_variables method to use application's environment_variables instead of environment_variables_preview - -### ⚙️ Miscellaneous Tasks - -- Update for version 289 -- Fix formatting issue in deployment index.blade.php file -- Remove unnecessary wire:navigate attribute in breadcrumbs.blade.php -- Rename docker dirs -- Update laravel/socialite to version v5.14.0 and livewire/livewire to version 3.4.9 -- Update modal styles for better user experience -- Update deployment index.blade.php script for better performance -- Update version numbers to 4.0.0-beta.290 -- Update version numbers to 4.0.0-beta.291 -- Update version numbers to 4.0.0-beta.292 -- Update version numbers to 4.0.0-beta.293 -- Add upgrade guide link to upgrade.blade.php -- Improve upgrade.blade.php with clearer instructions and formatting -- Update version numbers to 4.0.0-beta.294 -- Add Lightspeed.run as a sponsor -- Update Dockerfile to install vim - -## [4.0.0-beta.288] - 2024-05-28 - -### 🐛 Bug Fixes - -- Do not allow service storage mount point modifications -- Volume adding - -### ⚙️ Miscellaneous Tasks - -- Update Sentry release version to 4.0.0-beta.288 - -## [4.0.0-beta.287] - 2024-05-27 - -### 🚀 Features - -- Handle incomplete expired subscriptions in Stripe webhook -- Add more persistent storage types - -### 🐛 Bug Fixes - -- Force load services from cdn on reload list - -### ⚙️ Miscellaneous Tasks - -- Update Sentry release version to 4.0.0-beta.287 -- Add Thompson Edolo as a sponsor -- Add null checks for team in Stripe webhook - -## [4.0.0-beta.286] - 2024-05-27 - -### 🚀 Features - -- If the time seems too long it remains at 0s -- Improve Docker Engine start logic in ServerStatusJob -- If proxy stopped manually, it won't start back again -- Exclude_from_hc magic -- Gitea manual webhooks -- Add container logs in case the container does not start healthy - -### 🐛 Bug Fixes - -- Wrong time during a failed deployment -- Removal of the failed deployment condition, addition of since started instead of finished time -- Use local versions + service templates and query them every 10 minutes -- Check proxy functionality before removing unnecessary coolify.yaml file and checking Docker Engine -- Show first 20 users only in admin view -- Add subpath for services -- Ghost subdir -- Do not pull templates in dev -- Templates -- Update error message for invalid token to mention invalid signature -- Disable containerStopped job for now -- Disable unreachable/revived notifications for now -- JSON_UNESCAPED_UNICODE -- Add wget to nixpacks builds -- Pre and post deployment commands -- Bitbucket commits link -- Better way to add curl/wget to nixpacks -- Root team able to download backups -- Build server should not have a proxy -- Improve build server functionalities -- Sentry issue -- Sentry -- Sentry error + livewire downgrade -- Sentry -- Sentry -- Sentry error -- Sentry - -### 🚜 Refactor - -- Update edit-domain form in project service view -- Add Huly services to compose file -- Remove redundant heading in backup settings page -- Add isBuildServer method to Server model -- Update docker network creation in ApplicationDeploymentJob - -### ⚙️ Miscellaneous Tasks - -- Change pre and post deployment command length in applications table -- Refactor container name logic in GetContainersStatus.php and ForcePasswordReset.php -- Remove unnecessary content from Docker Compose file - -## [4.0.0-beta.285] - 2024-05-21 - -### 🚀 Features - -- Add SerpAPI as a Github Sponsor -- Admin view for deleting users -- Scheduled task failed notification - -### 🐛 Bug Fixes - -- Optimize new resource creation -- Show it docker compose has syntax errors - -### 💼 Other - -- Responsive here and there - -## [4.0.0-beta.284] - 2024-05-19 - -### 🚀 Features - -- Add hc logs to healthchecks - -### ◀️ Revert - -- Hc return code check - -## [4.0.0-beta.283] - 2024-05-17 - -### 🚀 Features - -- Update healthcheck test in StartMongodb action -- Add pull_request_id filter to get_last_successful_deployment method in Application model - -### 🐛 Bug Fixes - -- PR deployments have good predefined envs - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.283 - -## [4.0.0-beta.281] - 2024-05-17 - -### 🚀 Features - -- Shows the latest deployment commit + message on status -- New manual update process + remove next_channel -- Add lastDeploymentInfo and lastDeploymentLink props to breadcrumbs and status components -- Sort envs alphabetically and creation date -- Improve sorting of environment variables in the All component - -### 🐛 Bug Fixes - -- Hc from localhost to 127.0.0.1 -- Use rc in hc -- Telegram group chat notifications - -## [4.0.0-beta.280] - 2024-05-16 - -### 🐛 Bug Fixes - -- Commit message length - -## [4.0.0-beta.279] - 2024-05-16 - -### ⚙️ Miscellaneous Tasks - -- Update version numbers to 4.0.0-beta.279 -- Limit commit message length to 50 characters in ApplicationDeploymentJob - -## [4.0.0-beta.278] - 2024-05-16 - -### 🚀 Features - -- Adding new COOLIFY_ variables -- Save commit message and better view on deployments -- Toggle label escaping mechanism - -### 🐛 Bug Fixes - -- Use commit hash on webhooks - -### ⚙️ Miscellaneous Tasks - -- Refactor Service.php to handle missing admin user in extraFields() method -- Update twenty CRM template with environment variables and dependencies -- Refactor applications.php to remove unused imports and improve code readability -- Refactor deployment index.blade.php for improved readability and rollback handling -- Refactor GitHub app selection UI in project creation form -- Update ServerLimitCheckJob.php to handle missing serverLimit value -- Remove unnecessary code for saving commit message -- Update DOCKER_VERSION to 26.0 in install.sh script -- Update Docker and Docker Compose versions in Dockerfiles - -## [4.0.0-beta.277] - 2024-05-10 - -### 🚀 Features - -- Add AdminRemoveUser command to remove users from the database - -### 🐛 Bug Fixes - -- Color for resource operation server and project name -- Only show realtime error on non-cloud instances -- Only allow push and mr gitlab events -- Improve scheduled task adding/removing -- Docker compose dependencies for pr previews -- Properly populating dependencies - -### 💼 Other - -- Fix a few boxes here and there - -### ⚙️ Miscellaneous Tasks - -- Update version numbers to 4.0.0-beta.278 -- Update hover behavior and cursor style in scheduled task executions view -- Refactor scheduled task view to improve code readability and maintainability -- Skip scheduled tasks if application or service is not running -- Remove debug logging statements in Kernel.php -- Handle invalid cron strings in Kernel.php - -## [4.0.0-beta.275] - 2024-05-06 - -### 🚀 Features - -- Add container name to network aliases in ApplicationDeploymentJob -- Add lazy loading for images in General.php and improve Docker Compose file handling in Application.php -- Experimental sentinel -- Start Sentinel on servers. -- Pull new sentinel image and restart container -- Init metrics - -### 🐛 Bug Fixes - -- Typo in tags.blade.php -- Install.sh error -- Env file -- Comment out internal notification in email_verify method -- Confirmation for custom labels -- Change permissions on newly created dirs - -### 💼 Other - -- Fix tag view - -### 🚜 Refactor - -- Add SCHEDULER environment variable to StartSentinel.php - -### ⚙️ Miscellaneous Tasks - -- Dark mode should be the default -- Improve menu item styling and spacing in service configuration and index views -- Improve menu item styling and spacing in service configuration and index views -- Improve menu item styling and spacing in project index and show views -- Remove docker compose versions -- Add Listmonk service template and logo -- Refactor GetContainersStatus.php for improved readability and maintainability -- Refactor ApplicationDeploymentJob.php for improved readability and maintainability -- Add metrics and logs directories to installation script -- Update sentinel version to 0.0.2 in versions.json -- Update permissions on metrics and logs directories -- Comment out server sentinel check in ServerStatusJob - -## [4.0.0-beta.273] - 2024-05-03 - -### 🐛 Bug Fixes - -- Formbricks image origin -- Add port even if traefik is used - -### ⚙️ Miscellaneous Tasks - -- Update version to 4.0.0-beta.275 -- Update DNS server validation helper text - -## [4.0.0-beta.267] - 2024-04-26 - -### 🚀 Features - -- Initial datalist -- Update service contribution docs URL -- The final pricing plan, pay-as-you-go - -### 🐛 Bug Fixes - -- Move s3 storages to separate view -- Mongo db backup -- Backups -- Autoupdate -- Respect start period and chekc interval for hc -- Parse HEALTHCHECK from dockerfile -- Make s3 name and endpoint required -- Able to update source path for predefined volumes -- Get logs with non-root user -- Mongo 4.0 db backup - -### 💼 Other - -- Update resource operations view - -### ◀️ Revert - -- Variable parsing - -## [4.0.0-beta.266] - 2024-04-24 - -### 🐛 Bug Fixes - -- Refresh public ips on start - -## [4.0.0-beta.259] - 2024-04-17 - -### 🚀 Features - -- Literal env variables -- Lazy load stuffs + tell user if compose based deployments have missing envs -- Can edit file/dir volumes from ui in compose based apps -- Upgrade Appwrite service template to 1.5 -- Upgrade Appwrite service template to 1.5 -- Add db name to backup notifications - -### 🐛 Bug Fixes - -- Helper image only pulled if required, not every 10 mins -- Make sure that confs when checking if it is changed sorted -- Respect .env file (for default values) -- Remove temporary cloudflared config -- Remove lazy loading until bug figured out -- Rollback feature -- Base64 encode .env -- $ in labels escaped -- .env saved to deployment server, not to build server -- Do no able to delete gh app without deleting resources -- 500 error on edge case -- Able to select server when creating new destination -- N8n template - -### 💼 Other - -- Non-root user for remote servers -- Non-root - -## [4.0.0-beta.258] - 2024-04-12 - -### 🚀 Features - -- Dynamic mux time - -### 🐛 Bug Fixes - -- Check each required binaries one-by-one - -## [4.0.0-beta.256] - 2024-04-12 - -### 🚀 Features - -- Upload large backups -- Edit domains easier for compose -- Able to delete configuration from server -- Configuration checker for all resources -- Allow tab in textarea - -### 🐛 Bug Fixes - -- Service config hash update -- Redeploy if image not found in restart only mode - -### 💼 Other - -- New pricing -- Fix allowTab logic -- Use 2 space instead of tab - -## [4.0.0-beta.252] - 2024-04-09 - -### 🚀 Features - -- Add amazon linux 2023 - -### 🐛 Bug Fixes - -- Git submodule update -- Unintended left padding on sidebar -- Hashed random delimeter in ssh commands + make sure to remove the delimeter from the command - -## [4.0.0-beta.250] - 2024-04-05 - -### 🚀 Features - -- *(application)* Update submodules after git checkout - -## [4.0.0-beta.249] - 2024-04-03 - -### 🚀 Features - -- Able to make rsa/ed ssh keys - -### 🐛 Bug Fixes - -- Warning if you use multiple domains for a service -- New github app creation -- Always rebuild Dockerfile / dockerimage buildpacks -- Do not rebuild dockerfile based apps twice -- Make sure if envs are changed, rebuild is needed -- Members cannot manage subscriptions -- IsMember -- Storage layout -- How to update docker-compose, environment variables and fqdns - -### 💼 Other - -- Light buttons -- Multiple server view - -## [4.0.0-beta.242] - 2024-03-25 - -### 🚀 Features - -- Change page width -- Watch paths - -### 🐛 Bug Fixes - -- Compose env has SERVICE, but not defined for Coolify -- Public service database -- Make sure service db proxy restarted -- Restart service db proxies -- Two factor -- Ui for tags -- Update resources view -- Realtime connection check -- Multline env in dev mode -- Scheduled backup for other service databases (supabase) -- PR deployments should not be distributed to 2 servers -- Name/from address required for resend -- Autoupdater -- Async service loads -- Disabled inputs are not trucated -- Duplicated generated fqdns are now working -- Uis -- Ui for cftunnels -- Search services -- Trial users subscription page -- Async public key loading -- Unfunctional server should see resources - -### 💼 Other - -- Run cleanup every day -- Fix -- Fix log outputs -- Automatic cloudflare tunnels -- Backup executions - -## [4.0.0-beta.241] - 2024-03-20 - -### 🚀 Features - -- Able to run scheduler/horizon programatically - -### 🐛 Bug Fixes - -- Volumes for prs -- Shared env variable parsing - -### 💼 Other - -- Redesign -- Redesign - -## [4.0.0-beta.240] - 2024-03-18 - -### 🐛 Bug Fixes - -- Empty get logs number of lines -- Only escape envs after v239+ -- 0 in env value -- Consistent container name -- Custom ip address should turn off rolling update -- Multiline input -- Raw compose deployment -- Dashboard view if no project found - -## [4.0.0-beta.239] - 2024-03-14 - -### 🐛 Bug Fixes - -- Duplicate dockerfile -- Multiline env variables -- Server stopped, service page not reachable - -## [4.0.0-beta.237] - 2024-03-14 - -### 🚀 Features - -- Domains api endpoint -- Resources api endpoint -- Team api endpoint -- Add deployment details to deploy endpoint -- Add deployments api -- Experimental caddy support -- Dynamic configuration for caddy -- Reset password -- Show resources on source page - -### 🐛 Bug Fixes - -- Deploy api messages -- Fqdn null in case docker compose bp -- Reload caddy issue -- /realtime endpoint -- Proxy switch -- Service ports for services + caddy -- Failed deployments should send failed email/notification -- Consider custom healthchecks in dockerfile -- Create initial files async -- Docker compose validation - -## [4.0.0-beta.235] - 2024-03-05 - -### 🐛 Bug Fixes - -- Should note delete personal teams -- Make sure to show some buttons -- Sort repositories by name - -## [4.0.0-beta.224] - 2024-02-23 - -### 🚀 Features - -- Custom server limit -- Delay container/server jobs -- Add static ipv4 ipv6 support -- Server disabled by overflow -- Preview deployment logs -- Collect webhooks during maintenance -- Logs and execute commands with several servers - -### 🐛 Bug Fixes - -- Subscription / plan switch, etc -- Firefly service -- Force enable/disable server in case ultimate package quantity decreases -- Server disabled -- Custom dockerfile location always checked -- Import to mysql and mariadb -- Resource tab not loading if server is not reachable -- Load unmanaged async -- Do not show n/a networsk -- Service container status updates -- Public prs should not be commented -- Pull request deployments + build servers -- Env value generation -- Sentry error -- Service status updated - -### 💼 Other - -- Change + icon to hamburger. - -## [4.0.0-beta.222] - 2024-02-22 - -### 🚀 Features - -- Able to add dynamic configurations from proxy dashboard - -### 🐛 Bug Fixes - -- Connections being stuck and not processed until proxy restarts -- Use latest image if nothing is specified -- No coolify.yaml found -- Server validation -- Statuses -- Unknown image of service until it is uploaded - -## [4.0.0-beta.220] - 2024-02-19 - -### 🚀 Features - -- Save github app permission locally -- Minversion for services - -### 🐛 Bug Fixes - -- Add openbsd ssh server check -- Resources -- Empty build variables -- *(server)* Revalidate server button not showing in server's page -- Fluent bit ident level -- Submodule cloning -- Database status -- Permission change updates from webhook -- Server validation - -### 💼 Other - -- Updates - -## [4.0.0-beta.213] - 2024-02-12 - -### 🚀 Features - -- Magic for traefik redirectregex in services -- Revalidate server -- Disable gzip compression on service applications - -### 🐛 Bug Fixes - -- Cleanup scheduled tasks -- Padding left on input boxes -- Use ls / command instead ls -- Do not add the same server twice -- Only show redeployment required if status is not exited - -## [4.0.0-beta.212] - 2024-02-08 - -### 🚀 Features - -- Cleanup queue - -### 🐛 Bug Fixes - -- New menu on navbar -- Make sure resources are deleted in async mode -- Go to prod env from dashboard if there is no other envs defined -- User proper image_tag, if set -- New menu ui -- Lock logdrain configuration when one of them are enabled -- Add docker compose check during server validation -- Get service stack as uuid, not name -- Menu -- Flex wrap deployment previews -- Boolean docker options -- Only add 'networks' key if 'network_mode' is absent - -## [4.0.0-beta.206] - 2024-02-05 - -### 🚀 Features - -- Clone to env -- Multi deployments - -### 🐛 Bug Fixes - -- Wrap tags and avoid horizontal overflow -- Stripe webhooks -- Feedback from self-hosted envs to discord - -### 💼 Other - -- Specific about newrelic logdrains - -## [4.0.0-beta.201] - 2024-01-29 - -### 🚀 Features - -- Added manual webhook support for bitbucket -- Add initial support for custom docker run commands -- Cleanup unreachable servers -- Tags and tag deploy webhooks - -### 🐛 Bug Fixes - -- Bitbucket manual deployments -- Webhooks for multiple apps -- Unhealthy deployments should be failed -- Add env variables for wordpress template without database -- Service deletion function -- Service deletion fix -- Dns validation + duplicated fqdns -- Validate server navbar upated -- Regenerate labels on application clone -- Service deletion -- Not able to use other shared envs -- Sentry fix -- Sentry -- Sentry error -- Sentry -- Sentry error -- Create dynamic directory -- Migrate to new modal -- Duplicate domain check -- Tags - -### 💼 Other - -- New modal component - -## [4.0.0-beta.188] - 2024-01-11 - -### 🚀 Features - -- Search between resources -- Move resources between projects / environments -- Clone any resource -- Shared environments -- Concurrent builds / server -- Able to deploy multiple resources with webhook -- Add PR comments -- Dashboard live deployment view - -### 🐛 Bug Fixes - -- Preview deployments with nixpacks -- Cleanup docker stuffs before upgrading -- Service deletion command -- Cpuset limits was determined in a way that apps only used 1 CPU max, ehh, sorry. -- Service stack view -- Change proxy view -- Checkbox click -- Git pull command for deploy key based previews -- Server status job -- Service deletion bug! -- Links -- Redis custom conf -- Sentry error -- Restrict concurrent deployments per server -- Queue -- Change env variable length - -### 💼 Other - -- Send notification email if payment - -### 🚜 Refactor - -- Compose file and install script - -## [4.0.0-beta.186] - 2024-01-11 - -### 🚀 Features - -- Import backups - -### 🐛 Bug Fixes - -- Do not include thegameplan.json into build image -- Submit error on postgresql -- Email verification / forgot password -- Escape build envs properly for nixpacks + docker build -- Undead endpoint -- Upload limit on ui -- Save cmd output propely (merge) -- Load profile on remote commands -- Load profile and set envs on remote cmd -- Restart should not update config hash - -## [4.0.0-beta.184] - 2024-01-09 - -### 🐛 Bug Fixes - -- Healthy status -- Show framework based notification in build logs -- Traefik labels -- Use ip for sslip in dev if remote server is used -- Service labels without ports (unknown ports) -- Sort and rename (unique part) of labels -- Settings menu -- Remove traefik debug in dev mode -- Php pgsql to 8.2 -- Static buildpack should set port 80 -- Update navbar on build_pack change - -## [4.0.0-beta.183] - 2024-01-06 - -### 🚀 Features - -- Add www-non-www redirects to traefik - -### 🐛 Bug Fixes - -- Database env variables - -## [4.0.0-beta.182] - 2024-01-04 - -### 🐛 Bug Fixes - -- File storage save - -## [4.0.0-beta.181] - 2024-01-03 - -### 🐛 Bug Fixes - -- Nixpacks buildpack - -## [4.0.0-beta.180] - 2024-01-03 - -### 🐛 Bug Fixes - -- Nixpacks cache -- Only add restart policy if its empty (compose) - -## [4.0.0-beta.179] - 2024-01-02 - -### 🐛 Bug Fixes - -- Set deployment failed if new container is not healthy - -## [4.0.0-beta.177] - 2024-01-02 - -### 🚀 Features - -- Raw docker compose deployments - -### 🐛 Bug Fixes - -- Duplicate compose variable - -## [4.0.0-beta.176] - 2023-12-31 - -### 🐛 Bug Fixes - -- Horizon - -## [4.0.0-beta.175] - 2023-12-30 - -### 🚀 Features - -- Add environment description + able to change name - -### 🐛 Bug Fixes - -- Sub -- Wrong env variable parsing -- Deploy key + docker compose - -## [4.0.0-beta.174] - 2023-12-27 - -### 🐛 Bug Fixes - -- Restore falsely deleted coolify-db-backup - -## [4.0.0-beta.173] - 2023-12-27 - -### 🐛 Bug Fixes - -- Cpu limit to float from int -- Add source commit to final envs -- Routing, switch back to old one -- Deploy instead of restart in case swarm is used -- Button title - -## [4.0.0-beta.163] - 2023-12-15 - -### 🚀 Features - -- Custom docker compose commands - -### 🐛 Bug Fixes - -- Domains for compose bp -- No action in webhooks -- Add debug output to gitlab webhooks -- Do not push dockerimage -- Add alpha to swarm -- Server not found -- Do not autovalidate server on mount -- Server update schedule -- Swarm support ui -- Server ready -- Get swarm service logs -- Docker compose apps env rewritten -- Storage error on dbs -- Why?! -- Stay tuned - -### 💼 Other - -- Swarm -- Swarm - -## [4.0.0-beta.155] - 2023-12-11 - -### 🚀 Features - -- Autoupdate env during seed -- Disable autoupdate -- Randomly sleep between executions -- Pull latest images for services - -### 🐛 Bug Fixes - -- Do not send telegram noti on intent payment failed -- Database ui is realtime based -- Live mode for github webhooks -- Ui -- Realtime connection popup could be disabled -- Realtime check -- Add new destination -- Proxy logs -- Db status check -- Pusher host -- Add ipv6 -- Realtime connection?! -- Websocket -- Better handling of errors with install script -- Install script parse version -- Only allow to modify in .env file if AUTOUPDATE is set -- Is autoupdate not null -- Run init command after production seeder -- Init -- Comma in traefik custom labels -- Ignore if dynamic config could not be set -- Service env variable ovewritten if it has a default value -- Labelling -- Non-ascii chars in labels -- Labels -- Init script echos -- Update Coolify script -- Null notify -- Check queued deployments as well -- Copy invitation -- Password reset / invitation link requests -- Add catch all route -- Revert random container job delay -- Backup executions view -- Only check server status in container status job -- Improve server status check times -- Handle other types of generated values -- Server checking status -- Ui for adding new destination -- Reset domains on compose file change - -### 💼 Other - -- Fix for comma in labels -- Add image name to service stack + better options visibility - -### 🚜 Refactor - -- Service logs are now on one page -- Application status changed realtime -- Custom labels -- Clone project - -## [4.0.0-beta.154] - 2023-12-07 - -### 🚀 Features - -- Execute command in container - -### 🐛 Bug Fixes - -- Container selection -- Service navbar using new realtime events -- Do not create duplicated networks -- Live event -- Service start + event -- Service deletion job -- Double ws connection -- Boarding view - -### 💼 Other - -- Env vars -- Migrate to livewire 3 - -## [4.0.0-beta.124] - 2023-11-13 - -### 🚀 Features - -- Log drain (wip) -- Enable/disable log drain by service -- Log drainer container check -- Add docker engine support install script to rhel based systems -- Save timestamp configuration for logs -- Custom log drain endpoints -- Auto-restart tcp proxies for databases - -### 🐛 Bug Fixes - -- *(fider template)* Use the correct docs url -- Fqdn for minio -- Generate service fields -- Mariadb backups -- When to pull image -- Do not allow to enter local ip addresses -- Reset password -- Only report nonruntime errors -- Handle different label formats in services -- Server adding process -- Show defined resources in server tab, so you will know what you need to delete before you can delete the server. -- Lots of regarding git + docker compose deployments -- Pull request build variables -- Double default password length -- Do not remove deployment in case compose based failed -- No container servers -- Sentry issue -- Dockercompose save ./ volumes under /data/coolify -- Server view for link() -- Default value do not overwrite existing env value -- Use official install script with rancher (one will work for sure) -- Add cf tunnel to boarding server view -- Prevent autorefresh of proxy status -- Missing docker image thing -- Add hc for soketi -- Deploy the right compose file -- Bind volumes for compose bp -- Use hc port 80 in case of static build -- Switching to static build - -### 💼 Other - -- New deployment jobs -- Compose based apps -- Swarm -- Swarm -- Swarm -- Swarm -- Disable trial -- Meilisearch -- Broadcast -- 🌮 - -### 🚜 Refactor - -- Env variable generator - -### ◀️ Revert - -- Wip - -## [4.0.0-beta.109] - 2023-11-06 - -### 🚀 Features - -- Deployment logs fullscreen -- Service database backups -- Make service databases public - -### 🐛 Bug Fixes - -- Missing environment variables prevewi on service -- Invoice.paid should sleep for 5 seconds -- Local dev repo -- Deployments ui -- Dockerfile build pack fix -- Set labels on generate domain -- Network service parse -- Notification url in containerstatusjob -- Gh webhook response 200 to installation_repositories -- Delete destination -- No id found -- Missing $mailMessage -- Set default from/sender names -- No environments -- Telegram text -- Private key not found error -- UI -- Resourcesdelete command -- Port number should be int -- Separate delete with validation of server -- Add nixpacks info -- Remove filter -- Container logs are now followable in full-screen and sorted by timestamp -- Ui for labels -- Ui -- Deletions -- Build_image not found -- Github source view -- Github source view -- Dockercleanupjob should be released back -- Ui -- Local ip address -- Revert workdir to basedir -- Container status jobs for old pr deployments -- Service updates - -## [4.0.0-beta.99] - 2023-10-24 - -### 🚀 Features - -- Improve deployment time by a lot - -### 🐛 Bug Fixes - -- Space in build args -- Lock SERVICE_FQDN envs -- If user is invited, that means its email is verified -- Force password reset on invited accounts -- Add ssh options to git ls-remote -- Git ls-remote -- Remove coolify labels from ui - -### 💼 Other - -- Fix subs - -## [4.0.0-beta.97] - 2023-10-20 - -### 🚀 Features - -- Standalone mongodb -- Cloning project -- Api tokens + deploy webhook -- Start all kinds of things -- Simple search functionality -- Mysql, mariadb -- Lock environment variables -- Download local backups - -### 🐛 Bug Fixes - -- Service docs links -- Add PGUSER to prevent HC warning -- Preselect s3 storage if available -- Port exposes change, shoud regenerate label -- Boarding -- Clone to with the same environment name -- Cleanup stucked resources on start -- Do not allow to delete env if a resource is defined -- Service template generator + appwrite -- Mongodb backup -- Make sure coolfiy network exists on install -- Syncbunny command -- Encrypt mongodb password -- Mongodb healtcheck command -- Rate limit for api + add mariadb + mysql -- Server settings guarded - -### 💼 Other - -- Generate services -- Mongodb backup -- Mongodb backup -- Updates - -## [4.0.0-beta.93] - 2023-10-18 - -### 🚀 Features - -- Able to customize docker labels on applications -- Show if config is not applied - -### 🐛 Bug Fixes - -- Setup:dev script & contribution guide -- Do not show configuration changed if config_hash is null -- Add config_hash if its null (old deployments) -- Label generation -- Labels -- Email channel no recepients -- Limit horizon processes to 2 by default -- Add custom port as ssh option to deploy_key based commands -- Remove custom port from git repo url -- ContainerStatus job - -### 💼 Other - -- PAT by team - -## [4.0.0-beta.92] - 2023-10-17 - -### 🐛 Bug Fixes - -- Proxy start process - -## [4.0.0-beta.91] - 2023-10-17 - -### 🐛 Bug Fixes - -- Always start proxy if not NONE is selected - -### 💼 Other - -- Add helper to service domains - -## [4.0.0-beta.90] - 2023-10-17 - -### 🐛 Bug Fixes - -- Only include config.json if its exists and a file - -### 💼 Other - -- Wordpress - -## [4.0.0-beta.89] - 2023-10-17 - -### 🐛 Bug Fixes - -- Noindex meta tag -- Show docker build logs - -## [4.0.0-beta.88] - 2023-10-17 - -### 🚀 Features - -- Use docker login credentials from server - -## [4.0.0-beta.87] - 2023-10-17 - -### 🐛 Bug Fixes - -- Service status check is a bit better -- Generate fqdn if you deleted a service app, but it requires fqdn -- Cancel any deployments + queue next -- Add internal domain names during build process - -## [4.0.0-beta.86] - 2023-10-15 - -### 🐛 Bug Fixes - -- Build image before starting dockerfile buildpacks - -## [4.0.0-beta.85] - 2023-10-14 - -### 🐛 Bug Fixes - -- Redis URL generated - -## [4.0.0-beta.83] - 2023-10-13 - -### 🐛 Bug Fixes - -- Docker hub URL - -## [4.0.0-beta.70] - 2023-10-09 - -### 🚀 Features - -- Add email verification for cloud -- Able to deploy docker images -- Add dockerfile location -- Proxy logs on the ui -- Add custom redis conf - -### 🐛 Bug Fixes - -- Server validation process -- Fqdn could be null -- Small -- Server unreachable count -- Do not reset unreachable count -- Contact docs -- Check connection -- Server saving -- No env goto envs from dashboard -- Goto -- Tcp proxy for dbs -- Database backups -- Only send email if transactional email set -- Backupfailed notification is forced -- Use port exposed for reverse proxy -- Contact link -- Use only ip addresses for servers -- Deleted team and it is the current one -- Add new team button -- Transactional email link -- Dashboard goto link -- Only require registry image in case of dockerimage bp -- Instant save build pack change -- Public git -- Cannot remove localhost -- Check localhost connection -- Send unreachable/revived notifications -- Boarding + verification -- Make sure proxy wont start in NONE mode -- Service check status 10 sec -- IsCloud in production seeder -- Make sure to use IP address -- Dockerfile location feature -- Server ip could be hostname in self-hosted -- Urls should be password fields -- No backup for redis -- Show database logs in case of its not healthy and running -- Proxy check for ports, do not kill anything listening on port 80/443 -- Traefik dashboard ip -- Db labels -- Docker cleanup jobs -- Timeout for instant remote processes -- Dev containerjobs -- Backup database one-by-one. -- Turn off static deployment if you switch buildpacks - -### 💼 Other - -- Dockerimage -- Updated dashboard -- Fix -- Fix -- Coolify proxy access logs exposed in dev -- Able to select environment on new resource -- Delete server -- Redis - -## [4.0.0-beta.58] - 2023-10-02 - -### 🚀 Features - -- Reset root password -- Attach Coolify defined networks to services -- Delete resource command -- Multiselect removable resources -- Disable service, required version -- Basedir / monorepo initial support -- Init version of any git deployment -- Deploy private repo with ssh key - -### 🐛 Bug Fixes - -- If waitlist is disabled, redirect to register -- Add destination to new services -- Predefined content for files -- Move /data to ./_data in dev -- UI -- Show all storages in one place for services -- Ui -- Add _data to vite ignore -- Only use _ in volume names for services -- Volume names in services -- Volume names -- Service logs visible if the whole service stack is not running -- Ui -- Compose magic -- Compose parser updated -- Dev compose files -- Traefik labels for multiport deployments -- Visible version number -- Remove SERVICE_ from deployable compose -- Delete event to deleting -- Move dev data to volumes to prevent permission issues -- Traefik labelling in case of several http and https domain added -- PR deployments use the first fqdn as base -- Email notifications subscription fixed -- Services - do not remove unnecessary things for now -- Decrease max horizon processes to get lower memory usage -- Test emails only available for user owned smtp/resend -- Ui for self-hosted email settings -- Set smtp notifications on by default -- Select branch on other git -- Private repository -- Contribution guide -- Public repository names -- *(create)* Flex wrap on server & network selection -- Better unreachable/revived server statuses -- Able to set base dir for Dockerfile build pack - -### 💼 Other - -- Uptime kume hc updated -- Switch back to /data (volume errors) -- Notifications -- Add shared email option to everyone - -## [4.0.0-beta.57] - 2023-10-02 - -### 🚀 Features - -- Container logs - -### 🐛 Bug Fixes - -- Always pull helper image in dev -- Only show last 1000 lines -- Service status - -## [4.0.0-beta.47] - 2023-09-28 - -### 🐛 Bug Fixes - -- Next helper image -- Service templates -- Sync:bunny -- Update process if server has been renamed -- Reporting handler -- Localhost privatekey update -- Remove private key in case you removed a github app -- Only show manually added private keys on server view -- Show source on all type of applications -- Docker cleanup should be a job by server -- File/dir based volumes are now read from the server -- Respect server fqdn -- If public repository does not have a main branch -- Preselect branc on private repos -- Deploykey branch -- Backups are now working again -- Not found base_branch in git webhooks -- Coolify db backup -- Preview deployments name, status etc -- Services should have destination as well -- Dockerfile expose is not overwritten -- If app settings is not saved to db -- Do not show subscription cancelled noti -- Show real volume names -- Only parse expose in dockerfiles if ports_exposes is empty -- Add uuid to volume names -- New volumes for services should have - instead of _ - -### 💼 Other - -- Fix previews to preview - -## [4.0.0-beta.46] - 2023-09-28 - -### 🐛 Bug Fixes - -- Containerstatusjob -- Aaaaaaaaaaaaaaaaa -- Services view -- Services -- Manually create network for services -- Disable early updates -- Sslip for localhost -- ContainerStatusJob -- Cannot delete env with available services -- Sync command -- Install script drops an error -- Prevent sync version (it needs an option) -- Instance fqdn setting -- Sentry 4510197209 -- Sentry 4504136641 -- Sentry 4502634789 - -## [4.0.0-beta.45] - 2023-09-24 - -### 🚀 Features - -- Services -- Image tag for services - -### 🐛 Bug Fixes - -- Applications with port mappins do a normal update (not rolling update) -- Put back build pack chooser -- Proxy configuration + starter -- Show real storage name on services -- New service template layout - -### 💼 Other - -- Fixed z-index for version link. -- Add source button -- Fixed z-index for magicbar -- A bit better error -- More visible feedback button -- Update help modal -- Help -- Marketing emails - -## [4.0.0-beta.28] - 2023-09-08 - -### 🚀 Features - -- Telegram topics separation -- Developer view for env variables -- Cache team settings -- Generate public key from private keys -- Able to invite more people at once -- Trial -- Dynamic trial period -- Ssh-agent instead of filesystem based ssh keys -- New container status checks -- Generate ssh key -- Sentry add email for better support -- Healthcheck for apps -- Add cloudflare tunnel support - -### 🐛 Bug Fixes - -- Db backup job -- Sentry 4459819517 -- Sentry 4451028626 -- Ui -- Retry notifications -- Instance email settings -- Ui -- Test email on for admins or custom smtp -- Coolify already exists should not throw error -- Delete database related things when delete database -- Remove -q from docker compose -- Errors in views -- Only send internal notifcations to enabled channels -- Recovery code -- Email sending error -- Sentry 4469575117 -- Old docker version error -- Errors -- Proxy check, reduce jobs, etc -- Queue after commit -- Remove nixpkgarchive -- Remove nixpkgarchive from ui -- Webhooks should not run if server is not functional -- Server is functional check -- Confirm email before sending -- Help should send cc on email -- Sub type -- Show help modal everywhere -- Forgot password -- Disable dockerfile based healtcheck for now -- Add timeout for ssh commands -- Prevent weird ui bug for validateServer -- Lowercase email in forgot password -- Lower case email on waitlist -- Encrypt jobs -- ProcessWithEnv()->run -- Plus boarding step about Coolify -- SaveConfigurationSync -- Help uri -- Sub for root -- Redirect on server not found -- Ip check -- Uniqueips -- Simply reply to help messages -- Help -- Rate limit -- Collect billing address -- Invitation -- Smtp view -- Ssh-agent revert -- Restarting container state on ui -- Generate new key -- Missing upgrade js -- Team error -- 4.0.0-beta.37 -- Localhost -- Proxy start (if not proxy defined, use Traefik) -- Do not remove localhost in boarding -- Allow non ip address (DNS) -- InstallDocker id not found -- Boarding -- Errors -- Proxy container status -- Proxy configuration saving -- Convert startProxy to action -- Stop/start UI on apps and dbs -- Improve localhost boarding process -- Try to use old docker-compose -- Boarding again -- Send internal notifications of email errors -- Add github app change on new app view -- Delete environment variables on app/db delete -- Save proxy configuration -- Add proxy to network with periodic check -- Proxy connections -- Delete persistent storages on resource deletion -- Prevent overwrite already existing env variables in services -- Mappings -- Sentry issue 4478125289 -- Make sure proxy path created -- StartProxy -- Server validation with cf tunnels -- Only show traefik dashboard if its available -- Services -- Database schema -- Report livewire errors -- Links with path -- Add traefik labels no matter if traefik is selected or not -- Add expose port for containers -- Also check docker socks permission on validation - -### 💼 Other - -- User should know that the public key -- Services are not availble yet -- Show registered users on waitlist page -- Nixpacksarchive -- Add Plausible analytics -- Global env variables -- Fix -- Trial emails -- Server check instead of app check -- Show trial instead of sub -- Server lost connection -- Services -- Services -- Services -- Ui for services -- Services -- Services -- Services -- Fixes -- Fix typo - -## [4.0.0-beta.27] - 2023-09-08 - -### 🐛 Bug Fixes - -- Bug - -## [4.0.0-beta.26] - 2023-09-08 - -### 🚀 Features - -- Public database - -## [4.0.0-beta.25] - 2023-09-07 - -### 🐛 Bug Fixes - -- SaveModel email settings - -## [4.0.0-beta.24] - 2023-09-06 - -### 🚀 Features - -- Send request in cloud -- Add discord notifications - -### 🐛 Bug Fixes - -- Form address -- Show hosted email service, just disable for non pro subs -- Add navbar for source + keys -- Add docker network to build process -- Overlapping apps -- Do not show system wide git on cloud -- Lowercase image names -- Typo - -### 💼 Other - -- Backup existing database - -## [4.0.0-beta.23] - 2023-09-01 - -### 🐛 Bug Fixes - -- Sentry bug -- Button loading animation - -## [4.0.0-beta.22] - 2023-09-01 - -### 🚀 Features - -- Add resend as transactional emails - -### 🐛 Bug Fixes - -- DockerCleanupjob -- Validation -- Webhook endpoint in cloud and no system wide gh app -- Subscriptions -- Password confirmation -- Proxy start job -- Dockerimage jobs are not overlapping - -## [4.0.0-beta.21] - 2023-08-27 - -### 🚀 Features - -- Invite by email from waitlist -- Rolling update - -### 🐛 Bug Fixes - -- Limits & server creation page -- Fqdn on apps - -### 💼 Other - -- Boarding - -## [4.0.0-beta.20] - 2023-08-17 - -### 🚀 Features - -- Send internal notification to discord -- Monitor server connection - -### 🐛 Bug Fixes - -- Make coolify-db backups unique dir - -## [4.0.0-beta.19] - 2023-08-15 - -### 🚀 Features - -- Pricing plans ans subs -- Add s3 storages -- Init postgresql database -- Add backup notifications -- Dockerfile build pack -- Cloud -- Force password reset + waitlist - -### 🐛 Bug Fixes - -- Remove buggregator from dev -- Able to change localhost's private key -- Readonly input box -- Notifications -- Licensing -- Subscription link -- Migrate db schema for smtp + discord -- Text field -- Null fqdn notifications -- Remove old modal -- Proxy stop/start ui -- Proxy UI -- Empty description -- Input and textarea -- Postgres_username name to not name, lol -- DatabaseBackupJob.php -- No storage -- Backup now button -- Ui + subscription -- Self-hosted - -### 💼 Other - -- Scheduled backups - -## [4.0.0-beta.18] - 2023-07-14 - -### 🚀 Features - -- Able to control multiplexing -- Add runRemoteCommandSync -- Github repo with deployment key -- Add persistent volumes -- Debuggable executeNow commands -- Add private gh repos -- Delete gh app -- Installation/update github apps -- Auto-deploy -- Deploy key based deployments -- Resource limits -- Long running queue with 1 hour of timeout -- Add arm build to dev -- Disk cleanup threshold by server -- Notify user of disk cleanup init - -### 🐛 Bug Fixes - -- Logo of CCCareers -- Typo -- Ssh -- Nullable name on deploy_keys -- Enviroments -- Remove dd - oops -- Add inprogress activity -- Application view -- Only set status in case the last command block is finished -- Poll activity -- Small typo -- Show activity on load -- Deployment should fail on error -- Tests -- Version -- Status not needed -- No project redirect -- Gh actions -- Set status -- Seeders -- Do not modify localhost -- Deployment_uuid -> type_uuid -- Read env from config, bc of cache -- Private key change view -- New destination -- Do not update next channel all the time -- Cancel deployment button -- Public repo limit shown + branch should be preselected. -- Better status on ui for apps -- Arm coolify version -- Formatting -- Gh actions -- Show github app secrets -- Do not force next version updates -- Debug log button -- Deployment key based works -- Deployment cancel/debug buttons -- Upgrade button -- Changing static build changes port -- Overwrite default nginx configuration -- Do not overlap docker image names -- Oops -- Found image name -- Name length -- Semicolons encoding by traefik -- Base_dir wip & outputs -- Cleanup docker images -- Nginx try_files -- Master is the default, not main -- No ms in rate limit resets -- Loading after button text -- Default value -- Localhost is usable -- Update docker-compose prod -- Cloud/checkoutid/lms -- Type of license code -- More verbose error -- Version lol -- Update prod compose -- Version - -### 💼 Other - -- Extract process handling from async job. -- Extract process handling from async job. -- Extract process handling from async job. -- Extract process handling from async job. -- Extract process handling from async job. -- Extract process handling from async job. -- Extract process handling from async job. -- Persisting data - -## [3.12.28] - 2023-03-16 - -### 🐛 Bug Fixes - -- Revert from dockerhub if ghcr.io does not exists - -## [3.12.27] - 2023-03-07 - -### 🐛 Bug Fixes - -- Show ip address as host in public dbs - -## [3.12.24] - 2023-03-04 - -### 🐛 Bug Fixes - -- Nestjs buildpack - -## [3.12.22] - 2023-03-03 - -### 🚀 Features - -- Add host path to any container - -### 🐛 Bug Fixes - -- Set PACK_VERSION to 0.27.0 -- PublishDirectory -- Host volumes -- Replace . & .. & $PWD with ~ -- Handle log format volumes - -## [3.12.19] - 2023-02-20 - -### 🚀 Features - -- Github raw icon url -- Remove svg support - -### 🐛 Bug Fixes - -- Typos in docs -- Url -- Network in compose files -- Escape new line chars in wp custom configs -- Applications cannot be deleted -- Arm servics -- Base directory not found -- Cannot delete resource when you are not on root team -- Empty port in docker compose - -## [3.12.18] - 2023-01-24 - -### 🐛 Bug Fixes - -- CleanupStuckedContainers -- CleanupStuckedContainers - -## [3.12.16] - 2023-01-20 - -### 🐛 Bug Fixes - -- Stucked containers - -## [3.12.15] - 2023-01-20 - -### 🐛 Bug Fixes - -- Cleanup function -- Cleanup stucked containers -- Deletion + cleanupStuckedContainers - -## [3.12.14] - 2023-01-19 - -### 🐛 Bug Fixes - -- Www redirect - -## [3.12.13] - 2023-01-18 - -### 🐛 Bug Fixes - -- Secrets - -## [3.12.12] - 2023-01-17 - -### 🚀 Features - -- Init h2c (http2/grpc) support -- Http + h2c paralel - -### 🐛 Bug Fixes - -- Build args docker compose -- Grpc - -## [3.12.11] - 2023-01-16 - -### 🐛 Bug Fixes - -- Compose file location -- Docker log sequence -- Delete apps with previews -- Do not cleanup compose applications as unconfigured -- Build env variables with docker compose -- Public gh repo reload compose - -### 💼 Other - -- Trpc -- Trpc -- Trpc -- Trpc -- Trpc -- Trpc -- Trpc - -## [3.12.10] - 2023-01-11 - -### 💼 Other - -- Add missing variables - -## [3.12.9] - 2023-01-11 - -### 🚀 Features - -- Add Openblocks icon -- Adding icon for whoogle -- *(ui)* Add libretranslate service icon -- Handle invite_only plausible analytics - -### 🐛 Bug Fixes - -- Custom gitlab git user -- Add documentation link again -- Remove prefetches -- Doc link -- Temporary disable dns check with dns servers -- Local images for reverting -- Secrets - -## [3.12.8] - 2022-12-27 - -### 🐛 Bug Fixes - -- Parsing secrets -- Read-only permission -- Read-only iam -- $ sign in secrets - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.12.5] - 2022-12-26 - -### 🐛 Bug Fixes - -- Remove unused imports - -### 💼 Other - -- Conditional on environment - -## [3.12.2] - 2022-12-19 - -### 🐛 Bug Fixes - -- Appwrite tmp volume -- Do not replace secret -- Root user for dbs on arm -- Escape secrets -- Escape env vars -- Envs -- Docker buildpack env -- Secrets with newline -- Secrets -- Add default node_env variable -- Add default node_env variable -- Secrets -- Secrets -- Gh actions -- Duplicate env variables -- Cleanupstorage - -### 💼 Other - -- Trpc -- Trpc -- Trpc -- Trpc -- Trpc -- Trpc -- Trpc -- Trpc -- Trpc -- Trpc - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.12.1] - 2022-12-13 - -### 🐛 Bug Fixes - -- Build commands -- Migration file -- Adding missing appwrite volume - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.12.0] - 2022-12-09 - -### 🚀 Features - -- Use registry for building -- Docker registries working -- Custom docker compose file location in repo -- Save doNotTrackData to db -- Add default sentry -- Do not track in settings -- System wide git out of beta -- Custom previewseparator -- Sentry frontend -- Able to host static/php sites on arm -- Save application data before deploying -- SimpleDockerfile deployment -- Able to push image to docker registry -- Revert to remote image -- *(api)* Name label - -### 🐛 Bug Fixes - -- 0 destinations redirect after creation -- Seed -- Sentry dsn update -- Dnt -- Ui -- Only visible with publicrepo -- Migrations -- Prevent webhook errors to be logged -- Login error -- Remove beta from systemwide git -- Git checkout -- Remove sentry before migration -- Webhook previewseparator -- Apache on arm -- Update PR/MRs with new previewSeparator -- Static for arm -- Failed builds should not push images -- Turn off autodeploy for simpledockerfiles -- Security hole -- Rde -- Delete resource on dashboard -- Wrong port in case of docker compose -- Public db icon on dashboard -- Cleanup - -### 💼 Other - -- Pocketbase release - -## [3.11.10] - 2022-11-16 - -### 🚀 Features - -- Only show expose if no proxy conf defined in template -- Custom/private docker registries - -### 🐛 Bug Fixes - -- Local dev api/ws urls -- Wrong template/type -- Gitea icon is svg -- Gh actions -- Gh actions -- Replace $$generate vars -- Webhook traefik -- Exposed ports -- Wrong icons on dashboard -- Escape % in secrets -- Move debug log settings to build logs -- Storage for compose bp + debug on -- Hasura admin secret -- Logs -- Mounts -- Load logs after build failed -- Accept logged and not logged user in /base -- Remote haproxy password/etc -- Remove hardcoded sentry dsn -- Nope in database strings - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ -- Version++ -- Version++ - -## [3.11.9] - 2022-11-15 - -### 🐛 Bug Fixes - -- IsBot issue - -## [3.11.8] - 2022-11-14 - -### 🐛 Bug Fixes - -- Default icon for new services - -## [3.11.1] - 2022-11-08 - -### 🚀 Features - -- Rollback coolify - -### 🐛 Bug Fixes - -- Remove contribution docs -- Umami template -- Compose webhooks fixed -- Variable replacements -- Doc links -- For rollback -- N8n and weblate icon -- Expose ports for services -- Wp + mysql on arm -- Show rollback button loading -- No tags error -- Update on mobile -- Dashboard error -- GetTemplates -- Docker compose persistent volumes -- Application persistent storage things -- Volume names for undefined volume names in compose -- Empty secrets on UI -- Ports for services - -### 💼 Other - -- Secrets on apps -- Fix -- Fixes -- Reload compose loading - -### 🚜 Refactor - -- Code - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Add jda icon for lavalink service -- Version++ - -### ◀️ Revert - -- Revert: revert - -## [3.11.0] - 2022-11-07 - -### 🚀 Features - -- Initial support for specific git commit -- Add default to latest commit and support for gitlab -- Redirect catch-all rule - -### 🐛 Bug Fixes - -- Secret errors -- Service logs -- Heroku bp -- Expose port is readonly on the wrong condition -- Toast -- Traefik proxy q 10s -- App logs view -- Tooltip -- Toast, rde, webhooks -- Pathprefix -- Load public repos -- Webhook simplified -- Remote webhooks -- Previews wbh -- Webhooks -- Websecure redirect -- Wb for previews -- Pr stopps main deployment -- Preview wbh -- Wh catchall for all -- Remove old minio proxies -- Template files -- Compose icon -- Templates -- Confirm restart service -- Template -- Templates -- Templates -- Plausible analytics things -- Appwrite webhook -- Coolify instance proxy -- Migrate template -- Preview webhooks -- Simplify webhooks -- Remove ghost-mariadb from the list -- More simplified webhooks -- Umami + ghost issues - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.10.16] - 2022-10-12 - -### 🐛 Bug Fixes - -- Single container logs and usage with compose - -### 💼 Other - -- New resource label - -## [3.10.15] - 2022-10-12 - -### 🚀 Features - -- Monitoring by container - -### 🐛 Bug Fixes - -- Do not show nope as ip address for dbs -- Add git sha to build args -- Smart search for new services -- Logs for not running containers -- Update docker binaries -- Gh release -- Dev container -- Gitlab auth and compose reload -- Check compose domains in general -- Port required if fqdn is set -- Appwrite v1 missing containers -- Dockerfile -- Pull does not work remotely on huge compose file - -### ⚙️ Miscellaneous Tasks - -- Update staging release - -## [3.10.14] - 2022-10-05 - -### 🚀 Features - -- Docker compose support -- Docker compose -- Docker compose - -### 🐛 Bug Fixes - -- Do not use npx -- Pure docker based development - -### 💼 Other - -- Docker-compose support -- Docker compose -- Remove worker jobs -- One less worker thread - -### 🧪 Testing - -- Remove prisma - -## [3.10.5] - 2022-09-26 - -### 🚀 Features - -- Add migration button to appwrite -- Custom certificate -- Ssl cert on traefik config -- Refresh resource status on dashboard -- Ssl certificate sets custom ssl for applications -- System-wide github apps -- Cleanup unconfigured applications -- Cleanup unconfigured services and databases - -### 🐛 Bug Fixes - -- Ui -- Tooltip -- Dropdown -- Ssl certificate distribution -- Db migration -- Multiplex ssh connections -- Able to search with id -- Not found redirect -- Settings db requests -- Error during saving logs -- Consider base directory in heroku bp -- Basedirectory should be empty if null -- Allow basedirectory for heroku -- Stream logs for heroku bp -- Debug log for bp -- Scp without host verification & cert copy -- Base directory & docker bp -- Laravel php chooser -- Multiplex ssh and ssl copy -- Seed new preview secret types -- Error notification -- Empty preview value -- Error notification -- Seed -- Service logs -- Appwrite function network is not the default -- Logs in docker bp -- Able to delete apps in unconfigured state -- Disable development low disk space -- Only log things to console in dev mode -- Do not get status of more than 10 resources defined by category -- BaseDirectory -- Dashboard statuses -- Default buildImage and baseBuildImage -- Initial deploy status -- Show logs better -- Do not start tcp proxy without main container -- Cleanup stucked tcp proxies -- Default 0 pending invitations -- Handle forked repositories -- Typo -- Pr branches -- Fork pr previews -- Remove unnecessary things -- Meilisearch data dir -- Verify and configure remote docker engines -- Add buildkit features -- Nope if you are not logged in - -### 💼 Other - -- Responsive! -- Fixes -- Fix git icon -- Dropdown as infobox -- Small logs on mobile -- Improvements -- Fix destination view -- Settings view -- More UI improvements -- Fixes -- Fixes -- Fix -- Fixes -- Beta features -- Fix button -- Service fixes -- Fix basedirectory meaning -- Resource button fix -- Main resource search -- Dev logs -- Loading button -- Fix gitlab importer view -- Small fix -- Beta flag -- Hasura console notification -- Fix -- Fix -- Fixes -- Inprogress version of iam -- Fix indicato -- Iam & settings update -- Send 200 for ping and installation wh -- Settings icon - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ -- Version++ -- Version++ -- Version++ -- Version++ -- Version++ - -### ◀️ Revert - -- Show usage everytime - -## [3.10.2] - 2022-09-11 - -### 🚀 Features - -- Add queue reset button -- Previewapplications init -- PreviewApplications finalized -- Fluentbit -- Show remote servers -- *(layout)* Added drawer when user is in mobile -- Re-apply ui improves -- *(ui)* Improve header of pages -- *(styles)* Make header css component -- *(routes)* Improve ui for apps, databases and services logs - -### 🐛 Bug Fixes - -- Changing umami image URL to get latest version -- Gitlab importer for public repos -- Show error logs -- Umami init sql -- Plausible analytics actions -- Login -- Dev url -- UpdateMany build logs -- Fallback to db logs -- Fluentbit configuration -- Coolify update -- Fluentbit and logs -- Canceling build -- Logging -- Load more -- Build logs -- Versions of appwrite -- Appwrite?! -- Get building status -- Await -- Await #2 -- Update PR building status -- Appwrite default version 1.0 -- Undead endpoint does not require JWT -- *(routes)* Improve design of application page -- *(routes)* Improve design of git sources page -- *(routes)* Ui from destinations page -- *(routes)* Ui from databases page -- *(routes)* Ui from databases page -- *(routes)* Ui from databases page -- *(routes)* Ui from services page -- *(routes)* More ui tweaks -- *(routes)* More ui tweaks -- *(routes)* More ui tweaks -- *(routes)* More ui tweaks -- *(routes)* Ui from settings page -- *(routes)* Duplicates classes in services page -- *(routes)* Searchbar ui -- Github conflicts -- *(routes)* More ui tweaks -- *(routes)* More ui tweaks -- *(routes)* More ui tweaks -- *(routes)* More ui tweaks -- Ui with headers -- *(routes)* Header of settings page in databases -- *(routes)* Ui from secrets table - -### 💼 Other - -- Fix plausible -- Fix cleanup button -- Fix buttons - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Minor changes -- Minor changes -- Minor changes -- Whoops - -## [3.10.1] - 2022-09-10 - -### 🐛 Bug Fixes - -- Show restarting apps -- Show restarting application & logs -- Remove unnecessary gitlab group name -- Secrets for PR -- Volumes for services -- Build secrets for apps -- Delete resource use window location - -### 💼 Other - -- Fix button -- Fix follow button -- Arm should be on next all the time - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.10.0] - 2022-09-08 - -### 🚀 Features - -- New servers view - -### 🐛 Bug Fixes - -- Change to execa from utils -- Save search input -- Ispublic status on databases -- Port checkers -- Ui variables -- Glitchtip env to pyhton boolean +- Use tags in update +- New update process (#115) +- VaultWarden service +- Www <-> non-www redirection for apps +- Www <-> non-www redirection +- Follow logs +- Generate www & non-www SSL certs +- Basic password reset form +- Scan for lock files and set right commands +- Public port range (WIP) +- Ports range +- Random subdomain for demo +- Random domain for services +- Astro buildpack +- 11ty buildpack +- Registration page +- Languagetool service +- Send version with update request +- Service secrets +- Webhooks inititate all applications with the correct branch +- Check ssl for new apps/services first +- Autodeploy pause +- Install pnpm into docker image if pnpm lock file is used +- Add PHP modules +- Use compose instead of normal docker cmd +- Be able to redeploy PRs +- Add n8n.io service +- Add update kuma service +- Ghost service +- Initial python support +- Add loading on register button +- *(dev)* Allow windows users to use pnpm dev +- MeiliSearch service +- Add abilitry to paste env files +- Wordpress on-demand SFTP +- Finalize on-demand sftp for wp +- PHP Composer support +- Working on-demand sftp to wp data +- Admin team sees everything +- Able to change service version/tag +- Basic white labeled version +- Able to modify database passwords +- Add persistent storage for services +- Multiply dockerfile locations for docker buildpack +- Testing fluentd logging driver +- Fluentbit investigation +- Initial deno support +- Deno DB migration +- Show exited containers on UI & better UX +- Query container state periodically +- Install svelte-18n and init setup +- Umami service +- Coolify auto-updater - Autoupdater - -### 💼 Other - -- Dashboard updates -- Fix tooltip - -## [3.9.4] - 2022-09-07 - -### 🐛 Bug Fixes - -- DnsServer formatting -- Settings for service - -## [3.9.3] - 2022-09-07 - -### 🐛 Bug Fixes - -- Pr previews - -## [3.9.2] - 2022-09-07 - -### 🚀 Features - -- Add traefik acme json to coolify container -- Database secrets - -### 🐛 Bug Fixes - -- Gitlab webhook -- Use ip address instead of window location -- Use ip instead of window location host -- Service state update -- Add initial DNS servers -- Revert last change with domain check -- Service volume generation -- Minio default env variables -- Add php 8.1/8.2 -- Edgedb ui -- Edgedb stuff -- Edgedb - -### 💼 Other - -- Fix login/register page -- Update devcontainer -- Add debug log -- Fix initial loading icon bg -- Fix loading start/stop db/services -- Dashboard updates and a lot more - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ - -## [3.9.0] - 2022-09-06 - -### 🐛 Bug Fixes - -- Debug api logging + gh actions -- Workdir -- Move restart button to settings - -## [3.9.1-rc.1] - 2022-09-06 - -### 🚀 Features - -- *(routes)* Rework ui from login and register page - -### 🐛 Bug Fixes - -- Ssh pid agent name -- Repository link trim -- Fqdn or expose port required -- Service deploymentEnabled -- Expose port is not required -- Remote verification -- Dockerfile - -### 💼 Other - -- Database_branches -- Login page - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ - -## [3.9.0-rc.1] - 2022-09-02 - -### 🚀 Features - +- Select base image for buildpacks +- Hasura as a service +- Gzip compression +- Laravel buildpack is working! +- Laravel +- Fider service +- Database and services logs +- DNS check settings for SSL generation +- Cancel builds! +- Basic server usage on dashboard +- Show usage trends +- Usage on dashboard +- Custom script path for Plausible +- WP could have custom db +- Python image selection +- PageLoader +- Database + service usage +- Ability to change deployment type for nextjs +- Ability to change deployment type for nuxtjs +- Gitpod ready code(almost) +- Add Docker buildpack exposed port setting +- Custom port for git instances +- Gitpod integration +- Init moodle and separate stuffs to shared package +- Moodle init +- Remote docker engine init +- Working on remote docker engine +- Rde +- Remote docker engine +- Ipv4 and ipv6 +- Contributors +- Add arch to database +- Stop preview deployment +- Persistent storage for all services +- Cleanup clickhouse db +- Init heroku buildpacks +- Databases on ARM +- Mongodb arm support +- New dashboard +- Appwrite service +- Heroku deployments +- Deploy bots (no domains) +- Custom dns servers +- Import public repos (wip) +- Public repo deployment +- Force rebuild + env.PORT for port + public repo build +- Add GlitchTip service +- Searxng service +- *(ui)* Rework home UI and with responsive design - New service - weblate - Restart application - Show elapsed time on running builds - Github allow fual branches - Gitlab dual branch - Taiga - -### 🐛 Bug Fixes - -- Glitchtip things -- Loading state on start -- Ui -- Submodule -- Gitlab webhooks -- UI + refactor -- Exposedport on save -- Appwrite letsencrypt -- Traefik appwrite +- *(routes)* Rework ui from login and register page +- Add traefik acme json to coolify container +- Database secrets +- New servers view +- Add queue reset button +- Previewapplications init +- PreviewApplications finalized +- Fluentbit +- Show remote servers +- *(layout)* Added drawer when user is in mobile +- Re-apply ui improves +- *(ui)* Improve header of pages +- *(styles)* Make header css component +- *(routes)* Improve ui for apps, databases and services logs +- Add migration button to appwrite +- Custom certificate +- Ssl cert on traefik config +- Refresh resource status on dashboard +- Ssl certificate sets custom ssl for applications +- System-wide github apps +- Cleanup unconfigured applications +- Cleanup unconfigured services and databases +- Docker compose support +- Docker compose +- Docker compose +- Monitoring by container +- Initial support for specific git commit +- Add default to latest commit and support for gitlab +- Redirect catch-all rule +- Rollback coolify +- Only show expose if no proxy conf defined in template +- Custom/private docker registries +- Use registry for building +- Docker registries working +- Custom docker compose file location in repo +- Save doNotTrackData to db +- Add default sentry +- Do not track in settings +- System wide git out of beta +- Custom previewseparator +- Sentry frontend +- Able to host static/php sites on arm +- Save application data before deploying +- SimpleDockerfile deployment +- Able to push image to docker registry +- Revert to remote image +- *(api)* Name label +- Add Openblocks icon +- Adding icon for whoogle +- *(ui)* Add libretranslate service icon +- Handle invite_only plausible analytics +- Init h2c (http2/grpc) support +- Http + h2c paralel +- Github raw icon url +- Remove svg support +- Add host path to any container +- Able to control multiplexing +- Add runRemoteCommandSync +- Github repo with deployment key +- Add persistent volumes +- Debuggable executeNow commands +- Add private gh repos +- Delete gh app +- Installation/update github apps +- Auto-deploy +- Deploy key based deployments +- Resource limits +- Long running queue with 1 hour of timeout +- Add arm build to dev +- Disk cleanup threshold by server +- Notify user of disk cleanup init +- Pricing plans ans subs +- Add s3 storages +- Init postgresql database +- Add backup notifications +- Dockerfile build pack +- Cloud +- Force password reset + waitlist +- Send internal notification to discord +- Monitor server connection +- Invite by email from waitlist +- Rolling update +- Add resend as transactional emails +- Send request in cloud +- Add discord notifications +- Public database +- Telegram topics separation +- Developer view for env variables +- Cache team settings +- Generate public key from private keys +- Able to invite more people at once +- Trial +- Dynamic trial period +- Ssh-agent instead of filesystem based ssh keys +- New container status checks +- Generate ssh key +- Sentry add email for better support +- Healthcheck for apps +- Add cloudflare tunnel support +- Services +- Image tag for services +- Container logs +- Reset root password +- Attach Coolify defined networks to services +- Delete resource command +- Multiselect removable resources +- Disable service, required version +- Basedir / monorepo initial support +- Init version of any git deployment +- Deploy private repo with ssh key +- Add email verification for cloud +- Able to deploy docker images +- Add dockerfile location +- Proxy logs on the ui +- Add custom redis conf +- Use docker login credentials from server +- Able to customize docker labels on applications +- Show if config is not applied +- Standalone mongodb +- Cloning project +- Api tokens + deploy webhook +- Start all kinds of things +- Simple search functionality +- Mysql, mariadb +- Lock environment variables +- Download local backups +- Improve deployment time by a lot +- Deployment logs fullscreen +- Service database backups +- Make service databases public +- Log drain (wip) +- Enable/disable log drain by service +- Log drainer container check +- Add docker engine support install script to rhel based systems +- Save timestamp configuration for logs +- Custom log drain endpoints +- Auto-restart tcp proxies for databases +- Execute command in container +- Autoupdate env during seed +- Disable autoupdate +- Randomly sleep between executions +- Pull latest images for services +- Custom docker compose commands +- Add environment description + able to change name +- Raw docker compose deployments +- Add www-non-www redirects to traefik +- Import backups +- Search between resources +- Move resources between projects / environments +- Clone any resource +- Shared environments +- Concurrent builds / server +- Able to deploy multiple resources with webhook +- Add PR comments +- Dashboard live deployment view +- Added manual webhook support for bitbucket +- Add initial support for custom docker run commands +- Cleanup unreachable servers +- Tags and tag deploy webhooks +- Clone to env +- Multi deployments +- Cleanup queue +- Magic for traefik redirectregex in services +- Revalidate server +- Disable gzip compression on service applications +- Save github app permission locally +- Minversion for services +- Able to add dynamic configurations from proxy dashboard +- Custom server limit +- Delay container/server jobs +- Add static ipv4 ipv6 support +- Server disabled by overflow +- Preview deployment logs +- Collect webhooks during maintenance +- Logs and execute commands with several servers +- Domains api endpoint +- Resources api endpoint +- Team api endpoint +- Add deployment details to deploy endpoint +- Add deployments api +- Experimental caddy support +- Dynamic configuration for caddy +- Reset password +- Show resources on source page +- Able to run scheduler/horizon programatically +- Change page width +- Watch paths +- Able to make rsa/ed ssh keys +- *(application)* Update submodules after git checkout +- Add amazon linux 2023 +- Upload large backups +- Edit domains easier for compose +- Able to delete configuration from server +- Configuration checker for all resources +- Allow tab in textarea +- Dynamic mux time +- Literal env variables +- Lazy load stuffs + tell user if compose based deployments have missing envs +- Can edit file/dir volumes from ui in compose based apps +- Upgrade Appwrite service template to 1.5 +- Upgrade Appwrite service template to 1.5 +- Add db name to backup notifications +- Initial datalist +- Update service contribution docs URL +- The final pricing plan, pay-as-you-go +- Add container name to network aliases in ApplicationDeploymentJob +- Add lazy loading for images in General.php and improve Docker Compose file handling in Application.php +- Experimental sentinel +- Start Sentinel on servers. +- Pull new sentinel image and restart container +- Init metrics +- Add AdminRemoveUser command to remove users from the database +- Adding new COOLIFY_ variables +- Save commit message and better view on deployments +- Toggle label escaping mechanism +- Shows the latest deployment commit + message on status +- New manual update process + remove next_channel +- Add lastDeploymentInfo and lastDeploymentLink props to breadcrumbs and status components +- Sort envs alphabetically and creation date +- Improve sorting of environment variables in the All component +- Update healthcheck test in StartMongodb action +- Add pull_request_id filter to get_last_successful_deployment method in Application model +- Add hc logs to healthchecks +- Add SerpAPI as a Github Sponsor +- Admin view for deleting users +- Scheduled task failed notification +- If the time seems too long it remains at 0s +- Improve Docker Engine start logic in ServerStatusJob +- If proxy stopped manually, it won't start back again +- Exclude_from_hc magic +- Gitea manual webhooks +- Add container logs in case the container does not start healthy +- Handle incomplete expired subscriptions in Stripe webhook +- Add more persistent storage types +- Add PHP memory limit environment variable to docker-compose.prod.yml +- Add manual update option to UpdateCoolify handle method +- Add port configuration for Vaultwarden service +- Able to change database passwords on the UI. It won't sync to the database. +- Able to add several domains to compose based previews +- Add bounty program link to bug report template +- Add titles +- Db proxy logs +- Easily redirect between www-and-non-www domains +- Add logos for new sponsors +- Add homepage template +- Update homepage.yaml with environment variables and volumes +- Spanish translation +- Cancelling a deployment will check if new could be started. +- Add supaguide logo to donations section +- Nixpacks now could reach local dbs internally +- Add Tigris logo to other/logos directory +- COOLIFY_CONTAINER_NAME predefined variable +- Charts +- Sentinel + charts +- Container metrics +- Add high priority queue +- Add metrics warning for servers without Sentinel enabled +- Add blacksmith logo to donations section +- Preselect server and destination if only one found +- More api endpoints +- Add API endpoint to update application by UUID +- Update statusnook logo filename in compose template +- Local fonts +- More API endpoints +- Bulk env update api endpoint +- Update server settings metrics history days to 7 +- New app API endpoint +- Private gh deployments through api +- Lots of api endpoints +- Api api api api api api +- Rename CloudCleanupSubs to CloudCleanupSubscriptions +- Early fraud warning webhook +- Improve internal notification message for early fraud warning webhook +- Add schema for uuid property in app update response +- Cleanup unused docker networks from proxy +- Compose parser v2 +- Display time interval for rollback images +- Add security and storage access key env to twenty template +- Add new logo for Latitude +- Enable legacy model binding in Livewire configuration +- Improve error handling in loadComposeFile method +- Add readonly labels +- Preserve git repository +- Force cleanup server +- Create/delete project endpoints +- Add patch request to projects +- Add server api endpoints +- Add branddev logo to README.md +- Update API endpoint summaries +- Update Caddy button label in proxy.blade.php +- Check custom internal name through server's applications. +- New server check job +- Delete team in cloud without subscription +- Coolify init should cleanup stuck networks in proxy +- Add manual update check functionality to settings page +- Update auto update and update check frequencies in settings +- Update Upgrade component to check for latest version of Coolify +- Improve homepage service template +- Support map fields in Directus +- Labels by proxy type +- Able to generate only the required labels for resources +- Preserve git repository with advanced file storages +- Added Windmill template +- Added Budibase template +- Add shm-size for custom docker commands +- Add custom docker container options to all databases +- Able to select different postgres database +- Add new logos for jobscollider and hostinger +- Order scheduled task executions +- Add Code Server environment variables to Service model +- Add coolify build env variables to building phase +- Add new logos for GlueOps, Ubicloud, Juxtdigital, Saasykit, and Massivegrid +- Add new logos for GlueOps, Ubicloud, Juxtdigital, Saasykit, and Massivegrid +- Update server_settings table to force docker cleanup +- Update Docker Compose file with DB_URL environment variable +- Refactor shared.php to improve environment variable handling +- Expose project description in API response +- Add elixir finetunes to the deployment job +- Make coolify full width by default +- Fully functional terminal for command center +- Custom terminal host +- Add buddy logo +- Add nullable constraint to 'fingerprint' column in private_keys table +- *(api)* Add an endpoint to execute a command +- *(api)* Add endpoint to execute a command +- Add ContainerStatusTypes enum for managing container status +- Allow specify use_build_server when creating/updating an application +- Add support for `use_build_server` in API endpoints for creating/updating applications +- Add Mixpost template +- Update resource deletion job to allow configurable options through API +- Add query parameters for deleting configurations, volumes, docker cleanup, and connected networks +- Add command to check application deployment queue +- Support Hetzner S3 +- Handle HTTPS domain in ConfigureCloudflareTunnels +- Backup all databases for mysql,mariadb,postgresql +- Restart service without pulling the latest image +- Add strapi template +- Add it-tools service template and logo +- Add homarr service tamplate and logo +- Add Argilla service configuration to Service model +- Add Invoice Ninja service configuration to Service model +- Project search on frontend +- Add ollama service with open webui and logo +- Update setType method to use slug value for type +- Refactor setType method to use slug value for type +- Refactor setType method to use slug value for type +- Add Supertokens template +- Add easyappointments service template +- Add dozzle template +- Adds forgejo service with runners +- Add Mautic 4 and 5 to service templates +- Add keycloak template +- Add onedev template +- Improve search functionality in project selection +- Add customHelper to stack-form +- Add cloudbeaver template +- Add ntfy template +- Add qbittorrent template +- Add Homebox template +- Add owncloud service and logo +- Add immich service +- Auto generate url +- Refactored to work with coolify auto env vars +- Affine service template and logo +- Add LibreTranslate template +- Open version in a new tab +- Add Transmission template +- Add transmission healhcheck +- Add zipline template +- Dify template +- Required envs +- Add EdgeDB +- Show warning if people would like to use sslip with https +- Add is shared to env variables +- Variabel sync and support shared vars +- Add notification settings to server_disk_usage +- Add coder service tamplate and logo +- Debug mode for sentinel +- Add jitsi template +- Add --gpu support for custom docker command +- Add Firefox template +- Add template for Wiki.js +- Add upgrade logs to /data/coolify/source +- Custom nginx configuration for static deployments + fix 404 redirects in nginx conf +- Check local horizon scheduler deployments +- Add internal api docs to /docs/api with auth +- Add proxy type change to create/update apis +- Add MacOS template +- Add Windows template +- *(service)* :sparkles: add mealie +- Add hex magic env var +- Add deploy-only token permission +- Able to deploy without cache on every commit +- Update private key nam with new slug as well +- Allow disabling default redirect, set status to 503 +- Add TLS configuration for default redirect in Server model +- Slack notifications +- Introduce root permission +- Able to download schedule task logs +- Migrate old email notification settings from the teams table +- Migrate old discord notification settings from the teams table +- Migrate old telegram notification settings from the teams table +- Add slack notifications to a new table +- Enable success messages again +- Use new notification stuff inside team model +- Some more notification settings and better defaults +- New email notification settings +- New shared function name `is_transactional_emails_enabled()` +- New shared notifications functions +- Email Notification Settings Model +- Telegram notification settings Model +- Discord notification settings Model +- Slack notification settings Model +- New Discord notification UI +- New Slack notification UI +- New telegram UI +- Use new notification event names +- Always sent notifications +- Scheduled task success notification +- Notification trait +- Get discord Webhook form new table +- Get Slack Webhook form new table +- Use new table or instance settings for email +- Use new place for settings and topic IDs for telegram +- Encrypt instance email settings +- Use encryption in instance settings model +- Scheduled task success and failure notifications +- Add docker cleanup success and failure notification settings columns +- UI for docker cleanup success and failure notification +- Docker cleanup email views +- Docker cleanup success and failure notification files +- Scheduled task success email +- Send new docker cleanup notifications +- :passport_control: integrate Authentik authentication with Coolify +- *(notification)* Add Pushover +- Add seeder command and configuration for database seeding +- Add new password magic env with symbols +- Add documenso service +- New ServerReachabilityChanged event +- Use new ServerReachabilityChanged event instead of isDirty +- Add infomaniak oauth +- Add server disk usage check frequency +- Add environment_uuid support and update API documentation +- Add service/resource/project labels +- Add coolify.environment label +- Add database subtype +- Migrate to new encryption options +- New encryption options +- Able to import full db backups for pg/mysql/mariadb +- Restore backup from server file +- Docker volume data cloning +- Move volume data cloning to a Job +- Volume cloning for ResourceOperations +- Remote server volume cloning +- Add horizon server details to queue +- Enhance horizon:manage command with worker restart check +- Add is_coolify_host to the server api responses +- DB migration for Backup retention +- UI for backup retention settings +- New global s3 and local backup deletion function +- Use new backup deletion functions +- Add calibre-web service +- Add actual-budget service +- Add rallly service +- Template for Gotenberg, a Docker-powered stateless API for PDF files +- Enhance import command options with additional guidance and improved checkbox label +- Purify for better sanitization +- Move docker cleanup to its own tab +- DB and Model for docker cleanup executions +- DockerCleanupExecutions relationship +- DockerCleanupDone event +- Get command and output for logs from CleanupDocker +- New sidebar menu and order +- Docker cleanup executions UI +- Add execution log to dockerCleanupJob +- Improve deployment UI +- Root user envs and seeding +- Email, username and password validation when they are set via envs +- Improved error handling and log output +- Add root user configuration variables to production environment +- Add log file check message in upgrade script for better troubleshooting +- Add root user details to install script +- *(core)* Wip version of coolify.json +- *(core)* Add SOURCE_COMMIT variable to build environment in ApplicationDeploymentJob +- *(service)* Update affine.yaml with AI environment variables (#4918) +- *(service)* Add new service Flipt (#4875) +- *(docs)* Update tech stack +- *(terminal)* Show terminal unavailable if the container does not have a shell on the global terminal UI +- *(ui)* Improve deployment UI +- *(template)* Add Open Web UI +- *(templates)* Add Open Web UI service template +- *(ui)* Update GitHub source creation advanced section label +- *(core)* Add dynamic label reset for application settings +- *(ui)* Conditionally enable advanced application settings based on label readonly status +- *(env)* Added COOLIFY_RESOURCE_UUID environment variable +- *(vite)* Add Cloudflare async script and style tag attributes +- *(meta)* Add comprehensive SEO and social media meta tags +- *(core)* Add name to default proxy configuration +- Add application api route +- Container logs +- Remove ansi color from log +- Add lines query parameter +- *(changelog)* Add git cliff for automatic changelog generation +- *(workflows)* Improve changelog generation and workflows +- *(ui)* Add periodic status checking for services +- *(deployment)* Ensure private key is stored in filesystem before deployment +- *(slack)* Show message title in notification previews (#5063) +- *(i18n)* Add Arabic translations (#4991) +- *(i18n)* Add French translations (#4992) +- *(services)* Update `service-templates.json` +- *(ui)* Add top padding to pricing plans view +- *(core)* Add error logging and cron parsing to docker/server schedules +- *(core)* Prevent using servers with existing resources as build servers +- *(ui)* Add textarea switching option in service compose editor +- *(ui)* Add wire:key to two-step confirmation settings +- *(database)* Add index to scheduled task executions for improved query performance +- *(database)* Add index to scheduled database backup executions +- *(billing)* Add Stripe past due subscription status tracking +- *(ui)* Add past due subscription warning banner +- *(service)* Neon +- *(migration)* Add `ssl_certificates` table and model +- *(migration)* Add ssl setting to `standalone_postgresqls` table +- *(ui)* Add ssl settings to Postgres ui +- *(db)* Add ssl mode to Postgres URLs +- *(db)* Setup ssl during Postgres start +- *(migration)* Encrypt local file volumes content and paths +- *(ssl)* Ssl generation helper +- *(ssl)* Migrate to `ECC`certificates using `secp521r1` +- *(ssl)* Improve SSL helper +- *(ssl)* Add a Coolify CA Certificate to all servers +- *(seeder)* Call CA SSL seeder in prod and dev +- *(ssl)* Add Coolify CA Certificate when adding a new server +- *(installer)* Create CA folder during installation +- *(ssl)* Improve SSL helper +- *(ssl)* Use new improved helper for SSL generation +- *(ui)* Add CA cert UI +- *(ui)* New copy button component +- *(ui)* Use new copy button component everywhere +- *(ui)* Improve server advanced view +- *(migration)* Add CN and alternative names to DB +- *(databases)* Add CA SSL crt location to Postgres URLs +- *(ssl)* Improve ssl generation +- *(ssl)* Regenerate SSL certs job +- *(ssl)* Regenerate certificate and valid until UI +- *(ssl)* Regenerate CA cert and all other certs logic +- *(ssl)* Add full MySQL SSL Support +- *(ssl)* Add full MariaDB SSL support +- *(ssl)* Add `openssl.conf` to configure SSL extension properly +- *(ssl)* Improve SSL generation and security a lot +- *(ssl)* Check for SSL renewal twice daily +- *(ssl)* Add SSL relationships to all DBs +- Add full SSL support to MongoDB +- *(ssl)* Fix some issues and improve ssl generation helper +- *(ssl)* Ability to create `.pem` certs and add `clientAuth` to `extendedKeyUsage` +- *(ssl)* New modes for MongoDB and get `caCert` and `mountPath` correctly +- *(ssl)* Full SSL support for Redis +- New mode implementation for MongoDB +- *(ssl)* Improve Redis and remove modes +- Full SSL support for DrangonflyDB +- SSL notification +- *(github-source)* Enhance GitHub App configuration with manual and private key support +- *(ui)* Improve GitHub repository selection and styling +- *(database)* Implement two-step confirmation for database deletion +- *(assets)* Add new SVG logo for Coolify +- *(install)* Enhance Docker address pool configuration and validation +- *(install)* Improve Docker address pool management and service restart logic +- *(install)* Add missing env variable to install script +- *(LocalFileVolume)* Add binary file detection and update UI logic +- *(templates)* Change glance for v0.7 +- *(templates)* Add Freescout service template +- *(service)* Add Evolution API template +- *(service)* Add evolution-api and neon-ws-proxy templates +- *(svg)* Add coolify and evolution-api SVG logos +- *(api)* Add api to create custom services +- *(api)* Separate create and one-click routes +- *(api)* Update Services api routes and handlers +- *(api)* Unify service creation endpoint and enhance validation +- *(notifications)* Add discord ping functionality and settings +- *(user)* Implement session deletion on password reset +- *(github)* Enhance repository loading and validation in applications +- *(database)* Disable MongoDB SSL by default in migration +- *(database)* Add CA certificate generation for database servers +- *(application)* Add SPA configuration and update Nginx generation logic +- *(deployments)* Add list application deployments api route +- *(deploy)* Add pull request ID parameter to deploy endpoint +- *(api)* Add pull request ID parameter to applications endpoint +- *(api)* Add endpoints for retrieving application logs and deployments +- *(lang)* Added Norwegian language (#5280) +- *(dep)* Bump all dependencies +- *(lang)* Added Azerbaijani language updated turkish language. (#5497) +- *(lang)* Added Portuguese from Brazil language (#5500) +- *(lang)* Add Indonesian language translations (#5513) +- *(api)* Update OpenAPI spec for services (#5448) +- *(proxy)* Enhance proxy handling and port conflict detection +- *(Deploy)* Add info dispatch for proxy check initiation +- *(EnvironmentVariable)* Add handling for Redis credentials in the environment variable component +- *(EnvironmentVariable)* Implement protection for critical environment variables and enhance deletion logic +- *(Application)* Add networkAliases attribute for handling network aliases as JSON or comma-separated values +- *(GithubApp)* Update default events to include 'pull_request' and streamline event handling +- *(CleanupDocker)* Add support for realtime image management in Docker cleanup process +- *(Deployment)* Enhance queue_application_deployment to handle existing deployments and return appropriate status messages +- *(SourceManagement)* Add functionality to change Git source and display current source in the application settings +- *(OpenApi)* Enhance OpenAPI specifications by adding UUID parameters for application, project, and service updates; improve deployment listing with pagination parameters; update command signature for OpenApi generation +- *(subscription)* Enhance subscription management with loading states and Stripe status checks +- Add HTTP Basic Authentication +- *(readme)* Add new sponsors Supadata AI and WZ-IT to the README +- *(core)* Enable magic env variables for compose based applications +- *(deployment)* Add repository_project_id handling for private GitHub apps and clean up unused Caddy label logic +- *(api)* Enhance OpenAPI specifications with token variable and additional key attributes +- *(docker)* Add HTTP Basic Authentication support and enhance hostname parsing in Docker run conversion +- *(api)* Add HTTP Basic Authentication fields to OpenAPI specifications and enhance PrivateKey model descriptions +- *(README)* Add InterviewPal sponsorship link and corresponding SVG icon +- *(migration)* Add 'is_migrated' and 'custom_type' columns to service_applications and service_databases tables +- *(backup)* Implement custom database type selection and enhance scheduled backups management +- *(README)* Add Gozunga and Macarne to sponsors list +- *(redis)* Add scheduled cleanup command for Redis keys and enhance cleanup logic +- *(core)* Add 'postmarketos' to supported OS list +- *(service)* Add memos service template (#5032) +- *(ui)* Upgrade to Tailwind v4 (#5710) +- *(service)* Add Navidrome service template (#5022) +- *(service)* Add Passbolt service (#5769) +- *(service)* Add Vert service (#5663) +- *(service)* Add Ryot service (#5232) +- *(service)* Add Marimo service (#5559) +- *(service)* Add Diun service (#5113) +- *(service)* Add Observium service (#5613) +- *(service)* Add Leantime service (#5792) +- *(service)* Add Limesurvey service (#5751) +- *(service)* Add Paymenter service (#5809) +- *(service)* Add CodiMD service (#4867) +- *(modal)* Add dispatchAction property to confirmation modal +- *(security)* Implement server patching functionality +- *(service)* Add Typesense service (#5643) +- *(service)* Add Yamtrack service (#5845) +- *(service)* Add PG Back Web service (#5079) +- *(service)* Update Maybe service and adjust it for the new release (#5795) +- *(oauth)* Set redirect uri as optional and add default value (#5760) +- *(service)* Add apache superset service (#4891) +- *(service)* Add One Time Secret service (#5650) +- *(service)* Add Seafile service (#5817) +- *(service)* Add Netbird-Client service (#5873) +- *(service)* Add OrangeHRM and Grist services (#5212) +- *(rules)* Add comprehensive documentation for Coolify architecture and development practices for AI tools, especially for cursor +- *(server)* Implement server patch check notifications +- *(api)* Add latest query param to Service restart API (#5881) +- *(api)* Add connect_to_docker_network setting to App creation API (#5691) +- *(routes)* Restrict backup download access to team admins and owners +- *(destination)* Update confirmation modal text and add persistent storage warning for server deployment +- *(terminal-access)* Implement terminal access control for servers and containers, including UI updates and backend logic +- *(ca-certificate)* Add CA certificate management functionality with UI integration and routing +- *(security-patches)* Add update check initialization and enhance notification messaging in UI +- *(previews)* Add force deploy without cache functionality and update deploy method to accept force rebuild parameter +- *(security-patterns)* Expand sensitive patterns list to include additional security-related variables +- *(database-backup)* Add MongoDB credential extraction and backup handling to DatabaseBackupJob +- *(activity-monitor)* Implement auto-scrolling functionality and dynamic content observation for improved user experience +- *(utf8-handling)* Implement UTF-8 sanitization for command outputs and enhance error handling in logs processing +- *(navbar)* Add Traefik dashboard availability check and server IP handling; refactor dynamic configurations loading +- *(proxy-dashboard)* Implement ProxyDashboardCacheService to manage Traefik dashboard cache; clear cache on configuration changes and proxy actions +- *(terminal-connection)* Enhance terminal connection handling with auto-connect feature and improved status messaging +- *(terminal)* Implement resize handling with ResizeObserver for improved terminal responsiveness +- *(migration)* Add is_sentinel_enabled column to server_settings with default true +- *(seeder)* Dispatch StartProxy action for each server in ProductionSeeder +- *(seeder)* Add CheckAndStartSentinelJob dispatch for each server in ProductionSeeder +- *(seeder)* Conditionally dispatch StartProxy action based on proxy check result +- *(service)* Update Changedetection template (#5937) +- *(service)* Add Miniflux service (#5843) +- *(service)* Add Pingvin Share service (#5969) +- *(auth)* Add Discord OAuth Provider (#5552) +- *(auth)* Add Clerk OAuth Provider (#5553) +- *(auth)* Add Zitadel OAuth Provider (#5490) +- *(core)* Set custom API rate limit (#5984) +- *(service)* Enhance service status handling and UI updates +- *(cleanup)* Add functionality to delete teams with no members or servers in CleanupStuckedResources command +- *(ui)* Add heart icon and enhance popup messaging for sponsorship support +- *(settings)* Add sponsorship popup toggle and corresponding database migration +- *(migrations)* Add optimized indexes to activity_log for improved query performance +- *(template)* Added excalidraw (#6095) +- *(template)* Add excalidraw service configuration with documentation and tags +- *(scheduling)* Add command to manually run scheduled database backups and tasks with options for chunking, delays, and dry runs +- *(scheduling)* Add frequency filter option for manual execution of scheduled jobs +- *(logging)* Implement scheduled logs command and enhance backup/task scheduling with cron checks +- *(logging)* Add frequency filters for scheduled logs command to support hourly, daily, weekly, and monthly job views +- *(scheduling)* Introduce ScheduledJobManager and ServerResourceManager for enhanced job scheduling and resource management +- *(previews)* Implement soft delete and cleanup for ApplicationPreview, enhancing resource management in DeleteResourceJob +- *(service)* Enable password protection for the Wireguard Ul +- *(queues)* Improve Horizon config to reduce CPU and RAM usage (#6212) +- *(service)* Add Gowa service (#6164) +- *(container)* Add updatedSelectedContainer method to connect to non-default containers and update wire:model for improved reactivity +- *(application)* Implement environment variable updates for Docker Compose applications, including creation, updating, and deletion of SERVICE_FQDN and SERVICE_URL variables +- *(service)* Add TriliumNext service (#5970) +- *(service)* Add Matrix service (#6029) +- *(service)* Add GitHub Action runner service (#6209) +- *(terminal)* Dispatch focus event for terminal after connection and enhance focus handling in JavaScript +- *(lang)* Add Polish language & improve forgot_password translation (#6306) +- *(service)* Update Authentik template (#6264) +- *(service)* Add sequin template (#6105) +- *(service)* Add pi-hole template (#6020) +- *(services)* Add Chroma service (#6201) +- *(service)* Add OpenPanel template (#5310) +- *(service)* Add librechat template (#5654) +- *(service)* Add Homebox service (#6116) +- *(service)* Add pterodactyl & wings services (#5537) +- *(service)* Add Bluesky PDS template (#6302) +- *(input)* Add autofocus attribute to input component for improved accessibility +- *(core)* Finally fqdn is fqdn and url is url. haha +- *(user)* Add changelog read tracking and unread count method +- *(templates)* Add new service templates and update existing compose files for various applications +- *(changelog)* Implement automated changelog fetching from GitHub and enhance changelog read tracking +- *(drizzle-gateway)* Add new drizzle-gateway service with configuration and logo +- *(drizzle-gateway)* Enhance service configuration by adding Master Password field and updating compose file path +- *(templates)* Add new service templates for Homebox, LibreChat, Pterodactyl, and Wings with corresponding configurations and logos +- *(templates)* Add Bluesky PDS service template and update compose file with new environment variable +- *(readme)* Add CubePath as a big sponsor and include new small sponsors with logos +- *(api)* Add create_environment endpoint to ProjectController for environment creation in projects +- *(api)* Add endpoints for managing environments in projects, including listing, creating, and deleting environments +- *(backup)* Add disable local backup option and related logic for S3 uploads +- *(dev patches)* Add functionality to send test email with patch data in development mode +- *(templates)* Added category per service +- *(email)* Implement email change request and verification process +- Generate category for services +- *(service)* Add elasticsearch template (#6300) +- *(sanitization)* Integrate DOMPurify for HTML sanitization across components +- *(cleanup)* Add command for sanitizing name fields across models +- *(sanitization)* Enhance HTML sanitization with improved DOMPurify configuration +- *(validation)* Centralize validation patterns for names and descriptions +- *(git-settings)* Add support for shallow cloning in application settings +- *(auth)* Implement authorization checks for server updates across multiple components +- *(auth)* Implement authorization for PrivateKey management +- *(auth)* Implement authorization for Docker and server management +- *(validation)* Add custom validation rules for Git repository URLs and branches +- *(security)* Add authorization checks for package updates in Livewire components +- *(auth)* Implement authorization checks for application management +- *(auth)* Enhance API error handling for authorization exceptions +- *(auth)* Add comprehensive authorization checks for all kind of resource creations +- *(auth)* Implement authorization checks for database management +- *(auth)* Refine authorization checks for S3 storage and service management +- *(auth)* Implement comprehensive authorization checks across API controllers +- *(auth)* Introduce resource creation authorization middleware and policies for enhanced access control +- *(auth)* Add middleware for resource creation authorization +- *(auth)* Enhance authorization checks in Livewire components for resource management +- *(validation)* Add ValidIpOrCidr rule for validating IP addresses and CIDR notations; update API access settings UI and add comprehensive tests +- *(docs)* Update architecture and development guidelines; enhance form components with built-in authorization system and improve routing documentation +- *(docs)* Expand authorization documentation for custom Alpine.js components; include manual protection patterns and implementation guidelines +- *(sentinel)* Implement SentinelRestarted event and update Livewire components to handle server restart notifications +- *(api)* Enhance IP access control in middleware and settings; support CIDR notation and special case for 0.0.0.0 to allow all IPs +- *(acl)* Change views/backend code to able to use proper ACL's later on. Currently it is not enabled. +- *(docs)* Add Backlog.md guidelines and project manager backlog agent; enhance CLAUDE.md with new links for task management +- *(docs)* Add tasks for implementing Docker build caching and optimizing staging builds; include detailed acceptance criteria and implementation plans +- *(docker)* Implement Docker cleanup processing in ScheduledJobManager; refactor server task scheduling to streamline cleanup job dispatching +- *(docs)* Expand Backlog.md guidelines with comprehensive usage instructions, CLI commands, and best practices for task management to enhance project organization and collaboration +- *(policies)* Add EnvironmentVariablePolicy for managing environment variables ( it was missing ) +- *(domains)* Implement domain conflict detection and user confirmation modal across application components +- *(domains)* Add force_domain_override option and enhance domain conflict detection responses + +### 🐛 Bug Fixes + +- Secrets join +- ENV variables set differently +- Capture non-error as error +- Only delete id.rsa in case of it exists +- Status is not available yet +- Docker Engine bug related to live-restore and IPs +- Version +- PreventDefault on a button, thats all +- Haproxy check should not throw error +- Delete all build files +- Cleanup images +- More error handling in proxy configuration + cleanups +- Local static assets +- Check sentry +- Typo +- Package.json +- Build secrets should be visible in runtime +- New secret should have default values +- Validate secrets +- Truncate git clone errors +- Branch used does not throw error +- Typo +- Error handling +- Stopping service without proxy +- Coolify proxy start +- Window error in SSR +- GitHub sync PR's +- Load more button +- Small fixes +- Typo +- Error with follow logs +- IsDomainConfigured +- TransactionIds +- Coolify image cleanup +- Cleanup every 10 mins +- Cleanup images +- Add no user redis to uri +- Secure cookie disabled by default +- Buggy svelte-kit-cookie-session +- Login issues +- SSL app off +- Local docker host +- Typo +- Lets encrypt +- Remove SSL with stop +- SSL off for services +- Grr +- Running state css +- Minor fixes +- Remove force SSL when doing let's encrypt request +- GhToken in session now +- Random port for certbot +- Follow icon +- Plausible volume fixed +- Database connection strings +- Gitlab webhooks fixed +- If DNS not found, do not redirect +- Github token +- Move tokens from session to cookie/store +- Email is lowercased in login +- Lowercase email everywhere +- Use normal docker-compose in dev +- Random network name for demo +- Settings fqdn grr +- Revert default network +- Http for demo, oops +- Docker scanner +- Improvement on image pulls +- Coolify image pulls +- Remove wrong/stuck proxy configurations +- Always use a buildpack +- Add icons for eleventy + astro +- Fix proxy every 10 secs +- Do not remove coolify proxy +- Update version +- Be sure .env exists +- Missing fqdn for services +- Default npm command +- Add coolify-image label for build images +- Cleanup old images, > 3 days +- Better proxy check +- Ssl + sslrenew +- Null proxyhash on restart +- Reconfigure proxy on restart +- Update process +- Reload proxy on ssl cert +- Volume name +- Update process +- Check when a container is running +- Reload haproxy if new cert is added +- Cleanup coolify images +- Application state in UI +- Do not error if proxy is not running +- Personal Gitlab repos +- Autodeploy true by default for GH repos +- No cookie found +- Missing session data +- No error if GitSource is missing +- No webhook secret found? +- Basedir for dockerfiles +- Better queue system + more support on monorepos +- Remove build logs in case of app removed +- Cleanup old builds +- Only cleanup same app +- Add nginx + htaccess files +- Skip ssl cert in case of error +- Volumes +- Cleanup only 2 hours+ old images +- Ghost logo size +- Ghost icon, remove console.log +- List ghost services +- Reload window on settings saved +- Persistent storage on webhooks +- Add license +- Space in repo names +- Gitlab repo url +- No need to dashify anymore +- Registration enabled/disabled +- Add PROTO headers +- Haproxy errors +- Build variables +- Use NodeJS for sveltekit for now +- Ignore coolify proxy error for now +- Python no wsgi +- If user not found +- Rename envs to secrets +- Infinite loop on www domains +- No need to paste clear text env for previews +- Build log fix attempt #1 +- Small UI fix on logs +- Lets await! +- Async progress +- Remove console.log +- Build log +- UI +- Gitlab & Github urls +- Secrets build/runtime coudl be changed after save +- Default configuration +- *(php)* If .htaccess file found use apache +- Add default webhook domain for n8n +- Add git lfs while deploying +- Try to update build status several times +- Update stucked builds +- Update stucked builds on startup +- Revert seed +- Lame fixing +- Remove asyncUntil +- Add openssl to image +- Permission issues +- On-demand sFTP for wp +- Fix for fix haha +- Do not pull latest image +- Updated db versions +- Only show proxy for admin team +- Team view for root team +- Do not trigger >1 webhooks on GitLab +- Possible fix for spikes in CPU usage +- Last commit +- Www or not-www, that's the question +- Fix for the fix that fixes the fix +- Ton of updates for users/teams +- Small typo +- Unique storage paths +- Self-hosted GitLab URL +- No line during buildLog +- Html/apiUrls cannot end with / +- Typo +- Missing buildpack +- Enable https for Ghost +- Postgres root passwor shown and set +- Able to change postgres user password from ui +- DB Connecting string generator +- Missing install repositories GitHub +- Return own and other sources better +- Show config missing on sources +- Remove unnecessary save button haha +- Update dockerfile +- Haproxy build stuffs +- Proxy +- Types +- Invitations +- Timeout values +- Cleanup images older than a day +- Meilisearch service +- Load all branches, not just the first 30 +- ProjectID for Github +- DNS check before creating SSL cert +- Try catch me +- Restart policy for resources +- No permission on first registration +- Reverting postgres password for now +- Destinations to HAProxy +- Register should happen if coolify proxy cannot be started +- GitLab typo +- Remove system wide pw reset +- Postgres root pw is pw field +- Teams view +- Improved tcp proxy monitoring for databases/ftp +- Add HTTP proxy checks +- Loading of new destinations +- Better performance for cleanup images +- Remove proxy container in case of dependent container is down +- Restart local docker coolify proxy in case of something happens to it +- Id of service container +- Switch from bitnami/redis to normal redis +- Use redis-alpine +- Wordpress extra config +- Stop sFTP connection on wp stop +- Change user's id in sftp wp instance +- Use arm based certbot on arm +- Buildlog line number is not string +- Application logs paginated +- Switch to stream on applications logs +- Scroll to top for logs +- Pull new images for services all the time it's started. +- White-labeled custom logo +- Application logs +- Deno configurations +- Text on deno buildpack +- Correct branch shown in build logs +- Vscode permission fix +- I18n +- Locales +- Application logs is not reversed and queried better +- Do not activate i18n for now +- GitHub token cleanup on team switch +- No logs found +- Code cleanups +- Reactivate posgtres password +- Contribution guide +- Simplify list services +- Contribution +- Contribution guide +- Contribution guide +- Packagemanager finder +- Unami svg size +- Team switching moved to IAM menu +- Always use IP address for webhooks +- Remove unnecessary test endpoint +- UI +- Migration +- Fider envs +- Checking low disk space +- Build image +- Update autoupdate env variable +- Renew certificates +- Webhook build images +- Missing node versions +- ExposedPorts +- Logos for dbs +- Do not run SSL renew in development +- Check domain for coolify before saving +- Remove debug info +- Cancel jobs +- Cancel old builds in database +- Better DNS check to prevent errors +- Check DNS in prod only +- DNS check +- Disable sentry for now +- Cancel +- Sentry +- No image for Docker buildpack +- Default packagemanager +- Server usage only shown for root team +- Expose ports for services +- UI +- Navbar UI +- UI +- UI +- Remove RC python +- UI +- UI +- UI +- Default Python package +- WP custom db +- UI +- Gastby buildpack +- Service checks +- Remove console.log - Traefik -- Finally works! :) -- Rename components + remove PR/MR deployment from public repos -- Settings missing id -- Explainer component -- Database name on logs view -- Taiga - -### 💼 Other - -- Fixes -- Change tooltips and info boxes -- Added rc release - -### 🧪 Testing - -- Native binary target -- Dockerfile - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.8.9] - 2022-08-30 - -### 🐛 Bug Fixes - -- Oh god Prisma - -## [3.8.8] - 2022-08-30 - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.8.6] - 2022-08-30 - -### 🐛 Bug Fixes - -- Pr deployment -- CompareVersions -- Include -- Include -- Gitlab apps - -### 💼 Other - -- Fixes -- Route to the correct path when creating destination from db config - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.8.5] - 2022-08-27 - -### 🐛 Bug Fixes - -- Copy all files during install process -- Typo -- Process -- White labeled icon on navbar -- Whitelabeled icon -- Next/nuxt deployment type -- Again - -## [3.8.4] - 2022-08-27 - -### 🐛 Bug Fixes - -- UI thinkgs -- Delete team while it is active -- Team switching -- Queue cleanup -- Decrypt secrets -- Cleanup build cache as well -- Pr deployments + remove public gits - -### 💼 Other - -- Dashbord fixes -- Fixes - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.8.3] - 2022-08-26 - -### 🐛 Bug Fixes - -- Secrets decryption - -## [3.8.2] - 2022-08-26 - -### 🚀 Features - -- *(ui)* Rework home UI and with responsive design - -### 🐛 Bug Fixes - -- Never stop deplyo queue -- Build queue system -- High cpu usage -- Worker -- Better worker system - -### 💼 Other - -- Dashboard fine-tunes -- Fine-tune -- Fixes -- Fix - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.8.1] - 2022-08-24 - -### 🐛 Bug Fixes - -- Ui buttons -- Clear queue on cancelling jobs -- Cancelling jobs -- Dashboard for admins - -## [3.8.0] - 2022-08-23 - -### 🚀 Features - -- Searxng service - -### 🐛 Bug Fixes - -- Port checker -- Cancel build after 5 seconds -- ExposedPort checker -- Batch secret = -- Dashboard for non-root users -- Stream build logs -- Show build log start/end - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.7.0] - 2022-08-19 - -### 🚀 Features - -- Add GlitchTip service - -### 🐛 Bug Fixes - -- Missing commas -- ExposedPort is just optional - -### ⚙️ Miscellaneous Tasks - -- Add .pnpm-store in .gitignore -- Version++ - -## [3.6.0] - 2022-08-18 - -### 🚀 Features - -- Import public repos (wip) -- Public repo deployment -- Force rebuild + env.PORT for port + public repo build - -### 🐛 Bug Fixes - -- Bots without exposed ports - -### 💼 Other - -- Fixes here and there - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.5.2] - 2022-08-17 - -### 🐛 Bug Fixes - -- Restart containers on-failure instead of always -- Show that Ghost values could be changed - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.5.1] - 2022-08-17 - -### 🐛 Bug Fixes - -- Revert docker compose version to 2.6.1 -- Trim secrets - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.5.0] - 2022-08-17 - -### 🚀 Features - -- Deploy bots (no domains) -- Custom dns servers - -### 🐛 Bug Fixes - -- Dns button ui -- Bot deployments -- Bots -- AutoUpdater & cleanupStorage jobs - -### 💼 Other - -- Typing - -## [3.4.0] - 2022-08-16 - -### 🚀 Features - -- Appwrite service -- Heroku deployments - -### 🐛 Bug Fixes - +- Remove debug things +- WIP Traefik +- Proxy for http +- PR deployments view +- Minio urls + domain checks +- Remove gh token on git source changes +- Do not fetch app state in case of missconfiguration +- Demo instance save domain instantly +- Instant save on demo instance +- New source canceled view +- Lint errors in database services +- Otherfqdns +- Host key verification +- Ftp connection +- GitHub fixes +- TrustProxy +- Force restart proxy +- Only restart coolify proxy in case of version prior to 2.9.2 +- Force restart proxy on seeding +- Add GIT ENV variable for submodules +- Recurisve clone instead of submodule +- Versions +- Only reconfigure coolify proxy if its missconfigured +- Demo version forms +- Typo +- Revert gh and gl cloning +- Proxy stop missing argument +- Fider changed an env variable name +- Pnpm command +- Plausible custom script +- Plausible script and middlewares +- Remove console log +- Remove comments +- Traefik middleware +- Persistent nocodb +- Nocodb persistency +- Host and reload for uvicorn +- Remove package-lock +- Be able to change database + service versions +- Lock file +- Seeding +- Forgot that the version bump changed 😅 +- New destination can be created +- Include post +- New destinations +- Domain check +- Domain check +- TrustProxy for Fastify +- Hostname issue +- GitLab pagination load data +- Service domain checker +- Wp missing ftp solution +- Ftp WP issues +- Ftp?! +- Gitpod updates +- Gitpod +- Gitpod +- Wordpress FTP permission issues +- GitLab search fields +- GitHub App button +- GitLab loop on misconfigured source +- Gitpod +- Cleanup less often and can do it manually +- Admin password reset should not timeout +- Message for double branches +- Turn off autodeploy if double branch is configured +- More types for API +- More types +- Do not rebuild in case image exists and sha not changed +- Gitpod urls +- Remove new service start process +- Remove shared dir, deployment does not work +- Gitlab custom url +- Location url for services and apps +- Settings from api +- Selectable destinations +- Gitpod hardcodes +- Typo +- Typo +- Expose port checker +- States and exposed ports +- CleanupStorage +- Remote traefik webhook +- Remote engine ip address +- RemoteipAddress +- Explanation for remote engine url +- Tcp proxy +- Lol +- Webhook +- Dns check for rde +- Gitpod +- Revert last commit +- Dns check +- Dns checker +- Webhook +- Df and more debug +- Webhooks +- Load previews async +- Destination icon +- Pr webhook +- Cache image +- No ssh key found +- Prisma migration + update of docker and stuffs +- Ui +- Ui +- Only 1 ssh-agent is needed +- Reuse ssh connection +- Ssh tunnel +- Dns checking +- Fider BASE_URL set correctly +- Rde local ports +- Empty remote destinations could be removed +- Tips +- Lowercase issues fider +- Tooltip colors +- Update clickhouse configuration +- Cleanup command +- Enterprise Github instance endpoint +- Follow/cancel buttons +- Only remove coolify managed containers +- White-labeled env +- Schema +- Coolify-network on verification +- Cleanup stucked prisma-engines +- Toast +- Secrets +- Cleanup prisma engine if there is more than 1 +- !isARM to isARM +- Enterprise GH link +- Empty buildpack icons +- Debounce dashboard status requests +- Decryption errors +- Postgresql on ARM +- Make it public button +- Loading indicator - Replace docker compose with docker-compose on CSB - Dashboard ui - Create coolify-infra, if it does not exists @@ -7289,1369 +1681,3843 @@ All notable changes to this project will be documented in this file. - Services import - Heroku icon - Heroku icon - -## [3.3.4] - 2022-08-15 - -### 🐛 Bug Fixes - -- Make it public button -- Loading indicator - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.3.3] - 2022-08-14 - -### 🐛 Bug Fixes - -- Decryption errors -- Postgresql on ARM - -## [3.3.2] - 2022-08-12 - -### 🐛 Bug Fixes - -- Debounce dashboard status requests - -### 💼 Other - -- Fider - -## [3.3.1] - 2022-08-12 - -### 🐛 Bug Fixes - -- Empty buildpack icons - -## [3.2.3] - 2022-08-12 - -### 🚀 Features - -- Databases on ARM -- Mongodb arm support -- New dashboard - -### 🐛 Bug Fixes - -- Cleanup stucked prisma-engines -- Toast -- Secrets -- Cleanup prisma engine if there is more than 1 -- !isARM to isARM -- Enterprise GH link - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.2.2] - 2022-08-11 - -### 🐛 Bug Fixes - -- Coolify-network on verification - -## [3.2.1] - 2022-08-11 - -### 🚀 Features - -- Init heroku buildpacks - -### 🐛 Bug Fixes - -- Follow/cancel buttons -- Only remove coolify managed containers -- White-labeled env -- Schema - -### 💼 Other - -- Fix - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.2.0] - 2022-08-11 - -### 🚀 Features - -- Persistent storage for all services -- Cleanup clickhouse db - -### 🐛 Bug Fixes - -- Rde local ports -- Empty remote destinations could be removed -- Tips -- Lowercase issues fider -- Tooltip colors -- Update clickhouse configuration -- Cleanup command -- Enterprise Github instance endpoint - -### 💼 Other - -- Local ssh port -- Redesign a lot -- Fixes -- Loading indicator for plausible buttons - -## [3.1.4] - 2022-08-01 - -### 🚀 Features - -- Moodle init -- Remote docker engine init -- Working on remote docker engine -- Rde -- Remote docker engine -- Ipv4 and ipv6 -- Contributors -- Add arch to database -- Stop preview deployment - -### 🐛 Bug Fixes - -- Settings from api -- Selectable destinations -- Gitpod hardcodes +- Dns button ui +- Bot deployments +- Bots +- AutoUpdater & cleanupStorage jobs +- Revert docker compose version to 2.6.1 +- Trim secrets +- Restart containers on-failure instead of always +- Show that Ghost values could be changed +- Bots without exposed ports +- Missing commas +- ExposedPort is just optional +- Port checker +- Cancel build after 5 seconds +- ExposedPort checker +- Batch secret = +- Dashboard for non-root users +- Stream build logs +- Show build log start/end +- Ui buttons +- Clear queue on cancelling jobs +- Cancelling jobs +- Dashboard for admins +- Never stop deplyo queue +- Build queue system +- High cpu usage +- Worker +- Better worker system +- Secrets decryption +- UI thinkgs +- Delete team while it is active +- Team switching +- Queue cleanup +- Decrypt secrets +- Cleanup build cache as well +- Pr deployments + remove public gits +- Copy all files during install process - Typo +- Process +- White labeled icon on navbar +- Whitelabeled icon +- Next/nuxt deployment type +- Again +- Pr deployment +- CompareVersions +- Include +- Include +- Gitlab apps +- Oh god Prisma +- Glitchtip things +- Loading state on start +- Ui +- Submodule +- Gitlab webhooks +- UI + refactor +- Exposedport on save +- Appwrite letsencrypt +- Traefik appwrite +- Traefik +- Finally works! :) +- Rename components + remove PR/MR deployment from public repos +- Settings missing id +- Explainer component +- Database name on logs view +- Taiga +- Ssh pid agent name +- Repository link trim +- Fqdn or expose port required +- Service deploymentEnabled +- Expose port is not required +- Remote verification +- Dockerfile +- Debug api logging + gh actions +- Workdir +- Move restart button to settings +- Gitlab webhook +- Use ip address instead of window location +- Use ip instead of window location host +- Service state update +- Add initial DNS servers +- Revert last change with domain check +- Service volume generation +- Minio default env variables +- Add php 8.1/8.2 +- Edgedb ui +- Edgedb stuff +- Edgedb +- Pr previews +- DnsServer formatting +- Settings for service +- Change to execa from utils +- Save search input +- Ispublic status on databases +- Port checkers +- Ui variables +- Glitchtip env to pyhton boolean +- Autoupdater +- Show restarting apps +- Show restarting application & logs +- Remove unnecessary gitlab group name +- Secrets for PR +- Volumes for services +- Build secrets for apps +- Delete resource use window location +- Changing umami image URL to get latest version +- Gitlab importer for public repos +- Show error logs +- Umami init sql +- Plausible analytics actions +- Login +- Dev url +- UpdateMany build logs +- Fallback to db logs +- Fluentbit configuration +- Coolify update +- Fluentbit and logs +- Canceling build +- Logging +- Load more +- Build logs +- Versions of appwrite +- Appwrite?! +- Get building status +- Await +- Await #2 +- Update PR building status +- Appwrite default version 1.0 +- Undead endpoint does not require JWT +- *(routes)* Improve design of application page +- *(routes)* Improve design of git sources page +- *(routes)* Ui from destinations page +- *(routes)* Ui from databases page +- *(routes)* Ui from databases page +- *(routes)* Ui from databases page +- *(routes)* Ui from services page +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- *(routes)* Ui from settings page +- *(routes)* Duplicates classes in services page +- *(routes)* Searchbar ui +- Github conflicts +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- Ui with headers +- *(routes)* Header of settings page in databases +- *(routes)* Ui from secrets table +- Ui +- Tooltip +- Dropdown +- Ssl certificate distribution +- Db migration +- Multiplex ssh connections +- Able to search with id +- Not found redirect +- Settings db requests +- Error during saving logs +- Consider base directory in heroku bp +- Basedirectory should be empty if null +- Allow basedirectory for heroku +- Stream logs for heroku bp +- Debug log for bp +- Scp without host verification & cert copy +- Base directory & docker bp +- Laravel php chooser +- Multiplex ssh and ssl copy +- Seed new preview secret types +- Error notification +- Empty preview value +- Error notification +- Seed +- Service logs +- Appwrite function network is not the default +- Logs in docker bp +- Able to delete apps in unconfigured state +- Disable development low disk space +- Only log things to console in dev mode +- Do not get status of more than 10 resources defined by category +- BaseDirectory +- Dashboard statuses +- Default buildImage and baseBuildImage +- Initial deploy status +- Show logs better +- Do not start tcp proxy without main container +- Cleanup stucked tcp proxies +- Default 0 pending invitations +- Handle forked repositories - Typo -- Expose port checker -- States and exposed ports -- CleanupStorage -- Remote traefik webhook -- Remote engine ip address -- RemoteipAddress -- Explanation for remote engine url -- Tcp proxy -- Lol -- Webhook -- Dns check for rde -- Gitpod -- Revert last commit -- Dns check -- Dns checker -- Webhook -- Df and more debug +- Pr branches +- Fork pr previews +- Remove unnecessary things +- Meilisearch data dir +- Verify and configure remote docker engines +- Add buildkit features +- Nope if you are not logged in +- Do not use npx +- Pure docker based development +- Do not show nope as ip address for dbs +- Add git sha to build args +- Smart search for new services +- Logs for not running containers +- Update docker binaries +- Gh release +- Dev container +- Gitlab auth and compose reload +- Check compose domains in general +- Port required if fqdn is set +- Appwrite v1 missing containers +- Dockerfile +- Pull does not work remotely on huge compose file +- Single container logs and usage with compose +- Secret errors +- Service logs +- Heroku bp +- Expose port is readonly on the wrong condition +- Toast +- Traefik proxy q 10s +- App logs view +- Tooltip +- Toast, rde, webhooks +- Pathprefix +- Load public repos +- Webhook simplified +- Remote webhooks +- Previews wbh - Webhooks -- Load previews async -- Destination icon -- Pr webhook -- Cache image -- No ssh key found -- Prisma migration + update of docker and stuffs -- Ui +- Websecure redirect +- Wb for previews +- Pr stopps main deployment +- Preview wbh +- Wh catchall for all +- Remove old minio proxies +- Template files +- Compose icon +- Templates +- Confirm restart service +- Template +- Templates +- Templates +- Plausible analytics things +- Appwrite webhook +- Coolify instance proxy +- Migrate template +- Preview webhooks +- Simplify webhooks +- Remove ghost-mariadb from the list +- More simplified webhooks +- Umami + ghost issues +- Remove contribution docs +- Umami template +- Compose webhooks fixed +- Variable replacements +- Doc links +- For rollback +- N8n and weblate icon +- Expose ports for services +- Wp + mysql on arm +- Show rollback button loading +- No tags error +- Update on mobile +- Dashboard error +- GetTemplates +- Docker compose persistent volumes +- Application persistent storage things +- Volume names for undefined volume names in compose +- Empty secrets on UI +- Ports for services +- Default icon for new services +- IsBot issue +- Local dev api/ws urls +- Wrong template/type +- Gitea icon is svg +- Gh actions +- Gh actions +- Replace $$generate vars +- Webhook traefik +- Exposed ports +- Wrong icons on dashboard +- Escape % in secrets +- Move debug log settings to build logs +- Storage for compose bp + debug on +- Hasura admin secret +- Logs +- Mounts +- Load logs after build failed +- Accept logged and not logged user in /base +- Remote haproxy password/etc +- Remove hardcoded sentry dsn +- Nope in database strings +- 0 destinations redirect after creation +- Seed +- Sentry dsn update +- Dnt - Ui -- Only 1 ssh-agent is needed -- Reuse ssh connection -- Ssh tunnel -- Dns checking -- Fider BASE_URL set correctly - -### 💼 Other - -- Error message https://github.com/coollabsio/coolify/issues/502 -- Changes -- Settings -- For removing app - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.1.3] - 2022-07-18 - -### 🚀 Features - -- Init moodle and separate stuffs to shared package - -### 🐛 Bug Fixes - -- More types for API -- More types -- Do not rebuild in case image exists and sha not changed -- Gitpod urls -- Remove new service start process -- Remove shared dir, deployment does not work -- Gitlab custom url -- Location url for services and apps - -## [3.1.2] - 2022-07-14 - -### 🐛 Bug Fixes - -- Admin password reset should not timeout -- Message for double branches -- Turn off autodeploy if double branch is configured - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.1.1] - 2022-07-13 - -### 🚀 Features - -- Gitpod integration - -### 🐛 Bug Fixes - -- Cleanup less often and can do it manually - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ - -## [3.1.0] - 2022-07-12 - -### 🚀 Features - -- Ability to change deployment type for nextjs -- Ability to change deployment type for nuxtjs -- Gitpod ready code(almost) -- Add Docker buildpack exposed port setting -- Custom port for git instances - -### 🐛 Bug Fixes - -- GitLab pagination load data -- Service domain checker -- Wp missing ftp solution -- Ftp WP issues -- Ftp?! -- Gitpod updates -- Gitpod -- Gitpod -- Wordpress FTP permission issues -- GitLab search fields -- GitHub App button -- GitLab loop on misconfigured source -- Gitpod - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [3.0.3] - 2022-07-06 - -### 🐛 Bug Fixes - -- Domain check -- Domain check -- TrustProxy for Fastify -- Hostname issue - -## [3.0.2] - 2022-07-06 - -### 🐛 Bug Fixes - -- New destination can be created -- Include post -- New destinations - -## [3.0.1] - 2022-07-06 - -### 🐛 Bug Fixes - -- Seeding -- Forgot that the version bump changed 😅 - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.11] - 2022-06-20 - -### 🐛 Bug Fixes - -- Be able to change database + service versions -- Lock file - -## [2.9.10] - 2022-06-17 - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.9] - 2022-06-10 - -### 🐛 Bug Fixes - -- Host and reload for uvicorn -- Remove package-lock - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.8] - 2022-06-10 - -### 🐛 Bug Fixes - -- Persistent nocodb -- Nocodb persistency - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.7] - 2022-06-09 - -### 🐛 Bug Fixes - -- Plausible custom script -- Plausible script and middlewares -- Remove console log -- Remove comments -- Traefik middleware - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.6] - 2022-06-02 - -### 🐛 Bug Fixes - -- Fider changed an env variable name -- Pnpm command - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.5] - 2022-06-02 - -### 🐛 Bug Fixes - -- Proxy stop missing argument - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.4] - 2022-06-01 - -### 🐛 Bug Fixes - -- Demo version forms +- Only visible with publicrepo +- Migrations +- Prevent webhook errors to be logged +- Login error +- Remove beta from systemwide git +- Git checkout +- Remove sentry before migration +- Webhook previewseparator +- Apache on arm +- Update PR/MRs with new previewSeparator +- Static for arm +- Failed builds should not push images +- Turn off autodeploy for simpledockerfiles +- Security hole +- Rde +- Delete resource on dashboard +- Wrong port in case of docker compose +- Public db icon on dashboard +- Cleanup +- Build commands +- Migration file +- Adding missing appwrite volume +- Appwrite tmp volume +- Do not replace secret +- Root user for dbs on arm +- Escape secrets +- Escape env vars +- Envs +- Docker buildpack env +- Secrets with newline +- Secrets +- Add default node_env variable +- Add default node_env variable +- Secrets +- Secrets +- Gh actions +- Duplicate env variables +- Cleanupstorage +- Remove unused imports +- Parsing secrets +- Read-only permission +- Read-only iam +- $ sign in secrets +- Custom gitlab git user +- Add documentation link again +- Remove prefetches +- Doc link +- Temporary disable dns check with dns servers +- Local images for reverting +- Secrets +- Compose file location +- Docker log sequence +- Delete apps with previews +- Do not cleanup compose applications as unconfigured +- Build env variables with docker compose +- Public gh repo reload compose +- Build args docker compose +- Grpc +- Secrets +- Www redirect +- Cleanup function +- Cleanup stucked containers +- Deletion + cleanupStuckedContainers +- Stucked containers +- CleanupStuckedContainers +- CleanupStuckedContainers +- Typos in docs +- Url +- Network in compose files +- Escape new line chars in wp custom configs +- Applications cannot be deleted +- Arm servics +- Base directory not found +- Cannot delete resource when you are not on root team +- Empty port in docker compose +- Set PACK_VERSION to 0.27.0 +- PublishDirectory +- Host volumes +- Replace . & .. & $PWD with ~ +- Handle log format volumes +- Nestjs buildpack +- Show ip address as host in public dbs +- Revert from dockerhub if ghcr.io does not exists +- Logo of CCCareers - Typo -- Revert gh and gl cloning - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.3] - 2022-05-31 - -### 🐛 Bug Fixes - -- Recurisve clone instead of submodule -- Versions -- Only reconfigure coolify proxy if its missconfigured - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.2] - 2022-05-31 - -### 🐛 Bug Fixes - -- TrustProxy -- Force restart proxy -- Only restart coolify proxy in case of version prior to 2.9.2 -- Force restart proxy on seeding -- Add GIT ENV variable for submodules - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.1] - 2022-05-31 - -### 🐛 Bug Fixes - -- GitHub fixes - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.9.0] - 2022-05-31 - -### 🚀 Features - -- PageLoader -- Database + service usage - -### 🐛 Bug Fixes - -- Service checks -- Remove console.log -- Traefik -- Remove debug things -- WIP Traefik -- Proxy for http -- PR deployments view -- Minio urls + domain checks -- Remove gh token on git source changes -- Do not fetch app state in case of missconfiguration -- Demo instance save domain instantly -- Instant save on demo instance -- New source canceled view -- Lint errors in database services -- Otherfqdns -- Host key verification -- Ftp connection - -### 💼 Other - -- Appwrite -- Testing WS -- Traefik?! -- Traefik -- Traefik -- Traefik migration -- Traefik -- Traefik -- Traefik -- Notifications and application usage -- *(fix)* Traefik -- Css - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.8.2] - 2022-05-16 - -### 🐛 Bug Fixes - -- Gastby buildpack - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.8.1] - 2022-05-10 - -### 🐛 Bug Fixes - -- WP custom db +- Ssh +- Nullable name on deploy_keys +- Enviroments +- Remove dd - oops +- Add inprogress activity +- Application view +- Only set status in case the last command block is finished +- Poll activity +- Small typo +- Show activity on load +- Deployment should fail on error +- Tests +- Version +- Status not needed +- No project redirect +- Gh actions +- Set status +- Seeders +- Do not modify localhost +- Deployment_uuid -> type_uuid +- Read env from config, bc of cache +- Private key change view +- New destination +- Do not update next channel all the time +- Cancel deployment button +- Public repo limit shown + branch should be preselected. +- Better status on ui for apps +- Arm coolify version +- Formatting +- Gh actions +- Show github app secrets +- Do not force next version updates +- Debug log button +- Deployment key based works +- Deployment cancel/debug buttons +- Upgrade button +- Changing static build changes port +- Overwrite default nginx configuration +- Do not overlap docker image names +- Oops +- Found image name +- Name length +- Semicolons encoding by traefik +- Base_dir wip & outputs +- Cleanup docker images +- Nginx try_files +- Master is the default, not main +- No ms in rate limit resets +- Loading after button text +- Default value +- Localhost is usable +- Update docker-compose prod +- Cloud/checkoutid/lms +- Type of license code +- More verbose error +- Version lol +- Update prod compose +- Version +- Remove buggregator from dev +- Able to change localhost's private key +- Readonly input box +- Notifications +- Licensing +- Subscription link +- Migrate db schema for smtp + discord +- Text field +- Null fqdn notifications +- Remove old modal +- Proxy stop/start ui +- Proxy UI +- Empty description +- Input and textarea +- Postgres_username name to not name, lol +- DatabaseBackupJob.php +- No storage +- Backup now button +- Ui + subscription +- Self-hosted +- Make coolify-db backups unique dir +- Limits & server creation page +- Fqdn on apps +- DockerCleanupjob +- Validation +- Webhook endpoint in cloud and no system wide gh app +- Subscriptions +- Password confirmation +- Proxy start job +- Dockerimage jobs are not overlapping +- Sentry bug +- Button loading animation +- Form address +- Show hosted email service, just disable for non pro subs +- Add navbar for source + keys +- Add docker network to build process +- Overlapping apps +- Do not show system wide git on cloud +- Lowercase image names +- Typo +- SaveModel email settings +- Bug +- Db backup job +- Sentry 4459819517 +- Sentry 4451028626 +- Ui +- Retry notifications +- Instance email settings +- Ui +- Test email on for admins or custom smtp +- Coolify already exists should not throw error +- Delete database related things when delete database +- Remove -q from docker compose +- Errors in views +- Only send internal notifcations to enabled channels +- Recovery code +- Email sending error +- Sentry 4469575117 +- Old docker version error +- Errors +- Proxy check, reduce jobs, etc +- Queue after commit +- Remove nixpkgarchive +- Remove nixpkgarchive from ui +- Webhooks should not run if server is not functional +- Server is functional check +- Confirm email before sending +- Help should send cc on email +- Sub type +- Show help modal everywhere +- Forgot password +- Disable dockerfile based healtcheck for now +- Add timeout for ssh commands +- Prevent weird ui bug for validateServer +- Lowercase email in forgot password +- Lower case email on waitlist +- Encrypt jobs +- ProcessWithEnv()->run +- Plus boarding step about Coolify +- SaveConfigurationSync +- Help uri +- Sub for root +- Redirect on server not found +- Ip check +- Uniqueips +- Simply reply to help messages +- Help +- Rate limit +- Collect billing address +- Invitation +- Smtp view +- Ssh-agent revert +- Restarting container state on ui +- Generate new key +- Missing upgrade js +- Team error +- 4.0.0-beta.37 +- Localhost +- Proxy start (if not proxy defined, use Traefik) +- Do not remove localhost in boarding +- Allow non ip address (DNS) +- InstallDocker id not found +- Boarding +- Errors +- Proxy container status +- Proxy configuration saving +- Convert startProxy to action +- Stop/start UI on apps and dbs +- Improve localhost boarding process +- Try to use old docker-compose +- Boarding again +- Send internal notifications of email errors +- Add github app change on new app view +- Delete environment variables on app/db delete +- Save proxy configuration +- Add proxy to network with periodic check +- Proxy connections +- Delete persistent storages on resource deletion +- Prevent overwrite already existing env variables in services +- Mappings +- Sentry issue 4478125289 +- Make sure proxy path created +- StartProxy +- Server validation with cf tunnels +- Only show traefik dashboard if its available +- Services +- Database schema +- Report livewire errors +- Links with path +- Add traefik labels no matter if traefik is selected or not +- Add expose port for containers +- Also check docker socks permission on validation +- Applications with port mappins do a normal update (not rolling update) +- Put back build pack chooser +- Proxy configuration + starter +- Show real storage name on services +- New service template layout +- Containerstatusjob +- Aaaaaaaaaaaaaaaaa +- Services view +- Services +- Manually create network for services +- Disable early updates +- Sslip for localhost +- ContainerStatusJob +- Cannot delete env with available services +- Sync command +- Install script drops an error +- Prevent sync version (it needs an option) +- Instance fqdn setting +- Sentry 4510197209 +- Sentry 4504136641 +- Sentry 4502634789 +- Next helper image +- Service templates +- Sync:bunny +- Update process if server has been renamed +- Reporting handler +- Localhost privatekey update +- Remove private key in case you removed a github app +- Only show manually added private keys on server view +- Show source on all type of applications +- Docker cleanup should be a job by server +- File/dir based volumes are now read from the server +- Respect server fqdn +- If public repository does not have a main branch +- Preselect branc on private repos +- Deploykey branch +- Backups are now working again +- Not found base_branch in git webhooks +- Coolify db backup +- Preview deployments name, status etc +- Services should have destination as well +- Dockerfile expose is not overwritten +- If app settings is not saved to db +- Do not show subscription cancelled noti +- Show real volume names +- Only parse expose in dockerfiles if ports_exposes is empty +- Add uuid to volume names +- New volumes for services should have - instead of _ +- Always pull helper image in dev +- Only show last 1000 lines +- Service status +- If waitlist is disabled, redirect to register +- Add destination to new services +- Predefined content for files +- Move /data to ./_data in dev +- UI +- Show all storages in one place for services +- Ui +- Add _data to vite ignore +- Only use _ in volume names for services +- Volume names in services +- Volume names +- Service logs visible if the whole service stack is not running +- Ui +- Compose magic +- Compose parser updated +- Dev compose files +- Traefik labels for multiport deployments +- Visible version number +- Remove SERVICE_ from deployable compose +- Delete event to deleting +- Move dev data to volumes to prevent permission issues +- Traefik labelling in case of several http and https domain added +- PR deployments use the first fqdn as base +- Email notifications subscription fixed +- Services - do not remove unnecessary things for now +- Decrease max horizon processes to get lower memory usage +- Test emails only available for user owned smtp/resend +- Ui for self-hosted email settings +- Set smtp notifications on by default +- Select branch on other git +- Private repository +- Contribution guide +- Public repository names +- *(create)* Flex wrap on server & network selection +- Better unreachable/revived server statuses +- Able to set base dir for Dockerfile build pack +- Server validation process +- Fqdn could be null +- Small +- Server unreachable count +- Do not reset unreachable count +- Contact docs +- Check connection +- Server saving +- No env goto envs from dashboard +- Goto +- Tcp proxy for dbs +- Database backups +- Only send email if transactional email set +- Backupfailed notification is forced +- Use port exposed for reverse proxy +- Contact link +- Use only ip addresses for servers +- Deleted team and it is the current one +- Add new team button +- Transactional email link +- Dashboard goto link +- Only require registry image in case of dockerimage bp +- Instant save build pack change +- Public git +- Cannot remove localhost +- Check localhost connection +- Send unreachable/revived notifications +- Boarding + verification +- Make sure proxy wont start in NONE mode +- Service check status 10 sec +- IsCloud in production seeder +- Make sure to use IP address +- Dockerfile location feature +- Server ip could be hostname in self-hosted +- Urls should be password fields +- No backup for redis +- Show database logs in case of its not healthy and running +- Proxy check for ports, do not kill anything listening on port 80/443 +- Traefik dashboard ip +- Db labels +- Docker cleanup jobs +- Timeout for instant remote processes +- Dev containerjobs +- Backup database one-by-one. +- Turn off static deployment if you switch buildpacks +- Docker hub URL +- Redis URL generated +- Build image before starting dockerfile buildpacks +- Service status check is a bit better +- Generate fqdn if you deleted a service app, but it requires fqdn +- Cancel any deployments + queue next +- Add internal domain names during build process +- Noindex meta tag +- Show docker build logs +- Only include config.json if its exists and a file +- Always start proxy if not NONE is selected +- Proxy start process +- Setup:dev script & contribution guide +- Do not show configuration changed if config_hash is null +- Add config_hash if its null (old deployments) +- Label generation +- Labels +- Email channel no recepients +- Limit horizon processes to 2 by default +- Add custom port as ssh option to deploy_key based commands +- Remove custom port from git repo url +- ContainerStatus job +- Service docs links +- Add PGUSER to prevent HC warning +- Preselect s3 storage if available +- Port exposes change, shoud regenerate label +- Boarding +- Clone to with the same environment name +- Cleanup stucked resources on start +- Do not allow to delete env if a resource is defined +- Service template generator + appwrite +- Mongodb backup +- Make sure coolfiy network exists on install +- Syncbunny command +- Encrypt mongodb password +- Mongodb healtcheck command +- Rate limit for api + add mariadb + mysql +- Server settings guarded +- Space in build args +- Lock SERVICE_FQDN envs +- If user is invited, that means its email is verified +- Force password reset on invited accounts +- Add ssh options to git ls-remote +- Git ls-remote +- Remove coolify labels from ui +- Missing environment variables prevewi on service +- Invoice.paid should sleep for 5 seconds +- Local dev repo +- Deployments ui +- Dockerfile build pack fix +- Set labels on generate domain +- Network service parse +- Notification url in containerstatusjob +- Gh webhook response 200 to installation_repositories +- Delete destination +- No id found +- Missing $mailMessage +- Set default from/sender names +- No environments +- Telegram text +- Private key not found error - UI - -## [2.6.1] - 2022-05-03 - -### 🚀 Features - -- Basic server usage on dashboard -- Show usage trends -- Usage on dashboard -- Custom script path for Plausible -- WP could have custom db -- Python image selection - -### 🐛 Bug Fixes - -- ExposedPorts -- Logos for dbs -- Do not run SSL renew in development -- Check domain for coolify before saving -- Remove debug info -- Cancel jobs -- Cancel old builds in database -- Better DNS check to prevent errors -- Check DNS in prod only -- DNS check -- Disable sentry for now -- Cancel +- Resourcesdelete command +- Port number should be int +- Separate delete with validation of server +- Add nixpacks info +- Remove filter +- Container logs are now followable in full-screen and sorted by timestamp +- Ui for labels +- Ui +- Deletions +- Build_image not found +- Github source view +- Github source view +- Dockercleanupjob should be released back +- Ui +- Local ip address +- Revert workdir to basedir +- Container status jobs for old pr deployments +- Service updates +- *(fider template)* Use the correct docs url +- Fqdn for minio +- Generate service fields +- Mariadb backups +- When to pull image +- Do not allow to enter local ip addresses +- Reset password +- Only report nonruntime errors +- Handle different label formats in services +- Server adding process +- Show defined resources in server tab, so you will know what you need to delete before you can delete the server. +- Lots of regarding git + docker compose deployments +- Pull request build variables +- Double default password length +- Do not remove deployment in case compose based failed +- No container servers +- Sentry issue +- Dockercompose save ./ volumes under /data/coolify +- Server view for link() +- Default value do not overwrite existing env value +- Use official install script with rancher (one will work for sure) +- Add cf tunnel to boarding server view +- Prevent autorefresh of proxy status +- Missing docker image thing +- Add hc for soketi +- Deploy the right compose file +- Bind volumes for compose bp +- Use hc port 80 in case of static build +- Switching to static build +- Container selection +- Service navbar using new realtime events +- Do not create duplicated networks +- Live event +- Service start + event +- Service deletion job +- Double ws connection +- Boarding view +- Do not send telegram noti on intent payment failed +- Database ui is realtime based +- Live mode for github webhooks +- Ui +- Realtime connection popup could be disabled +- Realtime check +- Add new destination +- Proxy logs +- Db status check +- Pusher host +- Add ipv6 +- Realtime connection?! +- Websocket +- Better handling of errors with install script +- Install script parse version +- Only allow to modify in .env file if AUTOUPDATE is set +- Is autoupdate not null +- Run init command after production seeder +- Init +- Comma in traefik custom labels +- Ignore if dynamic config could not be set +- Service env variable ovewritten if it has a default value +- Labelling +- Non-ascii chars in labels +- Labels +- Init script echos +- Update Coolify script +- Null notify +- Check queued deployments as well +- Copy invitation +- Password reset / invitation link requests +- Add catch all route +- Revert random container job delay +- Backup executions view +- Only check server status in container status job +- Improve server status check times +- Handle other types of generated values +- Server checking status +- Ui for adding new destination +- Reset domains on compose file change +- Domains for compose bp +- No action in webhooks +- Add debug output to gitlab webhooks +- Do not push dockerimage +- Add alpha to swarm +- Server not found +- Do not autovalidate server on mount +- Server update schedule +- Swarm support ui +- Server ready +- Get swarm service logs +- Docker compose apps env rewritten +- Storage error on dbs +- Why?! +- Stay tuned +- Cpu limit to float from int +- Add source commit to final envs +- Routing, switch back to old one +- Deploy instead of restart in case swarm is used +- Button title +- Restore falsely deleted coolify-db-backup +- Sub +- Wrong env variable parsing +- Deploy key + docker compose +- Horizon +- Duplicate compose variable +- Set deployment failed if new container is not healthy +- Nixpacks cache +- Only add restart policy if its empty (compose) +- Nixpacks buildpack +- File storage save +- Database env variables +- Healthy status +- Show framework based notification in build logs +- Traefik labels +- Use ip for sslip in dev if remote server is used +- Service labels without ports (unknown ports) +- Sort and rename (unique part) of labels +- Settings menu +- Remove traefik debug in dev mode +- Php pgsql to 8.2 +- Static buildpack should set port 80 +- Update navbar on build_pack change +- Do not include thegameplan.json into build image +- Submit error on postgresql +- Email verification / forgot password +- Escape build envs properly for nixpacks + docker build +- Undead endpoint +- Upload limit on ui +- Save cmd output propely (merge) +- Load profile on remote commands +- Load profile and set envs on remote cmd +- Restart should not update config hash +- Preview deployments with nixpacks +- Cleanup docker stuffs before upgrading +- Service deletion command +- Cpuset limits was determined in a way that apps only used 1 CPU max, ehh, sorry. +- Service stack view +- Change proxy view +- Checkbox click +- Git pull command for deploy key based previews +- Server status job +- Service deletion bug! +- Links +- Redis custom conf +- Sentry error +- Restrict concurrent deployments per server +- Queue +- Change env variable length +- Bitbucket manual deployments +- Webhooks for multiple apps +- Unhealthy deployments should be failed +- Add env variables for wordpress template without database +- Service deletion function +- Service deletion fix +- Dns validation + duplicated fqdns +- Validate server navbar upated +- Regenerate labels on application clone +- Service deletion +- Not able to use other shared envs +- Sentry fix +- Sentry +- Sentry error +- Sentry +- Sentry error +- Create dynamic directory +- Migrate to new modal +- Duplicate domain check +- Tags +- Wrap tags and avoid horizontal overflow +- Stripe webhooks +- Feedback from self-hosted envs to discord +- New menu on navbar +- Make sure resources are deleted in async mode +- Go to prod env from dashboard if there is no other envs defined +- User proper image_tag, if set +- New menu ui +- Lock logdrain configuration when one of them are enabled +- Add docker compose check during server validation +- Get service stack as uuid, not name +- Menu +- Flex wrap deployment previews +- Boolean docker options +- Only add 'networks' key if 'network_mode' is absent +- Cleanup scheduled tasks +- Padding left on input boxes +- Use ls / command instead ls +- Do not add the same server twice +- Only show redeployment required if status is not exited +- Add openbsd ssh server check +- Resources +- Empty build variables +- *(server)* Revalidate server button not showing in server's page +- Fluent bit ident level +- Submodule cloning +- Database status +- Permission change updates from webhook +- Server validation +- Connections being stuck and not processed until proxy restarts +- Use latest image if nothing is specified +- No coolify.yaml found +- Server validation +- Statuses +- Unknown image of service until it is uploaded +- Subscription / plan switch, etc +- Firefly service +- Force enable/disable server in case ultimate package quantity decreases +- Server disabled +- Custom dockerfile location always checked +- Import to mysql and mariadb +- Resource tab not loading if server is not reachable +- Load unmanaged async +- Do not show n/a networsk +- Service container status updates +- Public prs should not be commented +- Pull request deployments + build servers +- Env value generation +- Sentry error +- Service status updated +- Should note delete personal teams +- Make sure to show some buttons +- Sort repositories by name +- Deploy api messages +- Fqdn null in case docker compose bp +- Reload caddy issue +- /realtime endpoint +- Proxy switch +- Service ports for services + caddy +- Failed deployments should send failed email/notification +- Consider custom healthchecks in dockerfile +- Create initial files async +- Docker compose validation +- Duplicate dockerfile +- Multiline env variables +- Server stopped, service page not reachable +- Empty get logs number of lines +- Only escape envs after v239+ +- 0 in env value +- Consistent container name +- Custom ip address should turn off rolling update +- Multiline input +- Raw compose deployment +- Dashboard view if no project found +- Volumes for prs +- Shared env variable parsing +- Compose env has SERVICE, but not defined for Coolify +- Public service database +- Make sure service db proxy restarted +- Restart service db proxies +- Two factor +- Ui for tags +- Update resources view +- Realtime connection check +- Multline env in dev mode +- Scheduled backup for other service databases (supabase) +- PR deployments should not be distributed to 2 servers +- Name/from address required for resend +- Autoupdater +- Async service loads +- Disabled inputs are not trucated +- Duplicated generated fqdns are now working +- Uis +- Ui for cftunnels +- Search services +- Trial users subscription page +- Async public key loading +- Unfunctional server should see resources +- Warning if you use multiple domains for a service +- New github app creation +- Always rebuild Dockerfile / dockerimage buildpacks +- Do not rebuild dockerfile based apps twice +- Make sure if envs are changed, rebuild is needed +- Members cannot manage subscriptions +- IsMember +- Storage layout +- How to update docker-compose, environment variables and fqdns +- Git submodule update +- Unintended left padding on sidebar +- Hashed random delimeter in ssh commands + make sure to remove the delimeter from the command +- Service config hash update +- Redeploy if image not found in restart only mode +- Check each required binaries one-by-one +- Helper image only pulled if required, not every 10 mins +- Make sure that confs when checking if it is changed sorted +- Respect .env file (for default values) +- Remove temporary cloudflared config +- Remove lazy loading until bug figured out +- Rollback feature +- Base64 encode .env +- $ in labels escaped +- .env saved to deployment server, not to build server +- Do no able to delete gh app without deleting resources +- 500 error on edge case +- Able to select server when creating new destination +- N8n template +- Refresh public ips on start +- Move s3 storages to separate view +- Mongo db backup +- Backups +- Autoupdate +- Respect start period and chekc interval for hc +- Parse HEALTHCHECK from dockerfile +- Make s3 name and endpoint required +- Able to update source path for predefined volumes +- Get logs with non-root user +- Mongo 4.0 db backup +- Formbricks image origin +- Add port even if traefik is used +- Typo in tags.blade.php +- Install.sh error +- Env file +- Comment out internal notification in email_verify method +- Confirmation for custom labels +- Change permissions on newly created dirs +- Color for resource operation server and project name +- Only show realtime error on non-cloud instances +- Only allow push and mr gitlab events +- Improve scheduled task adding/removing +- Docker compose dependencies for pr previews +- Properly populating dependencies +- Use commit hash on webhooks +- Commit message length +- Hc from localhost to 127.0.0.1 +- Use rc in hc +- Telegram group chat notifications +- PR deployments have good predefined envs +- Optimize new resource creation +- Show it docker compose has syntax errors +- Wrong time during a failed deployment +- Removal of the failed deployment condition, addition of since started instead of finished time +- Use local versions + service templates and query them every 10 minutes +- Check proxy functionality before removing unnecessary coolify.yaml file and checking Docker Engine +- Show first 20 users only in admin view +- Add subpath for services +- Ghost subdir +- Do not pull templates in dev +- Templates +- Update error message for invalid token to mention invalid signature +- Disable containerStopped job for now +- Disable unreachable/revived notifications for now +- JSON_UNESCAPED_UNICODE +- Add wget to nixpacks builds +- Pre and post deployment commands +- Bitbucket commits link +- Better way to add curl/wget to nixpacks +- Root team able to download backups +- Build server should not have a proxy +- Improve build server functionalities +- Sentry issue +- Sentry +- Sentry error + livewire downgrade +- Sentry +- Sentry +- Sentry error +- Sentry +- Force load services from cdn on reload list +- Do not allow service storage mount point modifications +- Volume adding +- Sync upgrade process +- Publish horizon +- Add missing team model +- Test new upgrade process? +- Throw exception +- Build server dirs not created on main server +- Compose load with non-root user +- Able to redeploy dockerfile based apps without cache +- Compose previews does have env variables +- Fine-tune cdn pulls +- Spamming :D +- Parse docker version better +- Compose issues +- SERVICE_FQDN has source port in it +- Logto service +- Allow invitations via email +- Sort by defined order + fixed typo +- Only ignore volumes with driver_opts +- Check env in args for compose based apps +- Custom docker compose commands, add project dir if needed +- Autoupdate process +- Backup executions view +- Handle previously defined compose previews +- Sort backup executions +- Supabase service, newest versions +- Set default name for Docker volumes if it is null +- Multiline variable should be literal + should be multiline in bash with \ +- Gitlab merge request should close PR +- Multiline build args +- Setup script doesnt link to the correct source code file +- Install.sh do not reinstall packages on arch +- Just restart +- Stripprefix middleware correctly labeled to http +- Bitbucket link +- Compose generator +- Do no truncate repositories wtih domain (git) in it +- In services should edit compose file for volumes and envs +- Handle laravel deployment better +- Db proxy status shown better in the UI +- Show commit message on webhooks + prs +- Metrics parsing +- Charts +- Application custom labels reset after saving +- Static build with new nixpacks build process +- Make server charts one livewire component with one interval selector +- You can now add env variable from ui to services +- Update compose environment with UI defined variables +- Refresh deployable compose without reload +- Remove cloud stripe notifications +- App deployment should be in high queue +- Remove zoom from modals +- Get envs before sortby +- MB is % lol +- Projects with 0 envs +- Run user commands on high prio queue +- Load js locally +- Remove lemon + paddle things +- Run container commands on high priority +- Image logo +- Remove both option for api endpoints. it just makes things complicated +- Cleanup subs in cloud +- Show keydbs/dragonflies/clickhouses +- Only run cloud clean on cloud + remove root team +- Force cleanup on busy servers +- Check domain on new app via api +- Custom container name will be the container name, not just internal network name +- Api updates +- Yaml everywhere +- Add newline character to private key before saving +- Add validation for webhook endpoint selection +- Database input validators +- Remove own app from domain checks +- Return data of app update +- Do not overwrite hardcoded variables if they rely on another variable +- Remove networks when deleting a docker compose based app +- Api +- Always set project name during app deployments +- Remove volumes as well +- Gitea pr previews +- Prevent instance fqdn persisting to other servers dynamic proxy configs +- Better volume cleanups +- Cleanup parameter +- Update redirect URL in unauthenticated exception handler +- Respect top-level configs and secrets +- Service status changed event +- Disable sentinel until a few bugs are fixed +- Service domains and envs are properly updated +- *(reactive-resume)* New healthcheck command for MinIO +- *(MinIO)* New command healthcheck +- Update minio hc in services +- Add validation for missing docker compose file +- Typo in is_literal helper +- Env is_literal helper text typo +- Update docker compose pull command with --policy always +- Plane service template +- Vikunja +- Docmost template +- Drupal +- Improve github source creation +- Tag deployments +- New docker compose parsing +- Handle / in preselecting branches +- Handle custom_internal_name check in ApplicationDeploymentJob.php +- If git limit reached, ignore it and continue with a default selection +- Backup downloads +- Missing input for api endpoint +- Volume detection (dir or file) is fixed +- Supabase +- Create file storage even if content is empty +- Preview deployments should be stopped properly via gh webhook +- Deleting application should delete preview deployments +- Plane service images +- Fix issue with deployment start command in ApplicationDeploymentJob +- Directory will be created by default for compose host mounts +- Restart proxy does not work + status indicator on the UI +- Uuid in api docs type +- Raw compose deployment .env not found +- Api -> application patch endpoint +- Remove pull always when uploading backup to s3 +- Handle array env vars +- Link in task failed job notifications +- Random generated uuid will be full length (not 7 characters) +- Gitlab service +- Gitlab logo +- Bitbucket repository url +- By default volumes that we cannot determine if they are directories or files are treated as directories +- Domain update on services on the UI +- Update SERVICE_FQDN/URL env variables when you change the domain +- Several shared environment variables in one value, parsed correctly +- Members of root team should not see instance admin stuff +- Parse docker composer +- Service env parsing +- Service env variables +- Activity type invalid +- Update env on ui +- Only append docker network if service/app is running +- Remove lazy load from scheduled tasks +- Plausible template +- Service_url should not have a trailing slash +- If usagebefore cannot be determined, cleanup docker with force +- Async remote command +- Only run logdrain if necessary +- Remove network if it is only connected to coolify proxy itself +- Dir mounts should have proper dirs +- File storages (dir/file mount) handled properly +- Do not use port exposes on docker compose buildpacks +- Minecraft server template fixed +- Graceful shutdown +- Stop resources gracefully +- Handle null and empty disk usage in DockerCleanupJob +- Show latest version on manual update view +- Empty string content should be saved as a file +- Update Traefik labels on init +- Add missing middleware for server check job +- Scheduledbackup not found +- Manual update process +- Timezone not updated when systemd is missing +- If volumes + file mounts are defined, should merge them together in the compose file +- All mongo v4 backups should use the different backup command +- Database custom environment variables +- Connect compose apps to the right predefined network +- Docker compose destination network +- Server status when there are multiple servers +- Sync fqdn change on the UI +- Pr build names in case custom name is used +- Application patch request instant_deploy +- Canceling deployment on build server +- Backup of password protected postgresql database +- Docker cleanup job +- Storages with preserved git repository +- Parser parser parser +- New parser only in dev +- Parser parser +- Numberoflines should be number +- Docker cleanup job +- Fix directory and file mount headings in file-storage.blade.php +- Preview fqdn generation +- Revert a few lines +- Service ui sync bug +- Setup script doesn't work on rhel based images with some curl variant already installed +- Let's wait for healthy container during installation and wait an extra 20 seconds (for migrations) +- Infra files +- Log drain only for Applications +- Copy large compose files through scp (not ssh) +- Check if array is associative or not +- Openapi endpoint urls +- Convert environment variables to one format in shared.php +- Logical volumes could be overwritten with new path +- Env variable in value parsed +- Pull coolify image only when the app needs to be updated +- Wrong executions order +- Handle project not found error in environment_details API endpoint +- Deployment running for - without "ago" +- Update helper image pulling logic to only pull if the version is newer +- Parser +- Plunk NEXT_PUBLIC_API_URI +- Reenable overlapping servercheckjob +- Appwrite template + parser +- Don't add `networks` key if `network_mode` is used +- Remove debug statement in shared.php +- Scp through cloudflare +- Delete older versions of the helper image other than the latest one +- Update remoteProcess.php to handle null values in logItem properties +- Disable mux_enabled during server validation +- Move mc command to coolify image from helper +- Keydb. add `:` delimiter for connection string +- Cloudflare tunnel with new multiplexing feature +- Keep-alive ws connections +- Add build.sh to debug logs +- Update Coolify installer +- Terminal +- Generate https for minio +- Install script +- Handle WebSocket connection close in terminal.blade.php +- Able to open terminal to any containers +- Refactor run-command +- If you exit a container manually, it should close the underlying tty as well +- Move terminal to separate view on services +- Only update helper image in DB +- Generated fqdn for SERVICE_FQDN_APP_3000 magic envs +- Proxy status +- Coolify-db should not be in the managed resources +- Store original root key in the original location +- Logto service +- Cloudflared service +- Migrations +- Cloudflare tunnel configuration, ui, etc +- Parser +- Exited services statuses +- Make sure to reload window if app status changes +- Deploy key based deployments +- Proxy fixes +- Proxy +- *(templates)* Filebrowser FQDN env variable +- Handle edge case when build variables and env variables are in different format +- Compose based terminal +- Filebrowser template +- Edit is_build_server_enabled upon creating application on other application type +- Save settings after assigning value +- In dev mode do not ask confirmation on delete +- Mixpost +- Handle deletion of 'hello' in confirmation modal for dev environment +- Remove autofocuses +- Ipv6 scp should use -6 flag +- Cleanup stucked applicationdeploymentqueue +- Realtime watch in development mode +- Able to select root permission easier +- Able to support more database dynamically from Coolify's UI +- Strapi template +- Bitcoin core template +- Api useBuildServer +- Service application view +- Add new supported database images +- Parse proxy config and check the set ports usage +- Update FQDN +- Scheduled backup for services view +- Parser, espacing container labels +- Reset description and subject fields after submitting feedback +- Tag mass redeployments +- Service env orders, application env orders +- Proxy conf in dev +- One-click services +- Use local service-templates in dev +- New services +- Remove not used extra host +- Chatwoot service +- Directus +- Database descriptions +- Update services +- Soketi +- Select server view +- Update mattermost image tag and add default port +- Remove env, change timezone +- Postgres healthcheck +- Azimutt template - still not working haha +- New parser with SERVICE_URL_ envs +- Improve service template readability +- Update password variables in Service model +- Scheduled database server +- Select server view +- Signup +- Application domains should be http and https only +- Validate and sanitize application domains +- Sanitize and validate application domains +- Use correct env variable for invoice ninja password +- Make sure caddy is not removed by cleanup +- Libretranslate +- Do not allow to change number of lines when streaming logs +- Plunk +- No manual timezones +- Helper push +- Format +- Add port metadata and Coolify magic to generate the domain +- Sentinel +- Metrics +- Generate sentinel url +- Only enable Sentinel for new servers +- Is_static through API +- Allow setting standalone redis variables via ENVs (team variables...) +- Check for username separately form password +- Encrypt all existing redis passwords +- Pull helper image on helper_version change +- Redis database user and password +- Able to update ipv4 / ipv6 instance settings +- Metrics for dbs +- Sentinel start fixed +- Validate sentinel custom URL when enabling sentinel +- Should be able to reset labels in read-only mode with manual click +- No sentinel for swarm yet +- Charts ui +- Volume +- Sentinel config changes restarts sentinel +- Disable sentinel for now +- Disable Sentinel temporarily +- Disable Sentinel temporarily for non-dev environments +- Access team's github apps only +- Admins should now invite owner +- Add experimental flag +- GenerateSentinelUrl method +- NumberOfLines could be null +- Login / register view +- Restart sentinel once a day +- Changing private key manually won't trigger a notification +- Grammar for helper +- Fix my own grammar +- Add telescope only in dev mode +- New way to update container statuses +- Only run server storage every 10 mins if sentinel is not active +- Cloud admin view +- Queries in kernel.php +- Lower case emails only +- Change emails to lowercase on init +- Do not error on update email +- Always authenticate with lowercase emails +- Dashboard refactor +- Add min/max length to input/texarea +- Remove livewire legacy from help view +- Remove unnecessary endpoints (magic) +- Transactional email livewire +- Destinations livewire refactor +- Refactor destination/docker view +- Logdrains validation +- Reworded +- Use Auth(), add new db proxy stop event refactor clickhouse view +- Add user/pw to db view +- Sort servers by name +- Keydb view +- Refactor tags view / remove obsolete one +- Send discord/telegram notifications on high job queue +- Server view refresh on validation +- ShowBoarding +- Show docker installation logs & ubuntu 24.10 notification +- Do not overlap servercheckjob +- Server limit check +- Server validation +- Clear route / view +- Only skip docker installation on 24.10 if its not installed +- For --gpus device support +- Db/service start should be on high queue +- Do not stop sentinel on Coolify restart +- Run resourceCheck after new serviceCheckJob +- Mongodb in dev +- Better invitation errors +- Loading indicator for db proxies +- Do not execute gh workflow on template changes +- Only use sentry in cloud +- Update packagejson of coolify-realtime + add lock file +- Update last online with old function +- Seeder should not start sentinel +- Start sentinel on seeder +- Notifications ui +- Disable wire:navigate +- Confirmation Settings css for light mode +- Server wildcard +- Saving resend api key +- Wildcard domain save +- Disable cloudflare tunnel on "localhost" +- Define separate volumes for mattermost service template +- Github app name is too long +- ServerTimezone update +- Trigger.dev db host & sslmode=disable +- Manual update should be executed only once + better UX +- Upgrade.sh +- Missing privateKey +- Show proper error message on invalid Git source +- Convert HTTP to SSH source when using deploy key on GitHub +- Cloud + stripe related +- Terminal view loading in async +- Cool 500 error (thanks hugodos) +- Update schema in code decorator +- Openapi docs +- Add tests for git url converts +- Minio / logto url generation +- Admin view +- Min docker version 26 +- Pull latest service-templates.json on init +- Workflow files for coolify build +- Autocompletes +- Timezone settings validation +- Invalid tz should not prevent other jobs to be executed +- Testing-host should be built locally +- Poll with modal issue +- Terminal opening issue +- If service img not found, use github as a source +- Fallback to local coolify.png +- Gather private ips +- Cf tunnel menu should be visible when server is not validated +- Deployment optimizations +- Init script + optimize laravel +- Default docker engine version + fix install script +- Pull helper image on init +- SPA static site default nginx conf +- Modal-input +- Modal (+ add) on dynamic config was not opening, removed x-cloak +- AUTOUPDATE + checkbox opacity +- Improve helper text for metrics input fields +- Refine helper text for metrics input fields +- If mux conn fails, still use it without mux + save priv key with better logic +- Migration +- Always validate ssh key +- Make sure important jobs/actions are running on high prio queue +- Do not send internal notification for backups and status jobs +- Validateconnection +- View issue +- Heading +- Remove mux cleanup +- Db backup for services +- Version should come from constants + fix stripe webhook error reporting +- Undefined variable +- Remove version.php as everything is coming from constants.php +- Sentry error +- Websocket connections autoreconnect +- Sentry error - Sentry -- No image for Docker buildpack -- Default packagemanager -- Server usage only shown for root team -- Expose ports for services -- UI -- Navbar UI -- UI -- UI -- Remove RC python -- UI -- UI -- UI -- Default Python package - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ -- Version++ -- Version++ - -## [2.6.0] - 2022-05-02 - -### 🚀 Features - -- Hasura as a service -- Gzip compression -- Laravel buildpack is working! -- Laravel -- Fider service -- Database and services logs -- DNS check settings for SSL generation -- Cancel builds! - -### 🐛 Bug Fixes - -- Unami svg size -- Team switching moved to IAM menu -- Always use IP address for webhooks -- Remove unnecessary test endpoint +- Empty server API response +- Incorrect server API patch response +- Missing `uuid` parameter on server API patch +- Missing `settings` property on servers API +- Move servers API `delete_unused_*` properties +- Servers API returning `port` as a string -> integer +- Only return server uuid on server update +- Service generate includes yml files as well (haha) +- ServercheckJob should run every 5 minutes on cloud +- New resource icons +- Search should be more visible on scroll on new resource +- Logdrain settings +- Ui +- Email should be retried with backoff +- Alpine in body layout +- Application view loading +- Postiz service +- Only able to select the right keys +- Test email should not be required +- A few inputs +- Api endpoint +- Resolve undefined searchInput reference in Alpine.js component +- URL and sync new app name +- Typos and naming +- Client and webhook secret disappear after sync +- Missing `mysql_password` API property +- Incorrect MongoDB init API property +- Old git versions does not have --cone implemented properly +- Don't allow editing traefik config +- Restart proxy +- Dev mode +- Ui +- Display actual values for disk space checks in installer script +- Proxy change behaviour +- Add warning color +- Import NotificationSlack correctly +- Add middleware to new abilities, better ux for selecting permissions, etc. +- Root + read:sensive could read senstive data with a middlewarew +- Always have download logs button on scheduled tasks +- Missing css +- Development image +- Dockerignore +- DB migration error +- Drop all unused smtp columns +- Backward compatibility +- Email notification channel enabled function +- Instance email settins +- Make sure resend is false if SMTP is true and vice versa +- Email Notification saving +- Slack and discord url now uses text filed because encryption makes the url very long +- Notification trait +- Encryption fixes +- Docker cleanup email template +- Add missing deployment notifications to telegram +- New docker cleanup settings are now saved to the DB correctly +- Ui + migrations +- Docker cleanup email notifications +- General notifications does not go through email channel +- Test notifications to only send it to the right channel +- Remove resale_license from db as well +- Nexus service +- Fileflows volume names +- --cone +- Provider error +- Database migration +- Seeder +- Migration call +- Slack helper +- Telegram helper +- Discord helper +- Telegram topic IDs +- Make pushover settings more clear +- Typo in pushover user key +- Use Livewire refresh method and lock properties +- Create pushover settings for existing teams +- Update token permission check from 'write' to 'root' +- Pushover +- Oauth seeder +- Correct heading display for OAuth settings in settings-oauth.blade.php +- Adjust spacing in login form for improved layout +- Services env values should be sensitive +- Documenso +- Dolibarr +- Typo +- Update OauthSettingSeeder to handle new provider definitions and ensure authentik is recreated if missing +- Improve OauthSettingSeeder to correctly delete non-existent providers and ensure proper handling of provider definitions +- Encrypt resend API key in instance settings +- Resend api key is already a text column +- Monaco editor light and dark mode switching +- Service status indicator + oauth saving +- Socialite for azure and authentik +- Saving oauth +- Fallback for copy button +- Copy the right text +- Maybe fallback is now working +- Only show copy button on secure context +- Render html on error page correctly +- Invalid API response on missing project +- Applications API response code + schema +- Applications API writing to unavailable models +- If an init script is renamed the old version is still on the server +- Oauthseeder +- Compose loading seq +- Resource clone name + volume name generation +- Update Dockerfile entrypoint path to /etc/entrypoint.d +- Debug mode +- Unreachable notifications +- Remove duplicated ServerCheckJob call +- Few fixes and use new ServerReachabilityChanged event +- Use serverStatus not just status +- Oauth seeder +- Service ui structure +- Check port 8080 and fallback to 80 +- Refactor database view +- Always use docker cleanup frequency +- Advanced server UI +- Html css +- Fix domain being override when update application +- Use nixpacks predefined build variables, but still could update the default values from Coolify +- Use local monaco-editor instead of Cloudflare +- N8n timezone +- Smtp encryption +- Bind() to 0.0.0.0:80 failed +- Oauth seeder +- Unreachable notifications +- Instance settings migration +- Only encrypt instance email settings if there are any +- Error message +- Update healthcheck and port configurations to use port 8080 +- Compose envs +- Scheduled tasks and backups are executed by server timezone. +- Show backup timezone on the UI +- Disappearing UI after livewire event received +- Add default vector db for anythingllm +- We need XSRF-TOKEN for terminal +- Prevent default link behavior for resource and settings actions in dashboard +- Increase default php memory limit +- Show if only build servers are added to your team +- Update Livewire button click method to use camelCase +- Local dropzonejs +- Import backups due to js stuff should not be navigated +- Install inetutils on Arch Linux +- Use ip in place of hostname from inetutils in arch +- Update import command to append file redirection for database restoration +- Ui bug on pw confirmation +- Exclude system and computed fields from model replication +- Service cloning on a separate server +- Application cloning +- `Undefined variable $fs_path` for databases +- Service and database cloning and label generation +- Labels and URL generation when cloning +- Clone naming for different database data volumes +- Implement all the cloneMe changes for ResourceOperations as well +- Volume and fileStorages cloning +- View text and helpers +- Teable +- Trigger with external db +- Set `EXPERIMENTAL_FEATURES` to false for labelstudio +- Monaco editor disabled state +- Edge case where executions could be null +- Create destination properly +- Getcontainer status should timeout after 30s +- Enable response for temporary unavailability in sentinel push endpoint +- Use timeout in cleanup resources +- Add timeout to sentinel process checks for improved reliability +- Horizon job checker +- Update response message for sentinel push route +- Add own servers on cloud +- Application deployment +- Service update statsu +- If $SERVICE found in the service specific configuration, then search for it in the db +- Instance wide GitHub apps are not available on other teams then the source team +- Function calls - UI -- Migration -- Fider envs -- Checking low disk space -- Build image -- Update autoupdate env variable -- Renew certificates -- Webhook build images -- Missing node versions +- Deletion of single backup +- Backup job deletion - delete all backups from s3 and local +- Use new removeOldBackups function +- Retention functions and folder deletion for local backups +- Storage retention setting +- Db without s3 should still backup +- Wording +- `Undefined variable $service` when creating a new service +- Nodebb service +- Calibre-web service +- Rallly and actualbudget service +- Removed container_name +- Added healthcheck for gotenberg template +- Gotenberg +- *(template)* Gotenberg healthcheck, use /health instead of /version +- Use wire:navigate on sidebar +- Use wire:navigate on dashboard +- Use wire:navigate on projects page +- More wire:navigate +- Even more wire:navigate +- Service navigation +- Logs icons everywhere + terminal +- Redis DB should use the new resourceable columns +- Joomla service +- Add back letters to prod password requirement +- Check System and GitHub time and throw and error if it is over 50s out of sync +- Error message and server time getting +- Error rendering +- Render html correctly now +- Indent +- Potential fix for permissions update +- Expiration time claim ('exp') must be a numeric value +- Sanitize html error messages +- Production password rule and cleanup code +- Use json as it is just better than string for huge amount of logs +- Use `wire:navigate` on server sidebar +- Use finished_at for the end time instead of created_at +- Cancelled deployments should not show end and duration time +- Redirect to server index instead of show on error in Advanced and DockerCleanup components +- Disable registration after creating the root user +- RootUserSeeder +- Regex username validation +- Add spacing around echo outputs +- Success message +- Silent return if envs are empty or not set. +- Create the private key before the server in the prod seeder +- Update ProductionSeeder to check for private key instead of server's private key +- *(ui)* Missing underline for docs link in the Swarm section (#4860) +- *(service)* Change chatwoot service postgres image from `postgres:12` to `pgvector/pgvector:pg12` +- Docker image parser +- Add public key attribute to privatekey model +- Correct service update logic in Docker Compose parser +- Update CDN URL in install script to point to nightly version +- *(service)* Add healthcheck to Cloudflared service (#4859) +- Remove wire:navigate from import backups +- *(ui)* Backups link should not redirected to general +- Envs with special chars during build +- *(db)* `finished_at` timestamps are not set for existing deployments +- Load service templates on cloud +- *(email)* Transactional email sending +- *(ui)* Add missing save button for new Docker Cleanup page +- *(ui)* Show preview deployment environment variables +- *(ui)* Show error on terminal if container has no shell (bash/sh) +- *(parser)* Resource URL should only be parsed if there is one +- *(core)* Compose parsing for apps +- *(redis)* Update environment variable keys from standalone_redis_id to resourceable_id +- *(routes)* Local API docs not available on domain or IP +- *(routes)* Local API docs not available on domain or IP +- *(core)* Update application_id references to resourable_id and resourable_type for Nixpacks configuration +- *(core)* Correct spelling of 'resourable' to 'resourceable' in Nixpacks configuration for ApplicationDeploymentJob +- *(ui)* Traefik dashboard url not working +- *(ui)* Proxy status badge flashing during navigation +- *(core)* Update environment variable generation logic in ApplicationDeploymentJob to handle different build packs +- *(env)* Shared variables can not be updated +- *(ui)* Metrics stuck in loading state +- *(ui)* Use `wire:navigate` to navigate to the server settings page +- *(service)* Plunk API & health check endpoint (#4925) +- *(service)* Infinite loading and lag with invoiceninja service (#4876) +- *(service)* Invoiceninja service +- *(workflows)* `Waiting for changes` label should also be considered and improved messages +- *(workflows)* Remove tags only if the PR has been merged into the main branch +- *(terminal)* Terminal shows that it is not available, even though it is +- *(labels)* Docker labels do not generated correctly +- *(helper)* Downgrade Nixpacks to v1.29.0 +- *(labels)* Generate labels when they are empty not when they are already generated +- *(storage)* Hetzner storage buckets not working +- *(ui)* Update database control UI to check server functionality before displaying actions +- *(ui)* Typo in upgrade message +- *(ui)* Cloudflare tunnel configuration should be an info, not a warning +- *(s3)* DigitalOcean storage buckets do not work +- *(ui)* Correct typo in container label helper text +- Disable certain parts if readonly label is turned off +- Cleanup old scheduled_task_executions +- Validate cron expression in Scheduled Task update +- *(core)* Check cron expression on save +- *(database)* Detect more postgres database image types +- *(templates)* Update service templates +- Remove quotes in COOLIFY_CONTAINER_NAME +- *(templates)* Update Trigger.dev service templates with v3 configuration +- *(database)* Adjust MongoDB restore command and import view styling +- *(core)* Improve public repository URL parsing for branch and base directory +- *(core)* Increase HTTP/2 max concurrent streams to 250 (default) +- *(ui)* Update docker compose file helper text to clarify repository modification +- *(ui)* Skip SERVICE_FQDN and SERVICE_URL variables during update +- *(core)* Stopping database is not disabling db proxy +- *(core)* Remove --remove-orphans flag from proxy startup command to prevent other proxy deletions (db) +- *(api)* Domain check when updating domain +- *(ui)* Always redirect to dashboard after team switch +- *(backup)* Escape special characters in database backup commands +- *(core)* Improve deployment failure Slack notification formatting +- *(core)* Update Slack notification formatting to use bold correctly +- *(core)* Enhance Slack deployment success notification formatting +- *(ui)* Simplify service templates loading logic +- *(ui)* Align title and add button vertically in various views +- Handle pullrequest:updated for reliable preview deployments +- *(ui)* Fix typo on team page (#5105) +- Cal.com documentation link give 404 (#5070) +- *(slack)* Notification settings URL in `HighDiskUsage` message (#5071) +- *(ui)* Correct typo in Storage delete dialog (#5061) +- *(lang)* Add missing italian translations (#5057) +- *(service)* Improve duplicati.yaml (#4971) +- *(service)* Links in homepage service (#5002) +- *(service)* Added SMTP credentials to getoutline yaml template file (#5011) +- *(service)* Added `KEY` Variable to Beszel Template (#5021) +- *(cloudflare-tunnels)* Dead links to docs (#5104) +- System-wide GitHub apps (#5114) +- Pull latest image from registry when using build server +- *(deployment)* Improve server selection for deployment cancellation +- *(deployment)* Improve log line rendering and formatting +- *(s3-storage)* Optimize team admin notification query +- *(core)* Improve connection testing with dynamic disk configuration for s3 backups +- *(core)* Update service status refresh event handling +- *(ui)* Adjust polling intervals for database and service status checks +- *(service)* Update Fider service template healthcheck command +- *(core)* Improve server selection error handling in Docker component +- *(core)* Add server functionality check before dispatching container status +- *(ui)* Disable sticky scroll in Monaco editor +- *(ui)* Add literal and multiline env support to services. +- *(services)* Owncloud docs link +- *(template)* Remove db-migration step from `infisical.yaml` (#5209) +- *(service)* Penpot (#5047) +- *(core)* Production dockerfile +- *(ui)* Update storage configuration guidance link +- *(ui)* Set default SMTP encryption to starttls +- *(notifications)* Correct environment URL path in application notifications +- *(config)* Update default PostgreSQL host to coolify-db instead of postgres +- *(docker)* Improve Docker compose file validation process +- *(ui)* Restrict service retrieval to current team +- *(core)* Only validate custom compose files +- *(mail)* Set default mailer to array when not specified +- *(ui)* Correct redirect routes after task deletion +- *(core)* Adding a new server should not try to make the default docker network +- *(core)* Clean up unnecessary files during application image build +- *(core)* Improve label generation and merging for applications and services +- *(billing)* Handle 'past_due' subscription status in Stripe processing +- *(revert)* Label parsing +- *(helpers)* Initialize command variable in parseCommandFromMagicEnvVariable +- *(billing)* Restrict Stripe subscription status update to 'active' only +- *(api)* Docker compose based apps creationg through api +- *(database)* Improve database type detection for Supabase Postgres images +- *(ssl)* Permission of ssl crt and key inside the container +- *(ui)* Make sure file mounts do not showing the encrypted values +- *(ssl)* Make default ssl mode require not verify-full as it does not need a ca cert +- *(ui)* Select component should not always uses title case +- *(db)* SSL certificates table and model +- *(migration)* Ssl certificates table +- *(databases)* Fix database name users new `uuid` instead of DB one +- *(database)* Fix volume and file mounts and naming +- *(migration)* Store subjectAlternativeNames as a json array in the db +- *(ssl)* Make sure the subjectAlternativeNames are unique and stored correctly +- *(ui)* Certificate expiration data is null before starting the DB +- *(deletion)* Fix DB deletion +- *(ssl)* Improve SSL cert file mounts +- *(ssl)* Always create ca crt on disk even if it is already there +- *(ssl)* Use mountPath parameter not a hardcoded path +- *(ssl)* Use 1 instead of on for mysql +- *(ssl)* Do not remove SSL directory +- *(ssl)* Wrong ssl cert is loaded to the server and UI error when regenerating SSL +- *(ssl)* Make sure when regenerating the CA cert it is not overwritten with a server cert +- *(ssl)* Regenerating certs for a specific DB +- *(ssl)* Fix MariaDB and MySQL need CA cert +- *(ssl)* Add mount path to DB to fix regeneration of certs +- *(ssl)* Fix SSL regeneration to sign with CA cert and use mount path +- *(ssl)* Get caCert correctly +- *(ssl)* Remove caCert even if it is a folder by accident +- *(ssl)* Ger caCert and `mountPath` correctly +- *(ui)* Only show Regenerate SSL Certificates button when there is a cert +- *(ssl)* Server id +- *(ssl)* When regenerating SSL certs the cert is not singed with the new CN +- *(ssl)* Adjust ca paths for MySQL +- *(ssl)* Remove mode selection for MariaDB as it is not supported +- *(ssl)* Permission issue with MariDB cert and key and paths +- *(ssl)* Rename Redis mode to verify-ca as it is not verify-full +- *(ui)* Remove unused mode for MongoDB +- *(ssl)* KeyDB port and caCert args are missing +- *(ui)* Enable SSL is not working correctly for KeyDB +- *(ssl)* Add `--tls` arg to DrangflyDB +- *(notification)* Always send SSL notifications +- *(database)* Change default value of enable_ssl to false for multiple tables +- *(ui)* Correct grammatical error in 404 page +- *(seeder)* Update GitHub app name in GithubAppSeeder +- *(plane)* Update APP_RELEASE to v0.25.2 in environment configuration +- *(domain)* Dispatch refreshStatus event after successful domain update +- *(database)* Correct container name generation for service databases +- *(database)* Limit container name length for database proxy +- *(database)* Handle unsupported database types in StartDatabaseProxy +- *(database)* Simplify container name generation in StartDatabaseProxy +- *(install)* Handle potential errors in Docker address pool configuration +- *(backups)* Retention settings +- *(redis)* Set default redis_username for new instances +- *(core)* Improve instantSave logic and error handling +- *(general)* Correct link to framework specific documentation +- *(core)* Redirect healthcheck route for dockercompose applications +- *(api)* Use name from request payload +- *(issue#4746)* Do not use setGitImportSettings inside of generateGitLsRemoteCommands +- Correct some spellings +- *(service)* Replace deprecated credentials env variables on keycloak service +- *(keycloak)* Update keycloak image version to 26.1 +- *(console)* Handle missing root user in password reset command +- *(ssl)* Handle missing CA certificate in SSL regeneration job +- *(copy-button)* Ensure text is safely passed to clipboard +- *(file-storage)* Double save on compose volumes +- *(parser)* Add logging support for applications in services +- Only get apps for the current team +- *(DeployController)* Cast 'pr' query parameter to integer +- *(deploy)* Validate team ID before deployment +- *(wakapi)* Typo in env variables and add some useful variables to wakapi.yaml (#5424) +- *(ui)* Instance Backup settings +- *(docs)* Comment out execute for now +- *(installation)* Mount the docker config +- *(installation)* Path to config file for docker login +- *(service)* Add health check to Bugsink service (#5512) +- *(email)* Emails are not sent in multiple cases +- *(deployments)* Use graceful shutdown instead of `rm` +- *(docs)* Contribute service url (#5517) +- *(proxy)* Proxy restart does not work on domain +- *(ui)* Only show copy button on https +- *(database)* Custom config for MongoDB (#5471) +- *(api)* Used ssh keys can be deleted +- *(email)* Transactional emails not sending +- *(CheckProxy)* Update port conflict check to ensure accurate grep matching +- *(CheckProxy)* Refine port conflict detection with improved grep patterns +- *(CheckProxy)* Enhance port conflict detection by adjusting ss command for better output +- *(api)* Add back validateDataApplications (#5539) +- *(CheckProxy, Status)* Prevent proxy checks when force_stop is active; remove debug statement in General +- *(Status)* Conditionally check proxy status and refresh button based on force_stop state +- *(General)* Change redis_password property to nullable string +- *(DeployController)* Update request handling to use input method and enhance OpenAPI description for deployment endpoint +- *(pre-commit)* Correct input redirection for /dev/tty and add OpenAPI generation command +- *(pricing-plans)* Adjust grid class for improved layout consistency in subscription pricing plans +- *(migrations)* Make stripe_comment field nullable in subscriptions table +- *(mongodb)* Also apply custom config when SSL is enabled +- *(templates)* Correct casing of denoKV references in service templates and YAML files +- *(deployment)* Handle missing destination in deployment process to prevent errors +- *(parser)* Transform associative array labels into key=value format for better compatibility +- *(redis)* Update username and password input handling to clarify database sync requirements +- *(source)* Update connected source display to handle cases with no source connected +- *(application)* Append base directory to git branch URLs for improved path handling +- *(templates)* Correct casing of "denokv" to "denoKV" in service templates JSON +- *(navbar)* Update error message link to use route for environment variables navigation +- Unsend template +- Replace ports with expose +- *(templates)* Update Unsend compose configuration for improved service integration +- *(backup-edit)* Conditionally enable S3 checkbox based on available validated S3 storage +- *(source)* Update no sources found message for clarity +- *(api)* Correct middleware for service update route to ensure proper permissions +- *(api)* Handle JSON response in service creation and update methods for improved error handling +- Add 201 json code to servers validate api response +- *(docker)* Ensure password hashing only occurs when HTTP Basic Authentication is enabled +- *(docker)* Enhance hostname and GPU option validation in Docker run to compose conversion +- *(terminal)* Enhance WebSocket client verification with authorized IPs in terminal server +- *(ApplicationDeploymentJob)* Ensure source is an object before checking GitHub app properties +- *(ui)* Disable livewire navigate feature (causing spam of setInterval()) +- *(ui)* Remove required attribute from image input in service application view +- *(ui)* Change application image validation to be nullable in service application view +- *(Server)* Correct proxy path formatting for Traefik proxy type +- *(service)* Graceful shutdown of old container (#5731) +- *(ServerCheck)* Enhance proxy container check to ensure it is running before proceeding +- *(applications)* Include pull_request_id in deployment queue check to prevent duplicate deployments +- *(database)* Update label for image input field to improve clarity +- *(ServerCheck)* Set default proxy status to 'exited' to handle missing container state +- *(database)* Reduce container stop timeout from 300 to 30 seconds for improved responsiveness +- *(ui)* System theming for charts (#5740) +- *(dev)* Mount points?! +- *(dev)* Proxy mount point +- *(ui)* Allow adding scheduled backups for non-migrated databases +- *(DatabaseBackupJob)* Escape PostgreSQL password in backup command (#5759) +- *(ui)* Correct closing div tag in service index view +- *(select)* Update fallback logo path to use absolute URL for improved reliability +- *(constants)* Adding 'fedora-asahi-remix' as a supported OS (#5646) +- *(authentik)* Update docker-compose configuration for authentik service +- *(api)* Allow nullable destination_uuid (#5683) +- *(service)* Fix documenso startup and mail (#5737) +- *(docker)* Fix production dockerfile +- *(service)* Navidrome service +- *(service)* Passbolt +- *(service)* Add missing ENVs to NTFY service (#5629) +- *(service)* NTFY is behind a proxy +- *(service)* Vert logo and ENVs +- *(service)* Add platform to Observium service +- *(ActivityMonitor)* Prevent multiple event dispatches during polling +- *(service)* Convex ENVs and update image versions (#5827) +- *(service)* Paymenter +- *(ApplicationDeploymentJob)* Ensure correct COOLIFY_FQDN/COOLIFY_URL values (#4719) +- *(service)* Snapdrop no matching manifest error (#5849) +- *(service)* Use the same volume between chatwoot and sidekiq (#5851) +- *(api)* Validate docker_compose_raw input in ApplicationsController +- *(api)* Enhance validation for docker_compose_raw in ApplicationsController +- *(select)* Update PostgreSQL versions and titles in resource selection +- *(database)* Include DatabaseStatusChanged event in activityMonitor dispatch +- *(css)* Tailwind v5 things +- *(service)* Diun ENV for consistency +- *(service)* Memos service name +- *(css)* 8+ issue with new tailwind v4 +- *(css)* `bg-coollabs-gradient` not working anymore +- *(ui)* Add back missing service navbar components +- *(deploy)* Update resource timestamp handling in deploy_resource method +- *(patches)* DNF reboot logic is flipped +- *(deployment)* Correct syntax for else statement in docker compose build command +- *(shared)* Remove unused relation from queryDatabaseByUuidWithinTeam function +- *(deployment)* Correct COOLIFY_URL and COOLIFY_FQDN assignments based on parsing version in preview deployments +- *(docker)* Ensure correct parsing of environment variables by limiting explode to 2 parts +- *(project)* Update selected environment handling to use environment name instead of UUID +- *(ui)* Update server status display and improve server addition layout +- *(service)* Neon WS Proxy service not working on ARM64 (#5887) +- *(server)* Enhance error handling in server patch check notifications +- *(PushServerUpdateJob)* Add null checks before updating application and database statuses +- *(environment-variables)* Update label text for build variable checkboxes to improve clarity +- *(service-management)* Update service stop and restart messages for improved clarity and formatting +- *(preview-form)* Update helper text formatting in preview URL template input for better readability +- *(application-management)* Improve stop messages for application, database, and service to enhance clarity and formatting +- *(application-configuration)* Prevent access to preview deployments for deploy_key applications and update menu visibility accordingly +- *(select-component)* Handle exceptions during parameter retrieval and environment selection in the mount method +- *(previews)* Escape container names in stopContainers method to prevent shell injection vulnerabilities +- *(docker)* Add protection against empty container queries in GetContainersStatus to prevent unnecessary updates +- *(modal-confirmation)* Decode HTML entities in confirmation text to ensure proper display +- *(select-component)* Enhance user interaction by adding cursor styles and disabling selection during processing +- *(deployment-show)* Remove unnecessary fixed positioning for button container to improve layout responsiveness +- *(email-notifications)* Change notify method to notifyNow for immediate test email delivery +- *(service-templates)* Update Convex service configuration to use FQDN variables +- *(database-heading)* Simplify stop database message for clarity +- *(navbar)* Remove unnecessary x-init directive for loading proxy configuration +- *(patches)* Add padding to loading message for better visibility during update checks +- *(terminal-connection)* Improve error handling and stability for auto-connection; enhance component readiness checks and retry logic +- *(terminal)* Add unique wire:key to terminal component for improved reactivity and state management +- *(css)* Adjust utility classes in utilities.css for consistent application of Tailwind directives +- *(css)* Refine utility classes in utilities.css for proper Tailwind directive application +- *(install)* Update Docker installation script to use dynamic OS_TYPE and correct installation URL +- *(cloudflare)* Add error handling to automated Cloudflare configuration script +- *(navbar)* Add error handling for proxy status check to improve user feedback +- *(web)* Update user team retrieval method for consistent authentication handling +- *(cloudflare)* Update refresh method to correctly set Cloudflare tunnel status and improve user notification on IP address update +- *(service)* Update service template for affine and add migration service for improved deployment process +- *(supabase)* Update Supabase service images and healthcheck methods for improved reliability +- *(terminal)* Now it should work +- *(degraded-status)* Remove unnecessary whitespace in badge element for cleaner HTML +- *(routes)* Add name to security route for improved route management +- *(migration)* Update default value handling for is_sentinel_enabled column in server_settings +- *(seeder)* Conditionally dispatch CheckAndStartSentinelJob based on server's sentinel status +- *(service)* Disable healthcheck logging for Gotenberg (#6005) +- *(service)* Joplin volume name (#5930) +- *(server)* Update sentinelUpdatedAt assignment to use server's sentinel_updated_at property +- *(service)* Audiobookshelf healthcheck command (#5993) +- *(service)* Downgrade Evolution API phone version (#5977) +- *(service)* Pingvinshare-with-clamav +- *(ssh)* Scp requires square brackets for ipv6 (#6001) +- *(github)* Changing github app breaks the webhook. it does not anymore +- *(parser)* Improve FQDN generation and update environment variable handling +- *(ui)* Enhance status refresh buttons with loading indicators +- *(ui)* Update confirmation button text for stopping database and service +- *(routes)* Update middleware for deploy route to use 'api.ability:deploy' +- *(ui)* Refine API token creation form and update helper text for clarity +- *(ui)* Adjust layout of deployments section for improved alignment +- *(ui)* Adjust project grid layout and refine server border styling for better visibility +- *(ui)* Update border styling for consistency across components and enhance loading indicators +- *(ui)* Add padding to section headers in settings views for improved spacing +- *(ui)* Reduce gap between input fields in email settings for better alignment +- *(docker)* Conditionally enable gzip compression in Traefik labels based on configuration +- *(parser)* Enable gzip compression conditionally for Pocketbase images and streamline service creation logic +- *(ui)* Update padding for trademarks policy and enhance spacing in advanced settings section +- *(ui)* Correct closing tag for sponsorship link in layout popups +- *(ui)* Refine wording in sponsorship donation prompt in layout popups +- *(ui)* Update navbar icon color and enhance popup layout for sponsorship support +- *(ui)* Add target="_blank" to sponsorship links in layout popups for improved user experience +- *(models)* Refine comment wording in User model for clarity on user deletion criteria +- *(models)* Improve user deletion logic in User model to handle team member roles and prevent deletion if user is alone in root team +- *(ui)* Update wording in sponsorship prompt for clarity and engagement +- *(shared)* Refactor gzip handling for Pocketbase in newParser function for improved clarity +- *(server)* Prepend 'mux_' to UUID in muxFilename method for consistent naming +- *(ui)* Enhance terminal access messaging to clarify server functionality and terminal status +- *(database)* Proxy ssl port if ssl is enabled +- *(terminal)* Ensure shell execution only uses valid shell if available in terminal command +- *(ui)* Improve destination selection description for clarity in resource segregation +- *(jobs)* Update middleware to use expireAfter for WithoutOverlapping in multiple job classes +- Removing eager loading (#6071) +- *(template)* Adjust health check interval and retries for excalidraw service +- *(ui)* Env variable settings wrong order +- *(service)* Ensure configuration changes are properly tracked and dispatched +- *(service)* Update Postiz compose configuration for improved server availability +- *(install.sh)* Use IPV4_PUBLIC_IP variable in output instead of repeated curl +- *(env)* Generate literal env variables better +- *(deployment)* Update x-data initialization in deployment view for improved functionality +- *(deployment)* Enhance COOLIFY_URL and COOLIFY_FQDN variable generation for better compatibility +- *(deployment)* Improve docker-compose domain handling and environment variable generation +- *(deployment)* Refactor domain parsing and environment variable generation using Spatie URL library +- *(deployment)* Update COOLIFY_URL and COOLIFY_FQDN generation to use Spatie URL library for improved accuracy +- *(scheduling)* Change redis cleanup command frequency from hourly to weekly for better resource management +- *(versions)* Update coolify version numbers in versions.json and constants.php to 4.0.0-beta.420.5 and 4.0.0-beta.420.6 +- *(database)* Ensure internal port defaults correctly for unsupported database types in StartDatabaseProxy +- *(versions)* Update coolify version numbers in versions.json and constants.php to 4.0.0-beta.420.6 and 4.0.0-beta.420.7 +- *(scheduling)* Remove unnecessary padding from scheduled task form layout for improved UI consistency +- *(horizon)* Update queue configuration to use environment variable for dynamic queue management +- *(horizon)* Add silenced jobs +- *(application)* Sanitize service names for HTML form binding and ensure original names are stored in docker compose domains +- *(previews)* Adjust padding for rate limit message in application previews +- *(previews)* Order application previews by pull request ID in descending order +- *(previews)* Add unique wire keys for preview containers and services based on pull request ID +- *(previews)* Enhance domain generation logic for application previews, ensuring unique domains are created when none are set +- *(previews)* Refine preview domain generation for Docker Compose applications, ensuring correct method usage based on build pack type +- *(ui)* Typo on proxy request handler tooltip (#6192) +- *(backups)* Large database backups are not working (#6217) +- *(backups)* Error message if there is no exception +- *(installer)* Public IPv4 link does not work +- *(composer)* Version constraint of prompts +- *(service)* Budibase secret keys (#6205) +- *(service)* Wg-easy host should be just the FQDN +- *(ui)* Search box overlaps the sidebar navigation (#6176) +- *(webhooks)* Exclude webhook routes from CSRF protection (#6200) +- *(services)* Update environment variable naming convention to use underscores instead of dashes for SERVICE_FQDN and SERVICE_URL +- *(service)* Triliumnext platform and link +- *(application)* Update service environment variables when generating domain for Docker Compose +- *(application)* Add option to suppress toast notifications when loading compose file +- *(git)* Tracking issue due to case sensitivity +- *(git)* Tracking issue due to case sensitivity +- *(git)* Tracking issue due to case sensitivity +- *(ui)* Delete button width on small screens (#6308) +- *(service)* Matrix entrypoint +- *(ui)* Add flex-wrap to prevent overflow on small screens (#6307) +- *(docker)* Volumes get delete when stopping a service if `Delete Unused Volumes` is activated (#6317) +- *(docker)* Cleanup always running on deletion +- *(proxy)* Remove hardcoded port 80/443 checks (#6275) +- *(service)* Update healthcheck of penpot backend container (#6272) +- *(api)* Duplicated logs in application endpoint (#6292) +- *(service)* Documenso signees always pending (#6334) +- *(api)* Update service upsert to retain name and description values if not set +- *(database)* Custom postgres configs with SSL (#6352) +- *(policy)* Update delete method to check for admin status in S3StoragePolicy +- *(container)* Sort containers alphabetically by name in ExecuteContainerCommand and update filtering in Terminal Index +- *(application)* Streamline environment variable updates for Docker Compose services and enhance FQDN generation logic +- *(constants)* Update 'Change Log' to 'Changelog' in settings dropdown +- *(constants)* Update coolify version to 4.0.0-beta.420.7 +- *(parsers)* Clarify comments and update variable checks for FQDN and URL handling +- *(terminal)* Update text color for terminal availability message and improve readability +- *(drizzle-gateway)* Remove healthcheck from drizzle-gateway compose file and update service template +- *(templates)* Should generate old SERVICE_FQDN service templates as well +- *(constants)* Update official service template URL to point to the v4.x branch for accuracy +- *(git)* Use exact refspec in ls-remote to avoid matching similarly named branches (e.g., changeset-release/main). Use refs/heads/ or provider-specific PR refs. +- *(ApplicationPreview)* Change null check to empty check for fqdn in generate_preview_fqdn method +- *(email notifications)* Enhance EmailChannel to validate team membership for recipients and handle errors gracefully +- *(service api)* Separate create and update service functionalities +- *(templates)* Added a category tag for the docs service filter +- *(application)* Clear Docker Compose specific data when switching away from dockercompose +- *(database)* Conditionally set started_at only if the database is running +- *(ui)* Handle null values in postgres metrics (#6388) +- Disable env sorting by default +- *(proxy)* Filter host network from default proxy (#6383) +- *(modal)* Enhance confirmation text handling +- *(notification)* Update unread count display and improve HTML rendering +- *(select)* Remove unnecessary sanitization for logo rendering +- *(tags)* Update tag display to limit name length and adjust styling +- *(init)* Improve error handling for deployment and template pulling processes +- *(settings-dropdown)* Adjust unread count badge size and display logic for better consistency +- *(sanitization)* Enhance DOMPurify hook to remove Alpine.js directives for improved XSS protection +- *(servercheck)* Properly check server statuses with and without Sentinel +- *(errors)* Update error pages to provide navigation options +- *(github-deploy-key)* Update background color for selected private keys in deployment key selection UI +- *(auth)* Enhance authorization checks in application management +- *(backups)* S3 backup upload is failing +- *(backups)* Rollback helper update for now +- *(parsers)* Replace hyphens with underscores in service names for consistency. this allows to properly parse custom domains in docker compose based applications +- *(parsers)* Implement parseDockerVolumeString function to handle various Docker volume formats and modes, including environment variables and Windows paths. Add unit tests for comprehensive coverage. +- *(git)* Submodule update command uses an unsupported option (#6454) +- *(service)* Swap URL for FQDN on matrix template (#6466) +- *(parsers)* Enhance volume string handling by preserving mode in application and service parsers. Update related unit tests for validation. +- *(docker)* Update parser version in FQDN generation for service-specific URLs +- *(parsers)* Do not modify service names, only for getting fqdns and related envs +- *(compose)* Temporary allow to edit volumes in apps (compose based) and services +- *(previews)* Simplify FQDN generation logic by removing unnecessary empty check +- *(templates)* Update Matrix service compose configuration for improved compatibility and clarity ### 💼 Other -- Laravel - -## [2.4.11] - 2022-04-20 - -### 🚀 Features - -- Deno DB migration -- Show exited containers on UI & better UX -- Query container state periodically -- Install svelte-18n and init setup +- Only allow cleanup in production +- Make copy/password visible +- Dns check +- Remote docker engine +- Colorful states +- Application start +- Colors on svelte-select +- Improvements +- Fix +- Better layout for root team +- Fix +- Fixes +- Fix +- Fix +- Fix +- Fix +- Fix +- Fix +- Fix +- Insane amount +- Fix +- Fixes +- Fixes +- Fix +- Fixes +- Fixes +- Show extraconfig if wp is running - Umami service -- Coolify auto-updater -- Autoupdater -- Select base image for buildpacks - -### 🐛 Bug Fixes - -- Deno configurations -- Text on deno buildpack -- Correct branch shown in build logs -- Vscode permission fix -- I18n -- Locales -- Application logs is not reversed and queried better -- Do not activate i18n for now -- GitHub token cleanup on team switch -- No logs found -- Code cleanups -- Reactivate posgtres password -- Contribution guide -- Simplify list services -- Contribution -- Contribution guide -- Contribution guide -- Packagemanager finder +- Base image selector +- Laravel +- Appwrite +- Testing WS +- Traefik?! +- Traefik +- Traefik +- Traefik migration +- Traefik +- Traefik +- Traefik +- Notifications and application usage +- *(fix)* Traefik +- Css +- Error message https://github.com/coollabsio/coolify/issues/502 +- Changes +- Settings +- For removing app +- Local ssh port +- Redesign a lot +- Fixes +- Loading indicator for plausible buttons +- Fix +- Fider +- Typing +- Fixes here and there +- Dashboard fine-tunes +- Fine-tune +- Fixes +- Fix +- Dashbord fixes +- Fixes +- Fixes +- Route to the correct path when creating destination from db config +- Fixes +- Change tooltips and info boxes +- Added rc release +- Database_branches +- Login page +- Fix login/register page +- Update devcontainer +- Add debug log +- Fix initial loading icon bg +- Fix loading start/stop db/services +- Dashboard updates and a lot more +- Dashboard updates +- Fix tooltip +- Fix button +- Fix follow button +- Arm should be on next all the time +- Fix plausible +- Fix cleanup button +- Fix buttons +- Responsive! +- Fixes +- Fix git icon +- Dropdown as infobox +- Small logs on mobile +- Improvements +- Fix destination view +- Settings view +- More UI improvements +- Fixes +- Fixes +- Fix +- Fixes +- Beta features +- Fix button +- Service fixes +- Fix basedirectory meaning +- Resource button fix +- Main resource search +- Dev logs +- Loading button +- Fix gitlab importer view +- Small fix +- Beta flag +- Hasura console notification +- Fix +- Fix +- Fixes +- Inprogress version of iam +- Fix indicato +- Iam & settings update +- Send 200 for ping and installation wh +- Settings icon +- Docker-compose support +- Docker compose +- Remove worker jobs +- One less worker thread +- New resource label +- Secrets on apps +- Fix +- Fixes +- Reload compose loading +- Pocketbase release +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Conditional on environment +- Add missing variables +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Extract process handling from async job. +- Extract process handling from async job. +- Extract process handling from async job. +- Extract process handling from async job. +- Extract process handling from async job. +- Extract process handling from async job. +- Extract process handling from async job. +- Persisting data +- Scheduled backups +- Boarding +- Backup existing database +- User should know that the public key +- Services are not availble yet +- Show registered users on waitlist page +- Nixpacksarchive +- Add Plausible analytics +- Global env variables +- Fix +- Trial emails +- Server check instead of app check +- Show trial instead of sub +- Server lost connection +- Services +- Services +- Services +- Ui for services +- Services +- Services +- Services +- Fixes +- Fix typo +- Fixed z-index for version link. +- Add source button +- Fixed z-index for magicbar +- A bit better error +- More visible feedback button +- Update help modal +- Help +- Marketing emails +- Fix previews to preview +- Uptime kume hc updated +- Switch back to /data (volume errors) +- Notifications +- Add shared email option to everyone +- Dockerimage +- Updated dashboard +- Fix +- Fix +- Coolify proxy access logs exposed in dev +- Able to select environment on new resource +- Delete server +- Redis +- Wordpress +- Add helper to service domains +- PAT by team +- Generate services +- Mongodb backup +- Mongodb backup +- Updates +- Fix subs +- New deployment jobs +- Compose based apps +- Swarm +- Swarm +- Swarm +- Swarm +- Disable trial +- Meilisearch +- Broadcast +- 🌮 +- Env vars +- Migrate to livewire 3 +- Fix for comma in labels +- Add image name to service stack + better options visibility +- Swarm +- Swarm +- Send notification email if payment +- New modal component +- Specific about newrelic logdrains +- Updates +- Change + icon to hamburger. +- Redesign +- Redesign +- Run cleanup every day +- Fix +- Fix log outputs +- Automatic cloudflare tunnels +- Backup executions +- Light buttons +- Multiple server view +- New pricing +- Fix allowTab logic +- Use 2 space instead of tab +- Non-root user for remote servers +- Non-root +- Update resource operations view +- Fix tag view +- Fix a few boxes here and there +- Responsive here and there +- Rocketchat +- New services based git apps +- Unnecessary notification +- Update process +- Glances service +- Glances +- Able to update application +- Add basedir + compose file in new compose based apps +- Formbricks template add required CRON_SECRET +- Add required CRON_SECRET to Formbricks template +- Service env parsing +- Actually update timezone on the server +- Cron jobs are executed based on the server timezone +- Server timezone seeder +- Recent backups UI +- Use apt-get instead of apt +- Typo +- Only pull helper image if the version is newer than the one +- Plunk svg +- Pull helper image if not available otherwise s3 backup upload fails +- Set a default server timezone +- Implement SSH Multiplexing +- Enabel mux +- Cleanup stale multiplexing connections +- Remote servers with port and user +- Do not change localhost server name on revalidation +- Release.md file +- SSH Multiplexing on docker desktop on Windows +- Remove labels and assignees on issue close +- Make sure this action is also triggered on PR issue close +- Volumes on development environment +- Clean new volume name for dev volumes +- Persist DBs, services and so on stored in data/coolify +- Add SSH Key fingerprint to DB +- Add a fingerprint to every private key on save, create... +- Make sure invalid private keys can not be added +- Encrypt private SSH keys in the DB +- Add is_sftp and is_server_ssh_key coloums +- New ssh key file name on disk +- Store all keys on disk by default +- Populate SSH key folder +- Populate SSH keys in dev +- Use new function names and logic everywhere +- Create a Multiplexing Helper +- SSH multiplexing +- Remove unused code form multiplexing +- SSH Key cleanup job +- Private key with ID 2 on dev +- Move more functions to the PrivateKey Model +- Add ssh key fingerprint and generate one for existing keys +- ID issues on dev seeders +- Server ID 0 +- Make sure in use private keys are not deleted +- Do not delete SSH Key from disk during server validation error +- UI bug, do not write ssh key to disk in server dialog +- SSH Multiplexing for Jobs +- SSH algorhytm text +- Few multiplexing things +- Clear mux directory +- Multiplexing do not write file manually +- Integrate tow step process in the modal component WIP +- Ability to hide labels +- DB start, stop confirm +- Del init script +- General confirm +- Preview deployments and typos +- Service confirmation +- Confirm file storage +- Stop service confirm +- DB image cleanup +- Confirm ressource operation +- Environment variabel deletion +- Confirm scheduled tasks +- Confirm API token +- Confirm private key +- Confirm server deletion +- Confirm server settings +- Proxy stop and restart confirmation +- GH app deletion confirmation +- Redeploy all confirmation +- User deletion confirmation +- Team deletion confirmation +- Backup job confirmation +- Delete volume confirmation +- More conformations and fixes +- Delete unused private keys button +- Ray error because port is not uncommented +- #3322 deploy DB alterations before updating +- Css issue with advanced settings and remove cf tunnel in onboarding +- New cf tunnel install flow +- Made help text more clear +- Cloudflare tunnel +- Make helper text more clean to use a FQDN and not an URL +- Manual cleanup button and unused volumes and network deletion +- Force helper image removal +- Use the new confirmation flow +- Typo +- Typo in install script +- If API is disabeled do not show API token creation stuff +- Disable API by default +- Add debug bar +- Remove memlock as it caused problems for some users +- Server storage check +- Show backup button on supported db service stacks +- Update helper version +- Outline +- Directus +- Supertokens +- Supertokens json +- Rabbitmq +- Easyappointments +- Soketi +- Dozzle +- Windmill +- Coolify.json +- Keycloak +- Other DB options for freshrss +- Nextcloud MariaDB and MySQL versions +- Add peppermint +- Loggy +- Add UI for redis password and username +- Wireguard-easy template +- Https://github.com/coollabsio/coolify/issues/4186 +- Separate resources by type in projects view +- Improve s3 add view +- Caddy docker labels do not honor "strip prefix" option +- Test rename GitHub app +- Checkmate service and fix prowlar slogan (too long) +- Arrrrr +- Dep +- Docker dep +- Trigger.dev templates - wrong key length issue +- Trigger.dev template - missing ports and wrong env usage +- Trigger.dev template - fixed otel config +- Trigger.dev template - fixed otel config +- Trigger.dev template - fixed port config +- Bump all dependencies (#5216) +- Bump Coolify to 4.0.0-beta.398 +- Bump Coolify to 4.0.0-beta.400 +- *(migration)* Add SSL fields to database tables +- SSL Support for KeyDB +- Add missing UUID to openapi spec +- Add missing openapi items to PrivateKey +- Adjust Workflows for v5 (#5689) +- Add support for postmarketOS (#5608) +- *(core)* Simplify events for app/db/service status changes +- *(settings-dropdown)* Add icons to buttons for improved UI in settings dropdown +- *(ui)* Introduce task for simplifying resource operations UI by replacing boxes with dropdown selections to enhance user experience and streamline interactions -### 💼 Other +### 🚜 Refactor -- Umami service -- Base image selector +- Code +- Env variable generator +- Service logs are now on one page +- Application status changed realtime +- Custom labels +- Clone project +- Compose file and install script +- Add SCHEDULER environment variable to StartSentinel.php +- Update edit-domain form in project service view +- Add Huly services to compose file +- Remove redundant heading in backup settings page +- Add isBuildServer method to Server model +- Update docker network creation in ApplicationDeploymentJob +- Update destination.blade.php to add group class for better styling +- Applicationdeploymentjob +- Improve code structure in ApplicationDeploymentJob.php +- Remove unnecessary debug statement in ApplicationDeploymentJob.php +- Remove unnecessary debug statements and improve code structure in RunRemoteProcess.php and ApplicationDeploymentJob.php +- Remove unnecessary logging statements from UpdateCoolify +- Update storage form inputs in show.blade.php +- Improve Docker Compose parsing for services +- Remove unnecessary port appending in updateCompose function +- Remove unnecessary form class in profile index.blade.php +- Update form layout in invite-link.blade.php +- Add log entry when starting new application deployment +- Improve Docker Compose parsing for services +- Update Docker Compose parsing for services +- Update slogan in shlink.yaml +- Improve display of deployment time in index.blade.php +- Remove commented out code for clearing Ray logs +- Update save_environment_variables method to use application's environment_variables instead of environment_variables_preview +- Append utm_source parameter to documentation URL +- Update save_environment_variables method to use application's environment_variables instead of environment_variables_preview +- Update deployment previews heading to "Deployments" +- Remove unused variables and improve code readability +- Initialize null properties in Github Change component +- Improve pre and post deployment command inputs +- Improve handling of Docker volumes in parseDockerComposeFile function +- Replaces duplications in code with a single function +- Update text color for stderr output in deployment show view +- Update text color for stderr output in deployment show view +- Remove debug code for saving environment variables +- Update Docker build commands for better performance and flexibility +- Update image sizes and add new logos to README.md +- Update README.md with new logos and fix styling +- Update shared.php to use correct key for retrieving sentinel version +- Update container name assignment in Application model +- Remove commented code for docker container removal +- Update Application model to include getDomainsByUuid method +- Update Project/Show component to sort environments by created_at +- Update profile index view to display 2FA QR code in a centered container +- Update dashboard.blade.php to use project's default environment for redirection +- Update gitCommitLink method to handle null values in source.html_url +- Update docker-compose generation to use multi-line literal block +- Update Service model's saveComposeConfigs method +- Add default environment to Service model's saveComposeConfigs method +- Improve handling of default environment in Service model's saveComposeConfigs method +- Remove commented out code in Service model's saveComposeConfigs method +- Update stack-form.blade.php to include wire:target attribute for submit button +- Update code to use str() instead of Str::of() for string manipulation +- Improve formatting and readability of source.blade.php +- Add is_build_time property to nixpacks_php_fallback_path and nixpacks_php_root_dir +- Simplify code for retrieving subscription in Stripe webhook +- Add force parameter to StartProxy handle method +- Comment out unused code for network cleanup +- Reset default labels when docker_compose_domains is modified +- Webhooks view +- Tags view +- Only get instanceSettings once from db +- Update Dockerfile to set CI environment variable to true +- Remove unnecessary code in AppServiceProvider.php +- Update Livewire configuration views +- Update Webhooks.php to use nullable type for webhook URLs +- Add lazy loading to tags in Livewire configuration view +- Update metrics.blade.php to improve alert message clarity +- Update version numbers to 4.0.0-beta.312 +- Update version numbers to 4.0.0-beta.314 +- Remove unused code and fix storage form layout +- Update Docker Compose build command to include --pull flag +- Update DockerCleanupJob to handle nullable usageBefore property +- Server status job and docker cleanup job +- Update DockerCleanupJob to use server settings for force cleanup +- Update DockerCleanupJob to use server settings for force cleanup +- Disable health check for Rust applications during deployment +- Update CleanupDatabase.php to adjust keep_days based on environment +- Adjust keep_days in CleanupDatabase.php based on environment +- Remove commented out code for cleaning up networks in CleanupDocker.php +- Update livewire polling interval in heading.blade.php +- Remove unused code for checking server status in Heading.php +- Simplify log drain installation in ServerCheckJob +- Remove unnecessary debug statement in ServerCheckJob +- Simplify log drain installation and stop log drain if necessary +- Cleanup unnecessary dynamic proxy configuration in Init command +- Remove unnecessary debug statement in ApplicationDeploymentJob +- Update timeout for graceful_shutdown_container in ApplicationDeploymentJob +- Remove unused code and optimize CheckForUpdatesJob +- Update ProxyTypes enum values to use TRAEFIK instead of TRAEFIK_V2 +- Update Traefik labels on init and cleanup unnecessary dynamic proxy configuration +- Update StandalonePostgresql database initialization and backup handling +- Update cron expressions and add helper text for scheduled tasks +- Update Server model getContainers method to use collect() for containers and containerReplicates +- Import ProxyTypes enum and use TRAEFIK instead of TRAEFIK_V2 +- Update event listeners in Show components +- Refresh application to get latest database changes +- Update RabbitMQ configuration to use environment variable for port +- Remove debug statement in parseDockerComposeFile function +- ParseServiceVolumes +- Update OpenApi command to generate documentation +- Remove unnecessary server status check in destination view +- Remove unnecessary admin user email and password in budibase.yaml +- Improve saving of custom internal name in Advanced.php +- Add conditional check for volumes in generate_compose_file() +- Improve storage mount forms in add.blade.php +- Load environment variables based on resource type in sortEnvironmentVariables() +- Remove unnecessary network cleanup in Init.php +- Remove unnecessary environment variable checks in parseDockerComposeFile() +- Add null check for docker_compose_raw in parseCompose() +- Update dockerComposeParser to use YAML data from $yaml instead of $compose +- Convert service variables to key-value pairs in parseDockerComposeFile function +- Update database service name from mariadb to mysql +- Remove unnecessary code in DatabaseBackupJob and BackupExecutions +- Update Docker Compose parsing function to convert service variables to key-value pairs +- Update Docker Compose parsing function to convert service variables to key-value pairs +- Remove unused server timezone seeder and related code +- Remove unused server timezone seeder and related code +- Remove unused PullCoolifyImageJob from schedule +- Update parse method in Advanced, All, ApplicationPreview, General, and ApplicationDeploymentJob classes +- Remove commented out code for getIptables() in Dashboard.php +- Update .env file path in install.sh script +- Update SELF_HOSTED environment variable in docker-compose.prod.yml +- Remove unnecessary code for creating coolify network in upgrade.sh +- Update environment variable handling in StartClickhouse.php and ApplicationDeploymentJob.php +- Improve handling of COOLIFY_URL in shared.php +- Update build_args property type in ApplicationDeploymentJob +- Update background color of sponsor section in README.md +- Update Docker Compose location handling in PublicGitRepository +- Upgrade process of Coolify +- Improve handling of server timezones in scheduled backups and tasks +- Improve handling of server timezones in scheduled backups and tasks +- Improve handling of server timezones in scheduled backups and tasks +- Update cleanup schedule to run daily at midnight +- Skip returning volume if driver type is cifs or nfs +- Improve environment variable handling in shared.php +- Improve handling of environment variable merging in upgrade script +- Remove unnecessary code in ExecuteContainerCommand.php +- Improve Docker network connection command in StartService.php +- Terminal / run command +- Add authorization check in ExecuteContainerCommand mount method +- Remove unnecessary code in Terminal.php +- Remove unnecessary code in Terminal.blade.php +- Update WebSocket connection initialization in terminal.blade.php +- Remove unnecessary console.log statements in terminal.blade.php +- Update Docker cleanup label in Heading.php and Navbar.php +- Remove commented out code in Navbar.php +- Remove CleanupSshKeysJob from schedule in Kernel.php +- Update getAJoke function to exclude offensive jokes +- Update getAJoke function to use HTTPS for API request +- Update CleanupHelperContainersJob to use more efficient Docker command +- Update PrivateKey model to improve code readability and maintainability +- Remove unnecessary code in PrivateKey model +- Update PrivateKey model to use ownedByCurrentTeam() scope for cleanupUnusedKeys() +- Update install.sh script to check if coolify-db volume exists before generating SSH key +- Update ServerSeeder and PopulateSshKeysDirectorySeeder +- Improve attribute sanitization in Server model +- Update confirmation button text for deletion actions +- Remove unnecessary code in shared.php file +- Update environment variables for services in compose files +- Update select.blade.php to improve trademarks policy display +- Update select.blade.php to improve trademarks policy display +- Fix typo in subscription URLs +- Add Postiz service to compose file (disabled for now) +- Update shared.php to include predefined ports for services +- Simplify SSH key synchronization logic +- Remove unused code in DatabaseBackupStatusJob and PopulateSshKeysDirectorySeeder +- Remove commented out code and improve environment variable handling in newParser function +- Improve label positioning in input and checkbox components +- Group and sort fields in StackForm by service name and password status +- Improve layout and add checkbox for task enablement in scheduled task form +- Update checkbox component to support full width option +- Update confirmation label in danger.blade.php template +- Fix typo in execute-container-command.blade.php +- Update OS_TYPE for Asahi Linux in install.sh script +- Add localhost as Server if it doesn't exist and not in cloud environment +- Add localhost as Server if it doesn't exist and not in cloud environment +- Update ProductionSeeder to fix issue with coolify_key assignment +- Improve modal confirmation titles and button labels +- Update install.sh script to remove redirection of upgrade output to /dev/null +- Fix modal input closeOutside prop in configuration.blade.php +- Add support for IPv6 addresses in sslip function +- Update environment variable name for uptime-kuma service +- Improve start proxy script to handle existing containers gracefully +- Update delete server confirmation modal buttons +- Remove unnecessary code +- Update search input placeholder in resource index view +- Remove deployment queue when deleting an application +- Improve SSH command generation in Terminal.php and terminal-server.js +- Fix indentation in modal-confirmation.blade.php +- Improve parsing of commands for sudo in parseCommandsByLineForSudo +- Improve popup component styling and button behavior +- Encode delimiter in SshMultiplexingHelper +- Remove inactivity timer in terminal-server.js +- Improve socket reconnection interval in terminal.js +- Remove unnecessary watch command from soketi service entrypoint +- Update Traefik configuration for improved security and logging +- Improve proxy configuration and code consistency in Server model +- Rename name method to sanitizedName in BaseModel for clarity +- Improve migration command and enhance application model with global scope and status checks +- Unify notification icon +- Remove unused Azure and Authentik service configurations from services.php +- Change email column types in instance_settings migration from string to text +- Change OauthSetting creation to updateOrCreate for better handling of existing records +- Rename `coolify.environment` to `coolify.environmentName` +- Rename parameter in DatabaseBackupJob for clarity +- Improve checkbox component accessibility and styling +- Remove unused tags method from ApplicationDeploymentJob +- Improve deployment status check in isAnyDeploymentInprogress function +- Extend HorizonServiceProvider from HorizonApplicationServiceProvider +- Streamline job status retrieval and clean up repository interface +- Enhance ApplicationDeploymentJob and HorizonServiceProvider for improved job handling +- Remove commented-out unsubscribe route from API +- Update redirect calls to use a consistent navigation method in deployment functions +- AppServiceProvider +- Github.php +- Improve data formatting and UI +- Comment out RootUserSeeder call in ProductionSeeder for clarity +- Streamline ProductionSeeder by removing debug logs and unnecessary checks, while ensuring essential seeding operations remain intact +- Remove debug echo statements from Init command to clean up output and improve readability +- *(workflows)* Replace jq with PHP script for version retrieval in workflows +- *(s3)* Improve S3 bucket endpoint formatting +- *(vite)* Improve environment variable handling in Vite configuration +- *(ui)* Simplify GitHub App registration UI and layout +- Simplify service start and restart workflows +- Use pull flag on docker compose up +- *(ui)* Simplify file storage modal confirmations +- *(notifications)* Improve transactional email settings handling +- *(scheduled-tasks)* Improve scheduled task creation and management +- *(billing)* Enhance Stripe subscription status handling and notifications +- *(ui)* Unhide log toggle in application settings +- *(nginx)* Streamline default Nginx configuration and improve error handling +- *(install)* Clean up install script and enhance Docker installation logic +- *(ScheduledTask)* Clean up code formatting and remove unused import +- *(app)* Remove unused MagicBar component and related code +- *(database)* Streamline SSL configuration handling across database types +- *(application)* Streamline healthcheck parsing from Dockerfile +- *(notifications)* Standardize getRecipients method signatures +- *(configuration)* Centralize configuration management in ConfigurationRepository +- *(docker)* Update image references to use centralized registry URL +- *(env)* Add centralized registry URL to environment configuration +- *(storage)* Simplify file storage iteration in Blade template +- *(models)* Add is_directory attribute to LocalFileVolume model +- *(modal)* Add ignoreWire attribute to modal-confirmation component +- *(invite-link)* Adjust layout for better responsiveness in form +- *(invite-link)* Enhance form layout for improved responsiveness +- *(network)* Enhance docker network creation with ipv6 fallback +- *(network)* Check for existing coolify network before creation +- *(database)* Enhance encryption process for local file volumes +- *(proxy)* Improve port availability checks with multiple methods +- *(database)* Update MongoDB SSL configuration for improved security +- *(database)* Enhance SSL configuration handling for various databases +- *(notifications)* Update Telegram button URL for staging environment +- *(models)* Remove unnecessary cloud check in isEnabled method +- *(database)* Streamline event listeners in Redis General component +- *(database)* Remove redundant database status display in MongoDB view +- *(database)* Update import statements for Auth in database components +- *(database)* Require PEM key file for SSL certificate regeneration +- *(database)* Change MySQL daemon command to MariaDB daemon +- *(nightly)* Update version numbers and enhance upgrade script +- *(versions)* Update version numbers for coolify and nightly +- *(email)* Validate team membership for email recipients +- *(shared)* Simplify deployment status check logic +- *(shared)* Add logging for running deployment jobs +- *(shared)* Enhance job status check to include 'reserved' +- *(email)* Improve error handling by passing context to handleError +- *(email)* Streamline email sending logic and improve configuration handling +- *(email)* Remove unnecessary whitespace in email sending logic +- *(email)* Allow custom email recipients in email sending logic +- *(email)* Enhance sender information formatting in email logic +- *(proxy)* Remove redundant stop call in restart method +- *(file-storage)* Add loadStorageOnServer method for improved error handling +- *(docker)* Parse and sanitize YAML compose file before encoding +- *(file-storage)* Improve layout and structure of input fields +- *(email)* Update label for test email recipient input +- *(database-backup)* Remove existing Docker container before backup upload +- *(database)* Improve decryption and deduplication of local file volumes +- *(database)* Remove debug output from volume update process +- *(dev)* Remove OpenAPI generation functionality +- *(migration)* Enhance local file volumes migration with logging +- *(CheckProxy)* Replace 'which' with 'command -v' for command availability checks +- *(Server)* Use data_get for safer access to settings properties in isFunctional method +- *(Application)* Rename network_aliases to custom_network_aliases across the application for clarity and consistency +- *(ApplicationDeploymentJob)* Streamline environment variable handling by introducing generate_coolify_env_variables method and consolidating logic for pull request and main branch scenarios +- *(ApplicationDeploymentJob, ApplicationDeploymentQueue)* Improve deployment status handling and log entry management with transaction support +- *(SourceManagement)* Sort sources by name and improve UI for changing Git source with better error handling +- *(Email)* Streamline SMTP and resend settings handling in copyFromInstanceSettings method +- *(Email)* Enhance error handling in SMTP and resend methods by passing context to handleError function +- *(DynamicConfigurations)* Improve handling of dynamic configuration content by ensuring fallback to empty string when content is null +- *(ServicesGenerate)* Update command signature from 'services:generate' to 'generate:services' for consistency; update Dockerfile to run service generation during build; update Odoo image version to 18 and add extra addons volume in compose configuration +- *(Dockerfile)* Streamline RUN commands for improved readability and maintainability by adding line continuations +- *(Dockerfile)* Reintroduce service generation command in the build process for consistency and ensure proper asset compilation +- *(commands)* Reorganize OpenAPI and Services generation commands into a new namespace for better structure; remove old command files +- *(Dockerfile)* Remove service generation command from the build process to streamline Dockerfile and improve build efficiency +- *(navbar-delete-team)* Simplify modal confirmation layout and enhance button styling for better user experience +- *(Server)* Remove debug logging from isReachableChanged method to clean up code and improve performance +- *(source)* Conditionally display connected source and change source options based on private key presence +- *(jobs)* Update WithoutOverlapping middleware to use expireAfter for better queue management +- *(jobs)* Comment out unused Caddy label handling in ApplicationDeploymentJob and simplify proxy path logic in Server model +- *(database)* Simplify database type checks in ServiceDatabase and enhance image validation in Docker helper +- *(shared)* Remove unused ray debugging statement from newParser function +- *(applications)* Remove redundant error response in create_env method +- *(api)* Restructure routes to include versioning and maintain existing feedback endpoint +- *(api)* Remove token variable from OpenAPI specifications for clarity +- *(environment-variables)* Remove protected variable checks from delete methods for cleaner logic +- *(http-basic-auth)* Rename 'http_basic_auth_enable' to 'http_basic_auth_enabled' across application files for consistency +- *(docker)* Remove debug statement and enhance hostname handling in Docker run conversion +- *(server)* Simplify proxy path logic and remove unnecessary conditions +- *(Database)* Streamline container shutdown process and reduce timeout duration +- *(core)* Streamline container stopping process and reduce timeout duration; update related methods for consistency +- *(database)* Update DB facade usage for consistency across service files +- *(database)* Enhance application conversion logic and add existence checks for databases and applications +- *(actions)* Standardize method naming for network and configuration deletion across application and service classes +- *(logdrain)* Consolidate log drain stopping logic to reduce redundancy +- *(StandaloneMariadb)* Add type hint for destination method to improve code clarity +- *(DeleteResourceJob)* Streamline resource deletion logic and improve conditional checks for database types +- *(jobs)* Update middleware to prevent job release after expiration for CleanupInstanceStuffsJob, RestartProxyJob, and ServerCheckJob +- *(jobs)* Unify middleware configuration to prevent job release after expiration for DockerCleanupJob and PushServerUpdateJob +- *(service)* Observium +- *(service)* Improve leantime +- *(service)* Imporve limesurvey +- *(service)* Improve CodiMD +- *(service)* Typsense +- *(services)* Improve yamtrack +- *(service)* Improve paymenter +- *(service)* Consolidate configuration change dispatch logic and remove unused navbar component +- *(sidebar)* Simplify server patching link by removing button element +- *(slide-over)* Streamline button element and improve code readability +- *(service)* Enhance modal confirmation component with event dispatching for service stop actions +- *(slide-over)* Enhance class merging for improved component styling +- *(core)* Use property promotion +- *(service)* Improve maybe +- *(applications)* Remove unused docker compose raw decoding +- *(service)* Make TYPESENSE_API_KEY required +- *(ui)* Show toast when server does not work and on stop +- *(service)* Improve superset +- *(service)* Improve Onetimesecret +- *(service)* Improve Seafile +- *(service)* Improve orangehrm +- *(service)* Improve grist +- *(application)* Enhance application stopping logic to support multiple servers +- *(pricing-plans)* Improve label class binding for payment frequency selection +- *(error-handling)* Replace generic Exception with RuntimeException for improved error specificity +- *(error-handling)* Change Exception to RuntimeException for clearer error reporting +- *(service)* Remove informational dispatch during service stop for cleaner execution +- *(server-ui)* Improve layout and messaging in advanced settings and charts views +- *(terminal-access)* Streamline resource retrieval and enhance terminal access messaging in UI +- *(terminal)* Enhance terminal connection management and error handling, including improved reconnection logic and cleanup procedures +- *(application-deployment)* Separate handling of FAILED and CANCELLED_BY_USER statuses for clearer logic and notification +- *(jobs)* Update middleware to include job-specific identifiers for WithoutOverlapping +- *(jobs)* Modify middleware to use job-specific identifier for WithoutOverlapping +- *(environment-variables)* Remove debug logging from bulk submit handling for cleaner code +- *(environment-variables)* Simplify application build pack check in environment variable handling +- *(logs)* Adjust padding in logs view for improved layout consistency +- *(application-deployment)* Streamline post-deployment process by always dispatching container status check +- *(service-management)* Enhance container stopping logic by implementing parallel processing and removing deprecated methods +- *(activity-monitor)* Change activity property visibility and update view references for consistency +- *(activity-monitor)* Enhance layout responsiveness by adjusting class bindings and structure for better display +- *(service-management)* Update stopContainersInParallel method to enforce Server type hint for improved type safety +- *(service-management)* Rearrange docker cleanup logic in StopService to improve readability +- *(database-management)* Simplify docker cleanup logic in StopDatabase to enhance readability +- *(activity-monitor)* Consolidate activity monitoring logic and remove deprecated NewActivityMonitor component +- *(activity-monitor)* Update dispatch method to use activityMonitor instead of deprecated newActivityMonitor +- *(push-server-update)* Enhance application preview handling by incorporating pull request IDs and adding status update protections +- *(docker-compose)* Replace hardcoded Docker Compose configuration with external YAML template for improved database detection testing +- *(test-database-detection)* Rename services for clarity, add new database configurations, and update application service dependencies +- *(database-detection)* Enhance isDatabaseImage function to utilize service configuration for improved detection accuracy +- *(install-scripts)* Update Docker installation process to include manual installation fallback and improve error handling +- *(logs-view)* Update logs display for service containers with improved headings and dynamic key binding +- *(logs)* Enhance container loading logic and improve UI for logs display across various resource types +- *(cloudflare-tunnel)* Enhance layout and structure of Cloudflare Tunnel documentation and confirmation modal +- *(terminal-connection)* Streamline auto-connection logic and improve component readiness checks +- *(logs)* Remove unused methods and debug functionality from Logs.php for cleaner code +- *(remoteProcess)* Update sanitize_utf8_text function to accept nullable string parameter for improved type safety +- *(events)* Remove ProxyStarted event and associated ProxyStartedNotification listener for code cleanup +- *(navbar)* Remove unnecessary parameters from server navbar component for cleaner implementation +- *(proxy)* Remove commented-out listener and method for cleaner code structure +- *(events)* Update ProxyStatusChangedUI constructor to accept nullable teamId for improved flexibility +- *(cloudflare)* Update server retrieval method for improved query efficiency +- *(navbar)* Remove unused PHP use statement for cleaner code +- *(proxy)* Streamline proxy status handling and improve dashboard availability checks +- *(navbar)* Simplify proxy status handling and enhance loading indicators for better user experience +- *(resource-operations)* Filter out build servers from the server list and clean up commented-out code in the resource operations view +- *(execute-container-command)* Simplify connection logic and improve terminal availability checks +- *(navigation)* Remove wire:navigate directive from configuration links for cleaner HTML structure +- *(proxy)* Update StartProxy calls to use named parameter for async option +- *(clone-project)* Enhance server retrieval by including destinations and filtering out build servers +- *(ui)* Terminal +- *(ui)* Remove terminal header from execute-container-command view +- *(ui)* Remove unnecessary padding from deployment, backup, and logs sections +- *(service)* Update Hoarder to their new name karakeep (#5964) +- *(service)* Karakeep naming and formatting +- *(service)* Improve miniflux +- *(core)* Rename API rate limit ENV +- *(ui)* Simplify container selection form in execute-container-command view +- *(email)* Streamline SMTP and resend settings logic for improved clarity +- *(invitation)* Rename methods for consistency and enhance invitation deletion logic +- *(user)* Streamline user deletion process and enhance team management logic +- *(ui)* Separate views for instance settings to separate paths to make it cleaner +- *(ui)* Remove unnecessary step3ButtonText attributes from modal confirmation components for cleaner code +- *(ui)* Enhance project cloning interface with improved table layout for server and resource selection +- *(terminal)* Simplify command construction for SSH execution +- *(settings)* Streamline instance admin checks and initialization of settings in Livewire components +- *(policy)* Optimize team membership checks in S3StoragePolicy +- *(popup)* Improve styling and structure of the small popup component +- *(shared)* Enhance FQDN generation logic for services in newParser function +- *(redis)* Enhance CleanupRedis command with dry-run option and improved key deletion logic +- *(init)* Standardize method naming conventions and improve command structure in Init.php +- *(shared)* Improve error handling in getTopLevelNetworks function to return network name on invalid docker-compose.yml +- *(database)* Improve error handling for unsupported database types in StartDatabaseProxy +- *(previews)* Streamline preview URL generation by utilizing application method +- *(application)* Adjust layout and spacing in general application view for improved UI +- *(postgresql)* Improve layout and spacing in SSL and Proxy configuration sections for better UI consistency +- *(scheduling)* Replace deprecated job checks with ScheduledJobManager and ServerResourceManager for improved scheduling efficiency +- *(previews)* Move preview domain generation logic to ApplicationPreview model for better encapsulation and consistency across webhook handlers +- *(service)* Improve gowa +- *(previews)* Streamline preview domain generation logic in ApplicationDeploymentJob for improved clarity and maintainability +- *(services)* Simplify environment variable updates by using updateOrCreate and add cleanup for removed FQDNs +- *(jobs)* Remove logging for ScheduledJobManager and ServerResourceManager start and completion +- *(services)* Update validation rules to be optional +- *(service)* Improve langfuse +- *(service)* Improve openpanel template +- *(service)* Improve librechat +- *(public-git-repository)* Enhance form structure and add autofocus to repository URL input +- *(public-git-repository)* Remove commented-out code for cleaner template +- *(templates)* Update service template file handling to use dynamic file name from constants +- *(parsers)* Streamline domain handling in applicationParser and improve DNS validation logic +- *(templates)* Replace SERVICE_FQDN variables with SERVICE_URL in compose files for consistency +- *(links)* Replace inline SVGs with reusable external link component for consistency and improved maintainability +- *(previews)* Improve layout and add deployment/application logs links for previews +- *(docker compose)* Remove deprecated newParser function and associated test file to streamline codebase +- *(shared helpers)* Remove unused parseServiceVolumes function to clean up codebase +- *(parsers)* Update volume parsing logic to use beforeLast and afterLast for improved accuracy +- *(validation)* Implement centralized validation patterns across components +- *(jobs)* Rename job classes to indicate deprecation status +- Update check frequency logic for cloud and self-hosted environments; streamline server task scheduling and timezone handling +- *(policies)* Remove Response type hint from update methods in ApplicationPreviewPolicy and DatabasePolicy for improved flexibility +- *(policies)* Remove Response type hint from update methods in ApplicationPreviewPolicy and DatabasePolicy for improved flexibility +- *(git)* Improve submodule cloning +- *(parsers)* Remove unnecessary hyphen-to-underscore replacement for service names in serviceParser function +- *(urls)* Replace generateFqdn with generateUrl for consistent URL generation across applications +- *(domains)* Rename check_domain_usage to checkDomainUsage and update references across the application +- *(auth)* Simplify access control logic in CanAccessTerminal and ServerPolicy by allowing all users to perform actions +- *(policy)* Simplify ServiceDatabasePolicy methods to always return true and add manageBackups method ### 📚 Documentation +- Contribution guide - How to add new services - Update - Update +- Update Plunk documentation link in compose/plunk.yaml +- Update link to deploy api docs +- Add TECH_STACK.md (#4883) +- *(services)* Reword nitropage url and slogan +- *(readme)* Add Convex to special sponsors section +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- *(CONTRIBUTING)* Add note about Laravel Horizon accessibility +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- *(service)* Add new docs link for zipline (#5912) +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- *(claude)* Clarify that artisan commands should only be run inside the "coolify" container during development +- Add AGENTS.md for project guidance and development instructions +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ -- Version++ - -## [2.4.10] - 2022-04-17 - -### 🚀 Features - -- Add persistent storage for services -- Multiply dockerfile locations for docker buildpack -- Testing fluentd logging driver -- Fluentbit investigation -- Initial deno support - -### 🐛 Bug Fixes - -- Switch from bitnami/redis to normal redis -- Use redis-alpine -- Wordpress extra config -- Stop sFTP connection on wp stop -- Change user's id in sftp wp instance -- Use arm based certbot on arm -- Buildlog line number is not string -- Application logs paginated -- Switch to stream on applications logs -- Scroll to top for logs -- Pull new images for services all the time it's started. -- White-labeled custom logo -- Application logs - -### 💼 Other - -- Show extraconfig if wp is running - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ - -## [2.4.9] - 2022-04-14 - -### 🐛 Bug Fixes - -- Postgres root pw is pw field -- Teams view -- Improved tcp proxy monitoring for databases/ftp -- Add HTTP proxy checks -- Loading of new destinations -- Better performance for cleanup images -- Remove proxy container in case of dependent container is down -- Restart local docker coolify proxy in case of something happens to it -- Id of service container - -### ⚙️ Miscellaneous Tasks - -- Version++ +### 🎨 Styling -## [2.4.8] - 2022-04-13 +- Linting +- *(css)* Update padding utility for password input and add newline in app.css +- *(css)* Refine badge utility styles in utilities.css +- *(css)* Enhance badge utility styles in utilities.css -### 🐛 Bug Fixes +### 🧪 Testing -- Register should happen if coolify proxy cannot be started -- GitLab typo -- Remove system wide pw reset +- Native binary target +- Dockerfile +- Remove prisma +- More tests +- Setup database for upcoming tests ### ⚙️ Miscellaneous Tasks +- Version bump +- Version +- Version +- Version++ +- Version++ +- Version++ +- Version++ +- Version ++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version ++ +- Version++ +- Version++ +- Version++ +- Fixed typo on New Git Source view +- Version++ +- Version++ +- Version++ +- Version++ +- Lock file + fix packages +- Version++ - Version++ - -## [2.4.7] - 2022-04-13 - -### 🐛 Bug Fixes - -- Destinations to HAProxy - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.4.6] - 2022-04-13 - -### 🐛 Bug Fixes - -- Cleanup images older than a day -- Meilisearch service -- Load all branches, not just the first 30 -- ProjectID for Github -- DNS check before creating SSL cert -- Try catch me -- Restart policy for resources -- No permission on first registration -- Reverting postgres password for now - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.4.5] - 2022-04-12 - -### 🐛 Bug Fixes - -- Types -- Invitations -- Timeout values - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.4.4] - 2022-04-12 - -### 🐛 Bug Fixes - -- Haproxy build stuffs -- Proxy - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.4.3] - 2022-04-12 - -### 🐛 Bug Fixes - -- Remove unnecessary save button haha -- Update dockerfile - -### ⚙️ Miscellaneous Tasks - - Update packages - Version++ - Update build scripts - Update build packages - -## [2.4.2] - 2022-04-09 - -### 🐛 Bug Fixes - -- Missing install repositories GitHub -- Return own and other sources better -- Show config missing on sources - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.4.1] - 2022-04-09 - -### 🐛 Bug Fixes - -- Enable https for Ghost -- Postgres root passwor shown and set -- Able to change postgres user password from ui -- DB Connecting string generator - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.4.0] - 2022-04-08 - -### 🚀 Features - -- Wordpress on-demand SFTP -- Finalize on-demand sftp for wp -- PHP Composer support -- Working on-demand sftp to wp data -- Admin team sees everything -- Able to change service version/tag -- Basic white labeled version -- Able to modify database passwords - -### 🐛 Bug Fixes - -- Add openssl to image -- Permission issues -- On-demand sFTP for wp -- Fix for fix haha -- Do not pull latest image -- Updated db versions -- Only show proxy for admin team -- Team view for root team -- Do not trigger >1 webhooks on GitLab -- Possible fix for spikes in CPU usage -- Last commit -- Www or not-www, that's the question -- Fix for the fix that fixes the fix -- Ton of updates for users/teams -- Small typo -- Unique storage paths -- Self-hosted GitLab URL -- No line during buildLog -- Html/apiUrls cannot end with / -- Typo -- Missing buildpack - -### 💼 Other - -- Fix -- Better layout for root team -- Fix -- Fixes -- Fix -- Fix -- Fix -- Fix -- Fix -- Fix -- Fix -- Insane amount -- Fix -- Fixes -- Fixes -- Fix -- Fixes -- Fixes - -### 📚 Documentation - -- Contribution guide - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.3.3] - 2022-04-05 - -### 🐛 Bug Fixes - -- Add git lfs while deploying -- Try to update build status several times -- Update stucked builds -- Update stucked builds on startup -- Revert seed -- Lame fixing -- Remove asyncUntil - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.3.2] - 2022-04-04 - -### 🐛 Bug Fixes - -- *(php)* If .htaccess file found use apache -- Add default webhook domain for n8n - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.3.1] - 2022-04-04 - -### 🐛 Bug Fixes - -- Secrets build/runtime coudl be changed after save -- Default configuration - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.3.0] - 2022-04-04 - -### 🚀 Features - -- Initial python support -- Add loading on register button -- *(dev)* Allow windows users to use pnpm dev -- MeiliSearch service -- Add abilitry to paste env files - -### 🐛 Bug Fixes - -- Ignore coolify proxy error for now -- Python no wsgi -- If user not found -- Rename envs to secrets -- Infinite loop on www domains -- No need to paste clear text env for previews -- Build log fix attempt #1 -- Small UI fix on logs -- Lets await! -- Async progress -- Remove console.log -- Build log -- UI -- Gitlab & Github urls - -### 💼 Other - -- Improvements - -### ⚙️ Miscellaneous Tasks - - Version++ - Version++ -- Lock file + fix packages - -## [2.2.7] - 2022-04-01 - -### 🐛 Bug Fixes - -- Haproxy errors -- Build variables -- Use NodeJS for sveltekit for now - -## [2.2.6] - 2022-03-31 - -### 🐛 Bug Fixes - -- Add PROTO headers - -## [2.2.5] - 2022-03-31 - -### 🐛 Bug Fixes - -- Registration enabled/disabled - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.2.4] - 2022-03-31 - -### 🐛 Bug Fixes - -- Gitlab repo url -- No need to dashify anymore - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.2.3] - 2022-03-31 - -### 🐛 Bug Fixes - -- List ghost services -- Reload window on settings saved -- Persistent storage on webhooks -- Add license -- Space in repo names - -### ⚙️ Miscellaneous Tasks - - Version++ - Version++ - Version++ -- Fixed typo on New Git Source view - -## [2.2.0] - 2022-03-27 - -### 🚀 Features - -- Add n8n.io service -- Add update kuma service -- Ghost service - -### 🐛 Bug Fixes - -- Ghost logo size -- Ghost icon, remove console.log - -### 💼 Other - -- Colors on svelte-select - -### ⚙️ Miscellaneous Tasks - -- Version ++ - -## [2.1.1] - 2022-03-25 - -### 🐛 Bug Fixes - -- Cleanup only 2 hours+ old images - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.1.0] - 2022-03-23 - -### 🚀 Features - -- Use compose instead of normal docker cmd -- Be able to redeploy PRs - -### 🐛 Bug Fixes - -- Skip ssl cert in case of error -- Volumes - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.0.31] - 2022-03-20 - -### 🚀 Features - -- Add PHP modules - -### 🐛 Bug Fixes - -- Cleanup old builds -- Only cleanup same app -- Add nginx + htaccess files - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.0.30] - 2022-03-19 - -### 🐛 Bug Fixes - -- No cookie found -- Missing session data -- No error if GitSource is missing -- No webhook secret found? -- Basedir for dockerfiles -- Better queue system + more support on monorepos -- Remove build logs in case of app removed - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.0.29] - 2022-03-11 - -### 🚀 Features - -- Webhooks inititate all applications with the correct branch -- Check ssl for new apps/services first -- Autodeploy pause -- Install pnpm into docker image if pnpm lock file is used - -### 🐛 Bug Fixes - -- Personal Gitlab repos -- Autodeploy true by default for GH repos - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.0.28] - 2022-03-04 - -### 🚀 Features - -- Service secrets - -### 🐛 Bug Fixes - -- Do not error if proxy is not running - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.0.27] - 2022-03-02 - -### 🚀 Features - -- Send version with update request - -### 🐛 Bug Fixes - -- Check when a container is running -- Reload haproxy if new cert is added -- Cleanup coolify images -- Application state in UI - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.0.26] - 2022-03-02 - -### 🐛 Bug Fixes - -- Update process - -## [2.0.25] - 2022-03-02 - -### 🚀 Features - -- Languagetool service - -### 🐛 Bug Fixes - -- Reload proxy on ssl cert -- Volume name - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.0.24] - 2022-03-02 - -### 🐛 Bug Fixes - -- Better proxy check -- Ssl + sslrenew -- Null proxyhash on restart -- Reconfigure proxy on restart -- Update process - -## [2.0.23] - 2022-02-28 - -### 🐛 Bug Fixes - -- Be sure .env exists -- Missing fqdn for services -- Default npm command -- Add coolify-image label for build images -- Cleanup old images, > 3 days - -### 💼 Other - -- Colorful states -- Application start - -### ⚙️ Miscellaneous Tasks - - Version++ - -## [2.0.22] - 2022-02-27 - -### 🐛 Bug Fixes - -- Coolify image pulls -- Remove wrong/stuck proxy configurations -- Always use a buildpack -- Add icons for eleventy + astro -- Fix proxy every 10 secs -- Do not remove coolify proxy +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Add .pnpm-store in .gitignore +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Minor changes +- Minor changes +- Minor changes +- Whoops +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Update staging release +- Version++ +- Version++ +- Add jda icon for lavalink service +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Update version to 4.0.0-beta.275 +- Update DNS server validation helper text +- Dark mode should be the default +- Improve menu item styling and spacing in service configuration and index views +- Improve menu item styling and spacing in service configuration and index views +- Improve menu item styling and spacing in project index and show views +- Remove docker compose versions +- Add Listmonk service template and logo +- Refactor GetContainersStatus.php for improved readability and maintainability +- Refactor ApplicationDeploymentJob.php for improved readability and maintainability +- Add metrics and logs directories to installation script +- Update sentinel version to 0.0.2 in versions.json +- Update permissions on metrics and logs directories +- Comment out server sentinel check in ServerStatusJob +- Update version numbers to 4.0.0-beta.278 +- Update hover behavior and cursor style in scheduled task executions view +- Refactor scheduled task view to improve code readability and maintainability +- Skip scheduled tasks if application or service is not running +- Remove debug logging statements in Kernel.php +- Handle invalid cron strings in Kernel.php +- Refactor Service.php to handle missing admin user in extraFields() method +- Update twenty CRM template with environment variables and dependencies +- Refactor applications.php to remove unused imports and improve code readability +- Refactor deployment index.blade.php for improved readability and rollback handling +- Refactor GitHub app selection UI in project creation form +- Update ServerLimitCheckJob.php to handle missing serverLimit value +- Remove unnecessary code for saving commit message +- Update DOCKER_VERSION to 26.0 in install.sh script +- Update Docker and Docker Compose versions in Dockerfiles +- Update version numbers to 4.0.0-beta.279 +- Limit commit message length to 50 characters in ApplicationDeploymentJob +- Update version to 4.0.0-beta.283 +- Change pre and post deployment command length in applications table +- Refactor container name logic in GetContainersStatus.php and ForcePasswordReset.php +- Remove unnecessary content from Docker Compose file +- Update Sentry release version to 4.0.0-beta.287 +- Add Thompson Edolo as a sponsor +- Add null checks for team in Stripe webhook +- Update Sentry release version to 4.0.0-beta.288 +- Update for version 289 +- Fix formatting issue in deployment index.blade.php file +- Remove unnecessary wire:navigate attribute in breadcrumbs.blade.php +- Rename docker dirs +- Update laravel/socialite to version v5.14.0 and livewire/livewire to version 3.4.9 +- Update modal styles for better user experience +- Update deployment index.blade.php script for better performance +- Update version numbers to 4.0.0-beta.290 +- Update version numbers to 4.0.0-beta.291 +- Update version numbers to 4.0.0-beta.292 +- Update version numbers to 4.0.0-beta.293 +- Add upgrade guide link to upgrade.blade.php +- Improve upgrade.blade.php with clearer instructions and formatting +- Update version numbers to 4.0.0-beta.294 +- Add Lightspeed.run as a sponsor +- Update Dockerfile to install vim +- Update Dockerfile with latest versions of Docker, Docker Compose, Docker Buildx, Pack, and Nixpacks +- Update version numbers to 4.0.0-beta.295 +- Update supported OS list with almalinux +- Update install.sh to support PopOS +- Update install.sh script to version 1.3.2 and handle Linux Mint as Ubuntu +- Update page title in resource index view +- Update logo file path in logto.yaml +- Update logo file path in logto.yaml +- Remove commented out code for docker container removal +- Add isAnyDeploymentInprogress function to check if any deployments are in progress +- Add ApplicationDeploymentJob and pint.json +- Update version numbers to 4.0.0-beta.298 +- Switch to database sessions from redis +- Update dependencies and remove unused code +- Update tailwindcss and vue versions in package.json +- Update service template URL in constants.php +- Update sentinel version to 0.0.8 +- Update chart styling and loading text +- Update sentinel version to 0.0.9 +- Update Spanish translation for failed authentication messages +- Add portuguese traslation +- Add Turkish translations +- Add Vietnamese translate +- Add Treive logo to donations section +- Update README.md with latest release version badge +- Update latest release version badge in README.md +- Update version to 4.0.0-beta.299 +- Move server delete component to the bottom of the page +- Update version to 4.0.0-beta.301 +- Update version to 4.0.0-beta.302 +- Update version to 4.0.0-beta.303 +- Update version to 4.0.0-beta.305 +- Update version to 4.0.0-beta.306 +- Add log1x/laravel-webfonts package +- Update version to 4.0.0-beta.307 +- Refactor ServerStatusJob constructor formatting +- Update Monaco Editor for Docker Compose and Proxy Configuration +- More details +- Refactor shared.php helper functions +- Update Plausible docker compose template to Plausible 2.1.0 +- Update Plausible docker compose template to Plausible 2.1.0 +- Update livewire/livewire dependency to version 3.4.9 +- Refactor checkIfDomainIsAlreadyUsed function +- Update storage.blade.php view for livewire project service +- Update version to 4.0.0-beta.310 +- Update composer dependencies +- Add new logo for Latitude +- Bump version to 4.0.0-beta.311 +- Update version to 4.0.0-beta.315 +- Update version to 4.0.0-beta.316 +- Update bug report template +- Update repository form with simplified URL input field +- Update width of container in general.blade.php +- Update checkbox labels in general.blade.php +- Update general page of apps +- Handle JSON parsing errors in format_docker_command_output_to_json +- Update Traefik image version to v2.11 +- Update version to 4.0.0-beta.317 +- Update version to 4.0.0-beta.318 +- Update helper message with link to documentation +- Disable health check by default +- Remove commented out code for sending internal notification +- Update APP_BASE_URL to use SERVICE_FQDN_PLANE +- Update resource-limits.blade.php with improved input field helpers +- Update version numbers to 4.0.0-beta.319 +- Remove commented out code for docker image pruning +- Collect/create/update volumes in parseDockerComposeFile function +- Update version to 4.0.0-beta.320 +- Add pull_request image builds to GH actions +- Add comment explaining the purpose of disconnecting the network in cleanup_unused_network_from_coolify_proxy() +- Update formbricks template +- Update registration view to display a notice for first user that it will be an admin +- Update server form to use password input for IP Address/Domain field +- Update navbar to include service status check +- Update navbar and configuration to improve service status check functionality +- Update workflows to include PR build and merge manifest steps +- Update UpdateCoolifyJob timeout to 10 minutes +- Update UpdateCoolifyJob to dispatch CheckForUpdatesJob synchronously +- Update version to 4.0.0-beta.321 +- Update version to 4.0.0-beta.322 +- Update version to 4.0.0-beta.323 +- Update version to 4.0.0-beta.324 +- New compose parser with tests +- Update version to 1.3.4 in install.sh and 1.0.6 in upgrade.sh +- Update memory limit to 64MB in horizon configuration +- Update php packages +- Update axios npm dependency to version 1.7.5 +- Update Coolify version to 4.0.0-beta.324 and fix file paths in upgrade script +- Update Coolify version to 4.0.0-beta.324 +- Update Coolify version to 4.0.0-beta.325 +- Update Coolify version to 4.0.0-beta.326 +- Add cd command to change directory before removing .env file +- Update Coolify version to 4.0.0-beta.327 +- Update Coolify version to 4.0.0-beta.328 +- Update sponsor links in README.md +- Update version.json to versions.json in GitHub workflow +- Cleanup stucked resources and scheduled backups +- Update GitHub workflow to use versions.json instead of version.json +- Update GitHub workflow to use versions.json instead of version.json +- Update GitHub workflow to use versions.json instead of version.json +- Update GitHub workflow to use jq container for version extraction +- Update GitHub workflow to use jq container for version extraction +- Update UI for displaying no executions found in scheduled task list +- Update UI for displaying deployment status in deployment list +- Update UI for displaying deployment status in deployment list +- Ignore unnecessary files in production build workflow +- Update server form layout and settings +- Update Dockerfile with latest versions of PACK and NIXPACKS +- Update coolify-helper.yml to get version from versions.json +- Disable Ray by default +- Enable Ray by default and update Dockerfile with latest versions of PACK and NIXPACKS +- Update Ray configuration and Dockerfile +- Add middleware for updating environment variables by UUID in `api.php` routes +- Expose port 3000 in browserless.yaml template +- Update Ray configuration and Dockerfile +- Update coolify version to 4.0.0-beta.331 +- Update versions.json and sentry.php to 4.0.0-beta.332 +- Update version to 4.0.0-beta.332 +- Update DATABASE_URL in plunk.yaml to use plunk database +- Add coolify.managed=true label to Docker image builds +- Update docker image pruning command to exclude managed images +- Update docker cleanup schedule to run daily at midnight +- Update versions.json to version 1.0.1 +- Update coolify-helper.yml to include "next" branch in push trigger +- Set timeout for ServerCheckJob to 60 seconds +- Update appwrite.yaml to include OpenSSL key variable assignment +- Update version numbers to 4.0.0-beta.333 +- Copy .env file to .env-{DATE} if it exists +- Update .env file with new values +- Update server check job middleware to use server ID instead of UUID +- Add reminder to backup .env file before running install script again +- Copy .env file to backup location during installation script +- Add reminder to backup .env file during installation script +- Update permissions in pr-build.yml and version numbers +- Add minio/mc command to Dockerfile +- Remove itsgoingd/clockwork from require-dev in composer.json +- Update 'key' value of gitlab in Service.php to use environment variable +- Update release version to 4.0.0-beta.335 +- Update constants.ssh.mux_enabled in remoteProcess.php +- Update listeners and proxy settings in server form and new server components +- Remove unnecessary null check for proxy_type in generate_default_proxy_configuration +- Remove unnecessary SSH command execution time logging +- Update release version to 4.0.0-beta.336 +- Update coolify environment variable assignment with double quotes +- Update shared.php to fix issues with source and network variables +- Update terminal styling for better readability +- Update button text for container connection form +- Update Dockerfile and workflow for Coolify Realtime (v4) +- Remove unused entrypoint script and update volume mapping +- Update .env file and docker-compose configuration +- Update APP_NAME environment variable in docker-compose.prod.yml +- Update WebSocket URL in terminal.blade.php +- Update Dockerfile and workflow for Coolify Realtime (v4) +- Update Dockerfile and workflow for Coolify Realtime (v4) +- Update Dockerfile and workflow for Coolify Realtime (v4) +- Rename Command Center to Terminal in code and views +- Update branch restriction for push event in coolify-helper.yml +- Update terminal button text and layout in application heading view +- Refactor terminal component and select form layout +- Update coolify nightly version to 4.0.0-beta.335 +- Update helper version to 1.0.1 +- Fix syntax error in versions.json +- Update version numbers to 4.0.0-beta.337 +- Update Coolify installer and scripts to include a function for fetching programming jokes +- Update docker network connection command in ApplicationDeploymentJob.php +- Add validation to prevent selecting 'default' server or container in RunCommand.php +- Update versions.json to reflect latest version of realtime container +- Update soketi image to version 1.0.1 +- Nightly - Update soketi image to version 1.0.1 and versions.json to reflect latest version of realtime container +- Update version numbers to 4.0.0-beta.339 +- Update version numbers to 4.0.0-beta.340 +- Update version numbers to 4.0.0-beta.341 +- Update version numbers to 4.0.0-beta.342 +- Update remove-labels-and-assignees-on-close.yml +- Add SSH key for localhost in ProductionSeeder +- Update SSH key generation in install.sh script +- Update ProductionSeeder to call OauthSettingSeeder and PopulateSshKeysDirectorySeeder +- Update install.sh to support Asahi Linux +- Update install.sh version to 1.6 +- Remove unused middleware and uniqueId method in DockerCleanupJob +- Refactor DockerCleanupJob to remove unused middleware and uniqueId method +- Remove unused migration file for populating SSH keys and clearing mux directory +- Add modified files to the commit +- Refactor pre-commit hook to improve performance and readability +- Update CONTRIBUTING.md with troubleshooting note about database migrations +- Refactor pre-commit hook to improve performance and readability +- Update cleanup command to use Redis instead of queue +- Update Docker commands to start proxy +- Update version numbers to 4.0.0-beta.343 +- Update version numbers to 4.0.0-beta.344 +- Update version numbers to 4.0.0-beta.345 +- Update version numbers to 4.0.0-beta.346 +- Add autocomplete attribute to input fields +- Refactor API Tokens component to use isApiEnabled flag +- Update versions.json file +- Remove unused .env.development.example file +- Update API Tokens view to include link to Settings menu +- Update web.php to cast server port as integer +- Update backup deletion labels to use language files +- Update database startup heading title +- Update database startup heading title +- Custom vite envs +- Update version numbers to 4.0.0-beta.348 +- Refactor code to improve SSH key handling and storage +- Update Mailpit logo to use SVG format +- Fix docs link in running state +- Update Coolify Realtime workflow to only trigger on the main branch +- Refactor instanceSettings() function to improve code readability +- Update Coolify Realtime image to version 1.0.2 +- Remove unnecessary code in DatabaseBackupJob.php +- Add "Not Usable" indicator for storage items +- Refactor instanceSettings() function and improve code readability +- Update version numbers to 4.0.0-beta.349 and 4.0.0-beta.350 +- Update version numbers to 4.0.0-beta.350 in configuration files +- Update command signature and description for cleanup application deployment queue +- Add missing import for Attribute class in ApplicationDeploymentQueue model +- Update modal input in server form to prevent closing on outside click +- Remove unnecessary command from SshMultiplexingHelper +- Remove commented out code for uploading to S3 in DatabaseBackupJob +- Update soketi service image to version 1.0.3 +- Update version to 4.0.0-beta.352 +- Refactor DatabaseBackupJob to handle missing team +- Update version to 4.0.0-beta.353 +- Update service application view +- Update version to 4.0.0-beta.354 +- Remove debug statement in Service model +- Remove commented code in Server model +- Fix application deployment queue filter logic +- Refactor modal-confirmation component +- Update it-tools service template and port configuration +- Update homarr service template and remove unnecessary code +- Update homarr service template and remove unnecessary code +- Update version to 4.0.0-beta.355 +- Update version to 4.0.0-beta.356 +- Remove commented code for shared variable type validation +- Update MariaDB image to version 11 and fix service environment variable orders +- Update anythingllm.yaml volumes configuration +- Update proxy configuration paths for Caddy and Nginx in dev +- Update password form submission in modal-confirmation component +- Update project query to order by name in uppercase +- Update project query to order by name in lowercase +- Update select.blade.php with improved search functionality +- Add Nitropage service template and logo +- Bump coolify-helper version to 1.0.2 +- Refactor loadServices2 method and remove unused code +- Update version to 4.0.0-beta.357 +- Update service names and volumes in windmill.yaml +- Update version to 4.0.0-beta.358 +- Ignore .ignition.json files in Docker and Git +- Add mattermost logo as svg +- Add mattermost svg to compose +- Update version to 4.0.0-beta.357 +- Fix form submission and keydown event handling in modal-confirmation.blade.php +- Update version numbers to 4.0.0-beta.359 in configuration files +- Disable adding default environment variables in shared.php +- Update laravel/horizon dependency to version 5.29.1 +- Update service extra fields to use dynamic keys +- Update livewire/livewire dependency to version 3.4.9 +- Add transmission template desc +- Update transmission docs link +- Update version numbers to 4.0.0-beta.360 in configuration files +- Update AWS environment variable names in unsend.yaml +- Update AWS environment variable names in unsend.yaml +- Update livewire/livewire dependency to version 3.4.9 +- Update version to 4.0.0-beta.361 +- Update Docker build and push actions to v6 +- Update Docker build and push actions to v6 +- Update Docker build and push actions to v6 +- Sync coolify-helper to dockerhub as well +- Push realtime to dockerhub +- Sync coolify-realtime to dockerhub +- Rename workflows +- Rename development to staging build +- Sync coolify-testing-host to dockerhbu +- Sync coolify prod image to dockerhub as well +- Update Docker version to 26.0 +- Update project resource index page +- Update project service configuration view +- Edit www helper +- Update dep +- Regenerate openapi spec +- Composer dep bump +- Dep bump +- Upgrade cloudflared and minio +- Remove comments and improve DB column naming +- Remove unused seeder +- Remove unused waitlist stuff +- Remove wired.php (not used anymore) +- Remove unused resale license job +- Remove commented out internal notification +- Remove more waitlist stuff +- Remove commented out notification +- Remove more waitlist stuff +- Remove unused code +- Fix typo +- Remove comment out code +- Some reordering +- Remove resale license reference +- Remove functions from shared.php +- Public settings for email notification +- Remove waitlist redirect +- Remove log +- Use new notification trait +- Remove unused route +- Remove unused email component +- Comment status changes as it is disabled for now +- Bump dep +- Reorder navbar +- Rename topicID to threadId like in the telegram API response +- Update PHP configuration to set memory limit using environment variable +- Regenerate API spec, removing notification fields +- Remove ray debugging +- Version ++ +- Improve Penpot healthchecks +- Switch up readonly lables to make more sense +- Remove unused computed fields +- Use the new job dispatch +- Disable volume data cloning for now +- Improve code +- Lowcoder service naming +- Use new functions +- Improve error styling +- Css +- More css as it still looks like shit +- Final css touches +- Ajust time to 50s (tests done) +- Remove debug log, finally found it +- Remove more logging +- Remove limit on commit message +- Remove dayjs +- Remove unused code and fix import +- *(dep)* Bump nixpacks version +- *(dep)* Version++ +- *(dep)* Bump helper version to 1.0.5 +- *(docker)* Add blank line for readability in Dockerfile +- *(versions)* Update coolify versions to v4.0.0-beta.388 +- *(versions)* Update coolify versions to v4.0.0-beta.389 and add helper version retrieval script +- *(versions)* Update coolify versions to v4.0.0-beta.389 +- *(core)* EnvironmentVariable Model now extends BaseModel to remove duplicated code +- *(versions)* Update coolify versions to v4.0.0-beta.3909 +- *(version)* Bump Coolify version to 4.0.0-beta.391 +- *(config)* Increase default PHP memory limit to 256M +- Add openapi response +- *(workflows)* Make naming more clear and remove unused code +- Bump Coolify version to 4.0.0-beta.392/393 +- *(ci)* Update changelog generation workflow to target 'next' branch +- *(ci)* Update changelog generation workflow to target main branch +- Rollback Coolify version to 4.0.0-beta.392 +- Bump Coolify version to 4.0.0-beta.393 +- Bump Coolify version to 4.0.0-beta.394 +- Bump Coolify version to 4.0.0-beta.395 +- Bump Coolify version to 4.0.0-beta.396 +- *(services)* Update zipline to use new Database env var. (#5210) +- *(service)* Upgrade authentik service +- *(service)* Remove unused env from zipline +- Bump helper and realtime version +- *(migration)* Remove unused columns +- *(ssl)* Improve code in ssl helper +- *(migration)* Ssl cert and key should not be nullable +- *(ssl)* Rename CA cert to `coolify-ca.crt` because of conflicts +- Rename ca crt folder to ssl +- *(ui)* Improve valid until handling +- Improve code quality suggested by code rabbit +- *(supabase)* Update Supabase service template and Postgres image version +- *(versions)* Update version numbers for coolify and nightly +- *(versions)* Update version numbers for coolify and nightly +- *(service)* Update minecraft service ENVs +- *(service)* Add more vars to infisical.yaml (#5418) +- *(service)* Add google variables to plausible.yaml (#5429) +- *(service)* Update authentik.yaml versions (#5373) +- *(core)* Remove redocs +- *(versions)* Update coolify version numbers to 4.0.0-beta.403 and 4.0.0-beta.404 +- *(service)* Remove unused code in Bugsink service +- *(versions)* Update version to 404 +- *(versions)* Bump version to 403 (#5520) +- *(versions)* Bump version to 404 +- *(versions)* Bump version to 406 +- *(versions)* Bump version to 407 +- *(versions)* Bump version to 406 +- *(versions)* Bump version to 407 and 408 for coolify and nightly +- *(versions)* Bump version to 408 for coolify and 409 for nightly +- *(versions)* Update nightly version to 4.0.0-beta.410 +- *(pre-commit)* Remove OpenAPI generation command from pre-commit hook +- *(versions)* Update realtime version to 1.0.7 and bump dependencies in package.json +- *(versions)* Bump coolify version to 4.0.0-beta.409 in configuration files +- *(versions)* Bump coolify version to 4.0.0-beta.410 and update nightly version to 4.0.0-beta.411 in configuration files +- *(templates)* Update plausible and clickhouse images to latest versions and remove mail service +- *(versions)* Update coolify version to 4.0.0-beta.411 and nightly version to 4.0.0-beta.412 in configuration files +- *(versions)* Update coolify version to 4.0.0-beta.412 and nightly version to 4.0.0-beta.413 in configuration files +- *(versions)* Update coolify version to 4.0.0-beta.413 and nightly version to 4.0.0-beta.414 in configuration files +- *(versions)* Update realtime version to 1.0.8 in versions.json +- *(versions)* Update realtime version to 1.0.8 in versions.json +- *(docker)* Update soketi image version to 1.0.8 in production configuration files +- *(versions)* Update coolify version to 4.0.0-beta.414 and nightly version to 4.0.0-beta.415 in configuration files +- *(workflows)* Adjust workflow for announcement +- *(versions)* Update coolify version to 4.0.0-beta.416 and nightly version to 4.0.0-beta.417 in configuration files; fix links in deployment view +- *(seeder)* Update git branch from 'main' to 'v4.x' for multiple examples in ApplicationSeeder +- *(versions)* Update coolify version to 4.0.0-beta.417 and nightly version to 4.0.0-beta.418 +- *(versions)* Update coolify version to 4.0.0-beta.418 +- *(versions)* Update coolify version to 4.0.0-beta.419 and nightly version to 4.0.0-beta.420 in configuration files +- *(service)* Rename hoarder server to karakeep (#5607) +- *(service)* Update Supabase services (#5708) +- *(service)* Remove unused documenso env +- *(service)* Formatting and cleanup of ryot +- *(docs)* Remove changelog and add it to gitignore +- *(versions)* Update version to 4.0.0-beta.419 +- *(service)* Diun formatting +- *(docs)* Update CHANGELOG.md +- *(service)* Switch convex vars +- *(service)* Pgbackweb formatting and naming update +- *(service)* Remove typesense default API key +- *(service)* Format yamtrack healthcheck +- *(core)* Remove unused function +- *(ui)* Remove unused stopEvent code +- *(service)* Remove unused env +- *(tests)* Update test environment database name and add new feature test for converting container environment variables to array +- *(service)* Update Immich service (#5886) +- *(service)* Remove unused logo +- *(api)* Update API docs +- *(dependencies)* Update package versions in composer.json and composer.lock for improved compatibility and performance +- *(dependencies)* Update package versions in package.json and package-lock.json for improved stability and features +- *(version)* Update coolify-realtime to version 1.0.9 in docker-compose and versions files +- *(version)* Update coolify version to 4.0.0-beta.420 and nightly version to 4.0.0-beta.421 +- *(service)* Changedetection remove unused code +- *(service)* Update Evolution API image to the official one (#6031) +- *(versions)* Bump coolify versions to v4.0.0-beta.420 and v4.0.0-beta.421 +- *(dependencies)* Update composer dependencies to latest versions including resend-laravel to ^0.19.0 and aws-sdk-php to 3.347.0 +- *(versions)* Update Coolify version to 4.0.0-beta.420.1 and add new services (karakeep, miniflux, pingvinshare) to service templates +- *(versions)* Update Coolify versions to 4.0.0-beta.420.2 and 4.0.0-beta.420.3 in multiple files +- *(versions)* Bump coolify and nightly versions to 4.0.0-beta.420.3 and 4.0.0-beta.420.4 respectively +- *(versions)* Update coolify and nightly versions to 4.0.0-beta.420.4 and 4.0.0-beta.420.5 respectively +- *(service)* Update Nitropage template (#6181) +- *(versions)* Update all version +- *(bump)* Update composer deps +- *(version)* Bump Coolify version to 4.0.0-beta.420.6 +- *(service)* Improve matrix service +- *(service)* Format runner service +- *(service)* Improve sequin +- *(service)* Add `NOT_SECURED` env to Postiz (#6243) +- *(service)* Improve evolution-api environment variables (#6283) +- *(service)* Update Langfuse template to v3 (#6301) +- *(core)* Remove unused argument +- *(deletion)* Rename isDeleteOperation to deleteConnectedNetworks +- *(docker)* Remove unused arguments on StopService +- *(service)* Homebox formatting +- Clarify usage of custom redis configuration (#6321) +- *(changelogs)* Add .gitignore for changelogs directory and remove outdated changelog files for May, June, and July 2025 +- *(service)* Change affine images (#6366) +- Elasticsearch URL, fromatting and add category +- Update service-templates json files +- *(docs)* Remove AGENTS.md file; enhance CLAUDE.md with detailed form authorization patterns and service configuration examples +- *(cleanup)* Remove unused GitLab view files for change, new, and show pages +- *(workflows)* Add backlog directory to build triggers for production and staging workflows +- *(config)* Disable auto_commit in backlog configuration to prevent automatic commits +- *(versions)* Update coolify version to 4.0.0-beta.420.8 and nightly version to 4.0.0-beta.420.9 in versions.json and constants.php +- *(docker)* Update soketi image version to 1.0.10 in production and Windows configurations +- *(core)* Update version +- *(core)* Update version +- *(versions)* Update coolify version to 4.0.0-beta.421 and nightly version to 4.0.0-beta.422 - Update version +- Update development node version +- Update coolify version to 4.0.0-beta.423 and nightly version to 4.0.0-beta.424 +- Update coolify version to 4.0.0-beta.424 and nightly version to 4.0.0-beta.425 +- Update coolify version to 4.0.0-beta.425 and nightly version to 4.0.0-beta.426 +- Update coolify version to 4.0.0-beta.426 and nightly version to 4.0.0-beta.427 -### 💼 Other - -- Remote docker engine - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.21] - 2022-02-24 - -### 🚀 Features - -- Random subdomain for demo -- Random domain for services -- Astro buildpack -- 11ty buildpack -- Registration page - -### 🐛 Bug Fixes - -- Http for demo, oops -- Docker scanner -- Improvement on image pulls - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.20] - 2022-02-23 - -### 🐛 Bug Fixes - -- Revert default network - -### 💼 Other - -- Dns check - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.19] - 2022-02-23 - -### 🐛 Bug Fixes - -- Random network name for demo -- Settings fqdn grr - -## [2.0.18] - 2022-02-22 - -### 🚀 Features - -- Ports range - -### 🐛 Bug Fixes - -- Email is lowercased in login -- Lowercase email everywhere -- Use normal docker-compose in dev - -### 💼 Other - -- Make copy/password visible - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.17] - 2022-02-21 - -### 🐛 Bug Fixes - -- Move tokens from session to cookie/store - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.14] - 2022-02-18 - -### 🚀 Features - -- Basic password reset form -- Scan for lock files and set right commands -- Public port range (WIP) - -### 🐛 Bug Fixes - -- SSL app off -- Local docker host -- Typo -- Lets encrypt -- Remove SSL with stop -- SSL off for services -- Grr -- Running state css -- Minor fixes -- Remove force SSL when doing let's encrypt request -- GhToken in session now -- Random port for certbot -- Follow icon -- Plausible volume fixed -- Database connection strings -- Gitlab webhooks fixed -- If DNS not found, do not redirect -- Github token - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version ++ - -## [2.0.13] - 2022-02-17 - -### 🐛 Bug Fixes - -- Login issues - -## [2.0.11] - 2022-02-15 - -### 🚀 Features - -- Follow logs -- Generate www & non-www SSL certs - -### 🐛 Bug Fixes - -- Window error in SSR -- GitHub sync PR's -- Load more button -- Small fixes -- Typo -- Error with follow logs -- IsDomainConfigured -- TransactionIds -- Coolify image cleanup -- Cleanup every 10 mins -- Cleanup images -- Add no user redis to uri -- Secure cookie disabled by default -- Buggy svelte-kit-cookie-session - -### 💼 Other - -- Only allow cleanup in production - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ - -## [2.0.10] - 2022-02-15 - -### 🐛 Bug Fixes - -- Typo -- Error handling -- Stopping service without proxy -- Coolify proxy start - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.8] - 2022-02-14 - -### 🐛 Bug Fixes - -- Validate secrets -- Truncate git clone errors -- Branch used does not throw error - -## [2.0.7] - 2022-02-13 - -### 🚀 Features - -- Www <-> non-www redirection for apps -- Www <-> non-www redirection - -### 🐛 Bug Fixes - -- Package.json -- Build secrets should be visible in runtime -- New secret should have default values - -## [2.0.5] - 2022-02-11 - -### 🚀 Features - -- VaultWarden service - -### 🐛 Bug Fixes - -- PreventDefault on a button, thats all -- Haproxy check should not throw error -- Delete all build files -- Cleanup images -- More error handling in proxy configuration + cleanups -- Local static assets -- Check sentry -- Typo - -### ⚙️ Miscellaneous Tasks - -- Version -- Version - -## [2.0.4] - 2022-02-11 - -### 🚀 Features - -- Use tags in update -- New update process (#115) - -### 🐛 Bug Fixes - -- Docker Engine bug related to live-restore and IPs -- Version - -## [2.0.3] - 2022-02-10 - -### 🐛 Bug Fixes - -- Capture non-error as error -- Only delete id.rsa in case of it exists -- Status is not available yet - -### ⚙️ Miscellaneous Tasks - -- Version bump - -## [2.0.2] - 2022-02-10 - -### 🐛 Bug Fixes - -- Secrets join -- ENV variables set differently +### ◀️ Revert -## [1.0.0] - 2021-03-24 +- Show usage everytime +- Revert: revert +- Wip +- Variable parsing +- Hc return code check +- Instancesettings +- Pull policy +- Advanced dropdown +- Databasebackup +- Remove Cloudflare async tag attributes +- Encrypting mount and fs_path +- *(parser)* Enhance FQDN generation logic for services and applications diff --git a/CLAUDE.md b/CLAUDE.md index 22e7621828..34149d28ad 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,10 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to **Claude Code** (claude.ai/code) when working with code in this repository. + +> **Note for AI Assistants**: This file is specifically for Claude Code. If you're using Cursor IDE, refer to the `.cursor/rules/` directory for detailed rule files. Both systems share core principles but are optimized for their respective workflows. +> +> **Maintaining Instructions**: When updating AI instructions, see [.AI_INSTRUCTIONS_SYNC.md](.AI_INSTRUCTIONS_SYNC.md) for synchronization guidelines between CLAUDE.md and .cursor/rules/. ## Project Overview @@ -23,7 +27,14 @@ Only run artisan commands inside "coolify" container when in development. ### Code Quality - `./vendor/bin/pint` - Run Laravel Pint for code formatting - `./vendor/bin/phpstan` - Run PHPStan for static analysis -- `./vendor/bin/pest` - Run Pest tests +- `./vendor/bin/pest` - Run Pest tests (unit tests only, without database) + +### Running Tests +**IMPORTANT**: Tests that require database connections MUST be run inside the Docker container: +- **Inside Docker**: `docker exec coolify php artisan test` (for feature tests requiring database) +- **Outside Docker**: `./vendor/bin/pest tests/Unit` (for pure unit tests without database dependencies) +- Unit tests should use mocking and avoid database connections +- Feature tests that require database must be run in the `coolify` container ## Architecture Overview @@ -149,6 +160,7 @@ class MyComponent extends Component - Use database transactions for critical operations - Leverage query scopes for reusable queries - Apply indexes for performance-critical queries +- **CRITICAL**: When adding new database columns, ALWAYS update the model's `$fillable` array to allow mass assignment ### Security Best Practices - **Authentication**: Multi-provider auth via Laravel Fortify & Sanctum @@ -173,6 +185,21 @@ class MyComponent extends Component - **Mocking**: Use Laravel's built-in mocking for external services - **Database**: Use RefreshDatabase trait for test isolation +#### Test Execution Environment +**CRITICAL**: Database-dependent tests MUST run inside Docker container: +- **Unit Tests** (`tests/Unit/`): Should NOT use database. Use mocking. Run with `./vendor/bin/pest tests/Unit` +- **Feature Tests** (`tests/Feature/`): May use database. MUST run inside Docker with `docker exec coolify php artisan test` +- If a test needs database (factories, migrations, etc.), it belongs in `tests/Feature/` +- Always mock external services and SSH connections in tests + +#### Test Design Philosophy +**PREFER MOCKING**: When designing features and writing tests: +- **Design for testability**: Structure code so it can be tested without database (use dependency injection, interfaces) +- **Mock by default**: Unit tests should mock models and external dependencies using Mockery +- **Avoid database when possible**: If you can test the logic without database, write it as a Unit test +- **Only use database when necessary**: Feature tests should test integration points, not isolated logic +- **Example**: Instead of `Server::factory()->create()`, use `Mockery::mock('App\Models\Server')` in unit tests + ### Routing Conventions - Group routes by middleware and prefix - Use route model binding for cleaner controllers @@ -228,7 +255,9 @@ When developing features: ## Additional Documentation -For more detailed guidelines and patterns, refer to the `.cursor/rules/` directory: +This file contains high-level guidelines for Claude Code. For **more detailed, topic-specific documentation**, refer to the `.cursor/rules/` directory (also accessible by Cursor IDE and other AI assistants): + +> **Cross-Reference**: The `.cursor/rules/` directory contains comprehensive, detailed documentation organized by topic. Start with [.cursor/rules/README.mdc](.cursor/rules/README.mdc) for an overview, then explore specific topics below. ### Architecture & Patterns - [Application Architecture](.cursor/rules/application-architecture.mdc) - Detailed application structure @@ -434,7 +463,7 @@ protected function isAccessible(User $user, ?string $path = null): bool ### Database - When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost. -- Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. +- Laravel 12 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. ### Models - Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. @@ -543,6 +572,10 @@ document.addEventListener('livewire:init', function () { - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - Tests should test all of the happy paths, failure paths, and weird paths. - Tests live in the `tests/Feature` and `tests/Unit` directories. +- **Unit tests** MUST use mocking and avoid database. They can run outside Docker. +- **Feature tests** can use database but MUST run inside Docker container. +- **Design for testability**: Structure code to be testable without database when possible. Use dependency injection and interfaces. +- **Mock by default**: Prefer `Mockery::mock()` over `Model::factory()->create()` in unit tests. - Pest tests look and behave like this: it('is true', function () { @@ -551,11 +584,23 @@ it('is true', function () { ### Running Tests -- Run the minimal number of tests using an appropriate filter before finalizing code edits. -- To run all tests: `php artisan test`. -- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file). -- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. +**IMPORTANT**: Always run tests in the correct environment based on database dependencies: + +**Unit Tests (no database):** +- Run outside Docker: `./vendor/bin/pest tests/Unit` +- Run specific file: `./vendor/bin/pest tests/Unit/ProxyCustomCommandsTest.php` +- These tests use mocking and don't require PostgreSQL + +**Feature Tests (with database):** +- Run inside Docker: `docker exec coolify php artisan test` +- Run specific file: `docker exec coolify php artisan test tests/Feature/ExampleTest.php` +- Filter by name: `docker exec coolify php artisan test --filter=testName` +- These tests require PostgreSQL and use factories/migrations + +**General Guidelines:** +- Run the minimal number of tests using an appropriate filter before finalizing code edits +- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite +- If you get database connection errors, you're running a Feature test outside Docker - move it inside ### Pest Assertions - When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.: @@ -650,7 +695,12 @@ it('has emails', function (string $email) { ## Test Enforcement - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test` with a specific filename or filter. +- Run the minimum number of tests needed to ensure code quality and speed. +- **For Unit tests**: Use `./vendor/bin/pest tests/Unit/YourTest.php` (runs outside Docker) +- **For Feature tests**: Use `docker exec coolify php artisan test --filter=YourTest` (runs inside Docker) +- Choose the correct test type based on database dependency: + - No database needed? → Unit test with mocking + - Database needed? → Feature test in Docker diff --git a/README.md b/README.md index 1c88f4c546..9be4130c21 100644 --- a/README.md +++ b/README.md @@ -53,40 +53,40 @@ Thank you so much! ## Big Sponsors -* [CubePath](https://cubepath.com/?ref=coolify.io) - Dedicated Servers & Instant Deploy -* [GlueOps](https://www.glueops.dev?ref=coolify.io) - DevOps automation and infrastructure management +* [23M](https://23m.com?ref=coolify.io) - Your experts for high-availability hosting solutions! * [Algora](https://algora.io?ref=coolify.io) - Open source contribution platform -* [Ubicloud](https://www.ubicloud.com?ref=coolify.io) - Open source cloud infrastructure platform -* [LiquidWeb](https://liquidweb.com?ref=coolify.io) - Premium managed hosting solutions -* [Convex](https://convex.link/coolify.io) - Open-source reactive database for web app developers +* [American Cloud](https://americancloud.com?ref=coolify.io) - US-based cloud infrastructure services * [Arcjet](https://arcjet.com?ref=coolify.io) - Advanced web security and performance solutions -* [SaasyKit](https://saasykit.com?ref=coolify.io) - Complete SaaS starter kit for developers -* [SupaGuide](https://supa.guide?ref=coolify.io) - Your comprehensive guide to Supabase -* [Logto](https://logto.io?ref=coolify.io) - The better identity infrastructure for developers -* [Trieve](https://trieve.ai?ref=coolify.io) - AI-powered search and analytics -* [Supadata AI](https://supadata.ai/?ref=coolify.io) - Scrape YouTube, web, and files. Get AI-ready, clean data +* [BC Direct](https://bc.direct?ref=coolify.io) - Your trusted technology consulting partner +* [Blacksmith](https://blacksmith.sh?ref=coolify.io) - Infrastructure automation platform +* [Brand.dev](https://brand.dev?ref=coolify.io) - API to personalize your product with logos, colors, and company info from any domain +* [ByteBase](https://www.bytebase.com?ref=coolify.io) - Database CI/CD and Security at Scale +* [CodeRabbit](https://coderabbit.ai?ref=coolify.io) - Cut Code Review Time & Bugs in Half +* [COMIT](https://comit.international?ref=coolify.io) - New York Times award–winning contractor +* [CompAI](https://www.trycomp.ai?ref=coolify.io) - Open source compliance automation platform +* [Convex](https://convex.link/coolify.io) - Open-source reactive database for web app developers +* [CubePath](https://cubepath.com/?ref=coolify.io) - Dedicated Servers & Instant Deploy * [Darweb](https://darweb.nl/?ref=coolify.io) - Design. Develop. Deliver. Specialized in 3D CPQ Solutions +* [Formbricks](https://formbricks.com?ref=coolify.io) - The open source feedback platform +* [GoldenVM](https://billing.goldenvm.com?ref=coolify.io) - Premium virtual machine hosting solutions +* [Gozunga](https://gozunga.com?ref=coolify.io) - Seriously Simple Cloud Infrastructure * [Hetzner](http://htznr.li/CoolifyXHetzner) - Server, cloud, hosting, and data center solutions -* [COMIT](https://comit.international?ref=coolify.io) - New York Times award–winning contractor -* [Blacksmith](https://blacksmith.sh?ref=coolify.io) - Infrastructure automation platform -* [WZ-IT](https://wz-it.com/?ref=coolify.io) - German agency for customised cloud solutions -* [BC Direct](https://bc.direct?ref=coolify.io) - Your trusted technology consulting partner -* [Tigris](https://www.tigrisdata.com?ref=coolify.io) - Modern developer data platform * [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - Web hosting and VPS solutions -* [QuantCDN](https://www.quantcdn.io?ref=coolify.io) - Enterprise-grade content delivery network -* [PFGLabs](https://pfglabs.com?ref=coolify.io) - Build Real Projects with Golang * [JobsCollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - 30,000+ remote jobs for developers * [Juxtdigital](https://juxtdigital.com?ref=coolify.io) - Digital PR & AI Authority Building Agency -* [Cloudify.ro](https://cloudify.ro?ref=coolify.io) - Cloud hosting solutions -* [CodeRabbit](https://coderabbit.ai?ref=coolify.io) - Cut Code Review Time & Bugs in Half -* [American Cloud](https://americancloud.com?ref=coolify.io) - US-based cloud infrastructure services -* [MassiveGrid](https://massivegrid.com?ref=coolify.io) - Enterprise cloud hosting solutions +* [LiquidWeb](https://liquidweb.com?ref=coolify.io) - Premium managed hosting solutions +* [Logto](https://logto.io?ref=coolify.io) - The better identity infrastructure for developers +* [Macarne](https://macarne.com?ref=coolify.io) - Best IP Transit & Carrier Ethernet Solutions for Simplified Network Connectivity +* [Mobb](https://vibe.mobb.ai/?ref=coolify.io) - Secure Your AI-Generated Code to Unlock Dev Productivity +* [PFGLabs](https://pfglabs.com?ref=coolify.io) - Build Real Projects with Golang +* [Ramnode](https://ramnode.com/?ref=coolify.io) - High Performance Cloud VPS Hosting +* [SaasyKit](https://saasykit.com?ref=coolify.io) - Complete SaaS starter kit for developers +* [SupaGuide](https://supa.guide?ref=coolify.io) - Your comprehensive guide to Supabase +* [Supadata AI](https://supadata.ai/?ref=coolify.io) - Scrape YouTube, web, and files. Get AI-ready, clean data * [Syntax.fm](https://syntax.fm?ref=coolify.io) - Podcast for web developers +* [Tigris](https://www.tigrisdata.com?ref=coolify.io) - Modern developer data platform * [Tolgee](https://tolgee.io?ref=coolify.io) - The open source localization platform -* [CompAI](https://www.trycomp.ai?ref=coolify.io) - Open source compliance automation platform -* [GoldenVM](https://billing.goldenvm.com?ref=coolify.io) - Premium virtual machine hosting solutions -* [Gozunga](https://gozunga.com?ref=coolify.io) - Seriously Simple Cloud Infrastructure -* [Macarne](https://macarne.com?ref=coolify.io) - Best IP Transit & Carrier Ethernet Solutions for Simplified Network Connectivity +* [Ubicloud](https://www.ubicloud.com?ref=coolify.io) - Open source cloud infrastructure platform ## Small Sponsors diff --git a/app/Actions/Database/StartClickhouse.php b/app/Actions/Database/StartClickhouse.php index f218fcabb0..38f6d7bc83 100644 --- a/app/Actions/Database/StartClickhouse.php +++ b/app/Actions/Database/StartClickhouse.php @@ -105,6 +105,8 @@ public function handle(StandaloneClickhouse $database) $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; + $this->commands[] = "docker stop --timeout=10 $container_name 2>/dev/null || true"; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; diff --git a/app/Actions/Database/StartDragonfly.php b/app/Actions/Database/StartDragonfly.php index 38ad99d2e6..300221d249 100644 --- a/app/Actions/Database/StartDragonfly.php +++ b/app/Actions/Database/StartDragonfly.php @@ -55,11 +55,11 @@ public function handle(StandaloneDragonfly $database) $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; $server = $this->database->destination->server; - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); if (! $caCert) { $server->generateCaCertificate(); - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); } if (! $caCert) { @@ -192,6 +192,8 @@ public function handle(StandaloneDragonfly $database) if ($this->database->enable_ssl) { $this->commands[] = "chown -R 999:999 $this->configuration_dir/ssl/server.key $this->configuration_dir/ssl/server.crt"; } + $this->commands[] = "docker stop --timeout=10 $container_name 2>/dev/null || true"; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; diff --git a/app/Actions/Database/StartKeydb.php b/app/Actions/Database/StartKeydb.php index 59bcd4123b..3a2ceebb36 100644 --- a/app/Actions/Database/StartKeydb.php +++ b/app/Actions/Database/StartKeydb.php @@ -56,11 +56,11 @@ public function handle(StandaloneKeydb $database) $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; $server = $this->database->destination->server; - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); if (! $caCert) { $server->generateCaCertificate(); - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); } if (! $caCert) { @@ -208,6 +208,8 @@ public function handle(StandaloneKeydb $database) if ($this->database->enable_ssl) { $this->commands[] = "chown -R 999:999 $this->configuration_dir/ssl/server.key $this->configuration_dir/ssl/server.crt"; } + $this->commands[] = "docker stop --timeout=10 $container_name 2>/dev/null || true"; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php index 13dba4b43c..8a936c8aef 100644 --- a/app/Actions/Database/StartMariadb.php +++ b/app/Actions/Database/StartMariadb.php @@ -57,11 +57,11 @@ public function handle(StandaloneMariadb $database) $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; $server = $this->database->destination->server; - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); if (! $caCert) { $server->generateCaCertificate(); - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); } if (! $caCert) { @@ -209,6 +209,8 @@ public function handle(StandaloneMariadb $database) $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; + $this->commands[] = "docker stop --timeout=10 $container_name 2>/dev/null || true"; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; if ($this->database->enable_ssl) { diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index 870b5b7e5a..19699d6849 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -61,11 +61,11 @@ public function handle(StandaloneMongodb $database) $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; $server = $this->database->destination->server; - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); if (! $caCert) { $server->generateCaCertificate(); - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); } if (! $caCert) { @@ -260,6 +260,8 @@ public function handle(StandaloneMongodb $database) $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; + $this->commands[] = "docker stop --timeout=10 $container_name 2>/dev/null || true"; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; if ($this->database->enable_ssl) { $this->commands[] = executeInDocker($this->database->uuid, 'chown mongodb:mongodb /etc/mongo/certs/server.pem'); diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php index 5d5611e07a..25546fa9d3 100644 --- a/app/Actions/Database/StartMysql.php +++ b/app/Actions/Database/StartMysql.php @@ -57,11 +57,11 @@ public function handle(StandaloneMysql $database) $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; $server = $this->database->destination->server; - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); if (! $caCert) { $server->generateCaCertificate(); - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); } if (! $caCert) { @@ -210,6 +210,8 @@ public function handle(StandaloneMysql $database) $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; + $this->commands[] = "docker stop --timeout=10 $container_name 2>/dev/null || true"; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; if ($this->database->enable_ssl) { diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 38d46b3c15..ac011acbe2 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -62,11 +62,11 @@ public function handle(StandalonePostgresql $database) $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; $server = $this->database->destination->server; - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); if (! $caCert) { $server->generateCaCertificate(); - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); } if (! $caCert) { @@ -223,6 +223,8 @@ public function handle(StandalonePostgresql $database) $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; + $this->commands[] = "docker stop --timeout=10 $container_name 2>/dev/null || true"; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; if ($this->database->enable_ssl) { $this->commands[] = executeInDocker($this->database->uuid, "chown {$this->database->postgres_user}:{$this->database->postgres_user} /var/lib/postgresql/certs/server.key /var/lib/postgresql/certs/server.crt"); diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php index 68a1f3fe39..8a7ae42a43 100644 --- a/app/Actions/Database/StartRedis.php +++ b/app/Actions/Database/StartRedis.php @@ -56,11 +56,11 @@ public function handle(StandaloneRedis $database) $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; $server = $this->database->destination->server; - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); if (! $caCert) { $server->generateCaCertificate(); - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); } if (! $caCert) { @@ -205,6 +205,8 @@ public function handle(StandaloneRedis $database) if ($this->database->enable_ssl) { $this->commands[] = "chown -R 999:999 $this->configuration_dir/ssl/server.key $this->configuration_dir/ssl/server.crt"; } + $this->commands[] = "docker stop --timeout=10 $container_name 2>/dev/null || true"; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index 5c881e7433..63f5b19790 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -49,7 +49,7 @@ private function stopContainer($database, string $containerName, int $timeout = { $server = $database->destination->server; instant_remote_process(command: [ - "docker stop --time=$timeout $containerName", + "docker stop --timeout=$timeout $containerName", "docker rm -f $containerName", ], server: $server, throwError: false); } diff --git a/app/Actions/Proxy/GetProxyConfiguration.php b/app/Actions/Proxy/GetProxyConfiguration.php index 3bf91c2818..3aa1d8d349 100644 --- a/app/Actions/Proxy/GetProxyConfiguration.php +++ b/app/Actions/Proxy/GetProxyConfiguration.php @@ -33,7 +33,13 @@ public function handle(Server $server, bool $forceRegenerate = false): string // 1. Force regenerate is requested // 2. Configuration file doesn't exist or is empty if ($forceRegenerate || empty(trim($proxy_configuration ?? ''))) { - $proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value(); + // Extract custom commands from existing config before regenerating + $custom_commands = []; + if (! empty(trim($proxy_configuration ?? ''))) { + $custom_commands = extractCustomProxyCommands($server, $proxy_configuration); + } + + $proxy_configuration = str(generateDefaultProxyConfiguration($server, $custom_commands))->trim()->value(); } if (empty($proxy_configuration)) { diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index ecfb13d0b9..8671a5f27a 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -19,6 +19,11 @@ public function handle(Server $server, bool $async = true, bool $force = false): if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) { return 'OK'; } + $server->proxy->set('status', 'starting'); + $server->save(); + $server->refresh(); + ProxyStatusChangedUI::dispatch($server->team_id); + $commands = collect([]); $proxy_path = $server->proxyPath(); $configuration = GetProxyConfiguration::run($server); @@ -64,14 +69,12 @@ public function handle(Server $server, bool $async = true, bool $force = false): ]); $commands = $commands->merge(connectProxyToNetworks($server)); } - $server->proxy->set('status', 'starting'); - $server->save(); - ProxyStatusChangedUI::dispatch($server->team_id); if ($async) { return remote_process($commands, $server, callEventOnFinish: 'ProxyStatusChanged', callEventData: $server->id); } else { instant_remote_process($commands, $server); + $server->proxy->set('type', $proxyType); $server->save(); ProxyStatusChanged::dispatch($server->id); diff --git a/app/Actions/Server/DeleteServer.php b/app/Actions/Server/DeleteServer.php index 15c892e755..45ec68abc2 100644 --- a/app/Actions/Server/DeleteServer.php +++ b/app/Actions/Server/DeleteServer.php @@ -2,16 +2,102 @@ namespace App\Actions\Server; +use App\Models\CloudProviderToken; use App\Models\Server; +use App\Models\Team; +use App\Notifications\Server\HetznerDeletionFailed; +use App\Services\HetznerService; use Lorisleiva\Actions\Concerns\AsAction; class DeleteServer { use AsAction; - public function handle(Server $server) + public function handle(int $serverId, bool $deleteFromHetzner = false, ?int $hetznerServerId = null, ?int $cloudProviderTokenId = null, ?int $teamId = null) { - StopSentinel::run($server); - $server->forceDelete(); + $server = Server::withTrashed()->find($serverId); + + // Delete from Hetzner even if server is already gone from Coolify + if ($deleteFromHetzner && ($hetznerServerId || ($server && $server->hetzner_server_id))) { + $this->deleteFromHetznerById( + $hetznerServerId ?? $server->hetzner_server_id, + $cloudProviderTokenId ?? $server->cloud_provider_token_id, + $teamId ?? $server->team_id + ); + } + + ray($server ? 'Deleting server from Coolify' : 'Server already deleted from Coolify, skipping Coolify deletion'); + + // If server is already deleted from Coolify, skip this part + if (! $server) { + return; // Server already force deleted from Coolify + } + + ray('force deleting server from Coolify', ['server_id' => $server->id]); + + try { + $server->forceDelete(); + } catch (\Throwable $e) { + ray('Failed to force delete server from Coolify', [ + 'error' => $e->getMessage(), + 'server_id' => $server->id, + ]); + logger()->error('Failed to force delete server from Coolify', [ + 'error' => $e->getMessage(), + 'server_id' => $server->id, + ]); + } + } + + private function deleteFromHetznerById(int $hetznerServerId, ?int $cloudProviderTokenId, int $teamId): void + { + try { + // Use the provided token, or fallback to first available team token + $token = null; + + if ($cloudProviderTokenId) { + $token = CloudProviderToken::find($cloudProviderTokenId); + } + + if (! $token) { + $token = CloudProviderToken::where('team_id', $teamId) + ->where('provider', 'hetzner') + ->first(); + } + + if (! $token) { + ray('No Hetzner token found for team, skipping Hetzner deletion', [ + 'team_id' => $teamId, + 'hetzner_server_id' => $hetznerServerId, + ]); + + return; + } + + $hetznerService = new HetznerService($token->token); + $hetznerService->deleteServer($hetznerServerId); + + ray('Deleted server from Hetzner', [ + 'hetzner_server_id' => $hetznerServerId, + 'team_id' => $teamId, + ]); + } catch (\Throwable $e) { + ray('Failed to delete server from Hetzner', [ + 'error' => $e->getMessage(), + 'hetzner_server_id' => $hetznerServerId, + 'team_id' => $teamId, + ]); + + // Log the error but don't prevent the server from being deleted from Coolify + logger()->error('Failed to delete server from Hetzner', [ + 'error' => $e->getMessage(), + 'hetzner_server_id' => $hetznerServerId, + 'team_id' => $teamId, + ]); + + // Notify the team about the failure + $team = Team::find($teamId); + $team?->notify(new HetznerDeletionFailed($hetznerServerId, $teamId, $e->getMessage())); + } } } diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index 5410b1cbd0..10589c8b9f 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -4,7 +4,6 @@ use App\Helpers\SslHelper; use App\Models\Server; -use App\Models\SslCertificate; use App\Models\StandaloneDocker; use Lorisleiva\Actions\Concerns\AsAction; @@ -20,7 +19,7 @@ public function handle(Server $server) throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: documentation.'); } - if (! SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->exists()) { + if (! $server->sslCertificates()->where('is_ca_certificate', true)->exists()) { $serverCert = SslHelper::generateSslCertificate( commonName: 'Coolify CA Certificate', serverId: $server->id, diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php index ce2d6d598a..0b13462efb 100644 --- a/app/Console/Commands/CleanupStuckedResources.php +++ b/app/Console/Commands/CleanupStuckedResources.php @@ -13,6 +13,7 @@ use App\Models\Service; use App\Models\ServiceApplication; use App\Models\ServiceDatabase; +use App\Models\SslCertificate; use App\Models\StandaloneClickhouse; use App\Models\StandaloneDragonfly; use App\Models\StandaloneKeydb; @@ -58,6 +59,15 @@ private function cleanup_stucked_resources() } catch (\Throwable $e) { echo "Error in cleaning stucked resources: {$e->getMessage()}\n"; } + try { + $servers = Server::onlyTrashed()->get(); + foreach ($servers as $server) { + echo "Force deleting stuck server: {$server->name}\n"; + $server->forceDelete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck servers: {$e->getMessage()}\n"; + } try { $applicationsDeploymentQueue = ApplicationDeploymentQueue::get(); foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) { @@ -427,5 +437,18 @@ private function cleanup_stucked_resources() } catch (\Throwable $e) { echo "Error in ServiceDatabases: {$e->getMessage()}\n"; } + + try { + $orphanedCerts = SslCertificate::whereNotIn('server_id', function ($query) { + $query->select('id')->from('servers'); + })->get(); + + foreach ($orphanedCerts as $cert) { + echo "Deleting orphaned SSL certificate: {$cert->id} (server_id: {$cert->server_id})\n"; + $cert->delete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning orphaned SSL certificates: {$e->getMessage()}\n"; + } } } diff --git a/app/Console/Commands/ClearGlobalSearchCache.php b/app/Console/Commands/ClearGlobalSearchCache.php new file mode 100644 index 0000000000..a368b0badc --- /dev/null +++ b/app/Console/Commands/ClearGlobalSearchCache.php @@ -0,0 +1,83 @@ +option('all')) { + return $this->clearAllTeamsCache(); + } + + if ($teamId = $this->option('team')) { + return $this->clearTeamCache($teamId); + } + + // If no options provided, clear cache for current user's team + if (! auth()->check()) { + $this->error('No authenticated user found. Use --team=ID or --all option.'); + + return Command::FAILURE; + } + + $teamId = auth()->user()->currentTeam()->id; + + return $this->clearTeamCache($teamId); + } + + private function clearTeamCache(int $teamId): int + { + $team = Team::find($teamId); + + if (! $team) { + $this->error("Team with ID {$teamId} not found."); + + return Command::FAILURE; + } + + GlobalSearch::clearTeamCache($teamId); + $this->info("✓ Cleared global search cache for team: {$team->name} (ID: {$teamId})"); + + return Command::SUCCESS; + } + + private function clearAllTeamsCache(): int + { + $teams = Team::all(); + + if ($teams->isEmpty()) { + $this->warn('No teams found.'); + + return Command::SUCCESS; + } + + $count = 0; + foreach ($teams as $team) { + GlobalSearch::clearTeamCache($team->id); + $count++; + } + + $this->info("✓ Cleared global search cache for {$count} team(s)"); + + return Command::SUCCESS; + } +} diff --git a/app/Events/ServerValidated.php b/app/Events/ServerValidated.php new file mode 100644 index 0000000000..95a116ebea --- /dev/null +++ b/app/Events/ServerValidated.php @@ -0,0 +1,51 @@ +check() && auth()->user()->currentTeam()) { + $teamId = auth()->user()->currentTeam()->id; + } + $this->teamId = $teamId; + $this->serverUuid = $serverUuid; + } + + public function broadcastOn(): array + { + if (is_null($this->teamId)) { + return []; + } + + return [ + new PrivateChannel("team.{$this->teamId}"), + ]; + } + + public function broadcastAs(): string + { + return 'ServerValidated'; + } + + public function broadcastWith(): array + { + return [ + 'teamId' => $this->teamId, + 'serverUuid' => $this->serverUuid, + ]; + } +} diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index ce9e723d4c..e9c52d2f50 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -17,6 +17,7 @@ use App\Models\Service; use App\Rules\ValidGitBranch; use App\Rules\ValidGitRepositoryUrl; +use App\Services\DockerImageParser; use Illuminate\Http\Request; use Illuminate\Validation\Rule; use OpenApi\Attributes as OA; @@ -1512,9 +1513,33 @@ private function create_application(Request $request, $type) if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - if (! $request->docker_registry_image_tag) { - $request->offsetSet('docker_registry_image_tag', 'latest'); + // Process docker image name and tag using DockerImageParser + $dockerImageName = $request->docker_registry_image_name; + $dockerImageTag = $request->docker_registry_image_tag; + + // Build the full Docker image string for parsing + if ($dockerImageTag) { + $dockerImageString = $dockerImageName.':'.$dockerImageTag; + } else { + $dockerImageString = $dockerImageName; + } + + // Parse using DockerImageParser to normalize the image reference + $parser = new DockerImageParser; + $parser->parse($dockerImageString); + + // Get normalized image name and tag + $normalizedImageName = $parser->getFullImageNameWithoutTag(); + + // Append @sha256 to image name if using digest + if ($parser->isImageHash() && ! str_ends_with($normalizedImageName, '@sha256')) { + $normalizedImageName .= '@sha256'; } + + // Set processed values back to request + $request->offsetSet('docker_registry_image_name', $normalizedImageName); + $request->offsetSet('docker_registry_image_tag', $parser->getTag()); + $application = new Application; removeUnnecessaryFieldsFromRequest($request); @@ -2469,7 +2494,7 @@ public function envs(Request $request) )] public function update_env_by_uuid(Request $request) { - $allowedFields = ['key', 'value', 'is_preview', 'is_literal']; + $allowedFields = ['key', 'value', 'is_preview', 'is_literal', 'is_multiline', 'is_shown_once', 'is_runtime', 'is_buildtime']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -2497,6 +2522,8 @@ public function update_env_by_uuid(Request $request) 'is_literal' => 'boolean', 'is_multiline' => 'boolean', 'is_shown_once' => 'boolean', + 'is_runtime' => 'boolean', + 'is_buildtime' => 'boolean', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -2692,7 +2719,7 @@ public function create_bulk_envs(Request $request) ], 400); } $bulk_data = collect($bulk_data)->map(function ($item) { - return collect($item)->only(['key', 'value', 'is_preview', 'is_literal']); + return collect($item)->only(['key', 'value', 'is_preview', 'is_literal', 'is_multiline', 'is_shown_once', 'is_runtime', 'is_buildtime']); }); $returnedEnvs = collect(); foreach ($bulk_data as $item) { @@ -2703,6 +2730,8 @@ public function create_bulk_envs(Request $request) 'is_literal' => 'boolean', 'is_multiline' => 'boolean', 'is_shown_once' => 'boolean', + 'is_runtime' => 'boolean', + 'is_buildtime' => 'boolean', ]); if ($validator->fails()) { return response()->json([ @@ -2862,7 +2891,7 @@ public function create_bulk_envs(Request $request) )] public function create_env(Request $request) { - $allowedFields = ['key', 'value', 'is_preview', 'is_literal']; + $allowedFields = ['key', 'value', 'is_preview', 'is_literal', 'is_multiline', 'is_shown_once', 'is_runtime', 'is_buildtime']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -2885,6 +2914,8 @@ public function create_env(Request $request) 'is_literal' => 'boolean', 'is_multiline' => 'boolean', 'is_shown_once' => 'boolean', + 'is_runtime' => 'boolean', + 'is_buildtime' => 'boolean', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 5871f481a9..7b85408cfd 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -317,6 +317,10 @@ public function database_by_uuid(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function update_by_uuid(Request $request) @@ -666,6 +670,10 @@ public function update_by_uuid(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function update_backup(Request $request) @@ -844,6 +852,10 @@ public function update_backup(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_database_postgresql(Request $request) @@ -907,6 +919,10 @@ public function create_database_postgresql(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_database_clickhouse(Request $request) @@ -969,6 +985,10 @@ public function create_database_clickhouse(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_database_dragonfly(Request $request) @@ -1032,6 +1052,10 @@ public function create_database_dragonfly(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_database_redis(Request $request) @@ -1095,6 +1119,10 @@ public function create_database_redis(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_database_keydb(Request $request) @@ -1161,6 +1189,10 @@ public function create_database_keydb(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_database_mariadb(Request $request) @@ -1227,6 +1259,10 @@ public function create_database_mariadb(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_database_mysql(Request $request) @@ -1290,6 +1326,10 @@ public function create_database_mysql(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_database_mongodb(Request $request) @@ -1941,7 +1981,7 @@ public function delete_by_uuid(Request $request) content: new OA\JsonContent( type: 'object', properties: [ - 'message' => new OA\Schema(type: 'string', example: 'Backup configuration and all executions deleted.'), + new OA\Property(property: 'message', type: 'string', example: 'Backup configuration and all executions deleted.'), ] ) ), @@ -1951,7 +1991,7 @@ public function delete_by_uuid(Request $request) content: new OA\JsonContent( type: 'object', properties: [ - 'message' => new OA\Schema(type: 'string', example: 'Backup configuration not found.'), + new OA\Property(property: 'message', type: 'string', example: 'Backup configuration not found.'), ] ) ), @@ -2065,7 +2105,7 @@ public function delete_backup_by_uuid(Request $request) content: new OA\JsonContent( type: 'object', properties: [ - 'message' => new OA\Schema(type: 'string', example: 'Backup execution deleted.'), + new OA\Property(property: 'message', type: 'string', example: 'Backup execution deleted.'), ] ) ), @@ -2075,7 +2115,7 @@ public function delete_backup_by_uuid(Request $request) content: new OA\JsonContent( type: 'object', properties: [ - 'message' => new OA\Schema(type: 'string', example: 'Backup execution not found.'), + new OA\Property(property: 'message', type: 'string', example: 'Backup execution not found.'), ] ) ), @@ -2171,17 +2211,18 @@ public function delete_execution_by_uuid(Request $request) content: new OA\JsonContent( type: 'object', properties: [ - 'executions' => new OA\Schema( + new OA\Property( + property: 'executions', type: 'array', items: new OA\Items( type: 'object', properties: [ - 'uuid' => ['type' => 'string'], - 'filename' => ['type' => 'string'], - 'size' => ['type' => 'integer'], - 'created_at' => ['type' => 'string'], - 'message' => ['type' => 'string'], - 'status' => ['type' => 'string'], + new OA\Property(property: 'uuid', type: 'string'), + new OA\Property(property: 'filename', type: 'string'), + new OA\Property(property: 'size', type: 'integer'), + new OA\Property(property: 'created_at', type: 'string'), + new OA\Property(property: 'message', type: 'string'), + new OA\Property(property: 'status', type: 'string'), ] ) ), diff --git a/app/Http/Controllers/Api/GithubController.php b/app/Http/Controllers/Api/GithubController.php index 8c8c872386..7ddbaf991c 100644 --- a/app/Http/Controllers/Api/GithubController.php +++ b/app/Http/Controllers/Api/GithubController.php @@ -219,7 +219,8 @@ public function create_github_app(Request $request) schema: new OA\Schema( type: 'object', properties: [ - 'repositories' => new OA\Schema( + new OA\Property( + property: 'repositories', type: 'array', items: new OA\Items(type: 'object') ), @@ -335,7 +336,8 @@ public function load_repositories($github_app_id) schema: new OA\Schema( type: 'object', properties: [ - 'branches' => new OA\Schema( + new OA\Property( + property: 'branches', type: 'array', items: new OA\Items(type: 'object') ), @@ -457,7 +459,7 @@ public function load_branches($github_app_id, $owner, $repo) ), new OA\Response(response: 401, description: 'Unauthorized'), new OA\Response(response: 404, description: 'GitHub app not found'), - new OA\Response(response: 422, description: 'Validation error'), + new OA\Response(response: 422, ref: '#/components/responses/422'), ] )] public function update_github_app(Request $request, $github_app_id) diff --git a/app/Http/Controllers/Api/OpenApi.php b/app/Http/Controllers/Api/OpenApi.php index 60337a76cf..69f71feafa 100644 --- a/app/Http/Controllers/Api/OpenApi.php +++ b/app/Http/Controllers/Api/OpenApi.php @@ -40,6 +40,27 @@ new OA\Property(property: 'message', type: 'string', example: 'Resource not found.'), ] )), + new OA\Response( + response: 422, + description: 'Validation error.', + content: new OA\JsonContent( + type: 'object', + properties: [ + new OA\Property(property: 'message', type: 'string', example: 'Validation error.'), + new OA\Property( + property: 'errors', + type: 'object', + additionalProperties: new OA\AdditionalProperties( + type: 'array', + items: new OA\Items(type: 'string') + ), + example: [ + 'name' => ['The name field is required.'], + 'api_url' => ['The api url field is required.', 'The api url format is invalid.'], + ] + ), + ] + )), ], )] class OpenApi diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php index 303e6535d5..8f2ba25c8f 100644 --- a/app/Http/Controllers/Api/OtherController.php +++ b/app/Http/Controllers/Api/OtherController.php @@ -21,8 +21,9 @@ class OtherController extends Controller new OA\Response( response: 200, description: 'Returns the version of the application', - content: new OA\JsonContent( - type: 'string', + content: new OA\MediaType( + mediaType: 'text/html', + schema: new OA\Schema(type: 'string'), example: 'v4.0.0', )), new OA\Response( @@ -166,8 +167,9 @@ public function feedback(Request $request) new OA\Response( response: 200, description: 'Healthcheck endpoint.', - content: new OA\JsonContent( - type: 'string', + content: new OA\MediaType( + mediaType: 'text/html', + schema: new OA\Schema(type: 'string'), example: 'OK', )), new OA\Response( diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index e688b8980f..951a1f5d91 100644 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -134,6 +134,10 @@ public function project_by_uuid(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function environment_details(Request $request) @@ -214,6 +218,10 @@ public function environment_details(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_project(Request $request) @@ -324,6 +332,10 @@ public function create_project(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function update_project(Request $request) @@ -425,6 +437,10 @@ public function update_project(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function delete_project(Request $request) @@ -487,6 +503,10 @@ public function delete_project(Request $request) response: 404, description: 'Project not found.', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function get_environments(Request $request) @@ -566,6 +586,10 @@ public function get_environments(Request $request) response: 409, description: 'Environment with this name already exists.', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_environment(Request $request) @@ -663,6 +687,10 @@ public function create_environment(Request $request) response: 404, description: 'Project or environment not found.', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function delete_environment(Request $request) diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php index 55a6cd9f42..e7b36cb9aa 100644 --- a/app/Http/Controllers/Api/SecurityController.php +++ b/app/Http/Controllers/Api/SecurityController.php @@ -163,6 +163,10 @@ public function key_by_uuid(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_key(Request $request) @@ -282,6 +286,10 @@ public function create_key(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function update_key(Request $request) diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index cbd20400a7..06baf2dde6 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -447,6 +447,10 @@ public function domains_by_server(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_server(Request $request) @@ -604,6 +608,10 @@ public function create_server(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function update_server(Request $request) @@ -722,6 +730,10 @@ public function update_server(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function delete_server(Request $request) @@ -746,7 +758,13 @@ public function delete_server(Request $request) return response()->json(['message' => 'Local server cannot be deleted.'], 400); } $server->delete(); - DeleteServer::dispatch($server); + DeleteServer::dispatch( + $server->id, + false, // Don't delete from Hetzner via API + $server->hetzner_server_id, + $server->cloud_provider_token_id, + $server->team_id + ); return response()->json(['message' => 'Server deleted.']); } @@ -790,6 +808,10 @@ public function delete_server(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function validate_server(Request $request) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index e240e326e8..737724d226 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -235,6 +235,10 @@ public function services(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_service(Request $request) @@ -704,6 +708,10 @@ public function delete_by_uuid(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function update_by_uuid(Request $request) @@ -954,6 +962,10 @@ public function envs(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function update_env_by_uuid(Request $request) @@ -1075,6 +1087,10 @@ public function update_env_by_uuid(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_bulk_envs(Request $request) @@ -1191,6 +1207,10 @@ public function create_bulk_envs(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), ] )] public function create_env(Request $request) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 965eab68e4..94c299364c 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -116,16 +116,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private $env_args; - private $environment_variables; - private $env_nixpacks_args; private $docker_compose; private $docker_compose_base64; - private ?string $env_filename = null; - private ?string $nixpacks_plan = null; private Collection $nixpacks_plan_json; @@ -488,9 +484,18 @@ private function deploy_simple_dockerfile() ); $this->generate_image_names(); $this->generate_compose_file(); + + // Save build-time .env file BEFORE the build + $this->save_buildtime_environment_variables(); + $this->generate_build_env_variables(); $this->add_build_env_variables_to_dockerfile(); $this->build_image(); + + // Save runtime environment variables AFTER the build + // This overwrites the build-time .env with ALL variables (build-time + runtime) + $this->save_runtime_environment_variables(); + $this->push_to_docker_registry(); $this->rolling_update(); } @@ -503,7 +508,12 @@ private function deploy_dockerimage_buildpack() } else { $this->dockerImageTag = $this->application->docker_registry_image_tag; } - $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}."); + + // Check if this is an image hash deployment + $isImageHash = str($this->dockerImageTag)->startsWith('sha256-'); + $displayName = $isImageHash ? "{$this->dockerImage}@sha256:".str($this->dockerImageTag)->after('sha256-') : "{$this->dockerImage}:{$this->dockerImageTag}"; + + $this->application_deployment_queue->addLogEntry("Starting deployment of {$displayName} to {$this->server->name}."); $this->generate_image_names(); $this->prepare_builder_image(); $this->generate_compose_file(); @@ -571,7 +581,6 @@ private function deploy_docker_compose_buildpack() if ($this->application->settings->is_raw_compose_deployment_enabled) { $this->application->oldRawParser(); $yaml = $composeFile = $this->application->docker_compose_raw; - $this->generate_runtime_environment_variables(); // For raw compose, we cannot automatically add secrets configuration // User must define it manually in their docker-compose file @@ -580,16 +589,14 @@ private function deploy_docker_compose_buildpack() } } else { $composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id')); - $this->generate_runtime_environment_variables(); - if (filled($this->env_filename)) { - $services = collect(data_get($composeFile, 'services', [])); - $services = $services->map(function ($service, $name) { - $service['env_file'] = [$this->env_filename]; + // Always add .env file to services + $services = collect(data_get($composeFile, 'services', [])); + $services = $services->map(function ($service, $name) { + $service['env_file'] = ['.env']; - return $service; - }); - $composeFile['services'] = $services->toArray(); - } + return $service; + }); + $composeFile['services'] = $services->toArray(); if (empty($composeFile)) { $this->application_deployment_queue->addLogEntry('Failed to parse docker-compose file.'); $this->fail('Failed to parse docker-compose file.'); @@ -615,6 +622,9 @@ private function deploy_docker_compose_buildpack() // Build new container to limit downtime. $this->application_deployment_queue->addLogEntry('Pulling & building required images.'); + // Save build-time .env file BEFORE the build + $this->save_buildtime_environment_variables(); + if ($this->docker_compose_custom_build_command) { // Prepend DOCKER_BUILDKIT=1 if BuildKit is supported $build_command = $this->docker_compose_custom_build_command; @@ -630,9 +640,8 @@ private function deploy_docker_compose_buildpack() if ($this->dockerBuildkitSupported) { $command = "DOCKER_BUILDKIT=1 {$command}"; } - if (filled($this->env_filename)) { - $command .= " --env-file {$this->workdir}/{$this->env_filename}"; - } + // Use build-time .env file from /artifacts (outside Docker context to prevent it from being in the image) + $command .= ' --env-file /artifacts/build-time.env'; if ($this->force_rebuild) { $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull --no-cache"; } else { @@ -652,6 +661,10 @@ private function deploy_docker_compose_buildpack() ); } + // Save runtime environment variables AFTER the build + // This overwrites the build-time .env with ALL variables (build-time + runtime) + $this->save_runtime_environment_variables(); + $this->stop_running_container(force: true); $this->application_deployment_queue->addLogEntry('Starting new application.'); $networkId = $this->application->uuid; @@ -685,9 +698,8 @@ private function deploy_docker_compose_buildpack() $this->docker_compose_location = '/docker-compose.yaml'; $command = "{$this->coolify_variables} docker compose"; - if (filled($this->env_filename)) { - $command .= " --env-file {$server_workdir}/{$this->env_filename}"; - } + // Always use .env file + $command .= " --env-file {$server_workdir}/.env"; $command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"; $this->execute_remote_command( ['command' => $command, 'hidden' => true], @@ -702,9 +714,8 @@ private function deploy_docker_compose_buildpack() } else { $command = "{$this->coolify_variables} docker compose"; if ($this->preserveRepository) { - if (filled($this->env_filename)) { - $command .= " --env-file {$server_workdir}/{$this->env_filename}"; - } + // Always use .env file + $command .= " --env-file {$server_workdir}/.env"; $command .= " --project-name {$this->application->uuid} --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"; $this->write_deployment_configurations(); @@ -712,9 +723,8 @@ private function deploy_docker_compose_buildpack() ['command' => $command, 'hidden' => true], ); } else { - if (filled($this->env_filename)) { - $command .= " --env-file {$this->workdir}/{$this->env_filename}"; - } + // Always use .env file + $command .= " --env-file {$this->workdir}/.env"; $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"; $this->execute_remote_command( [executeInDocker($this->deployment_uuid, $command), 'hidden' => true], @@ -748,9 +758,18 @@ private function deploy_dockerfile_buildpack() } $this->cleanup_git(); $this->generate_compose_file(); + + // Save build-time .env file BEFORE the build + $this->save_buildtime_environment_variables(); + $this->generate_build_env_variables(); $this->add_build_env_variables_to_dockerfile(); $this->build_image(); + + // Save runtime environment variables AFTER the build + // This overwrites the build-time .env with ALL variables (build-time + runtime) + $this->save_runtime_environment_variables(); + $this->push_to_docker_registry(); $this->rolling_update(); } @@ -774,11 +793,15 @@ private function deploy_nixpacks_buildpack() $this->cleanup_git(); $this->generate_nixpacks_confs(); $this->generate_compose_file(); + + // Save build-time .env file BEFORE the build for Nixpacks + $this->save_buildtime_environment_variables(); + $this->generate_build_env_variables(); $this->build_image(); // For Nixpacks, save runtime environment variables AFTER the build - // to prevent them from being accessible during the build process + // This overwrites the build-time .env with ALL variables (build-time + runtime) $this->save_runtime_environment_variables(); $this->push_to_docker_registry(); $this->rolling_update(); @@ -802,7 +825,16 @@ private function deploy_static_buildpack() $this->clone_repository(); $this->cleanup_git(); $this->generate_compose_file(); + + // Save build-time .env file BEFORE the build + $this->save_buildtime_environment_variables(); + $this->build_static_image(); + + // Save runtime environment variables AFTER the build + // This overwrites the build-time .env with ALL variables (build-time + runtime) + $this->save_runtime_environment_variables(); + $this->push_to_docker_registry(); $this->rolling_update(); } @@ -934,7 +966,13 @@ private function generate_image_names() $this->production_image_name = "{$this->application->uuid}:latest"; } } elseif ($this->application->build_pack === 'dockerimage') { - $this->production_image_name = "{$this->dockerImage}:{$this->dockerImageTag}"; + // Check if this is an image hash deployment + if (str($this->dockerImageTag)->startsWith('sha256-')) { + $hash = str($this->dockerImageTag)->after('sha256-'); + $this->production_image_name = "{$this->dockerImage}@sha256:{$hash}"; + } else { + $this->production_image_name = "{$this->dockerImage}:{$this->dockerImageTag}"; + } } elseif ($this->pull_request_id !== 0) { if ($this->application->docker_registry_image_name) { $this->build_image_name = "{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build"; @@ -976,6 +1014,10 @@ private function should_skip_build() $this->skip_build = true; $this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); $this->generate_compose_file(); + + // Save runtime environment variables even when skipping build + $this->save_runtime_environment_variables(); + $this->push_to_docker_registry(); $this->rolling_update(); @@ -985,6 +1027,10 @@ private function should_skip_build() $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); $this->skip_build = true; $this->generate_compose_file(); + + // Save runtime environment variables even when skipping build + $this->save_runtime_environment_variables(); + $this->push_to_docker_registry(); $this->rolling_update(); @@ -1049,8 +1095,6 @@ private function generate_runtime_environment_variables() $envs->push($key.'='.$item); }); if ($this->pull_request_id === 0) { - $this->env_filename = '.env'; - // Generate SERVICE_ variables first for dockercompose if ($this->build_pack === 'dockercompose') { $domains = collect(json_decode($this->application->docker_compose_domains)) ?? collect([]); @@ -1109,8 +1153,6 @@ private function generate_runtime_environment_variables() $envs->push('HOST=0.0.0.0'); } } else { - $this->env_filename = '.env'; - // Generate SERVICE_ variables first for dockercompose preview if ($this->build_pack === 'dockercompose') { $domains = collect(json_decode(data_get($this->preview, 'docker_compose_domains'))) ?? collect([]); @@ -1165,13 +1207,56 @@ private function generate_runtime_environment_variables() $envs->push('HOST=0.0.0.0'); } } - if ($envs->isEmpty()) { - if ($this->env_filename) { + + // Return the generated environment variables instead of storing them globally + return $envs; + } + + private function save_runtime_environment_variables() + { + // This method saves the .env file with ALL runtime variables + // For builds, it should be called AFTER the build to include runtime-only variables + + // Generate runtime environment variables locally + $environment_variables = $this->generate_runtime_environment_variables(); + + // Handle empty environment variables + if ($environment_variables->isEmpty()) { + // For Docker Compose, we need to create an empty .env file + // because we always reference it in the compose file + if ($this->build_pack === 'dockercompose') { + $this->application_deployment_queue->addLogEntry('Creating empty .env file (no environment variables defined).'); + + // Create empty .env file + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "touch $this->workdir/.env"), + ] + ); + + // Also create in configuration directory if ($this->use_build_server) { $this->server = $this->original_server; $this->execute_remote_command( [ - 'command' => "rm -f $this->configuration_dir/{$this->env_filename}", + "touch $this->configuration_dir/.env", + ] + ); + $this->server = $this->build_server; + } else { + $this->execute_remote_command( + [ + "touch $this->configuration_dir/.env", + ] + ); + } + } else { + // For non-Docker Compose deployments, clean up any existing .env files + if ($this->use_build_server) { + $this->server = $this->original_server; + $this->execute_remote_command( + [ + 'command' => "rm -f $this->configuration_dir/.env", 'hidden' => true, 'ignore_errors' => true, ] @@ -1179,7 +1264,7 @@ private function generate_runtime_environment_variables() $this->server = $this->build_server; $this->execute_remote_command( [ - 'command' => "rm -f $this->configuration_dir/{$this->env_filename}", + 'command' => "rm -f $this->configuration_dir/.env", 'hidden' => true, 'ignore_errors' => true, ] @@ -1187,77 +1272,185 @@ private function generate_runtime_environment_variables() } else { $this->execute_remote_command( [ - 'command' => "rm -f $this->configuration_dir/{$this->env_filename}", + 'command' => "rm -f $this->configuration_dir/.env", 'hidden' => true, 'ignore_errors' => true, ] ); } } - $this->env_filename = null; + + return; + } + + // Write the environment variables to file + $envs_base64 = base64_encode($environment_variables->implode("\n")); + + // Write .env file to workdir (for container runtime) + $this->application_deployment_queue->addLogEntry('Creating .env file with runtime variables for build phase.', hidden: true); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/.env > /dev/null"), + ], + [ + executeInDocker($this->deployment_uuid, "cat $this->workdir/.env"), + 'hidden' => true, + + ] + ); + + // Write .env file to configuration directory + if ($this->use_build_server) { + $this->server = $this->original_server; + $this->execute_remote_command( + [ + "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/.env > /dev/null", + ] + ); + $this->server = $this->build_server; } else { - // For Nixpacks builds, we save the .env file AFTER the build to prevent - // runtime-only variables from being accessible during the build process - if ($this->application->build_pack !== 'nixpacks' || $this->skip_build) { - $envs_base64 = base64_encode($envs->implode("\n")); - $this->execute_remote_command( - [ - executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null"), - ], + $this->execute_remote_command( + [ + "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/.env > /dev/null", + ] + ); + } + } - ); - if ($this->use_build_server) { - $this->server = $this->original_server; - $this->execute_remote_command( - [ - "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null", - ] - ); - $this->server = $this->build_server; + private function generate_buildtime_environment_variables() + { + $envs = collect([]); + $coolify_envs = $this->generate_coolify_env_variables(); + + // Add COOLIFY variables + $coolify_envs->each(function ($item, $key) use ($envs) { + $envs->push($key.'='.$item); + }); + + // Add SERVICE_NAME variables for Docker Compose builds + if ($this->build_pack === 'dockercompose') { + if ($this->pull_request_id === 0) { + // Generate SERVICE_NAME for dockercompose services from processed compose + if ($this->application->settings->is_raw_compose_deployment_enabled) { + $dockerCompose = Yaml::parse($this->application->docker_compose_raw); } else { - $this->execute_remote_command( - [ - "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null", - ] - ); + $dockerCompose = Yaml::parse($this->application->docker_compose); } + $services = data_get($dockerCompose, 'services', []); + foreach ($services as $serviceName => $_) { + $envs->push('SERVICE_NAME_'.str($serviceName)->upper().'='.$serviceName); + } + + // Generate SERVICE_FQDN & SERVICE_URL for non-PR deployments + $domains = collect(json_decode($this->application->docker_compose_domains)) ?? collect([]); + foreach ($domains as $forServiceName => $domain) { + $parsedDomain = data_get($domain, 'domain'); + if (filled($parsedDomain)) { + $parsedDomain = str($parsedDomain)->explode(',')->first(); + $coolifyUrl = Url::fromString($parsedDomain); + $coolifyScheme = $coolifyUrl->getScheme(); + $coolifyFqdn = $coolifyUrl->getHost(); + $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); + $envs->push('SERVICE_URL_'.str($forServiceName)->upper().'='.$coolifyUrl->__toString()); + $envs->push('SERVICE_FQDN_'.str($forServiceName)->upper().'='.$coolifyFqdn); + } + } + } else { + // Generate SERVICE_NAME for preview deployments + $rawDockerCompose = Yaml::parse($this->application->docker_compose_raw); + $rawServices = data_get($rawDockerCompose, 'services', []); + foreach ($rawServices as $rawServiceName => $_) { + $envs->push('SERVICE_NAME_'.str($rawServiceName)->upper().'='.addPreviewDeploymentSuffix($rawServiceName, $this->pull_request_id)); + } + + // Generate SERVICE_FQDN & SERVICE_URL for preview deployments with PR-specific domains + $domains = collect(json_decode(data_get($this->preview, 'docker_compose_domains'))) ?? collect([]); + foreach ($domains as $forServiceName => $domain) { + $parsedDomain = data_get($domain, 'domain'); + if (filled($parsedDomain)) { + $parsedDomain = str($parsedDomain)->explode(',')->first(); + $coolifyUrl = Url::fromString($parsedDomain); + $coolifyScheme = $coolifyUrl->getScheme(); + $coolifyFqdn = $coolifyUrl->getHost(); + $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); + $envs->push('SERVICE_URL_'.str($forServiceName)->upper().'='.$coolifyUrl->__toString()); + $envs->push('SERVICE_FQDN_'.str($forServiceName)->upper().'='.$coolifyFqdn); + } + } + } + } + + // Add build-time user variables only + if ($this->pull_request_id === 0) { + $sorted_environment_variables = $this->application->environment_variables() + ->where('key', 'not like', 'NIXPACKS_%') + ->where('is_buildtime', true) // ONLY build-time variables + ->orderBy($this->application->settings->is_env_sorting_enabled ? 'key' : 'id') + ->get(); + + // For Docker Compose, filter out SERVICE_FQDN and SERVICE_URL as we generate these + if ($this->build_pack === 'dockercompose') { + $sorted_environment_variables = $sorted_environment_variables->filter(function ($env) { + return ! str($env->key)->startsWith('SERVICE_FQDN_') && ! str($env->key)->startsWith('SERVICE_URL_'); + }); + } + + foreach ($sorted_environment_variables as $env) { + $envs->push($env->key.'='.$env->real_value); + } + } else { + $sorted_environment_variables = $this->application->environment_variables_preview() + ->where('key', 'not like', 'NIXPACKS_%') + ->where('is_buildtime', true) // ONLY build-time variables + ->orderBy($this->application->settings->is_env_sorting_enabled ? 'key' : 'id') + ->get(); + + // For Docker Compose, filter out SERVICE_FQDN and SERVICE_URL as we generate these with PR-specific values + if ($this->build_pack === 'dockercompose') { + $sorted_environment_variables = $sorted_environment_variables->filter(function ($env) { + return ! str($env->key)->startsWith('SERVICE_FQDN_') && ! str($env->key)->startsWith('SERVICE_URL_'); + }); + } + + foreach ($sorted_environment_variables as $env) { + $envs->push($env->key.'='.$env->real_value); } } - $this->environment_variables = $envs; + + // Return the generated environment variables + return $envs; } - private function save_runtime_environment_variables() + private function save_buildtime_environment_variables() { - // This method saves the .env file with runtime variables - // It should be called AFTER the build for Nixpacks to prevent runtime-only variables - // from being accessible during the build process + // Generate build-time environment variables locally + $environment_variables = $this->generate_buildtime_environment_variables(); + + // Save .env file for build phase in /artifacts to prevent it from being copied into Docker images + if ($environment_variables->isNotEmpty()) { + $envs_base64 = base64_encode($environment_variables->implode("\n")); - if ($this->environment_variables && $this->environment_variables->isNotEmpty() && $this->env_filename) { - $envs_base64 = base64_encode($this->environment_variables->implode("\n")); + $this->application_deployment_queue->addLogEntry('Creating build-time .env file in /artifacts (outside Docker context).', hidden: true); - // Write .env file to workdir (for container runtime) $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null"), + executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee /artifacts/build-time.env > /dev/null"), + ], + [ + executeInDocker($this->deployment_uuid, 'cat /artifacts/build-time.env'), + 'hidden' => true, ], ); + } elseif ($this->build_pack === 'dockercompose' || $this->build_pack === 'dockerfile') { + // For Docker Compose and Dockerfile, create an empty .env file even if there are no build-time variables + // This ensures the file exists when referenced in build commands + $this->application_deployment_queue->addLogEntry('Creating empty build-time .env file in /artifacts (no build-time variables defined).', hidden: true); - // Write .env file to configuration directory - if ($this->use_build_server) { - $this->server = $this->original_server; - $this->execute_remote_command( - [ - "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null", - ] - ); - $this->server = $this->build_server; - } else { - $this->execute_remote_command( - [ - "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null", - ] - ); - } + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, 'touch /artifacts/build-time.env'), + ] + ); } } @@ -1472,15 +1665,18 @@ private function deploy_pull_request() $this->generate_nixpacks_confs(); } $this->generate_compose_file(); + + // Save build-time .env file BEFORE the build + $this->save_buildtime_environment_variables(); + $this->generate_build_env_variables(); if ($this->application->build_pack === 'dockerfile') { $this->add_build_env_variables_to_dockerfile(); } $this->build_image(); - // For Nixpacks, save runtime environment variables AFTER the build - if ($this->application->build_pack === 'nixpacks') { - $this->save_runtime_environment_variables(); - } + + // This overwrites the build-time .env with ALL variables (build-time + runtime) + $this->save_runtime_environment_variables(); $this->push_to_docker_registry(); $this->rolling_update(); } @@ -1515,7 +1711,7 @@ private function create_workdir() } } - private function prepare_builder_image() + private function prepare_builder_image(bool $firstTry = true) { $this->checkForCancellation(); $settings = instanceSettings(); @@ -1538,7 +1734,12 @@ private function prepare_builder_image() $runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} {$env_flags} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}"; } } - $this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage."); + if ($firstTry) { + $this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage"); + } else { + $this->application_deployment_queue->addLogEntry('Preparing container with helper image with updated envs.'); + } + $this->graceful_shutdown_container($this->deployment_uuid); $this->execute_remote_command( [ @@ -1561,7 +1762,7 @@ private function restart_builder_container_with_actual_commit() $this->env_args = null; // Restart the helper container with updated environment variables (including actual SOURCE_COMMIT) - $this->prepare_builder_image(); + $this->prepare_builder_image(firstTry: false); } private function deploy_to_additional_destinations() @@ -1696,9 +1897,27 @@ private function check_git_if_build_needed() ); } if ($this->saved_outputs->get('git_commit_sha') && ! $this->rollback) { - $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); - $this->application_deployment_queue->commit = $this->commit; - $this->application_deployment_queue->save(); + // Extract commit SHA from git ls-remote output, handling multi-line output (e.g., redirect warnings) + // Expected format: "commit_sha\trefs/heads/branch" possibly preceded by warning lines + // Note: Git warnings can be on the same line as the result (no newline) + $lsRemoteOutput = $this->saved_outputs->get('git_commit_sha'); + + // Find the part containing a tab (the actual ls-remote result) + // Handle cases where warning is on the same line as the result + if ($lsRemoteOutput->contains("\t")) { + // Get everything from the last occurrence of a valid commit SHA pattern before the tab + // A valid commit SHA is 40 hex characters + $output = $lsRemoteOutput->value(); + + // Extract the line with the tab (actual ls-remote result) + preg_match('/\b([0-9a-fA-F]{40})(?=\s*\t)/', $output, $matches); + + if (isset($matches[1])) { + $this->commit = $matches[1]; + $this->application_deployment_queue->commit = $this->commit; + $this->application_deployment_queue->save(); + } + } } $this->set_coolify_variables(); @@ -1713,7 +1932,7 @@ private function clone_repository() { $importCommands = $this->generate_git_import_commands(); $this->application_deployment_queue->addLogEntry("\n----------------------------------------"); - $this->application_deployment_queue->addLogEntry("Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}."); + $this->application_deployment_queue->addLogEntry("Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->commit}) to {$this->basedir}."); if ($this->pull_request_id !== 0) { $this->application_deployment_queue->addLogEntry("Checking out tag pull/{$this->pull_request_id}/head."); } @@ -1809,6 +2028,15 @@ private function generate_nixpacks_confs() if ($this->nixpacks_type === 'elixir') { $this->elixir_finetunes(); } + if ($this->nixpacks_type === 'node') { + // Check if NIXPACKS_NODE_VERSION is set + $variables = data_get($parsed, 'variables', []); + if (! isset($variables['NIXPACKS_NODE_VERSION'])) { + $this->application_deployment_queue->addLogEntry('----------------------------------------'); + $this->application_deployment_queue->addLogEntry('⚠️ NIXPACKS_NODE_VERSION not set. Nixpacks will use Node.js 18 by default, which is EOL.'); + $this->application_deployment_queue->addLogEntry('You can override this by setting NIXPACKS_NODE_VERSION=22 in your environment variables.'); + } + } $this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT); $this->nixpacks_plan_json = collect($parsed); $this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true); @@ -1954,10 +2182,14 @@ private function generate_env_variables() { $this->env_args = collect([]); $this->env_args->put('SOURCE_COMMIT', $this->commit); + $coolify_envs = $this->generate_coolify_env_variables(); + $coolify_envs->each(function ($value, $key) { + $this->env_args->put($key, $value); + }); + // For build process, include only environment variables where is_buildtime = true if ($this->pull_request_id === 0) { - // Get environment variables that are marked as available during build $envs = $this->application->environment_variables() ->where('key', 'not like', 'NIXPACKS_%') ->where('is_buildtime', true) @@ -1966,24 +2198,9 @@ private function generate_env_variables() foreach ($envs as $env) { if (! is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); - if (str($env->real_value)->startsWith('$')) { - $variable_key = str($env->real_value)->after('$'); - if ($variable_key->startsWith('COOLIFY_')) { - $variable = $coolify_envs->get($variable_key->value()); - if (filled($variable)) { - $this->env_args->prepend($variable, $variable_key->value()); - } - } else { - $variable = $this->application->environment_variables()->where('key', $variable_key)->first(); - if ($variable) { - $this->env_args->prepend($variable->real_value, $env->key); - } - } - } } } } else { - // Get preview environment variables that are marked as available during build $envs = $this->application->environment_variables_preview() ->where('key', 'not like', 'NIXPACKS_%') ->where('is_buildtime', true) @@ -1992,29 +2209,9 @@ private function generate_env_variables() foreach ($envs as $env) { if (! is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); - if (str($env->real_value)->startsWith('$')) { - $variable_key = str($env->real_value)->after('$'); - if ($variable_key->startsWith('COOLIFY_')) { - $variable = $coolify_envs->get($variable_key->value()); - if (filled($variable)) { - $this->env_args->prepend($variable, $variable_key->value()); - } - } else { - $variable = $this->application->environment_variables_preview()->where('key', $variable_key)->first(); - if ($variable) { - $this->env_args->prepend($variable->real_value, $env->key); - } - } - } } } } - - // Merge COOLIFY_* variables into env_args for build process - // This ensures they're available for both build args and build secrets - $coolify_envs->each(function ($value, $key) { - $this->env_args->put($key, $value); - }); } private function generate_compose_file() @@ -2025,7 +2222,6 @@ private function generate_compose_file() $persistent_storages = $this->generate_local_persistent_volumes(); $persistent_file_volumes = $this->application->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); - $this->generate_runtime_environment_variables(); if (data_get($this->application, 'custom_labels')) { $this->application->parseContainerLabels(); $labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels))); @@ -2094,9 +2290,8 @@ private function generate_compose_file() ], ], ]; - if (filled($this->env_filename)) { - $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; - } + // Always use .env file + $docker_compose['services'][$this->container_name]['env_file'] = ['.env']; $docker_compose['services'][$this->container_name]['healthcheck'] = [ 'test' => [ 'CMD-SHELL', @@ -2381,6 +2576,18 @@ private function build_static_image() $this->application_deployment_queue->addLogEntry('Building docker image completed.'); } + /** + * Wrap a docker build command with environment export from /artifacts/build-time.env + * This enables shell interpolation of variables (e.g., APP_URL=$COOLIFY_URL) + * + * @param string $build_command The docker build command to wrap + * @return string The wrapped command with export statement + */ + private function wrap_build_command_with_env_export(string $build_command): string + { + return "cd {$this->workdir} && set -a && source /artifacts/build-time.env && set +a && {$build_command}"; + } + private function build_image() { // Add Coolify related variables to the build args/secrets @@ -2388,13 +2595,12 @@ private function build_image() // Coolify variables are already included in the secrets from generate_build_env_variables // build_secrets is already a string at this point } else { - // Traditional build args approach - $this->environment_variables->filter(function ($key, $value) { - return str($key)->startsWith('COOLIFY_'); - })->each(function ($key, $value) { + // Traditional build args approach - generate COOLIFY_ variables locally + // Generate COOLIFY_ variables locally for build args + $coolify_envs = $this->generate_coolify_env_variables(); + $coolify_envs->each(function ($value, $key) { $this->build_args->push("--build-arg '{$key}'"); }); - $this->build_args = $this->build_args instanceof \Illuminate\Support\Collection ? $this->build_args->implode(' ') : (string) $this->build_args; @@ -2431,12 +2637,13 @@ private function build_image() // Modify the nixpacks Dockerfile to use build secrets $this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile"); $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; - $build_command = "DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->workdir}"); } elseif ($this->dockerBuildkitSupported) { // BuildKit without secrets - $build_command = "DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}"); + ray($build_command); } else { - $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}"); } } else { $this->execute_remote_command([ @@ -2446,13 +2653,18 @@ private function build_image() executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"), 'hidden' => true, ]); - if ($this->dockerBuildkitSupported) { + if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) { // Modify the nixpacks Dockerfile to use build secrets $this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile"); $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; - $build_command = "DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->workdir}"); + } elseif ($this->dockerBuildkitSupported) { + // BuildKit without secrets + $this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile"); + $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}"); } else { - $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}"); } } @@ -2479,16 +2691,25 @@ private function build_image() $this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}"); $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; if ($this->force_rebuild) { - $build_command = "DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->workdir}"); } else { - $build_command = "DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->workdir}"); + } + } elseif ($this->dockerBuildkitSupported) { + // BuildKit without secrets + $this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}"); + $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; + if ($this->force_rebuild) { + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}"); + } else { + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}"); } } else { // Traditional build with args if ($this->force_rebuild) { - $build_command = "docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"); } else { - $build_command = "docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"); } } $base64_build_command = base64_encode($build_command); @@ -2507,10 +2728,12 @@ private function build_image() ] ); } + $publishDir = trim($this->application->publish_directory, '/'); + $publishDir = $publishDir ? "/{$publishDir}" : ''; $dockerfile = base64_encode("FROM {$this->application->static_image} WORKDIR /usr/share/nginx/html/ LABEL coolify.deploymentId={$this->deployment_uuid} -COPY --from=$this->build_image_name /app/{$this->application->publish_directory} . +COPY --from=$this->build_image_name /app{$publishDir} . COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if (str($this->application->custom_nginx_configuration)->isNotEmpty()) { $nginx_config = base64_encode($this->application->custom_nginx_configuration); @@ -2521,7 +2744,7 @@ private function build_image() $nginx_config = base64_encode(defaultNginxConfiguration()); } } - $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"); $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ @@ -2558,9 +2781,9 @@ private function build_image() } else { // Traditional build with args if ($this->force_rebuild) { - $build_command = "docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"); } else { - $build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"); } } $base64_build_command = base64_encode($build_command); @@ -2590,13 +2813,18 @@ private function build_image() executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"), 'hidden' => true, ]); - if ($this->dockerBuildkitSupported) { + if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) { // Modify the nixpacks Dockerfile to use build secrets $this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile"); $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; - $build_command = "DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}"); + } elseif ($this->dockerBuildkitSupported) { + // BuildKit without secrets + $this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile"); + $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}"); } else { - $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}"); } } else { $this->execute_remote_command([ @@ -2606,13 +2834,18 @@ private function build_image() executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"), 'hidden' => true, ]); - if ($this->dockerBuildkitSupported) { + if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) { // Modify the nixpacks Dockerfile to use build secrets $this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile"); $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; - $build_command = "DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}"); + } elseif ($this->dockerBuildkitSupported) { + // BuildKit without secrets + $this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile"); + $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}"); } else { - $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}"); } } $base64_build_command = base64_encode($build_command); @@ -2633,22 +2866,31 @@ private function build_image() $this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]); } else { // Dockerfile buildpack - if ($this->dockerBuildkitSupported) { + if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) { // Modify the Dockerfile to use build secrets $this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}"); // Use BuildKit with secrets $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; if ($this->force_rebuild) { - $build_command = "DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}"); + } else { + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}"); + } + } elseif ($this->dockerBuildkitSupported) { + // BuildKit without secrets + $this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}"); + $secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : ''; + if ($this->force_rebuild) { + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}"); } else { - $build_command = "DOCKER_BUILDKIT=1 docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}"); } } else { // Traditional build with args if ($this->force_rebuild) { - $build_command = "docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"); } else { - $build_command = "docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"); } } $base64_build_command = base64_encode($build_command); @@ -2924,6 +3166,7 @@ private function add_build_env_variables_to_dockerfile() executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile', + 'ignore_errors' => true, ]); $dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); if ($this->pull_request_id === 0) { @@ -2982,10 +3225,17 @@ private function add_build_env_variables_to_dockerfile() } $dockerfile_base64 = base64_encode($dockerfile->implode("\n")); - $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"), - 'hidden' => true, - ]); + $this->application_deployment_queue->addLogEntry('Final Dockerfile:', type: 'info', hidden: true); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"), + 'hidden' => true, + ], + [ + executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), + 'hidden' => true, + 'ignore_errors' => true, + ]); } } @@ -3143,7 +3393,6 @@ private function modify_dockerfiles_for_compose($composeFile) $argsToAdd->push("ARG {$key}"); } - ray($argsToAdd); if ($argsToAdd->isEmpty()) { $this->application_deployment_queue->addLogEntry("Service {$serviceName}: No build-time variables to add."); diff --git a/app/Jobs/ApplicationPullRequestUpdateJob.php b/app/Jobs/ApplicationPullRequestUpdateJob.php index ef8e6efb6e..a92f53b394 100755 --- a/app/Jobs/ApplicationPullRequestUpdateJob.php +++ b/app/Jobs/ApplicationPullRequestUpdateJob.php @@ -39,17 +39,18 @@ public function handle() $this->delete_comment(); return; - } elseif ($this->status === ProcessStatus::IN_PROGRESS) { - $this->body = "The preview deployment is in progress. 🟡\n\n"; - } elseif ($this->status === ProcessStatus::FINISHED) { - $this->body = "The preview deployment is ready. 🟢\n\n"; - if ($this->preview->fqdn) { - $this->body .= "[Open Preview]({$this->preview->fqdn}) | "; - } - } elseif ($this->status === ProcessStatus::ERROR) { - $this->body = "The preview deployment failed. 🔴\n\n"; } - $this->build_logs_url = base_url()."/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; + + match ($this->status) { + ProcessStatus::QUEUED => $this->body = "The preview deployment is queued. ⏳\n\n", + ProcessStatus::IN_PROGRESS => $this->body = "The preview deployment is in progress. 🟡\n\n", + ProcessStatus::FINISHED => $this->body = "The preview deployment is ready. 🟢\n\n".($this->preview->fqdn ? "[Open Preview]({$this->preview->fqdn}) | " : ''), + ProcessStatus::ERROR => $this->body = "The preview deployment failed. 🔴\n\n", + ProcessStatus::KILLED => $this->body = "The preview deployment was killed. ⚫\n\n", + ProcessStatus::CANCELLED => $this->body = "The preview deployment was cancelled. 🚫\n\n", + ProcessStatus::CLOSED => '', // Already handled above, but included for completeness + }; + $this->build_logs_url = base_url()."/project/{$this->application->environment->project->uuid}/environment/{$this->application->environment->uuid}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; $this->body .= '[Open Build Logs]('.$this->build_logs_url.")\n\n\n"; $this->body .= 'Last updated at: '.now()->toDateTimeString().' CET'; diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 92db14a61c..11da6fac18 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -15,6 +15,7 @@ use App\Models\Team; use App\Notifications\Database\BackupFailed; use App\Notifications\Database\BackupSuccess; +use App\Notifications\Database\BackupSuccessWithS3Warning; use Carbon\Carbon; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; @@ -68,7 +69,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue public $timeout = 3600; - public string $backup_log_uuid; + public ?string $backup_log_uuid = null; public function __construct(public ScheduledDatabaseBackup $backup) { @@ -298,6 +299,10 @@ public function handle(): void } while ($exists); $size = 0; + $localBackupSucceeded = false; + $s3UploadError = null; + + // Step 1: Create local backup try { if (str($databaseType)->contains('postgres')) { $this->backup_file = "/pg-dump-$database-".Carbon::now()->timestamp.'.dmp'; @@ -310,6 +315,7 @@ public function handle(): void 'database_name' => $database, 'filename' => $this->backup_location, 'scheduled_database_backup_id' => $this->backup->id, + 'local_storage_deleted' => false, ]); $this->backup_standalone_postgresql($database); } elseif (str($databaseType)->contains('mongo')) { @@ -330,6 +336,7 @@ public function handle(): void 'database_name' => $databaseName, 'filename' => $this->backup_location, 'scheduled_database_backup_id' => $this->backup->id, + 'local_storage_deleted' => false, ]); $this->backup_standalone_mongodb($database); } elseif (str($databaseType)->contains('mysql')) { @@ -343,6 +350,7 @@ public function handle(): void 'database_name' => $database, 'filename' => $this->backup_location, 'scheduled_database_backup_id' => $this->backup->id, + 'local_storage_deleted' => false, ]); $this->backup_standalone_mysql($database); } elseif (str($databaseType)->contains('mariadb')) { @@ -356,56 +364,77 @@ public function handle(): void 'database_name' => $database, 'filename' => $this->backup_location, 'scheduled_database_backup_id' => $this->backup->id, + 'local_storage_deleted' => false, ]); $this->backup_standalone_mariadb($database); } else { throw new \Exception('Unsupported database type'); } + $size = $this->calculate_size(); - if ($this->backup->save_s3) { + + // Verify local backup succeeded + if ($size > 0) { + $localBackupSucceeded = true; + } else { + throw new \Exception('Local backup file is empty or was not created'); + } + } catch (\Throwable $e) { + // Local backup failed + if ($this->backup_log) { + $this->backup_log->update([ + 'status' => 'failed', + 'message' => $this->error_output ?? $this->backup_output ?? $e->getMessage(), + 'size' => $size, + 'filename' => null, + 's3_uploaded' => null, + ]); + } + $this->team?->notify(new BackupFailed($this->backup, $this->database, $this->error_output ?? $this->backup_output ?? $e->getMessage(), $database)); + + continue; + } + + // Step 2: Upload to S3 if enabled (independent of local backup) + $localStorageDeleted = false; + if ($this->backup->save_s3 && $localBackupSucceeded) { + try { $this->upload_to_s3(); // If local backup is disabled, delete the local file immediately after S3 upload if ($this->backup->disable_local_backup) { deleteBackupsLocally($this->backup_location, $this->server); + $localStorageDeleted = true; } + } catch (\Throwable $e) { + // S3 upload failed but local backup succeeded + $s3UploadError = $e->getMessage(); } + } + + // Step 3: Update status and send notifications based on results + if ($localBackupSucceeded) { + $message = $this->backup_output; - $this->team->notify(new BackupSuccess($this->backup, $this->database, $database)); + if ($s3UploadError) { + $message = $message + ? $message."\n\nWarning: S3 upload failed: ".$s3UploadError + : 'Warning: S3 upload failed: '.$s3UploadError; + } $this->backup_log->update([ 'status' => 'success', - 'message' => $this->backup_output, + 'message' => $message, 'size' => $size, + 's3_uploaded' => $this->backup->save_s3 ? $this->s3_uploaded : null, + 'local_storage_deleted' => $localStorageDeleted, ]); - } catch (\Throwable $e) { - // Check if backup actually failed or if it's just a post-backup issue - $actualBackupFailed = ! $this->s3_uploaded && $this->backup->save_s3; - - if ($actualBackupFailed || $size === 0) { - // Real backup failure - if ($this->backup_log) { - $this->backup_log->update([ - 'status' => 'failed', - 'message' => $this->error_output ?? $this->backup_output ?? $e->getMessage(), - 'size' => $size, - 'filename' => null, - ]); - } - $this->team?->notify(new BackupFailed($this->backup, $this->database, $this->error_output ?? $this->backup_output ?? $e->getMessage(), $database)); + + // Send appropriate notification + if ($s3UploadError) { + $this->team->notify(new BackupSuccessWithS3Warning($this->backup, $this->database, $database, $s3UploadError)); } else { - // Backup succeeded but post-processing failed (cleanup, notification, etc.) - if ($this->backup_log) { - $this->backup_log->update([ - 'status' => 'success', - 'message' => $this->backup_output ? $this->backup_output."\nWarning: Post-backup cleanup encountered an issue: ".$e->getMessage() : 'Warning: '.$e->getMessage(), - 'size' => $size, - ]); - } - // Send success notification since the backup itself succeeded $this->team->notify(new BackupSuccess($this->backup, $this->database, $database)); - // Log the post-backup issue - ray('Post-backup operation failed but backup was successful: '.$e->getMessage()); } } } @@ -591,24 +620,24 @@ private function upload_to_s3(): void $fullImageName = $this->getFullImageName(); - $containerExists = instant_remote_process(["docker ps -a -q -f name=backup-of-{$this->backup->uuid}"], $this->server, false); + $containerExists = instant_remote_process(["docker ps -a -q -f name=backup-of-{$this->backup_log_uuid}"], $this->server, false); if (filled($containerExists)) { - instant_remote_process(["docker rm -f backup-of-{$this->backup->uuid}"], $this->server, false); + instant_remote_process(["docker rm -f backup-of-{$this->backup_log_uuid}"], $this->server, false); } if (isDev()) { if ($this->database->name === 'coolify-db') { $backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file; - $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}"; + $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup_log_uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}"; } else { $backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file; - $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}"; + $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup_log_uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}"; } } else { - $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}"; + $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup_log_uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}"; } - $commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set temporary {$endpoint} {$key} \"{$secret}\""; - $commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/"; + $commands[] = "docker exec backup-of-{$this->backup_log_uuid} mc alias set temporary {$endpoint} {$key} \"{$secret}\""; + $commands[] = "docker exec backup-of-{$this->backup_log_uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/"; instant_remote_process($commands, $this->server); $this->s3_uploaded = true; @@ -617,7 +646,7 @@ private function upload_to_s3(): void $this->add_to_error_output($e->getMessage()); throw $e; } finally { - $command = "docker rm -f backup-of-{$this->backup->uuid}"; + $command = "docker rm -f backup-of-{$this->backup_log_uuid}"; instant_remote_process([$command], $this->server); } } diff --git a/app/Jobs/RegenerateSslCertJob.php b/app/Jobs/RegenerateSslCertJob.php index cf598c75cb..c0284e1ee9 100644 --- a/app/Jobs/RegenerateSslCertJob.php +++ b/app/Jobs/RegenerateSslCertJob.php @@ -45,7 +45,7 @@ public function handle() $query->cursor()->each(function ($certificate) use ($regenerated) { try { - $caCert = SslCertificate::where('server_id', $certificate->server_id) + $caCert = $certificate->server->sslCertificates() ->where('is_ca_certificate', true) ->first(); diff --git a/app/Jobs/SendWebhookJob.php b/app/Jobs/SendWebhookJob.php new file mode 100644 index 0000000000..607fda3fe2 --- /dev/null +++ b/app/Jobs/SendWebhookJob.php @@ -0,0 +1,60 @@ +onQueue('high'); + } + + /** + * Execute the job. + */ + public function handle(): void + { + if (isDev()) { + ray('Sending webhook notification', [ + 'url' => $this->webhookUrl, + 'payload' => $this->payload, + ]); + } + + $response = Http::post($this->webhookUrl, $this->payload); + + if (isDev()) { + ray('Webhook response', [ + 'status' => $response->status(), + 'body' => $response->body(), + 'successful' => $response->successful(), + ]); + } + } +} diff --git a/app/Jobs/ServerConnectionCheckJob.php b/app/Jobs/ServerConnectionCheckJob.php index 8b55434f63..9dbce4bfe5 100644 --- a/app/Jobs/ServerConnectionCheckJob.php +++ b/app/Jobs/ServerConnectionCheckJob.php @@ -54,6 +54,11 @@ public function handle() return; } + // Check Hetzner server status if applicable + if ($this->server->hetzner_server_id && $this->server->cloudProviderToken) { + $this->checkHetznerStatus(); + } + // Temporarily disable mux if requested if ($this->disableMux) { $this->disableSshMux(); @@ -86,6 +91,11 @@ public function handle() ]); } catch (\Throwable $e) { + + Log::error('ServerConnectionCheckJob failed', [ + 'error' => $e->getMessage(), + 'server_id' => $this->server->id, + ]); $this->server->settings->update([ 'is_reachable' => false, 'is_usable' => false, @@ -95,6 +105,30 @@ public function handle() } } + private function checkHetznerStatus(): void + { + try { + $hetznerService = new \App\Services\HetznerService($this->server->cloudProviderToken->token); + $serverData = $hetznerService->getServer($this->server->hetzner_server_id); + $status = $serverData['status'] ?? null; + + } catch (\Throwable $e) { + Log::debug('ServerConnectionCheck: Hetzner status check failed', [ + 'server_id' => $this->server->id, + 'error' => $e->getMessage(), + ]); + } + if ($this->server->hetzner_server_status !== $status) { + $this->server->update(['hetzner_server_status' => $status]); + $this->server->hetzner_server_status = $status; + if ($status === 'off') { + ray('Server is powered off, marking as unreachable'); + throw new \Exception('Server is powered off'); + } + } + + } + private function checkConnection(): bool { try { diff --git a/app/Jobs/ValidateAndInstallServerJob.php b/app/Jobs/ValidateAndInstallServerJob.php new file mode 100644 index 0000000000..388791f10e --- /dev/null +++ b/app/Jobs/ValidateAndInstallServerJob.php @@ -0,0 +1,162 @@ +onQueue('high'); + } + + public function handle(): void + { + try { + // Mark validation as in progress + $this->server->update(['is_validating' => true]); + + Log::info('ValidateAndInstallServer: Starting validation', [ + 'server_id' => $this->server->id, + 'server_name' => $this->server->name, + 'attempt' => $this->numberOfTries + 1, + ]); + + // Validate connection + ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(); + if (! $uptime) { + $errorMessage = 'Server is not reachable. Please validate your configuration and connection.
Check this documentation for further help.

Error: '.$error; + $this->server->update([ + 'validation_logs' => $errorMessage, + 'is_validating' => false, + ]); + Log::error('ValidateAndInstallServer: Server not reachable', [ + 'server_id' => $this->server->id, + 'error' => $error, + ]); + + return; + } + + // Validate OS + $supportedOsType = $this->server->validateOS(); + if (! $supportedOsType) { + $errorMessage = 'Server OS type is not supported. Please install Docker manually before continuing: documentation.'; + $this->server->update([ + 'validation_logs' => $errorMessage, + 'is_validating' => false, + ]); + Log::error('ValidateAndInstallServer: OS not supported', [ + 'server_id' => $this->server->id, + ]); + + return; + } + + // Check if Docker is installed + $dockerInstalled = $this->server->validateDockerEngine(); + $dockerComposeInstalled = $this->server->validateDockerCompose(); + + if (! $dockerInstalled || ! $dockerComposeInstalled) { + // Try to install Docker + if ($this->numberOfTries >= $this->maxTries) { + $errorMessage = 'Docker Engine could not be installed after '.$this->maxTries.' attempts. Please install Docker manually before continuing: documentation.'; + $this->server->update([ + 'validation_logs' => $errorMessage, + 'is_validating' => false, + ]); + Log::error('ValidateAndInstallServer: Docker installation failed after max tries', [ + 'server_id' => $this->server->id, + 'attempts' => $this->numberOfTries, + ]); + + return; + } + + Log::info('ValidateAndInstallServer: Installing Docker', [ + 'server_id' => $this->server->id, + 'attempt' => $this->numberOfTries + 1, + ]); + + // Install Docker + $this->server->installDocker(); + + // Retry validation after installation + self::dispatch($this->server, $this->numberOfTries + 1)->delay(now()->addSeconds(30)); + + return; + } + + // Validate Docker version + $dockerVersion = $this->server->validateDockerEngineVersion(); + if (! $dockerVersion) { + $requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.'); + $errorMessage = 'Minimum Docker Engine version '.$requiredDockerVersion.' is not installed. Please install Docker manually before continuing: documentation.'; + $this->server->update([ + 'validation_logs' => $errorMessage, + 'is_validating' => false, + ]); + Log::error('ValidateAndInstallServer: Docker version not sufficient', [ + 'server_id' => $this->server->id, + ]); + + return; + } + + // Validation successful! + Log::info('ValidateAndInstallServer: Validation successful', [ + 'server_id' => $this->server->id, + 'server_name' => $this->server->name, + ]); + + // Start proxy if needed + if (! $this->server->isBuildServer()) { + $proxyShouldRun = CheckProxy::run($this->server, true); + if ($proxyShouldRun) { + StartProxy::dispatch($this->server); + } + } + + // Mark validation as complete + $this->server->update(['is_validating' => false]); + + // Refresh server to get latest state + $this->server->refresh(); + + // Broadcast events to update UI + ServerValidated::dispatch($this->server->team_id, $this->server->uuid); + ServerReachabilityChanged::dispatch($this->server); + + } catch (\Throwable $e) { + Log::error('ValidateAndInstallServer: Exception occurred', [ + 'server_id' => $this->server->id, + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + $this->server->update([ + 'validation_logs' => 'An error occurred during validation: '.$e->getMessage(), + 'is_validating' => false, + ]); + } + } +} diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php index 430470fa00..ac2b9213b7 100644 --- a/app/Livewire/Boarding/Index.php +++ b/app/Livewire/Boarding/Index.php @@ -16,14 +16,18 @@ class Index extends Component { protected $listeners = ['refreshBoardingIndex' => 'validateServer']; + #[\Livewire\Attributes\Url(as: 'step', history: true)] public string $currentState = 'welcome'; + #[\Livewire\Attributes\Url(keep: true)] public ?string $selectedServerType = null; public ?Collection $privateKeys = null; + #[\Livewire\Attributes\Url(keep: true)] public ?int $selectedExistingPrivateKey = null; + #[\Livewire\Attributes\Url(keep: true)] public ?string $privateKeyType = null; public ?string $privateKey = null; @@ -38,6 +42,7 @@ class Index extends Component public ?Collection $servers = null; + #[\Livewire\Attributes\Url(keep: true)] public ?int $selectedExistingServer = null; public ?string $remoteServerName = null; @@ -58,6 +63,7 @@ class Index extends Component public Collection $projects; + #[\Livewire\Attributes\Url(keep: true)] public ?int $selectedProject = null; public ?Project $createdProject = null; @@ -79,17 +85,68 @@ public function mount() $this->minDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.'); $this->privateKeyName = generate_random_name(); $this->remoteServerName = generate_random_name(); - if (isDev()) { - $this->privateKey = '-----BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW -QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk -hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA -AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV -uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== ------END OPENSSH PRIVATE KEY-----'; - $this->privateKeyDescription = 'Created by Coolify'; - $this->remoteServerDescription = 'Created by Coolify'; - $this->remoteServerHost = 'coolify-testing-host'; + + // Initialize collections to avoid null errors + if ($this->privateKeys === null) { + $this->privateKeys = collect(); + } + if ($this->servers === null) { + $this->servers = collect(); + } + if (! isset($this->projects)) { + $this->projects = collect(); + } + + // Restore state when coming from URL with query params + if ($this->selectedServerType === 'localhost' && $this->selectedExistingServer === 0) { + $this->createdServer = Server::find(0); + if ($this->createdServer) { + $this->serverPublicKey = $this->createdServer->privateKey->getPublicKey(); + } + } + + if ($this->selectedServerType === 'remote') { + if ($this->privateKeys->isEmpty()) { + $this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get(); + } + if ($this->servers->isEmpty()) { + $this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get(); + } + + if ($this->selectedExistingServer) { + $this->createdServer = Server::find($this->selectedExistingServer); + if ($this->createdServer) { + $this->serverPublicKey = $this->createdServer->privateKey->getPublicKey(); + $this->updateServerDetails(); + } + } + + if ($this->selectedExistingPrivateKey) { + $this->createdPrivateKey = PrivateKey::where('team_id', currentTeam()->id) + ->where('id', $this->selectedExistingPrivateKey) + ->first(); + if ($this->createdPrivateKey) { + $this->privateKey = $this->createdPrivateKey->private_key; + $this->publicKey = $this->createdPrivateKey->getPublicKey(); + } + } + + // Auto-regenerate key pair for "Generate with Coolify" mode on page refresh + if ($this->privateKeyType === 'create' && empty($this->privateKey)) { + $this->createNewPrivateKey(); + } + } + + if ($this->selectedProject) { + $this->createdProject = Project::find($this->selectedProject); + if (! $this->createdProject) { + $this->projects = Project::ownedByCurrentTeam(['name'])->get(); + } + } + + // Load projects when on create-project state (for page refresh) + if ($this->currentState === 'create-project' && $this->projects->isEmpty()) { + $this->projects = Project::ownedByCurrentTeam(['name'])->get(); } } @@ -129,41 +186,16 @@ public function setServerType(string $type) return $this->validateServer('localhost'); } elseif ($this->selectedServerType === 'remote') { - if (isDev()) { - $this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->get(); - } else { - $this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get(); - } + $this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get(); + // Auto-select first key if available for better UX if ($this->privateKeys->count() > 0) { $this->selectedExistingPrivateKey = $this->privateKeys->first()->id; } - $this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get(); - if ($this->servers->count() > 0) { - $this->selectedExistingServer = $this->servers->first()->id; - $this->updateServerDetails(); - $this->currentState = 'select-existing-server'; - - return; - } + // Onboarding always creates new servers, skip existing server selection $this->currentState = 'private-key'; } } - public function selectExistingServer() - { - $this->createdServer = Server::find($this->selectedExistingServer); - if (! $this->createdServer) { - $this->dispatch('error', 'Server is not found.'); - $this->currentState = 'private-key'; - - return; - } - $this->selectedExistingPrivateKey = $this->createdServer->privateKey->id; - $this->serverPublicKey = $this->createdServer->privateKey->getPublicKey(); - $this->updateServerDetails(); - $this->currentState = 'validate-server'; - } - private function updateServerDetails() { if ($this->createdServer) { @@ -181,7 +213,7 @@ public function getProxyType() public function selectExistingPrivateKey() { if (is_null($this->selectedExistingPrivateKey)) { - $this->restartBoarding(); + $this->dispatch('error', 'Please select a private key.'); return; } @@ -202,6 +234,9 @@ public function setPrivateKey(string $type) $this->privateKeyType = $type; if ($type === 'create') { $this->createNewPrivateKey(); + } else { + $this->privateKey = null; + $this->publicKey = null; } $this->currentState = 'create-private-key'; } diff --git a/app/Livewire/DeploymentsIndicator.php b/app/Livewire/DeploymentsIndicator.php index 0293ad6c6c..ac9cfd1c2d 100644 --- a/app/Livewire/DeploymentsIndicator.php +++ b/app/Livewire/DeploymentsIndicator.php @@ -16,7 +16,8 @@ public function deployments() { $servers = Server::ownedByCurrentTeam()->get(); - return ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued']) + return ApplicationDeploymentQueue::with(['application.environment.project']) + ->whereIn('status', ['in_progress', 'queued']) ->whereIn('server_id', $servers->pluck('id')) ->orderBy('id') ->get([ diff --git a/app/Livewire/GlobalSearch.php b/app/Livewire/GlobalSearch.php index 15de5d8386..e1dd678ff8 100644 --- a/app/Livewire/GlobalSearch.php +++ b/app/Livewire/GlobalSearch.php @@ -22,24 +22,66 @@ class GlobalSearch extends Component { public $searchQuery = ''; + private $previousTrimmedQuery = ''; + public $isModalOpen = false; public $searchResults = []; public $allSearchableItems = []; + public $isCreateMode = false; + + public $creatableItems = []; + + public $autoOpenResource = null; + + // Resource selection state + public $isSelectingResource = false; + + public $selectedResourceType = null; + + public $loadingServers = false; + + public $loadingProjects = false; + + public $loadingEnvironments = false; + + public $availableServers = []; + + public $availableProjects = []; + + public $availableEnvironments = []; + + public $selectedServerId = null; + + public $selectedDestinationUuid = null; + + public $selectedProjectUuid = null; + + public $selectedEnvironmentUuid = null; + + public $availableDestinations = []; + + public $loadingDestinations = false; + public function mount() { $this->searchQuery = ''; $this->isModalOpen = false; $this->searchResults = []; $this->allSearchableItems = []; + $this->isCreateMode = false; + $this->creatableItems = []; + $this->autoOpenResource = null; + $this->isSelectingResource = false; } public function openSearchModal() { $this->isModalOpen = true; $this->loadSearchableItems(); + $this->loadCreatableItems(); $this->dispatch('search-modal-opened'); } @@ -47,6 +89,7 @@ public function closeSearchModal() { $this->isModalOpen = false; $this->searchQuery = ''; + $this->previousTrimmedQuery = ''; $this->searchResults = []; } @@ -62,7 +105,144 @@ public static function clearTeamCache($teamId) public function updatedSearchQuery() { - $this->search(); + $trimmedQuery = trim($this->searchQuery); + + // If only spaces were added/removed, don't trigger a search + if ($trimmedQuery === $this->previousTrimmedQuery) { + return; + } + + $this->previousTrimmedQuery = $trimmedQuery; + + // If search query is empty, just clear results without processing + if (empty($trimmedQuery)) { + $this->searchResults = []; + $this->isCreateMode = false; + $this->creatableItems = []; + $this->autoOpenResource = null; + $this->isSelectingResource = false; + $this->cancelResourceSelection(); + + return; + } + + $query = strtolower($trimmedQuery); + + // Reset keyboard navigation index + $this->dispatch('reset-selected-index'); + + // Only enter create mode if query is exactly "new" or starts with "new " (space after) + if ($query === 'new' || str_starts_with($query, 'new ')) { + $this->isCreateMode = true; + $this->loadCreatableItems(); + + // Check for sub-commands like "new project", "new server", etc. + $detectedType = $this->detectSpecificResource($query); + if ($detectedType) { + $this->navigateToResource($detectedType); + } else { + // If no specific resource detected, reset selection state + $this->cancelResourceSelection(); + } + + // Also search for existing resources that match the query + // This allows users to find resources with "new" in their name + $this->search(); + } else { + $this->isCreateMode = false; + $this->creatableItems = []; + $this->autoOpenResource = null; + $this->isSelectingResource = false; + $this->search(); + } + } + + private function detectSpecificResource(string $query): ?string + { + // Map of keywords to resource types - order matters for multi-word matches + $resourceMap = [ + // Quick Actions + 'new project' => 'project', + 'new server' => 'server', + 'new team' => 'team', + 'new storage' => 'storage', + 'new s3' => 'storage', + 'new private key' => 'private-key', + 'new privatekey' => 'private-key', + 'new key' => 'private-key', + 'new github app' => 'source', + 'new github' => 'source', + 'new source' => 'source', + + // Applications - Git-based + 'new public' => 'public', + 'new public git' => 'public', + 'new public repo' => 'public', + 'new public repository' => 'public', + 'new private github' => 'private-gh-app', + 'new private gh' => 'private-gh-app', + 'new private deploy' => 'private-deploy-key', + 'new deploy key' => 'private-deploy-key', + + // Applications - Docker-based + 'new dockerfile' => 'dockerfile', + 'new docker compose' => 'docker-compose-empty', + 'new compose' => 'docker-compose-empty', + 'new docker image' => 'docker-image', + 'new image' => 'docker-image', + + // Databases + 'new postgresql' => 'postgresql', + 'new postgres' => 'postgresql', + 'new mysql' => 'mysql', + 'new mariadb' => 'mariadb', + 'new redis' => 'redis', + 'new keydb' => 'keydb', + 'new dragonfly' => 'dragonfly', + 'new mongodb' => 'mongodb', + 'new mongo' => 'mongodb', + 'new clickhouse' => 'clickhouse', + ]; + + foreach ($resourceMap as $command => $type) { + if ($query === $command) { + // Check if user has permission for this resource type + if ($this->canCreateResource($type)) { + return $type; + } + } + } + + return null; + } + + private function canCreateResource(string $type): bool + { + $user = auth()->user(); + + // Quick Actions + if (in_array($type, ['server', 'storage', 'private-key'])) { + return $user->isAdmin() || $user->isOwner(); + } + + if ($type === 'team') { + return true; + } + + // Applications, Databases, Services, and other resources + if (in_array($type, [ + 'project', 'source', + // Applications + 'public', 'private-gh-app', 'private-deploy-key', + 'dockerfile', 'docker-compose-empty', 'docker-image', + // Databases + 'postgresql', 'mysql', 'mariadb', 'redis', 'keydb', + 'dragonfly', 'mongodb', 'clickhouse', + ]) || str_starts_with($type, 'one-click-service-')) { + return $user->can('createAnyResource'); + } + + return false; } private function loadSearchableItems() @@ -116,7 +296,7 @@ private function loadSearchableItems() 'project' => $app->environment->project->name ?? null, 'environment' => $app->environment->name ?? null, 'fqdns' => $fqdns->take(2)->implode(', '), // Show first 2 FQDNs in UI - 'search_text' => strtolower($app->name.' '.$app->description.' '.$fqdnsString), + 'search_text' => strtolower($app->name.' '.$app->description.' '.$fqdnsString.' application applications app apps'), ]; }); @@ -145,7 +325,7 @@ private function loadSearchableItems() 'project' => $service->environment->project->name ?? null, 'environment' => $service->environment->name ?? null, 'fqdns' => $fqdns->take(2)->implode(', '), // Show first 2 FQDNs in UI - 'search_text' => strtolower($service->name.' '.$service->description.' '.$fqdnsString), + 'search_text' => strtolower($service->name.' '.$service->description.' '.$fqdnsString.' service services'), ]; }); @@ -168,7 +348,7 @@ private function loadSearchableItems() 'link' => $db->link(), 'project' => $db->environment->project->name ?? null, 'environment' => $db->environment->name ?? null, - 'search_text' => strtolower($db->name.' postgresql '.$db->description), + 'search_text' => strtolower($db->name.' postgresql '.$db->description.' database databases db'), ]; }) ); @@ -189,7 +369,7 @@ private function loadSearchableItems() 'link' => $db->link(), 'project' => $db->environment->project->name ?? null, 'environment' => $db->environment->name ?? null, - 'search_text' => strtolower($db->name.' mysql '.$db->description), + 'search_text' => strtolower($db->name.' mysql '.$db->description.' database databases db'), ]; }) ); @@ -210,7 +390,7 @@ private function loadSearchableItems() 'link' => $db->link(), 'project' => $db->environment->project->name ?? null, 'environment' => $db->environment->name ?? null, - 'search_text' => strtolower($db->name.' mariadb '.$db->description), + 'search_text' => strtolower($db->name.' mariadb '.$db->description.' database databases db'), ]; }) ); @@ -231,7 +411,7 @@ private function loadSearchableItems() 'link' => $db->link(), 'project' => $db->environment->project->name ?? null, 'environment' => $db->environment->name ?? null, - 'search_text' => strtolower($db->name.' mongodb '.$db->description), + 'search_text' => strtolower($db->name.' mongodb '.$db->description.' database databases db'), ]; }) ); @@ -252,7 +432,7 @@ private function loadSearchableItems() 'link' => $db->link(), 'project' => $db->environment->project->name ?? null, 'environment' => $db->environment->name ?? null, - 'search_text' => strtolower($db->name.' redis '.$db->description), + 'search_text' => strtolower($db->name.' redis '.$db->description.' database databases db'), ]; }) ); @@ -273,7 +453,7 @@ private function loadSearchableItems() 'link' => $db->link(), 'project' => $db->environment->project->name ?? null, 'environment' => $db->environment->name ?? null, - 'search_text' => strtolower($db->name.' keydb '.$db->description), + 'search_text' => strtolower($db->name.' keydb '.$db->description.' database databases db'), ]; }) ); @@ -294,7 +474,7 @@ private function loadSearchableItems() 'link' => $db->link(), 'project' => $db->environment->project->name ?? null, 'environment' => $db->environment->name ?? null, - 'search_text' => strtolower($db->name.' dragonfly '.$db->description), + 'search_text' => strtolower($db->name.' dragonfly '.$db->description.' database databases db'), ]; }) ); @@ -315,7 +495,7 @@ private function loadSearchableItems() 'link' => $db->link(), 'project' => $db->environment->project->name ?? null, 'environment' => $db->environment->name ?? null, - 'search_text' => strtolower($db->name.' clickhouse '.$db->description), + 'search_text' => strtolower($db->name.' clickhouse '.$db->description.' database databases db'), ]; }) ); @@ -333,10 +513,10 @@ private function loadSearchableItems() 'link' => $server->url(), 'project' => null, 'environment' => null, - 'search_text' => strtolower($server->name.' '.$server->ip.' '.$server->description), + 'search_text' => strtolower($server->name.' '.$server->ip.' '.$server->description.' server servers'), ]; }); - + ray($servers); // Get all projects $projects = Project::ownedByCurrentTeam() ->withCount(['environments', 'applications', 'services']) @@ -358,15 +538,12 @@ private function loadSearchableItems() 'environment' => null, 'resource_count' => $resourceSummary, 'environment_count' => $project->environments_count, - 'search_text' => strtolower($project->name.' '.$project->description.' project'), + 'search_text' => strtolower($project->name.' '.$project->description.' project projects'), ]; }); // Get all environments - $environments = Environment::query() - ->whereHas('project', function ($query) { - $query->where('team_id', auth()->user()->currentTeam()->id); - }) + $environments = Environment::ownedByCurrentTeam() ->with('project') ->withCount(['applications', 'services']) ->get() @@ -405,8 +582,143 @@ private function loadSearchableItems() ]; }); + // Add navigation routes + $navigation = collect([ + [ + 'name' => 'Dashboard', + 'type' => 'navigation', + 'description' => 'Go to main dashboard', + 'link' => route('dashboard'), + 'search_text' => 'dashboard home main overview', + ], + [ + 'name' => 'Servers', + 'type' => 'navigation', + 'description' => 'View all servers', + 'link' => route('server.index'), + 'search_text' => 'servers all list view', + ], + [ + 'name' => 'Projects', + 'type' => 'navigation', + 'description' => 'View all projects', + 'link' => route('project.index'), + 'search_text' => 'projects all list view', + ], + [ + 'name' => 'Destinations', + 'type' => 'navigation', + 'description' => 'View all destinations', + 'link' => route('destination.index'), + 'search_text' => 'destinations docker networks', + ], + [ + 'name' => 'Security', + 'type' => 'navigation', + 'description' => 'Manage private keys and API tokens', + 'link' => route('security.private-key.index'), + 'search_text' => 'security private keys ssh api tokens cloud-init scripts', + ], + [ + 'name' => 'Cloud-Init Scripts', + 'type' => 'navigation', + 'description' => 'Manage reusable cloud-init scripts', + 'link' => route('security.cloud-init-scripts'), + 'search_text' => 'cloud-init scripts cloud init cloudinit initialization startup server setup', + ], + [ + 'name' => 'Sources', + 'type' => 'navigation', + 'description' => 'Manage GitHub apps and Git sources', + 'link' => route('source.all'), + 'search_text' => 'sources github apps git repositories', + ], + [ + 'name' => 'Storages', + 'type' => 'navigation', + 'description' => 'Manage S3 storage for backups', + 'link' => route('storage.index'), + 'search_text' => 'storages s3 backups', + ], + [ + 'name' => 'Shared Variables', + 'type' => 'navigation', + 'description' => 'View all shared variables', + 'link' => route('shared-variables.index'), + 'search_text' => 'shared variables environment all', + ], + [ + 'name' => 'Team Shared Variables', + 'type' => 'navigation', + 'description' => 'Manage team-wide shared variables', + 'link' => route('shared-variables.team.index'), + 'search_text' => 'shared variables team environment', + ], + [ + 'name' => 'Project Shared Variables', + 'type' => 'navigation', + 'description' => 'Manage project shared variables', + 'link' => route('shared-variables.project.index'), + 'search_text' => 'shared variables project environment', + ], + [ + 'name' => 'Environment Shared Variables', + 'type' => 'navigation', + 'description' => 'Manage environment shared variables', + 'link' => route('shared-variables.environment.index'), + 'search_text' => 'shared variables environment', + ], + [ + 'name' => 'Tags', + 'type' => 'navigation', + 'description' => 'View resources by tags', + 'link' => route('tags.show'), + 'search_text' => 'tags labels organize', + ], + [ + 'name' => 'Terminal', + 'type' => 'navigation', + 'description' => 'Access server terminal', + 'link' => route('terminal'), + 'search_text' => 'terminal ssh console shell command line', + ], + [ + 'name' => 'Profile', + 'type' => 'navigation', + 'description' => 'Manage your profile and preferences', + 'link' => route('profile'), + 'search_text' => 'profile account user settings preferences', + ], + [ + 'name' => 'Team', + 'type' => 'navigation', + 'description' => 'Manage team members and settings', + 'link' => route('team.index'), + 'search_text' => 'team settings members users invitations', + ], + [ + 'name' => 'Notifications', + 'type' => 'navigation', + 'description' => 'Configure email, Discord, Telegram notifications', + 'link' => route('notifications.email'), + 'search_text' => 'notifications alerts email discord telegram slack pushover', + ], + ]); + + // Add instance settings only for self-hosted and root team + if (! isCloud() && $team->id === 0) { + $navigation->push([ + 'name' => 'Settings', + 'type' => 'navigation', + 'description' => 'Instance settings and configuration', + 'link' => route('settings.index'), + 'search_text' => 'settings configuration instance', + ]); + } + // Merge all collections - $items = $items->merge($applications) + $items = $items->merge($navigation) + ->merge($applications) ->merge($services) ->merge($databases) ->merge($servers) @@ -419,7 +731,7 @@ private function loadSearchableItems() private function search() { - if (strlen($this->searchQuery) < 2) { + if (strlen($this->searchQuery) < 1) { $this->searchResults = []; return; @@ -427,14 +739,723 @@ private function search() $query = strtolower($this->searchQuery); - // Case-insensitive search in the items - $this->searchResults = collect($this->allSearchableItems) + // Detect resource category queries + $categoryMapping = [ + 'server' => ['server', 'type' => 'server'], + 'servers' => ['server', 'type' => 'server'], + 'app' => ['application', 'type' => 'application'], + 'apps' => ['application', 'type' => 'application'], + 'application' => ['application', 'type' => 'application'], + 'applications' => ['application', 'type' => 'application'], + 'db' => ['database', 'type' => 'standalone-postgresql'], + 'database' => ['database', 'type' => 'standalone-postgresql'], + 'databases' => ['database', 'type' => 'standalone-postgresql'], + 'service' => ['service', 'category' => 'Services'], + 'services' => ['service', 'category' => 'Services'], + 'project' => ['project', 'type' => 'project'], + 'projects' => ['project', 'type' => 'project'], + ]; + + $priorityCreatableItem = null; + + // Check if query matches a resource category + if (isset($categoryMapping[$query])) { + $this->loadCreatableItems(); + $mapping = $categoryMapping[$query]; + + // Find the matching creatable item + $priorityCreatableItem = collect($this->creatableItems) + ->first(function ($item) use ($mapping) { + if (isset($mapping['type'])) { + return $item['type'] === $mapping['type']; + } + if (isset($mapping['category'])) { + return isset($item['category']) && $item['category'] === $mapping['category']; + } + + return false; + }); + + if ($priorityCreatableItem) { + $priorityCreatableItem['is_creatable_suggestion'] = true; + } + } + + // Search for matching creatable resources to show as suggestions (if no priority item) + if (! $priorityCreatableItem) { + $this->loadCreatableItems(); + + // Search in regular creatable items (apps, databases, quick actions) + $creatableSuggestions = collect($this->creatableItems) + ->filter(function ($item) use ($query) { + $searchText = strtolower($item['name'].' '.$item['description'].' '.($item['type'] ?? '')); + + // Use word boundary matching to avoid substring matches (e.g., "wordpress" shouldn't match "classicpress") + return preg_match('/\b'.preg_quote($query, '/').'/i', $searchText); + }) + ->map(function ($item) use ($query) { + // Calculate match priority: name > type > description + $name = strtolower($item['name']); + $type = strtolower($item['type'] ?? ''); + $description = strtolower($item['description']); + + if (preg_match('/\b'.preg_quote($query, '/').'/i', $name)) { + $item['match_priority'] = 1; + } elseif (preg_match('/\b'.preg_quote($query, '/').'/i', $type)) { + $item['match_priority'] = 2; + } else { + $item['match_priority'] = 3; + } + + $item['is_creatable_suggestion'] = true; + + return $item; + }); + + // Also search in services (loaded on-demand) + $serviceSuggestions = collect($this->services) + ->filter(function ($item) use ($query) { + $searchText = strtolower($item['name'].' '.$item['description'].' '.($item['type'] ?? '')); + + return preg_match('/\b'.preg_quote($query, '/').'/i', $searchText); + }) + ->map(function ($item) use ($query) { + // Calculate match priority: name > type > description + $name = strtolower($item['name']); + $type = strtolower($item['type'] ?? ''); + $description = strtolower($item['description']); + + if (preg_match('/\b'.preg_quote($query, '/').'/i', $name)) { + $item['match_priority'] = 1; + } elseif (preg_match('/\b'.preg_quote($query, '/').'/i', $type)) { + $item['match_priority'] = 2; + } else { + $item['match_priority'] = 3; + } + + $item['is_creatable_suggestion'] = true; + + return $item; + }); + + // Merge and sort all suggestions + $creatableSuggestions = $creatableSuggestions + ->merge($serviceSuggestions) + ->sortBy('match_priority') + ->take(10) + ->values() + ->toArray(); + } else { + $creatableSuggestions = []; + } + + // Case-insensitive search in existing resources + $existingResults = collect($this->allSearchableItems) ->filter(function ($item) use ($query) { - return str_contains($item['search_text'], $query); + // Use word boundary matching to avoid substring matches (e.g., "wordpress" shouldn't match "classicpress") + return preg_match('/\b'.preg_quote($query, '/').'/i', $item['search_text']); + }) + ->map(function ($item) use ($query) { + // Calculate match priority: name > type > description + $name = strtolower($item['name'] ?? ''); + $type = strtolower($item['type'] ?? ''); + $description = strtolower($item['description'] ?? ''); + + if (preg_match('/\b'.preg_quote($query, '/').'/i', $name)) { + $item['match_priority'] = 1; + } elseif (preg_match('/\b'.preg_quote($query, '/').'/i', $type)) { + $item['match_priority'] = 2; + } else { + $item['match_priority'] = 3; + } + + return $item; }) + ->sortBy('match_priority') ->take(20) ->values() ->toArray(); + + // Merge results: existing resources first, then priority create item, then other creatable suggestions + $results = []; + + // If we have existing results, show them first + $results = array_merge($results, $existingResults); + + // Then show the priority "Create New" item (if exists) + if ($priorityCreatableItem) { + $results[] = $priorityCreatableItem; + } + + // Finally show other creatable suggestions + $results = array_merge($results, $creatableSuggestions); + + $this->searchResults = $results; + } + + private function loadCreatableItems() + { + $items = collect(); + $user = auth()->user(); + + // === Quick Actions Category === + + // Project - can be created if user has createAnyResource permission + if ($user->can('createAnyResource')) { + $items->push([ + 'name' => 'Project', + 'description' => 'Create a new project to organize your resources', + 'quickcommand' => '(type: new project)', + 'type' => 'project', + 'category' => 'Quick Actions', + 'component' => 'project.add-empty', + ]); + } + + // Server - can be created if user is admin or owner + if ($user->isAdmin() || $user->isOwner()) { + $items->push([ + 'name' => 'Server', + 'description' => 'Add a new server to deploy your applications', + 'quickcommand' => '(type: new server)', + 'type' => 'server', + 'category' => 'Quick Actions', + 'component' => 'server.create', + ]); + } + + // Team - can be created by anyone (they become owner of new team) + $items->push([ + 'name' => 'Team', + 'description' => 'Create a new team to collaborate with others', + 'quickcommand' => '(type: new team)', + 'type' => 'team', + 'category' => 'Quick Actions', + 'component' => 'team.create', + ]); + + // Storage - can be created if user is admin or owner + if ($user->isAdmin() || $user->isOwner()) { + $items->push([ + 'name' => 'S3 Storage', + 'description' => 'Add S3 storage for backups and file uploads', + 'quickcommand' => '(type: new storage)', + 'type' => 'storage', + 'category' => 'Quick Actions', + 'component' => 'storage.create', + ]); + } + + // Private Key - can be created if user is admin or owner + if ($user->isAdmin() || $user->isOwner()) { + $items->push([ + 'name' => 'Private Key', + 'description' => 'Add an SSH private key for server access', + 'quickcommand' => '(type: new private key)', + 'type' => 'private-key', + 'category' => 'Quick Actions', + 'component' => 'security.private-key.create', + ]); + } + + // GitHub Source - can be created if user has createAnyResource permission + if ($user->can('createAnyResource')) { + $items->push([ + 'name' => 'GitHub App', + 'description' => 'Connect a GitHub app for source control', + 'quickcommand' => '(type: new github)', + 'type' => 'source', + 'category' => 'Quick Actions', + 'component' => 'source.github.create', + ]); + } + + // === Applications Category === + + if ($user->can('createAnyResource')) { + // Git-based applications + $items->push([ + 'name' => 'Public Git Repository', + 'description' => 'Deploy from any public Git repository', + 'quickcommand' => '(type: new public)', + 'type' => 'public', + 'category' => 'Applications', + 'resourceType' => 'application', + ]); + + $items->push([ + 'name' => 'Private Repository (GitHub App)', + 'description' => 'Deploy private repositories through GitHub Apps', + 'quickcommand' => '(type: new private github)', + 'type' => 'private-gh-app', + 'category' => 'Applications', + 'resourceType' => 'application', + ]); + + $items->push([ + 'name' => 'Private Repository (Deploy Key)', + 'description' => 'Deploy private repositories with a deploy key', + 'quickcommand' => '(type: new private deploy)', + 'type' => 'private-deploy-key', + 'category' => 'Applications', + 'resourceType' => 'application', + ]); + + // Docker-based applications + $items->push([ + 'name' => 'Dockerfile', + 'description' => 'Deploy a simple Dockerfile without Git', + 'quickcommand' => '(type: new dockerfile)', + 'type' => 'dockerfile', + 'category' => 'Applications', + 'resourceType' => 'application', + ]); + + $items->push([ + 'name' => 'Docker Compose', + 'description' => 'Deploy complex applications with Docker Compose', + 'quickcommand' => '(type: new compose)', + 'type' => 'docker-compose-empty', + 'category' => 'Applications', + 'resourceType' => 'application', + ]); + + $items->push([ + 'name' => 'Docker Image', + 'description' => 'Deploy an existing Docker image from any registry', + 'quickcommand' => '(type: new image)', + 'type' => 'docker-image', + 'category' => 'Applications', + 'resourceType' => 'application', + ]); + } + + // === Databases Category === + + if ($user->can('createAnyResource')) { + $items->push([ + 'name' => 'PostgreSQL', + 'description' => 'Robust, advanced open-source database', + 'quickcommand' => '(type: new postgresql)', + 'type' => 'postgresql', + 'category' => 'Databases', + 'resourceType' => 'database', + ]); + + $items->push([ + 'name' => 'MySQL', + 'description' => 'Popular open-source relational database', + 'quickcommand' => '(type: new mysql)', + 'type' => 'mysql', + 'category' => 'Databases', + 'resourceType' => 'database', + ]); + + $items->push([ + 'name' => 'MariaDB', + 'description' => 'Community-developed fork of MySQL', + 'quickcommand' => '(type: new mariadb)', + 'type' => 'mariadb', + 'category' => 'Databases', + 'resourceType' => 'database', + ]); + + $items->push([ + 'name' => 'Redis', + 'description' => 'In-memory data structure store', + 'quickcommand' => '(type: new redis)', + 'type' => 'redis', + 'category' => 'Databases', + 'resourceType' => 'database', + ]); + + $items->push([ + 'name' => 'KeyDB', + 'description' => 'High-performance Redis alternative', + 'quickcommand' => '(type: new keydb)', + 'type' => 'keydb', + 'category' => 'Databases', + 'resourceType' => 'database', + ]); + + $items->push([ + 'name' => 'Dragonfly', + 'description' => 'Modern in-memory datastore', + 'quickcommand' => '(type: new dragonfly)', + 'type' => 'dragonfly', + 'category' => 'Databases', + 'resourceType' => 'database', + ]); + + $items->push([ + 'name' => 'MongoDB', + 'description' => 'Document-oriented NoSQL database', + 'quickcommand' => '(type: new mongodb)', + 'type' => 'mongodb', + 'category' => 'Databases', + 'resourceType' => 'database', + ]); + + $items->push([ + 'name' => 'Clickhouse', + 'description' => 'Column-oriented database for analytics', + 'quickcommand' => '(type: new clickhouse)', + 'type' => 'clickhouse', + 'category' => 'Databases', + 'resourceType' => 'database', + ]); + } + + // Merge with services + $items = $items->merge(collect($this->services)); + + $this->creatableItems = $items->toArray(); + } + + public function navigateToResource($type) + { + // Find the item by type - check regular items first, then services + $item = collect($this->creatableItems)->firstWhere('type', $type); + + if (! $item) { + $item = collect($this->services)->firstWhere('type', $type); + } + + if (! $item) { + return; + } + + // If it has a component, it's a modal-based resource + // Close search modal and open the appropriate creation modal + if (isset($item['component'])) { + $this->dispatch('closeSearchModal'); + $this->dispatch('open-create-modal-'.$type); + + return; + } + + // For applications, databases, and services, navigate to resource creation + // with smart defaults (auto-select if only 1 server/project/environment) + if (isset($item['resourceType'])) { + $this->navigateToResourceCreation($type); + } + } + + private function navigateToResourceCreation($type) + { + // Start the selection flow + $this->selectedResourceType = $type; + $this->isSelectingResource = true; + + // Clear search query to show selection UI instead of creatable items + $this->searchQuery = ''; + + // Reset selections + $this->selectedServerId = null; + $this->selectedDestinationUuid = null; + $this->selectedProjectUuid = null; + $this->selectedEnvironmentUuid = null; + + // Start loading servers first (in order: servers -> destinations -> projects -> environments) + $this->loadServers(); + } + + public function loadServers() + { + $this->loadingServers = true; + $servers = Server::isUsable()->get()->sortBy('name'); + $this->availableServers = $servers->map(fn ($s) => [ + 'id' => $s->id, + 'name' => $s->name, + 'description' => $s->description, + ])->toArray(); + $this->loadingServers = false; + + // Auto-select if only one server + if (count($this->availableServers) === 1) { + $this->selectServer($this->availableServers[0]['id']); + } + } + + public function selectServer($serverId, $shouldProgress = true) + { + $this->selectedServerId = $serverId; + + if ($shouldProgress) { + $this->loadDestinations(); + } + } + + public function loadDestinations() + { + $this->loadingDestinations = true; + $server = Server::find($this->selectedServerId); + + if (! $server) { + $this->loadingDestinations = false; + + return $this->dispatch('error', message: 'Server not found'); + } + + $destinations = $server->destinations(); + + if ($destinations->isEmpty()) { + $this->loadingDestinations = false; + + return $this->dispatch('error', message: 'No destinations found on this server'); + } + + $this->availableDestinations = $destinations->map(fn ($d) => [ + 'uuid' => $d->uuid, + 'name' => $d->name, + 'network' => $d->network ?? 'default', + ])->toArray(); + + $this->loadingDestinations = false; + + // Auto-select if only one destination + if (count($this->availableDestinations) === 1) { + $this->selectDestination($this->availableDestinations[0]['uuid']); + } + } + + public function selectDestination($destinationUuid, $shouldProgress = true) + { + $this->selectedDestinationUuid = $destinationUuid; + + if ($shouldProgress) { + $this->loadProjects(); + } + } + + public function loadProjects() + { + $this->loadingProjects = true; + $user = auth()->user(); + $team = $user->currentTeam(); + $projects = Project::where('team_id', $team->id)->get(); + + if ($projects->isEmpty()) { + $this->loadingProjects = false; + + return $this->dispatch('error', message: 'Please create a project first'); + } + + $this->availableProjects = $projects->map(fn ($p) => [ + 'uuid' => $p->uuid, + 'name' => $p->name, + 'description' => $p->description, + ])->toArray(); + $this->loadingProjects = false; + + // Auto-select if only one project + if (count($this->availableProjects) === 1) { + $this->selectProject($this->availableProjects[0]['uuid']); + } + } + + public function selectProject($projectUuid, $shouldProgress = true) + { + $this->selectedProjectUuid = $projectUuid; + + if ($shouldProgress) { + $this->loadEnvironments(); + } + } + + public function loadEnvironments() + { + $this->loadingEnvironments = true; + $project = Project::where('uuid', $this->selectedProjectUuid)->first(); + + if (! $project) { + $this->loadingEnvironments = false; + + return; + } + + $environments = $project->environments; + + if ($environments->isEmpty()) { + $this->loadingEnvironments = false; + + return $this->dispatch('error', message: 'No environments found in project'); + } + + $this->availableEnvironments = $environments->map(fn ($e) => [ + 'uuid' => $e->uuid, + 'name' => $e->name, + 'description' => $e->description, + ])->toArray(); + $this->loadingEnvironments = false; + + // Auto-select if only one environment + if (count($this->availableEnvironments) === 1) { + $this->selectEnvironment($this->availableEnvironments[0]['uuid']); + } + } + + public function selectEnvironment($environmentUuid, $shouldProgress = true) + { + $this->selectedEnvironmentUuid = $environmentUuid; + + if ($shouldProgress) { + $this->completeResourceCreation(); + } + } + + private function completeResourceCreation() + { + // All selections made - navigate to resource creation + if ($this->selectedProjectUuid && $this->selectedEnvironmentUuid && $this->selectedResourceType && $this->selectedServerId !== null && $this->selectedDestinationUuid) { + $queryParams = [ + 'type' => $this->selectedResourceType, + 'destination' => $this->selectedDestinationUuid, + 'server_id' => $this->selectedServerId, + ]; + + // PostgreSQL requires a database_image parameter + if ($this->selectedResourceType === 'postgresql') { + $queryParams['database_image'] = 'postgres:16-alpine'; + } + + $this->redirect(route('project.resource.create', [ + 'project_uuid' => $this->selectedProjectUuid, + 'environment_uuid' => $this->selectedEnvironmentUuid, + ] + $queryParams)); + } + } + + public function cancelResourceSelection() + { + $this->isSelectingResource = false; + $this->selectedResourceType = null; + $this->selectedServerId = null; + $this->selectedDestinationUuid = null; + $this->selectedProjectUuid = null; + $this->selectedEnvironmentUuid = null; + $this->availableServers = []; + $this->availableDestinations = []; + $this->availableProjects = []; + $this->availableEnvironments = []; + $this->autoOpenResource = null; + } + + public function getFilteredCreatableItemsProperty() + { + $query = strtolower(trim($this->searchQuery)); + + // Check if query matches a category keyword + $categoryKeywords = ['server', 'servers', 'app', 'apps', 'application', 'applications', 'db', 'database', 'databases', 'service', 'services', 'project', 'projects']; + if (in_array($query, $categoryKeywords)) { + return $this->filterCreatableItemsByCategory($query); + } + + // Extract search term - everything after "new " + if (str_starts_with($query, 'new ')) { + $searchTerm = trim(substr($query, strlen('new '))); + + if (empty($searchTerm)) { + return $this->creatableItems; + } + + // Filter items by name or description + return collect($this->creatableItems)->filter(function ($item) use ($searchTerm) { + $searchText = strtolower($item['name'].' '.$item['description'].' '.$item['category']); + + return str_contains($searchText, $searchTerm); + })->values()->toArray(); + } + + return $this->creatableItems; + } + + private function filterCreatableItemsByCategory($categoryKeyword) + { + // Map keywords to category names + $categoryMap = [ + 'server' => 'Quick Actions', + 'servers' => 'Quick Actions', + 'app' => 'Applications', + 'apps' => 'Applications', + 'application' => 'Applications', + 'applications' => 'Applications', + 'db' => 'Databases', + 'database' => 'Databases', + 'databases' => 'Databases', + 'service' => 'Services', + 'services' => 'Services', + 'project' => 'Applications', + 'projects' => 'Applications', + ]; + + $category = $categoryMap[$categoryKeyword] ?? null; + + if (! $category) { + return []; + } + + return collect($this->creatableItems) + ->filter(fn ($item) => $item['category'] === $category) + ->values() + ->toArray(); + } + + public function getSelectedResourceNameProperty() + { + if (! $this->selectedResourceType) { + return null; + } + + // Load creatable items if not loaded yet + if (empty($this->creatableItems)) { + $this->loadCreatableItems(); + } + + // Find the item by type - check regular items first, then services + $item = collect($this->creatableItems)->firstWhere('type', $this->selectedResourceType); + + if (! $item) { + $item = collect($this->services)->firstWhere('type', $this->selectedResourceType); + } + + return $item ? $item['name'] : null; + } + + public function getServicesProperty() + { + // Cache services in a static property to avoid reloading on every access + static $cachedServices = null; + + if ($cachedServices !== null) { + return $cachedServices; + } + + $user = auth()->user(); + + if (! $user->can('createAnyResource')) { + $cachedServices = []; + + return $cachedServices; + } + + // Load all services + $allServices = get_service_templates(); + $items = collect(); + + foreach ($allServices as $serviceKey => $service) { + $items->push([ + 'name' => str($serviceKey)->headline()->toString(), + 'description' => data_get($service, 'slogan', 'Deploy '.str($serviceKey)->headline()), + 'type' => 'one-click-service-'.$serviceKey, + 'category' => 'Services', + 'resourceType' => 'service', + ]); + } + + $cachedServices = $items->toArray(); + + return $cachedServices; } public function render() diff --git a/app/Livewire/Notifications/Webhook.php b/app/Livewire/Notifications/Webhook.php new file mode 100644 index 0000000000..cf4e711052 --- /dev/null +++ b/app/Livewire/Notifications/Webhook.php @@ -0,0 +1,196 @@ +team = auth()->user()->currentTeam(); + $this->settings = $this->team->webhookNotificationSettings; + $this->authorize('view', $this->settings); + $this->syncData(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->authorize('update', $this->settings); + $this->settings->webhook_enabled = $this->webhookEnabled; + $this->settings->webhook_url = $this->webhookUrl; + + $this->settings->deployment_success_webhook_notifications = $this->deploymentSuccessWebhookNotifications; + $this->settings->deployment_failure_webhook_notifications = $this->deploymentFailureWebhookNotifications; + $this->settings->status_change_webhook_notifications = $this->statusChangeWebhookNotifications; + $this->settings->backup_success_webhook_notifications = $this->backupSuccessWebhookNotifications; + $this->settings->backup_failure_webhook_notifications = $this->backupFailureWebhookNotifications; + $this->settings->scheduled_task_success_webhook_notifications = $this->scheduledTaskSuccessWebhookNotifications; + $this->settings->scheduled_task_failure_webhook_notifications = $this->scheduledTaskFailureWebhookNotifications; + $this->settings->docker_cleanup_success_webhook_notifications = $this->dockerCleanupSuccessWebhookNotifications; + $this->settings->docker_cleanup_failure_webhook_notifications = $this->dockerCleanupFailureWebhookNotifications; + $this->settings->server_disk_usage_webhook_notifications = $this->serverDiskUsageWebhookNotifications; + $this->settings->server_reachable_webhook_notifications = $this->serverReachableWebhookNotifications; + $this->settings->server_unreachable_webhook_notifications = $this->serverUnreachableWebhookNotifications; + $this->settings->server_patch_webhook_notifications = $this->serverPatchWebhookNotifications; + + $this->settings->save(); + refreshSession(); + } else { + $this->webhookEnabled = $this->settings->webhook_enabled; + $this->webhookUrl = $this->settings->webhook_url; + + $this->deploymentSuccessWebhookNotifications = $this->settings->deployment_success_webhook_notifications; + $this->deploymentFailureWebhookNotifications = $this->settings->deployment_failure_webhook_notifications; + $this->statusChangeWebhookNotifications = $this->settings->status_change_webhook_notifications; + $this->backupSuccessWebhookNotifications = $this->settings->backup_success_webhook_notifications; + $this->backupFailureWebhookNotifications = $this->settings->backup_failure_webhook_notifications; + $this->scheduledTaskSuccessWebhookNotifications = $this->settings->scheduled_task_success_webhook_notifications; + $this->scheduledTaskFailureWebhookNotifications = $this->settings->scheduled_task_failure_webhook_notifications; + $this->dockerCleanupSuccessWebhookNotifications = $this->settings->docker_cleanup_success_webhook_notifications; + $this->dockerCleanupFailureWebhookNotifications = $this->settings->docker_cleanup_failure_webhook_notifications; + $this->serverDiskUsageWebhookNotifications = $this->settings->server_disk_usage_webhook_notifications; + $this->serverReachableWebhookNotifications = $this->settings->server_reachable_webhook_notifications; + $this->serverUnreachableWebhookNotifications = $this->settings->server_unreachable_webhook_notifications; + $this->serverPatchWebhookNotifications = $this->settings->server_patch_webhook_notifications; + } + } + + public function instantSaveWebhookEnabled() + { + try { + $original = $this->webhookEnabled; + $this->validate([ + 'webhookUrl' => 'required', + ], [ + 'webhookUrl.required' => 'Webhook URL is required.', + ]); + $this->saveModel(); + } catch (\Throwable $e) { + $this->webhookEnabled = $original; + + return handleError($e, $this); + } + } + + public function instantSave() + { + try { + $this->syncData(true); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function submit() + { + try { + $this->resetErrorBag(); + $this->syncData(true); + $this->saveModel(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function saveModel() + { + $this->syncData(true); + refreshSession(); + + if (isDev()) { + ray('Webhook settings saved', [ + 'webhook_enabled' => $this->settings->webhook_enabled, + 'webhook_url' => $this->settings->webhook_url, + ]); + } + + $this->dispatch('success', 'Settings saved.'); + } + + public function sendTestNotification() + { + try { + $this->authorize('sendTest', $this->settings); + + if (isDev()) { + ray('Sending test webhook notification', [ + 'team_id' => $this->team->id, + 'webhook_url' => $this->settings->webhook_url, + ]); + } + + $this->team->notify(new Test(channel: 'webhook')); + $this->dispatch('success', 'Test notification sent.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.notifications.webhook'); + } +} diff --git a/app/Livewire/Project/AddEmpty.php b/app/Livewire/Project/AddEmpty.php index 751b4945b7..974f0608a6 100644 --- a/app/Livewire/Project/AddEmpty.php +++ b/app/Livewire/Project/AddEmpty.php @@ -37,7 +37,12 @@ public function submit() 'uuid' => (string) new Cuid2, ]); - return redirect()->route('project.show', $project->uuid); + $productionEnvironment = $project->environments()->where('name', 'production')->first(); + + return redirect()->route('project.resource.index', [ + 'project_uuid' => $project->uuid, + 'environment_uuid' => $productionEnvironment->uuid, + ]); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index ae9bd314b9..b42f29fa58 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -544,6 +544,9 @@ public function submit($showToaster = true) { try { $this->authorize('update', $this->application); + + $this->validate(); + $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { @@ -584,7 +587,6 @@ public function submit($showToaster = true) return; } } - $this->validate(); if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) { $this->resetDefaultLabels(); diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php index 98d076ac0c..7deaa82a9d 100644 --- a/app/Livewire/Project/Database/BackupEdit.php +++ b/app/Livewire/Project/Database/BackupEdit.php @@ -85,6 +85,7 @@ class BackupEdit extends Component public function mount() { try { + $this->authorize('view', $this->backup->database); $this->parameters = get_route_parameters(); $this->syncData(); } catch (Exception $e) { @@ -208,7 +209,7 @@ private function customValidate() // Validate that disable_local_backup can only be true when S3 backup is enabled if ($this->backup->disable_local_backup && ! $this->backup->save_s3) { - throw new \Exception('Local backup can only be disabled when S3 backup is enabled.'); + $this->backup->disable_local_backup = $this->disableLocalBackup = false; } $isValid = validate_cron_expression($this->backup->frequency); diff --git a/app/Livewire/Project/Database/BackupExecutions.php b/app/Livewire/Project/Database/BackupExecutions.php index 2f3aae8cf1..0b6d8338b2 100644 --- a/app/Livewire/Project/Database/BackupExecutions.php +++ b/app/Livewire/Project/Database/BackupExecutions.php @@ -202,11 +202,6 @@ public function server() public function render() { - return view('livewire.project.database.backup-executions', [ - 'checkboxes' => [ - ['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently from S3 Storage'], - // ['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently from SFTP Storage'], - ], - ]); + return view('livewire.project.database.backup-executions'); } } diff --git a/app/Livewire/Project/Database/Clickhouse/General.php b/app/Livewire/Project/Database/Clickhouse/General.php index b80775853e..c4a7983b8f 100644 --- a/app/Livewire/Project/Database/Clickhouse/General.php +++ b/app/Livewire/Project/Database/Clickhouse/General.php @@ -16,7 +16,7 @@ class General extends Component { use AuthorizesRequests; - public Server $server; + public ?Server $server = null; public StandaloneClickhouse $database; @@ -56,8 +56,14 @@ public function getListeners() public function mount() { try { + $this->authorize('view', $this->database); $this->syncData(); $this->server = data_get($this->database, 'destination.server'); + if (! $this->server) { + $this->dispatch('error', 'Database destination server is not configured.'); + + return; + } } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Database/Configuration.php b/app/Livewire/Project/Database/Configuration.php index 88ecccf995..513ba9f164 100644 --- a/app/Livewire/Project/Database/Configuration.php +++ b/app/Livewire/Project/Database/Configuration.php @@ -3,10 +3,12 @@ namespace App\Livewire\Project\Database; use Auth; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; class Configuration extends Component { + use AuthorizesRequests; public $currentRoute; public $database; @@ -42,6 +44,8 @@ public function mount() ->where('uuid', request()->route('database_uuid')) ->firstOrFail(); + $this->authorize('view', $database); + $this->database = $database; $this->project = $project; $this->environment = $environment; diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php index fabbc7cb4e..9052a4749e 100644 --- a/app/Livewire/Project/Database/Dragonfly/General.php +++ b/app/Livewire/Project/Database/Dragonfly/General.php @@ -6,7 +6,6 @@ use App\Actions\Database\StopDatabaseProxy; use App\Helpers\SslHelper; use App\Models\Server; -use App\Models\SslCertificate; use App\Models\StandaloneDragonfly; use App\Support\ValidationPatterns; use Carbon\Carbon; @@ -19,7 +18,7 @@ class General extends Component { use AuthorizesRequests; - public Server $server; + public ?Server $server = null; public StandaloneDragonfly $database; @@ -63,8 +62,14 @@ public function getListeners() public function mount() { try { + $this->authorize('view', $this->database); $this->syncData(); $this->server = data_get($this->database, 'destination.server'); + if (! $this->server) { + $this->dispatch('error', 'Database destination server is not configured.'); + + return; + } $existingCert = $this->database->sslCertificates()->first(); @@ -249,13 +254,13 @@ public function regenerateSslCertificate() $server = $this->database->destination->server; - $caCert = SslCertificate::where('server_id', $server->id) + $caCert = $server->sslCertificates() ->where('is_ca_certificate', true) ->first(); if (! $caCert) { $server->generateCaCertificate(); - $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); } if (! $caCert) { diff --git a/app/Livewire/Project/Database/Import.php b/app/Livewire/Project/Database/Import.php index 3f974f63d1..7d6ac3131c 100644 --- a/app/Livewire/Project/Database/Import.php +++ b/app/Livewire/Project/Database/Import.php @@ -131,6 +131,7 @@ public function getContainers() if (is_null($resource)) { abort(404); } + $this->authorize('view', $resource); $this->resource = $resource; $this->server = $this->resource->destination->server; $this->container = $this->resource->uuid; diff --git a/app/Livewire/Project/Database/Keydb/General.php b/app/Livewire/Project/Database/Keydb/General.php index 7502d001de..6d21988e7b 100644 --- a/app/Livewire/Project/Database/Keydb/General.php +++ b/app/Livewire/Project/Database/Keydb/General.php @@ -6,7 +6,6 @@ use App\Actions\Database\StopDatabaseProxy; use App\Helpers\SslHelper; use App\Models\Server; -use App\Models\SslCertificate; use App\Models\StandaloneKeydb; use App\Support\ValidationPatterns; use Carbon\Carbon; @@ -19,7 +18,7 @@ class General extends Component { use AuthorizesRequests; - public Server $server; + public ?Server $server = null; public StandaloneKeydb $database; @@ -59,15 +58,20 @@ public function getListeners() return [ "echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped', "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', - 'refresh' => '$refresh', ]; } public function mount() { try { + $this->authorize('view', $this->database); $this->syncData(); $this->server = data_get($this->database, 'destination.server'); + if (! $this->server) { + $this->dispatch('error', 'Database destination server is not configured.'); + + return; + } $existingCert = $this->database->sslCertificates()->first(); @@ -255,7 +259,7 @@ public function regenerateSslCertificate() return; } - $caCert = SslCertificate::where('server_id', $existingCert->server_id) + $caCert = $this->server->sslCertificates() ->where('is_ca_certificate', true) ->first(); diff --git a/app/Livewire/Project/Database/Mariadb/General.php b/app/Livewire/Project/Database/Mariadb/General.php index c82c4538f7..429cfc387d 100644 --- a/app/Livewire/Project/Database/Mariadb/General.php +++ b/app/Livewire/Project/Database/Mariadb/General.php @@ -6,7 +6,6 @@ use App\Actions\Database\StopDatabaseProxy; use App\Helpers\SslHelper; use App\Models\Server; -use App\Models\SslCertificate; use App\Models\StandaloneMariadb; use App\Support\ValidationPatterns; use Carbon\Carbon; @@ -19,12 +18,38 @@ class General extends Component { use AuthorizesRequests; - protected $listeners = ['refresh']; - - public Server $server; + public ?Server $server = null; public StandaloneMariadb $database; + public string $name; + + public ?string $description = null; + + public string $mariadbRootPassword; + + public string $mariadbUser; + + public string $mariadbPassword; + + public string $mariadbDatabase; + + public ?string $mariadbConf = null; + + public string $image; + + public ?string $portsMappings = null; + + public ?bool $isPublic = null; + + public ?int $publicPort = null; + + public bool $isLogDrainEnabled = false; + + public ?string $customDockerRunOptions = null; + + public bool $enableSsl = false; + public ?string $db_url = null; public ?string $db_url_public = null; @@ -37,27 +62,26 @@ public function getListeners() return [ "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', - 'refresh' => '$refresh', ]; } protected function rules(): array { return [ - 'database.name' => ValidationPatterns::nameRules(), - 'database.description' => ValidationPatterns::descriptionRules(), - 'database.mariadb_root_password' => 'required', - 'database.mariadb_user' => 'required', - 'database.mariadb_password' => 'required', - 'database.mariadb_database' => 'required', - 'database.mariadb_conf' => 'nullable', - 'database.image' => 'required', - 'database.ports_mappings' => 'nullable', - 'database.is_public' => 'nullable|boolean', - 'database.public_port' => 'nullable|integer', - 'database.is_log_drain_enabled' => 'nullable|boolean', - 'database.custom_docker_run_options' => 'nullable', - 'database.enable_ssl' => 'boolean', + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'mariadbRootPassword' => 'required', + 'mariadbUser' => 'required', + 'mariadbPassword' => 'required', + 'mariadbDatabase' => 'required', + 'mariadbConf' => 'nullable', + 'image' => 'required', + 'portsMappings' => 'nullable', + 'isPublic' => 'nullable|boolean', + 'publicPort' => 'nullable|integer', + 'isLogDrainEnabled' => 'nullable|boolean', + 'customDockerRunOptions' => 'nullable', + 'enableSsl' => 'boolean', ]; } @@ -66,45 +90,96 @@ protected function messages(): array return array_merge( ValidationPatterns::combinedMessages(), [ - 'database.name.required' => 'The Name field is required.', - 'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', - 'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', - 'database.mariadb_root_password.required' => 'The Root Password field is required.', - 'database.mariadb_user.required' => 'The MariaDB User field is required.', - 'database.mariadb_password.required' => 'The MariaDB Password field is required.', - 'database.mariadb_database.required' => 'The MariaDB Database field is required.', - 'database.image.required' => 'The Docker Image field is required.', - 'database.public_port.integer' => 'The Public Port must be an integer.', + 'name.required' => 'The Name field is required.', + 'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'mariadbRootPassword.required' => 'The Root Password field is required.', + 'mariadbUser.required' => 'The MariaDB User field is required.', + 'mariadbPassword.required' => 'The MariaDB Password field is required.', + 'mariadbDatabase.required' => 'The MariaDB Database field is required.', + 'image.required' => 'The Docker Image field is required.', + 'publicPort.integer' => 'The Public Port must be an integer.', ] ); } protected $validationAttributes = [ - 'database.name' => 'Name', - 'database.description' => 'Description', - 'database.mariadb_root_password' => 'Root Password', - 'database.mariadb_user' => 'User', - 'database.mariadb_password' => 'Password', - 'database.mariadb_database' => 'Database', - 'database.mariadb_conf' => 'MariaDB Configuration', - 'database.image' => 'Image', - 'database.ports_mappings' => 'Port Mapping', - 'database.is_public' => 'Is Public', - 'database.public_port' => 'Public Port', - 'database.custom_docker_run_options' => 'Custom Docker Options', - 'database.enable_ssl' => 'Enable SSL', + 'name' => 'Name', + 'description' => 'Description', + 'mariadbRootPassword' => 'Root Password', + 'mariadbUser' => 'User', + 'mariadbPassword' => 'Password', + 'mariadbDatabase' => 'Database', + 'mariadbConf' => 'MariaDB Configuration', + 'image' => 'Image', + 'portsMappings' => 'Port Mapping', + 'isPublic' => 'Is Public', + 'publicPort' => 'Public Port', + 'customDockerRunOptions' => 'Custom Docker Options', + 'enableSsl' => 'Enable SSL', ]; public function mount() { - $this->db_url = $this->database->internal_db_url; - $this->db_url_public = $this->database->external_db_url; - $this->server = data_get($this->database, 'destination.server'); + try { + $this->authorize('view', $this->database); + $this->syncData(); + $this->server = data_get($this->database, 'destination.server'); + if (! $this->server) { + $this->dispatch('error', 'Database destination server is not configured.'); + + return; + } - $existingCert = $this->database->sslCertificates()->first(); + $existingCert = $this->database->sslCertificates()->first(); - if ($existingCert) { - $this->certificateValidUntil = $existingCert->valid_until; + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->database->name = $this->name; + $this->database->description = $this->description; + $this->database->mariadb_root_password = $this->mariadbRootPassword; + $this->database->mariadb_user = $this->mariadbUser; + $this->database->mariadb_password = $this->mariadbPassword; + $this->database->mariadb_database = $this->mariadbDatabase; + $this->database->mariadb_conf = $this->mariadbConf; + $this->database->image = $this->image; + $this->database->ports_mappings = $this->portsMappings; + $this->database->is_public = $this->isPublic; + $this->database->public_port = $this->publicPort; + $this->database->is_log_drain_enabled = $this->isLogDrainEnabled; + $this->database->custom_docker_run_options = $this->customDockerRunOptions; + $this->database->enable_ssl = $this->enableSsl; + $this->database->save(); + + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; + } else { + $this->name = $this->database->name; + $this->description = $this->database->description; + $this->mariadbRootPassword = $this->database->mariadb_root_password; + $this->mariadbUser = $this->database->mariadb_user; + $this->mariadbPassword = $this->database->mariadb_password; + $this->mariadbDatabase = $this->database->mariadb_database; + $this->mariadbConf = $this->database->mariadb_conf; + $this->image = $this->database->image; + $this->portsMappings = $this->database->ports_mappings; + $this->isPublic = $this->database->is_public; + $this->publicPort = $this->database->public_port; + $this->isLogDrainEnabled = $this->database->is_log_drain_enabled; + $this->customDockerRunOptions = $this->database->custom_docker_run_options; + $this->enableSsl = $this->database->enable_ssl; + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; } } @@ -114,12 +189,12 @@ public function instantSaveAdvanced() $this->authorize('update', $this->database); if (! $this->server->isLogDrainEnabled()) { - $this->database->is_log_drain_enabled = false; + $this->isLogDrainEnabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); return; } - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Database updated.'); $this->dispatch('success', 'You need to restart the service for the changes to take effect.'); } catch (Exception $e) { @@ -132,11 +207,10 @@ public function submit() try { $this->authorize('update', $this->database); - if (str($this->database->public_port)->isEmpty()) { - $this->database->public_port = null; + if (str($this->publicPort)->isEmpty()) { + $this->publicPort = null; } - $this->validate(); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Database updated.'); } catch (Exception $e) { return handleError($e, $this); @@ -154,16 +228,16 @@ public function instantSave() try { $this->authorize('update', $this->database); - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->isPublic && ! $this->publicPort) { $this->dispatch('error', 'Public port is required.'); - $this->database->is_public = false; + $this->isPublic = false; return; } - if ($this->database->is_public) { + if ($this->isPublic) { if (! str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); - $this->database->is_public = false; + $this->isPublic = false; return; } @@ -173,10 +247,9 @@ public function instantSave() StopDatabaseProxy::run($this->database); $this->dispatch('success', 'Database is no longer publicly accessible.'); } - $this->db_url_public = $this->database->external_db_url; - $this->database->save(); + $this->syncData(true); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->isPublic = ! $this->isPublic; return handleError($e, $this); } @@ -187,7 +260,7 @@ public function instantSaveSSL() try { $this->authorize('update', $this->database); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'SSL configuration updated.'); } catch (Exception $e) { return handleError($e, $this); @@ -207,7 +280,7 @@ public function regenerateSslCertificate() return; } - $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first(); + $caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first(); SslHelper::generateSslCertificate( commonName: $existingCert->common_name, @@ -231,6 +304,7 @@ public function regenerateSslCertificate() public function refresh(): void { $this->database->refresh(); + $this->syncData(); } public function render() diff --git a/app/Livewire/Project/Database/Mongodb/General.php b/app/Livewire/Project/Database/Mongodb/General.php index 4fbc454376..ae725fa4b6 100644 --- a/app/Livewire/Project/Database/Mongodb/General.php +++ b/app/Livewire/Project/Database/Mongodb/General.php @@ -6,7 +6,6 @@ use App\Actions\Database\StopDatabaseProxy; use App\Helpers\SslHelper; use App\Models\Server; -use App\Models\SslCertificate; use App\Models\StandaloneMongodb; use App\Support\ValidationPatterns; use Carbon\Carbon; @@ -19,12 +18,38 @@ class General extends Component { use AuthorizesRequests; - protected $listeners = ['refresh']; - - public Server $server; + public ?Server $server = null; public StandaloneMongodb $database; + public string $name; + + public ?string $description = null; + + public ?string $mongoConf = null; + + public string $mongoInitdbRootUsername; + + public string $mongoInitdbRootPassword; + + public string $mongoInitdbDatabase; + + public string $image; + + public ?string $portsMappings = null; + + public ?bool $isPublic = null; + + public ?int $publicPort = null; + + public bool $isLogDrainEnabled = false; + + public ?string $customDockerRunOptions = null; + + public bool $enableSsl = false; + + public ?string $sslMode = null; + public ?string $db_url = null; public ?string $db_url_public = null; @@ -37,27 +62,26 @@ public function getListeners() return [ "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', - 'refresh' => '$refresh', ]; } protected function rules(): array { return [ - 'database.name' => ValidationPatterns::nameRules(), - 'database.description' => ValidationPatterns::descriptionRules(), - 'database.mongo_conf' => 'nullable', - 'database.mongo_initdb_root_username' => 'required', - 'database.mongo_initdb_root_password' => 'required', - 'database.mongo_initdb_database' => 'required', - 'database.image' => 'required', - 'database.ports_mappings' => 'nullable', - 'database.is_public' => 'nullable|boolean', - 'database.public_port' => 'nullable|integer', - 'database.is_log_drain_enabled' => 'nullable|boolean', - 'database.custom_docker_run_options' => 'nullable', - 'database.enable_ssl' => 'boolean', - 'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-full', + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'mongoConf' => 'nullable', + 'mongoInitdbRootUsername' => 'required', + 'mongoInitdbRootPassword' => 'required', + 'mongoInitdbDatabase' => 'required', + 'image' => 'required', + 'portsMappings' => 'nullable', + 'isPublic' => 'nullable|boolean', + 'publicPort' => 'nullable|integer', + 'isLogDrainEnabled' => 'nullable|boolean', + 'customDockerRunOptions' => 'nullable', + 'enableSsl' => 'boolean', + 'sslMode' => 'nullable|string|in:allow,prefer,require,verify-full', ]; } @@ -66,45 +90,96 @@ protected function messages(): array return array_merge( ValidationPatterns::combinedMessages(), [ - 'database.name.required' => 'The Name field is required.', - 'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', - 'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', - 'database.mongo_initdb_root_username.required' => 'The Root Username field is required.', - 'database.mongo_initdb_root_password.required' => 'The Root Password field is required.', - 'database.mongo_initdb_database.required' => 'The MongoDB Database field is required.', - 'database.image.required' => 'The Docker Image field is required.', - 'database.public_port.integer' => 'The Public Port must be an integer.', - 'database.ssl_mode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-full.', + 'name.required' => 'The Name field is required.', + 'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'mongoInitdbRootUsername.required' => 'The Root Username field is required.', + 'mongoInitdbRootPassword.required' => 'The Root Password field is required.', + 'mongoInitdbDatabase.required' => 'The MongoDB Database field is required.', + 'image.required' => 'The Docker Image field is required.', + 'publicPort.integer' => 'The Public Port must be an integer.', + 'sslMode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-full.', ] ); } protected $validationAttributes = [ - 'database.name' => 'Name', - 'database.description' => 'Description', - 'database.mongo_conf' => 'Mongo Configuration', - 'database.mongo_initdb_root_username' => 'Root Username', - 'database.mongo_initdb_root_password' => 'Root Password', - 'database.mongo_initdb_database' => 'Database', - 'database.image' => 'Image', - 'database.ports_mappings' => 'Port Mapping', - 'database.is_public' => 'Is Public', - 'database.public_port' => 'Public Port', - 'database.custom_docker_run_options' => 'Custom Docker Run Options', - 'database.enable_ssl' => 'Enable SSL', - 'database.ssl_mode' => 'SSL Mode', + 'name' => 'Name', + 'description' => 'Description', + 'mongoConf' => 'Mongo Configuration', + 'mongoInitdbRootUsername' => 'Root Username', + 'mongoInitdbRootPassword' => 'Root Password', + 'mongoInitdbDatabase' => 'Database', + 'image' => 'Image', + 'portsMappings' => 'Port Mapping', + 'isPublic' => 'Is Public', + 'publicPort' => 'Public Port', + 'customDockerRunOptions' => 'Custom Docker Run Options', + 'enableSsl' => 'Enable SSL', + 'sslMode' => 'SSL Mode', ]; public function mount() { - $this->db_url = $this->database->internal_db_url; - $this->db_url_public = $this->database->external_db_url; - $this->server = data_get($this->database, 'destination.server'); + try { + $this->authorize('view', $this->database); + $this->syncData(); + $this->server = data_get($this->database, 'destination.server'); + if (! $this->server) { + $this->dispatch('error', 'Database destination server is not configured.'); + + return; + } - $existingCert = $this->database->sslCertificates()->first(); + $existingCert = $this->database->sslCertificates()->first(); - if ($existingCert) { - $this->certificateValidUntil = $existingCert->valid_until; + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->database->name = $this->name; + $this->database->description = $this->description; + $this->database->mongo_conf = $this->mongoConf; + $this->database->mongo_initdb_root_username = $this->mongoInitdbRootUsername; + $this->database->mongo_initdb_root_password = $this->mongoInitdbRootPassword; + $this->database->mongo_initdb_database = $this->mongoInitdbDatabase; + $this->database->image = $this->image; + $this->database->ports_mappings = $this->portsMappings; + $this->database->is_public = $this->isPublic; + $this->database->public_port = $this->publicPort; + $this->database->is_log_drain_enabled = $this->isLogDrainEnabled; + $this->database->custom_docker_run_options = $this->customDockerRunOptions; + $this->database->enable_ssl = $this->enableSsl; + $this->database->ssl_mode = $this->sslMode; + $this->database->save(); + + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; + } else { + $this->name = $this->database->name; + $this->description = $this->database->description; + $this->mongoConf = $this->database->mongo_conf; + $this->mongoInitdbRootUsername = $this->database->mongo_initdb_root_username; + $this->mongoInitdbRootPassword = $this->database->mongo_initdb_root_password; + $this->mongoInitdbDatabase = $this->database->mongo_initdb_database; + $this->image = $this->database->image; + $this->portsMappings = $this->database->ports_mappings; + $this->isPublic = $this->database->is_public; + $this->publicPort = $this->database->public_port; + $this->isLogDrainEnabled = $this->database->is_log_drain_enabled; + $this->customDockerRunOptions = $this->database->custom_docker_run_options; + $this->enableSsl = $this->database->enable_ssl; + $this->sslMode = $this->database->ssl_mode; + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; } } @@ -114,12 +189,12 @@ public function instantSaveAdvanced() $this->authorize('update', $this->database); if (! $this->server->isLogDrainEnabled()) { - $this->database->is_log_drain_enabled = false; + $this->isLogDrainEnabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); return; } - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Database updated.'); $this->dispatch('success', 'You need to restart the service for the changes to take effect.'); } catch (Exception $e) { @@ -132,14 +207,13 @@ public function submit() try { $this->authorize('update', $this->database); - if (str($this->database->public_port)->isEmpty()) { - $this->database->public_port = null; + if (str($this->publicPort)->isEmpty()) { + $this->publicPort = null; } - if (str($this->database->mongo_conf)->isEmpty()) { - $this->database->mongo_conf = null; + if (str($this->mongoConf)->isEmpty()) { + $this->mongoConf = null; } - $this->validate(); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Database updated.'); } catch (Exception $e) { return handleError($e, $this); @@ -157,16 +231,16 @@ public function instantSave() try { $this->authorize('update', $this->database); - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->isPublic && ! $this->publicPort) { $this->dispatch('error', 'Public port is required.'); - $this->database->is_public = false; + $this->isPublic = false; return; } - if ($this->database->is_public) { + if ($this->isPublic) { if (! str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); - $this->database->is_public = false; + $this->isPublic = false; return; } @@ -176,16 +250,15 @@ public function instantSave() StopDatabaseProxy::run($this->database); $this->dispatch('success', 'Database is no longer publicly accessible.'); } - $this->db_url_public = $this->database->external_db_url; - $this->database->save(); + $this->syncData(true); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->isPublic = ! $this->isPublic; return handleError($e, $this); } } - public function updatedDatabaseSslMode() + public function updatedSslMode() { $this->instantSaveSSL(); } @@ -195,7 +268,7 @@ public function instantSaveSSL() try { $this->authorize('update', $this->database); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'SSL configuration updated.'); } catch (Exception $e) { return handleError($e, $this); @@ -215,7 +288,7 @@ public function regenerateSslCertificate() return; } - $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first(); + $caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first(); SslHelper::generateSslCertificate( commonName: $existingCert->common_name, @@ -239,6 +312,7 @@ public function regenerateSslCertificate() public function refresh(): void { $this->database->refresh(); + $this->syncData(); } public function render() diff --git a/app/Livewire/Project/Database/Mysql/General.php b/app/Livewire/Project/Database/Mysql/General.php index ada1b3a2c0..cffedcd23c 100644 --- a/app/Livewire/Project/Database/Mysql/General.php +++ b/app/Livewire/Project/Database/Mysql/General.php @@ -6,7 +6,6 @@ use App\Actions\Database\StopDatabaseProxy; use App\Helpers\SslHelper; use App\Models\Server; -use App\Models\SslCertificate; use App\Models\StandaloneMysql; use App\Support\ValidationPatterns; use Carbon\Carbon; @@ -19,11 +18,39 @@ class General extends Component { use AuthorizesRequests; - protected $listeners = ['refresh']; - public StandaloneMysql $database; - public Server $server; + public ?Server $server = null; + + public string $name; + + public ?string $description = null; + + public string $mysqlRootPassword; + + public string $mysqlUser; + + public string $mysqlPassword; + + public string $mysqlDatabase; + + public ?string $mysqlConf = null; + + public string $image; + + public ?string $portsMappings = null; + + public ?bool $isPublic = null; + + public ?int $publicPort = null; + + public bool $isLogDrainEnabled = false; + + public ?string $customDockerRunOptions = null; + + public bool $enableSsl = false; + + public ?string $sslMode = null; public ?string $db_url = null; @@ -37,28 +64,27 @@ public function getListeners() return [ "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', - 'refresh' => '$refresh', ]; } protected function rules(): array { return [ - 'database.name' => ValidationPatterns::nameRules(), - 'database.description' => ValidationPatterns::descriptionRules(), - 'database.mysql_root_password' => 'required', - 'database.mysql_user' => 'required', - 'database.mysql_password' => 'required', - 'database.mysql_database' => 'required', - 'database.mysql_conf' => 'nullable', - 'database.image' => 'required', - 'database.ports_mappings' => 'nullable', - 'database.is_public' => 'nullable|boolean', - 'database.public_port' => 'nullable|integer', - 'database.is_log_drain_enabled' => 'nullable|boolean', - 'database.custom_docker_run_options' => 'nullable', - 'database.enable_ssl' => 'boolean', - 'database.ssl_mode' => 'nullable|string|in:PREFERRED,REQUIRED,VERIFY_CA,VERIFY_IDENTITY', + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'mysqlRootPassword' => 'required', + 'mysqlUser' => 'required', + 'mysqlPassword' => 'required', + 'mysqlDatabase' => 'required', + 'mysqlConf' => 'nullable', + 'image' => 'required', + 'portsMappings' => 'nullable', + 'isPublic' => 'nullable|boolean', + 'publicPort' => 'nullable|integer', + 'isLogDrainEnabled' => 'nullable|boolean', + 'customDockerRunOptions' => 'nullable', + 'enableSsl' => 'boolean', + 'sslMode' => 'nullable|string|in:PREFERRED,REQUIRED,VERIFY_CA,VERIFY_IDENTITY', ]; } @@ -67,47 +93,100 @@ protected function messages(): array return array_merge( ValidationPatterns::combinedMessages(), [ - 'database.name.required' => 'The Name field is required.', - 'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', - 'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', - 'database.mysql_root_password.required' => 'The Root Password field is required.', - 'database.mysql_user.required' => 'The MySQL User field is required.', - 'database.mysql_password.required' => 'The MySQL Password field is required.', - 'database.mysql_database.required' => 'The MySQL Database field is required.', - 'database.image.required' => 'The Docker Image field is required.', - 'database.public_port.integer' => 'The Public Port must be an integer.', - 'database.ssl_mode.in' => 'The SSL Mode must be one of: PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY.', + 'name.required' => 'The Name field is required.', + 'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'mysqlRootPassword.required' => 'The Root Password field is required.', + 'mysqlUser.required' => 'The MySQL User field is required.', + 'mysqlPassword.required' => 'The MySQL Password field is required.', + 'mysqlDatabase.required' => 'The MySQL Database field is required.', + 'image.required' => 'The Docker Image field is required.', + 'publicPort.integer' => 'The Public Port must be an integer.', + 'sslMode.in' => 'The SSL Mode must be one of: PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY.', ] ); } protected $validationAttributes = [ - 'database.name' => 'Name', - 'database.description' => 'Description', - 'database.mysql_root_password' => 'Root Password', - 'database.mysql_user' => 'User', - 'database.mysql_password' => 'Password', - 'database.mysql_database' => 'Database', - 'database.mysql_conf' => 'MySQL Configuration', - 'database.image' => 'Image', - 'database.ports_mappings' => 'Port Mapping', - 'database.is_public' => 'Is Public', - 'database.public_port' => 'Public Port', - 'database.custom_docker_run_options' => 'Custom Docker Run Options', - 'database.enable_ssl' => 'Enable SSL', - 'database.ssl_mode' => 'SSL Mode', + 'name' => 'Name', + 'description' => 'Description', + 'mysqlRootPassword' => 'Root Password', + 'mysqlUser' => 'User', + 'mysqlPassword' => 'Password', + 'mysqlDatabase' => 'Database', + 'mysqlConf' => 'MySQL Configuration', + 'image' => 'Image', + 'portsMappings' => 'Port Mapping', + 'isPublic' => 'Is Public', + 'publicPort' => 'Public Port', + 'customDockerRunOptions' => 'Custom Docker Run Options', + 'enableSsl' => 'Enable SSL', + 'sslMode' => 'SSL Mode', ]; public function mount() { - $this->db_url = $this->database->internal_db_url; - $this->db_url_public = $this->database->external_db_url; - $this->server = data_get($this->database, 'destination.server'); + try { + $this->authorize('view', $this->database); + $this->syncData(); + $this->server = data_get($this->database, 'destination.server'); + if (! $this->server) { + $this->dispatch('error', 'Database destination server is not configured.'); - $existingCert = $this->database->sslCertificates()->first(); + return; + } - if ($existingCert) { - $this->certificateValidUntil = $existingCert->valid_until; + $existingCert = $this->database->sslCertificates()->first(); + + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->database->name = $this->name; + $this->database->description = $this->description; + $this->database->mysql_root_password = $this->mysqlRootPassword; + $this->database->mysql_user = $this->mysqlUser; + $this->database->mysql_password = $this->mysqlPassword; + $this->database->mysql_database = $this->mysqlDatabase; + $this->database->mysql_conf = $this->mysqlConf; + $this->database->image = $this->image; + $this->database->ports_mappings = $this->portsMappings; + $this->database->is_public = $this->isPublic; + $this->database->public_port = $this->publicPort; + $this->database->is_log_drain_enabled = $this->isLogDrainEnabled; + $this->database->custom_docker_run_options = $this->customDockerRunOptions; + $this->database->enable_ssl = $this->enableSsl; + $this->database->ssl_mode = $this->sslMode; + $this->database->save(); + + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; + } else { + $this->name = $this->database->name; + $this->description = $this->database->description; + $this->mysqlRootPassword = $this->database->mysql_root_password; + $this->mysqlUser = $this->database->mysql_user; + $this->mysqlPassword = $this->database->mysql_password; + $this->mysqlDatabase = $this->database->mysql_database; + $this->mysqlConf = $this->database->mysql_conf; + $this->image = $this->database->image; + $this->portsMappings = $this->database->ports_mappings; + $this->isPublic = $this->database->is_public; + $this->publicPort = $this->database->public_port; + $this->isLogDrainEnabled = $this->database->is_log_drain_enabled; + $this->customDockerRunOptions = $this->database->custom_docker_run_options; + $this->enableSsl = $this->database->enable_ssl; + $this->sslMode = $this->database->ssl_mode; + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; } } @@ -117,12 +196,12 @@ public function instantSaveAdvanced() $this->authorize('update', $this->database); if (! $this->server->isLogDrainEnabled()) { - $this->database->is_log_drain_enabled = false; + $this->isLogDrainEnabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); return; } - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Database updated.'); $this->dispatch('success', 'You need to restart the service for the changes to take effect.'); } catch (Exception $e) { @@ -135,11 +214,10 @@ public function submit() try { $this->authorize('update', $this->database); - if (str($this->database->public_port)->isEmpty()) { - $this->database->public_port = null; + if (str($this->publicPort)->isEmpty()) { + $this->publicPort = null; } - $this->validate(); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Database updated.'); } catch (Exception $e) { return handleError($e, $this); @@ -157,16 +235,16 @@ public function instantSave() try { $this->authorize('update', $this->database); - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->isPublic && ! $this->publicPort) { $this->dispatch('error', 'Public port is required.'); - $this->database->is_public = false; + $this->isPublic = false; return; } - if ($this->database->is_public) { + if ($this->isPublic) { if (! str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); - $this->database->is_public = false; + $this->isPublic = false; return; } @@ -176,16 +254,15 @@ public function instantSave() StopDatabaseProxy::run($this->database); $this->dispatch('success', 'Database is no longer publicly accessible.'); } - $this->db_url_public = $this->database->external_db_url; - $this->database->save(); + $this->syncData(true); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->isPublic = ! $this->isPublic; return handleError($e, $this); } } - public function updatedDatabaseSslMode() + public function updatedSslMode() { $this->instantSaveSSL(); } @@ -195,7 +272,7 @@ public function instantSaveSSL() try { $this->authorize('update', $this->database); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'SSL configuration updated.'); } catch (Exception $e) { return handleError($e, $this); @@ -215,7 +292,7 @@ public function regenerateSslCertificate() return; } - $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first(); + $caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first(); SslHelper::generateSslCertificate( commonName: $existingCert->common_name, @@ -239,6 +316,7 @@ public function regenerateSslCertificate() public function refresh(): void { $this->database->refresh(); + $this->syncData(); } public function render() diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index 2d37620b99..3240aadd27 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -6,7 +6,6 @@ use App\Actions\Database\StopDatabaseProxy; use App\Helpers\SslHelper; use App\Models\Server; -use App\Models\SslCertificate; use App\Models\StandalonePostgresql; use App\Support\ValidationPatterns; use Carbon\Carbon; @@ -21,7 +20,41 @@ class General extends Component public StandalonePostgresql $database; - public Server $server; + public ?Server $server = null; + + public string $name; + + public ?string $description = null; + + public string $postgresUser; + + public string $postgresPassword; + + public string $postgresDb; + + public ?string $postgresInitdbArgs = null; + + public ?string $postgresHostAuthMethod = null; + + public ?string $postgresConf = null; + + public ?array $initScripts = null; + + public string $image; + + public ?string $portsMappings = null; + + public ?bool $isPublic = null; + + public ?int $publicPort = null; + + public bool $isLogDrainEnabled = false; + + public ?string $customDockerRunOptions = null; + + public bool $enableSsl = false; + + public ?string $sslMode = null; public string $new_filename; @@ -39,7 +72,6 @@ public function getListeners() return [ "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', - 'refresh' => '$refresh', 'save_init_script', 'delete_init_script', ]; @@ -48,23 +80,23 @@ public function getListeners() protected function rules(): array { return [ - 'database.name' => ValidationPatterns::nameRules(), - 'database.description' => ValidationPatterns::descriptionRules(), - 'database.postgres_user' => 'required', - 'database.postgres_password' => 'required', - 'database.postgres_db' => 'required', - 'database.postgres_initdb_args' => 'nullable', - 'database.postgres_host_auth_method' => 'nullable', - 'database.postgres_conf' => 'nullable', - 'database.init_scripts' => 'nullable', - 'database.image' => 'required', - 'database.ports_mappings' => 'nullable', - 'database.is_public' => 'nullable|boolean', - 'database.public_port' => 'nullable|integer', - 'database.is_log_drain_enabled' => 'nullable|boolean', - 'database.custom_docker_run_options' => 'nullable', - 'database.enable_ssl' => 'boolean', - 'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-ca,verify-full', + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'postgresUser' => 'required', + 'postgresPassword' => 'required', + 'postgresDb' => 'required', + 'postgresInitdbArgs' => 'nullable', + 'postgresHostAuthMethod' => 'nullable', + 'postgresConf' => 'nullable', + 'initScripts' => 'nullable', + 'image' => 'required', + 'portsMappings' => 'nullable', + 'isPublic' => 'nullable|boolean', + 'publicPort' => 'nullable|integer', + 'isLogDrainEnabled' => 'nullable|boolean', + 'customDockerRunOptions' => 'nullable', + 'enableSsl' => 'boolean', + 'sslMode' => 'nullable|string|in:allow,prefer,require,verify-ca,verify-full', ]; } @@ -73,48 +105,105 @@ protected function messages(): array return array_merge( ValidationPatterns::combinedMessages(), [ - 'database.name.required' => 'The Name field is required.', - 'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', - 'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', - 'database.postgres_user.required' => 'The Postgres User field is required.', - 'database.postgres_password.required' => 'The Postgres Password field is required.', - 'database.postgres_db.required' => 'The Postgres Database field is required.', - 'database.image.required' => 'The Docker Image field is required.', - 'database.public_port.integer' => 'The Public Port must be an integer.', - 'database.ssl_mode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-ca, verify-full.', + 'name.required' => 'The Name field is required.', + 'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'postgresUser.required' => 'The Postgres User field is required.', + 'postgresPassword.required' => 'The Postgres Password field is required.', + 'postgresDb.required' => 'The Postgres Database field is required.', + 'image.required' => 'The Docker Image field is required.', + 'publicPort.integer' => 'The Public Port must be an integer.', + 'sslMode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-ca, verify-full.', ] ); } protected $validationAttributes = [ - 'database.name' => 'Name', - 'database.description' => 'Description', - 'database.postgres_user' => 'Postgres User', - 'database.postgres_password' => 'Postgres Password', - 'database.postgres_db' => 'Postgres DB', - 'database.postgres_initdb_args' => 'Postgres Initdb Args', - 'database.postgres_host_auth_method' => 'Postgres Host Auth Method', - 'database.postgres_conf' => 'Postgres Configuration', - 'database.init_scripts' => 'Init Scripts', - 'database.image' => 'Image', - 'database.ports_mappings' => 'Port Mapping', - 'database.is_public' => 'Is Public', - 'database.public_port' => 'Public Port', - 'database.custom_docker_run_options' => 'Custom Docker Run Options', - 'database.enable_ssl' => 'Enable SSL', - 'database.ssl_mode' => 'SSL Mode', + 'name' => 'Name', + 'description' => 'Description', + 'postgresUser' => 'Postgres User', + 'postgresPassword' => 'Postgres Password', + 'postgresDb' => 'Postgres DB', + 'postgresInitdbArgs' => 'Postgres Initdb Args', + 'postgresHostAuthMethod' => 'Postgres Host Auth Method', + 'postgresConf' => 'Postgres Configuration', + 'initScripts' => 'Init Scripts', + 'image' => 'Image', + 'portsMappings' => 'Port Mapping', + 'isPublic' => 'Is Public', + 'publicPort' => 'Public Port', + 'customDockerRunOptions' => 'Custom Docker Run Options', + 'enableSsl' => 'Enable SSL', + 'sslMode' => 'SSL Mode', ]; public function mount() { - $this->db_url = $this->database->internal_db_url; - $this->db_url_public = $this->database->external_db_url; - $this->server = data_get($this->database, 'destination.server'); + try { + $this->authorize('view', $this->database); + $this->syncData(); + $this->server = data_get($this->database, 'destination.server'); + if (! $this->server) { + $this->dispatch('error', 'Database destination server is not configured.'); + + return; + } + + $existingCert = $this->database->sslCertificates()->first(); - $existingCert = $this->database->sslCertificates()->first(); + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->database->name = $this->name; + $this->database->description = $this->description; + $this->database->postgres_user = $this->postgresUser; + $this->database->postgres_password = $this->postgresPassword; + $this->database->postgres_db = $this->postgresDb; + $this->database->postgres_initdb_args = $this->postgresInitdbArgs; + $this->database->postgres_host_auth_method = $this->postgresHostAuthMethod; + $this->database->postgres_conf = $this->postgresConf; + $this->database->init_scripts = $this->initScripts; + $this->database->image = $this->image; + $this->database->ports_mappings = $this->portsMappings; + $this->database->is_public = $this->isPublic; + $this->database->public_port = $this->publicPort; + $this->database->is_log_drain_enabled = $this->isLogDrainEnabled; + $this->database->custom_docker_run_options = $this->customDockerRunOptions; + $this->database->enable_ssl = $this->enableSsl; + $this->database->ssl_mode = $this->sslMode; + $this->database->save(); - if ($existingCert) { - $this->certificateValidUntil = $existingCert->valid_until; + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; + } else { + $this->name = $this->database->name; + $this->description = $this->database->description; + $this->postgresUser = $this->database->postgres_user; + $this->postgresPassword = $this->database->postgres_password; + $this->postgresDb = $this->database->postgres_db; + $this->postgresInitdbArgs = $this->database->postgres_initdb_args; + $this->postgresHostAuthMethod = $this->database->postgres_host_auth_method; + $this->postgresConf = $this->database->postgres_conf; + $this->initScripts = $this->database->init_scripts; + $this->image = $this->database->image; + $this->portsMappings = $this->database->ports_mappings; + $this->isPublic = $this->database->is_public; + $this->publicPort = $this->database->public_port; + $this->isLogDrainEnabled = $this->database->is_log_drain_enabled; + $this->customDockerRunOptions = $this->database->custom_docker_run_options; + $this->enableSsl = $this->database->enable_ssl; + $this->sslMode = $this->database->ssl_mode; + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; } } @@ -124,12 +213,12 @@ public function instantSaveAdvanced() $this->authorize('update', $this->database); if (! $this->server->isLogDrainEnabled()) { - $this->database->is_log_drain_enabled = false; + $this->isLogDrainEnabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); return; } - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Database updated.'); $this->dispatch('success', 'You need to restart the service for the changes to take effect.'); } catch (Exception $e) { @@ -137,7 +226,7 @@ public function instantSaveAdvanced() } } - public function updatedDatabaseSslMode() + public function updatedSslMode() { $this->instantSaveSSL(); } @@ -147,10 +236,8 @@ public function instantSaveSSL() try { $this->authorize('update', $this->database); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'SSL configuration updated.'); - $this->db_url = $this->database->internal_db_url; - $this->db_url_public = $this->database->external_db_url; } catch (Exception $e) { return handleError($e, $this); } @@ -169,7 +256,7 @@ public function regenerateSslCertificate() return; } - $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first(); + $caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first(); SslHelper::generateSslCertificate( commonName: $existingCert->common_name, @@ -195,16 +282,16 @@ public function instantSave() try { $this->authorize('update', $this->database); - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->isPublic && ! $this->publicPort) { $this->dispatch('error', 'Public port is required.'); - $this->database->is_public = false; + $this->isPublic = false; return; } - if ($this->database->is_public) { + if ($this->isPublic) { if (! str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); - $this->database->is_public = false; + $this->isPublic = false; return; } @@ -214,10 +301,9 @@ public function instantSave() StopDatabaseProxy::run($this->database); $this->dispatch('success', 'Database is no longer publicly accessible.'); } - $this->db_url_public = $this->database->external_db_url; - $this->database->save(); + $this->syncData(true); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->isPublic = ! $this->isPublic; return handleError($e, $this); } @@ -227,7 +313,7 @@ public function save_init_script($script) { $this->authorize('update', $this->database); - $initScripts = collect($this->database->init_scripts ?? []); + $initScripts = collect($this->initScripts ?? []); $existingScript = $initScripts->firstWhere('filename', $script['filename']); $oldScript = $initScripts->firstWhere('index', $script['index']); @@ -263,7 +349,7 @@ public function save_init_script($script) $initScripts->push($script); } - $this->database->init_scripts = $initScripts->values() + $this->initScripts = $initScripts->values() ->map(function ($item, $index) { $item['index'] = $index; @@ -271,7 +357,7 @@ public function save_init_script($script) }) ->all(); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Init script saved and updated.'); } @@ -279,7 +365,7 @@ public function delete_init_script($script) { $this->authorize('update', $this->database); - $collection = collect($this->database->init_scripts); + $collection = collect($this->initScripts); $found = $collection->firstWhere('filename', $script['filename']); if ($found) { $container_name = $this->database->uuid; @@ -304,8 +390,8 @@ public function delete_init_script($script) }) ->all(); - $this->database->init_scripts = $updatedScripts; - $this->database->save(); + $this->initScripts = $updatedScripts; + $this->syncData(true); $this->dispatch('refresh')->self(); $this->dispatch('success', 'Init script deleted from the database and the server.'); } @@ -319,23 +405,23 @@ public function save_new_init_script() 'new_filename' => 'required|string', 'new_content' => 'required|string', ]); - $found = collect($this->database->init_scripts)->firstWhere('filename', $this->new_filename); + $found = collect($this->initScripts)->firstWhere('filename', $this->new_filename); if ($found) { $this->dispatch('error', 'Filename already exists.'); return; } - if (! isset($this->database->init_scripts)) { - $this->database->init_scripts = []; + if (! isset($this->initScripts)) { + $this->initScripts = []; } - $this->database->init_scripts = array_merge($this->database->init_scripts, [ + $this->initScripts = array_merge($this->initScripts, [ [ - 'index' => count($this->database->init_scripts), + 'index' => count($this->initScripts), 'filename' => $this->new_filename, 'content' => $this->new_content, ], ]); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Init script added.'); $this->new_content = ''; $this->new_filename = ''; @@ -346,11 +432,10 @@ public function submit() try { $this->authorize('update', $this->database); - if (str($this->database->public_port)->isEmpty()) { - $this->database->public_port = null; + if (str($this->publicPort)->isEmpty()) { + $this->publicPort = null; } - $this->validate(); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Database updated.'); } catch (Exception $e) { return handleError($e, $this); diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php index 1eb4f5c8d4..846614d21e 100644 --- a/app/Livewire/Project/Database/Redis/General.php +++ b/app/Livewire/Project/Database/Redis/General.php @@ -6,7 +6,6 @@ use App\Actions\Database\StopDatabaseProxy; use App\Helpers\SslHelper; use App\Models\Server; -use App\Models\SslCertificate; use App\Models\StandaloneRedis; use App\Support\ValidationPatterns; use Carbon\Carbon; @@ -19,19 +18,39 @@ class General extends Component { use AuthorizesRequests; - public Server $server; + public ?Server $server = null; public StandaloneRedis $database; - public string $redis_username; + public string $name; - public ?string $redis_password; + public ?string $description = null; - public string $redis_version; + public ?string $redisConf = null; - public ?string $db_url = null; + public string $image; - public ?string $db_url_public = null; + public ?string $portsMappings = null; + + public ?bool $isPublic = null; + + public ?int $publicPort = null; + + public bool $isLogDrainEnabled = false; + + public ?string $customDockerRunOptions = null; + + public string $redisUsername; + + public string $redisPassword; + + public string $redisVersion; + + public ?string $dbUrl = null; + + public ?string $dbUrlPublic = null; + + public bool $enableSsl = false; public ?Carbon $certificateValidUntil = null; @@ -42,25 +61,24 @@ public function getListeners() return [ "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', 'envsUpdated' => 'refresh', - 'refresh', ]; } protected function rules(): array { return [ - 'database.name' => ValidationPatterns::nameRules(), - 'database.description' => ValidationPatterns::descriptionRules(), - 'database.redis_conf' => 'nullable', - 'database.image' => 'required', - 'database.ports_mappings' => 'nullable', - 'database.is_public' => 'nullable|boolean', - 'database.public_port' => 'nullable|integer', - 'database.is_log_drain_enabled' => 'nullable|boolean', - 'database.custom_docker_run_options' => 'nullable', - 'redis_username' => 'required', - 'redis_password' => 'required', - 'database.enable_ssl' => 'boolean', + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'redisConf' => 'nullable', + 'image' => 'required', + 'portsMappings' => 'nullable', + 'isPublic' => 'nullable|boolean', + 'publicPort' => 'nullable|integer', + 'isLogDrainEnabled' => 'nullable|boolean', + 'customDockerRunOptions' => 'nullable', + 'redisUsername' => 'required', + 'redisPassword' => 'required', + 'enableSsl' => 'boolean', ]; } @@ -69,39 +87,87 @@ protected function messages(): array return array_merge( ValidationPatterns::combinedMessages(), [ - 'database.name.required' => 'The Name field is required.', - 'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', - 'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', - 'database.image.required' => 'The Docker Image field is required.', - 'database.public_port.integer' => 'The Public Port must be an integer.', - 'redis_username.required' => 'The Redis Username field is required.', - 'redis_password.required' => 'The Redis Password field is required.', + 'name.required' => 'The Name field is required.', + 'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'image.required' => 'The Docker Image field is required.', + 'publicPort.integer' => 'The Public Port must be an integer.', + 'redisUsername.required' => 'The Redis Username field is required.', + 'redisPassword.required' => 'The Redis Password field is required.', ] ); } protected $validationAttributes = [ - 'database.name' => 'Name', - 'database.description' => 'Description', - 'database.redis_conf' => 'Redis Configuration', - 'database.image' => 'Image', - 'database.ports_mappings' => 'Port Mapping', - 'database.is_public' => 'Is Public', - 'database.public_port' => 'Public Port', - 'database.custom_docker_run_options' => 'Custom Docker Options', - 'redis_username' => 'Redis Username', - 'redis_password' => 'Redis Password', - 'database.enable_ssl' => 'Enable SSL', + 'name' => 'Name', + 'description' => 'Description', + 'redisConf' => 'Redis Configuration', + 'image' => 'Image', + 'portsMappings' => 'Port Mapping', + 'isPublic' => 'Is Public', + 'publicPort' => 'Public Port', + 'customDockerRunOptions' => 'Custom Docker Options', + 'redisUsername' => 'Redis Username', + 'redisPassword' => 'Redis Password', + 'enableSsl' => 'Enable SSL', ]; public function mount() { - $this->server = data_get($this->database, 'destination.server'); - $this->refreshView(); - $existingCert = $this->database->sslCertificates()->first(); + try { + $this->authorize('view', $this->database); + $this->syncData(); + $this->server = data_get($this->database, 'destination.server'); + if (! $this->server) { + $this->dispatch('error', 'Database destination server is not configured.'); + + return; + } + + $existingCert = $this->database->sslCertificates()->first(); + + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->database->name = $this->name; + $this->database->description = $this->description; + $this->database->redis_conf = $this->redisConf; + $this->database->image = $this->image; + $this->database->ports_mappings = $this->portsMappings; + $this->database->is_public = $this->isPublic; + $this->database->public_port = $this->publicPort; + $this->database->is_log_drain_enabled = $this->isLogDrainEnabled; + $this->database->custom_docker_run_options = $this->customDockerRunOptions; + $this->database->enable_ssl = $this->enableSsl; + $this->database->save(); - if ($existingCert) { - $this->certificateValidUntil = $existingCert->valid_until; + $this->dbUrl = $this->database->internal_db_url; + $this->dbUrlPublic = $this->database->external_db_url; + } else { + $this->name = $this->database->name; + $this->description = $this->database->description; + $this->redisConf = $this->database->redis_conf; + $this->image = $this->database->image; + $this->portsMappings = $this->database->ports_mappings; + $this->isPublic = $this->database->is_public; + $this->publicPort = $this->database->public_port; + $this->isLogDrainEnabled = $this->database->is_log_drain_enabled; + $this->customDockerRunOptions = $this->database->custom_docker_run_options; + $this->enableSsl = $this->database->enable_ssl; + $this->dbUrl = $this->database->internal_db_url; + $this->dbUrlPublic = $this->database->external_db_url; + $this->redisVersion = $this->database->getRedisVersion(); + $this->redisUsername = $this->database->redis_username; + $this->redisPassword = $this->database->redis_password; } } @@ -111,12 +177,12 @@ public function instantSaveAdvanced() $this->authorize('update', $this->database); if (! $this->server->isLogDrainEnabled()) { - $this->database->is_log_drain_enabled = false; + $this->isLogDrainEnabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); return; } - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'Database updated.'); $this->dispatch('success', 'You need to restart the service for the changes to take effect.'); } catch (Exception $e) { @@ -129,20 +195,19 @@ public function submit() try { $this->authorize('manageEnvironment', $this->database); - $this->validate(); + $this->syncData(true); - if (version_compare($this->redis_version, '6.0', '>=')) { + if (version_compare($this->redisVersion, '6.0', '>=')) { $this->database->runtime_environment_variables()->updateOrCreate( ['key' => 'REDIS_USERNAME'], - ['value' => $this->redis_username, 'resourceable_id' => $this->database->id] + ['value' => $this->redisUsername, 'resourceable_id' => $this->database->id] ); } $this->database->runtime_environment_variables()->updateOrCreate( ['key' => 'REDIS_PASSWORD'], - ['value' => $this->redis_password, 'resourceable_id' => $this->database->id] + ['value' => $this->redisPassword, 'resourceable_id' => $this->database->id] ); - $this->database->save(); $this->dispatch('success', 'Database updated.'); } catch (Exception $e) { return handleError($e, $this); @@ -156,16 +221,16 @@ public function instantSave() try { $this->authorize('update', $this->database); - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->isPublic && ! $this->publicPort) { $this->dispatch('error', 'Public port is required.'); - $this->database->is_public = false; + $this->isPublic = false; return; } - if ($this->database->is_public) { + if ($this->isPublic) { if (! str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); - $this->database->is_public = false; + $this->isPublic = false; return; } @@ -175,10 +240,11 @@ public function instantSave() StopDatabaseProxy::run($this->database); $this->dispatch('success', 'Database is no longer publicly accessible.'); } - $this->db_url_public = $this->database->external_db_url; - $this->database->save(); + $this->dbUrlPublic = $this->database->external_db_url; + $this->syncData(true); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->isPublic = ! $this->isPublic; + $this->syncData(true); return handleError($e, $this); } @@ -189,7 +255,7 @@ public function instantSaveSSL() try { $this->authorize('update', $this->database); - $this->database->save(); + $this->syncData(true); $this->dispatch('success', 'SSL configuration updated.'); } catch (Exception $e) { return handleError($e, $this); @@ -209,7 +275,7 @@ public function regenerateSslCertificate() return; } - $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first(); + $caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first(); SslHelper::generateSslCertificate( commonName: $existingCert->commonName, @@ -233,16 +299,7 @@ public function regenerateSslCertificate() public function refresh(): void { $this->database->refresh(); - $this->refreshView(); - } - - private function refreshView() - { - $this->db_url = $this->database->internal_db_url; - $this->db_url_public = $this->database->external_db_url; - $this->redis_version = $this->database->getRedisVersion(); - $this->redis_username = $this->database->redis_username; - $this->redis_password = $this->database->redis_password; + $this->syncData(); } public function render() diff --git a/app/Livewire/Project/Index.php b/app/Livewire/Project/Index.php index 5381fa78d9..a27a3652f6 100644 --- a/app/Livewire/Project/Index.php +++ b/app/Livewire/Project/Index.php @@ -21,6 +21,14 @@ public function mount() $this->projects = Project::ownedByCurrentTeam()->get()->map(function ($project) { $project->settingsRoute = route('project.edit', ['project_uuid' => $project->uuid]); $project->canUpdate = auth()->user()->can('update', $project); + $project->canCreateResource = auth()->user()->can('createAnyResource'); + $firstEnvironment = $project->environments->first(); + $project->addResourceRoute = $firstEnvironment + ? route('project.resource.create', [ + 'project_uuid' => $project->uuid, + 'environment_uuid' => $firstEnvironment->uuid, + ]) + : null; return $project; }); diff --git a/app/Livewire/Project/New/DockerImage.php b/app/Livewire/Project/New/DockerImage.php index dbb223de20..9d04ca9a52 100644 --- a/app/Livewire/Project/New/DockerImage.php +++ b/app/Livewire/Project/New/DockerImage.php @@ -12,7 +12,11 @@ class DockerImage extends Component { - public string $dockerImage = ''; + public string $imageName = ''; + + public string $imageTag = ''; + + public string $imageSha256 = ''; public array $parameters; @@ -24,14 +28,88 @@ public function mount() $this->query = request()->query(); } + /** + * Auto-parse image name when user pastes a complete Docker image reference + * Examples: + * - nginx:stable-alpine3.21-perl@sha256:4e272eef... + * - ghcr.io/user/app:v1.2.3 + * - nginx@sha256:abc123... + */ + public function updatedImageName(): void + { + if (empty($this->imageName)) { + return; + } + + // Don't auto-parse if user has already manually filled tag or sha256 fields + if (! empty($this->imageTag) || ! empty($this->imageSha256)) { + return; + } + + // Only auto-parse if the image name contains a tag (:) or digest (@) + if (! str_contains($this->imageName, ':') && ! str_contains($this->imageName, '@')) { + return; + } + + try { + $parser = new DockerImageParser; + $parser->parse($this->imageName); + + // Extract the base image name (without tag/digest) + $baseImageName = $parser->getFullImageNameWithoutTag(); + + // Only update if parsing resulted in different base name + // This prevents unnecessary updates when user types just the name + if ($baseImageName !== $this->imageName) { + if ($parser->isImageHash()) { + // It's a SHA256 digest (takes priority over tag) + $this->imageSha256 = $parser->getTag(); + $this->imageTag = ''; + } elseif ($parser->getTag() !== 'latest' || str_contains($this->imageName, ':')) { + // It's a regular tag (only set if not default 'latest' or explicitly specified) + $this->imageTag = $parser->getTag(); + $this->imageSha256 = ''; + } + + // Update imageName to just the base name + $this->imageName = $baseImageName; + } + } catch (\Exception $e) { + // If parsing fails, leave the image name as-is + // User will see validation error on submit + } + } + public function submit() { $this->validate([ - 'dockerImage' => 'required', + 'imageName' => ['required', 'string'], + 'imageTag' => ['nullable', 'string', 'regex:/^[a-z0-9][a-z0-9._-]*$/i'], + 'imageSha256' => ['nullable', 'string', 'regex:/^[a-f0-9]{64}$/i'], ]); + // Validate that either tag or sha256 is provided, but not both + if ($this->imageTag && $this->imageSha256) { + $this->addError('imageTag', 'Provide either a tag or SHA256 digest, not both.'); + $this->addError('imageSha256', 'Provide either a tag or SHA256 digest, not both.'); + + return; + } + + // Build the full Docker image string + if ($this->imageSha256) { + // Strip 'sha256:' prefix if user pasted it + $sha256Hash = preg_replace('/^sha256:/i', '', trim($this->imageSha256)); + $dockerImage = $this->imageName.'@sha256:'.$sha256Hash; + } elseif ($this->imageTag) { + $dockerImage = $this->imageName.':'.$this->imageTag; + } else { + $dockerImage = $this->imageName.':latest'; + } + + // Parse using DockerImageParser to normalize the image reference $parser = new DockerImageParser; - $parser->parse($this->dockerImage); + $parser->parse($dockerImage); $destination_uuid = $this->query['destination']; $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); @@ -45,6 +123,16 @@ public function submit() $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); + + // Append @sha256 to image name if using digest and not already present + $imageName = $parser->getFullImageNameWithoutTag(); + if ($parser->isImageHash() && ! str_ends_with($imageName, '@sha256')) { + $imageName .= '@sha256'; + } + + // Determine the image tag based on whether it's a hash or regular tag + $imageTag = $parser->isImageHash() ? 'sha256-'.$parser->getTag() : $parser->getTag(); + $application = Application::create([ 'name' => 'docker-image-'.new Cuid2, 'repository_project_id' => 0, @@ -52,8 +140,8 @@ public function submit() 'git_branch' => 'main', 'build_pack' => 'dockerimage', 'ports_exposes' => 80, - 'docker_registry_image_name' => $parser->getFullImageNameWithoutTag(), - 'docker_registry_image_tag' => $parser->getTag(), + 'docker_registry_image_name' => $imageName, + 'docker_registry_image_tag' => $imageTag, 'environment_id' => $environment->id, 'destination_id' => $destination->id, 'destination_type' => $destination_class, diff --git a/app/Livewire/Project/New/GithubPrivateRepository.php b/app/Livewire/Project/New/GithubPrivateRepository.php index a2071931eb..27ecacb996 100644 --- a/app/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Livewire/Project/New/GithubPrivateRepository.php @@ -55,7 +55,7 @@ class GithubPrivateRepository extends Component public ?string $publish_directory = null; // In case of docker compose - public ?string $base_directory = null; + public ?string $base_directory = '/'; public ?string $docker_compose_location = '/docker-compose.yaml'; // End of docker compose @@ -198,6 +198,7 @@ public function submit() 'build_pack' => $this->build_pack, 'ports_exposes' => $this->port, 'publish_directory' => $this->publish_directory, + 'base_directory' => $this->base_directory, 'environment_id' => $environment->id, 'destination_id' => $destination->id, 'destination_type' => $destination_class, @@ -212,7 +213,6 @@ public function submit() } if ($this->build_pack === 'dockercompose') { $application['docker_compose_location'] = $this->docker_compose_location; - $application['base_directory'] = $this->base_directory; } $fqdn = generateUrl(server: $destination->server, random: $application->uuid); $application->fqdn = $fqdn; diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index 73960d288e..cdf95d2e4b 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -81,7 +81,7 @@ public function mount() 'destination_id' => $destination->id, 'destination_type' => $destination->getMorphClass(), ]; - if ($oneClickServiceName === 'cloudflared') { + if ($oneClickServiceName === 'cloudflared' || $oneClickServiceName === 'pgadmin') { data_set($service_payload, 'connect_to_docker_network', true); } $service = Service::create($service_payload); diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php index 559851e3a3..2d69ceb12b 100644 --- a/app/Livewire/Project/Service/Configuration.php +++ b/app/Livewire/Project/Service/Configuration.php @@ -33,6 +33,8 @@ public function getListeners() return [ "echo-private:team.{$teamId},ServiceChecked" => 'serviceChecked', + 'refreshServices' => 'refreshServices', + 'refresh' => 'refreshServices', ]; } diff --git a/app/Livewire/Project/Service/EditDomain.php b/app/Livewire/Project/Service/EditDomain.php index 7c718393d4..dbbcca8f87 100644 --- a/app/Livewire/Project/Service/EditDomain.php +++ b/app/Livewire/Project/Service/EditDomain.php @@ -73,6 +73,7 @@ public function submit() } $this->application->service->parse(); $this->dispatch('refresh'); + $this->dispatch('refreshServices'); $this->dispatch('configurationChanged'); } catch (\Throwable $e) { $originalFqdn = $this->application->getOriginal('fqdn'); diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php index 2933a8cca5..7f0caaba3f 100644 --- a/app/Livewire/Project/Service/FileStorage.php +++ b/app/Livewire/Project/Service/FileStorage.php @@ -34,6 +34,8 @@ class FileStorage extends Component public bool $permanently_delete = true; + public bool $isReadOnly = false; + protected $rules = [ 'fileStorage.is_directory' => 'required', 'fileStorage.fs_path' => 'required', @@ -52,6 +54,8 @@ public function mount() $this->workdir = null; $this->fs_path = $this->fileStorage->fs_path; } + + $this->isReadOnly = $this->fileStorage->isReadOnlyVolume(); } public function convertToDirectory() diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 639c025c7f..07938d9d08 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -212,6 +212,12 @@ private function handleSingleSubmit($data) $environment = $this->createEnvironmentVariable($data); $environment->order = $maxOrder + 1; $environment->save(); + + // Clear computed property cache to force refresh + unset($this->environmentVariables); + unset($this->environmentVariablesPreview); + + $this->dispatch('success', 'Environment variable added.'); } private function createEnvironmentVariable($data) @@ -300,6 +306,9 @@ private function updateOrCreateVariables($isPreview, $variables) public function refreshEnvs() { $this->resource->refresh(); + // Clear computed property cache to force refresh + unset($this->environmentVariables); + unset($this->environmentVariablesPreview); $this->getDevView(); } } diff --git a/app/Livewire/Project/Shared/Storages/Show.php b/app/Livewire/Project/Shared/Storages/Show.php index 3928ee1d4a..4f57cbfa6f 100644 --- a/app/Livewire/Project/Shared/Storages/Show.php +++ b/app/Livewire/Project/Shared/Storages/Show.php @@ -37,6 +37,11 @@ class Show extends Component 'host_path' => 'host', ]; + public function mount() + { + $this->isReadOnly = $this->storage->isReadOnlyVolume(); + } + public function submit() { $this->authorize('update', $this->resource); diff --git a/app/Livewire/Security/CloudInitScriptForm.php b/app/Livewire/Security/CloudInitScriptForm.php new file mode 100644 index 0000000000..33beff3341 --- /dev/null +++ b/app/Livewire/Security/CloudInitScriptForm.php @@ -0,0 +1,101 @@ +scriptId = $scriptId; + $cloudInitScript = CloudInitScript::ownedByCurrentTeam()->findOrFail($scriptId); + $this->authorize('update', $cloudInitScript); + + $this->name = $cloudInitScript->name; + $this->script = $cloudInitScript->script; + } else { + $this->authorize('create', CloudInitScript::class); + } + } + + protected function rules(): array + { + return [ + 'name' => 'required|string|max:255', + 'script' => ['required', 'string', new \App\Rules\ValidCloudInitYaml], + ]; + } + + protected function messages(): array + { + return [ + 'name.required' => 'Script name is required.', + 'name.max' => 'Script name cannot exceed 255 characters.', + 'script.required' => 'Cloud-init script content is required.', + ]; + } + + public function save() + { + $this->validate(); + + try { + if ($this->scriptId) { + // Update existing script + $cloudInitScript = CloudInitScript::ownedByCurrentTeam()->findOrFail($this->scriptId); + $this->authorize('update', $cloudInitScript); + + $cloudInitScript->update([ + 'name' => $this->name, + 'script' => $this->script, + ]); + + $message = 'Cloud-init script updated successfully.'; + } else { + // Create new script + $this->authorize('create', CloudInitScript::class); + + CloudInitScript::create([ + 'team_id' => currentTeam()->id, + 'name' => $this->name, + 'script' => $this->script, + ]); + + $message = 'Cloud-init script created successfully.'; + } + + // Only reset fields if creating (not editing) + if (! $this->scriptId) { + $this->reset(['name', 'script']); + } + + $this->dispatch('scriptSaved'); + $this->dispatch('success', $message); + + if ($this->modal_mode) { + $this->dispatch('closeModal'); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.security.cloud-init-script-form'); + } +} diff --git a/app/Livewire/Security/CloudInitScripts.php b/app/Livewire/Security/CloudInitScripts.php new file mode 100644 index 0000000000..13bcf2caab --- /dev/null +++ b/app/Livewire/Security/CloudInitScripts.php @@ -0,0 +1,52 @@ +authorize('viewAny', CloudInitScript::class); + $this->loadScripts(); + } + + public function getListeners() + { + return [ + 'scriptSaved' => 'loadScripts', + ]; + } + + public function loadScripts() + { + $this->scripts = CloudInitScript::ownedByCurrentTeam()->orderBy('created_at', 'desc')->get(); + } + + public function deleteScript(int $scriptId) + { + try { + $script = CloudInitScript::ownedByCurrentTeam()->findOrFail($scriptId); + $this->authorize('delete', $script); + + $script->delete(); + $this->loadScripts(); + + $this->dispatch('success', 'Cloud-init script deleted successfully.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.security.cloud-init-scripts'); + } +} diff --git a/app/Livewire/Security/CloudProviderTokenForm.php b/app/Livewire/Security/CloudProviderTokenForm.php new file mode 100644 index 0000000000..7affb15311 --- /dev/null +++ b/app/Livewire/Security/CloudProviderTokenForm.php @@ -0,0 +1,99 @@ +authorize('create', CloudProviderToken::class); + } + + protected function rules(): array + { + return [ + 'provider' => 'required|string|in:hetzner,digitalocean', + 'token' => 'required|string', + 'name' => 'required|string|max:255', + ]; + } + + protected function messages(): array + { + return [ + 'provider.required' => 'Please select a cloud provider.', + 'provider.in' => 'Invalid cloud provider selected.', + 'token.required' => 'API token is required.', + 'name.required' => 'Token name is required.', + ]; + } + + private function validateToken(string $provider, string $token): bool + { + try { + if ($provider === 'hetzner') { + $response = Http::withHeaders([ + 'Authorization' => 'Bearer '.$token, + ])->timeout(10)->get('https://api.hetzner.cloud/v1/servers'); + ray($response); + + return $response->successful(); + } + + // Add other providers here in the future + // if ($provider === 'digitalocean') { ... } + + return false; + } catch (\Throwable $e) { + return false; + } + } + + public function addToken() + { + $this->validate(); + + try { + // Validate the token with the provider's API + if (! $this->validateToken($this->provider, $this->token)) { + return $this->dispatch('error', 'Invalid API token. Please check your token and try again.'); + } + + $savedToken = CloudProviderToken::create([ + 'team_id' => currentTeam()->id, + 'provider' => $this->provider, + 'token' => $this->token, + 'name' => $this->name, + ]); + + $this->reset(['token', 'name']); + + // Dispatch event with token ID so parent components can react + $this->dispatch('tokenAdded', tokenId: $savedToken->id); + + $this->dispatch('success', 'Cloud provider token added successfully.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.security.cloud-provider-token-form'); + } +} diff --git a/app/Livewire/Security/CloudProviderTokens.php b/app/Livewire/Security/CloudProviderTokens.php new file mode 100644 index 0000000000..f05b3c0cae --- /dev/null +++ b/app/Livewire/Security/CloudProviderTokens.php @@ -0,0 +1,60 @@ +authorize('viewAny', CloudProviderToken::class); + $this->loadTokens(); + } + + public function getListeners() + { + return [ + 'tokenAdded' => 'loadTokens', + ]; + } + + public function loadTokens() + { + $this->tokens = CloudProviderToken::ownedByCurrentTeam()->get(); + } + + public function deleteToken(int $tokenId) + { + try { + $token = CloudProviderToken::ownedByCurrentTeam()->findOrFail($tokenId); + $this->authorize('delete', $token); + + // Check if any servers are using this token + if ($token->hasServers()) { + $serverCount = $token->servers()->count(); + $this->dispatch('error', "Cannot delete this token. It is currently used by {$serverCount} server(s). Please reassign those servers to a different token first."); + + return; + } + + $token->delete(); + $this->loadTokens(); + + $this->dispatch('success', 'Cloud provider token deleted successfully.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.security.cloud-provider-tokens'); + } +} diff --git a/app/Livewire/Security/CloudTokens.php b/app/Livewire/Security/CloudTokens.php new file mode 100644 index 0000000000..d6d1333f12 --- /dev/null +++ b/app/Livewire/Security/CloudTokens.php @@ -0,0 +1,13 @@ + currentTeam()->id, ]); + // If in modal mode, dispatch event and don't redirect + if ($this->modal_mode) { + $this->dispatch('privateKeyCreated', keyId: $privateKey->id); + $this->dispatch('success', 'Private key created successfully.'); + + return; + } + return $this->redirectAfterCreation($privateKey); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Server/CaCertificate/Show.php b/app/Livewire/Server/CaCertificate/Show.php index 039b5f71dc..c929d9b3d5 100644 --- a/app/Livewire/Server/CaCertificate/Show.php +++ b/app/Livewire/Server/CaCertificate/Show.php @@ -39,7 +39,7 @@ public function mount(string $server_uuid) public function loadCaCertificate() { - $this->caCertificate = SslCertificate::where('server_id', $this->server->id)->where('is_ca_certificate', true)->first(); + $this->caCertificate = $this->server->sslCertificates()->where('is_ca_certificate', true)->first(); if ($this->caCertificate) { $this->certificateContent = $this->caCertificate->ssl_certificate; diff --git a/app/Livewire/Server/CloudProviderToken/Show.php b/app/Livewire/Server/CloudProviderToken/Show.php new file mode 100644 index 0000000000..6b22fddc60 --- /dev/null +++ b/app/Livewire/Server/CloudProviderToken/Show.php @@ -0,0 +1,144 @@ +server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail(); + $this->loadTokens(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function getListeners() + { + return [ + 'tokenAdded' => 'handleTokenAdded', + ]; + } + + public function loadTokens() + { + $this->cloudProviderTokens = CloudProviderToken::ownedByCurrentTeam() + ->where('provider', 'hetzner') + ->get(); + } + + public function handleTokenAdded($tokenId) + { + $this->loadTokens(); + } + + public function setCloudProviderToken($tokenId) + { + $ownedToken = CloudProviderToken::ownedByCurrentTeam()->find($tokenId); + if (is_null($ownedToken)) { + $this->dispatch('error', 'You are not allowed to use this token.'); + + return; + } + try { + $this->authorize('update', $this->server); + + // Validate the token works and can access this specific server + $validationResult = $this->validateTokenForServer($ownedToken); + if (! $validationResult['valid']) { + $this->dispatch('error', $validationResult['error']); + + return; + } + + $this->server->cloudProviderToken()->associate($ownedToken); + $this->server->save(); + $this->dispatch('success', 'Hetzner token updated successfully.'); + $this->dispatch('refreshServerShow'); + } catch (\Exception $e) { + $this->server->refresh(); + $this->dispatch('error', $e->getMessage()); + } + } + + private function validateTokenForServer(CloudProviderToken $token): array + { + try { + // First, validate the token itself + $response = \Illuminate\Support\Facades\Http::withHeaders([ + 'Authorization' => 'Bearer '.$token->token, + ])->timeout(10)->get('https://api.hetzner.cloud/v1/servers'); + + if (! $response->successful()) { + return [ + 'valid' => false, + 'error' => 'This token is invalid or has insufficient permissions.', + ]; + } + + // Check if this token can access the specific Hetzner server + if ($this->server->hetzner_server_id) { + $serverResponse = \Illuminate\Support\Facades\Http::withHeaders([ + 'Authorization' => 'Bearer '.$token->token, + ])->timeout(10)->get("https://api.hetzner.cloud/v1/servers/{$this->server->hetzner_server_id}"); + + if (! $serverResponse->successful()) { + return [ + 'valid' => false, + 'error' => 'This token cannot access this server. It may belong to a different Hetzner project.', + ]; + } + } + + return ['valid' => true]; + } catch (\Throwable $e) { + return [ + 'valid' => false, + 'error' => 'Failed to validate token: '.$e->getMessage(), + ]; + } + } + + public function validateToken() + { + try { + $token = $this->server->cloudProviderToken; + if (! $token) { + $this->dispatch('error', 'No Hetzner token is associated with this server.'); + + return; + } + + $response = \Illuminate\Support\Facades\Http::withHeaders([ + 'Authorization' => 'Bearer '.$token->token, + ])->timeout(10)->get('https://api.hetzner.cloud/v1/servers'); + + if ($response->successful()) { + $this->dispatch('success', 'Hetzner token is valid and working.'); + } else { + $this->dispatch('error', 'Hetzner token is invalid or has insufficient permissions.'); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.server.cloud-provider-token.show'); + } +} diff --git a/app/Livewire/Server/Create.php b/app/Livewire/Server/Create.php index 2d4ba4430c..cf77664fec 100644 --- a/app/Livewire/Server/Create.php +++ b/app/Livewire/Server/Create.php @@ -2,6 +2,7 @@ namespace App\Livewire\Server; +use App\Models\CloudProviderToken; use App\Models\PrivateKey; use App\Models\Team; use Livewire\Component; @@ -12,6 +13,8 @@ class Create extends Component public bool $limit_reached = false; + public bool $has_hetzner_tokens = false; + public function mount() { $this->private_keys = PrivateKey::ownedByCurrentTeam()->get(); @@ -21,6 +24,11 @@ public function mount() return; } $this->limit_reached = Team::serverLimitReached(); + + // Check if user has Hetzner tokens + $this->has_hetzner_tokens = CloudProviderToken::ownedByCurrentTeam() + ->where('provider', 'hetzner') + ->exists(); } public function render() diff --git a/app/Livewire/Server/Delete.php b/app/Livewire/Server/Delete.php index b9e3944b54..8c2c54c996 100644 --- a/app/Livewire/Server/Delete.php +++ b/app/Livewire/Server/Delete.php @@ -16,6 +16,8 @@ class Delete extends Component public Server $server; + public bool $delete_from_hetzner = false; + public function mount(string $server_uuid) { try { @@ -41,8 +43,15 @@ public function delete($password) return; } + $this->server->delete(); - DeleteServer::dispatch($this->server); + DeleteServer::dispatch( + $this->server->id, + $this->delete_from_hetzner, + $this->server->hetzner_server_id, + $this->server->cloud_provider_token_id, + $this->server->team_id + ); return redirect()->route('server.index'); } catch (\Throwable $e) { @@ -52,6 +61,18 @@ public function delete($password) public function render() { - return view('livewire.server.delete'); + $checkboxes = []; + + if ($this->server->hetzner_server_id) { + $checkboxes[] = [ + 'id' => 'delete_from_hetzner', + 'label' => 'Also delete server from Hetzner Cloud', + 'default_warning' => 'The actual server on Hetzner Cloud will NOT be deleted.', + ]; + } + + return view('livewire.server.delete', [ + 'checkboxes' => $checkboxes, + ]); } } diff --git a/app/Livewire/Server/Navbar.php b/app/Livewire/Server/Navbar.php index beefed12ac..6baa54672d 100644 --- a/app/Livewire/Server/Navbar.php +++ b/app/Livewire/Server/Navbar.php @@ -118,17 +118,31 @@ public function checkProxyStatus() public function showNotification() { + $this->server->refresh(); $this->proxyStatus = $this->server->proxy->status ?? 'unknown'; - $forceStop = $this->server->proxy->force_stop ?? false; switch ($this->proxyStatus) { case 'running': $this->loadProxyConfiguration(); + $this->dispatch('success', 'Proxy is running.'); break; case 'restarting': $this->dispatch('info', 'Initiating proxy restart.'); break; + case 'exited': + $this->dispatch('info', 'Proxy has exited.'); + break; + case 'stopping': + $this->dispatch('info', 'Proxy is stopping.'); + break; + case 'starting': + $this->dispatch('info', 'Proxy is starting.'); + break; + case 'unknown': + $this->dispatch('info', 'Proxy status is unknown.'); + break; default: + $this->dispatch('info', 'Proxy status updated.'); break; } diff --git a/app/Livewire/Server/New/ByHetzner.php b/app/Livewire/Server/New/ByHetzner.php new file mode 100644 index 0000000000..a77c5df789 --- /dev/null +++ b/app/Livewire/Server/New/ByHetzner.php @@ -0,0 +1,587 @@ +authorize('viewAny', CloudProviderToken::class); + $this->loadTokens(); + $this->loadSavedCloudInitScripts(); + $this->server_name = generate_random_name(); + if ($this->private_keys->count() > 0) { + $this->private_key_id = $this->private_keys->first()->id; + } + } + + public function loadSavedCloudInitScripts() + { + $this->saved_cloud_init_scripts = CloudInitScript::ownedByCurrentTeam()->get(); + } + + public function getListeners() + { + return [ + 'tokenAdded' => 'handleTokenAdded', + 'privateKeyCreated' => 'handlePrivateKeyCreated', + 'modalClosed' => 'resetSelection', + ]; + } + + public function resetSelection() + { + $this->selected_token_id = null; + $this->current_step = 1; + $this->cloud_init_script = null; + $this->save_cloud_init_script = false; + $this->cloud_init_script_name = null; + $this->selected_cloud_init_script_id = null; + } + + public function loadTokens() + { + $this->available_tokens = CloudProviderToken::ownedByCurrentTeam() + ->where('provider', 'hetzner') + ->get(); + } + + public function handleTokenAdded($tokenId) + { + // Refresh token list + $this->loadTokens(); + + // Auto-select the new token + $this->selected_token_id = $tokenId; + + // Automatically proceed to next step + $this->nextStep(); + } + + public function handlePrivateKeyCreated($keyId) + { + // Refresh private keys list + $this->private_keys = PrivateKey::ownedByCurrentTeam()->get(); + + // Auto-select the new key + $this->private_key_id = $keyId; + + // Clear validation errors for private_key_id + $this->resetErrorBag('private_key_id'); + } + + protected function rules(): array + { + $rules = [ + 'selected_token_id' => 'required|integer|exists:cloud_provider_tokens,id', + ]; + + if ($this->current_step === 2) { + $rules = array_merge($rules, [ + 'server_name' => ['required', 'string', 'max:253', new ValidHostname], + 'selected_location' => 'required|string', + 'selected_image' => 'required|integer', + 'selected_server_type' => 'required|string', + 'private_key_id' => 'required|integer|exists:private_keys,id,team_id,'.currentTeam()->id, + 'selectedHetznerSshKeyIds' => 'nullable|array', + 'selectedHetznerSshKeyIds.*' => 'integer', + 'enable_ipv4' => 'required|boolean', + 'enable_ipv6' => 'required|boolean', + 'cloud_init_script' => ['nullable', 'string', new \App\Rules\ValidCloudInitYaml], + 'save_cloud_init_script' => 'boolean', + 'cloud_init_script_name' => 'nullable|string|max:255', + 'selected_cloud_init_script_id' => 'nullable|integer|exists:cloud_init_scripts,id', + ]); + } + + return $rules; + } + + protected function messages(): array + { + return [ + 'selected_token_id.required' => 'Please select a Hetzner token.', + 'selected_token_id.exists' => 'Selected token not found.', + ]; + } + + public function selectToken(int $tokenId) + { + $this->selected_token_id = $tokenId; + } + + private function validateHetznerToken(string $token): bool + { + try { + $response = Http::withHeaders([ + 'Authorization' => 'Bearer '.$token, + ])->timeout(10)->get('https://api.hetzner.cloud/v1/servers'); + + return $response->successful(); + } catch (\Throwable $e) { + return false; + } + } + + private function getHetznerToken(): string + { + if ($this->selected_token_id) { + $token = $this->available_tokens->firstWhere('id', $this->selected_token_id); + + return $token ? $token->token : ''; + } + + return ''; + } + + public function nextStep() + { + // Validate step 1 - just need a token selected + $this->validate([ + 'selected_token_id' => 'required|integer|exists:cloud_provider_tokens,id', + ]); + + try { + $hetznerToken = $this->getHetznerToken(); + + if (! $hetznerToken) { + return $this->dispatch('error', 'Please select a valid Hetzner token.'); + } + + // Load Hetzner data + $this->loadHetznerData($hetznerToken); + + // Move to step 2 + $this->current_step = 2; + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function previousStep() + { + $this->current_step = 1; + } + + private function loadHetznerData(string $token) + { + $this->loading_data = true; + + try { + $hetznerService = new HetznerService($token); + + $this->locations = $hetznerService->getLocations(); + $this->serverTypes = $hetznerService->getServerTypes(); + + // Get images and sort by name + $images = $hetznerService->getImages(); + + ray('Raw images from Hetzner API', [ + 'total_count' => count($images), + 'types' => collect($images)->pluck('type')->unique()->values(), + 'sample' => array_slice($images, 0, 3), + ]); + + $this->images = collect($images) + ->filter(function ($image) { + // Only system images + if (! isset($image['type']) || $image['type'] !== 'system') { + return false; + } + + // Filter out deprecated images + if (isset($image['deprecated']) && $image['deprecated'] === true) { + return false; + } + + return true; + }) + ->sortBy('name') + ->values() + ->toArray(); + + ray('Filtered images', [ + 'filtered_count' => count($this->images), + 'debian_images' => collect($this->images)->filter(fn ($img) => str_contains($img['name'] ?? '', 'debian'))->values(), + ]); + + // Load SSH keys from Hetzner + $this->hetznerSshKeys = $hetznerService->getSshKeys(); + + ray('Hetzner SSH Keys', [ + 'total_count' => count($this->hetznerSshKeys), + 'keys' => $this->hetznerSshKeys, + ]); + + $this->loading_data = false; + } catch (\Throwable $e) { + $this->loading_data = false; + throw $e; + } + } + + private function getCpuVendorInfo(array $serverType): string|null + { + $name = strtolower($serverType['name'] ?? ''); + + if (str_starts_with($name, 'ccx')) { + return 'AMD Milan EPYC™'; + } elseif (str_starts_with($name, 'cpx')) { + return 'AMD EPYC™'; + } elseif (str_starts_with($name, 'cx')) { + return 'Intel® Xeon®'; + } elseif (str_starts_with($name, 'cax')) { + return 'Ampere® Altra®'; + } + + return null; + } + + public function getAvailableServerTypesProperty() + { + ray('Getting available server types', [ + 'selected_location' => $this->selected_location, + 'total_server_types' => count($this->serverTypes), + ]); + + if (! $this->selected_location) { + return $this->serverTypes; + } + + $filtered = collect($this->serverTypes) + ->filter(function ($type) { + if (! isset($type['locations'])) { + return false; + } + + $locationNames = collect($type['locations'])->pluck('name')->toArray(); + + return in_array($this->selected_location, $locationNames); + }) + ->map(function ($serverType) { + $serverType['cpu_vendor_info'] = $this->getCpuVendorInfo($serverType); + + return $serverType; + }) + ->values() + ->toArray(); + + ray('Filtered server types', [ + 'selected_location' => $this->selected_location, + 'filtered_count' => count($filtered), + ]); + + return $filtered; + } + + public function getAvailableImagesProperty() + { + ray('Getting available images', [ + 'selected_server_type' => $this->selected_server_type, + 'total_images' => count($this->images), + 'images' => $this->images, + ]); + + if (! $this->selected_server_type) { + return $this->images; + } + + $serverType = collect($this->serverTypes)->firstWhere('name', $this->selected_server_type); + + ray('Server type data', $serverType); + + if (! $serverType || ! isset($serverType['architecture'])) { + ray('No architecture in server type, returning all'); + + return $this->images; + } + + $architecture = $serverType['architecture']; + + $filtered = collect($this->images) + ->filter(fn ($image) => ($image['architecture'] ?? null) === $architecture) + ->values() + ->toArray(); + + ray('Filtered images', [ + 'architecture' => $architecture, + 'filtered_count' => count($filtered), + ]); + + return $filtered; + } + + public function getSelectedServerPriceProperty(): ?string + { + if (! $this->selected_server_type) { + return null; + } + + $serverType = collect($this->serverTypes)->firstWhere('name', $this->selected_server_type); + + if (! $serverType || ! isset($serverType['prices'][0]['price_monthly']['gross'])) { + return null; + } + + $price = $serverType['prices'][0]['price_monthly']['gross']; + + return '€'.number_format($price, 2); + } + + public function updatedSelectedLocation($value) + { + ray('Location selected', $value); + + // Reset server type and image when location changes + $this->selected_server_type = null; + $this->selected_image = null; + } + + public function updatedSelectedServerType($value) + { + ray('Server type selected', $value); + + // Reset image when server type changes + $this->selected_image = null; + } + + public function updatedSelectedImage($value) + { + ray('Image selected', $value); + } + + public function updatedSelectedCloudInitScriptId($value) + { + if ($value) { + $script = CloudInitScript::ownedByCurrentTeam()->findOrFail($value); + $this->cloud_init_script = $script->script; + $this->cloud_init_script_name = $script->name; + } + } + + public function clearCloudInitScript() + { + $this->selected_cloud_init_script_id = null; + $this->cloud_init_script = ''; + $this->cloud_init_script_name = ''; + $this->save_cloud_init_script = false; + } + + private function createHetznerServer(string $token): array + { + $hetznerService = new HetznerService($token); + + // Get the private key and extract public key + $privateKey = PrivateKey::ownedByCurrentTeam()->findOrFail($this->private_key_id); + + $publicKey = $privateKey->getPublicKey(); + $md5Fingerprint = PrivateKey::generateMd5Fingerprint($privateKey->private_key); + + ray('Private Key Info', [ + 'private_key_id' => $this->private_key_id, + 'sha256_fingerprint' => $privateKey->fingerprint, + 'md5_fingerprint' => $md5Fingerprint, + ]); + + // Check if SSH key already exists on Hetzner by comparing MD5 fingerprints + $existingSshKeys = $hetznerService->getSshKeys(); + $existingKey = null; + + ray('Existing SSH Keys on Hetzner', $existingSshKeys); + + foreach ($existingSshKeys as $key) { + if ($key['fingerprint'] === $md5Fingerprint) { + $existingKey = $key; + break; + } + } + + // Upload SSH key if it doesn't exist + if ($existingKey) { + $sshKeyId = $existingKey['id']; + ray('Using existing SSH key', ['ssh_key_id' => $sshKeyId]); + } else { + $sshKeyName = $privateKey->name; + $uploadedKey = $hetznerService->uploadSshKey($sshKeyName, $publicKey); + $sshKeyId = $uploadedKey['id']; + ray('Uploaded new SSH key', ['ssh_key_id' => $sshKeyId, 'name' => $sshKeyName]); + } + + // Normalize server name to lowercase for RFC 1123 compliance + $normalizedServerName = strtolower(trim($this->server_name)); + + // Prepare SSH keys array: Coolify key + user-selected Hetzner keys + $sshKeys = array_merge( + [$sshKeyId], // Coolify key (always included) + $this->selectedHetznerSshKeyIds // User-selected Hetzner keys + ); + + // Remove duplicates in case the Coolify key was also selected + $sshKeys = array_unique($sshKeys); + $sshKeys = array_values($sshKeys); // Re-index array + + // Prepare server creation parameters + $params = [ + 'name' => $normalizedServerName, + 'server_type' => $this->selected_server_type, + 'image' => $this->selected_image, + 'location' => $this->selected_location, + 'start_after_create' => true, + 'ssh_keys' => $sshKeys, + 'public_net' => [ + 'enable_ipv4' => $this->enable_ipv4, + 'enable_ipv6' => $this->enable_ipv6, + ], + ]; + + // Add cloud-init script if provided + if (! empty($this->cloud_init_script)) { + $params['user_data'] = $this->cloud_init_script; + } + + ray('Server creation parameters', $params); + + // Create server on Hetzner + $hetznerServer = $hetznerService->createServer($params); + + ray('Hetzner server created', $hetznerServer); + + return $hetznerServer; + } + + public function submit() + { + $this->validate(); + + try { + $this->authorize('create', Server::class); + + if (Team::serverLimitReached()) { + return $this->dispatch('error', 'You have reached the server limit for your subscription.'); + } + + // Save cloud-init script if requested + if ($this->save_cloud_init_script && ! empty($this->cloud_init_script) && ! empty($this->cloud_init_script_name)) { + $this->authorize('create', CloudInitScript::class); + + CloudInitScript::create([ + 'team_id' => currentTeam()->id, + 'name' => $this->cloud_init_script_name, + 'script' => $this->cloud_init_script, + ]); + } + + $hetznerToken = $this->getHetznerToken(); + + // Create server on Hetzner + $hetznerServer = $this->createHetznerServer($hetznerToken); + + // Determine IP address to use (prefer IPv4, fallback to IPv6) + $ipAddress = null; + if ($this->enable_ipv4 && isset($hetznerServer['public_net']['ipv4']['ip'])) { + $ipAddress = $hetznerServer['public_net']['ipv4']['ip']; + } elseif ($this->enable_ipv6 && isset($hetznerServer['public_net']['ipv6']['ip'])) { + $ipAddress = $hetznerServer['public_net']['ipv6']['ip']; + } + + if (! $ipAddress) { + throw new \Exception('No public IP address available. Enable at least one of IPv4 or IPv6.'); + } + + // Create server in Coolify database + $server = Server::create([ + 'name' => $this->server_name, + 'ip' => $ipAddress, + 'user' => 'root', + 'port' => 22, + 'team_id' => currentTeam()->id, + 'private_key_id' => $this->private_key_id, + 'cloud_provider_token_id' => $this->selected_token_id, + 'hetzner_server_id' => $hetznerServer['id'], + ]); + + $server->proxy->set('status', 'exited'); + $server->proxy->set('type', ProxyTypes::TRAEFIK->value); + $server->save(); + + return redirect()->route('server.show', $server->uuid); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.server.new.by-hetzner'); + } +} diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php index db4dc9b886..4626a9135f 100644 --- a/app/Livewire/Server/Show.php +++ b/app/Livewire/Server/Show.php @@ -67,13 +67,21 @@ class Show extends Component public string $serverTimezone; + public ?string $hetznerServerStatus = null; + + public bool $hetznerServerManuallyStarted = false; + + public bool $isValidating = false; + public function getListeners() { $teamId = $this->server->team_id ?? auth()->user()->currentTeam()->id; return [ 'refreshServerShow' => 'refresh', + 'refreshServer' => '$refresh', "echo-private:team.{$teamId},SentinelRestarted" => 'handleSentinelRestarted', + "echo-private:team.{$teamId},ServerValidated" => 'handleServerValidated', ]; } @@ -138,6 +146,10 @@ public function mount(string $server_uuid) if (! $this->server->isEmpty()) { $this->isBuildServerLocked = true; } + // Load saved Hetzner status and validation state + $this->hetznerServerStatus = $this->server->hetzner_server_status; + $this->isValidating = $this->server->is_validating ?? false; + } catch (\Throwable $e) { return handleError($e, $this); } @@ -218,6 +230,7 @@ public function syncData(bool $toModel = false) $this->isSentinelDebugEnabled = $this->server->settings->is_sentinel_debug_enabled; $this->sentinelUpdatedAt = $this->server->sentinel_updated_at; $this->serverTimezone = $this->server->settings->server_timezone; + $this->isValidating = $this->server->is_validating ?? false; } } @@ -361,6 +374,87 @@ public function instantSave() } } + public function checkHetznerServerStatus(bool $manual = false) + { + try { + if (! $this->server->hetzner_server_id || ! $this->server->cloudProviderToken) { + $this->dispatch('error', 'This server is not associated with a Hetzner Cloud server or token.'); + + return; + } + + $hetznerService = new \App\Services\HetznerService($this->server->cloudProviderToken->token); + $serverData = $hetznerService->getServer($this->server->hetzner_server_id); + + $this->hetznerServerStatus = $serverData['status'] ?? null; + + // Save status to database without triggering model events + if ($this->server->hetzner_server_status !== $this->hetznerServerStatus) { + $this->server->hetzner_server_status = $this->hetznerServerStatus; + $this->server->update(['hetzner_server_status' => $this->hetznerServerStatus]); + } + if ($manual) { + $this->dispatch('success', 'Server status refreshed: '.ucfirst($this->hetznerServerStatus ?? 'unknown')); + } + + // If Hetzner server is off but Coolify thinks it's still reachable, update Coolify's state + if ($this->hetznerServerStatus === 'off' && $this->server->settings->is_reachable) { + ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(); + if ($uptime) { + $this->dispatch('success', 'Server is reachable.'); + $this->server->settings->is_reachable = $this->isReachable = true; + $this->server->settings->is_usable = $this->isUsable = true; + $this->server->settings->save(); + ServerReachabilityChanged::dispatch($this->server); + } else { + $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.

Check this documentation for further help.

Error: '.$error); + + return; + } + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function handleServerValidated($event = null) + { + // Check if event is for this server + if ($event && isset($event['serverUuid']) && $event['serverUuid'] !== $this->server->uuid) { + return; + } + + // Refresh server data + $this->server->refresh(); + $this->syncData(); + + // Update validation state + $this->isValidating = $this->server->is_validating ?? false; + $this->dispatch('refreshServerShow'); + $this->dispatch('refreshServer'); + } + + public function startHetznerServer() + { + try { + if (! $this->server->hetzner_server_id || ! $this->server->cloudProviderToken) { + $this->dispatch('error', 'This server is not associated with a Hetzner Cloud server or token.'); + + return; + } + + $hetznerService = new \App\Services\HetznerService($this->server->cloudProviderToken->token); + $hetznerService->powerOnServer($this->server->hetzner_server_id); + + $this->hetznerServerStatus = 'starting'; + $this->server->update(['hetzner_server_status' => 'starting']); + $this->hetznerServerManuallyStarted = true; // Set flag to trigger auto-validation when running + $this->dispatch('success', 'Hetzner server is starting...'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function submit() { try { diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php index bf0b7b6a51..bbd7f3dd92 100644 --- a/app/Livewire/Server/ValidateAndInstall.php +++ b/app/Livewire/Server/ValidateAndInstall.php @@ -4,6 +4,7 @@ use App\Actions\Proxy\CheckProxy; use App\Actions\Proxy\StartProxy; +use App\Events\ServerValidated; use App\Models\Server; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; @@ -63,6 +64,19 @@ public function startValidatingAfterAsking() $this->init(); } + public function retry() + { + $this->authorize('update', $this->server); + $this->uptime = null; + $this->supported_os_type = null; + $this->docker_installed = null; + $this->docker_compose_installed = null; + $this->docker_version = null; + $this->error = null; + $this->number_of_tries = 0; + $this->init(); + } + public function validateConnection() { $this->authorize('update', $this->server); @@ -136,8 +150,12 @@ public function validateDockerVersion() } else { $this->docker_version = $this->server->validateDockerEngineVersion(); if ($this->docker_version) { + // Mark validation as complete + $this->server->update(['is_validating' => false]); + $this->dispatch('refreshServerShow'); $this->dispatch('refreshBoardingIndex'); + ServerValidated::dispatch($this->server->team_id, $this->server->uuid); $this->dispatch('success', 'Server validated, proxy is starting in a moment.'); $proxyShouldRun = CheckProxy::run($this->server, true); if (! $proxyShouldRun) { diff --git a/app/Livewire/SettingsBackup.php b/app/Livewire/SettingsBackup.php index 57cb79fcaa..84f5c6081a 100644 --- a/app/Livewire/SettingsBackup.php +++ b/app/Livewire/SettingsBackup.php @@ -120,6 +120,8 @@ public function addCoolifyDatabase() public function submit() { + $this->validate(); + $this->database->update([ 'name' => $this->name, 'description' => $this->description, diff --git a/app/Livewire/Storage/Form.php b/app/Livewire/Storage/Form.php index 41541f6b92..9438b7727e 100644 --- a/app/Livewire/Storage/Form.php +++ b/app/Livewire/Storage/Form.php @@ -5,6 +5,7 @@ use App\Models\S3Storage; use App\Support\ValidationPatterns; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; +use Illuminate\Support\Facades\DB; use Livewire\Component; class Form extends Component @@ -91,9 +92,24 @@ public function submit() try { $this->authorize('update', $this->storage); - $this->validate(); - $this->testConnection(); + DB::transaction(function () { + $this->validate(); + $this->storage->save(); + + // Test connection with new values - if this fails, transaction will rollback + $this->storage->testConnection(shouldSave: false); + + // If we get here, the connection test succeeded + $this->storage->is_usable = true; + $this->storage->unusable_email_sent = false; + $this->storage->save(); + }); + + $this->dispatch('success', 'Storage settings updated and connection verified.'); } catch (\Throwable $e) { + // Refresh the model to revert UI to database values after rollback + $this->storage->refresh(); + return handleError($e, $this); } } diff --git a/app/Livewire/Team/Create.php b/app/Livewire/Team/Create.php index d3d27556cd..cd15be67d7 100644 --- a/app/Livewire/Team/Create.php +++ b/app/Livewire/Team/Create.php @@ -35,7 +35,7 @@ public function submit() 'personal_team' => false, ]); auth()->user()->teams()->attach($team, ['role' => 'admin']); - refreshSession(); + refreshSession($team); return redirect()->route('team.index'); } catch (\Throwable $e) { diff --git a/app/Livewire/Terminal/Index.php b/app/Livewire/Terminal/Index.php index 03dbc1d914..6bb4c5e908 100644 --- a/app/Livewire/Terminal/Index.php +++ b/app/Livewire/Terminal/Index.php @@ -61,6 +61,10 @@ private function getAllActiveContainers() public function updatedSelectedUuid() { + if ($this->selected_uuid === 'default') { + // When cleared to default, do nothing (no error message) + return; + } $this->connectToContainer(); } diff --git a/app/Models/Application.php b/app/Models/Application.php index 4f1796790b..33c1b7fc4d 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -182,6 +182,21 @@ protected static function booted() ]); $application->compose_parsing_version = self::$parserVersion; $application->save(); + + // Add default NIXPACKS_NODE_VERSION environment variable for Nixpacks applications + if ($application->build_pack === 'nixpacks') { + EnvironmentVariable::create([ + 'key' => 'NIXPACKS_NODE_VERSION', + 'value' => '22', + 'is_multiline' => false, + 'is_literal' => false, + 'is_buildtime' => true, + 'is_runtime' => false, + 'is_preview' => false, + 'resourceable_type' => Application::class, + 'resourceable_id' => $application->id, + ]); + } }); static::forceDeleting(function ($application) { $application->update(['fqdn' => null]); @@ -739,9 +754,9 @@ public function environment_variables() return $this->morphMany(EnvironmentVariable::class, 'resourceable') ->where('is_preview', false) ->orderByRaw(" - CASE - WHEN LOWER(key) LIKE 'service_%' THEN 1 - WHEN is_required = true AND (value IS NULL OR value = '') THEN 2 + CASE + WHEN is_required = true THEN 1 + WHEN LOWER(key) LIKE 'service_%' THEN 2 ELSE 3 END, LOWER(key) ASC @@ -767,9 +782,9 @@ public function environment_variables_preview() return $this->morphMany(EnvironmentVariable::class, 'resourceable') ->where('is_preview', true) ->orderByRaw(" - CASE - WHEN LOWER(key) LIKE 'service_%' THEN 1 - WHEN is_required = true AND (value IS NULL OR value = '') THEN 2 + CASE + WHEN is_required = true THEN 1 + WHEN LOWER(key) LIKE 'service_%' THEN 2 ELSE 3 END, LOWER(key) ASC @@ -988,29 +1003,30 @@ public function dirOnServer() public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false) { $baseDir = $this->generateBaseDir($deployment_uuid); + $escapedBaseDir = escapeshellarg($baseDir); $isShallowCloneEnabled = $this->settings?->is_git_shallow_clone_enabled ?? false; if ($this->git_commit_sha !== 'HEAD') { // If shallow clone is enabled and we need a specific commit, // we need to fetch that specific commit with depth=1 if ($isShallowCloneEnabled) { - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git fetch --depth=1 origin {$this->git_commit_sha} && git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1"; + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git fetch --depth=1 origin {$this->git_commit_sha} && git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1"; } else { - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1"; + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1"; } } if ($this->settings->is_git_submodules_enabled) { // Check if .gitmodules file exists before running submodule commands - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && if [ -f .gitmodules ]; then"; + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && if [ -f .gitmodules ]; then"; if ($public) { - $git_clone_command = "{$git_clone_command} sed -i \"s#git@\(.*\):#https://\\1/#g\" {$baseDir}/.gitmodules || true &&"; + $git_clone_command = "{$git_clone_command} sed -i \"s#git@\(.*\):#https://\\1/#g\" {$escapedBaseDir}/.gitmodules || true &&"; } // Add shallow submodules flag if shallow clone is enabled $submoduleFlags = $isShallowCloneEnabled ? '--depth=1' : ''; $git_clone_command = "{$git_clone_command} git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git submodule update --init --recursive {$submoduleFlags}; fi"; } if ($this->settings->is_git_lfs_enabled) { - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git lfs pull"; + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git lfs pull"; } return $git_clone_command; @@ -1115,7 +1131,8 @@ public function generateGitLsRemoteCommands(string $deployment_uuid, bool $exec_ if ($this->deploymentType() === 'other') { $fullRepoUrl = $customRepository; - $base_command = "{$base_command} {$customRepository}"; + $escapedCustomRepository = escapeshellarg($customRepository); + $base_command = "{$base_command} {$escapedCustomRepository}"; if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, $base_command)); @@ -1257,7 +1274,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); } elseif ($git_type === 'github' || $git_type === 'gitea') { $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; if ($exec_in_docker) { @@ -1265,14 +1282,14 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); } elseif ($git_type === 'bitbucket') { if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit); + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit); } } @@ -1290,7 +1307,8 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req } if ($this->deploymentType() === 'other') { $fullRepoUrl = $customRepository; - $git_clone_command = "{$git_clone_command} {$customRepository} {$baseDir}"; + $escapedCustomRepository = escapeshellarg($customRepository); + $git_clone_command = "{$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}"; $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true); if ($pull_request_id !== 0) { @@ -1301,7 +1319,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); } elseif ($git_type === 'github' || $git_type === 'gitea') { $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; if ($exec_in_docker) { @@ -1309,14 +1327,14 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); } elseif ($git_type === 'bitbucket') { if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit); + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit); } } diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index 8df6877ab6..4e8eee10fc 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -41,11 +41,9 @@ class ApplicationDeploymentQueue extends Model { protected $guarded = []; - public function application(): Attribute + public function application() { - return Attribute::make( - get: fn () => Application::find($this->application_id), - ); + return $this->belongsTo(Application::class); } public function server(): Attribute diff --git a/app/Models/CloudInitScript.php b/app/Models/CloudInitScript.php new file mode 100644 index 0000000000..2c78cc5827 --- /dev/null +++ b/app/Models/CloudInitScript.php @@ -0,0 +1,33 @@ + 'encrypted', + ]; + } + + public function team() + { + return $this->belongsTo(Team::class); + } + + public static function ownedByCurrentTeam(array $select = ['*']) + { + $selectArray = collect($select)->concat(['id']); + + return self::whereTeamId(currentTeam()->id)->select($selectArray->all()); + } +} diff --git a/app/Models/CloudProviderToken.php b/app/Models/CloudProviderToken.php new file mode 100644 index 0000000000..607040269c --- /dev/null +++ b/app/Models/CloudProviderToken.php @@ -0,0 +1,41 @@ + 'encrypted', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function servers() + { + return $this->hasMany(Server::class); + } + + public function hasServers(): bool + { + return $this->servers()->exists(); + } + + public static function ownedByCurrentTeam(array $select = ['*']) + { + $selectArray = collect($select)->concat(['id']); + + return self::whereTeamId(currentTeam()->id)->select($selectArray->all()); + } + + public function scopeForProvider($query, string $provider) + { + return $query->where('provider', $provider); + } +} diff --git a/app/Models/Environment.php b/app/Models/Environment.php index bfeee01c93..c2ad9d2cb9 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -35,6 +35,11 @@ protected static function booted() }); } + public static function ownedByCurrentTeam() + { + return Environment::whereRelation('project.team', 'id', currentTeam()->id)->orderBy('name'); + } + public function isEmpty() { return $this->applications()->count() == 0 && diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php index b3e71d75de..376ea9c5ea 100644 --- a/app/Models/LocalFileVolume.php +++ b/app/Models/LocalFileVolume.php @@ -5,6 +5,7 @@ use App\Events\FileStorageChanged; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Symfony\Component\Yaml\Yaml; class LocalFileVolume extends BaseModel { @@ -192,4 +193,61 @@ public function scopeWherePlainMountPath($query, $path) { return $query->get()->where('plain_mount_path', $path); } + + // Check if this volume is read-only by parsing the docker-compose content + public function isReadOnlyVolume(): bool + { + try { + // Only check for services + $service = $this->service; + if (! $service || ! method_exists($service, 'service')) { + return false; + } + + $actualService = $service->service; + if (! $actualService || ! $actualService->docker_compose_raw) { + return false; + } + + // Parse the docker-compose content + $compose = Yaml::parse($actualService->docker_compose_raw); + if (! isset($compose['services'])) { + return false; + } + + // Find the service that this volume belongs to + $serviceName = $service->name; + if (! isset($compose['services'][$serviceName]['volumes'])) { + return false; + } + + $volumes = $compose['services'][$serviceName]['volumes']; + + // Check each volume to find a match + foreach ($volumes as $volume) { + // Volume can be string like "host:container:ro" or "host:container" + if (is_string($volume)) { + $parts = explode(':', $volume); + + // Check if this volume matches our fs_path and mount_path + if (count($parts) >= 2) { + $hostPath = $parts[0]; + $containerPath = $parts[1]; + $options = $parts[2] ?? null; + + // Match based on fs_path and mount_path + if ($hostPath === $this->fs_path && $containerPath === $this->mount_path) { + return $options === 'ro'; + } + } + } + } + + return false; + } catch (\Throwable $e) { + ray($e->getMessage(), 'Error checking read-only volume'); + + return false; + } + } } diff --git a/app/Models/LocalPersistentVolume.php b/app/Models/LocalPersistentVolume.php index 00dc15fea2..e7862478b9 100644 --- a/app/Models/LocalPersistentVolume.php +++ b/app/Models/LocalPersistentVolume.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; +use Symfony\Component\Yaml\Yaml; class LocalPersistentVolume extends Model { @@ -48,4 +49,69 @@ protected function hostPath(): Attribute } ); } + + // Check if this volume is read-only by parsing the docker-compose content + public function isReadOnlyVolume(): bool + { + try { + // Get the resource (can be application, service, or database) + $resource = $this->resource; + if (! $resource) { + return false; + } + + // Only check for services + if (! method_exists($resource, 'service')) { + return false; + } + + $actualService = $resource->service; + if (! $actualService || ! $actualService->docker_compose_raw) { + return false; + } + + // Parse the docker-compose content + $compose = Yaml::parse($actualService->docker_compose_raw); + if (! isset($compose['services'])) { + return false; + } + + // Find the service that this volume belongs to + $serviceName = $resource->name; + if (! isset($compose['services'][$serviceName]['volumes'])) { + return false; + } + + $volumes = $compose['services'][$serviceName]['volumes']; + + // Check each volume to find a match + foreach ($volumes as $volume) { + // Volume can be string like "host:container:ro" or "host:container" + if (is_string($volume)) { + $parts = explode(':', $volume); + + // Check if this volume matches our mount_path + if (count($parts) >= 2) { + $containerPath = $parts[1]; + $options = $parts[2] ?? null; + + // Match based on mount_path + // Remove leading slash from mount_path if present for comparison + $mountPath = str($this->mount_path)->ltrim('/')->toString(); + $containerPathClean = str($containerPath)->ltrim('/')->toString(); + + if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) { + return $options === 'ro'; + } + } + } + } + + return false; + } catch (\Throwable $e) { + ray($e->getMessage(), 'Error checking read-only persistent volume'); + + return false; + } + } } diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php index c210f3c5b4..08f3f1ebda 100644 --- a/app/Models/PrivateKey.php +++ b/app/Models/PrivateKey.php @@ -289,6 +289,17 @@ public static function generateFingerprint($privateKey) } } + public static function generateMd5Fingerprint($privateKey) + { + try { + $key = PublicKeyLoader::load($privateKey); + + return $key->getPublicKey()->getFingerprint('md5'); + } catch (\Throwable $e) { + return null; + } + } + public static function fingerprintExists($fingerprint, $excludeId = null) { $query = self::query() diff --git a/app/Models/ScheduledDatabaseBackupExecution.php b/app/Models/ScheduledDatabaseBackupExecution.php index b06dd5b45d..c0298ecc82 100644 --- a/app/Models/ScheduledDatabaseBackupExecution.php +++ b/app/Models/ScheduledDatabaseBackupExecution.php @@ -8,6 +8,15 @@ class ScheduledDatabaseBackupExecution extends BaseModel { protected $guarded = []; + protected function casts(): array + { + return [ + 's3_uploaded' => 'boolean', + 'local_storage_deleted' => 'boolean', + 's3_storage_deleted' => 'boolean', + ]; + } + public function scheduledDatabaseBackup(): BelongsTo { return $this->belongsTo(ScheduledDatabaseBackup::class); diff --git a/app/Models/Server.php b/app/Models/Server.php index 829a4b5aa0..e39526949a 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -136,6 +136,7 @@ protected static function booted() $destination->delete(); }); $server->settings()->delete(); + $server->sslCertificates()->delete(); }); } @@ -161,7 +162,11 @@ protected static function booted() 'user', 'description', 'private_key_id', + 'cloud_provider_token_id', 'team_id', + 'hetzner_server_id', + 'hetzner_server_status', + 'is_validating', ]; protected $guarded = []; @@ -889,6 +894,16 @@ public function privateKey() return $this->belongsTo(PrivateKey::class); } + public function cloudProviderToken() + { + return $this->belongsTo(CloudProviderToken::class); + } + + public function sslCertificates() + { + return $this->hasMany(SslCertificate::class); + } + public function muxFilename() { return 'mux_'.$this->uuid; @@ -1327,7 +1342,7 @@ public function generateCaCertificate() isCaCertificate: true, validityDays: 10 * 365 ); - $caCertificate = SslCertificate::where('server_id', $this->id)->where('is_ca_certificate', true)->first(); + $caCertificate = $this->sslCertificates()->where('is_ca_certificate', true)->first(); ray('CA certificate generated', $caCertificate); if ($caCertificate) { $certificateContent = $caCertificate->ssl_certificate; diff --git a/app/Models/Service.php b/app/Models/Service.php index c9bf4cbcdb..c4b8623e08 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -547,6 +547,21 @@ public function extraFields() } $fields->put('Grafana', $data->toArray()); break; + case $image->contains('elasticsearch'): + $data = collect([]); + $elastic_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ELASTICSEARCH')->first(); + if ($elastic_password) { + $data = $data->merge([ + 'Password (default user: elastic)' => [ + 'key' => data_get($elastic_password, 'key'), + 'value' => data_get($elastic_password, 'value'), + 'rules' => 'required', + 'isPassword' => true, + ], + ]); + } + $fields->put('Elasticsearch', $data->toArray()); + break; case $image->contains('directus'): $data = collect([]); $admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first(); @@ -1231,9 +1246,9 @@ public function environment_variables() { return $this->morphMany(EnvironmentVariable::class, 'resourceable') ->orderByRaw(" - CASE - WHEN LOWER(key) LIKE 'service_%' THEN 1 - WHEN is_required = true AND (value IS NULL OR value = '') THEN 2 + CASE + WHEN is_required = true THEN 1 + WHEN LOWER(key) LIKE 'service_%' THEN 2 ELSE 3 END, LOWER(key) ASC diff --git a/app/Models/Team.php b/app/Models/Team.php index 51fdeffa4f..6c30389ee7 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -54,6 +54,7 @@ protected static function booted() $team->slackNotificationSettings()->create(); $team->telegramNotificationSettings()->create(); $team->pushoverNotificationSettings()->create(); + $team->webhookNotificationSettings()->create(); }); static::saving(function ($team) { @@ -258,6 +259,11 @@ public function privateKeys() return $this->hasMany(PrivateKey::class); } + public function cloudProviderTokens() + { + return $this->hasMany(CloudProviderToken::class); + } + public function sources() { $sources = collect([]); @@ -307,4 +313,9 @@ public function pushoverNotificationSettings() { return $this->hasOne(PushoverNotificationSettings::class); } + + public function webhookNotificationSettings() + { + return $this->hasOne(WebhookNotificationSettings::class); + } } diff --git a/app/Models/WebhookNotificationSettings.php b/app/Models/WebhookNotificationSettings.php new file mode 100644 index 0000000000..4ca89e0d31 --- /dev/null +++ b/app/Models/WebhookNotificationSettings.php @@ -0,0 +1,64 @@ + 'boolean', + 'webhook_url' => 'encrypted', + + 'deployment_success_webhook_notifications' => 'boolean', + 'deployment_failure_webhook_notifications' => 'boolean', + 'status_change_webhook_notifications' => 'boolean', + 'backup_success_webhook_notifications' => 'boolean', + 'backup_failure_webhook_notifications' => 'boolean', + 'scheduled_task_success_webhook_notifications' => 'boolean', + 'scheduled_task_failure_webhook_notifications' => 'boolean', + 'docker_cleanup_webhook_notifications' => 'boolean', + 'server_disk_usage_webhook_notifications' => 'boolean', + 'server_reachable_webhook_notifications' => 'boolean', + 'server_unreachable_webhook_notifications' => 'boolean', + 'server_patch_webhook_notifications' => 'boolean', + ]; + } + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + return $this->webhook_enabled; + } +} diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index dec361e782..8fff7f03bb 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -185,4 +185,30 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => false, + 'message' => 'Deployment failed', + 'event' => 'deployment_failed', + 'application_name' => $this->application_name, + 'application_uuid' => $this->application->uuid, + 'deployment_uuid' => $this->deployment_uuid, + 'deployment_url' => $this->deployment_url, + 'project' => data_get($this->application, 'environment.project.name'), + 'environment' => $this->environment_name, + ]; + + if ($this->preview) { + $data['pull_request_id'] = $this->preview->pull_request_id; + $data['preview_fqdn'] = $this->preview->fqdn; + } + + if ($this->fqdn) { + $data['fqdn'] = $this->fqdn; + } + + return $data; + } } diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index 9b59d9162a..415df5831d 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -205,4 +205,30 @@ public function toSlack(): SlackMessage color: SlackMessage::successColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => true, + 'message' => 'New version successfully deployed', + 'event' => 'deployment_success', + 'application_name' => $this->application_name, + 'application_uuid' => $this->application->uuid, + 'deployment_uuid' => $this->deployment_uuid, + 'deployment_url' => $this->deployment_url, + 'project' => data_get($this->application, 'environment.project.name'), + 'environment' => $this->environment_name, + ]; + + if ($this->preview) { + $data['pull_request_id'] = $this->preview->pull_request_id; + $data['preview_fqdn'] = $this->preview->fqdn; + } + + if ($this->fqdn) { + $data['fqdn'] = $this->fqdn; + } + + return $data; + } } diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index fab5487ef4..ef61b7e6a5 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -113,4 +113,19 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + return [ + 'success' => false, + 'message' => 'Application stopped', + 'event' => 'status_changed', + 'application_name' => $this->resource_name, + 'application_uuid' => $this->resource->uuid, + 'url' => $this->resource_url, + 'project' => data_get($this->resource, 'environment.project.name'), + 'environment' => $this->environment_name, + 'fqdn' => $this->fqdn, + ]; + } } diff --git a/app/Notifications/Channels/WebhookChannel.php b/app/Notifications/Channels/WebhookChannel.php new file mode 100644 index 0000000000..8c3e74b17c --- /dev/null +++ b/app/Notifications/Channels/WebhookChannel.php @@ -0,0 +1,37 @@ +webhookNotificationSettings; + + if (! $webhookSettings || ! $webhookSettings->isEnabled() || ! $webhookSettings->webhook_url) { + if (isDev()) { + ray('Webhook notification skipped - not enabled or no URL configured'); + } + + return; + } + + $payload = $notification->toWebhook(); + + if (isDev()) { + ray('Dispatching webhook notification', [ + 'notification' => get_class($notification), + 'url' => $webhookSettings->webhook_url, + 'payload' => $payload, + ]); + } + + SendWebhookJob::dispatch($payload, $webhookSettings->webhook_url); + } +} diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php index f6ae694819..2d7eb58b50 100644 --- a/app/Notifications/Container/ContainerRestarted.php +++ b/app/Notifications/Container/ContainerRestarted.php @@ -102,4 +102,22 @@ public function toSlack(): SlackMessage color: SlackMessage::warningColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => true, + 'message' => 'Resource restarted automatically', + 'event' => 'container_restarted', + 'container_name' => $this->name, + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + ]; + + if ($this->url) { + $data['url'] = $this->url; + } + + return $data; + } } diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index fc9410a851..f518cd2fdd 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -102,4 +102,22 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => false, + 'message' => 'Resource stopped unexpectedly', + 'event' => 'container_stopped', + 'container_name' => $this->name, + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + ]; + + if ($this->url) { + $data['url'] = $this->url; + } + + return $data; + } } diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index a19fb04317..c2b21b1d51 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -88,4 +88,21 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/project/'.data_get($this->database, 'environment.project.uuid').'/environment/'.data_get($this->database, 'environment.uuid').'/database/'.$this->database->uuid; + + return [ + 'success' => false, + 'message' => 'Database backup failed', + 'event' => 'backup_failed', + 'database_name' => $this->name, + 'database_uuid' => $this->database->uuid, + 'database_type' => $this->database_name, + 'frequency' => $this->frequency, + 'error_output' => $this->output, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index 78bcfafe35..3d2d8ece3b 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -85,4 +85,20 @@ public function toSlack(): SlackMessage color: SlackMessage::successColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/project/'.data_get($this->database, 'environment.project.uuid').'/environment/'.data_get($this->database, 'environment.uuid').'/database/'.$this->database->uuid; + + return [ + 'success' => true, + 'message' => 'Database backup successful', + 'event' => 'backup_success', + 'database_name' => $this->name, + 'database_uuid' => $this->database->uuid, + 'database_type' => $this->database_name, + 'frequency' => $this->frequency, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Database/BackupSuccessWithS3Warning.php b/app/Notifications/Database/BackupSuccessWithS3Warning.php new file mode 100644 index 0000000000..ee24ef17de --- /dev/null +++ b/app/Notifications/Database/BackupSuccessWithS3Warning.php @@ -0,0 +1,139 @@ +onQueue('high'); + + $this->name = $database->name; + $this->frequency = $backup->frequency; + + if ($backup->s3) { + $this->s3_storage_url = base_url().'/storages/'.$backup->s3->uuid; + } + } + + public function via(object $notifiable): array + { + return $notifiable->getEnabledChannels('backup_failure'); + } + + public function toMail(): MailMessage + { + $mail = new MailMessage; + $mail->subject("Coolify: Backup succeeded locally but S3 upload failed for {$this->database->name}"); + $mail->view('emails.backup-success-with-s3-warning', [ + 'name' => $this->name, + 'database_name' => $this->database_name, + 'frequency' => $this->frequency, + 's3_error' => $this->s3_error, + 's3_storage_url' => $this->s3_storage_url, + ]); + + return $mail; + } + + public function toDiscord(): DiscordMessage + { + $message = new DiscordMessage( + title: ':warning: Database backup succeeded locally, S3 upload failed', + description: "Database backup for {$this->name} (db:{$this->database_name}) was created successfully on local storage, but failed to upload to S3.", + color: DiscordMessage::warningColor(), + ); + + $message->addField('Frequency', $this->frequency, true); + $message->addField('S3 Error', $this->s3_error); + + if ($this->s3_storage_url) { + $message->addField('S3 Storage', '[Check Configuration]('.$this->s3_storage_url.')'); + } + + return $message; + } + + public function toTelegram(): array + { + $message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} succeeded locally but failed to upload to S3.\n\nS3 Error:\n{$this->s3_error}"; + + if ($this->s3_storage_url) { + $message .= "\n\nCheck S3 Configuration: {$this->s3_storage_url}"; + } + + return [ + 'message' => $message, + ]; + } + + public function toPushover(): PushoverMessage + { + $message = "Database backup for {$this->name} (db:{$this->database_name}) was created successfully on local storage, but failed to upload to S3.

Frequency: {$this->frequency}.
S3 Error: {$this->s3_error}"; + + if ($this->s3_storage_url) { + $message .= "

s3_storage_url}\">Check S3 Configuration"; + } + + return new PushoverMessage( + title: 'Database backup succeeded locally, S3 upload failed', + level: 'warning', + message: $message, + ); + } + + public function toSlack(): SlackMessage + { + $title = 'Database backup succeeded locally, S3 upload failed'; + $description = "Database backup for {$this->name} (db:{$this->database_name}) was created successfully on local storage, but failed to upload to S3."; + + $description .= "\n\n*Frequency:* {$this->frequency}"; + $description .= "\n\n*S3 Error:* {$this->s3_error}"; + + if ($this->s3_storage_url) { + $description .= "\n\n*S3 Storage:* <{$this->s3_storage_url}|Check Configuration>"; + } + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::warningColor() + ); + } + + public function toWebhook(): array + { + $url = base_url().'/project/'.data_get($this->database, 'environment.project.uuid').'/environment/'.data_get($this->database, 'environment.uuid').'/database/'.$this->database->uuid; + + $data = [ + 'success' => true, + 'message' => 'Database backup succeeded locally, S3 upload failed', + 'event' => 'backup_success_with_s3_warning', + 'database_name' => $this->name, + 'database_uuid' => $this->database->uuid, + 'database_type' => $this->database_name, + 'frequency' => $this->frequency, + 's3_error' => $this->s3_error, + 'url' => $url, + ]; + + if ($this->s3_storage_url) { + $data['s3_storage_url'] = $this->s3_storage_url; + } + + return $data; + } +} diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php index eb4fc7e79f..bd060112ae 100644 --- a/app/Notifications/ScheduledTask/TaskFailed.php +++ b/app/Notifications/ScheduledTask/TaskFailed.php @@ -114,4 +114,28 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => false, + 'message' => 'Scheduled task failed', + 'event' => 'task_failed', + 'task_name' => $this->task->name, + 'task_uuid' => $this->task->uuid, + 'output' => $this->output, + ]; + + if ($this->task->application) { + $data['application_uuid'] = $this->task->application->uuid; + } elseif ($this->task->service) { + $data['service_uuid'] = $this->task->service->uuid; + } + + if ($this->url) { + $data['url'] = $this->url; + } + + return $data; + } } diff --git a/app/Notifications/ScheduledTask/TaskSuccess.php b/app/Notifications/ScheduledTask/TaskSuccess.php index c45784db24..58c959bd8d 100644 --- a/app/Notifications/ScheduledTask/TaskSuccess.php +++ b/app/Notifications/ScheduledTask/TaskSuccess.php @@ -105,4 +105,28 @@ public function toSlack(): SlackMessage color: SlackMessage::successColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => true, + 'message' => 'Scheduled task succeeded', + 'event' => 'task_success', + 'task_name' => $this->task->name, + 'task_uuid' => $this->task->uuid, + 'output' => $this->output, + ]; + + if ($this->task->application) { + $data['application_uuid'] = $this->task->application->uuid; + } elseif ($this->task->service) { + $data['service_uuid'] = $this->task->service->uuid; + } + + if ($this->url) { + $data['url'] = $this->url; + } + + return $data; + } } diff --git a/app/Notifications/Server/DockerCleanupFailed.php b/app/Notifications/Server/DockerCleanupFailed.php index 0291eed19d..9cbdeb4888 100644 --- a/app/Notifications/Server/DockerCleanupFailed.php +++ b/app/Notifications/Server/DockerCleanupFailed.php @@ -66,4 +66,19 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/server/'.$this->server->uuid; + + return [ + 'success' => false, + 'message' => 'Docker cleanup job failed', + 'event' => 'docker_cleanup_failed', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'error_message' => $this->message, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Server/DockerCleanupSuccess.php b/app/Notifications/Server/DockerCleanupSuccess.php index 1a652d1890..d28f25c6c8 100644 --- a/app/Notifications/Server/DockerCleanupSuccess.php +++ b/app/Notifications/Server/DockerCleanupSuccess.php @@ -66,4 +66,19 @@ public function toSlack(): SlackMessage color: SlackMessage::successColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/server/'.$this->server->uuid; + + return [ + 'success' => true, + 'message' => 'Docker cleanup job succeeded', + 'event' => 'docker_cleanup_success', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'cleanup_message' => $this->message, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Server/HetznerDeletionFailed.php b/app/Notifications/Server/HetznerDeletionFailed.php new file mode 100644 index 0000000000..de894331b0 --- /dev/null +++ b/app/Notifications/Server/HetznerDeletionFailed.php @@ -0,0 +1,71 @@ +onQueue('high'); + } + + public function via(object $notifiable): array + { + ray('hello'); + ray($notifiable); + + return $notifiable->getEnabledChannels('hetzner_deletion_failed'); + } + + public function toMail(): MailMessage + { + $mail = new MailMessage; + $mail->subject("Coolify: [ACTION REQUIRED] Failed to delete Hetzner server #{$this->hetznerServerId}"); + $mail->view('emails.hetzner-deletion-failed', [ + 'hetznerServerId' => $this->hetznerServerId, + 'errorMessage' => $this->errorMessage, + ]); + + return $mail; + } + + public function toDiscord(): DiscordMessage + { + return new DiscordMessage( + title: ':cross_mark: Coolify: [ACTION REQUIRED] Failed to delete Hetzner server', + description: "Failed to delete Hetzner server #{$this->hetznerServerId} from Hetzner Cloud.\n\n**Error:** {$this->errorMessage}\n\nThe server has been removed from Coolify, but may still exist in your Hetzner Cloud account. Please check your Hetzner Cloud console and manually delete the server if needed.", + color: DiscordMessage::errorColor(), + ); + } + + public function toTelegram(): array + { + return [ + 'message' => "Coolify: [ACTION REQUIRED] Failed to delete Hetzner server #{$this->hetznerServerId} from Hetzner Cloud.\n\nError: {$this->errorMessage}\n\nThe server has been removed from Coolify, but may still exist in your Hetzner Cloud account. Please check your Hetzner Cloud console and manually delete the server if needed.", + ]; + } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Hetzner Server Deletion Failed', + level: 'error', + message: "[ACTION REQUIRED] Failed to delete Hetzner server #{$this->hetznerServerId}.\n\nError: {$this->errorMessage}\n\nThe server has been removed from Coolify, but may still exist in your Hetzner Cloud account. Please check and manually delete if needed.", + ); + } + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Coolify: [ACTION REQUIRED] Hetzner Server Deletion Failed', + description: "Failed to delete Hetzner server #{$this->hetznerServerId} from Hetzner Cloud.\n\nError: {$this->errorMessage}\n\nThe server has been removed from Coolify, but may still exist in your Hetzner Cloud account. Please check your Hetzner Cloud console and manually delete the server if needed.", + color: SlackMessage::errorColor() + ); + } +} diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index 983e6d81e7..149d1bbc86 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -88,4 +88,18 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + return [ + 'success' => false, + 'message' => 'High disk usage detected', + 'event' => 'high_disk_usage', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'disk_usage' => $this->disk_usage, + 'threshold' => $this->server_disk_usage_notification_threshold, + 'url' => base_url().'/server/'.$this->server->uuid, + ]; + } } diff --git a/app/Notifications/Server/Reachable.php b/app/Notifications/Server/Reachable.php index e03aef6b79..e64b0af2ac 100644 --- a/app/Notifications/Server/Reachable.php +++ b/app/Notifications/Server/Reachable.php @@ -74,4 +74,18 @@ public function toSlack(): SlackMessage color: SlackMessage::successColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/server/'.$this->server->uuid; + + return [ + 'success' => true, + 'message' => 'Server revived', + 'event' => 'server_reachable', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Server/ServerPatchCheck.php b/app/Notifications/Server/ServerPatchCheck.php index 1686a6f37c..4d3053569b 100644 --- a/app/Notifications/Server/ServerPatchCheck.php +++ b/app/Notifications/Server/ServerPatchCheck.php @@ -345,4 +345,47 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + // Handle error case + if (isset($this->patchData['error'])) { + return [ + 'success' => false, + 'message' => 'Failed to check patches', + 'event' => 'server_patch_check_error', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'os_id' => $this->patchData['osId'] ?? 'unknown', + 'package_manager' => $this->patchData['package_manager'] ?? 'unknown', + 'error' => $this->patchData['error'], + 'url' => $this->serverUrl, + ]; + } + + $totalUpdates = $this->patchData['total_updates'] ?? 0; + $updates = $this->patchData['updates'] ?? []; + + // Check for critical packages + $criticalPackages = collect($updates)->filter(function ($update) { + return str_contains(strtolower($update['package']), 'docker') || + str_contains(strtolower($update['package']), 'kernel') || + str_contains(strtolower($update['package']), 'openssh') || + str_contains(strtolower($update['package']), 'ssl'); + }); + + return [ + 'success' => false, + 'message' => 'Server patches available', + 'event' => 'server_patch_check', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'total_updates' => $totalUpdates, + 'os_id' => $this->patchData['osId'] ?? 'unknown', + 'package_manager' => $this->patchData['package_manager'] ?? 'unknown', + 'updates' => $updates, + 'critical_packages_count' => $criticalPackages->count(), + 'url' => $this->serverUrl, + ]; + } } diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index fe90cc6105..99742f3b73 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -82,4 +82,18 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/server/'.$this->server->uuid; + + return [ + 'success' => false, + 'message' => 'Server unreachable', + 'event' => 'server_unreachable', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 0b1d8d6b10..60bc8a0ee5 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -7,6 +7,7 @@ use App\Notifications\Channels\PushoverChannel; use App\Notifications\Channels\SlackChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\WebhookChannel; use App\Notifications\Dto\DiscordMessage; use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; @@ -36,6 +37,7 @@ public function via(object $notifiable): array 'telegram' => [TelegramChannel::class], 'slack' => [SlackChannel::class], 'pushover' => [PushoverChannel::class], + 'webhook' => [WebhookChannel::class], default => [], }; } else { @@ -110,4 +112,14 @@ public function toSlack(): SlackMessage description: 'This is a test Slack notification from Coolify.' ); } + + public function toWebhook(): array + { + return [ + 'success' => true, + 'message' => 'This is a test webhook notification from Coolify.', + 'event' => 'test', + 'url' => base_url(), + ]; + } } diff --git a/app/Policies/CloudInitScriptPolicy.php b/app/Policies/CloudInitScriptPolicy.php new file mode 100644 index 0000000000..0be4f26629 --- /dev/null +++ b/app/Policies/CloudInitScriptPolicy.php @@ -0,0 +1,65 @@ +isAdmin(); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, CloudInitScript $cloudInitScript): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, CloudInitScript $cloudInitScript): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, CloudInitScript $cloudInitScript): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, CloudInitScript $cloudInitScript): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, CloudInitScript $cloudInitScript): bool + { + return $user->isAdmin(); + } +} diff --git a/app/Policies/CloudProviderTokenPolicy.php b/app/Policies/CloudProviderTokenPolicy.php new file mode 100644 index 0000000000..b7b108ba88 --- /dev/null +++ b/app/Policies/CloudProviderTokenPolicy.php @@ -0,0 +1,65 @@ +isAdmin(); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, CloudProviderToken $cloudProviderToken): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, CloudProviderToken $cloudProviderToken): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, CloudProviderToken $cloudProviderToken): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, CloudProviderToken $cloudProviderToken): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, CloudProviderToken $cloudProviderToken): bool + { + return $user->isAdmin(); + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index c017a580ed..5d33479366 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -45,6 +45,7 @@ class AuthServiceProvider extends ServiceProvider \App\Models\TelegramNotificationSettings::class => \App\Policies\NotificationPolicy::class, \App\Models\SlackNotificationSettings::class => \App\Policies\NotificationPolicy::class, \App\Models\PushoverNotificationSettings::class => \App\Policies\NotificationPolicy::class, + \App\Models\WebhookNotificationSettings::class => \App\Policies\NotificationPolicy::class, // API Token policy \Laravel\Sanctum\PersonalAccessToken::class => \App\Policies\ApiTokenPolicy::class, diff --git a/app/Rules/DockerImageFormat.php b/app/Rules/DockerImageFormat.php new file mode 100644 index 0000000000..a6a78a76c2 --- /dev/null +++ b/app/Rules/DockerImageFormat.php @@ -0,0 +1,41 @@ +getMessage()); + } + + return; + } + + // If it doesn't start with #! or #cloud-config, try to parse as YAML + // (some users might omit the #cloud-config header) + try { + Yaml::parse($script); + } catch (ParseException $e) { + $fail('The :attribute must be either a valid bash script (starting with #!) or valid cloud-config YAML. YAML parse error: '.$e->getMessage()); + } + } +} diff --git a/app/Rules/ValidHostname.php b/app/Rules/ValidHostname.php new file mode 100644 index 0000000000..b6b2b8d329 --- /dev/null +++ b/app/Rules/ValidHostname.php @@ -0,0 +1,114 @@ + 253) { + $fail('The :attribute must not exceed 253 characters.'); + + return; + } + + // Check for dangerous shell metacharacters + $dangerousChars = [ + ';', '|', '&', '$', '`', '(', ')', '{', '}', + '<', '>', '\n', '\r', '\0', '"', "'", '\\', + '!', '*', '?', '[', ']', '~', '^', ':', '#', + '@', '%', '=', '+', ',', ' ', + ]; + + foreach ($dangerousChars as $char) { + if (str_contains($hostname, $char)) { + try { + $logData = [ + 'hostname' => $hostname, + 'character' => $char, + ]; + + if (function_exists('request') && app()->has('request')) { + $logData['ip'] = request()->ip(); + } + + if (function_exists('auth') && app()->has('auth')) { + $logData['user_id'] = auth()->id(); + } + + Log::warning('Hostname validation failed - dangerous character', $logData); + } catch (\Throwable $e) { + // Ignore errors when facades are not available (e.g., in unit tests) + } + + $fail('The :attribute contains invalid characters. Only lowercase letters (a-z), numbers (0-9), hyphens (-), and dots (.) are allowed.'); + + return; + } + } + + // Additional validation: hostname should not start or end with a dot + if (str_starts_with($hostname, '.') || str_ends_with($hostname, '.')) { + $fail('The :attribute cannot start or end with a dot.'); + + return; + } + + // Check for consecutive dots + if (str_contains($hostname, '..')) { + $fail('The :attribute cannot contain consecutive dots.'); + + return; + } + + // Split into labels (segments between dots) + $labels = explode('.', $hostname); + + foreach ($labels as $label) { + // Check label length (RFC 1123: max 63 characters per label) + if (strlen($label) < 1 || strlen($label) > 63) { + $fail('The :attribute contains an invalid label. Each segment must be 1-63 characters.'); + + return; + } + + // Check if label starts or ends with hyphen + if (str_starts_with($label, '-') || str_ends_with($label, '-')) { + $fail('The :attribute contains an invalid label. Labels cannot start or end with a hyphen.'); + + return; + } + + // Check if label contains only valid characters (lowercase letters, digits, hyphens) + if (! preg_match('/^[a-z0-9-]+$/', $label)) { + $fail('The :attribute contains invalid characters. Only lowercase letters (a-z), numbers (0-9), hyphens (-), and dots (.) are allowed.'); + + return; + } + + // RFC 1123 allows labels to be all numeric (unlike RFC 952) + // So we don't need to check for all-numeric labels + } + } +} diff --git a/app/Services/DockerImageParser.php b/app/Services/DockerImageParser.php index 1fd6625b32..b483c979a3 100644 --- a/app/Services/DockerImageParser.php +++ b/app/Services/DockerImageParser.php @@ -10,20 +10,33 @@ class DockerImageParser private string $tag = 'latest'; + private bool $isImageHash = false; + public function parse(string $imageString): self { - // First split by : to handle the tag, but be careful with registry ports - $lastColon = strrpos($imageString, ':'); - $hasSlash = str_contains($imageString, '/'); - - // If the last colon appears after the last slash, it's a tag - // Otherwise it might be a port in the registry URL - if ($lastColon !== false && (! $hasSlash || $lastColon > strrpos($imageString, '/'))) { - $mainPart = substr($imageString, 0, $lastColon); - $this->tag = substr($imageString, $lastColon + 1); + // Check for @sha256: format first (e.g., nginx@sha256:abc123...) + if (preg_match('/^(.+)@sha256:([a-f0-9]{64})$/i', $imageString, $matches)) { + $mainPart = $matches[1]; + $this->tag = $matches[2]; + $this->isImageHash = true; } else { - $mainPart = $imageString; - $this->tag = 'latest'; + // Split by : to handle the tag, but be careful with registry ports + $lastColon = strrpos($imageString, ':'); + $hasSlash = str_contains($imageString, '/'); + + // If the last colon appears after the last slash, it's a tag + // Otherwise it might be a port in the registry URL + if ($lastColon !== false && (! $hasSlash || $lastColon > strrpos($imageString, '/'))) { + $mainPart = substr($imageString, 0, $lastColon); + $this->tag = substr($imageString, $lastColon + 1); + + // Check if the tag is a SHA256 hash + $this->isImageHash = $this->isSha256Hash($this->tag); + } else { + $mainPart = $imageString; + $this->tag = 'latest'; + $this->isImageHash = false; + } } // Split the main part by / to handle registry and image name @@ -41,6 +54,37 @@ public function parse(string $imageString): self return $this; } + /** + * Check if the given string is a SHA256 hash + */ + private function isSha256Hash(string $hash): bool + { + // SHA256 hashes are 64 characters long and contain only hexadecimal characters + return preg_match('/^[a-f0-9]{64}$/i', $hash) === 1; + } + + /** + * Check if the current tag is an image hash + */ + public function isImageHash(): bool + { + return $this->isImageHash; + } + + /** + * Get the full image name with hash if present + */ + public function getFullImageNameWithHash(): string + { + $imageName = $this->getFullImageNameWithoutTag(); + + if ($this->isImageHash) { + return $imageName.'@sha256:'.$this->tag; + } + + return $imageName.':'.$this->tag; + } + public function getFullImageNameWithoutTag(): string { if ($this->registryUrl) { @@ -73,6 +117,10 @@ public function toString(): string } $parts[] = $this->imageName; + if ($this->isImageHash) { + return implode('/', $parts).'@sha256:'.$this->tag; + } + return implode('/', $parts).':'.$this->tag; } } diff --git a/app/Services/HetznerService.php b/app/Services/HetznerService.php new file mode 100644 index 0000000000..aa6de38972 --- /dev/null +++ b/app/Services/HetznerService.php @@ -0,0 +1,143 @@ +token = $token; + } + + private function request(string $method, string $endpoint, array $data = []) + { + $response = Http::withHeaders([ + 'Authorization' => 'Bearer '.$this->token, + ]) + ->timeout(30) + ->retry(3, function (int $attempt, \Exception $exception) { + // Handle rate limiting (429 Too Many Requests) + if ($exception instanceof \Illuminate\Http\Client\RequestException) { + $response = $exception->response; + + if ($response && $response->status() === 429) { + // Get rate limit reset timestamp from headers + $resetTime = $response->header('RateLimit-Reset'); + + if ($resetTime) { + // Calculate wait time until rate limit resets + $waitSeconds = max(0, $resetTime - time()); + + // Cap wait time at 60 seconds for safety + return min($waitSeconds, 60) * 1000; + } + } + } + + // Exponential backoff for other retriable errors: 100ms, 200ms, 400ms + return $attempt * 100; + }) + ->{$method}($this->baseUrl.$endpoint, $data); + + if (! $response->successful()) { + throw new \Exception('Hetzner API error: '.$response->json('error.message', 'Unknown error')); + } + + return $response->json(); + } + + private function requestPaginated(string $method, string $endpoint, string $resourceKey, array $data = []): array + { + $allResults = []; + $page = 1; + + do { + $data['page'] = $page; + $data['per_page'] = 50; + + $response = $this->request($method, $endpoint, $data); + + if (isset($response[$resourceKey])) { + $allResults = array_merge($allResults, $response[$resourceKey]); + } + + $nextPage = $response['meta']['pagination']['next_page'] ?? null; + $page = $nextPage; + } while ($nextPage !== null); + + return $allResults; + } + + public function getLocations(): array + { + return $this->requestPaginated('get', '/locations', 'locations'); + } + + public function getImages(): array + { + return $this->requestPaginated('get', '/images', 'images', [ + 'type' => 'system', + ]); + } + + public function getServerTypes(): array + { + return $this->requestPaginated('get', '/server_types', 'server_types'); + } + + public function getSshKeys(): array + { + return $this->requestPaginated('get', '/ssh_keys', 'ssh_keys'); + } + + public function uploadSshKey(string $name, string $publicKey): array + { + $response = $this->request('post', '/ssh_keys', [ + 'name' => $name, + 'public_key' => $publicKey, + ]); + + return $response['ssh_key'] ?? []; + } + + public function createServer(array $params): array + { + ray('Hetzner createServer request', [ + 'endpoint' => '/servers', + 'params' => $params, + ]); + + $response = $this->request('post', '/servers', $params); + + ray('Hetzner createServer response', [ + 'response' => $response, + ]); + + return $response['server'] ?? []; + } + + public function getServer(int $serverId): array + { + $response = $this->request('get', "/servers/{$serverId}"); + + return $response['server'] ?? []; + } + + public function powerOnServer(int $serverId): array + { + $response = $this->request('post', "/servers/{$serverId}/actions/poweron"); + + return $response['action'] ?? []; + } + + public function deleteServer(int $serverId): void + { + $this->request('delete', "/servers/{$serverId}"); + } +} diff --git a/app/Traits/HasNotificationSettings.php b/app/Traits/HasNotificationSettings.php index 236e4d97cf..fded435fdb 100644 --- a/app/Traits/HasNotificationSettings.php +++ b/app/Traits/HasNotificationSettings.php @@ -7,6 +7,7 @@ use App\Notifications\Channels\PushoverChannel; use App\Notifications\Channels\SlackChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\WebhookChannel; use Illuminate\Database\Eloquent\Model; trait HasNotificationSettings @@ -17,6 +18,7 @@ trait HasNotificationSettings 'general', 'test', 'ssl_certificate_renewal', + 'hetzner_deletion_failure', ]; /** @@ -30,6 +32,7 @@ public function getNotificationSettings(string $channel): ?Model 'telegram' => $this->telegramNotificationSettings, 'slack' => $this->slackNotificationSettings, 'pushover' => $this->pushoverNotificationSettings, + 'webhook' => $this->webhookNotificationSettings, default => null, }; } @@ -77,6 +80,7 @@ public function getEnabledChannels(string $event): array 'telegram' => TelegramChannel::class, 'slack' => SlackChannel::class, 'pushover' => PushoverChannel::class, + 'webhook' => WebhookChannel::class, ]; if ($event === 'general') { diff --git a/app/View/Components/Forms/Checkbox.php b/app/View/Components/Forms/Checkbox.php index ece7f0e357..88f858ec93 100644 --- a/app/View/Components/Forms/Checkbox.php +++ b/app/View/Components/Forms/Checkbox.php @@ -22,7 +22,7 @@ public function __construct( public string|bool|null $checked = false, public string|bool $instantSave = false, public bool $disabled = false, - public string $defaultClass = 'dark:border-neutral-700 text-coolgray-400 focus:ring-warning dark:bg-coolgray-100 rounded-sm cursor-pointer dark:disabled:bg-base dark:disabled:cursor-not-allowed', + public string $defaultClass = 'dark:border-neutral-700 text-coolgray-400 dark:bg-coolgray-100 rounded-sm cursor-pointer dark:disabled:bg-base dark:disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-base', public ?string $canGate = null, public mixed $canResource = null, public bool $autoDisable = true, diff --git a/app/View/Components/Forms/Datalist.php b/app/View/Components/Forms/Datalist.php index 25643753d7..33e264e374 100644 --- a/app/View/Components/Forms/Datalist.php +++ b/app/View/Components/Forms/Datalist.php @@ -4,7 +4,7 @@ use Closure; use Illuminate\Contracts\View\View; -use Illuminate\Support\Str; +use Illuminate\Support\Facades\Gate; use Illuminate\View\Component; use Visus\Cuid2\Cuid2; @@ -19,9 +19,27 @@ public function __construct( public ?string $label = null, public ?string $helper = null, public bool $required = false, - public string $defaultClass = 'input' + public bool $disabled = false, + public bool $readonly = false, + public bool $multiple = false, + public string|bool $instantSave = false, + public ?string $value = null, + public ?string $placeholder = null, + public bool $autofocus = false, + public string $defaultClass = 'input', + public ?string $canGate = null, + public mixed $canResource = null, + public bool $autoDisable = true, ) { - // + // Handle authorization-based disabling + if ($this->canGate && $this->canResource && $this->autoDisable) { + $hasPermission = Gate::allows($this->canGate, $this->canResource); + + if (! $hasPermission) { + $this->disabled = true; + $this->instantSave = false; // Disable instant save for unauthorized users + } + } } /** @@ -36,8 +54,6 @@ public function render(): View|Closure|string $this->name = $this->id; } - $this->label = Str::title($this->label); - return view('components.forms.datalist'); } } diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index b568e090c3..36243e1199 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -21,13 +21,23 @@ 'bitnami/mariadb', 'bitnami/mongodb', 'bitnami/redis', + 'bitnamilegacy/mariadb', + 'bitnamilegacy/mongodb', + 'bitnamilegacy/redis', + 'bitnamisecure/mariadb', + 'bitnamisecure/mongodb', + 'bitnamisecure/redis', 'mysql', 'bitnami/mysql', + 'bitnamilegacy/mysql', + 'bitnamisecure/mysql', 'mysql/mysql-server', 'mariadb', 'postgis/postgis', 'postgres', 'bitnami/postgresql', + 'bitnamilegacy/postgresql', + 'bitnamisecure/postgresql', 'supabase/postgres', 'elestio/postgres', 'mongo', diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php index 5dbd46b5eb..aa7be3236d 100644 --- a/bootstrap/helpers/databases.php +++ b/bootstrap/helpers/databases.php @@ -237,12 +237,11 @@ function removeOldBackups($backup): void { try { if ($backup->executions) { - // If local backup is disabled, mark all executions as having local storage deleted - if ($backup->disable_local_backup && $backup->save_s3) { - $backup->executions() - ->where('local_storage_deleted', false) - ->update(['local_storage_deleted' => true]); - } else { + // Delete old local backups (only if local backup is NOT disabled) + // Note: When disable_local_backup is enabled, each execution already marks its own + // local_storage_deleted status at the time of backup, so we don't need to retroactively + // update old executions + if (! $backup->disable_local_backup) { $localBackupsToDelete = deleteOldBackupsLocally($backup); if ($localBackupsToDelete->isNotEmpty()) { $backup->executions() @@ -261,18 +260,18 @@ function removeOldBackups($backup): void } } - // Delete executions where both local and S3 storage are marked as deleted - // or where only S3 is enabled and S3 storage is deleted - if ($backup->disable_local_backup && $backup->save_s3) { - $backup->executions() - ->where('s3_storage_deleted', true) - ->delete(); - } else { - $backup->executions() - ->where('local_storage_deleted', true) - ->where('s3_storage_deleted', true) - ->delete(); - } + // Delete execution records where all backup copies are gone + // Case 1: Both local and S3 backups are deleted + $backup->executions() + ->where('local_storage_deleted', true) + ->where('s3_storage_deleted', true) + ->delete(); + + // Case 2: Local backup is deleted and S3 was never used (s3_uploaded is null) + $backup->executions() + ->where('local_storage_deleted', true) + ->whereNull('s3_uploaded') + ->delete(); } catch (\Exception $e) { throw $e; diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index af26c97bd6..b63c3fc3be 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -1122,9 +1122,10 @@ function escapeDollarSign($value) /** * Generate Docker build arguments from environment variables collection + * Returns only keys (no values) since values are sourced from environment via export * * @param \Illuminate\Support\Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline' - * @return \Illuminate\Support\Collection Collection of formatted --build-arg strings + * @return \Illuminate\Support\Collection Collection of formatted --build-arg strings (keys only) */ function generateDockerBuildArgs($variables): \Illuminate\Support\Collection { @@ -1132,21 +1133,9 @@ function generateDockerBuildArgs($variables): \Illuminate\Support\Collection return $variables->map(function ($var) { $key = is_array($var) ? data_get($var, 'key') : $var->key; - $value = is_array($var) ? data_get($var, 'value') : $var->value; - $isMultiline = is_array($var) ? data_get($var, 'is_multiline', false) : ($var->is_multiline ?? false); - if ($isMultiline) { - // For multiline variables, strip surrounding quotes and escape for bash - $raw_value = trim($value, "'"); - $escaped_value = str_replace(['\\', '"', '$', '`'], ['\\\\', '\\"', '\\$', '\\`'], $raw_value); - - return "--build-arg {$key}=\"{$escaped_value}\""; - } - - // For regular variables, use escapeshellarg for security - $value = escapeshellarg($value); - - return "--build-arg {$key}={$value}"; + // Only return the key - Docker will get the value from the environment + return "--build-arg {$key}"; }); } diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index a588ed8820..09d4c75494 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -1181,23 +1181,26 @@ function serviceParser(Service $resource): Collection $image = data_get_str($service, 'image'); $isDatabase = isDatabaseImage($image, $service); if ($isDatabase) { - $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); + $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first(); if ($applicationFound) { $savedService = $applicationFound; } else { $savedService = ServiceDatabase::firstOrCreate([ 'name' => $serviceName, - 'image' => $image, 'service_id' => $resource->id, ]); } } else { $savedService = ServiceApplication::firstOrCreate([ 'name' => $serviceName, - 'image' => $image, 'service_id' => $resource->id, ]); } + // Update image if it changed + if ($savedService->image !== $image) { + $savedService->image = $image; + $savedService->save(); + } } foreach ($services as $serviceName => $service) { $predefinedPort = null; @@ -1514,20 +1517,18 @@ function serviceParser(Service $resource): Collection } if ($isDatabase) { - $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); + $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first(); if ($applicationFound) { $savedService = $applicationFound; } else { $savedService = ServiceDatabase::firstOrCreate([ 'name' => $serviceName, - 'image' => $image, 'service_id' => $resource->id, ]); } } else { $savedService = ServiceApplication::firstOrCreate([ 'name' => $serviceName, - 'image' => $image, 'service_id' => $resource->id, ]); } diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index 5bc1d005eb..924bad3075 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -108,7 +108,63 @@ function connectProxyToNetworks(Server $server) return $commands->flatten(); } -function generate_default_proxy_configuration(Server $server) +function extractCustomProxyCommands(Server $server, string $existing_config): array +{ + $custom_commands = []; + $proxy_type = $server->proxyType(); + + if ($proxy_type !== ProxyTypes::TRAEFIK->value || empty($existing_config)) { + return $custom_commands; + } + + try { + $yaml = Yaml::parse($existing_config); + $existing_commands = data_get($yaml, 'services.traefik.command', []); + + if (empty($existing_commands)) { + return $custom_commands; + } + + // Define default commands that Coolify generates + $default_command_prefixes = [ + '--ping=', + '--api.', + '--entrypoints.http.address=', + '--entrypoints.https.address=', + '--entrypoints.http.http.encodequerysemicolons=', + '--entryPoints.http.http2.maxConcurrentStreams=', + '--entrypoints.https.http.encodequerysemicolons=', + '--entryPoints.https.http2.maxConcurrentStreams=', + '--entrypoints.https.http3', + '--providers.file.', + '--certificatesresolvers.', + '--providers.docker', + '--providers.swarm', + '--log.level=', + '--accesslog.', + ]; + + // Extract commands that don't match default prefixes (these are custom) + foreach ($existing_commands as $command) { + $is_default = false; + foreach ($default_command_prefixes as $prefix) { + if (str_starts_with($command, $prefix)) { + $is_default = true; + break; + } + } + if (! $is_default) { + $custom_commands[] = $command; + } + } + } catch (\Exception $e) { + // If we can't parse the config, return empty array + // Silently fail to avoid breaking the proxy regeneration + } + + return $custom_commands; +} +function generateDefaultProxyConfiguration(Server $server, array $custom_commands = []) { $proxy_path = $server->proxyPath(); $proxy_type = $server->proxyType(); @@ -228,6 +284,13 @@ function generate_default_proxy_configuration(Server $server) $config['services']['traefik']['command'][] = '--providers.docker=true'; $config['services']['traefik']['command'][] = '--providers.docker.exposedbydefault=false'; } + + // Append custom commands (e.g., trustedIPs for Cloudflare) + if (! empty($custom_commands)) { + foreach ($custom_commands as $custom_command) { + $config['services']['traefik']['command'][] = $custom_command; + } + } } elseif ($proxy_type === 'CADDY') { $config = [ 'networks' => $array_of_networks->toArray(), diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 656c607bfd..308f522fb6 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1317,6 +1317,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'name' => $serviceName, 'service_id' => $resource->id, ])->first(); + if (is_null($savedService)) { + $savedService = ServiceDatabase::create([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $resource->id, + ]); + } } } else { if ($isNew) { @@ -1330,21 +1337,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'name' => $serviceName, 'service_id' => $resource->id, ])->first(); - } - } - if (is_null($savedService)) { - if ($isDatabase) { - $savedService = ServiceDatabase::create([ - 'name' => $serviceName, - 'image' => $image, - 'service_id' => $resource->id, - ]); - } else { - $savedService = ServiceApplication::create([ - 'name' => $serviceName, - 'image' => $image, - 'service_id' => $resource->id, - ]); + if (is_null($savedService)) { + $savedService = ServiceApplication::create([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $resource->id, + ]); + } } } diff --git a/conductor.json b/conductor.json new file mode 100644 index 0000000000..851d13ed09 --- /dev/null +++ b/conductor.json @@ -0,0 +1,7 @@ +{ + "scripts": { + "setup": "./scripts/conductor-setup.sh", + "run": "docker rm -f coolify coolify-realtime coolify-minio coolify-testing-host coolify-redis coolify-db coolify-mail coolify-vite; spin up; spin down" + }, + "runScriptMode": "nonconcurrent" +} diff --git a/config/constants.php b/config/constants.php index ddda70d191..01eaa7fa10 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.434', + 'version' => '4.0.0-beta.435', 'helper_version' => '1.0.11', 'realtime_version' => '1.0.10', 'self_hosted' => env('SELF_HOSTED', true), diff --git a/database/migrations/2025_06_26_131350_optimize_activity_log_indexes.php b/database/migrations/2025_06_26_131350_optimize_activity_log_indexes.php index 6ffe97c07f..4d4be12321 100644 --- a/database/migrations/2025_06_26_131350_optimize_activity_log_indexes.php +++ b/database/migrations/2025_06_26_131350_optimize_activity_log_indexes.php @@ -6,6 +6,12 @@ return new class extends Migration { + /** + * Disable transactions for this migration because CREATE INDEX CONCURRENTLY + * cannot run inside a transaction block in PostgreSQL. + */ + public $withinTransaction = false; + /** * Run the migrations. */ @@ -13,10 +19,10 @@ public function up(): void { try { // Add specific index for type_uuid queries with ordering - DB::statement('CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_activity_type_uuid_created_at ON activity_log ((properties->>\'type_uuid\'), created_at DESC)'); + DB::unprepared('CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_activity_type_uuid_created_at ON activity_log ((properties->>\'type_uuid\'), created_at DESC)'); // Add specific index for status queries on properties - DB::statement('CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_activity_properties_status ON activity_log ((properties->>\'status\'))'); + DB::unprepared('CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_activity_properties_status ON activity_log ((properties->>\'status\'))'); } catch (\Exception $e) { Log::error('Error adding optimized indexes to activity_log: '.$e->getMessage()); @@ -29,8 +35,8 @@ public function up(): void public function down(): void { try { - DB::statement('DROP INDEX CONCURRENTLY IF EXISTS idx_activity_type_uuid_created_at'); - DB::statement('DROP INDEX CONCURRENTLY IF EXISTS idx_activity_properties_status'); + DB::unprepared('DROP INDEX CONCURRENTLY IF EXISTS idx_activity_type_uuid_created_at'); + DB::unprepared('DROP INDEX CONCURRENTLY IF EXISTS idx_activity_properties_status'); } catch (\Exception $e) { Log::error('Error dropping optimized indexes from activity_log: '.$e->getMessage()); } diff --git a/database/migrations/2025_10_03_154100_update_clickhouse_image.php b/database/migrations/2025_10_03_154100_update_clickhouse_image.php new file mode 100644 index 0000000000..e573540373 --- /dev/null +++ b/database/migrations/2025_10_03_154100_update_clickhouse_image.php @@ -0,0 +1,32 @@ +string('image')->default('bitnamilegacy/clickhouse')->change(); + }); + // Optionally, update any existing rows with the old default to the new one + DB::table('standalone_clickhouses') + ->where('image', 'bitnami/clickhouse') + ->update(['image' => 'bitnamilegacy/clickhouse']); + } + + public function down() + { + Schema::table('standalone_clickhouses', function (Blueprint $table) { + $table->string('image')->default('bitnami/clickhouse')->change(); + }); + // Optionally, revert any changed values + DB::table('standalone_clickhouses') + ->where('image', 'bitnamilegacy/clickhouse') + ->update(['image' => 'bitnami/clickhouse']); + } +}; diff --git a/database/migrations/2025_10_07_120723_add_s3_uploaded_to_scheduled_database_backup_executions_table.php b/database/migrations/2025_10_07_120723_add_s3_uploaded_to_scheduled_database_backup_executions_table.php new file mode 100644 index 0000000000..d80f2621bb --- /dev/null +++ b/database/migrations/2025_10_07_120723_add_s3_uploaded_to_scheduled_database_backup_executions_table.php @@ -0,0 +1,28 @@ +boolean('s3_uploaded')->nullable()->after('filename'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('scheduled_database_backup_executions', function (Blueprint $table) { + $table->dropColumn('s3_uploaded'); + }); + } +}; diff --git a/database/migrations/2025_10_08_181125_create_cloud_provider_tokens_table.php b/database/migrations/2025_10_08_181125_create_cloud_provider_tokens_table.php new file mode 100644 index 0000000000..2c92b0e19b --- /dev/null +++ b/database/migrations/2025_10_08_181125_create_cloud_provider_tokens_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('team_id')->constrained()->onDelete('cascade'); + $table->string('provider'); + $table->text('token'); + $table->string('name')->nullable(); + $table->timestamps(); + + $table->index(['team_id', 'provider']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cloud_provider_tokens'); + } +}; diff --git a/database/migrations/2025_10_08_185203_add_hetzner_server_id_to_servers_table.php b/database/migrations/2025_10_08_185203_add_hetzner_server_id_to_servers_table.php new file mode 100644 index 0000000000..b1c9ec48b9 --- /dev/null +++ b/database/migrations/2025_10_08_185203_add_hetzner_server_id_to_servers_table.php @@ -0,0 +1,28 @@ +bigInteger('hetzner_server_id')->nullable()->after('id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('hetzner_server_id'); + }); + } +}; diff --git a/database/migrations/2025_10_09_095905_add_cloud_provider_token_id_to_servers_table.php b/database/migrations/2025_10_09_095905_add_cloud_provider_token_id_to_servers_table.php new file mode 100644 index 0000000000..a25a4ce830 --- /dev/null +++ b/database/migrations/2025_10_09_095905_add_cloud_provider_token_id_to_servers_table.php @@ -0,0 +1,29 @@ +foreignId('cloud_provider_token_id')->nullable()->after('private_key_id')->constrained()->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign(['cloud_provider_token_id']); + $table->dropColumn('cloud_provider_token_id'); + }); + } +}; diff --git a/database/migrations/2025_10_09_113602_add_hetzner_server_status_to_servers_table.php b/database/migrations/2025_10_09_113602_add_hetzner_server_status_to_servers_table.php new file mode 100644 index 0000000000..d94c9c76f9 --- /dev/null +++ b/database/migrations/2025_10_09_113602_add_hetzner_server_status_to_servers_table.php @@ -0,0 +1,28 @@ +string('hetzner_server_status')->nullable()->after('hetzner_server_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('hetzner_server_status'); + }); + } +}; diff --git a/database/migrations/2025_10_09_125036_add_is_validating_to_servers_table.php b/database/migrations/2025_10_09_125036_add_is_validating_to_servers_table.php new file mode 100644 index 0000000000..ddb655d2c7 --- /dev/null +++ b/database/migrations/2025_10_09_125036_add_is_validating_to_servers_table.php @@ -0,0 +1,28 @@ +boolean('is_validating')->default(false)->after('hetzner_server_status'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('is_validating'); + }); + } +}; diff --git a/database/migrations/2025_10_10_120000_create_cloud_init_scripts_table.php b/database/migrations/2025_10_10_120000_create_cloud_init_scripts_table.php new file mode 100644 index 0000000000..fe216a57da --- /dev/null +++ b/database/migrations/2025_10_10_120000_create_cloud_init_scripts_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('team_id')->constrained()->onDelete('cascade'); + $table->string('name'); + $table->text('script'); // Encrypted in the model + $table->timestamps(); + + $table->index('team_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cloud_init_scripts'); + } +}; diff --git a/database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php b/database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php new file mode 100644 index 0000000000..a3edacbf96 --- /dev/null +++ b/database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php @@ -0,0 +1,46 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('webhook_enabled')->default(false); + $table->text('webhook_url')->nullable(); + + $table->boolean('deployment_success_webhook_notifications')->default(false); + $table->boolean('deployment_failure_webhook_notifications')->default(true); + $table->boolean('status_change_webhook_notifications')->default(false); + $table->boolean('backup_success_webhook_notifications')->default(false); + $table->boolean('backup_failure_webhook_notifications')->default(true); + $table->boolean('scheduled_task_success_webhook_notifications')->default(false); + $table->boolean('scheduled_task_failure_webhook_notifications')->default(true); + $table->boolean('docker_cleanup_success_webhook_notifications')->default(false); + $table->boolean('docker_cleanup_failure_webhook_notifications')->default(true); + $table->boolean('server_disk_usage_webhook_notifications')->default(true); + $table->boolean('server_reachable_webhook_notifications')->default(false); + $table->boolean('server_unreachable_webhook_notifications')->default(true); + $table->boolean('server_patch_webhook_notifications')->default(false); + + $table->unique(['team_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('webhook_notification_settings'); + } +}; diff --git a/database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php b/database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php new file mode 100644 index 0000000000..de27075575 --- /dev/null +++ b/database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php @@ -0,0 +1,47 @@ +get(); + + foreach ($teams as $team) { + DB::table('webhook_notification_settings')->updateOrInsert( + ['team_id' => $team->id], + [ + 'webhook_enabled' => false, + 'webhook_url' => null, + 'deployment_success_webhook_notifications' => false, + 'deployment_failure_webhook_notifications' => true, + 'status_change_webhook_notifications' => false, + 'backup_success_webhook_notifications' => false, + 'backup_failure_webhook_notifications' => true, + 'scheduled_task_success_webhook_notifications' => false, + 'scheduled_task_failure_webhook_notifications' => true, + 'docker_cleanup_success_webhook_notifications' => false, + 'docker_cleanup_failure_webhook_notifications' => true, + 'server_disk_usage_webhook_notifications' => true, + 'server_reachable_webhook_notifications' => false, + 'server_unreachable_webhook_notifications' => true, + 'server_patch_webhook_notifications' => false, + ] + ); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // We don't need to do anything in down() since the webhook_notification_settings + // table will be dropped by the create migration's down() method + } +}; diff --git a/database/seeders/CaSslCertSeeder.php b/database/seeders/CaSslCertSeeder.php index 09f6cc9840..1b71a5e432 100644 --- a/database/seeders/CaSslCertSeeder.php +++ b/database/seeders/CaSslCertSeeder.php @@ -4,7 +4,6 @@ use App\Helpers\SslHelper; use App\Models\Server; -use App\Models\SslCertificate; use Illuminate\Database\Seeder; class CaSslCertSeeder extends Seeder @@ -13,7 +12,7 @@ public function run() { Server::chunk(200, function ($servers) { foreach ($servers as $server) { - $existingCaCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + $existingCaCert = $server->sslCertificates()->where('is_ca_certificate', true)->first(); if (! $existingCaCert) { $caCert = SslHelper::generateSslCertificate( diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index e8402b7afa..fee17dad65 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,5 +1,7 @@ services: coolify: + image: coolify:dev + pull_policy: never build: context: . dockerfile: ./docker/development/Dockerfile @@ -41,6 +43,8 @@ services: volumes: - dev_redis_data:/data soketi: + image: coolify-realtime:dev + pull_policy: never build: context: . dockerfile: ./docker/coolify-realtime/Dockerfile @@ -61,6 +65,7 @@ services: vite: image: node:24-alpine pull_policy: always + container_name: coolify-vite working_dir: /var/www/html environment: VITE_HOST: "${VITE_HOST:-localhost}" @@ -73,6 +78,8 @@ services: networks: - coolify testing-host: + image: coolify-testing-host:dev + pull_policy: never build: context: . dockerfile: ./docker/testing-host/Dockerfile diff --git a/openapi.json b/openapi.json index 901741dd08..3667cbe876 100644 --- a/openapi.json +++ b/openapi.json @@ -3698,6 +3698,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -3753,7 +3756,7 @@ "application\/json": { "schema": { "properties": { - "": { + "message": { "type": "string", "example": "Backup configuration and all executions deleted." } @@ -3769,7 +3772,7 @@ "application\/json": { "schema": { "properties": { - "": { + "message": { "type": "string", "example": "Backup configuration not found." } @@ -3892,6 +3895,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -4033,6 +4039,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -4158,6 +4167,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -4279,6 +4291,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -4404,6 +4419,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -4529,6 +4547,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -4666,6 +4687,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -4803,6 +4827,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -4928,6 +4955,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -4993,7 +5023,7 @@ "application\/json": { "schema": { "properties": { - "": { + "message": { "type": "string", "example": "Backup execution deleted." } @@ -5009,7 +5039,7 @@ "application\/json": { "schema": { "properties": { - "": { + "message": { "type": "string", "example": "Backup execution not found." } @@ -5063,7 +5093,7 @@ "application\/json": { "schema": { "properties": { - "": { + "executions": { "type": "array", "items": { "properties": { @@ -5690,7 +5720,7 @@ "application\/json": { "schema": { "properties": { - "": { + "repositories": { "type": "array", "items": { "type": "object" @@ -5763,7 +5793,7 @@ "application\/json": { "schema": { "properties": { - "": { + "branches": { "type": "array", "items": { "type": "object" @@ -5968,7 +5998,7 @@ "description": "GitHub app not found" }, "422": { - "description": "Validation error" + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -5987,7 +6017,7 @@ "200": { "description": "Returns the version of the application", "content": { - "application\/json": { + "text\/html": { "schema": { "type": "string" }, @@ -6122,7 +6152,7 @@ "200": { "description": "Healthcheck endpoint.", "content": { - "application\/json": { + "text\/html": { "schema": { "type": "string" }, @@ -6228,6 +6258,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -6327,6 +6360,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -6408,6 +6444,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -6464,6 +6503,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -6514,6 +6556,9 @@ }, "404": { "description": "Project not found." + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -6586,6 +6631,9 @@ }, "409": { "description": "Environment with this name already exists." + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -6648,6 +6696,9 @@ }, "404": { "description": "Project or environment not found." + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -6779,6 +6830,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -6840,6 +6894,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -7094,6 +7151,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -7193,6 +7253,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -7292,6 +7355,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -7473,6 +7539,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -7702,6 +7771,9 @@ }, "400": { "$ref": "#\/components\/responses\/400" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -7953,6 +8025,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -8093,6 +8168,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -8186,6 +8264,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -8288,6 +8369,9 @@ }, "404": { "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" } }, "security": [ @@ -9750,6 +9834,40 @@ } } } + }, + "422": { + "description": "Validation error.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Validation error." + }, + "errors": { + "type": "object", + "example": { + "name": [ + "The name field is required." + ], + "api_url": [ + "The api url field is required.", + "The api url format is invalid." + ] + }, + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "type": "object" + } + } + } } }, "securitySchemes": { diff --git a/openapi.yaml b/openapi.yaml index 3e39c5d362..b7df655677 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2377,6 +2377,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -2418,7 +2420,7 @@ paths: application/json: schema: properties: - '': { type: string, example: 'Backup configuration and all executions deleted.' } + message: { type: string, example: 'Backup configuration and all executions deleted.' } type: object '404': description: 'Backup configuration not found.' @@ -2426,7 +2428,7 @@ paths: application/json: schema: properties: - '': { type: string, example: 'Backup configuration not found.' } + message: { type: string, example: 'Backup configuration not found.' } type: object security: - @@ -2510,6 +2512,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -2612,6 +2616,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -2702,6 +2708,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -2789,6 +2797,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -2879,6 +2889,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -2969,6 +2981,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -3068,6 +3082,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -3167,6 +3183,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -3257,6 +3275,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -3306,7 +3326,7 @@ paths: application/json: schema: properties: - '': { type: string, example: 'Backup execution deleted.' } + message: { type: string, example: 'Backup execution deleted.' } type: object '404': description: 'Backup execution not found.' @@ -3314,7 +3334,7 @@ paths: application/json: schema: properties: - '': { type: string, example: 'Backup execution not found.' } + message: { type: string, example: 'Backup execution not found.' } type: object security: - @@ -3349,7 +3369,7 @@ paths: application/json: schema: properties: - '': { type: array, items: { properties: { uuid: { type: string }, filename: { type: string }, size: { type: integer }, created_at: { type: string }, message: { type: string }, status: { type: string } }, type: object } } + executions: { type: array, items: { properties: { uuid: { type: string }, filename: { type: string }, size: { type: integer }, created_at: { type: string }, message: { type: string }, status: { type: string } }, type: object } } type: object '404': description: 'Backup configuration not found.' @@ -3727,7 +3747,7 @@ paths: application/json: schema: properties: - '': { type: array, items: { type: object } } + repositories: { type: array, items: { type: object } } type: object '400': $ref: '#/components/responses/400' @@ -3774,7 +3794,7 @@ paths: application/json: schema: properties: - '': { type: array, items: { type: object } } + branches: { type: array, items: { type: object } } type: object '400': $ref: '#/components/responses/400' @@ -3900,7 +3920,7 @@ paths: '404': description: 'GitHub app not found' '422': - description: 'Validation error' + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -3913,7 +3933,7 @@ paths: '200': description: 'Returns the version of the application' content: - application/json: + text/html: schema: type: string example: v4.0.0 @@ -3991,7 +4011,7 @@ paths: '200': description: 'Healthcheck endpoint.' content: - application/json: + text/html: schema: type: string example: OK @@ -4057,6 +4077,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4121,6 +4143,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4170,6 +4194,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4208,6 +4234,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4241,6 +4269,8 @@ paths: $ref: '#/components/responses/400' '404': description: 'Project not found.' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4286,6 +4316,8 @@ paths: description: 'Project not found.' '409': description: 'Environment with this name already exists.' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4326,6 +4358,8 @@ paths: description: 'Environment has resources, so it cannot be deleted.' '404': description: 'Project or environment not found.' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4409,6 +4443,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4447,6 +4483,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4610,6 +4648,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4674,6 +4714,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4740,6 +4782,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4837,6 +4881,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -4929,6 +4975,8 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -5097,6 +5145,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -5190,6 +5240,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -5252,6 +5304,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -5299,6 +5353,8 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' security: - bearerAuth: [] @@ -6322,6 +6378,24 @@ components: type: string example: 'Resource not found.' type: object + '422': + description: 'Validation error.' + content: + application/json: + schema: + properties: + message: + type: string + example: 'Validation error.' + errors: + type: object + example: + name: ['The name field is required.'] + api_url: ['The api url field is required.', 'The api url format is invalid.'] + additionalProperties: + type: array + items: { type: string } + type: object securitySchemes: bearerAuth: type: http diff --git a/other/nightly/versions.json b/other/nightly/versions.json index b5cf3360ad..2e5cc5e849 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.433" + "version": "4.0.0-beta.435" }, "nightly": { - "version": "4.0.0-beta.434" + "version": "4.0.0-beta.436" }, "helper": { "version": "1.0.11" diff --git a/package-lock.json b/package-lock.json index 56e48288ce..210747b4e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -888,8 +888,7 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@tailwindcss/forms": { "version": "0.5.10", @@ -1404,7 +1403,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/asynckit": { "version": "0.4.0", @@ -1567,7 +1567,6 @@ "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", @@ -1582,7 +1581,6 @@ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -1601,7 +1599,6 @@ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" } @@ -2376,6 +2373,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2452,6 +2450,7 @@ "integrity": "sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tweetnacl": "^1.0.3" } @@ -2534,7 +2533,6 @@ "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -2551,7 +2549,6 @@ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -2570,7 +2567,6 @@ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -2585,7 +2581,6 @@ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -2634,7 +2629,8 @@ "version": "4.1.10", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz", "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.2.2", @@ -2700,6 +2696,7 @@ "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -2799,6 +2796,7 @@ "integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.16", "@vue/compiler-sfc": "3.5.16", @@ -2821,7 +2819,6 @@ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -2843,7 +2840,6 @@ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", "dev": true, - "peer": true, "engines": { "node": ">=0.4.0" } diff --git a/public/svgs/ente.png b/public/svgs/ente.png new file mode 100644 index 0000000000..f510a7bf7d Binary files /dev/null and b/public/svgs/ente.png differ diff --git a/public/svgs/gotify.png b/public/svgs/gotify.png new file mode 100644 index 0000000000..5fe38d60a6 Binary files /dev/null and b/public/svgs/gotify.png differ diff --git a/public/svgs/gramps-web.svg b/public/svgs/gramps-web.svg new file mode 100644 index 0000000000..eeef330470 --- /dev/null +++ b/public/svgs/gramps-web.svg @@ -0,0 +1,154 @@ + +image/svg+xml diff --git a/public/svgs/hetzner.svg b/public/svgs/hetzner.svg new file mode 100644 index 0000000000..68b1b868d4 --- /dev/null +++ b/public/svgs/hetzner.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/svgs/lobe-chat.png b/public/svgs/lobe-chat.png new file mode 100644 index 0000000000..2d340df888 Binary files /dev/null and b/public/svgs/lobe-chat.png differ diff --git a/public/svgs/newapi.png b/public/svgs/newapi.png new file mode 100644 index 0000000000..f62bfd57f1 Binary files /dev/null and b/public/svgs/newapi.png differ diff --git a/public/svgs/once-campfire.png b/public/svgs/once-campfire.png new file mode 100644 index 0000000000..d1158445b5 Binary files /dev/null and b/public/svgs/once-campfire.png differ diff --git a/public/svgs/rybbit.svg b/public/svgs/rybbit.svg new file mode 100644 index 0000000000..5715b29bd4 --- /dev/null +++ b/public/svgs/rybbit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/swetrix.svg b/public/svgs/swetrix.svg new file mode 100644 index 0000000000..8bb5bfdfa6 --- /dev/null +++ b/public/svgs/swetrix.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/resources/css/app.css b/resources/css/app.css index c1dc7e56d6..fa1e61cb27 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -18,7 +18,7 @@ --color-base: #101010; --color-warning: #fcd452; - --color-success: #16a34a; + --color-success: #22C55E; --color-error: #dc2626; --color-coollabs-50: #f5f0ff; --color-coollabs: #6b16ed; diff --git a/resources/css/utilities.css b/resources/css/utilities.css index bedfb51bca..1a95de03af 100644 --- a/resources/css/utilities.css +++ b/resources/css/utilities.css @@ -32,7 +32,7 @@ } @utility input-sticky { - @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-1 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 focus:ring-2 focus:ring-neutral-400 dark:focus:ring-coolgray-300; + @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-1 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-base; } @utility input-sticky-active { @@ -41,7 +41,7 @@ /* Focus */ @utility input-focus { - @apply focus:ring-2 focus:ring-neutral-400 dark:focus:ring-coolgray-300; + @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-base; } /* input, select before */ @@ -52,18 +52,18 @@ /* Readonly */ @utility input { @apply dark:read-only:text-neutral-500 dark:read-only:ring-0 dark:read-only:bg-coolgray-100/40 placeholder:text-neutral-300 dark:placeholder:text-neutral-700 read-only:text-neutral-500 read-only:bg-neutral-200; - @apply input-focus; @apply input-select; + @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-base; } @utility select { @apply w-full; - @apply input-focus; @apply input-select; + @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-base; } @utility button { - @apply flex gap-2 justify-center items-center px-2 h-8 text-sm text-black normal-case rounded-sm border-2 outline-0 cursor-pointer font-medium bg-white border-neutral-200 hover:bg-neutral-100 dark:bg-coolgray-100 dark:text-white dark:hover:text-white dark:hover:bg-coolgray-200 dark:border-coolgray-300 hover:text-black disabled:cursor-not-allowed min-w-fit dark:disabled:text-neutral-600 disabled:border-transparent disabled:hover:bg-transparent disabled:bg-transparent disabled:text-neutral-300 focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-coolgray-100; + @apply flex gap-2 justify-center items-center px-2 h-8 text-sm text-black normal-case rounded-sm border-2 outline-0 cursor-pointer font-medium bg-white border-neutral-200 hover:bg-neutral-100 dark:bg-coolgray-100 dark:text-white dark:hover:text-white dark:hover:bg-coolgray-200 dark:border-coolgray-300 hover:text-black disabled:cursor-not-allowed min-w-fit dark:disabled:text-neutral-600 disabled:border-transparent disabled:hover:bg-transparent disabled:bg-transparent disabled:text-neutral-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-base; } @utility alert-success { diff --git a/resources/views/components/boarding-progress.blade.php b/resources/views/components/boarding-progress.blade.php new file mode 100644 index 0000000000..dec34abac5 --- /dev/null +++ b/resources/views/components/boarding-progress.blade.php @@ -0,0 +1,47 @@ +@props(['currentStep' => 1, 'totalSteps' => 3]) + +
+
+ @for ($i = 1; $i <= $totalSteps; $i++) +
+
+
+ @if ($i < $currentStep) + + + + @else + + {{ $i }} + + @endif +
+ + @if ($i === 1) + Server + @elseif ($i === 2) + Connection + @elseif ($i === 3) + Complete + @endif + +
+ @if ($i < $totalSteps) +
+
+ @endif +
+ @endfor +
+
diff --git a/resources/views/components/boarding-step.blade.php b/resources/views/components/boarding-step.blade.php index d963e55f0f..716987bafe 100644 --- a/resources/views/components/boarding-step.blade.php +++ b/resources/views/components/boarding-step.blade.php @@ -1,25 +1,29 @@ -
-
-

{{ $title }}

-
+
+
+
+

{{ $title }}

@isset($question) -

+

{{ $question }} -

+
@endisset + + @if ($actions) +
+ {{ $actions }} +
+ @endif
- @if ($actions) -
- {{ $actions }} + + @isset($explanation) +
+

+ Technical Details +

+
+ {{ $explanation }} +
- @endif + @endisset
- @isset($explanation) -
-

Explanation

-
- {{ $explanation }} -
-
- @endisset
diff --git a/resources/views/components/forms/datalist.blade.php b/resources/views/components/forms/datalist.blade.php index c9710b7288..7f9ffefece 100644 --- a/resources/views/components/forms/datalist.blade.php +++ b/resources/views/components/forms/datalist.blade.php @@ -1,6 +1,6 @@
-
+@endif + +@error($id) + +@enderror
diff --git a/resources/views/components/forms/input.blade.php b/resources/views/components/forms/input.blade.php index 858f5ac1c8..f6c86f1776 100644 --- a/resources/views/components/forms/input.blade.php +++ b/resources/views/components/forms/input.blade.php @@ -28,8 +28,7 @@ class="flex absolute inset-y-0 right-0 items-center pr-2 cursor-pointer dark:hov 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" + wire:dirty.class="dark:ring-warning ring-warning" wire:loading.attr="disabled" type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}" name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}" aria-placeholder="{{ $attributes->get('placeholder') }}" @@ -40,8 +39,7 @@ class="flex absolute inset-y-0 right-0 items-center pr-2 cursor-pointer dark:hov 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' - wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled" + wire:dirty.class="dark:ring-warning ring-warning" wire:loading.attr="disabled" type="{{ $type }}" @disabled($disabled) min="{{ $attributes->get('min') }}" max="{{ $attributes->get('max') }}" minlength="{{ $attributes->get('minlength') }}" maxlength="{{ $attributes->get('maxlength') }}" diff --git a/resources/views/components/forms/select.blade.php b/resources/views/components/forms/select.blade.php index 508a85e0c5..3c8eea25ac 100644 --- a/resources/views/components/forms/select.blade.php +++ b/resources/views/components/forms/select.blade.php @@ -11,8 +11,7 @@ class="flex gap-1 items-center mb-1 text-sm font-medium {{ $disabled ? 'text-neu @endif diff --git a/resources/views/components/forms/textarea.blade.php b/resources/views/components/forms/textarea.blade.php index b4dec192a9..a1c57e7757 100644 --- a/resources/views/components/forms/textarea.blade.php +++ b/resources/views/components/forms/textarea.blade.php @@ -46,8 +46,7 @@ class="absolute inset-y-0 right-0 flex items-center h-6 pt-2 pr-2 cursor-pointer merge(['class' => $defaultClassInput]) }} @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" + wire:dirty.class="dark:ring-warning ring-warning" wire:loading.attr="disabled" type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}" name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}" aria-placeholder="{{ $attributes->get('placeholder') }}"> @@ -56,7 +55,7 @@ class="absolute inset-y-0 right-0 flex items-center h-6 pt-2 pr-2 cursor-pointer @if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}" @else wire:model={{ $value ?? $id }} - wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300' wire:dirty.class="dark:focus:ring-warning dark:ring-warning" @endif + wire:dirty.class="dark:ring-warning ring-warning" @endif @disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}" name="{{ $name }}" name={{ $id }}> @@ -68,7 +67,7 @@ class="absolute inset-y-0 right-0 flex items-center h-6 pt-2 pr-2 cursor-pointer @if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}" @else wire:model={{ $value ?? $id }} - wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300' wire:dirty.class="dark:focus:ring-warning dark:ring-warning" @endif + wire:dirty.class="dark:ring-warning ring-warning" @endif @disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}" name="{{ $name }}" name={{ $id }}> @endif diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php index 1a3c88f806..46164840d8 100644 --- a/resources/views/components/modal-confirmation.blade.php +++ b/resources/views/components/modal-confirmation.blade.php @@ -114,7 +114,7 @@ } } }" - @keydown.escape.window="modalOpen = false; resetModal()" :class="{ 'z-40': modalOpen }" + @keydown.escape.window="if (modalOpen) { modalOpen = false; resetModal(); }" :class="{ 'z-40': modalOpen }" class="relative w-auto h-auto"> @if ($customButton) @if ($buttonFullWidth) @@ -250,6 +250,18 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300"> {{ $checkbox['label'] }} + @if (isset($checkbox['default_warning'])) + + @endif @endforeach @if (!$disableTwoStepConfirmation) diff --git a/resources/views/components/modal-input.blade.php b/resources/views/components/modal-input.blade.php index c15985d03b..6291e87745 100644 --- a/resources/views/components/modal-input.blade.php +++ b/resources/views/components/modal-input.blade.php @@ -8,8 +8,11 @@ 'content' => null, 'closeOutside' => true, 'minWidth' => '36rem', + 'isFullWidth' => false, ]) -
@if ($content)
@@ -17,13 +20,13 @@ class="relative w-auto h-auto" wire:ignore>
@else @if ($disabled) - {{ $buttonTitle }} + $isFullWidth])>{{ $buttonTitle }} @elseif ($isErrorButton) - {{ $buttonTitle }} + $isFullWidth])>{{ $buttonTitle }} @elseif ($isHighlightedButton) - {{ $buttonTitle }} + $isFullWidth])>{{ $buttonTitle }} @else - {{ $buttonTitle }} + $isFullWidth])>{{ $buttonTitle }} @endif @endif