Implement a per-application setting (`docker_images_to_keep`) in `application_settings` table to control how many Docker images are preserved during cleanup. The cleanup process now:
- Respects per-application retention settings (default: 2 images)
- Preserves the N most recent images per application for easy rollback
- Always deletes PR images and keeps the currently running image
- Dynamically excludes application images from general Docker image prune
- Cleans up non-Coolify unused images to prevent disk bloat
Fixes issues where cleanup would delete all images needed for rollback.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added a new `collapsible` property to GetLogs component that allows disabling the expandable header, useful for log viewers in dedicated pages and slide-overs. Applied this to Sentinel logs, Proxy logs, and Coolify Proxy log pages. Also improved the toolbar by moving the lines counter to the left side with an inline prefix label and repositioning the match counter next to it for better organization.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add configurable deployment_queue_limit server setting (default: 25)
- Check queue size before accepting new deployments
- Return 429 status for webhooks/API when queue is full (allows retry)
- Show error toast in UI when queue limit reached
- Add UI control in Server Advanced settings
Fixes#6708🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Moved .log-highlight styles from Livewire component views to resources/css/app.css for better separation of concerns and reusability. This follows Laravel and Livewire best practices by keeping styles in the appropriate location rather than inline in component views.
Changes:
- Added .log-highlight styles to resources/css/app.css
- Removed inline <style> tags from deployment/show.blade.php
- Removed inline <style> tags from get-logs.blade.php
- Added XSS security test for log viewer
- Applied code formatting with Laravel Pint
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Features:
- Add client-side search filtering for runtime and deployment logs
- Add log download functionality (respects search filters)
- Make runtime log sections collapsible by default
- Auto-expand single container and lazy load logs on first expand
- Match deployment and runtime log view heights (40rem)
- Add debug toggle for deployment logs
- Improve scroll behavior with follow logs feature
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
The guard was setting and immediately resetting the flag in the same
synchronous execution, providing no actual protection. Now the flag
stays true until proxy reaches a stable state (running/exited/error)
via WebSocket notification, with additional client-side guard.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Extended the maximum allowed timeout for scheduled tasks from 3600 to 36000 seconds (10 hours). Also passes the configured timeout to instant_remote_process() so the SSH command respects the timeout setting.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When restarting the proxy on localhost (server id 0), shows a warning
banner in the logs sidebar explaining that the connection may be
temporarily lost and to refresh the browser if logs stop updating.
Also cleans up notification noise by commenting out intermediate
status notifications (restarting, starting, stopping) that were
redundant with the visual status indicators.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add restartInitiated flag to prevent duplicate "Proxy restart initiated" messages
- Restore ProxyStatusChangedUI dispatch with activityId in RestartProxyJob
- This allows the UI to open the activity monitor and show logs during restart
- Simplified restart message (removed redundant "Monitor progress" text)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The Application::loadComposeFile method's finally block always saves
the model, which was persisting invalid base_directory values when
validation failed.
Changes:
- Add restoreBaseDirectory and restoreDockerComposeLocation parameters
to loadComposeFile() in both Application model and General component
- The finally block now restores BOTH base_directory and
docker_compose_location to the provided original values before saving
- When called from submit(), pass the original DB values so they are
restored on failure instead of the new invalid values
This ensures invalid paths are never persisted to the database.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When restarting the proxy on localhost (where Coolify is running), the UI becomes inaccessible because the connection is lost. This change makes all proxy restarts run as background jobs with WebSocket notifications, allowing the operation to complete even after connection loss.
Changes:
- Enhanced ProxyStatusChangedUI event to carry activityId for log monitoring
- Updated RestartProxyJob to dispatch status events and track activity
- Simplified Navbar restart() to always dispatch job for all servers
- Enhanced showNotification() to handle activity monitoring and new statuses
- Added comprehensive unit and feature tests
Benefits:
- Prevents localhost lockout during proxy restarts
- Consistent behavior across all server types
- Non-blocking UI with real-time progress updates
- Automatic activity log monitoring
- Proper error handling and recovery
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Move compose file validation BEFORE database save to prevent invalid
base directory and docker compose location values from being persisted
when validation fails.
Changes:
- Move compose file validation before $this->application->save()
- Restore original values when validation fails
- Add resetErrorBag() to clear stale validation errors
This fixes two bugs:
1. Invalid paths were saved to DB even when validation failed
2. Error messages persisted after correcting to valid path
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Apply the same frontend path normalization pattern from commit f6398f7cf
to the General Settings page for consistency across all forms.
Changes:
- Add Alpine.js path normalization to Docker Compose section (base directory + compose location)
- Add Alpine.js path normalization to non-Docker Compose section (base directory + dockerfile location)
- Change wire:model to wire:model.defer to prevent backend requests during tab navigation
- Add @blur event handlers for immediate path normalization feedback
- Backend normalization remains as defensive fallback
This ensures consistent validation behavior and fixes potential tab focus
issues on the General Settings page.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Change wire:model.blur to wire:model.defer to prevent backend requests
during form navigation. Add Alpine.js path normalization functions that
run on blur, fixing tab focus issues while keeping path validation
purely on the frontend.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When users updated Traefik configuration or version and restarted the proxy, the warning triangle icon showing outdated version info persisted until the weekly CheckTraefikVersionJob ran (Sundays at 00:00).
This was caused by the UI warning indicators reading from cached database columns (detected_traefik_version, traefik_outdated_info) that were only updated by the weekly scheduled job, not after proxy restarts.
Solution: Add version check to ProxyStatusChangedNotification listener that triggers automatically after proxy status changes to "running".
Changes:
- Add Traefik version check in ProxyStatusChangedNotification::handle()
- Triggers automatically when ProxyStatusChanged event fires with status="running"
- Removed duplicate version check from Navbar::restart() (now handled by event)
- Event fires after StartProxy/StopProxy actions complete via async jobs
- Gracefully handles missing versions.json data with warning log
Benefits:
- Version check happens AFTER proxy is confirmed running (more accurate)
- Reuses existing event infrastructure (ProxyStatusChanged)
- Works for all proxy restart scenarios (manual restart, config save + restart, etc.)
- No duplicate checks - single source of truth in event listener
- Async job runs in background (5-10 seconds) to update database
- User sees warning cleared after page refresh
Flow:
1. User updates config and restarts proxy (or manually restarts)
2. StartProxy action completes async, dispatches ProxyStatusChanged event
3. ProxyStatusChangedNotification listener receives event
4. Listener checks proxy status = "running", dispatches CheckTraefikVersionForServerJob
5. Job detects version via SSH, updates database columns
6. UI re-renders with cleared warnings
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The "Final Build Command (Preview)" field now shows build arguments
that will be injected during deployment, matching the actual command
that runs. This provides transparency and helps users debug build issues.
Changes:
- Modified getDockerComposeBuildCommandPreviewProperty() to inject build args
- Uses same helper functions as deployment (generateDockerBuildArgs, injectDockerComposeBuildArgs)
- Respects use_build_secrets setting (build args only shown when disabled)
- Filters environment variables where is_buildtime = true
Example output:
docker compose -f ./docker-compose.yaml --env-file /artifacts/build-time.env build --build-arg FOO --build-arg BAR backend
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add instantSaveSettings() method to save gzip, stripprefix, and
exclude_from_status checkboxes without triggering port validation modal.
These settings don't require domain/port validation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes two critical issues preventing Traefik proxy startup:
1. TypeError when restarting proxy: Handle null return from get_traefik_versions()
- Add null check before dispatching CheckTraefikVersionForServerJob
- Log warning when version data is unavailable
- Prevents: "Argument #2 must be of type array, null given"
2. Docker network error: Filter out predefined Docker networks
- Add isDockerPredefinedNetwork() helper to centralize network filtering
- Apply filtering in collectDockerNetworksByServer() before operations
- Apply filtering in generateDefaultProxyConfiguration()
- Prevents: "operation is not permitted on predefined default network"
Also: Move $cachedVersionsFile assignment after null check in Proxy.php
Tests: Added 7 new unit tests for network filtering function
All existing tests pass with no regressions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix incorrect Alpine state reference: Changed `this.$wire.showProgress` to `this.showProgress` in upgrade.blade.php:155
- Remove unused `$showProgress` property from Upgrade.php Livewire component
- The backend property was never set or used; all progress tracking is handled by Alpine state
- This fixes potential race conditions where the guard condition was not working as intended
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from `->before('-')` to `->beforeLast('-')` to correctly parse service
names with hyphens. This fixes prerequisite application for ~230+ services
containing hyphens in their template names (e.g., docker-registry,
elasticsearch-with-kibana).
Added comprehensive test coverage for hyphenated service names and fixed
existing tests to use realistic CUID2 UUID format. All unit tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Refactors the Appwrite and Beszel service-specific application settings
to use a centralized constant-based approach, following the same pattern
as NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK.
Changes:
- Added NEEDS_TO_DISABLE_GZIP constant for services requiring gzip disabled
- Added NEEDS_TO_DISABLE_STRIPPREFIX constant for services requiring stripprefix disabled
- Created applyServiceApplicationPrerequisites() helper function in bootstrap/helpers/services.php
- Updated all service creation flows to use the centralized helper:
* app/Livewire/Project/Resource/Create.php (web handler)
* app/Http/Controllers/Api/ServicesController.php (API handler - BUG FIX)
* app/Livewire/Project/New/DockerCompose.php (custom compose handler)
* app/Http/Controllers/Api/ApplicationsController.php (API custom compose handler)
- Added comprehensive unit tests for the new helper function
Benefits:
- Single source of truth for service prerequisites
- DRY - eliminates code duplication between web and API handlers
- Fixes bug where API-created services didn't get prerequisites applied
- Easy to extend for future services (just edit the constant)
- More maintainable and testable
Related commits: 3a94f1ea1 (Beszel), 02b18c86e (Appwrite)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix sudo prefix bug: Use word boundary matching to prevent 'do' keyword from matching 'docker' commands
- Add ensureProxyNetworksExist() helper to create networks before docker compose up
- Ensure networks exist synchronously before dispatching async proxy startup to prevent race conditions
- Update comprehensive unit tests for sudo parsing (50 tests passing)
This resolves issues where Docker commands failed to execute with sudo on non-root servers and where proxy networks were not created before the proxy container started.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Without this fix users have to manually uncheck strip prefix option for appwrite, appwrite-console, and appwrite-realtime services for the service to work
Add two new application settings to control Docker build cache invalidation:
- inject_build_args_to_dockerfile (default: true) - Skip Dockerfile ARG injection
- include_source_commit_in_build (default: false) - Exclude SOURCE_COMMIT from build context
These toggles let users preserve Docker cache when SOURCE_COMMIT or custom ARGs change frequently. Development-only logging shows which ARGs are being injected for debugging.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes the "Snapshot missing on Livewire component" error that occurs when
toggling the "Backup includes all databases" checkbox during MariaDB database
import operations.
Root Cause:
- ActivityMonitor component was initialized without proper lifecycle hooks
- When parent Import component re-rendered (via checkbox toggle), the
ActivityMonitor's Livewire snapshot became stale
- Missing null checks caused errors when querying with undefined activityId
- No state cleanup when slide-over closed, causing issues on subsequent opens
Changes:
- Add updatedActivityId() lifecycle hook to ActivityMonitor for proper hydration
- Add defensive null check in hydrateActivity() to prevent query errors
- Track activityId in Import component for state management
- Add slideOverClosed event dispatch in slide-over component
- Add event listener in Import component to reset activityId on close
Testing:
- Manually verify checkbox toggle doesn't trigger popup
- Verify actual restore operations work correctly
- Test both file-based and S3-based restore methods
- Ensure X button properly closes the modal
- Verify no console errors or Livewire warnings
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Move the success dispatch outside the DB transaction closure to ensure
it only fires after the transaction has successfully committed. Use
reference variable to track changes across the closure boundary.
- Add path attribute mutator to S3Storage model ensuring paths start with /
- Add updatedS3Path hook to normalize path and reset validation state on blur
- Add updatedS3StorageId hook to reset validation state when storage changes
- Add Enter key support to trigger file check from path input
- Use wire:model.live for S3 storage select, wire:model.blur for path input
- Improve shell escaping in restore job cleanup commands
- Fix isSafeTmpPath helper logic for directory validation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit enhances the boarding flow to handle prerequisite installation asynchronously with proper retry logic and user feedback:
- Add retry mechanism with max 3 attempts for prerequisite installation
- Display live installation logs via ActivityMonitor during boarding
- Reset ActivityMonitor state when starting new activity to prevent stale event dispatching
- Support dynamic header updates in ActivityMonitor
- Add prerequisitesInstalled event handler to revalidate after installation completes
- Extract validation logic into continueValidation() method for cleaner flow
- Add unit tests for prerequisite installation logic
This improves UX by showing users real-time progress during prerequisite installation and handles installation failures gracefully with automatic retries.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds a new EnvVarInput component that provides autocomplete suggestions for shared environment variables from team, project, and environment scopes. Users can reference variables using {{ syntax.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix double-slash issue in Docker Compose preview paths when baseDirectory is "/"
- Normalize baseDirectory using rtrim() to prevent path concatenation issues
- Replace hardcoded '/artifacts/build-time.env' with ApplicationDeploymentJob::BUILD_TIME_ENV_PATH
- Make BUILD_TIME_ENV_PATH constant public for reusability
- Add comprehensive unit tests (11 test cases, 25 assertions)
Fixes preview path generation in:
- getDockerComposeBuildCommandPreviewProperty()
- getDockerComposeStartCommandPreviewProperty()
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When a user restarts the proxy from the Navbar UI component, the system now automatically dispatches a version check job immediately after the restart completes. This provides immediate feedback about available Traefik updates without waiting for the weekly scheduled check.
Changes:
- Import CheckTraefikVersionForServerJob in Navbar component
- After successful proxy restart, dispatch version check for Traefik servers
- Version check only runs for servers using Traefik proxy
This ensures users get up-to-date version information right after restarting their proxy infrastructure.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit introduces several improvements to the Traefik version tracking
feature and proxy configuration UI:
## Caching Improvements
1. **New centralized helper functions** (bootstrap/helpers/versions.php):
- `get_versions_data()`: Redis-cached access to versions.json (1 hour TTL)
- `get_traefik_versions()`: Extract Traefik versions from cached data
- `invalidate_versions_cache()`: Clear cache when file is updated
2. **Performance optimization**:
- Single Redis cache key: `coolify:versions:all`
- Eliminates 2-4 file reads per page load
- 95-97.5% reduction in disk I/O time
- Shared cache across all servers in distributed setup
3. **Updated all consumers to use cached helpers**:
- CheckTraefikVersionJob: Use get_traefik_versions()
- Server/Proxy: Two-level caching (Redis + in-memory per-request)
- CheckForUpdatesJob: Auto-invalidate cache after updating file
- bootstrap/helpers/shared.php: Use cached data for Coolify version
## UI/UX Improvements
1. **Navbar warning indicator**:
- Added yellow warning triangle icon next to "Proxy" menu item
- Appears when server has outdated Traefik version
- Uses existing traefik_outdated_info data for instant checks
- Provides at-a-glance visibility of version issues
2. **Proxy sidebar persistence**:
- Fixed sidebar disappearing when clicking "Switch Proxy"
- Configuration link now always visible (needed for proxy selection)
- Dynamic Configurations and Logs only show when proxy is configured
- Better navigation context during proxy switching workflow
## Code Quality
- Added comprehensive PHPDoc for Server::$traefik_outdated_info property
- Improved code organization with centralized helper approach
- All changes formatted with Laravel Pint
- Maintains backward compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Store both patch update and newer minor version information simultaneously
- Display patch update availability alongside minor version upgrades in notifications
- Add newer_branch_target and newer_branch_latest fields to traefik_outdated_info
- Update all notification channels (Discord, Telegram, Slack, Pushover, Email, Webhook)
- Show minor version in format (e.g., v3.6) for upgrade targets instead of patch version
- Enhance UI callouts with clearer messaging about available upgrades
- Remove verbose logging in favor of cleaner code structure
- Handle edge case where SSH command returns empty response
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Add automated Traefik version checking job running weekly on Sundays
- Implement version detection from running containers and comparison with versions.json
- Add notifications across all channels (Email, Discord, Slack, Telegram, Pushover, Webhook) for outdated versions
- Create dismissible callout component with localStorage persistence
- Display cross-branch upgrade warnings (e.g., v3.5 -> v3.6) with changelog links
- Show patch update notifications within same branch
- Add warning icon that appears when callouts are dismissed
- Prevent duplicate notifications during proxy restart by adding restarting parameter
- Fix notification spam with transition-based logic for status changes
- Enable system email settings by default in development mode
- Track last saved/applied proxy settings to detect configuration drift
Move buildpack switching cleanup from Livewire component to Application model's boot lifecycle. This improves separation of concerns and ensures cleanup happens consistently regardless of how the buildpack change is triggered. Also clears Dockerfile-specific data when switching away from dockerfile buildpack.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix double-slash issue in Docker Compose preview paths when baseDirectory is "/"
- Normalize baseDirectory using rtrim() to prevent path concatenation issues
- Replace hardcoded '/artifacts/build-time.env' with ApplicationDeploymentJob::BUILD_TIME_ENV_PATH
- Make BUILD_TIME_ENV_PATH constant public for reusability
- Add comprehensive unit tests (11 test cases, 25 assertions)
Fixes preview path generation in:
- getDockerComposeBuildCommandPreviewProperty()
- getDockerComposeStartCommandPreviewProperty()
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Move buildpack switching cleanup from Livewire component to Application model's boot lifecycle. This improves separation of concerns and ensures cleanup happens consistently regardless of how the buildpack change is triggered. Also clears Dockerfile-specific data when switching away from dockerfile buildpack.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When a user restarts the proxy from the Navbar UI component, the system now automatically dispatches a version check job immediately after the restart completes. This provides immediate feedback about available Traefik updates without waiting for the weekly scheduled check.
Changes:
- Import CheckTraefikVersionForServerJob in Navbar component
- After successful proxy restart, dispatch version check for Traefik servers
- Version check only runs for servers using Traefik proxy
This ensures users get up-to-date version information right after restarting their proxy infrastructure.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit introduces several improvements to the Traefik version tracking
feature and proxy configuration UI:
## Caching Improvements
1. **New centralized helper functions** (bootstrap/helpers/versions.php):
- `get_versions_data()`: Redis-cached access to versions.json (1 hour TTL)
- `get_traefik_versions()`: Extract Traefik versions from cached data
- `invalidate_versions_cache()`: Clear cache when file is updated
2. **Performance optimization**:
- Single Redis cache key: `coolify:versions:all`
- Eliminates 2-4 file reads per page load
- 95-97.5% reduction in disk I/O time
- Shared cache across all servers in distributed setup
3. **Updated all consumers to use cached helpers**:
- CheckTraefikVersionJob: Use get_traefik_versions()
- Server/Proxy: Two-level caching (Redis + in-memory per-request)
- CheckForUpdatesJob: Auto-invalidate cache after updating file
- bootstrap/helpers/shared.php: Use cached data for Coolify version
## UI/UX Improvements
1. **Navbar warning indicator**:
- Added yellow warning triangle icon next to "Proxy" menu item
- Appears when server has outdated Traefik version
- Uses existing traefik_outdated_info data for instant checks
- Provides at-a-glance visibility of version issues
2. **Proxy sidebar persistence**:
- Fixed sidebar disappearing when clicking "Switch Proxy"
- Configuration link now always visible (needed for proxy selection)
- Dynamic Configurations and Logs only show when proxy is configured
- Better navigation context during proxy switching workflow
## Code Quality
- Added comprehensive PHPDoc for Server::$traefik_outdated_info property
- Improved code organization with centralized helper approach
- All changes formatted with Laravel Pint
- Maintains backward compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Optimizations:
- Add immediate cleanup of helper container and server temp files after copying to database
- Add pre-cleanup to handle interrupted restores
- Combine restore + cleanup commands to remove DB temp files immediately after restore
- Reduce temp file lifetime from minutes to seconds (70-80% reduction)
- Add progress tracking via MinIO client (shows by default)
- Update user message to mention progress visibility
Benefits:
- Temp files exist only as long as needed (not until end of process)
- Real-time S3 download progress shown in activity monitor
- Better disk space management through aggressive cleanup
- Improved error recovery with pre-cleanup
Compatibility:
- Works with all database types (PostgreSQL, MySQL, MariaDB, MongoDB)
- All existing tests passing
- Event-based cleanup acts as safety net for edge cases
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major architectural improvements:
- Merged download and restore into single atomic operation
- Eliminated separate S3DownloadFinished event (redundant)
- Files now transfer directly: S3 → helper container → server → database container
- Removed download progress tracking in favor of unified restore progress
UI/UX improvements:
- Unified restore method selection with visual cards
- Consistent "File Information" display between local and S3 restore
- Single slide-over for all restore operations (removed separate S3 download monitor)
- Better visual feedback with loading states
Security enhancements:
- Added isSafeTmpPath() helper for path traversal protection
- URL decode validation to catch encoded attacks
- Canonical path resolution to prevent symlink attacks
- Comprehensive path validation in all cleanup events
Cleanup improvements:
- S3RestoreJobFinished now handles all cleanup (helper container + all temp files)
- RestoreJobFinished uses new isSafeTmpPath() validation
- CoolifyTask dispatches cleanup events even on job failure
- All cleanup uses non-throwing commands (2>/dev/null || true)
Other improvements:
- S3 storage policy authorization on Show component
- Storage Form properly syncs is_usable state after test
- Removed debug code and improved error handling
- Better command organization and documentation
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Store both patch update and newer minor version information simultaneously
- Display patch update availability alongside minor version upgrades in notifications
- Add newer_branch_target and newer_branch_latest fields to traefik_outdated_info
- Update all notification channels (Discord, Telegram, Slack, Pushover, Email, Webhook)
- Show minor version in format (e.g., v3.6) for upgrade targets instead of patch version
- Enhance UI callouts with clearer messaging about available upgrades
- Remove verbose logging in favor of cleaner code structure
- Handle edge case where SSH command returns empty response
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Add automated Traefik version checking job running weekly on Sundays
- Implement version detection from running containers and comparison with versions.json
- Add notifications across all channels (Email, Discord, Slack, Telegram, Pushover, Webhook) for outdated versions
- Create dismissible callout component with localStorage persistence
- Display cross-branch upgrade warnings (e.g., v3.5 -> v3.6) with changelog links
- Show patch update notifications within same branch
- Add warning icon that appears when callouts are dismissed
- Prevent duplicate notifications during proxy restart by adding restarting parameter
- Fix notification spam with transition-based logic for status changes
- Enable system email settings by default in development mode
- Track last saved/applied proxy settings to detect configuration drift
The first click did nothing because instant_remote_process() blocked the
Livewire response, preventing UI state updates. The button also remained
visible during download, allowing multiple clicks.
- Replace blocking instant_remote_process() with async command in queue
- Add container cleanup to command queue with error suppression
- Hide "Download & Prepare" button when s3DownloadInProgress is true
- Button now properly disappears when clicked, preventing double-clicks
- No more blocking operations in downloadFromS3() method
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The s3DownloadedFile was being set immediately when download started,
causing the "Restore" button to appear while still downloading and
the download message to not hide properly.
- Remove immediate setting of s3DownloadedFile in downloadFromS3()
- Set s3DownloadedFile only in handleS3DownloadFinished() event handler
- Add broadcastWith() to S3DownloadFinished to send downloadPath
- Store downloadPath as public property for broadcasting
- Now download message hides and restore button shows only when complete
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The event was broadcasting to the first user in the team instead of
the actual user who triggered the download. This caused the download
message to never hide for other team members.
- Pass userId in S3DownloadFinished event data
- Use the specific userId from event data for broadcasting
- Remove unused User model import
- Ensures broadcast reaches the correct user's private channel
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Make S3DownloadFinished implement ShouldBroadcast
- Add listener in Import component to handle S3DownloadFinished event
- Set s3DownloadInProgress to false when download completes
- This hides "Downloading from S3..." message after download finishes
- Follows the same pattern as DatabaseStatusChanged event
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove App\\Events\\ prefix from event class names
- RunRemoteProcess already prepends App\\Events\\ to the class name
- Use 'S3DownloadFinished' instead of 'App\\Events\\S3DownloadFinished'
- Use 'S3RestoreJobFinished' instead of 'App\\Events\\S3RestoreJobFinished'
- Fixes "Class 'App\Events\App\Events\S3DownloadFinished' not found" error
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create S3DownloadFinished event to cleanup MinIO containers
- Create S3RestoreJobFinished event to cleanup temp files and S3 downloads
- Add formatBytes() helper function for human-readable file sizes
- Update Import component to use full Event class names in callEventOnFinish
- Fix activity monitor visibility issues with proper event dispatching
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add debugging to understand why the download message stays visible after completion.
This will help us see if:
1. The event is being dispatched by ActivityMonitor
2. The event is being received by Import component
3. The property is being set to false
4. The entangle is syncing to Alpine properly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause analysis:
- Changed from dispatch to property binding broke the activity monitor completely
- ActivityMonitor component expects activityMonitor event, not property binding
- Original approach was correct: use dispatch + event listeners
Solution:
- Revert to original dispatch('activityMonitor', $activity->id) calls
- Use @if conditionals to render only one monitor at a time (removes from DOM)
- Add unique wire:key to each monitor instance to prevent conflicts
- S3 download monitor: wire:key="s3-download-{{ $resource->uuid }}"
- Database restore monitor: wire:key="database-restore-{{ $resource->uuid }}"
This ensures:
- Activity monitors display correctly when processes start
- Only one monitor is rendered at a time (S3 download OR database restore)
- Each monitor has unique identity via wire:key
- Event listeners work as designed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add updatedActivityId method to watch for changes to activityId property
- When activityId is set/updated, automatically hydrate the activity and enable polling
- This allows the activity monitor to display content when activityId is bound from parent component
- Fixes issue where activity monitor was empty because activity wasn't loaded
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add S3DownloadFinished event listener to Import component
- Add handleS3DownloadFinished method to set s3DownloadInProgress to false
- This ensures the 'Downloading from S3...' message is hidden when download completes
- The success message now properly displays after download finishes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add currentActivityId property to track the active process
- Replace event dispatching with property assignment for cleaner state management
- S3 download monitor only renders during download and is removed when complete
- Database restore monitor only renders during restore operation
- Both monitors now share the same activity-monitor component instance with proper lifecycle management
- When user starts restore after S3 download, S3 monitor is removed from DOM
- Fixes issue where S3 download and database restore showed identical output
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add unique wire keys to activity-monitor components (s3-download-monitor and database-restore-monitor)
- Update dispatch calls to target specific components using ->to() method
- This prevents both activity monitors from listening to the same activityMonitor event and displaying identical output
- S3 download now shows in s3-download-monitor component
- Database restore now shows in database-restore-monitor component
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add Alpine.js entangle bindings for s3StorageId and s3Path to enable
reactive button state without server requests
- Change button disabled binding from PHP :disabled to Alpine x-bind:disabled
for client-side reactivity using deferred wire:model inputs
- Replace S3Storage::findOrFail with ownedByCurrentTeam()->findOrFail in
checkS3File() and downloadFromS3() methods
- Remove redundant manual team verification since ownedByCurrentTeam scope
automatically filters to current team
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit introduces functionality for integrating S3 storage into the import process. It allows users to select S3 storage, check for file existence, and download files directly from S3. This enhancement improves the flexibility of the import feature by enabling users to work with files stored in S3, addressing a common use case for teams that utilize cloud storage solutions.
Removed duplicate queue_application_deployment() call in Heading.php deploy method that was causing "Deployment already queued for this commit" error to display even though deployment was successfully queued.
Also changed notification type from 'success' to 'error' when deployment is actually skipped for proper user feedback.
Add detection system for PORT environment variable to help users configure applications correctly:
- Add detectPortFromEnvironment() method to Application model to detect PORT env var
- Add getDetectedPortInfoProperty() computed property in General Livewire component
- Display contextual info banners in UI when PORT is detected:
- Warning when PORT exists but ports_exposes is empty
- Warning when PORT doesn't match ports_exposes configuration
- Info message when PORT matches ports_exposes
- Add deployment logging to warn about PORT/ports_exposes mismatches
- Include comprehensive unit tests for port detection logic
The ports_exposes field remains authoritative for proxy configuration, while
PORT detection provides helpful suggestions to users.
Track container restart counts from Docker and detect crash loops to provide better visibility into application health issues.
- Add restart_count, last_restart_at, and last_restart_type columns to applications table
- Detect restart count increases from Docker inspect data and send notifications
- Show restart count badge in UI with warning icon on Logs navigation
- Distinguish between crash restarts and manual restarts
- Implement 30-second grace period to prevent false "exited" status during crash loops
- Reset restart count on manual stop, restart, and redeploy actions
- Add unit tests for restart count tracking logic
This helps users quickly identify when containers are in crash loops and need attention, even when the container status flickers between states during Docker's restart backoff period.
- Add retry configuration to CoolifyTask (3 tries, 600s timeout)
- Add retry configuration to ScheduledTaskJob (3 tries, configurable timeout)
- Add retry configuration to DatabaseBackupJob (2 tries)
- Implement exponential backoff for all jobs (30s, 60s, 120s intervals)
- Add failed() handlers with comprehensive error logging to scheduled-errors channel
- Add execution tracking: started_at, retry_count, duration (decimal), error_details
- Add configurable timeout field to scheduled tasks (60-3600s, default 300s)
- Update UI to include timeout configuration in task creation/editing forms
- Increase ScheduledJobManager lock expiration from 60s to 90s for high-load environments
- Implement safe queue cleanup with restart vs runtime modes
- Restart mode: aggressive cleanup (marks all processing jobs as failed)
- Runtime mode: conservative cleanup (only marks jobs >12h as failed, skips deployments)
- Add cleanup:redis --restart flag for system startup
- Integrate cleanup into Dev.php init() for development environment
- Increase scheduled-errors log retention from 7 to 14 days
- Create comprehensive test suite (unit and feature tests)
- Add TESTING_GUIDE.md with manual testing instructions
Fixes issues with jobs failing after single attempt and "attempted too many times" errors
- Added `requiredPort` property to `ServiceApplicationView` to track the required port for services.
- Introduced modal confirmation for removing required ports, including methods to confirm or cancel the action.
- Enhanced `Service` model with `getRequiredPort` and `requiresPort` methods to retrieve port information from service templates.
- Implemented `extractPortFromUrl` method in `ServiceApplication` to extract port from FQDN URLs.
- Updated frontend views to display warnings when required ports are missing from domains.
- Created unit tests for service port validation and extraction logic, ensuring correct behavior for various scenarios.
- Added feature tests for Livewire component handling of domain submissions with required ports.
- Fix SPA toggle not triggering nginx configuration regeneration by capturing old value before syncData
- Fix similar issue with is_http_basic_auth_enabled using value comparison instead of isDirty
- Remove redundant application settings save() call
- Add confirmation modal to nginx generation button to prevent accidental overwrites
- Pass correct type parameter (spa/static) to generateNginxConfiguration method
- Changed `$cast` to `$casts` in ApplicationSetting model to enable proper boolean casting for new fields.
- Added boolean fields: `is_spa`, `is_build_server_enabled`, `is_preserve_repository_enabled`, `is_container_label_escape_enabled`, `is_container_label_readonly_enabled`, and `use_build_secrets`.
fix: Update Livewire component to reflect new property names
- Updated references in the Livewire component for the new camelCase property names.
- Adjusted bindings and IDs for consistency with the updated model.
test: Add unit tests for ApplicationSetting boolean casting
- Created tests to verify boolean casting for `is_static` and other boolean fields in ApplicationSetting.
- Ensured all boolean fields are correctly defined in the casts array.
test: Implement tests for SynchronizesModelData trait
- Added tests to verify the functionality of the SynchronizesModelData trait, ensuring it correctly syncs properties between the component and the model.
- Included tests for handling non-existent properties gracefully.
- Add null check before updating OAuth settings to prevent calling methods on null
- Apply couldBeEnabled() validation for all settings in bulk update (not just instant save)
- Disable OAuth providers that fail validation and collect error messages
- Surface all validation errors to the user instead of silently failing
- Update oauth_settings_map with fresh data after saving each setting
This ensures bulk updates follow the same validation logic as instant-save paths
and prevents bypassing model validation by directly calling update.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit fixes two related issues preventing the Monaco editor from displaying Docker Compose file content:
1. Data Sync Issue:
- After loadComposeFile() fetches the compose content from Git and updates the database model, the Livewire component properties were never synced
- Monaco editor binds to component properties via wire:model, so it remained empty
- Fixed by calling syncFromModel() after refresh() in loadComposeFile() method
2. Script Duplication Issue:
- Multiple Monaco editors on the same page (compose files, dockerfile, labels) caused race condition
- Each instance tried to inject the Monaco loader script simultaneously
- Resulted in "SyntaxError: Identifier '_amdLoaderGlobal' has already been declared"
- Fixed by adding a global flag to prevent duplicate script injection
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed multiple issues with GitHub App source creation and management:
1. **Fixed null property assignment error on component mount**
- Changed property types to nullable in Change component (appId, installationId, clientId, etc.)
- Updated validation rules to allow nullable values
- Allows mounting component with newly created GitHub Apps that don't have these fields set yet
2. **Fixed Livewire morphing error on manual creation**
- Modified createGithubAppManually() to redirect after saving
- Prevents "Cannot read properties of null" error when view structure changes
- Fields now properly populated after manual creation without requiring page refresh
3. **Fixed is_system_wide not being saved on creation**
- Removed backwards logic that only saved is_system_wide on cloud instances
- Added is_system_wide to GithubApp model casts for proper boolean handling
- System-wide checkbox now works correctly on self-hosted instances
4. **Fixed misleading preview deployment checkbox**
- Removed instantSave attribute from permission checkboxes in unconfigured state
- These are configuration options for GitHub App creation, not database fields
- Prevents "GitHub App updated" success message when nothing was actually saved
5. **Added validation for Refetch Permissions button**
- Validates App ID and Private Key are set before attempting to fetch
- Shows clear error messages: "Cannot fetch permissions. Please set the following required fields first: App ID, Private Key"
- Prevents crash when private key is null or invalid
6. **Better error handling for unsupported private key formats**
- Detects OpenSSH format keys vs RSA PEM format
- Shows helpful message: "Please use an RSA private key in PEM format (BEGIN RSA PRIVATE KEY). OpenSSH format keys are not supported."
- GitHub Apps require RSA PEM format, not OpenSSH format
7. **Made GitHub App view mobile responsive**
- Updated all flex layouts to stack vertically on mobile (flex-col sm:flex-row)
- Form fields, buttons, and sections now properly responsive
- No more cut-off fields on small screens
Added comprehensive test coverage:
- GithubSourceChangeTest.php with 7 tests
- GithubSourceCreateTest.php with 6 tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The loading icon was appearing during automatic background status checks
(every 10 seconds) even when users didn't click anything, which caused
confusion and made it seem like something was running unexpectedly.
Changes:
- Added manualCheckStatus() method to Application, Database, and Service
Heading components that wraps the checkStatus() call
- Updated status component buttons to call manualCheckStatus() instead
of checkStatus()
- Added wire:target="manualCheckStatus" to loading directives so the
loading icon only appears when users explicitly click the refresh button
- Added delay.shortest to prevent flickering on fast operations
The automatic wire:poll.10000ms="checkStatus" now runs silently in the
background without showing the loading icon, while manual refreshes
still provide visual feedback to the user.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit improves the visual presentation of service, application, and database logos on the new resource selection page:
- Remove grayscale filter: Logos now display in their original colors by default instead of being greyed out
- Dark mode support for SVGs: Updated SVG logos to use `fill="currentColor"` and added `text-black dark:text-white` wrapper for proper light/dark theme adaptation
- Consistent aspect ratios: Removed `aspect-square` and added `object-contain` to preserve original logo proportions
- Uniform sizing: Implemented fixed-size container (4.5rem × 4.5rem) with centered logo positioning to ensure all logos appear at consistent sizes regardless of intrinsic dimensions
- Improved mobile UX: Adjusted sticky search bar positioning from `top-10` to `top-20` to prevent navbar overlap
Files modified:
- resources/views/livewire/project/new/select.blade.php
- resources/views/components/resource-view.blade.php
- app/Livewire/Project/New/Select.php
- public/svgs/*.svg (12 SVG files updated with currentColor)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add a searchable category dropdown filter on the new resource page to help users filter services by category.
Features:
- Category dropdown positioned next to search input
- Auto-focus on search field when dropdown opens
- Case-insensitive category filtering
- Proper acronym formatting (AI, API, CI, etc. displayed in uppercase)
- Loading/disabled state while categories are being fetched
- Category search/filter within dropdown
- Alphabetical sorting (case-insensitive)
Backend changes:
- Extract unique categories from service templates
- Handle comma-separated categories
- Format common acronyms to uppercase
- Case-insensitive natural sorting
Frontend changes:
- Searchable dropdown component with Alpine.js
- Category filter integration with existing search
- Disabled state placeholder during loading
- Auto-focus behavior for better UX
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add ownedAndOnlySShKeys() method to filter out git-related keys
- Update Boarding component to use new filtering method
- Enhance datalist component with better multi-select and single-select handling
- Fix Alpine.js reactivity and improve UI interactions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When creating a Hetzner server from the onboarding view, the redirect
to the server details page was not working properly due to modal context.
The standard redirect() call doesn't handle navigation from within modals.
Changes:
- Add from_onboarding flag to ByHetzner component
- Use wire:navigate redirect when in onboarding mode
- Pass from_onboarding=true from boarding view
This ensures proper navigation to the newly created server page instead
of staying on the onboarding view.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When a docker compose service has multiple comma-separated domains, the
generate() method was only processing the first domain and truncating the rest.
The issue was that Url::fromString() can't parse comma-separated URLs - it only
parses the first one.
Fixed by:
1. Splitting comma-separated domains with explode(',', $domain_string)
2. Processing each domain individually in a foreach loop
3. Generating preview URLs for each domain using the same template/random/pr_id
4. Joining the results back with implode(',', $preview_fqdns)
This ensures all domains get properly transformed for preview deployments.
Example:
- Original: http://domain1.com,http://domain2.com
- Preview: http://57.domain1.com,http://57.domain2.com
- Before fix: http://57.domain1.com,http (truncated)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed three potential fatal errors where json_decode could return null:
1. save() method (lines 39-41): Added null coalescing to default to empty array,
and ensure service entry exists before writing domain
2. generate() method (line 56): Changed to use assoc flag consistently and
fallback to empty array
3. generate() method (lines 95-97): Same fix as save() - null coalescing and
service entry initialization
All json_decode calls now consistently:
- Use the assoc flag to return arrays (not objects)
- Fall back to empty array with ?: []
- Initialize service entry with ?? [] before writing
This prevents "Attempt to modify property of null" fatal errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Resolved merge conflicts between Livewire model binding refactoring and UI/CSS updates from next branch. Key integrations:
- Preserved unique HTML ID generation for form components
- Maintained wire:model bindings using $modelBinding
- Integrated new wire:dirty.class styles (border-l-warning pattern)
- Kept both syncData(true) and validateDockerComposeForInjection in StackForm
- Merged security tests and helper improvements from next
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses a critical security issue where malicious Docker Compose
data was being saved to the database before validation occurred.
Problem:
- Service models were saved to database first
- Validation ran afterwards during parse()
- Malicious data persisted even when validation failed
- User saw error but damage was already done
Solution:
1. Created validateDockerComposeForInjection() to validate YAML before save
2. Added pre-save validation to all Service creation/update points:
- Livewire: DockerCompose.php, StackForm.php
- API: ServicesController.php (create, update, one-click)
3. Validates service names and volume paths (string + array formats)
4. Blocks shell metacharacters: backticks, $(), |, ;, &, >, <, newlines
Security fixes:
- Volume source paths (string format) - validated before save
- Volume source paths (array format) - validated before save
- Service names - validated before save
- Environment variable patterns - safe ${VAR} allowed, ${VAR:-$(cmd)} blocked
Testing:
- 60 security tests pass (176 assertions)
- PreSaveValidationTest.php: 15 tests for pre-save validation
- ValidateShellSafePathTest.php: 15 tests for core validation
- VolumeSecurityTest.php: 15 tests for volume parsing
- ServiceNameSecurityTest.php: 15 tests for service names
Related commits:
- Previous: Added validation during parse() phase
- This commit: Moves validation before database save
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses a critical security vulnerability where low-privileged
users (members) could invite high-privileged users (admins/owners) to teams,
allowing them to escalate their own privileges through password reset.
Root Causes Fixed:
1. TeamPolicy authorization checks were commented out, allowing all team
members to manage invitations instead of just admins/owners
2. Missing role elevation checks in InviteLink component allowed members
to invite users with higher privileges
Security Fixes:
1. app/Policies/TeamPolicy.php
- Uncommented and enforced authorization checks for:
* update() - Only admins/owners can update team settings
* delete() - Only admins/owners can delete teams
* manageMembers() - Only admins/owners can manage team members
* viewAdmin() - Only admins/owners can view admin panel
* manageInvitations() - Only admins/owners can manage invitations
2. app/Livewire/Team/InviteLink.php
- Added explicit role elevation checks to prevent:
* Members from inviting admins or owners
* Admins from inviting owners (defense-in-depth)
- Validates that inviter has sufficient privileges for target role
Test Coverage:
1. tests/Feature/TeamPolicyTest.php
- 24 comprehensive tests covering all policy methods
- Tests for owner, admin, member, and non-member access
- Specific tests for the privilege escalation vulnerability
2. tests/Feature/TeamInvitationPrivilegeEscalationTest.php
- 11 tests covering all role elevation scenarios
- Tests member → admin/owner escalation (blocked)
- Tests admin → owner escalation (blocked)
- Tests valid invitation paths for each role
Impact:
- Prevents privilege escalation attacks
- Protects all Coolify instances from unauthorized access
- Enforces proper role hierarchy in team management
References:
- Identified by Aikido AI whitebox pentest service
- CVE: Pending assignment
- Severity: Critical
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed three issues preventing the "new image" quick action from working:
1. Frontend matching logic wasn't checking the quickcommand field
- Added check for item.quickcommand in the matching logic
- Now "new image" matches docker-image via its quickcommand "(type: new image)"
2. Search query remained populated after triggering selection flow
- Clear searchQuery in navigateToResourceCreation() to show selection UI
- This switches the UI from creatable items list to server selection
3. Redirect wasn't using Livewire's redirect method
- Changed from redirect()->route() to $this->redirect(route())
- Ensures proper Livewire component redirect behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace manual regex parsing with DockerImageParser in ApplicationsController
- Fix double-decoration bug where image names like nginx@sha256:hash would
become nginx:hash@sha256 causing malformed references
- Add auto-parse feature in Livewire DockerImage component
- Users can now paste complete references like nginx:stable@sha256:abc123...
and fields auto-populate
- Update UI placeholder with examples: nginx, docker.io/nginx:latest,
ghcr.io/user/app:v1.2.3, nginx:stable@sha256:abc123...
- Add comprehensive unit tests for auto-parse functionality
- All tests passing (20 tests, 73 assertions)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The code was computing $imageTag with the 'sha256-' prefix for digest-based
images but then using $parser->getTag() directly when creating the Application,
which bypassed the prefix logic entirely.
This fix ensures that digest-based Docker images preserve their 'sha256-' prefix
by using the computed $imageTag variable instead of calling $parser->getTag()
directly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added authorization checks to 11 database-related Livewire components
that were loading sensitive database configuration without verifying
user permissions.
Changes:
- Added authorize('view', $database) to all 8 database type General.php mount() methods
- Added authorization to Configuration.php before loading database
- Added authorization to BackupEdit.php before loading backup config
- Added authorization to Import.php before loading database resource
This prevents unauthorized users from accessing database credentials,
connection strings, and configuration details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Nullable server + guard to avoid TypeError/NPE. Don't terminate the app, terminate the bug.
Changes:
- Made Server property nullable (?Server $server = null) in all 8 database General components
- Added guard clause in mount() to check for null server before accessing it
- Displays user-friendly error message when destination server is not configured
- Prevents crashes in methods like isLogDrainEnabled() and sslCertificates()
Fixed components:
- Mariadb, Dragonfly, Clickhouse, Keydb
- Mysql, Mongodb, Redis, Postgresql
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses two critical issues with Docker Compose service management:
## Issue 1: Duplicate Services Created on Image Change
When changing the image in a docker-compose file, the parser was creating new
ServiceApplication/ServiceDatabase records instead of updating existing ones.
**Root Cause**: The parsers used `firstOrCreate()` with `['name', 'image', 'service_id']`,
meaning any image change would create a new record.
**Fix**: Remove `image` from `firstOrCreate()` queries and update it separately after
finding or creating the service record.
**Changes**:
- `bootstrap/helpers/parsers.php` (serviceParser v3): Fixed in presave loop (lines 1188-1203)
and main parsing loop (lines 1519-1539)
- `bootstrap/helpers/shared.php` (parseDockerComposeFile v2): Fixed null check logic
(lines 1308-1348)
## Issue 2: UI Not Refreshing After Changes
When compose file or domain was modified, the Configuration component wasn't receiving
events to refresh its data, requiring manual page refresh to see updates.
**Root Cause**: The Configuration component wasn't listening for refresh events dispatched
by child components (StackForm, EditDomain).
**Fix**: Add event listeners and dispatchers to enable real-time UI updates.
**Changes**:
- `app/Livewire/Project/Service/Configuration.php`: Added listeners for `refreshServices`
and `refresh` events (lines 36-37)
- `app/Livewire/Project/Service/EditDomain.php`: Added `refreshServices` dispatch (line 76)
- Note: `app/Livewire/Project/Service/StackForm.php` already had the dispatch
## Tests Added
- `tests/Unit/ServiceParserImageUpdateTest.php`: 4 tests verifying no duplicates created
- `tests/Unit/ServiceConfigurationRefreshTest.php`: 4 tests verifying event dispatching
All 8 new tests pass, and all existing unit tests continue to pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove global 'refresh' event listeners from all database General components
- Migrate Redis, MySQL, MariaDB, MongoDB, PostgreSQL, and KeyDB components to use explicit public properties instead of wire:model="database.field"
- Implement syncData() method in each component for manual data synchronization between properties and Eloquent models
- Update all validation rules, messages, and attributes to reference new property names
- Update Blade views to bind inputs to explicit properties (e.g., id="name" instead of id="database.name")
- Prepare codebase for disabling Livewire's legacy_model_binding configuration option
This refactoring resolves form field reset issues caused by global refresh events
and follows Livewire 3 best practices for component property management.
- Auto-select first SSH key when available instead of requiring explicit selection
- Remove disabled placeholder option from dropdown
- Prevents confusing error when user clicks "Use Selected Key" without changing dropdown
- Improves onboarding flow by having a sensible default selection
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add Hetzner Cloud server creation option to onboarding flow
- Change grid from 2 to 3 columns to accommodate all server options
- Mark both Hetzner and Remote Server as "Recommended"
- Fix Hetzner card height to match other cards
- Remove "select existing server" phase - onboarding always creates new servers
- Fix project loading on page refresh in Project Setup phase
- Fix browser back button navigation - remove aggressive restartBoarding() call
- Fix SSH key dropdown to not auto-select first key - require explicit selection
- Make checkpoint titles more prominent across all phases
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add centered, card-based layout with clean design
- Implement 3-step progress indicator component
- Add proper dark/light mode support following Coolify design system
- Implement Livewire URL state persistence for browser navigation
- Separate private key textareas for "Generate" vs "Add your own" modes
- Consistent checkpoint styling across all onboarding phases
- Enhanced typography with prominent titles (semibold, white in dark mode)
- Fixed state restoration on page refresh and browser back/forward navigation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhanced the terminal server/container selection with a new searchable datalist component:
**Terminal View Changes:**
- Replaced `x-forms.select` with `x-forms.datalist` for server/container selection
- Added search functionality for filtering servers and containers
- Fixed form validation by adding hidden input for proper HTML5 validation
- Prevented error messages when clearing selection (sets to 'default')
**Datalist Component (Single Selection):**
- Implemented Alpine.js-powered dropdown with search functionality
- Added visual dropdown arrow that rotates when opened
- Proper entangle binding for wire:model support
- Keyboard support (Escape to close)
- Click outside to close behavior
- Disabled options filtering (skips disabled options)
- Consistent styling with input/textarea components
**Styling Improvements:**
- Explicit background colors: `bg-white` (light) and `dark:bg-coolgray-100` (dark)
- Proper ring border: `ring-1 ring-inset ring-neutral-200 dark:ring-coolgray-300`
- Focus states: `focus-within:ring-2 focus-within:ring-coollabs dark:focus-within:ring-warning`
- Text colors: `text-black dark:text-white`
- Added custom scrollbar styling for dropdown lists
- Wire:dirty state support for visual feedback
- Proper padding and spacing (`py-1.5`, `px-1`, `px-2`)
**Multiple Selection Mode:**
- Also updated for consistent styling and scrollbar support
- Added proper background colors and focus states
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add a 'Clear' button next to the cloud-init script dropdown that:
- Resets the dropdown to default (placeholder option)
- Clears the cloud-init script textarea
- Clears the script name input
- Unchecks the 'save script' checkbox
Improves UX by allowing users to quickly reset cloud-init fields
without manually clearing each field.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove debug sleep(4) statement from openSearchModal method.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add manual cache clearing command (search:clear) for testing
- Integrate cloud-init scripts into global search navigation
- Improve form UX by preventing field reset during edit operations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add cloud-init scripts to the global search navigation routes, making
them discoverable via the quick search (Cmd+K / Ctrl+K).
Changes:
- Added dedicated "Cloud-Init Scripts" navigation entry
- Searchable via: cloud-init, scripts, cloud init, cloudinit,
initialization, startup, server setup
- Updated Security entry to include cloud-init in search terms
- Links to /security/cloud-init-scripts route
Users can now quickly navigate to cloud-init script management by
typing "cloud-init" or related terms in global search.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fix multiple UI/UX issues with cloud-init scripts management:
1. Fix card styling - Remove purple box background, use simple border
- Changed from .box class to inline flex/border styling
- Matches cloud provider tokens styling pattern
2. Remove script preview section
- Preview was taking too much space and looked cluttered
- Users can edit to see full script content
3. Make edit modal full width
- Added fullWidth attribute to x-modal-input component
- Provides better editing experience for long scripts
4. Fix fields clearing after update
- Fields were being reset even in edit mode
- Now only reset fields when creating new script
- Edit mode preserves values after save
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive cloud-init script management interface in the Security
section, allowing users to create, edit, delete, and reuse cloud-init
scripts across their team.
New Components:
- CloudInitScripts: Main listing page with grid view of scripts
- CloudInitScriptForm: Modal form for create/edit operations
Features:
- Create new cloud-init scripts with name and content
- Edit existing scripts
- Delete scripts with confirmation (requires typing script name)
- View script preview (first 200 characters)
- Scripts are encrypted in database
- Full authorization using CloudInitScriptPolicy
- Real-time updates via Livewire events
UI Location:
- Added to Security section nav: /security/cloud-init-scripts
- Positioned between Cloud Tokens and API Tokens
- Follows existing security UI patterns
Files Created:
- app/Livewire/Security/CloudInitScripts.php
- app/Livewire/Security/CloudInitScriptForm.php
- resources/views/livewire/security/cloud-init-scripts.blade.php
- resources/views/livewire/security/cloud-init-script-form.blade.php
Files Modified:
- routes/web.php - Added route
- resources/views/components/security/navbar.blade.php - Added nav link
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add cloud-init script fields to the resetSelection() method that's
called when the modal is closed. This ensures a clean slate when
reopening the "Connect a Hetzner Server" view.
Fields reset:
- cloud_init_script
- save_cloud_init_script
- cloud_init_script_name
- selected_cloud_init_script_id
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
1. Remove description field from cloud-init scripts
- Updated migration to remove description column
- Updated model to remove description from fillable array
2. Redesign script name input layout
- Move script name input next to checkbox (always visible)
- Remove conditional rendering - input always shown
- Use placeholder instead of label for cleaner look
3. Fix dropdown type error
- Replace wire:change event with wire:model.live
- Use updatedSelectedCloudInitScriptId() lifecycle hook
- Add "disabled" attribute to placeholder option
- Properly handle empty string vs null in type casting
4. Improve validation
- Require both script content AND name for saving
- Remove description validation rule
- Add selected_cloud_init_script_id validation
5. Auto-populate name when loading saved script
- When user selects saved script, auto-fill the name field
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit adds the ability to use cloud-init scripts when creating Hetzner servers through the integration. Users can write custom scripts that will be executed during server initialization, and optionally save these scripts at the team level for future reuse.
Key features:
- Textarea field for entering cloud-init scripts (bash or cloud-config YAML)
- Checkbox to save scripts for later use at team level
- Dropdown to load previously saved scripts
- Scripts are encrypted in the database
- Full validation and authorization checks
- Comprehensive unit and feature tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Display the monthly cost on the "Buy & Create Server" button
to give users clear visibility of the price before purchasing.
- Add computed property to calculate selected server's monthly price
- Update button text to show price dynamically (e.g., "€12.99/mo")
- Add tests for price formatting and edge cases
- Price updates reactively when user changes server type
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add support for configuring IPv4 and IPv6 public network interfaces when creating servers through the Hetzner integration. Users can now enable or disable IPv4 and IPv6 independently, with both enabled by default.
Features:
- Added enable_ipv4 and enable_ipv6 checkboxes in the server creation form
- Both options are enabled by default as per Hetzner best practices
- IPv4 is preferred when both are enabled
- Fallback to IPv6 when only IPv6 is enabled
- Proper validation and error handling for network configuration
- Comprehensive test coverage for IP address selection logic
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>